refactor(frontend): migrate MFA dialogs to Modal chrome system (D-3) (#907)

Replace raw Dialog/AlertDialog scaffolding in MfaEnrollDialog,
MfaDisableDialog, and MfaBackupCodesDialog with the shared Modal*
primitives introduced in D-1. Extract the duplicated BackupCodeTicket
component into a shared file consumed by both enroll and regen flows.

Fix a CSS grid auto-column blowout in Modal: the dialog's implicit auto
column was sizing to the step rail's min-content (492px), overflowing
the 448px max-w-md constraint and clipping right-side content. Adding
grid-cols-1 to the DialogContent override forces the column to use
minmax(0, 1fr), preventing any grid item from expanding the track past
available space. Also add min-w-0 to the TOTP secret code element so
its long monospaced string can truncate rather than drive column sizing.

Kicker prefixes updated to SECURITY · per the tracker convention.
This commit is contained in:
Anso
2026-05-04 09:08:44 -04:00
committed by GitHub
parent 2a2a7deef1
commit 90ea26f7c7
5 changed files with 285 additions and 345 deletions
@@ -1,13 +1,5 @@
import { useEffect, useRef, useState } from 'react';
import {
AlertDialog,
AlertDialogContent,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogCancel,
} from '@/components/ui/alert-dialog';
import { Modal, ModalDestructiveHeader, ModalBody, ModalFooter } from '@/components/ui/modal';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { toast } from '@/components/ui/toast-store';
@@ -127,76 +119,70 @@ export function MfaDisableDialog({ open, onOpenChange, onDisabled }: MfaDisableD
};
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent className="max-w-md overflow-hidden p-0">
<div className="relative">
<span aria-hidden className="absolute inset-y-0 left-0 w-[3px] bg-destructive/70" />
<AlertDialogHeader className="border-b border-card-border/60 px-6 pt-6 pb-4 text-left">
<div className="font-mono text-[10px] uppercase tracking-[0.22em] text-destructive">
SENCHO · MFA · DISABLE
</div>
<AlertDialogTitle className="mt-1 font-display text-[1.75rem] italic leading-tight text-stat-value">
Turn off two-factor
</AlertDialogTitle>
<AlertDialogDescription className="mt-2 text-sm leading-snug text-stat-subtitle">
Disabling 2FA removes this login layer. Your backup codes become invalid. Confirm with a current code to proceed.
</AlertDialogDescription>
</AlertDialogHeader>
<div className="flex flex-col gap-4 px-6 py-5">
{useBackup ? (
<div className="flex flex-col gap-1.5">
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
Backup code · 10 chars
</span>
<Input
id="mfa-disable-backup"
type="text"
inputMode="text"
autoComplete="one-time-code"
maxLength={BACKUP_CODE_DISPLAY_LENGTH}
value={display}
onChange={(e) => handleBackupChange(e.target.value)}
placeholder="ABCDE-FGHIJ"
className="h-12 bg-background/60 border-card-border text-center font-mono text-lg tabular-nums tracking-[0.3em] shadow-[inset_0_2px_4px_0_oklch(0_0_0/0.25)] focus-visible:border-brand/60 focus-visible:ring-2 focus-visible:ring-brand/40"
/>
</div>
) : (
<OtpDigitField
id="mfa-disable-code"
value={display}
onChange={handleOtpChange}
state={otpState}
disabled={loading || otpState === 'success'}
autoFocus
/>
)}
<button
type="button"
onClick={handleToggleBackup}
className="self-start font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle transition-colors hover:text-brand"
>
{useBackup ? '[ Use authenticator ]' : '[ Use backup code ]'}
</button>
{error && <ErrorRail>{error}</ErrorRail>}
<Modal size="md" open={open} onOpenChange={onOpenChange}>
<ModalDestructiveHeader
kicker="SECURITY · MFA · DISABLE"
title="Turn off two-factor"
description="Disabling 2FA removes this login layer. Your backup codes become invalid."
/>
<ModalBody>
<p className="text-sm leading-snug text-stat-subtitle">
Disabling 2FA removes this login layer. Your backup codes become invalid.
Confirm with a current code to proceed.
</p>
{useBackup ? (
<div className="flex flex-col gap-1.5">
<span className="font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle">
Backup code · 10 chars
</span>
<Input
id="mfa-disable-backup"
type="text"
inputMode="text"
autoComplete="one-time-code"
maxLength={BACKUP_CODE_DISPLAY_LENGTH}
value={display}
onChange={(e) => handleBackupChange(e.target.value)}
placeholder="ABCDE-FGHIJ"
className="h-12 bg-background/60 border-card-border text-center font-mono text-lg tabular-nums tracking-[0.3em] shadow-[inset_0_2px_4px_0_oklch(0_0_0/0.25)] focus-visible:border-brand/60 focus-visible:ring-2 focus-visible:ring-brand/40"
/>
</div>
<AlertDialogFooter className="border-t border-card-border/60 px-6 py-4">
<AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
<Button
type="button"
variant="destructive"
disabled={loading || raw.length !== expectedLength}
onClick={handleDisableClick}
>
{loading ? 'Disabling...' : 'Disable'}
</Button>
</AlertDialogFooter>
</div>
</AlertDialogContent>
</AlertDialog>
) : (
<OtpDigitField
id="mfa-disable-code"
value={display}
onChange={handleOtpChange}
state={otpState}
disabled={loading || otpState === 'success'}
autoFocus
/>
)}
<button
type="button"
onClick={handleToggleBackup}
className="self-start font-mono text-[10px] uppercase tracking-[0.18em] text-stat-subtitle transition-colors hover:text-brand"
>
{useBackup ? '[ Use authenticator ]' : '[ Use backup code ]'}
</button>
{error && <ErrorRail>{error}</ErrorRail>}
</ModalBody>
<ModalFooter
secondary={
<Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={loading}>
Cancel
</Button>
}
primary={
<Button
type="button"
variant="destructive"
disabled={loading || raw.length !== expectedLength}
onClick={handleDisableClick}
>
{loading ? 'Disabling...' : 'Disable'}
</Button>
}
/>
</Modal>
);
}