Skip to content

Commit 8bceaff

Browse files
committed
refactor(mfa): Simplify method icon and label handling in SetupMfaStartScreen; optimize phone number retrieval in SmsCodeFlow; improve step management in wizard
1 parent 364549f commit 8bceaff

File tree

3 files changed

+55
-39
lines changed

3 files changed

+55
-39
lines changed

packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SetupMfaStartScreen.tsx

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Actions } from '@/elements/Actions';
44
import { useCardState } from '@/elements/contexts';
55
import { PreviewButton } from '@/elements/PreviewButton';
66
import { AuthApp, Mobile } from '@/icons';
7-
import { Col, descriptors, Flex, Icon, localizationKeys, Text } from '@/ui/customizables';
7+
import { descriptors, Flex, Icon, localizationKeys, Text, type LocalizationKey } from '@/ui/customizables';
88
import { Card } from '@/ui/elements/Card';
99
import { Header } from '@/ui/elements/Header';
1010

@@ -16,18 +16,15 @@ type SetupMfaStartScreenProps = {
1616
goToStep: (step: number) => void;
1717
};
1818

19-
const getMethodIconAndLabel = (method: VerificationStrategy) => {
20-
switch (method) {
21-
case 'totp':
22-
return { icon: <Icon icon={AuthApp} />, label: localizationKeys('taskSetupMfa.start.methodSelection.totp') };
23-
case 'phone_code':
24-
return {
25-
icon: <Icon icon={Mobile} />,
26-
label: localizationKeys('taskSetupMfa.start.methodSelection.phoneCode'),
27-
};
28-
default:
29-
return { icon: null, label: null };
30-
}
19+
const METHOD_CONFIG: Record<'totp' | 'phone_code', { icon: JSX.Element; label: LocalizationKey }> = {
20+
totp: {
21+
icon: <Icon icon={AuthApp} />,
22+
label: localizationKeys('taskSetupMfa.start.methodSelection.totp'),
23+
},
24+
phone_code: {
25+
icon: <Icon icon={Mobile} />,
26+
label: localizationKeys('taskSetupMfa.start.methodSelection.phoneCode'),
27+
},
3128
};
3229

3330
export const SetupMfaStartScreen = (props: SetupMfaStartScreenProps) => {
@@ -59,9 +56,9 @@ export const SetupMfaStartScreen = (props: SetupMfaStartScreenProps) => {
5956
})}
6057
>
6158
{availableMethods.map(method => {
62-
const { icon, label } = getMethodIconAndLabel(method);
59+
const methodConfig = METHOD_CONFIG[method] ?? null;
6360

64-
if (!icon || !label) {
61+
if (!methodConfig) {
6562
return null;
6663
}
6764

@@ -86,11 +83,11 @@ export const SetupMfaStartScreen = (props: SetupMfaStartScreenProps) => {
8683
backgroundColor: t.colors.$neutralAlpha50,
8784
})}
8885
>
89-
{icon}
86+
{methodConfig.icon}
9087
</Flex>
9188
<Text
9289
variant='buttonLarge'
93-
localizationKey={label}
90+
localizationKey={methodConfig.label}
9491
/>
9592
</Flex>
9693
</PreviewButton>

packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/SmsCodeFlowScreen.tsx

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useReverification, useUser } from '@clerk/shared/react';
22
import type { PhoneNumberResource, UserResource } from '@clerk/shared/types';
3-
import React, { useRef } from 'react';
3+
import React, { useMemo, useRef } from 'react';
44

55
import { useWizard, Wizard } from '@/common';
66
import { MfaBackupCodeList } from '@/components/UserProfile/MfaBackupCodeList';
@@ -27,6 +27,10 @@ type MFAVerifyPhoneForSessionTasksProps = {
2727
onReset: () => void;
2828
};
2929

30+
export const getAvailablePhonesFromUser = (user: UserResource | undefined | null) => {
31+
return user?.phoneNumbers.filter(p => !p.reservedForSecondFactor) || [];
32+
};
33+
3034
const MFAVerifyPhoneForSessionTasks = (props: MFAVerifyPhoneForSessionTasksProps) => {
3135
const { onSuccess, resourceRef, onReset } = props;
3236
const card = useCardState();
@@ -257,7 +261,7 @@ const SmsCodeScreen = (props: SmsCodeScreenProps) => {
257261
return null;
258262
}
259263

260-
const availablePhones = user.phoneNumbers.filter(p => !p.reservedForSecondFactor);
264+
const availablePhones = getAvailablePhonesFromUser(user);
261265

262266
return (
263267
<Flow.Part part='phoneCode'>
@@ -267,14 +271,15 @@ const SmsCodeScreen = (props: SmsCodeScreenProps) => {
267271
badgeText={localizationKeys('taskSetupMfa.badge')}
268272
sx={t => ({
269273
paddingTop: t.space.$8,
270-
paddingLeft: t.space.$8,
271-
paddingRight: t.space.$8,
274+
paddingInline: t.space.$8,
272275
})}
273276
>
274277
<Header.Title localizationKey={localizationKeys('taskSetupMfa.smsCode.title')} />
275278
<Header.Subtitle localizationKey={localizationKeys('taskSetupMfa.smsCode.subtitle')} />
276279
</Header.Root>
277-
<Card.Alert>{card.error}</Card.Alert>
280+
<Flex sx={t => ({ paddingInline: t.space.$8 })}>
281+
<Card.Alert>{card.error}</Card.Alert>
282+
</Flex>
278283
<Col>
279284
<Actions
280285
role='menu'
@@ -342,13 +347,28 @@ type SmsCodeFlowProps = {
342347
goToStartStep: () => void;
343348
};
344349

350+
// This is the order of the steps in the wizard
351+
const STEPS = {
352+
ADD_PHONE: 0,
353+
VERIFY_PHONE: 1,
354+
SELECT_PHONE: 2,
355+
SUCCESS: 3,
356+
} as const;
357+
345358
export const SmsCodeFlow = (props: SmsCodeFlowProps) => {
346359
const { onSuccess, goToStartStep } = props;
347360
const { user } = useUser();
348361

349362
const ref = useRef<PhoneNumberResource>();
350-
const availablePhones = user?.phoneNumbers.filter(p => !p.reservedForSecondFactor) || [];
351-
const wizard = useWizard({ defaultStep: availablePhones.length > 0 ? 2 : 0 });
363+
364+
const availablePhones = useMemo(() => {
365+
if (!user) {
366+
return [];
367+
}
368+
return getAvailablePhonesFromUser(user);
369+
}, [user]);
370+
371+
const wizard = useWizard({ defaultStep: availablePhones.length > 0 ? STEPS.SELECT_PHONE : STEPS.ADD_PHONE });
352372

353373
return (
354374
<Card.Root>
@@ -360,24 +380,24 @@ export const SmsCodeFlow = (props: SmsCodeFlowProps) => {
360380
<AddPhoneForSessionTasks
361381
resourceRef={ref}
362382
onSuccess={wizard.nextStep}
363-
onUseExistingNumberClick={() => wizard.goToStep(2)}
364-
onReset={() => (availablePhones.length > 0 ? wizard.goToStep(2) : goToStartStep())}
383+
onUseExistingNumberClick={() => wizard.goToStep(STEPS.SELECT_PHONE)}
384+
onReset={() => (availablePhones.length > 0 ? wizard.goToStep(STEPS.SELECT_PHONE) : goToStartStep())}
365385
/>
366386
{/* Step 1: Verify phone */}
367387
<MFAVerifyPhoneForSessionTasks
368388
resourceRef={ref}
369-
onSuccess={() => wizard.goToStep(3)}
370-
onReset={() => wizard.goToStep(2)}
389+
onSuccess={() => wizard.goToStep(STEPS.SUCCESS)}
390+
onReset={() => wizard.goToStep(STEPS.VERIFY_PHONE)}
371391
/>
372392
{/* Step 2: Phone selection (default if available phones) */}
373393
<SmsCodeScreen
374394
availablePhones={availablePhones}
375395
onSuccess={wizard.nextStep}
376396
onReset={goToStartStep}
377-
onAddPhoneClick={() => wizard.goToStep(0)}
397+
onAddPhoneClick={() => wizard.goToStep(STEPS.ADD_PHONE)}
378398
onUnverifiedPhoneClick={(phone: PhoneNumberResource) => {
379399
ref.current = phone;
380-
wizard.goToStep(1);
400+
wizard.goToStep(STEPS.SELECT_PHONE);
381401
}}
382402
resourceRef={ref}
383403
/>

packages/ui/src/components/SessionTasks/tasks/TaskSetupMfa/TOTPCodeFlowScreen.tsx

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { useClerk, useReverification, useUser } from '@clerk/shared/react';
1+
import { useReverification, useUser } from '@clerk/shared/react';
22
import type { TOTPResource } from '@clerk/shared/types';
33
import React, { useRef } from 'react';
44

55
import { QRCode, useWizard, Wizard } from '@/common';
66
import { MfaBackupCodeList } from '@/components/UserProfile/MfaBackupCodeList';
7-
import { useRouter } from '@/router';
8-
import { Button, Col, descriptors, Flex, localizationKeys, Text } from '@/ui/customizables';
7+
import { Button, Col, descriptors, localizationKeys, Text } from '@/ui/customizables';
98
import { Card } from '@/ui/elements/Card';
109
import { SuccessPage } from '@/ui/elements/SuccessPage';
1110
import { SharedFooterActionForSignOut } from './shared';
@@ -55,7 +54,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
5554
.then(totp => setTOTP(totp))
5655
.catch(err => {
5756
if (isClerkRuntimeError(err) && err.code === 'reverification_cancelled') {
58-
return close();
57+
return onReset();
5958
}
6059
return handleError(err, [], card.setError);
6160
});
@@ -67,7 +66,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
6766
headerTitle={localizationKeys('taskSetupMfa.totpCode.title')}
6867
headerTitleTextVariant='h2'
6968
headerSubtitle={
70-
displayFormat == 'qr'
69+
displayFormat === 'qr'
7170
? localizationKeys('taskSetupMfa.totpCode.addAuthenticatorApp.infoText__ableToScan')
7271
: localizationKeys('taskSetupMfa.totpCode.addAuthenticatorApp.infoText__unableToScan')
7372
}
@@ -78,14 +77,14 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
7877
{totp && (
7978
<>
8079
<Col gap={4}>
81-
{displayFormat == 'qr' && (
80+
{displayFormat === 'qr' && (
8281
<QRCode
8382
justify='center'
8483
url={totp.uri || ''}
8584
/>
8685
)}
8786

88-
{displayFormat == 'uri' && (
87+
{displayFormat === 'uri' && (
8988
<>
9089
<Text
9190
colorScheme='secondary'
@@ -107,7 +106,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
107106
gap: theme.space.$8,
108107
})}
109108
>
110-
{displayFormat == 'qr' && (
109+
{displayFormat === 'qr' && (
111110
<Button
112111
variant='outline'
113112
textVariant='buttonLarge'
@@ -117,7 +116,7 @@ export const AddAuthenticatorApp = withCardStateProvider((props: AddAuthenticato
117116
)}
118117
/>
119118
)}
120-
{displayFormat == 'uri' && (
119+
{displayFormat === 'uri' && (
121120
<Button
122121
variant='outline'
123122
block

0 commit comments

Comments
 (0)