Skip to content

Commit

Permalink
fix(clerk-js): Improve UX when adding adding phone MFA (#4860)
Browse files Browse the repository at this point in the history
  • Loading branch information
octoper authored Jan 10, 2025
1 parent 813b16d commit 7cc1b3a
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-tables-watch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/clerk-js': minor
---

Improve UX when adding a new phone number as an MFA option
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
import { Plus } from '../../icons';
import { getCountryFromPhoneString, handleError, stringToFormattedPhoneString } from '../../utils';
import { MfaBackupCodeList } from './MfaBackupCodeList';
import { AddPhone, VerifyPhone } from './PhoneForm';
import { AddPhone } from './PhoneForm';
import { VerifyWithCode } from './VerifyWithCode';

type MfaPhoneCodeScreenProps = FormProps;
export const MfaPhoneCodeScreen = withCardStateProvider((props: MfaPhoneCodeScreenProps) => {
Expand All @@ -37,10 +38,10 @@ export const MfaPhoneCodeScreen = withCardStateProvider((props: MfaPhoneCodeScre
onUseExistingNumberClick={() => wizard.goToStep(2)}
onReset={onReset}
/>
<VerifyPhone
<MFAVerifyPhone
title={localizationKeys('userProfile.mfaPhoneCodePage.title')}
resourceRef={ref}
onSuccess={wizard.nextStep}
onSuccess={() => (isInstanceWithBackupCodes ? wizard.goToStep(3) : onSuccess)}
onReset={() => wizard.goToStep(2)}
/>
<AddMfa
Expand All @@ -57,10 +58,14 @@ export const MfaPhoneCodeScreen = withCardStateProvider((props: MfaPhoneCodeScre
{isInstanceWithBackupCodes && (
<SuccessPage
title={localizationKeys('userProfile.mfaPhoneCodePage.successTitle')}
text={[
localizationKeys('userProfile.mfaPhoneCodePage.successMessage1'),
localizationKeys('userProfile.mfaPhoneCodePage.successMessage2'),
]}
text={
ref.current?.backupCodes && ref.current?.backupCodes.length > 0
? [
localizationKeys('userProfile.mfaPhoneCodePage.successMessage1'),
localizationKeys('userProfile.mfaPhoneCodePage.successMessage2'),
]
: [localizationKeys('userProfile.mfaPhoneCodePage.successMessage1')]
}
onFinish={onReset}
contents={<MfaBackupCodeList backupCodes={ref.current?.backupCodes} />}
/>
Expand Down Expand Up @@ -120,6 +125,50 @@ const EnableMFAButtonForPhone = (
);
};

type MFAVerifyPhoneProps = FormProps & {
title: LocalizationKey;
resourceRef: React.MutableRefObject<PhoneNumberResource | undefined>;
};

export const MFAVerifyPhone = (props: MFAVerifyPhoneProps) => {
const { title, onSuccess, resourceRef, onReset } = props;
const card = useCardState();
const phone = resourceRef.current;
const [setReservedForSecondFactor] = useReverification(() => phone?.setReservedForSecondFactor({ reserved: true }));

const enableMfa = async () => {
card.setLoading(phone?.id);
try {
await setReservedForSecondFactor();
resourceRef.current = phone;
onSuccess();
} catch (err) {
handleError(err, [], card.setError);
} finally {
card.setIdle();
}
};

return (
<FormContainer
headerTitle={title}
headerSubtitle={localizationKeys('userProfile.phoneNumberPage.verifySubtitle', {
identifier: resourceRef.current?.phoneNumber,
})}
>
<VerifyWithCode
nextStep={() => {
void enableMfa();
}}
identification={resourceRef.current}
identifier={resourceRef.current?.phoneNumber}
prepareVerification={resourceRef.current?.prepareVerification}
onReset={onReset}
/>
</FormContainer>
);
};

const AddMfa = (props: AddMfaProps) => {
const { onSuccess, onReset, title, onAddPhoneClick, onUnverifiedPhoneClick, resourceRef } = props;
const { user } = useUser();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,14 @@ describe('MfaPage', () => {
});

it('Complete verification with phone_code and autogenerated backup codes', async () => {
const { wrapper, fixtures } = await createFixtures(initConfig);
const { wrapper, fixtures } = await createFixtures(f => {
f.withPhoneNumber({ second_factors: ['phone_code'], used_for_second_factor: true });
f.withUser({
phone_numbers: [{ phone_number: '+306911111111', id: 'id', backup_codes: ['111111', '111111'] }],
two_factor_enabled: true,
});
f.withBackupCode();
});

fixtures.clerk.user?.phoneNumbers[0].setReservedForSecondFactor.mockResolvedValue({} as PhoneNumberResource);
const { getByText, userEvent, getByRole } = render(<MfaSection />, { wrapper });
Expand Down Expand Up @@ -132,7 +139,12 @@ describe('MfaPage', () => {
getByText(
/Save these backup codes and store them somewhere safe. If you lose access to your authentication device, you can use backup codes to sign in./i,
);
getByText(/backup codes/i);

const backupCodesTitle = getByText(/backup codes/i, {
selector: '[data-localization-key="userProfile.backupCodePage.title__codelist"]',
});

expect(backupCodesTitle).toBeInTheDocument();

await userEvent.click(getByRole('button', { name: /finish/i }));
});
Expand Down

0 comments on commit 7cc1b3a

Please sign in to comment.