diff --git a/.changeset/tidy-device-trust-methods.md b/.changeset/tidy-device-trust-methods.md
new file mode 100644
index 00000000000..0b3908e7a77
--- /dev/null
+++ b/.changeset/tidy-device-trust-methods.md
@@ -0,0 +1,5 @@
+---
+'@clerk/ui': patch
+---
+
+Hide the “Use another method” action during Device Trust and second-factor verification when no alternative verification method is available.
diff --git a/integration/tests/client-trust.test.ts b/integration/tests/client-trust.test.ts
index 7cf5e377f21..9817b5b0135 100644
--- a/integration/tests/client-trust.test.ts
+++ b/integration/tests/client-trust.test.ts
@@ -38,6 +38,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withNeedsClientTrust] })(
// Should contain the new device verification notice
await expect(u.page.getByText("You're signing in from a new device.")).toBeVisible();
+ await expect(u.page.getByRole('link', { name: 'Use another method' })).toBeHidden();
// User should not be signed in yet since client trust step is required
await u.po.expect.toBeSignedOut();
diff --git a/packages/ui/src/components/SignIn/SignInClientTrust.tsx b/packages/ui/src/components/SignIn/SignInClientTrust.tsx
index ca3273fc2ff..aa14fd77fb1 100644
--- a/packages/ui/src/components/SignIn/SignInClientTrust.tsx
+++ b/packages/ui/src/components/SignIn/SignInClientTrust.tsx
@@ -19,6 +19,8 @@ function SignInClientTrustInternal(): JSX.Element {
showAllStrategies,
toggleAllStrategies,
} = useSecondFactorSelection(signIn.supportedSecondFactors);
+ const onShowAlternativeMethodsClicked =
+ signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined;
if (!currentFactor) {
return ;
@@ -41,7 +43,7 @@ function SignInClientTrustInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
case 'email_code':
@@ -51,7 +53,7 @@ function SignInClientTrustInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
case 'email_link':
@@ -61,7 +63,7 @@ function SignInClientTrustInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
default:
diff --git a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx
index 90bfddd6df7..1af218d771c 100644
--- a/packages/ui/src/components/SignIn/SignInFactorTwo.tsx
+++ b/packages/ui/src/components/SignIn/SignInFactorTwo.tsx
@@ -28,6 +28,8 @@ function SignInFactorTwoInternal(): JSX.Element {
showAllStrategies,
toggleAllStrategies,
} = useSecondFactorSelection(signIn.supportedSecondFactors);
+ const onShowAlternativeMethodsClicked =
+ signIn.supportedSecondFactors && signIn.supportedSecondFactors.length > 1 ? toggleAllStrategies : undefined;
React.useEffect(() => {
if (clerk.__internal_setActiveInProgress) {
@@ -69,7 +71,7 @@ function SignInFactorTwoInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
case 'totp':
@@ -78,18 +80,18 @@ function SignInFactorTwoInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
case 'backup_code':
- return ;
+ return ;
case 'email_code':
return (
);
case 'email_link':
@@ -98,7 +100,7 @@ function SignInFactorTwoInternal(): JSX.Element {
factorAlreadyPrepared={factorAlreadyPrepared}
onFactorPrepare={handleFactorPrepare}
factor={currentFactor}
- onShowAlternativeMethodsClicked={toggleAllStrategies}
+ onShowAlternativeMethodsClicked={onShowAlternativeMethodsClicked}
/>
);
default:
diff --git a/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx b/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx
index ddd37690b03..3ffe439aa36 100644
--- a/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx
+++ b/packages/ui/src/components/SignIn/SignInFactorTwoBackupCodeCard.tsx
@@ -19,7 +19,7 @@ import { navigateOnSignInProtectGate } from './handleProtectCheck';
import { isResetPasswordStrategy } from './utils';
type SignInFactorTwoBackupCodeCardProps = {
- onShowAlternativeMethodsClicked: React.MouseEventHandler;
+ onShowAlternativeMethodsClicked?: React.MouseEventHandler;
};
export const SignInFactorTwoBackupCodeCard = (props: SignInFactorTwoBackupCodeCardProps) => {
diff --git a/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx
new file mode 100644
index 00000000000..029f51e9619
--- /dev/null
+++ b/packages/ui/src/components/SignIn/__tests__/SignInClientTrust.test.tsx
@@ -0,0 +1,35 @@
+import type { SignInResource } from '@clerk/shared/types';
+import { describe, expect, it } from 'vitest';
+
+import { bindCreateFixtures } from '@/test/create-fixtures';
+import { render, screen } from '@/test/utils';
+
+import { SignInClientTrust } from '../SignInClientTrust';
+
+const { createFixtures } = bindCreateFixtures('SignIn');
+
+describe('SignInClientTrust', () => {
+ describe('Use another method', () => {
+ it('does not render when only one verification factor is available', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.startSignInClientTrust({ supportPhoneCode: true });
+ });
+
+ fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
+ render(, { wrapper });
+
+ expect(screen.queryByText('Use another method')).not.toBeInTheDocument();
+ });
+
+ it('renders when multiple verification factors are available', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.startSignInClientTrust({ supportPhoneCode: true, supportEmailCode: true });
+ });
+
+ fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
+ render(, { wrapper });
+
+ expect(screen.getByText('Use another method')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx
index 3d944954df9..287e934288c 100644
--- a/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx
+++ b/packages/ui/src/components/SignIn/__tests__/SignInFactorTwo.test.tsx
@@ -407,6 +407,28 @@ describe('SignInFactorTwo', () => {
});
describe('Use another method', () => {
+ it('does not render when only one verification factor is available', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.startSignInFactorTwo({ supportPhoneCode: true });
+ });
+
+ fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
+ render(, { wrapper });
+
+ expect(screen.queryByText('Use another method')).not.toBeInTheDocument();
+ });
+
+ it('renders when multiple verification factors are available', async () => {
+ const { wrapper, fixtures } = await createFixtures(f => {
+ f.startSignInFactorTwo({ supportPhoneCode: true, supportEmailCode: true });
+ });
+
+ fixtures.signIn.prepareSecondFactor.mockResolvedValueOnce({} as SignInResource);
+ render(, { wrapper });
+
+ expect(screen.getByText('Use another method')).toBeInTheDocument();
+ });
+
it('renders the other authentication methods list component when clicking on "Use another method"', async () => {
const { wrapper, fixtures } = await createFixtures(f => {
f.withEmailAddress();
diff --git a/packages/ui/src/test/fixture-helpers.ts b/packages/ui/src/test/fixture-helpers.ts
index 9508c2ffe75..51320d1077f 100644
--- a/packages/ui/src/test/fixture-helpers.ts
+++ b/packages/ui/src/test/fixture-helpers.ts
@@ -119,6 +119,7 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
type SignInFactorTwoParams = {
identifier?: string;
supportPhoneCode?: boolean;
+ supportEmailCode?: boolean;
supportTotp?: boolean;
supportBackupCode?: boolean;
supportResetPasswordEmail?: boolean;
@@ -185,17 +186,21 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
} as SignInJSON;
};
- const startSignInFactorTwo = (params?: SignInFactorTwoParams) => {
+ const startSignInVerification = (
+ status: Extract,
+ params?: SignInFactorTwoParams,
+ ) => {
const {
identifier = '+30 691 1111111',
supportPhoneCode = true,
+ supportEmailCode,
supportTotp,
supportBackupCode,
supportResetPasswordEmail,
supportResetPasswordPhone,
} = params || {};
baseClient.sign_in = {
- status: 'needs_second_factor',
+ status,
identifier,
...(supportResetPasswordEmail
? {
@@ -216,6 +221,7 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
supported_identifiers: ['email_address', 'phone_number'],
supported_second_factors: [
...(supportPhoneCode ? [{ strategy: 'phone_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
+ ...(supportEmailCode ? [{ strategy: 'email_code', safe_identifier: 'n*****@clerk.com' }] : []),
...(supportTotp ? [{ strategy: 'totp', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
...(supportBackupCode ? [{ strategy: 'backup_code', safe_identifier: identifier || 'n*****@clerk.com' }] : []),
],
@@ -223,6 +229,12 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
} as SignInJSON;
};
+ const startSignInFactorTwo = (params?: SignInFactorTwoParams) =>
+ startSignInVerification('needs_second_factor', params);
+
+ const startSignInClientTrust = (params?: SignInFactorTwoParams) =>
+ startSignInVerification('needs_client_trust', params);
+
const startSignInWithProtectCheck = (params?: {
expiresAt?: number;
uiHints?: Record;
@@ -249,7 +261,13 @@ const createSignInFixtureHelpers = (baseClient: ClientJSON) => {
} as SignInJSON;
};
- return { startSignInWithEmailAddress, startSignInWithPhoneNumber, startSignInFactorTwo, startSignInWithProtectCheck };
+ return {
+ startSignInWithEmailAddress,
+ startSignInWithPhoneNumber,
+ startSignInFactorTwo,
+ startSignInClientTrust,
+ startSignInWithProtectCheck,
+ };
};
const createSignUpFixtureHelpers = (baseClient: ClientJSON) => {