refactor: address PR feedback for security and cleanups

- refactor: migrate dangerouslySetInnerHTML(t.raw) to t.rich() to prevent HTML injection vectors
- fix: restore per-status wording for flow activation toasts ("Flow activated.", "Archived.", "Saved as draft.")
- chore: remove unused `tComposer` in message-thread.tsx
- chore: remove unused `ROLE_DESCRIPTIONS` in invite-member-dialog.tsx
This commit is contained in:
Ivan Mercedes 2026-07-08 07:58:33 -04:00
parent de75715cff
commit 4690c4d929
7 changed files with 18 additions and 13 deletions

View File

@ -1144,7 +1144,9 @@
"editorState": { "editorState": {
"saved": "Saved.", "saved": "Saved.",
"fixIssues": "Fix the issues below before activating.", "fixIssues": "Fix the issues below before activating.",
"statusChanged": "Flow is now {status}." "statusActivated": "Flow activated.",
"statusArchived": "Archived.",
"statusDraft": "Saved as draft."
}, },
"summary": { "summary": {
"options": "{count} {count, plural, =1 {option} other {options}}", "options": "{count} {count, plural, =1 {option} other {options}}",

View File

@ -174,7 +174,9 @@ export function FlowBuilder() {
</div> </div>
{state.nodes.length === 0 ? ( {state.nodes.length === 0 ? (
<div className="border-border bg-card/50 text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm" dangerouslySetInnerHTML={{ __html: t.raw('nodesEmpty') }} /> <div className="border-border bg-card/50 text-muted-foreground rounded-lg border border-dashed p-8 text-center text-sm">
{t.rich('nodesEmpty', { strong: (chunks) => <strong>{chunks}</strong> })}
</div>
) : ( ) : (
state.nodes.map((node) => ( state.nodes.map((node) => (
<NodeCard <NodeCard

View File

@ -383,7 +383,13 @@ export function FlowEditorProvider({
throw new Error(json.error ?? `Status update failed: ${res.status}`); throw new Error(json.error ?? `Status update failed: ${res.status}`);
} }
setStateRaw((s) => ({ ...s, status: next })); setStateRaw((s) => ({ ...s, status: next }));
toast.success(t("statusChanged", { status: next })); toast.success(
next === "active"
? t("statusActivated")
: next === "archived"
? t("statusArchived")
: t("statusDraft")
);
} catch (err) { } catch (err) {
const msg = err instanceof Error ? err.message : "Status update failed"; const msg = err instanceof Error ? err.message : "Status update failed";
toast.error(msg); toast.error(msg);

View File

@ -168,7 +168,6 @@ export function MessageThread({
}: MessageThreadProps) { }: MessageThreadProps) {
const t = useTranslations("Inbox.messageThread"); const t = useTranslations("Inbox.messageThread");
const tTimer = useTranslations("Inbox.sessionTimer"); const tTimer = useTranslations("Inbox.sessionTimer");
const tComposer = useTranslations("Inbox.composer");
const tQuote = useTranslations("Inbox.replyQuote"); const tQuote = useTranslations("Inbox.replyQuote");
const { user } = useAuth(); const { user } = useAuth();

View File

@ -55,12 +55,6 @@ const EXPIRY_OPTIONS = [
{ value: '30', labelKey: 'days30' }, { value: '30', labelKey: 'days30' },
]; ];
const ROLE_DESCRIPTIONS = [
'adminHint',
'agentHint',
'viewerHint',
];
// Server caps label at 80 chars (see src/app/api/account/invitations/route.ts). // Server caps label at 80 chars (see src/app/api/account/invitations/route.ts).
// Mirror it on the client so we short-circuit before the round-trip // Mirror it on the client so we short-circuit before the round-trip
// rather than letting the user submit and bounce off a 400. // rather than letting the user submit and bounce off a 400.

View File

@ -503,7 +503,9 @@ export function MembersTab() {
<p className="mt-2 text-sm text-muted-foreground"> <p className="mt-2 text-sm text-muted-foreground">
{t('noPendingTitle')} {t('noPendingTitle')}
</p> </p>
<p className="mt-1 text-xs text-muted-foreground" dangerouslySetInnerHTML={{ __html: t.raw('noPendingDesc') }} /> <p className="mt-1 text-xs text-muted-foreground">
{t.rich('noPendingDesc', { bold: (chunks) => <strong>{chunks}</strong> })}
</p>
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (

View File

@ -653,7 +653,7 @@ export function TemplateManager() {
{form.category === 'Authentication' && ( {form.category === 'Authentication' && (
<div className="flex items-start gap-2 rounded border border-amber-700/40 bg-amber-950/30 px-3 py-2 text-xs text-amber-300"> <div className="flex items-start gap-2 rounded border border-amber-700/40 bg-amber-950/30 px-3 py-2 text-xs text-amber-300">
<AlertCircle className="size-4 mt-0.5 shrink-0" /> <AlertCircle className="size-4 mt-0.5 shrink-0" />
<p dangerouslySetInnerHTML={{ __html: t.raw('authWarning') }} /> <p>{t.rich('authWarning', { bold: (chunks) => <strong>{chunks}</strong> })}</p>
</div> </div>
)} )}
@ -724,7 +724,7 @@ export function TemplateManager() {
{editingId ? ( {editingId ? (
t('langFixed') t('langFixed')
) : ( ) : (
<span dangerouslySetInnerHTML={{ __html: t.raw('langHint') }} /> <span>{t.rich('langHint', { code: (chunks) => <code>{chunks}</code> })}</span>
)} )}
</p> </p>
</div> </div>