diff --git a/client/scripts/size-baseline.json b/client/scripts/size-baseline.json
index b3e2b7b14..93805aa4d 100644
--- a/client/scripts/size-baseline.json
+++ b/client/scripts/size-baseline.json
@@ -18,7 +18,7 @@
"src/index.css": 1636,
"src/mobile/screens/admin/MAdminPluginsPanel.tsx": 1506,
"src/pages/JourneyDetailPage.test.tsx": 4028,
- "src/pages/LoginPage.tsx": 1347,
+ "src/pages/LoginPage.tsx": 1294,
"src/pages/TripPlannerPage.tsx": 1128,
"src/pages/atlas/useAtlas.ts": 1207,
"src/pages/tripPlanner/useTripPlanner.roadtrip.test.tsx": 3518,
diff --git a/client/src/pages/LoginPage.tsx b/client/src/pages/LoginPage.tsx
index 61703be83..2945ae510 100644
--- a/client/src/pages/LoginPage.tsx
+++ b/client/src/pages/LoginPage.tsx
@@ -4,7 +4,6 @@ import {
EyeOff,
Fingerprint,
Globe,
- KeyRound,
Loader2,
Lock,
Mail,
@@ -18,6 +17,7 @@ import { SUPPORTED_LANGUAGES, useTranslation } from '../i18n';
import { IDP_LOGIN_URL, useLogin } from './login/useLogin';
import LoginWorld from './login/LoginWorld';
import NativeServerLink from './login/NativeServerLink';
+import MfaCodeStep from './login/MfaCodeStep';
import PasswordChecklist from '../components/shared/PasswordChecklist';
/** Fixed so the sky does not reshuffle on every render. */
@@ -739,93 +739,18 @@ export default function LoginPage(): React.ReactElement {
)}
{mode === 'login' && mfaStep && !passwordChangeStep && (
-
-
- {/* The account the code belongs to, for password managers: they match a
- one-time code to a saved login through the username in the same form,
- and the email field is gone in this step. */}
-
-
+ {
+ setMfaStep(false);
+ setMfaToken('');
+ setMfaCode('');
+ setError('');
+ }}
+ />
)}
{/* Username (register only) */}
diff --git a/client/src/pages/login/MfaCodeStep.tsx b/client/src/pages/login/MfaCodeStep.tsx
new file mode 100644
index 000000000..364f21d1d
--- /dev/null
+++ b/client/src/pages/login/MfaCodeStep.tsx
@@ -0,0 +1,99 @@
+import { KeyRound } from 'lucide-react';
+import React from 'react';
+import { useTranslation } from '../../i18n';
+
+interface MfaCodeStepProps {
+ /** The account being signed in, kept in the form for password managers. */
+ email: string;
+ code: string;
+ onCode: (code: string) => void;
+ inputStyle: React.CSSProperties;
+ onBack: () => void;
+}
+
+/** The login's second step: the one-time or backup code, and the way back. */
+export default function MfaCodeStep({ email, code, onCode, inputStyle, onBack }: MfaCodeStepProps) {
+ const { t } = useTranslation();
+ return (
+
+
+ {/* The account the code belongs to, for password managers: they match a
+ one-time code to a saved login through the username in the same form,
+ and the email field is gone in this step. */}
+
+