Skip to content

Commit cb59c75

Browse files
committed
More cleanup
1 parent 58ec5e9 commit cb59c75

4 files changed

Lines changed: 90 additions & 111 deletions

File tree

apps/webapp/app/routes/resources.account.mfa.setup/MfaRecoveryDialog.tsx

Lines changed: 0 additions & 102 deletions
This file was deleted.

apps/webapp/app/routes/resources.account.mfa.setup/MfaSetupDialog.tsx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Form } from "@remix-run/react";
2+
import { DownloadIcon } from "lucide-react";
23
import { QRCodeSVG } from "qrcode.react";
34
import { useState } from "react";
45
import { Button } from "~/components/primitives/Buttons";
56
import { CopyableText } from "~/components/primitives/CopyableText";
7+
import { CopyButton } from "~/components/primitives/CopyButton";
68
import {
79
Dialog,
810
DialogContent,
@@ -20,19 +22,23 @@ interface MfaSetupDialogProps {
2022
secret: string;
2123
otpAuthUrl: string;
2224
};
25+
recoveryCodes?: string[];
2326
error?: string;
2427
isSubmitting: boolean;
2528
onValidate: (code: string) => void;
2629
onCancel: () => void;
30+
onSaveRecoveryCodes: () => void;
2731
}
2832

2933
export function MfaSetupDialog({
3034
isOpen,
3135
setupData,
36+
recoveryCodes,
3237
error,
3338
isSubmitting,
3439
onValidate,
3540
onCancel,
41+
onSaveRecoveryCodes,
3642
}: MfaSetupDialogProps) {
3743
const [totpCode, setTotpCode] = useState("");
3844

@@ -47,6 +53,86 @@ export function MfaSetupDialog({
4753
onCancel();
4854
};
4955

56+
const handleRecoverySubmit = (e: React.FormEvent) => {
57+
e.preventDefault();
58+
onSaveRecoveryCodes();
59+
};
60+
61+
const downloadRecoveryCodes = () => {
62+
if (!recoveryCodes) return;
63+
64+
const content = recoveryCodes.join("\n");
65+
const blob = new Blob([content], { type: "text/plain" });
66+
const url = URL.createObjectURL(blob);
67+
const a = document.createElement("a");
68+
a.href = url;
69+
a.download = "trigger-dev-recovery-codes.txt";
70+
document.body.appendChild(a);
71+
a.click();
72+
document.body.removeChild(a);
73+
URL.revokeObjectURL(url);
74+
};
75+
76+
// Show recovery codes if they exist
77+
if (recoveryCodes && recoveryCodes.length > 0) {
78+
return (
79+
<Dialog open={isOpen}>
80+
<DialogContent showCloseButton={false}>
81+
<DialogHeader>
82+
<DialogTitle>Recovery codes</DialogTitle>
83+
</DialogHeader>
84+
<Form method="post" onSubmit={handleRecoverySubmit}>
85+
<div className="flex flex-col gap-2 pb-0 pt-3">
86+
<Paragraph spacing>
87+
Copy and store these recovery codes carefully in case you lose your device.
88+
</Paragraph>
89+
90+
<div className="flex flex-col gap-6 rounded border border-grid-dimmed bg-background-bright pt-6">
91+
<div className="grid grid-cols-3 gap-2">
92+
{recoveryCodes.map((code, index) => (
93+
<div key={index} className="text-center font-mono text-sm text-text-bright">
94+
{code}
95+
</div>
96+
))}
97+
</div>
98+
<div className="flex items-center justify-end border-t border-grid-bright px-1.5 py-1.5">
99+
<Button
100+
type="button"
101+
variant="minimal/medium"
102+
onClick={downloadRecoveryCodes}
103+
LeadingIcon={DownloadIcon}
104+
>
105+
Download
106+
</Button>
107+
<CopyButton
108+
value={recoveryCodes.join("\n")}
109+
buttonVariant="minimal"
110+
showTooltip={false}
111+
>
112+
Copy
113+
</CopyButton>
114+
</div>
115+
</div>
116+
</div>
117+
118+
<DialogFooter className="justify-end border-t-0">
119+
<Button
120+
type="submit"
121+
variant="primary/medium"
122+
shortcut={{ key: "Enter" }}
123+
hideShortcutKey
124+
autoFocus
125+
>
126+
Continue
127+
</Button>
128+
</DialogFooter>
129+
</Form>
130+
</DialogContent>
131+
</Dialog>
132+
);
133+
}
134+
135+
// Show QR setup if no recovery codes yet
50136
if (!setupData) return null;
51137

52138
return (

apps/webapp/app/routes/resources.account.mfa.setup/route.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { requireUserId } from "~/services/session.server";
77
import { useMfaSetup } from "./useMfaSetup";
88
import { MfaToggle } from "./MfaToggle";
99
import { MfaSetupDialog } from "./MfaSetupDialog";
10-
import { MfaRecoveryDialog } from "./MfaRecoveryDialog";
1110
import { MfaDisableDialog } from "./MfaDisableDialog";
1211

1312
const formSchema = z.discriminatedUnion("action", [
@@ -150,16 +149,12 @@ export function MfaSetup({ isEnabled }: { isEnabled: boolean }) {
150149
<MfaSetupDialog
151150
isOpen={isQrDialogOpen}
152151
setupData={state.setupData}
152+
recoveryCodes={state.recoveryCodes}
153153
error={state.error}
154154
isSubmitting={state.isSubmitting}
155155
onValidate={actions.validateTotp}
156156
onCancel={actions.cancelSetup}
157-
/>
158-
159-
<MfaRecoveryDialog
160-
isOpen={isRecoveryDialogOpen}
161-
recoveryCodes={state.recoveryCodes}
162-
onSave={actions.saveRecoveryCodes}
157+
onSaveRecoveryCodes={actions.saveRecoveryCodes}
163158
/>
164159

165160
<MfaDisableDialog

apps/webapp/app/routes/resources.account.mfa.setup/useMfaSetup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,8 +295,8 @@ export function useMfaSetup(initialIsEnabled: boolean) {
295295
state,
296296
actions,
297297
// Computed properties for easier access
298-
isQrDialogOpen: state.phase === 'enabling' && !!state.setupData,
299-
isRecoveryDialogOpen: state.phase === 'showing-recovery' && !!state.recoveryCodes,
298+
isQrDialogOpen: (state.phase === 'enabling' && !!state.setupData) || (state.phase === 'showing-recovery' && !!state.recoveryCodes),
299+
isRecoveryDialogOpen: false, // Recovery is now handled within the setup dialog
300300
isDisableDialogOpen: state.phase === 'disabling',
301301
};
302302
}

0 commit comments

Comments
 (0)