@@ -13,52 +135,52 @@
From 132233d1bfd0849966dcf770b4f2a41d4d3624f8 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Mon, 18 Nov 2024 16:53:19 -0500
Subject: [PATCH 06/29] init
---
packages/clerk-js/sandbox/app.js | 11 ++-
.../src/ui/components/SignIn/SignIn.tsx | 89 ++++++++++++++++++-
.../src/ui/components/SignIn/SignInStart.tsx | 25 ++++--
.../src/ui/components/SignUp/SignUpStart.tsx | 5 +-
packages/localizations/src/ar-SA.ts | 1 +
packages/localizations/src/be-BY.ts | 1 +
packages/localizations/src/bg-BG.ts | 1 +
packages/localizations/src/cs-CZ.ts | 1 +
packages/localizations/src/da-DK.ts | 1 +
packages/localizations/src/de-DE.ts | 1 +
packages/localizations/src/el-GR.ts | 1 +
packages/localizations/src/en-US.ts | 1 +
packages/localizations/src/es-ES.ts | 1 +
packages/localizations/src/es-MX.ts | 1 +
packages/localizations/src/fi-FI.ts | 1 +
packages/localizations/src/fr-FR.ts | 1 +
packages/localizations/src/he-IL.ts | 1 +
packages/localizations/src/hu-HU.ts | 1 +
packages/localizations/src/is-IS.ts | 1 +
packages/localizations/src/it-IT.ts | 1 +
packages/localizations/src/ja-JP.ts | 1 +
packages/localizations/src/ko-KR.ts | 1 +
packages/localizations/src/mn-MN.ts | 1 +
packages/localizations/src/nb-NO.ts | 1 +
packages/localizations/src/nl-NL.ts | 1 +
packages/localizations/src/pl-PL.ts | 1 +
packages/localizations/src/pt-BR.ts | 1 +
packages/localizations/src/pt-PT.ts | 1 +
packages/localizations/src/ro-RO.ts | 1 +
packages/localizations/src/ru-RU.ts | 1 +
packages/localizations/src/sk-SK.ts | 1 +
packages/localizations/src/sr-RS.ts | 1 +
packages/localizations/src/sv-SE.ts | 1 +
packages/localizations/src/th-TH.ts | 1 +
packages/localizations/src/tr-TR.ts | 1 +
packages/localizations/src/uk-UA.ts | 1 +
packages/localizations/src/vi-VN.ts | 1 +
packages/localizations/src/zh-CN.ts | 1 +
packages/localizations/src/zh-TW.ts | 1 +
packages/types/src/clerk.ts | 4 +-
packages/types/src/localization.ts | 1 +
41 files changed, 156 insertions(+), 14 deletions(-)
diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js
index 9518bd7e60c..f3ac0f9daa0 100644
--- a/packages/clerk-js/sandbox/app.js
+++ b/packages/clerk-js/sandbox/app.js
@@ -36,7 +36,11 @@ const routes = {
mountIndex(app);
},
'/sign-in': () => {
- Clerk.mountSignIn(app, {});
+ Clerk.mountSignIn(app, {
+ __experimental: {
+ combinedFlow: true,
+ },
+ });
},
'/sign-up': () => {
Clerk.mountSignUp(app, {});
@@ -81,7 +85,10 @@ function addCurrentRouteIndicator(currentRoute) {
if (route in routes) {
const renderCurrentRoute = routes[route];
addCurrentRouteIndicator(route);
- await Clerk.load();
+ await Clerk.load({
+ signInUrl: '/sign-in',
+ signUpUrl: '/sign-up',
+ });
renderCurrentRoute();
}
})();
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index bef7b6e6eb6..29398dc2ed5 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -2,10 +2,21 @@ import { useClerk } from '@clerk/shared/react';
import type { SignInModalProps, SignInProps } from '@clerk/types';
import React from 'react';
-import { SignInEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
-import { SignInContext, useSignInContext, withCoreSessionSwitchGuard } from '../../contexts';
+import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
+import {
+ SignInContext,
+ SignUpContext,
+ useSignInContext,
+ useSignUpContext,
+ withCoreSessionSwitchGuard,
+} from '../../contexts';
import { Flow } from '../../customizables';
import { Route, Switch, VIRTUAL_ROUTER_BASE_PATH } from '../../router';
+import { SignUpContinue } from '../SignUp/SignUpContinue';
+import { SignUpSSOCallback } from '../SignUp/SignUpSSOCallback';
+import { SignUpStart } from '../SignUp/SignUpStart';
+import { SignUpVerifyEmail } from '../SignUp/SignUpVerifyEmail';
+import { SignUpVerifyPhone } from '../SignUp/SignUpVerifyPhone';
import { ResetPassword } from './ResetPassword';
import { ResetPasswordSuccess } from './ResetPasswordSuccess';
import { SignInAccountSwitcher } from './SignInAccountSwitcher';
@@ -24,6 +35,7 @@ function RedirectToSignIn() {
function SignInRoutes(): JSX.Element {
const signInContext = useSignInContext();
+ const signUpContext = useSignUpContext();
return (
@@ -62,6 +74,61 @@ function SignInRoutes(): JSX.Element {
redirectUrl='../factor-two'
/>
+ {signInContext.__experimental?.combinedFlow && (
+
+ !!clerk.client.signUp.emailAddress}
+ >
+
+
+ !!clerk.client.signUp.phoneNumber}
+ >
+
+
+
+
+
+
+
+
+
+ !!clerk.client.signUp.emailAddress}
+ >
+
+
+ !!clerk.client.signUp.phoneNumber}
+ >
+
+
+
+
+
+
+
+
+
+
+ )}
@@ -73,9 +140,25 @@ function SignInRoutes(): JSX.Element {
);
}
+function SignInRoot() {
+ const signInContext = useSignInContext();
+
+ return (
+
+
+
+ );
+}
+
SignInRoutes.displayName = 'SignIn';
-export const SignIn: React.ComponentType = withCoreSessionSwitchGuard(SignInRoutes);
+export const SignIn: React.ComponentType = withCoreSessionSwitchGuard(SignInRoot);
export const SignInModal = (props: SignInModalProps): JSX.Element => {
const signInProps = {
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 028170a46c7..17d961013da 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -65,7 +65,8 @@ export function _SignInStart(): JSX.Element {
const signIn = useCoreSignIn();
const { navigate } = useRouter();
const ctx = useSignInContext();
- const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
+ const { afterSignInUrl, signUpUrl, waitlistUrl, __experimental } = ctx;
+ const isCombinedFlow = __experimental?.combinedFlow || false;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
@@ -301,7 +302,7 @@ export function _SignInStart(): JSX.Element {
}
}
} catch (e) {
- return attemptToRecoverFromSignInError(e);
+ return attemptToRecoverFromSignInError(e, fields);
}
};
@@ -316,7 +317,7 @@ export function _SignInStart(): JSX.Element {
});
};
- const attemptToRecoverFromSignInError = async (e: any) => {
+ const attemptToRecoverFromSignInError = async (e: any, fields: any) => {
if (!e.errors) {
return;
}
@@ -334,6 +335,12 @@ export function _SignInStart(): JSX.Element {
const sid = alreadySignedInError.meta!.sessionId!;
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
} else {
+ if (isCombinedFlow) {
+ clerk.client.signUp.emailAddress = fields.find(f => f.name === 'identifier')?.value;
+ clerk.client.signUp.phoneNumber = fields.find(f => f.name === 'identifier')?.value;
+ clerk.client.signUp.username = fields.find(f => f.name === 'identifier')?.value;
+ return navigate('create');
+ }
handleError(e, [identifierField, instantPasswordField], card.setError);
}
};
@@ -365,8 +372,14 @@ export function _SignInStart(): JSX.Element {
-
-
+ {isCombinedFlow ? (
+
+ ) : (
+ <>
+
+
+ >
+ )}
{card.error}
{/*TODO: extract main in its own component */}
@@ -415,7 +428,7 @@ export function _SignInStart(): JSX.Element {
- {userSettings.signUp.mode === SIGN_UP_MODES.PUBLIC && (
+ {userSettings.signUp.mode === SIGN_UP_MODES.PUBLIC && !isCombinedFlow && (
(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
@@ -315,7 +316,7 @@ function _SignUpStart(): JSX.Element {
diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts
index 14339709d0e..ebaaa7ea2b3 100644
--- a/packages/localizations/src/ar-SA.ts
+++ b/packages/localizations/src/ar-SA.ts
@@ -460,6 +460,7 @@ export const arSA: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'للمتابعة إلى {{applicationName}}',
title: 'تسجيل الدخول',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'رمز التحقق',
diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts
index 8cc8c9749bf..99d81ebcebc 100644
--- a/packages/localizations/src/be-BY.ts
+++ b/packages/localizations/src/be-BY.ts
@@ -464,6 +464,7 @@ export const beBY: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'каб працягнуць працу ў "{{applicationName}}"',
title: 'Увайсці',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Код верыфікацыі',
diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts
index 7f4dd3176d1..3963a8f42be 100644
--- a/packages/localizations/src/bg-BG.ts
+++ b/packages/localizations/src/bg-BG.ts
@@ -460,6 +460,7 @@ export const bgBG: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Добре дошли обратно! Моля, влезте, за да продължите',
title: 'Влезте в {{applicationName}}',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Код за потвърждение',
diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts
index 26e92e8a5d0..a4d76ff5e88 100644
--- a/packages/localizations/src/cs-CZ.ts
+++ b/packages/localizations/src/cs-CZ.ts
@@ -459,6 +459,7 @@ export const csCZ: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pro pokračování do {{applicationName}}',
title: 'Přihlásit se',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Ověřovací kód',
diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts
index 916db9b0354..ac9ce74a9ab 100644
--- a/packages/localizations/src/da-DK.ts
+++ b/packages/localizations/src/da-DK.ts
@@ -460,6 +460,7 @@ export const daDK: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Forsæt til {{applicationName}}',
title: 'Log ind',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Bekræftelseskode',
diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts
index a8b48707be2..664c68d39bc 100644
--- a/packages/localizations/src/de-DE.ts
+++ b/packages/localizations/src/de-DE.ts
@@ -466,6 +466,7 @@ export const deDE: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'weiter zu {{applicationName}}',
title: 'Einloggen',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Bestätigungscode',
diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts
index 61d0e6760af..9a866d5b300 100644
--- a/packages/localizations/src/el-GR.ts
+++ b/packages/localizations/src/el-GR.ts
@@ -461,6 +461,7 @@ export const elGR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'για να συνεχίσετε στο {{applicationName}}',
title: 'Σύνδεση',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Κωδικός επαλήθευσης',
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 4775dfd6cff..721120eac92 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -450,6 +450,7 @@ export const enUS: LocalizationResource = {
actionText__join_waitlist: 'Want early access?',
subtitle: 'Welcome back! Please sign in to continue',
title: 'Sign in to {{applicationName}}',
+ titleCombined: 'Continue to {{applicationName}}',
},
totpMfa: {
formTitle: 'Verification code',
diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts
index b621c74641d..b26d77136bc 100644
--- a/packages/localizations/src/es-ES.ts
+++ b/packages/localizations/src/es-ES.ts
@@ -461,6 +461,7 @@ export const esES: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar a {{applicationName}}',
title: 'Entrar',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificación',
diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts
index f208064746e..9a11ae9e812 100644
--- a/packages/localizations/src/es-MX.ts
+++ b/packages/localizations/src/es-MX.ts
@@ -465,6 +465,7 @@ export const esMX: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar con {{applicationName}}',
title: 'Iniciar sesión',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificación',
diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts
index a09fa89bf23..b96901c718c 100644
--- a/packages/localizations/src/fi-FI.ts
+++ b/packages/localizations/src/fi-FI.ts
@@ -462,6 +462,7 @@ export const fiFI: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'jatkaaksesi kohteeseen {{applicationName}}',
title: 'Kirjaudu sisään',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Todennuskoodi',
diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts
index 0d772633ca5..f637c906440 100644
--- a/packages/localizations/src/fr-FR.ts
+++ b/packages/localizations/src/fr-FR.ts
@@ -464,6 +464,7 @@ export const frFR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pour continuer vers {{applicationName}}',
title: "S'identifier",
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Le code de vérification',
diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts
index 75d3a8b5e1f..a9d26f3ceb5 100644
--- a/packages/localizations/src/he-IL.ts
+++ b/packages/localizations/src/he-IL.ts
@@ -454,6 +454,7 @@ export const heIL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'להמשיך אל {{applicationName}}',
title: 'התחבר',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'קוד אימות',
diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts
index 1d54e4b419d..a592dba9b99 100644
--- a/packages/localizations/src/hu-HU.ts
+++ b/packages/localizations/src/hu-HU.ts
@@ -461,6 +461,7 @@ export const huHU: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Üdv újra! A folytatáshoz kérlek jelentkezz be.',
title: 'Bejelentkezés a(z) {{applicationName}} fiókba',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Visszaigazoló kód',
diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts
index 5340dd1cd09..f90c3132f15 100644
--- a/packages/localizations/src/is-IS.ts
+++ b/packages/localizations/src/is-IS.ts
@@ -463,6 +463,7 @@ export const isIS: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Velkomin aftur! Vinsamlegast skráðu þig inn til að halda áfram',
title: 'Skrá inn í {{applicationName}}',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Staðfestingarkóði',
diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts
index f6b9d6fecce..6e26f13f2cf 100644
--- a/packages/localizations/src/it-IT.ts
+++ b/packages/localizations/src/it-IT.ts
@@ -461,6 +461,7 @@ export const itIT: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'per continuare su {{applicationName}}',
title: 'Accedi',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Codice di verifica',
diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts
index 8d874cd52c8..9abee458cdf 100644
--- a/packages/localizations/src/ja-JP.ts
+++ b/packages/localizations/src/ja-JP.ts
@@ -461,6 +461,7 @@ export const jaJP: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '{{applicationName}}へのアクセスを続ける',
title: 'サインイン',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: '検証コード',
diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts
index db9c329bd0e..c2c2c78730e 100644
--- a/packages/localizations/src/ko-KR.ts
+++ b/packages/localizations/src/ko-KR.ts
@@ -456,6 +456,7 @@ export const koKR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '환영합니다! 계속하려면 로그인해 주세요',
title: '{{applicationName}}에 로그인',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: '인증 코드',
diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts
index 6063b5b0df9..e481d4b83c0 100644
--- a/packages/localizations/src/mn-MN.ts
+++ b/packages/localizations/src/mn-MN.ts
@@ -462,6 +462,7 @@ export const mnMN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Тавтай морил! Үргэлжлүүлэхийн тулд нэвтэрнэ үү',
title: '{{applicationName}} руу нэвтрэх',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Баталгаажуулах код',
diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts
index 4be9bf34559..8ebda650b04 100644
--- a/packages/localizations/src/nb-NO.ts
+++ b/packages/localizations/src/nb-NO.ts
@@ -461,6 +461,7 @@ export const nbNO: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'for å fortsette til {{applicationName}}',
title: 'Logg inn',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifiseringskode',
diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts
index adea79d9c5b..b2192968621 100644
--- a/packages/localizations/src/nl-NL.ts
+++ b/packages/localizations/src/nl-NL.ts
@@ -461,6 +461,7 @@ export const nlNL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'om door te gaan naar {{applicationName}}',
title: 'Inloggen',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verificatiecode',
diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts
index 59b3c48d95c..0eaba6dd133 100644
--- a/packages/localizations/src/pl-PL.ts
+++ b/packages/localizations/src/pl-PL.ts
@@ -460,6 +460,7 @@ export const plPL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'aby przejść do {{applicationName}}',
title: 'Zaloguj się',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Kod weryfikacyjny',
diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts
index 6c5be5442eb..4cc14197481 100644
--- a/packages/localizations/src/pt-BR.ts
+++ b/packages/localizations/src/pt-BR.ts
@@ -461,6 +461,7 @@ export const ptBR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar em {{applicationName}}',
title: 'Entrar',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificação',
diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts
index 9dd30c63b20..db7b165c3d1 100644
--- a/packages/localizations/src/pt-PT.ts
+++ b/packages/localizations/src/pt-PT.ts
@@ -459,6 +459,7 @@ export const ptPT: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar em {{applicationName}}',
title: 'Entrar',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificação',
diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts
index bf78cb0be38..422030200ef 100644
--- a/packages/localizations/src/ro-RO.ts
+++ b/packages/localizations/src/ro-RO.ts
@@ -463,6 +463,7 @@ export const roRO: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pentru a continua la {{applicationName}}',
title: 'Conectați-vă',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Cod de verificare',
diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts
index 96a47e8e674..0210efae63d 100644
--- a/packages/localizations/src/ru-RU.ts
+++ b/packages/localizations/src/ru-RU.ts
@@ -470,6 +470,7 @@ export const ruRU: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'чтобы продолжить работу в "{{applicationName}}"',
title: 'Войти',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Верификационный код',
diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts
index edd8f5e9fec..062e6d9a686 100644
--- a/packages/localizations/src/sk-SK.ts
+++ b/packages/localizations/src/sk-SK.ts
@@ -459,6 +459,7 @@ export const skSK: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pre pokračovanie do {{applicationName}}',
title: 'Prihlásiť sa',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Overovací kód',
diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts
index f27e5261025..8fc33c78f66 100644
--- a/packages/localizations/src/sr-RS.ts
+++ b/packages/localizations/src/sr-RS.ts
@@ -460,6 +460,7 @@ export const srRS: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Dobro došao nazad! Molimo prijavi se da nastaviš',
title: 'Prijavi se na {{applicationName}}',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifikacioni kod',
diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts
index cde0f91ac2f..3e6929dc5d5 100644
--- a/packages/localizations/src/sv-SE.ts
+++ b/packages/localizations/src/sv-SE.ts
@@ -463,6 +463,7 @@ export const svSE: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'för att fortsätta till {{applicationName}}',
title: 'Logga in',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifieringskod',
diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts
index a5b92f23b8d..d5da8caefbf 100644
--- a/packages/localizations/src/th-TH.ts
+++ b/packages/localizations/src/th-TH.ts
@@ -457,6 +457,7 @@ export const thTH: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'ยินดีต้อนรับกลับ! กรุณาเข้าสู่ระบบเพื่อดำเนินการต่อ',
title: 'เข้าสู่ระบบ {{applicationName}}',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'รหัสการตรวจสอบ',
diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts
index 60e3d2d17a3..1ff54a56745 100644
--- a/packages/localizations/src/tr-TR.ts
+++ b/packages/localizations/src/tr-TR.ts
@@ -462,6 +462,7 @@ export const trTR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '{{applicationName}} ile devam etmek için',
title: 'Giriş yap',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Doğrulama kodu',
diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts
index 73ac9ed74e4..de2370edf64 100644
--- a/packages/localizations/src/uk-UA.ts
+++ b/packages/localizations/src/uk-UA.ts
@@ -459,6 +459,7 @@ export const ukUA: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'щоб продовжити роботу в "{{applicationName}}"',
title: 'Увійти',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Верифікаційний код',
diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts
index 421c311f63d..710b9f755be 100644
--- a/packages/localizations/src/vi-VN.ts
+++ b/packages/localizations/src/vi-VN.ts
@@ -459,6 +459,7 @@ export const viVN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'để tiếp tục với {{applicationName}}',
title: 'Đăng nhập',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: 'Mã xác minh',
diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts
index 5fba3e89c6a..7dc3d2e7cc2 100644
--- a/packages/localizations/src/zh-CN.ts
+++ b/packages/localizations/src/zh-CN.ts
@@ -450,6 +450,7 @@ export const zhCN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '继续使用 {{applicationName}}',
title: '登录',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: '验证码',
diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts
index 8ced64c75bd..7911b73b4fa 100644
--- a/packages/localizations/src/zh-TW.ts
+++ b/packages/localizations/src/zh-TW.ts
@@ -456,6 +456,7 @@ export const zhTW: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '繼續使用 {{applicationName}}',
title: '登錄',
+ titleCombined: undefined,
},
totpMfa: {
formTitle: '驗證碼',
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 48d89b411dd..ec5ab49fe58 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -894,12 +894,14 @@ export type SignInProps = RoutingOptions & {
/**
* Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
*/
- __experimental?: Record & { newComponents?: boolean };
+ __experimental?: Record & { newComponents?: boolean; combinedFlow?: boolean };
/**
* Full URL or path to for the waitlist process.
* Used to fill the "Join waitlist" link in the SignUp component.
*/
waitlistUrl?: string;
+
+ signUpProps?: SignUpProps;
} & TransferableOption &
SignUpForceRedirectUrl &
SignUpFallbackRedirectUrl &
diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts
index 14f008942b1..cd323e4ef37 100644
--- a/packages/types/src/localization.ts
+++ b/packages/types/src/localization.ts
@@ -168,6 +168,7 @@ type _LocalizationResource = {
signIn: {
start: {
title: LocalizationValue;
+ titleCombined: LocalizationValue;
subtitle: LocalizationValue;
actionText: LocalizationValue;
actionLink: LocalizationValue;
From aec24d4a31ece92581bd0ca9486339a952871034 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 09:44:36 -0500
Subject: [PATCH 07/29] wip
---
packages/clerk-js/sandbox/app.js | 6 +++++-
packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx | 7 ++-----
2 files changed, 7 insertions(+), 6 deletions(-)
diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js
index e991783d0d4..f3ac0f9daa0 100644
--- a/packages/clerk-js/sandbox/app.js
+++ b/packages/clerk-js/sandbox/app.js
@@ -36,7 +36,11 @@ const routes = {
mountIndex(app);
},
'/sign-in': () => {
- Clerk.mountSignIn(app, {});
+ Clerk.mountSignIn(app, {
+ __experimental: {
+ combinedFlow: true,
+ },
+ });
},
'/sign-up': () => {
Clerk.mountSignUp(app, {});
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 2cc853cc1b8..6b52a0825c4 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -303,7 +303,7 @@ export function _SignInStart(): JSX.Element {
}
}
} catch (e) {
- return attemptToRecoverFromSignInError(e, fields);
+ return attemptToRecoverFromSignInError(e);
}
};
@@ -318,7 +318,7 @@ export function _SignInStart(): JSX.Element {
});
};
- const attemptToRecoverFromSignInError = async (e: any, fields: any) => {
+ const attemptToRecoverFromSignInError = async (e: any) => {
if (!e.errors) {
return;
}
@@ -337,9 +337,6 @@ export function _SignInStart(): JSX.Element {
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
} else {
if (isCombinedFlow) {
- clerk.client.signUp.emailAddress = fields.find(f => f.name === 'identifier')?.value;
- clerk.client.signUp.phoneNumber = fields.find(f => f.name === 'identifier')?.value;
- clerk.client.signUp.username = fields.find(f => f.name === 'identifier')?.value;
return navigate('create');
}
handleError(e, [identifierField, instantPasswordField], card.setError);
From 4916296435d32d58e4d2c48da2b6e224beed994d Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 12:21:12 -0500
Subject: [PATCH 08/29] wip
---
.../clerk-js/src/ui/components/SignIn/SignInStart.tsx | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 6b52a0825c4..2e33f540dd0 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -28,6 +28,8 @@ import { buildRequest, handleError, isMobileDevice, useFormControl } from '../..
import { useHandleAuthenticateWithPasskey } from './shared';
import { SignInSocialButtons } from './SignInSocialButtons';
+const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
+
const useAutoFillPasskey = () => {
const [isSupported, setIsSupported] = useState(false);
const { navigate } = useRouter();
@@ -337,6 +339,13 @@ export function _SignInStart(): JSX.Element {
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
} else {
if (isCombinedFlow) {
+ if (identifierField.type === 'tel') {
+ clerk.client.signUp.phoneNumber = identifierField.value;
+ } else if (isEmail(identifierField.value)) {
+ clerk.client.signUp.emailAddress = identifierField.value;
+ } else {
+ clerk.client.signUp.username = identifierField.value;
+ }
return navigate('create');
}
handleError(e, [identifierField, instantPasswordField], card.setError);
From 75663b50fdcce55e292ace856f2774e137150e67 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 12:36:59 -0500
Subject: [PATCH 09/29] wip
---
.../clerk-js/src/ui/components/SignIn/SignInStart.tsx | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 2e33f540dd0..218877b928e 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -29,6 +29,15 @@ import { useHandleAuthenticateWithPasskey } from './shared';
import { SignInSocialButtons } from './SignInSocialButtons';
const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
+const getSignUpAttributeFromIdentifier = (identifier: FormControlState<'identifier'>) => {
+ if (identifier.type === 'tel') {
+ return 'phoneNumber';
+ } else if (isEmail(identifier.value)) {
+ return 'emailAddress';
+ } else {
+ return 'username';
+ }
+};
const useAutoFillPasskey = () => {
const [isSupported, setIsSupported] = useState(false);
@@ -346,6 +355,8 @@ export function _SignInStart(): JSX.Element {
} else {
clerk.client.signUp.username = identifierField.value;
}
+ const attribute = getSignUpAttributeFromIdentifier(identifierField);
+ clerk.client.signUp[attribute] = identifierField.value;
return navigate('create');
}
handleError(e, [identifierField, instantPasswordField], card.setError);
From 022c6aea9d4102606cc3ef928cb14ff3c6a3ed61 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 12:40:16 -0500
Subject: [PATCH 10/29] wip
---
.../clerk-js/src/ui/components/SignIn/SignInStart.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 218877b928e..63a68323896 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -32,11 +32,13 @@ const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
const getSignUpAttributeFromIdentifier = (identifier: FormControlState<'identifier'>) => {
if (identifier.type === 'tel') {
return 'phoneNumber';
- } else if (isEmail(identifier.value)) {
+ }
+
+ if (isEmail(identifier.value)) {
return 'emailAddress';
- } else {
- return 'username';
}
+
+ return 'username';
};
const useAutoFillPasskey = () => {
From a78605a559c94645d793144c3a80d14de35094e7 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 12:46:52 -0500
Subject: [PATCH 11/29] wip
---
.../src/ui/components/SignIn/SignInStart.tsx | 21 +------------------
.../src/ui/components/SignIn/utils.ts | 14 +++++++++++++
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 63a68323896..ec112fd971b 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -27,19 +27,7 @@ import type { FormControlState } from '../../utils';
import { buildRequest, handleError, isMobileDevice, useFormControl } from '../../utils';
import { useHandleAuthenticateWithPasskey } from './shared';
import { SignInSocialButtons } from './SignInSocialButtons';
-
-const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
-const getSignUpAttributeFromIdentifier = (identifier: FormControlState<'identifier'>) => {
- if (identifier.type === 'tel') {
- return 'phoneNumber';
- }
-
- if (isEmail(identifier.value)) {
- return 'emailAddress';
- }
-
- return 'username';
-};
+import { getSignUpAttributeFromIdentifier } from './utils';
const useAutoFillPasskey = () => {
const [isSupported, setIsSupported] = useState(false);
@@ -350,13 +338,6 @@ export function _SignInStart(): JSX.Element {
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
} else {
if (isCombinedFlow) {
- if (identifierField.type === 'tel') {
- clerk.client.signUp.phoneNumber = identifierField.value;
- } else if (isEmail(identifierField.value)) {
- clerk.client.signUp.emailAddress = identifierField.value;
- } else {
- clerk.client.signUp.username = identifierField.value;
- }
const attribute = getSignUpAttributeFromIdentifier(identifierField);
clerk.client.signUp[attribute] = identifierField.value;
return navigate('create');
diff --git a/packages/clerk-js/src/ui/components/SignIn/utils.ts b/packages/clerk-js/src/ui/components/SignIn/utils.ts
index ced616f462e..b76aceb2942 100644
--- a/packages/clerk-js/src/ui/components/SignIn/utils.ts
+++ b/packages/clerk-js/src/ui/components/SignIn/utils.ts
@@ -1,6 +1,7 @@
import { titleize } from '@clerk/shared/underscore';
import { isWebAuthnSupported } from '@clerk/shared/webauthn';
import type { PreferredSignInStrategy, SignInFactor, SignInResource, SignInStrategy } from '@clerk/types';
+import type { FormControlState } from 'ui/utils';
import { PREFERRED_SIGN_IN_STRATEGIES } from '../../common/constants';
import { otpPrefFactorComparator, passwordPrefFactorComparator } from '../../utils/factorSorting';
@@ -108,3 +109,16 @@ export function determineStartingSignInSecondFactor(secondFactors: SignInFactor[
const resetPasswordStrategies: SignInStrategy[] = ['reset_password_phone_code', 'reset_password_email_code'];
export const isResetPasswordStrategy = (strategy: SignInStrategy | string | null | undefined) =>
!!strategy && resetPasswordStrategies.includes(strategy as SignInStrategy);
+
+const isEmail = (str: string) => /^\S+@\S+\.\S+$/.test(str);
+export function getSignUpAttributeFromIdentifier(identifier: FormControlState<'identifier'>) {
+ if (identifier.type === 'tel') {
+ return 'phoneNumber';
+ }
+
+ if (isEmail(identifier.value)) {
+ return 'emailAddress';
+ }
+
+ return 'username';
+}
From 9b58e3b5622c80a4be8b2b2b9c893463ec61843c Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 19 Nov 2024 18:06:30 -0500
Subject: [PATCH 12/29] wip
---
packages/clerk-js/sandbox/app.js | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js
index f3ac0f9daa0..e991783d0d4 100644
--- a/packages/clerk-js/sandbox/app.js
+++ b/packages/clerk-js/sandbox/app.js
@@ -36,11 +36,7 @@ const routes = {
mountIndex(app);
},
'/sign-in': () => {
- Clerk.mountSignIn(app, {
- __experimental: {
- combinedFlow: true,
- },
- });
+ Clerk.mountSignIn(app, {});
},
'/sign-up': () => {
Clerk.mountSignUp(app, {});
From d82bb14b2067c05cde08090e6084efa66037d39f Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 20 Nov 2024 09:27:09 -0500
Subject: [PATCH 13/29] experimental prefix
---
packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx | 2 +-
packages/localizations/src/ar-SA.ts | 2 +-
packages/localizations/src/be-BY.ts | 2 +-
packages/localizations/src/bg-BG.ts | 2 +-
packages/localizations/src/cs-CZ.ts | 2 +-
packages/localizations/src/da-DK.ts | 2 +-
packages/localizations/src/de-DE.ts | 2 +-
packages/localizations/src/el-GR.ts | 2 +-
packages/localizations/src/en-US.ts | 2 +-
packages/localizations/src/es-ES.ts | 2 +-
packages/localizations/src/es-MX.ts | 2 +-
packages/localizations/src/fi-FI.ts | 2 +-
packages/localizations/src/fr-FR.ts | 2 +-
packages/localizations/src/he-IL.ts | 2 +-
packages/localizations/src/hu-HU.ts | 2 +-
packages/localizations/src/is-IS.ts | 2 +-
packages/localizations/src/it-IT.ts | 2 +-
packages/localizations/src/ja-JP.ts | 2 +-
packages/localizations/src/ko-KR.ts | 2 +-
packages/localizations/src/mn-MN.ts | 2 +-
packages/localizations/src/nb-NO.ts | 2 +-
packages/localizations/src/nl-NL.ts | 2 +-
packages/localizations/src/pl-PL.ts | 2 +-
packages/localizations/src/pt-BR.ts | 2 +-
packages/localizations/src/pt-PT.ts | 2 +-
packages/localizations/src/ro-RO.ts | 2 +-
packages/localizations/src/ru-RU.ts | 2 +-
packages/localizations/src/sk-SK.ts | 2 +-
packages/localizations/src/sr-RS.ts | 2 +-
packages/localizations/src/sv-SE.ts | 2 +-
packages/localizations/src/th-TH.ts | 2 +-
packages/localizations/src/tr-TR.ts | 2 +-
packages/localizations/src/uk-UA.ts | 2 +-
packages/localizations/src/vi-VN.ts | 2 +-
packages/localizations/src/zh-CN.ts | 2 +-
packages/localizations/src/zh-TW.ts | 2 +-
packages/types/src/localization.ts | 2 +-
37 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index ec112fd971b..7686acde3d0 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -374,7 +374,7 @@ export function _SignInStart(): JSX.Element {
{isCombinedFlow ? (
-
+
) : (
<>
diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts
index ebaaa7ea2b3..ff32b9af4b0 100644
--- a/packages/localizations/src/ar-SA.ts
+++ b/packages/localizations/src/ar-SA.ts
@@ -460,7 +460,7 @@ export const arSA: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'للمتابعة إلى {{applicationName}}',
title: 'تسجيل الدخول',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'رمز التحقق',
diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts
index 99d81ebcebc..aa14fd091f6 100644
--- a/packages/localizations/src/be-BY.ts
+++ b/packages/localizations/src/be-BY.ts
@@ -464,7 +464,7 @@ export const beBY: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'каб працягнуць працу ў "{{applicationName}}"',
title: 'Увайсці',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Код верыфікацыі',
diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts
index 3963a8f42be..7b9d3bef254 100644
--- a/packages/localizations/src/bg-BG.ts
+++ b/packages/localizations/src/bg-BG.ts
@@ -460,7 +460,7 @@ export const bgBG: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Добре дошли обратно! Моля, влезте, за да продължите',
title: 'Влезте в {{applicationName}}',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Код за потвърждение',
diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts
index a4d76ff5e88..559fe7466d1 100644
--- a/packages/localizations/src/cs-CZ.ts
+++ b/packages/localizations/src/cs-CZ.ts
@@ -459,7 +459,7 @@ export const csCZ: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pro pokračování do {{applicationName}}',
title: 'Přihlásit se',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Ověřovací kód',
diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts
index ac9ce74a9ab..9acf9bfb778 100644
--- a/packages/localizations/src/da-DK.ts
+++ b/packages/localizations/src/da-DK.ts
@@ -460,7 +460,7 @@ export const daDK: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Forsæt til {{applicationName}}',
title: 'Log ind',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Bekræftelseskode',
diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts
index 664c68d39bc..60894f753e3 100644
--- a/packages/localizations/src/de-DE.ts
+++ b/packages/localizations/src/de-DE.ts
@@ -466,7 +466,7 @@ export const deDE: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'weiter zu {{applicationName}}',
title: 'Einloggen',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Bestätigungscode',
diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts
index c2b3eb3b781..4ef53e3f880 100644
--- a/packages/localizations/src/el-GR.ts
+++ b/packages/localizations/src/el-GR.ts
@@ -462,7 +462,7 @@ export const elGR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'για να συνεχίσετε στο {{applicationName}}',
title: 'Σύνδεση',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Κωδικός επαλήθευσης',
diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts
index 721120eac92..c8c1051b243 100644
--- a/packages/localizations/src/en-US.ts
+++ b/packages/localizations/src/en-US.ts
@@ -450,7 +450,7 @@ export const enUS: LocalizationResource = {
actionText__join_waitlist: 'Want early access?',
subtitle: 'Welcome back! Please sign in to continue',
title: 'Sign in to {{applicationName}}',
- titleCombined: 'Continue to {{applicationName}}',
+ __experimental_titleCombined: 'Continue to {{applicationName}}',
},
totpMfa: {
formTitle: 'Verification code',
diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts
index b26d77136bc..c247176d02d 100644
--- a/packages/localizations/src/es-ES.ts
+++ b/packages/localizations/src/es-ES.ts
@@ -461,7 +461,7 @@ export const esES: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar a {{applicationName}}',
title: 'Entrar',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificación',
diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts
index 9a11ae9e812..8af9608bbb4 100644
--- a/packages/localizations/src/es-MX.ts
+++ b/packages/localizations/src/es-MX.ts
@@ -465,7 +465,7 @@ export const esMX: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar con {{applicationName}}',
title: 'Iniciar sesión',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificación',
diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts
index b96901c718c..b0fc321f767 100644
--- a/packages/localizations/src/fi-FI.ts
+++ b/packages/localizations/src/fi-FI.ts
@@ -462,7 +462,7 @@ export const fiFI: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'jatkaaksesi kohteeseen {{applicationName}}',
title: 'Kirjaudu sisään',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Todennuskoodi',
diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts
index f637c906440..4c1ed3c324e 100644
--- a/packages/localizations/src/fr-FR.ts
+++ b/packages/localizations/src/fr-FR.ts
@@ -464,7 +464,7 @@ export const frFR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pour continuer vers {{applicationName}}',
title: "S'identifier",
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Le code de vérification',
diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts
index a9d26f3ceb5..9c7fa5335af 100644
--- a/packages/localizations/src/he-IL.ts
+++ b/packages/localizations/src/he-IL.ts
@@ -454,7 +454,7 @@ export const heIL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'להמשיך אל {{applicationName}}',
title: 'התחבר',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'קוד אימות',
diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts
index a592dba9b99..a242131509b 100644
--- a/packages/localizations/src/hu-HU.ts
+++ b/packages/localizations/src/hu-HU.ts
@@ -461,7 +461,7 @@ export const huHU: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Üdv újra! A folytatáshoz kérlek jelentkezz be.',
title: 'Bejelentkezés a(z) {{applicationName}} fiókba',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Visszaigazoló kód',
diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts
index f90c3132f15..f079104ce68 100644
--- a/packages/localizations/src/is-IS.ts
+++ b/packages/localizations/src/is-IS.ts
@@ -463,7 +463,7 @@ export const isIS: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Velkomin aftur! Vinsamlegast skráðu þig inn til að halda áfram',
title: 'Skrá inn í {{applicationName}}',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Staðfestingarkóði',
diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts
index 6e26f13f2cf..8ad23e9a32b 100644
--- a/packages/localizations/src/it-IT.ts
+++ b/packages/localizations/src/it-IT.ts
@@ -461,7 +461,7 @@ export const itIT: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'per continuare su {{applicationName}}',
title: 'Accedi',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Codice di verifica',
diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts
index 9abee458cdf..1b8f53c92da 100644
--- a/packages/localizations/src/ja-JP.ts
+++ b/packages/localizations/src/ja-JP.ts
@@ -461,7 +461,7 @@ export const jaJP: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '{{applicationName}}へのアクセスを続ける',
title: 'サインイン',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: '検証コード',
diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts
index c2c2c78730e..1e93bfd19f0 100644
--- a/packages/localizations/src/ko-KR.ts
+++ b/packages/localizations/src/ko-KR.ts
@@ -456,7 +456,7 @@ export const koKR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '환영합니다! 계속하려면 로그인해 주세요',
title: '{{applicationName}}에 로그인',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: '인증 코드',
diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts
index e481d4b83c0..cf6a1e6862c 100644
--- a/packages/localizations/src/mn-MN.ts
+++ b/packages/localizations/src/mn-MN.ts
@@ -462,7 +462,7 @@ export const mnMN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Тавтай морил! Үргэлжлүүлэхийн тулд нэвтэрнэ үү',
title: '{{applicationName}} руу нэвтрэх',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Баталгаажуулах код',
diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts
index 8ebda650b04..ebe4cade6d1 100644
--- a/packages/localizations/src/nb-NO.ts
+++ b/packages/localizations/src/nb-NO.ts
@@ -461,7 +461,7 @@ export const nbNO: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'for å fortsette til {{applicationName}}',
title: 'Logg inn',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifiseringskode',
diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts
index b2192968621..9323db0a145 100644
--- a/packages/localizations/src/nl-NL.ts
+++ b/packages/localizations/src/nl-NL.ts
@@ -461,7 +461,7 @@ export const nlNL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'om door te gaan naar {{applicationName}}',
title: 'Inloggen',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verificatiecode',
diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts
index 0eaba6dd133..94621d3f8cd 100644
--- a/packages/localizations/src/pl-PL.ts
+++ b/packages/localizations/src/pl-PL.ts
@@ -460,7 +460,7 @@ export const plPL: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'aby przejść do {{applicationName}}',
title: 'Zaloguj się',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Kod weryfikacyjny',
diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts
index 4cc14197481..d7324000be4 100644
--- a/packages/localizations/src/pt-BR.ts
+++ b/packages/localizations/src/pt-BR.ts
@@ -461,7 +461,7 @@ export const ptBR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar em {{applicationName}}',
title: 'Entrar',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificação',
diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts
index db7b165c3d1..28e83815eaf 100644
--- a/packages/localizations/src/pt-PT.ts
+++ b/packages/localizations/src/pt-PT.ts
@@ -459,7 +459,7 @@ export const ptPT: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'para continuar em {{applicationName}}',
title: 'Entrar',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Código de verificação',
diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts
index 422030200ef..fc0c86370bb 100644
--- a/packages/localizations/src/ro-RO.ts
+++ b/packages/localizations/src/ro-RO.ts
@@ -463,7 +463,7 @@ export const roRO: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pentru a continua la {{applicationName}}',
title: 'Conectați-vă',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Cod de verificare',
diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts
index 0210efae63d..dded48c2055 100644
--- a/packages/localizations/src/ru-RU.ts
+++ b/packages/localizations/src/ru-RU.ts
@@ -470,7 +470,7 @@ export const ruRU: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'чтобы продолжить работу в "{{applicationName}}"',
title: 'Войти',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Верификационный код',
diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts
index 062e6d9a686..29f4d80cf2d 100644
--- a/packages/localizations/src/sk-SK.ts
+++ b/packages/localizations/src/sk-SK.ts
@@ -459,7 +459,7 @@ export const skSK: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'pre pokračovanie do {{applicationName}}',
title: 'Prihlásiť sa',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Overovací kód',
diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts
index 8fc33c78f66..3ac5fe96bc6 100644
--- a/packages/localizations/src/sr-RS.ts
+++ b/packages/localizations/src/sr-RS.ts
@@ -460,7 +460,7 @@ export const srRS: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'Dobro došao nazad! Molimo prijavi se da nastaviš',
title: 'Prijavi se na {{applicationName}}',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifikacioni kod',
diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts
index 3e6929dc5d5..5d4feaa9d68 100644
--- a/packages/localizations/src/sv-SE.ts
+++ b/packages/localizations/src/sv-SE.ts
@@ -463,7 +463,7 @@ export const svSE: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'för att fortsätta till {{applicationName}}',
title: 'Logga in',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Verifieringskod',
diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts
index d5da8caefbf..c15a75f908b 100644
--- a/packages/localizations/src/th-TH.ts
+++ b/packages/localizations/src/th-TH.ts
@@ -457,7 +457,7 @@ export const thTH: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'ยินดีต้อนรับกลับ! กรุณาเข้าสู่ระบบเพื่อดำเนินการต่อ',
title: 'เข้าสู่ระบบ {{applicationName}}',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'รหัสการตรวจสอบ',
diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts
index 1ff54a56745..7b9464209fb 100644
--- a/packages/localizations/src/tr-TR.ts
+++ b/packages/localizations/src/tr-TR.ts
@@ -462,7 +462,7 @@ export const trTR: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '{{applicationName}} ile devam etmek için',
title: 'Giriş yap',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Doğrulama kodu',
diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts
index de2370edf64..5c813d0f8d4 100644
--- a/packages/localizations/src/uk-UA.ts
+++ b/packages/localizations/src/uk-UA.ts
@@ -459,7 +459,7 @@ export const ukUA: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'щоб продовжити роботу в "{{applicationName}}"',
title: 'Увійти',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Верифікаційний код',
diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts
index 710b9f755be..2d0bdbf18ad 100644
--- a/packages/localizations/src/vi-VN.ts
+++ b/packages/localizations/src/vi-VN.ts
@@ -459,7 +459,7 @@ export const viVN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: 'để tiếp tục với {{applicationName}}',
title: 'Đăng nhập',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: 'Mã xác minh',
diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts
index 7dc3d2e7cc2..08cbfc4fd76 100644
--- a/packages/localizations/src/zh-CN.ts
+++ b/packages/localizations/src/zh-CN.ts
@@ -450,7 +450,7 @@ export const zhCN: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '继续使用 {{applicationName}}',
title: '登录',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: '验证码',
diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts
index 7911b73b4fa..461394298dd 100644
--- a/packages/localizations/src/zh-TW.ts
+++ b/packages/localizations/src/zh-TW.ts
@@ -456,7 +456,7 @@ export const zhTW: LocalizationResource = {
actionText__join_waitlist: undefined,
subtitle: '繼續使用 {{applicationName}}',
title: '登錄',
- titleCombined: undefined,
+ __experimental_titleCombined: undefined,
},
totpMfa: {
formTitle: '驗證碼',
diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts
index cd323e4ef37..47b2b4e5927 100644
--- a/packages/types/src/localization.ts
+++ b/packages/types/src/localization.ts
@@ -168,7 +168,7 @@ type _LocalizationResource = {
signIn: {
start: {
title: LocalizationValue;
- titleCombined: LocalizationValue;
+ __experimental_titleCombined: LocalizationValue;
subtitle: LocalizationValue;
actionText: LocalizationValue;
actionLink: LocalizationValue;
From da880cc14721f58b4041385b1a2804829239b6e2 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 20 Nov 2024 09:34:42 -0500
Subject: [PATCH 14/29] move signUpProps to experimental
---
packages/clerk-js/src/ui/components/SignIn/SignIn.tsx | 2 +-
packages/types/src/clerk.ts | 4 +---
2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index 29398dc2ed5..4a4bfc99f69 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -147,7 +147,7 @@ function SignInRoot() {
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index ec5ab49fe58..7b1c0f319f3 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -894,14 +894,12 @@ export type SignInProps = RoutingOptions & {
/**
* Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
*/
- __experimental?: Record & { newComponents?: boolean; combinedFlow?: boolean };
+ __experimental?: Record & { newComponents?: boolean; combinedFlow?: boolean; signUpProps?: SignUpProps };
/**
* Full URL or path to for the waitlist process.
* Used to fill the "Join waitlist" link in the SignUp component.
*/
waitlistUrl?: string;
-
- signUpProps?: SignUpProps;
} & TransferableOption &
SignUpForceRedirectUrl &
SignUpFallbackRedirectUrl &
From 555fd916b204e011e26f657d180b52a4ab9af412 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 20 Nov 2024 09:42:24 -0500
Subject: [PATCH 15/29] add changeset
---
.changeset/loud-balloons-grow.md | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 .changeset/loud-balloons-grow.md
diff --git a/.changeset/loud-balloons-grow.md b/.changeset/loud-balloons-grow.md
new file mode 100644
index 00000000000..abd8cb131b9
--- /dev/null
+++ b/.changeset/loud-balloons-grow.md
@@ -0,0 +1,7 @@
+---
+'@clerk/localizations': patch
+'@clerk/clerk-js': patch
+'@clerk/types': patch
+---
+
+Introduce experimental sign-in combined flow.
From 78c045c620964b546ddd76846ebbb594feaa702c Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 20 Nov 2024 16:41:51 -0500
Subject: [PATCH 16/29] feat(clerk-js,types): Move to waitlist with email
address in combined flow (#4615)
Co-authored-by: Dylan Staley <88163+dstaley@users.noreply.github.com>
---
packages/clerk-js/sandbox/app.js | 1 +
packages/clerk-js/src/core/clerk.ts | 7 +++----
.../src/ui/components/SignIn/SignInStart.tsx | 17 +++++++++++++++--
.../src/ui/components/SignUp/SignUpStart.tsx | 4 ++--
.../src/ui/components/Waitlist/Waitlist.tsx | 4 +++-
.../src/ui/contexts/components/Waitlist.ts | 14 +++++++++++++-
packages/types/src/clerk.ts | 2 +-
7 files changed, 38 insertions(+), 11 deletions(-)
diff --git a/packages/clerk-js/sandbox/app.js b/packages/clerk-js/sandbox/app.js
index 6f7debdec9f..1098f28736c 100644
--- a/packages/clerk-js/sandbox/app.js
+++ b/packages/clerk-js/sandbox/app.js
@@ -175,6 +175,7 @@ function addCurrentRouteIndicator(currentRoute) {
await Clerk.load({
signInUrl: '/sign-in',
signUpUrl: '/sign-up',
+ waitlistUrl: '/waitlist',
});
renderCurrentRoute();
} else {
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index db377ee7cc3..8e862b9309e 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -1044,14 +1044,13 @@ export class Clerk implements ClerkInterface {
return this.buildUrlWithAuth(this.#options.afterSignOutUrl);
}
- public buildWaitlistUrl(): string {
+ public buildWaitlistUrl(options?: { initialValues?: Record }): string {
if (!this.environment || !this.environment.displayConfig) {
return '';
}
-
const waitlistUrl = this.#options['waitlistUrl'] || this.environment.displayConfig.waitlistUrl;
-
- return buildURL({ base: waitlistUrl }, { stringify: true });
+ const initValues = new URLSearchParams(options?.initialValues || {});
+ return buildURL({ base: waitlistUrl, hashSearchParams: [initValues] }, { stringify: true });
}
public buildAfterMultiSessionSingleSignOutUrl(): string {
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 7686acde3d0..b5f5bade1cd 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -66,8 +66,8 @@ export function _SignInStart(): JSX.Element {
const signIn = useCoreSignIn();
const { navigate } = useRouter();
const ctx = useSignInContext();
- const { afterSignInUrl, signUpUrl, waitlistUrl, __experimental } = ctx;
- const isCombinedFlow = __experimental?.combinedFlow || false;
+ const { afterSignInUrl, signInUrl, signUpUrl, waitlistUrl, __experimental } = ctx;
+ const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
@@ -337,6 +337,19 @@ export function _SignInStart(): JSX.Element {
const sid = alreadySignedInError.meta!.sessionId!;
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
} else {
+ if (isCombinedFlow && userSettings.signUp.mode === SIGN_UP_MODES.WAITLIST) {
+ const attribute = getSignUpAttributeFromIdentifier(identifierField);
+ const waitlistUrl = clerk.buildWaitlistUrl(
+ attribute === 'emailAddress'
+ ? {
+ initialValues: {
+ [attribute]: identifierField.value,
+ },
+ }
+ : {},
+ );
+ return navigate(waitlistUrl);
+ }
if (isCombinedFlow) {
const attribute = getSignUpAttributeFromIdentifier(identifierField);
clerk.client.signUp[attribute] = identifierField.value;
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
index 031650c7396..49bd7fab0fc 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
@@ -39,8 +39,8 @@ function _SignUpStart(): JSX.Element {
const { attributes } = userSettings;
const { setActive } = useClerk();
const ctx = useSignUpContext();
- const { afterSignUpUrl, signInUrl, unsafeMetadata, __experimental } = ctx;
- const isCombinedFlow = __experimental?.combinedFlow;
+ const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata, __experimental } = ctx;
+ const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
diff --git a/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx b/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
index 398b4707e02..ded89c5caf2 100644
--- a/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
+++ b/packages/clerk-js/src/ui/components/Waitlist/Waitlist.tsx
@@ -13,8 +13,10 @@ const _Waitlist = () => {
const ctx = useWaitlistContext();
const { signInUrl } = ctx;
+ const initialValues = ctx.initialValues || {};
+
const formState = {
- emailAddress: useFormControl('emailAddress', '', {
+ emailAddress: useFormControl('emailAddress', initialValues.emailAddress || '', {
type: 'email',
label: localizationKeys('formFieldLabel__emailAddress'),
placeholder: localizationKeys('formFieldInputPlaceholder__emailAddress'),
diff --git a/packages/clerk-js/src/ui/contexts/components/Waitlist.ts b/packages/clerk-js/src/ui/contexts/components/Waitlist.ts
index 6a00f335b7a..767a37ee12d 100644
--- a/packages/clerk-js/src/ui/contexts/components/Waitlist.ts
+++ b/packages/clerk-js/src/ui/contexts/components/Waitlist.ts
@@ -1,12 +1,17 @@
-import { createContext, useContext } from 'react';
+import { createContext, useContext, useMemo } from 'react';
import { buildURL } from '../../../utils';
import { useEnvironment, useOptions } from '../../contexts';
+import { useRouter } from '../../router';
import type { WaitlistCtx } from '../../types';
+import { getInitialValuesFromQueryParams } from '../utils';
+
+const WAITLIST_INITIAL_VALUE_KEYS = ['email_address'];
export type WaitlistContextType = WaitlistCtx & {
signInUrl: string;
afterJoinWaitlistUrl?: string;
+ initialValues: any;
};
export const WaitlistContext = createContext(null);
@@ -15,6 +20,12 @@ export const useWaitlistContext = (): WaitlistContextType => {
const context = useContext(WaitlistContext);
const { displayConfig } = useEnvironment();
const options = useOptions();
+ const { queryString } = useRouter();
+
+ const initialValuesFromQueryParams = useMemo(
+ () => getInitialValuesFromQueryParams(queryString, WAITLIST_INITIAL_VALUE_KEYS),
+ [],
+ );
if (!context || context.componentName !== 'Waitlist') {
throw new Error('Clerk: useWaitlistContext called outside Waitlist.');
@@ -29,5 +40,6 @@ export const useWaitlistContext = (): WaitlistContextType => {
...ctx,
componentName,
signInUrl,
+ initialValues: { ...initialValuesFromQueryParams },
};
};
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 7b1c0f319f3..3ee85edcc82 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -465,7 +465,7 @@ export interface Clerk {
/**
* Returns the configured url where is mounted or a custom waitlist page is rendered.
*/
- buildWaitlistUrl(): string;
+ buildWaitlistUrl(opts?: { initialValues?: Record }): string;
/**
*
From 11e78272733dbf9ddcdd40593162d37c6724445b Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Thu, 21 Nov 2024 11:23:16 -0500
Subject: [PATCH 17/29] padding
---
packages/clerk-js/sandbox/template.html | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html
index 3d1c1eea4c7..ef864f6f162 100644
--- a/packages/clerk-js/sandbox/template.html
+++ b/packages/clerk-js/sandbox/template.html
@@ -241,7 +241,10 @@
-
+
From 0bf95165170d8bac051c4403b093bbc2a3b3dc16 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Mon, 25 Nov 2024 11:22:31 -0500
Subject: [PATCH 18/29] feat(clerk-js): Add support for combined flow in
`buildUrl` (#4626)
---
packages/clerk-js/src/core/clerk.ts | 14 ++++++++-
packages/clerk-js/src/core/constants.ts | 1 +
.../src/ui/components/SignIn/SignIn.tsx | 5 ++--
.../src/ui/components/SignIn/SignInStart.tsx | 30 ++++++++++++-------
.../src/ui/components/SignUp/SignUpStart.tsx | 7 +++--
packages/types/src/clerk.ts | 3 +-
6 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts
index 0821926e5b2..7f7b5a1d718 100644
--- a/packages/clerk-js/src/core/clerk.ts
+++ b/packages/clerk-js/src/core/clerk.ts
@@ -356,6 +356,10 @@ export class Clerk implements ClerkInterface {
}
};
+ #isCombinedFlow(): boolean {
+ return this.#options.experimental?.combinedFlow && this.#options.signInUrl === this.#options.signUpUrl;
+ }
+
public signOut: SignOut = async (callbackOrOptions?: SignOutCallback | SignOutOptions, options?: SignOutOptions) => {
if (!this.client || this.client.sessions.length === 0) {
return;
@@ -2023,10 +2027,18 @@ export class Clerk implements ClerkInterface {
if (!key || !this.loaded || !this.environment || !this.environment.displayConfig) {
return '';
}
+
const signInOrUpUrl = this.#options[key] || this.environment.displayConfig[key];
const redirectUrls = new RedirectUrls(this.#options, options).toSearchParams();
const initValues = new URLSearchParams(_initValues || {});
- const url = buildURL({ base: signInOrUpUrl, hashSearchParams: [initValues, redirectUrls] }, { stringify: true });
+ const url = buildURL(
+ {
+ base: signInOrUpUrl,
+ hashPath: this.#isCombinedFlow() && key === 'signUpUrl' ? '/create' : '',
+ hashSearchParams: [initValues, redirectUrls],
+ },
+ { stringify: true },
+ );
return this.buildUrlWithAuth(url);
};
diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts
index ae34145cb60..488568c4e6c 100644
--- a/packages/clerk-js/src/core/constants.ts
+++ b/packages/clerk-js/src/core/constants.ts
@@ -32,6 +32,7 @@ export const ERROR_CODES = {
ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'enterprise_sso_email_address_domain_mismatch',
ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: 'enterprise_sso_hosted_domain_mismatch',
SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'saml_email_address_domain_mismatch',
+ INVITATION_ACCOUNT_NOT_EXISTS: 'invitation_account_not_exists',
} as const;
export const SIGN_IN_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'username'];
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index 4a4bfc99f69..968ab8526e4 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -6,6 +6,7 @@ import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../
import {
SignInContext,
SignUpContext,
+ useOptions,
useSignInContext,
useSignUpContext,
withCoreSessionSwitchGuard,
@@ -36,6 +37,7 @@ function RedirectToSignIn() {
function SignInRoutes(): JSX.Element {
const signInContext = useSignInContext();
const signUpContext = useSignUpContext();
+ const options = useOptions();
return (
@@ -74,7 +76,7 @@ function SignInRoutes(): JSX.Element {
redirectUrl='../factor-two'
/>
- {signInContext.__experimental?.combinedFlow && (
+ {options.experimental?.combinedFlow && (
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index b5f5bade1cd..90026ed5e9a 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -9,7 +9,7 @@ import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils';
import type { SignInStartIdentifier } from '../../common';
import { getIdentifierControlDisplayValues, groupIdentifiers, withRedirectToAfterSignIn } from '../../common';
import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts';
+import { useCoreSignIn, useEnvironment, useOptions, useSignInContext } from '../../contexts';
import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
import {
Card,
@@ -65,9 +65,10 @@ export function _SignInStart(): JSX.Element {
const { displayConfig, userSettings } = useEnvironment();
const signIn = useCoreSignIn();
const { navigate } = useRouter();
+ const options = useOptions();
const ctx = useSignInContext();
- const { afterSignInUrl, signInUrl, signUpUrl, waitlistUrl, __experimental } = ctx;
- const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false;
+ const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
+ const isCombinedFlow = (options?.experimental?.combinedFlow && options.signInUrl === options.signUpUrl) || false;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
@@ -327,18 +328,24 @@ export function _SignInStart(): JSX.Element {
(e: ClerkAPIError) =>
e.code === ERROR_CODES.INVALID_STRATEGY_FOR_USER || e.code === ERROR_CODES.FORM_PASSWORD_INCORRECT,
);
+
const alreadySignedInError: ClerkAPIError = e.errors.find(
(e: ClerkAPIError) => e.code === 'identifier_already_signed_in',
);
+ const accountDoesNotExistError: ClerkAPIError = e.errors.find(
+ (e: ClerkAPIError) =>
+ e.code === ERROR_CODES.INVITATION_ACCOUNT_NOT_EXISTS || e.code === ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND,
+ );
if (instantPasswordError) {
await signInWithFields(identifierField);
} else if (alreadySignedInError) {
const sid = alreadySignedInError.meta!.sessionId!;
await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl });
- } else {
- if (isCombinedFlow && userSettings.signUp.mode === SIGN_UP_MODES.WAITLIST) {
- const attribute = getSignUpAttributeFromIdentifier(identifierField);
+ } else if (isCombinedFlow && accountDoesNotExistError) {
+ const attribute = getSignUpAttributeFromIdentifier(identifierField);
+
+ if (userSettings.signUp.mode === SIGN_UP_MODES.WAITLIST) {
const waitlistUrl = clerk.buildWaitlistUrl(
attribute === 'emailAddress'
? {
@@ -350,11 +357,14 @@ export function _SignInStart(): JSX.Element {
);
return navigate(waitlistUrl);
}
- if (isCombinedFlow) {
- const attribute = getSignUpAttributeFromIdentifier(identifierField);
- clerk.client.signUp[attribute] = identifierField.value;
- return navigate('create');
+
+ clerk.client.signUp[attribute] = identifierField.value;
+ const paramsToForward = new URLSearchParams();
+ if (organizationTicket) {
+ paramsToForward.set('__clerk_ticket', organizationTicket);
}
+ return navigate(`create?${paramsToForward.toString()}`);
+ } else {
handleError(e, [identifierField, instantPasswordField], card.setError);
}
};
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
index 49bd7fab0fc..d5758243c92 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
@@ -4,7 +4,7 @@ import React from 'react';
import { ERROR_CODES, SIGN_UP_MODES } from '../../../core/constants';
import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils/getClerkQueryParam';
import { buildSSOCallbackURL, withRedirectToAfterSignUp } from '../../common';
-import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
+import { useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables';
import {
Card,
@@ -39,8 +39,9 @@ function _SignUpStart(): JSX.Element {
const { attributes } = userSettings;
const { setActive } = useClerk();
const ctx = useSignUpContext();
- const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata, __experimental } = ctx;
- const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false;
+ const options = useOptions();
+ const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata } = ctx;
+ const isCombinedFlow = (options.experimental?.combinedFlow && signInUrl === signUpUrl) || false;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index eb6bc030e67..6b511a3f621 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -739,6 +739,7 @@ export type ClerkOptions = ClerkOptionsNavigation &
{
persistClient: boolean;
rethrowOfflineNetworkErrors: boolean;
+ combinedFlow: boolean;
},
Record
>;
@@ -894,7 +895,7 @@ export type SignInProps = RoutingOptions & {
/**
* Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
*/
- __experimental?: Record & { newComponents?: boolean; combinedFlow?: boolean; signUpProps?: SignUpProps };
+ __experimental?: Record & { newComponents?: boolean; signUpProps?: SignUpProps };
/**
* Full URL or path to for the waitlist process.
* Used to fill the "Join waitlist" link in the SignUp component.
From acee56f2e004ab71978149dd6c00d093af5a17d5 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 27 Nov 2024 08:32:31 -0500
Subject: [PATCH 19/29] feat(clerk-js): Combined flow transfer (#4637)
---
packages/clerk-js/sandbox/template.html | 4 +-
.../src/ui/common/EmailLinkVerify.tsx | 4 +-
packages/clerk-js/src/ui/common/redirects.ts | 3 +-
.../src/ui/components/SignIn/SignIn.tsx | 1 +
.../src/ui/components/SignIn/SignInStart.tsx | 21 ++++-
.../SignIn/handleCombinedFlowTransfer.ts | 76 +++++++++++++++++++
.../SignUp/SignUpVerificationCodeForm.tsx | 1 +
.../src/ui/contexts/components/SignIn.ts | 7 ++
.../clerk-js/src/utils/completeSignUpFlow.ts | 6 ++
9 files changed, 117 insertions(+), 6 deletions(-)
create mode 100644 packages/clerk-js/src/ui/components/SignIn/handleCombinedFlowTransfer.ts
diff --git a/packages/clerk-js/sandbox/template.html b/packages/clerk-js/sandbox/template.html
index 29be36c364a..e8ada02e49a 100644
--- a/packages/clerk-js/sandbox/template.html
+++ b/packages/clerk-js/sandbox/template.html
@@ -133,7 +133,7 @@
viewBox="0 0 62 18"
fill="none"
aria-hidden="true"
- class="h-[1.125rem] text-gray-950 dark:text-white"
+ class="h-[1.125rem] text-gray-950"
>
Sandbox
diff --git a/packages/clerk-js/src/ui/common/EmailLinkVerify.tsx b/packages/clerk-js/src/ui/common/EmailLinkVerify.tsx
index 9f0c08110c9..e67d25be897 100644
--- a/packages/clerk-js/src/ui/common/EmailLinkVerify.tsx
+++ b/packages/clerk-js/src/ui/common/EmailLinkVerify.tsx
@@ -15,11 +15,12 @@ export type EmailLinkVerifyProps = {
redirectUrl?: string;
verifyEmailPath?: string;
verifyPhonePath?: string;
+ continuePath?: string;
texts: Record;
};
export const EmailLinkVerify = (props: EmailLinkVerifyProps) => {
- const { redirectUrl, redirectUrlComplete, verifyEmailPath, verifyPhonePath } = props;
+ const { redirectUrl, redirectUrlComplete, verifyEmailPath, verifyPhonePath, continuePath } = props;
const { handleEmailLinkVerification } = useClerk();
const { navigate } = useRouter();
const signUp = useCoreSignUp();
@@ -36,6 +37,7 @@ export const EmailLinkVerify = (props: EmailLinkVerifyProps) => {
signUp,
verifyEmailPath,
verifyPhonePath,
+ continuePath,
navigate,
});
} catch (err) {
diff --git a/packages/clerk-js/src/ui/common/redirects.ts b/packages/clerk-js/src/ui/common/redirects.ts
index 212dfeca9dc..e54d09b1427 100644
--- a/packages/clerk-js/src/ui/common/redirects.ts
+++ b/packages/clerk-js/src/ui/common/redirects.ts
@@ -9,12 +9,13 @@ export function buildEmailLinkRedirectUrl(
baseUrl: string | undefined = '',
): string {
const { routing, authQueryString, path } = ctx;
+ const isCombinedFlow = 'signInUrl' in ctx && 'signUpUrl' in ctx && ctx.signInUrl === ctx.signUpUrl;
return buildRedirectUrl({
routing,
baseUrl,
authQueryString,
path,
- endpoint: MAGIC_LINK_VERIFY_PATH_ROUTE,
+ endpoint: isCombinedFlow ? `/create${MAGIC_LINK_VERIFY_PATH_ROUTE}` : MAGIC_LINK_VERIFY_PATH_ROUTE,
});
}
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index 968ab8526e4..85b4a8fcf43 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -107,6 +107,7 @@ function SignInRoutes(): JSX.Element {
redirectUrlComplete={signUpContext.afterSignUpUrl}
verifyEmailPath='../verify-email-address'
verifyPhonePath='../verify-phone-number'
+ continuePath='../continue'
/>
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 90026ed5e9a..d09dc97d1e5 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -9,7 +9,7 @@ import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils';
import type { SignInStartIdentifier } from '../../common';
import { getIdentifierControlDisplayValues, groupIdentifiers, withRedirectToAfterSignIn } from '../../common';
import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignIn, useEnvironment, useOptions, useSignInContext } from '../../contexts';
+import { useCoreSignIn, useEnvironment, useOptions, useSignInContext, useSignUpContext } from '../../contexts';
import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
import {
Card,
@@ -25,6 +25,7 @@ import { useSupportEmail } from '../../hooks/useSupportEmail';
import { useRouter } from '../../router';
import type { FormControlState } from '../../utils';
import { buildRequest, handleError, isMobileDevice, useFormControl } from '../../utils';
+import { handleCombinedFlowTransfer } from './handleCombinedFlowTransfer';
import { useHandleAuthenticateWithPasskey } from './shared';
import { SignInSocialButtons } from './SignInSocialButtons';
import { getSignUpAttributeFromIdentifier } from './utils';
@@ -67,6 +68,7 @@ export function _SignInStart(): JSX.Element {
const { navigate } = useRouter();
const options = useOptions();
const ctx = useSignInContext();
+ const signUpCtx = useSignUpContext();
const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
const isCombinedFlow = (options?.experimental?.combinedFlow && options.signInUrl === options.signUpUrl) || false;
const supportEmail = useSupportEmail();
@@ -363,7 +365,22 @@ export function _SignInStart(): JSX.Element {
if (organizationTicket) {
paramsToForward.set('__clerk_ticket', organizationTicket);
}
- return navigate(`create?${paramsToForward.toString()}`);
+
+ const redirectUrl = buildSSOCallbackURL(signUpCtx, displayConfig.signUpUrl);
+ const redirectUrlComplete = signUpCtx.afterSignUpUrl || '/';
+
+ return handleCombinedFlowTransfer({
+ afterSignUpUrl: signUpCtx.afterSignUpUrl || '/',
+ clerk,
+ handleError: e => handleError(e, [identifierField, instantPasswordField], card.setError),
+ identifierAttribute: attribute,
+ identifierValue: identifierField.value,
+ navigate,
+ organizationTicket,
+ signUpMode: userSettings.signUp.mode,
+ redirectUrl,
+ redirectUrlComplete,
+ });
} else {
handleError(e, [identifierField, instantPasswordField], card.setError);
}
diff --git a/packages/clerk-js/src/ui/components/SignIn/handleCombinedFlowTransfer.ts b/packages/clerk-js/src/ui/components/SignIn/handleCombinedFlowTransfer.ts
new file mode 100644
index 00000000000..0a883497b90
--- /dev/null
+++ b/packages/clerk-js/src/ui/components/SignIn/handleCombinedFlowTransfer.ts
@@ -0,0 +1,76 @@
+import type { LoadedClerk, SignUpModes } from '@clerk/types';
+
+import { SIGN_UP_MODES } from '../../../core/constants';
+import { completeSignUpFlow } from '../SignUp/util';
+
+type HandleCombinedFlowTransferProps = {
+ identifierAttribute: 'emailAddress' | 'phoneNumber' | 'username';
+ identifierValue: string;
+ signUpMode: SignUpModes;
+ navigate: (to: string) => Promise;
+ organizationTicket?: string;
+ afterSignUpUrl: string;
+ clerk: LoadedClerk;
+ handleError: (err: any) => void;
+ redirectUrl?: string;
+ redirectUrlComplete?: string;
+};
+
+/**
+ * This function is used to handle transfering from a sign in to a sign up when SignIn is rendered as the combined flow.
+ * There is special logic to handle transfer email-based sign ups directly to verification, bypassing the initial sign up form.
+ */
+export function handleCombinedFlowTransfer({
+ identifierAttribute,
+ identifierValue,
+ signUpMode,
+ navigate,
+ organizationTicket,
+ afterSignUpUrl,
+ clerk,
+ handleError,
+ redirectUrl,
+ redirectUrlComplete,
+}: HandleCombinedFlowTransferProps): Promise | void {
+ if (signUpMode === SIGN_UP_MODES.WAITLIST) {
+ const waitlistUrl = clerk.buildWaitlistUrl(
+ identifierAttribute === 'emailAddress'
+ ? {
+ initialValues: {
+ [identifierAttribute]: identifierValue,
+ },
+ }
+ : {},
+ );
+ return navigate(waitlistUrl);
+ }
+
+ clerk.client.signUp[identifierAttribute] = identifierValue;
+ const paramsToForward = new URLSearchParams();
+ if (organizationTicket) {
+ paramsToForward.set('__clerk_ticket', organizationTicket);
+ }
+
+ // Attempt to transfer directly to sign up verification if email or phone was used. The signUp.create() call will
+ // inform us if the instance is eligible for moving directly to verification.
+ if (identifierAttribute === 'emailAddress' || identifierAttribute === 'phoneNumber') {
+ return clerk.client.signUp
+ .create({
+ [identifierAttribute]: identifierValue,
+ })
+ .then(res => {
+ return completeSignUpFlow({
+ signUp: res,
+ verifyEmailPath: 'create/verify-email-address',
+ verifyPhonePath: 'create/verify-phone-number',
+ handleComplete: () => clerk.setActive({ session: res.createdSessionId, redirectUrl: afterSignUpUrl }),
+ navigate,
+ redirectUrl,
+ redirectUrlComplete,
+ });
+ })
+ .catch(err => handleError(err));
+ }
+
+ return navigate(`create?${paramsToForward.toString()}`);
+}
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpVerificationCodeForm.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpVerificationCodeForm.tsx
index 358ff0fae1f..3f0ae424894 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpVerificationCodeForm.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpVerificationCodeForm.tsx
@@ -36,6 +36,7 @@ export const SignUpVerificationCodeForm = (props: SignInFactorOneCodeFormProps)
signUp: res,
verifyEmailPath: '../verify-email-address',
verifyPhonePath: '../verify-phone-number',
+ continuePath: '../continue',
handleComplete: () => setActive({ session: res.createdSessionId, redirectUrl: afterSignUpUrl }),
navigate,
});
diff --git a/packages/clerk-js/src/ui/contexts/components/SignIn.ts b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
index 1d45b1edb02..68279677a30 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignIn.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
@@ -71,6 +71,13 @@ export const useSignInContext = (): SignInContextType => {
signUpUrl = buildURL({ base: signUpUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
waitlistUrl = buildURL({ base: waitlistUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
+ if (options.experimental?.combinedFlow && signInUrl === signUpUrl) {
+ signUpUrl = buildURL(
+ { base: signInUrl, hashPath: '/create', hashSearchParams: [queryParams, preservedParams] },
+ { stringify: true },
+ );
+ }
+
const signUpContinueUrl = buildURL({ base: signUpUrl, hashPath: '/continue' }, { stringify: true });
return {
diff --git a/packages/clerk-js/src/utils/completeSignUpFlow.ts b/packages/clerk-js/src/utils/completeSignUpFlow.ts
index 6d9742ad30b..b64d5c0db67 100644
--- a/packages/clerk-js/src/utils/completeSignUpFlow.ts
+++ b/packages/clerk-js/src/utils/completeSignUpFlow.ts
@@ -4,6 +4,7 @@ type CompleteSignUpFlowProps = {
signUp: SignUpResource;
verifyEmailPath?: string;
verifyPhonePath?: string;
+ continuePath?: string;
navigate: (to: string) => Promise;
handleComplete?: () => Promise;
redirectUrl?: string;
@@ -14,6 +15,7 @@ export const completeSignUpFlow = ({
signUp,
verifyEmailPath,
verifyPhonePath,
+ continuePath,
navigate,
handleComplete,
redirectUrl = '',
@@ -37,6 +39,10 @@ export const completeSignUpFlow = ({
if (signUp.unverifiedFields?.includes('phone_number') && verifyPhonePath) {
return navigate(verifyPhonePath);
}
+
+ if (continuePath) {
+ return navigate(continuePath);
+ }
}
return;
};
From 059520219873268616f3f10e58daab7fa2883e47 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 27 Nov 2024 10:36:54 -0500
Subject: [PATCH 20/29] remove signUpContext usage
---
.../clerk-js/src/ui/components/SignIn/SignInStart.tsx | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index d09dc97d1e5..8eb5d0ca794 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -9,7 +9,7 @@ import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils';
import type { SignInStartIdentifier } from '../../common';
import { getIdentifierControlDisplayValues, groupIdentifiers, withRedirectToAfterSignIn } from '../../common';
import { buildSSOCallbackURL } from '../../common/redirects';
-import { useCoreSignIn, useEnvironment, useOptions, useSignInContext, useSignUpContext } from '../../contexts';
+import { useCoreSignIn, useEnvironment, useOptions, useSignInContext } from '../../contexts';
import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
import {
Card,
@@ -68,7 +68,6 @@ export function _SignInStart(): JSX.Element {
const { navigate } = useRouter();
const options = useOptions();
const ctx = useSignInContext();
- const signUpCtx = useSignUpContext();
const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
const isCombinedFlow = (options?.experimental?.combinedFlow && options.signInUrl === options.signUpUrl) || false;
const supportEmail = useSupportEmail();
@@ -366,11 +365,11 @@ export function _SignInStart(): JSX.Element {
paramsToForward.set('__clerk_ticket', organizationTicket);
}
- const redirectUrl = buildSSOCallbackURL(signUpCtx, displayConfig.signUpUrl);
- const redirectUrlComplete = signUpCtx.afterSignUpUrl || '/';
+ const redirectUrl = buildSSOCallbackURL(ctx, displayConfig.signUpUrl);
+ const redirectUrlComplete = ctx.afterSignUpUrl || '/';
return handleCombinedFlowTransfer({
- afterSignUpUrl: signUpCtx.afterSignUpUrl || '/',
+ afterSignUpUrl: ctx.afterSignUpUrl || '/',
clerk,
handleError: e => handleError(e, [identifierField, instantPasswordField], card.setError),
identifierAttribute: attribute,
From a20a08c65ce1a31f25c31617bcd2bc8bc2aff9ef Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Tue, 3 Dec 2024 15:59:31 -0500
Subject: [PATCH 21/29] chore(clerk-js,types): Add experimental
`SignInCombinedProps` option (#4691)
---
packages/clerk-js/src/ui/common/redirects.ts | 2 +-
.../src/ui/components/SignIn/SignIn.tsx | 2 +-
.../src/ui/components/SignIn/SignInStart.tsx | 2 +-
.../src/ui/components/SignUp/SignUpStart.tsx | 4 +-
.../src/ui/contexts/components/SignIn.ts | 5 +-
packages/types/src/clerk.ts | 55 ++++++++++++++++++-
6 files changed, 62 insertions(+), 8 deletions(-)
diff --git a/packages/clerk-js/src/ui/common/redirects.ts b/packages/clerk-js/src/ui/common/redirects.ts
index e54d09b1427..347b45bf9a2 100644
--- a/packages/clerk-js/src/ui/common/redirects.ts
+++ b/packages/clerk-js/src/ui/common/redirects.ts
@@ -9,7 +9,7 @@ export function buildEmailLinkRedirectUrl(
baseUrl: string | undefined = '',
): string {
const { routing, authQueryString, path } = ctx;
- const isCombinedFlow = 'signInUrl' in ctx && 'signUpUrl' in ctx && ctx.signInUrl === ctx.signUpUrl;
+ const isCombinedFlow = '__experimental' in ctx && ctx.__experimental?.combinedProps;
return buildRedirectUrl({
routing,
baseUrl,
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
index 85b4a8fcf43..fbce6789212 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
@@ -150,7 +150,7 @@ function SignInRoot() {
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index 8eb5d0ca794..f2de0475e3f 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -69,7 +69,7 @@ export function _SignInStart(): JSX.Element {
const options = useOptions();
const ctx = useSignInContext();
const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
- const isCombinedFlow = (options?.experimental?.combinedFlow && options.signInUrl === options.signUpUrl) || false;
+ const isCombinedFlow = options?.experimental?.combinedFlow || false;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
index d5758243c92..c74dfa41c0e 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
@@ -40,8 +40,8 @@ function _SignUpStart(): JSX.Element {
const { setActive } = useClerk();
const ctx = useSignUpContext();
const options = useOptions();
- const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata } = ctx;
- const isCombinedFlow = (options.experimental?.combinedFlow && signInUrl === signUpUrl) || false;
+ const { afterSignUpUrl, signInUrl, unsafeMetadata } = ctx;
+ const isCombinedFlow = options.experimental?.combinedFlow || false;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
diff --git a/packages/clerk-js/src/ui/contexts/components/SignIn.ts b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
index 68279677a30..c717bec4dab 100644
--- a/packages/clerk-js/src/ui/contexts/components/SignIn.ts
+++ b/packages/clerk-js/src/ui/contexts/components/SignIn.ts
@@ -37,7 +37,8 @@ export const useSignInContext = (): SignInContextType => {
throw new Error(`Clerk: useSignInContext called outside of the mounted SignIn component.`);
}
- const { componentName, ...ctx } = context;
+ const { componentName, ..._ctx } = context;
+ const ctx = _ctx.__experimental?.combinedProps || _ctx;
const initialValuesFromQueryParams = useMemo(
() => getInitialValuesFromQueryParams(queryString, SIGN_IN_INITIAL_VALUE_KEYS),
@@ -71,7 +72,7 @@ export const useSignInContext = (): SignInContextType => {
signUpUrl = buildURL({ base: signUpUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
waitlistUrl = buildURL({ base: waitlistUrl, hashSearchParams: [queryParams, preservedParams] }, { stringify: true });
- if (options.experimental?.combinedFlow && signInUrl === signUpUrl) {
+ if (options.experimental?.combinedFlow) {
signUpUrl = buildURL(
{ base: signInUrl, hashPath: '/create', hashSearchParams: [queryParams, preservedParams] },
{ stringify: true },
diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts
index 6b511a3f621..01d94f49f4a 100644
--- a/packages/types/src/clerk.ts
+++ b/packages/types/src/clerk.ts
@@ -895,7 +895,7 @@ export type SignInProps = RoutingOptions & {
/**
* Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
*/
- __experimental?: Record & { newComponents?: boolean; signUpProps?: SignUpProps };
+ __experimental?: Record & { newComponents?: boolean; combinedProps?: SignInCombinedProps };
/**
* Full URL or path to for the waitlist process.
* Used to fill the "Join waitlist" link in the SignUp component.
@@ -907,6 +907,59 @@ export type SignInProps = RoutingOptions & {
LegacyRedirectProps &
AfterSignOutUrl;
+export type SignInCombinedProps = RoutingOptions & {
+ /**
+ * Full URL or path to navigate after successful sign in.
+ * This value has precedence over other redirect props, environment variables or search params.
+ * Use this prop to override the redirect URL when needed.
+ * @default undefined
+ */
+ forceRedirectUrl?: string | null;
+ /**
+ * Full URL or path to navigate after successful sign in.
+ * This value is used when no other redirect props, environment variables or search params are present.
+ * @default undefined
+ */
+ fallbackRedirectUrl?: string | null;
+ /**
+ * Full URL or path to for the sign in process.
+ * Used to fill the "Sign in" link in the SignUp component.
+ */
+ signInUrl?: string;
+ /**
+ * Full URL or path to for the sign up process.
+ * Used to fill the "Sign up" link in the SignUp component.
+ */
+ signUpUrl?: string;
+ /**
+ * Customisation options to fully match the Clerk components to your own brand.
+ * These options serve as overrides and will be merged with the global `appearance`
+ * prop of ClerkProvider (if one is provided)
+ */
+ appearance?: SignInTheme;
+ /**
+ * Initial values that are used to prefill the sign in or up forms.
+ */
+ initialValues?: SignInInitialValues & SignUpInitialValues;
+ /**
+ * Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
+ */
+ __experimental?: Record & { newComponents?: boolean };
+ /**
+ * Full URL or path to for the waitlist process.
+ * Used to fill the "Join waitlist" link in the SignUp component.
+ */
+ waitlistUrl?: string;
+ /**
+ * Additional arbitrary metadata to be stored alongside the User object
+ */
+ unsafeMetadata?: SignUpUnsafeMetadata;
+} & TransferableOption &
+ SignUpForceRedirectUrl &
+ SignUpFallbackRedirectUrl &
+ LegacyRedirectProps &
+ AfterSignOutUrl;
+
interface TransferableOption {
/**
* Indicates whether or not sign in attempts are transferable to the sign up flow.
From a2dadee3f271a60117f6a8579b59e3dc74c85389 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Tue, 3 Dec 2024 14:07:01 -0800
Subject: [PATCH 22/29] test(e2e): Add tests for combined sign in/sign up flow
(#4707)
---
integration/presets/envs.ts | 10 ++
integration/presets/longRunningApps.ts | 1 +
.../next-app-router/src/app/layout.tsx | 3 +
.../src/app/sign-in/[[...catchall]]/page.tsx | 3 +
.../tests/combined-sign-in-flow.test.ts | 160 ++++++++++++++++++
.../tests/combined-sign-up-flow.test.ts | 118 +++++++++++++
6 files changed, 295 insertions(+)
create mode 100644 integration/tests/combined-sign-in-flow.test.ts
create mode 100644 integration/tests/combined-sign-up-flow.test.ts
diff --git a/integration/presets/envs.ts b/integration/presets/envs.ts
index ef44f865f3a..068a9845b5b 100644
--- a/integration/presets/envs.ts
+++ b/integration/presets/envs.ts
@@ -113,6 +113,15 @@ const withWaitlistdMode = withEmailCodes
.setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-waitlist-mode').sk)
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-waitlist-mode').pk);
+const withCombinedFlow = withEmailCodes
+ .clone()
+ .setId('withCombinedFlow')
+ .setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-email-codes').sk)
+ .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-email-codes').pk)
+ .setEnvVariable('public', 'EXPERIMENTAL_COMBINED_FLOW', 'true')
+ .setEnvVariable('public', 'CLERK_SIGN_IN_URL', '/sign-in')
+ .setEnvVariable('public', 'CLERK_SIGN_UP_URL', '/sign-in');
+
export const envs = {
base,
withEmailCodes,
@@ -129,4 +138,5 @@ export const envs = {
withRestrictedMode,
withLegalConsent,
withWaitlistdMode,
+ withCombinedFlow,
} as const;
diff --git a/integration/presets/longRunningApps.ts b/integration/presets/longRunningApps.ts
index 3c310184f4e..f6de9ce0992 100644
--- a/integration/presets/longRunningApps.ts
+++ b/integration/presets/longRunningApps.ts
@@ -32,6 +32,7 @@ export const createLongRunningApps = () => {
},
{ id: 'next.appRouter.withCustomRoles', config: next.appRouter, env: envs.withCustomRoles },
{ id: 'next.appRouter.withReverification', config: next.appRouter, env: envs.withReverification },
+ { id: 'next.appRouter.withCombinedFlow', config: next.appRouter, env: envs.withCombinedFlow },
{ id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart },
{ id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes },
{ id: 'astro.node.withCustomRoles', config: astro.node, env: envs.withCustomRoles },
diff --git a/integration/templates/next-app-router/src/app/layout.tsx b/integration/templates/next-app-router/src/app/layout.tsx
index b8b377146ce..0c43679815c 100644
--- a/integration/templates/next-app-router/src/app/layout.tsx
+++ b/integration/templates/next-app-router/src/app/layout.tsx
@@ -13,6 +13,9 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
);
diff --git a/integration/tests/combined-sign-in-flow.test.ts b/integration/tests/combined-sign-in-flow.test.ts
new file mode 100644
index 00000000000..548ea47ef63
--- /dev/null
+++ b/integration/tests/combined-sign-in-flow.test.ts
@@ -0,0 +1,160 @@
+import { expect, test } from '@playwright/test';
+
+import { appConfigs } from '../presets';
+import { createTestUtils, type FakeUser, testAgainstRunningApps } from '../testUtils';
+
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withCombinedFlow] })('combined sign in flow @nextjs', ({ app }) => {
+ test.describe.configure({ mode: 'serial' });
+
+ let fakeUser: FakeUser;
+
+ test.beforeAll(async () => {
+ const u = createTestUtils({ app });
+ fakeUser = u.services.users.createFakeUser({
+ withPhoneNumber: true,
+ withUsername: true,
+ });
+ await u.services.users.createBapiUser(fakeUser);
+ });
+
+ test.afterAll(async () => {
+ await fakeUser.deleteIfExists();
+ await app.teardown();
+ });
+
+ test('flows are combined', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+
+ await expect(u.page.getByText(`Don’t have an account?`)).toBeHidden();
+ });
+
+ test('sign in with email and password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.setIdentifier(fakeUser.email);
+ await u.po.signIn.continue();
+ await u.po.signIn.setPassword(fakeUser.password);
+ await u.po.signIn.continue();
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('sign in with email and instant password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('sign in with email code', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.getIdentifierInput().fill(fakeUser.email);
+ await u.po.signIn.continue();
+ await u.po.signIn.getUseAnotherMethodLink().click();
+ await u.po.signIn.getAltMethodsEmailCodeButton().click();
+ await u.po.signIn.enterTestOtpCode();
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('sign in with phone number and password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.usePhoneNumberIdentifier().click();
+ await u.po.signIn.getIdentifierInput().fill(fakeUser.phoneNumber);
+ await u.po.signIn.setPassword(fakeUser.password);
+ await u.po.signIn.continue();
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('sign in only with phone number', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const fakeUserWithoutPassword = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: false,
+ withPhoneNumber: true,
+ });
+ await u.services.users.createBapiUser(fakeUserWithoutPassword);
+ await u.po.signIn.goTo();
+ await u.po.signIn.usePhoneNumberIdentifier().click();
+ await u.po.signIn.getIdentifierInput().fill(fakeUserWithoutPassword.phoneNumber);
+ await u.po.signIn.continue();
+ await u.po.signIn.enterTestOtpCode();
+ await u.po.expect.toBeSignedIn();
+
+ await fakeUserWithoutPassword.deleteIfExists();
+ });
+
+ test('sign in with username and password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.getIdentifierInput().fill(fakeUser.username);
+ await u.po.signIn.setPassword(fakeUser.password);
+ await u.po.signIn.continue();
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('can reset password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const fakeUserWithPasword = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+ await u.services.users.createBapiUser(fakeUserWithPasword);
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.getIdentifierInput().fill(fakeUserWithPasword.email);
+ await u.po.signIn.continue();
+ await u.po.signIn.getForgotPassword().click();
+ await u.po.signIn.getResetPassword().click();
+ await u.po.signIn.enterTestOtpCode();
+ await u.po.signIn.setPassword(`${fakeUserWithPasword.password}_reset`);
+ await u.po.signIn.setPasswordConfirmation(`${fakeUserWithPasword.password}_reset`);
+ await u.po.signIn.getResetPassword().click();
+ await u.po.expect.toBeSignedIn();
+
+ await fakeUserWithPasword.deleteIfExists();
+ });
+
+ test('cannot sign in with wrong password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.getIdentifierInput().fill(fakeUser.email);
+ await u.po.signIn.continue();
+ await u.po.signIn.setPassword('wrong-password');
+ await u.po.signIn.continue();
+ await expect(u.page.getByText(/^password is incorrect/i)).toBeVisible();
+
+ await u.po.expect.toBeSignedOut();
+ });
+
+ test('cannot sign in with wrong password but can sign in with email', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.getIdentifierInput().fill(fakeUser.email);
+ await u.po.signIn.continue();
+ await u.po.signIn.setPassword('wrong-password');
+ await u.po.signIn.continue();
+
+ await expect(u.page.getByText(/^password is incorrect/i)).toBeVisible();
+
+ await u.po.signIn.getUseAnotherMethodLink().click();
+ await u.po.signIn.getAltMethodsEmailCodeButton().click();
+ await u.po.signIn.enterTestOtpCode();
+
+ await u.po.expect.toBeSignedIn();
+ });
+
+ test('access protected page @express', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ await u.po.signIn.goTo();
+ await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
+ await u.po.expect.toBeSignedIn();
+
+ expect(await u.page.locator("data-test-id='protected-api-response'").count()).toEqual(0);
+ await u.page.goToRelative('/protected');
+ await u.page.isVisible("data-test-id='protected-api-response'");
+ });
+});
diff --git a/integration/tests/combined-sign-up-flow.test.ts b/integration/tests/combined-sign-up-flow.test.ts
new file mode 100644
index 00000000000..e2c3d8adc29
--- /dev/null
+++ b/integration/tests/combined-sign-up-flow.test.ts
@@ -0,0 +1,118 @@
+import { expect, test } from '@playwright/test';
+
+import { appConfigs } from '../presets';
+import { createTestUtils, testAgainstRunningApps } from '../testUtils';
+
+testAgainstRunningApps({ withEnv: [appConfigs.envs.withCombinedFlow] })('combined sign up flow @nextjs', ({ app }) => {
+ test.describe.configure({ mode: 'serial' });
+
+ test.afterAll(async () => {
+ await app.teardown();
+ });
+
+ test('sign up with email and password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const fakeUser = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ });
+
+ // Go to sign in page
+ await u.po.signIn.goTo();
+
+ // Fill in sign in form
+ await u.po.signIn.setIdentifier(fakeUser.email);
+ await u.po.signIn.continue();
+
+ // Verify email
+ await u.po.signUp.enterTestOtpCode();
+
+ await u.page.waitForAppUrl('/sign-in/create/continue');
+
+ await u.po.signUp.setPassword(fakeUser.password);
+ await u.po.signUp.continue();
+
+ // Check if user is signed in
+ await u.po.expect.toBeSignedIn();
+
+ await fakeUser.deleteIfExists();
+ });
+
+ test('sign up with username, email, and password', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const fakeUser = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPassword: true,
+ withUsername: true,
+ });
+
+ await u.po.signIn.goTo();
+ await u.po.signIn.setIdentifier(fakeUser.username);
+ await u.po.signIn.continue();
+ await u.page.waitForAppUrl('/sign-in/create');
+
+ const prefilledUsername = await u.po.signUp.getUsernameInput().inputValue();
+ expect(prefilledUsername).toBe(fakeUser.username);
+
+ await u.po.signUp.setEmailAddress(fakeUser.email);
+ await u.po.signUp.setPassword(fakeUser.password);
+ await u.po.signUp.continue();
+
+ await u.po.signUp.enterTestOtpCode();
+
+ await u.po.expect.toBeSignedIn();
+
+ await fakeUser.deleteIfExists();
+ });
+
+ test('sign up, sign out and sign in again', async ({ page, context }) => {
+ const u = createTestUtils({ app, page, context });
+ const fakeUser = u.services.users.createFakeUser({
+ fictionalEmail: true,
+ withPhoneNumber: true,
+ withUsername: true,
+ });
+
+ // Go to sign in page
+ await u.po.signIn.goTo();
+
+ // Fill in sign in form
+ await u.po.signIn.setIdentifier(fakeUser.email);
+ await u.po.signIn.continue();
+
+ // Verify email
+ await u.po.signUp.enterTestOtpCode();
+
+ await u.page.waitForAppUrl('/sign-in/create/continue');
+
+ await u.po.signUp.setPassword(fakeUser.password);
+ await u.po.signUp.continue();
+
+ // Check if user is signed in
+ await u.po.expect.toBeSignedIn();
+
+ // Toggle user button
+ await u.po.userButton.toggleTrigger();
+ await u.po.userButton.waitForPopover();
+
+ // Click sign out
+ await u.po.userButton.triggerSignOut();
+
+ // Check if user is signed out
+ await u.po.expect.toBeSignedOut();
+
+ // Go to sign in page
+ await u.po.signIn.goTo();
+
+ // Fill in sign in form
+ await u.po.signIn.signInWithEmailAndInstantPassword({
+ email: fakeUser.email,
+ password: fakeUser.password,
+ });
+
+ // Check if user is signed in
+ await u.po.expect.toBeSignedIn();
+
+ await fakeUser.deleteIfExists();
+ });
+});
From fe0209130457f1b295b87e7f78487dacfc8adc21 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Wed, 4 Dec 2024 13:46:26 -0500
Subject: [PATCH 23/29] signup continue navigation
---
.../clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
index c08478e2990..f7a3d8f1c2c 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
@@ -1,7 +1,7 @@
import { useClerk } from '@clerk/shared/react';
import React, { useMemo } from 'react';
-import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
+import { useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
import {
Card,
@@ -33,6 +33,8 @@ function _SignUpContinue() {
const { attributes } = userSettings;
const { afterSignUpUrl, signInUrl, unsafeMetadata, initialValues = {} } = useSignUpContext();
const signUp = useCoreSignUp();
+ const options = useOptions();
+ const isCombinedFlow = options.experimental?.combinedFlow || false;
const isProgressiveSignUp = userSettings.signUp.progressive;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
@@ -216,7 +218,7 @@ function _SignUpContinue() {
From 09c408bd1f4ad2ea65a1bb6149d136f446d4a499 Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Wed, 4 Dec 2024 11:29:23 -0800
Subject: [PATCH 24/29] fix(clerk-js): Detect if SignUp is rendered within
SignIn
---
.../clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 5 +++--
packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx | 5 +++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
index f7a3d8f1c2c..a9ece531e31 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
@@ -1,7 +1,7 @@
import { useClerk } from '@clerk/shared/react';
import React, { useMemo } from 'react';
-import { useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
+import { SignInContext, useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
import {
Card,
@@ -34,7 +34,8 @@ function _SignUpContinue() {
const { afterSignUpUrl, signInUrl, unsafeMetadata, initialValues = {} } = useSignUpContext();
const signUp = useCoreSignUp();
const options = useOptions();
- const isCombinedFlow = options.experimental?.combinedFlow || false;
+ const isWithinSignInContext = !!React.useContext(SignInContext);
+ const isCombinedFlow = (options.experimental?.combinedFlow && !!isWithinSignInContext) || false;
const isProgressiveSignUp = userSettings.signUp.progressive;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
index c74dfa41c0e..2f8cded9934 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
@@ -4,7 +4,7 @@ import React from 'react';
import { ERROR_CODES, SIGN_UP_MODES } from '../../../core/constants';
import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils/getClerkQueryParam';
import { buildSSOCallbackURL, withRedirectToAfterSignUp } from '../../common';
-import { useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
+import { SignInContext, useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables';
import {
Card,
@@ -40,8 +40,9 @@ function _SignUpStart(): JSX.Element {
const { setActive } = useClerk();
const ctx = useSignUpContext();
const options = useOptions();
+ const isWithinSignInContext = !!React.useContext(SignInContext);
const { afterSignUpUrl, signInUrl, unsafeMetadata } = ctx;
- const isCombinedFlow = options.experimental?.combinedFlow || false;
+ const isCombinedFlow = (options.experimental?.combinedFlow && !!isWithinSignInContext) || false;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
From c6cdeabf0ec0f7e5e6945ade6736e7bf5e3bee8d Mon Sep 17 00:00:00 2001
From: Dylan Staley <88163+dstaley@users.noreply.github.com>
Date: Wed, 4 Dec 2024 11:52:37 -0800
Subject: [PATCH 25/29] fix(elements): Restore type cast removed by eslint
---
.../src/internals/machines/sign-in/verification.machine.ts | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/elements/src/internals/machines/sign-in/verification.machine.ts b/packages/elements/src/internals/machines/sign-in/verification.machine.ts
index 763d0323d8e..bb04445e562 100644
--- a/packages/elements/src/internals/machines/sign-in/verification.machine.ts
+++ b/packages/elements/src/internals/machines/sign-in/verification.machine.ts
@@ -411,7 +411,7 @@ export const SignInFirstFactorMachine = SignInVerificationMachine.provide({
// the assertion is unnecessary, and will remove it during the pre-commit hook. To prevent that, we disable the
// rule for the line.
- const { params, parent, resendable } = input;
+ const { params, parent, resendable } = input as PrepareFirstFactorInput;
const clerk = parent.getSnapshot().context.clerk;
// If a prepare call has already been fired recently, don't re-send
@@ -525,7 +525,7 @@ export const SignInSecondFactorMachine = SignInVerificationMachine.provide({
),
),
prepare: fromPromise(async ({ input }) => {
- const { params, parent, resendable } = input;
+ const { params, parent, resendable } = input as PrepareSecondFactorInput;
const clerk = parent.getSnapshot().context.clerk;
// If a prepare call has already been fired recently, don't re-send
From 256e597973b3280a3dce48cc8b366c6831642814 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Thu, 5 Dec 2024 11:33:45 -0500
Subject: [PATCH 26/29] remove sign-in action from continue when in
combinedFlow
---
.../src/ui/components/SignUp/SignUpContinue.tsx | 16 +++++++++-------
1 file changed, 9 insertions(+), 7 deletions(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
index 2c1963c6ae7..6e507a7ed9a 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
@@ -221,13 +221,15 @@ function _SignUpContinue() {
-
-
-
-
+ {!isCombinedFlow ? (
+
+
+
+
+ ) : null}
From d53bc919188b63d69dd818f89d0ead461eef6e14 Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Thu, 5 Dec 2024 13:04:46 -0500
Subject: [PATCH 27/29] Update
packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
Co-authored-by: Bryce Kalow
---
packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
index 2f8cded9934..2fe85d65aff 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
@@ -42,7 +42,7 @@ function _SignUpStart(): JSX.Element {
const options = useOptions();
const isWithinSignInContext = !!React.useContext(SignInContext);
const { afterSignUpUrl, signInUrl, unsafeMetadata } = ctx;
- const isCombinedFlow = (options.experimental?.combinedFlow && !!isWithinSignInContext) || false;
+ const isCombinedFlow = !!(options.experimental?.combinedFlow && !!isWithinSignInContext);
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
From 697bc380c3288183379c2ae61fe33f77cd1a152d Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Thu, 5 Dec 2024 13:04:53 -0500
Subject: [PATCH 28/29] Update
packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
Co-authored-by: Bryce Kalow
---
packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
index e875b367b44..483cfd5eb23 100644
--- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
+++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
@@ -69,7 +69,7 @@ export function _SignInStart(): JSX.Element {
const options = useOptions();
const ctx = useSignInContext();
const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
- const isCombinedFlow = options?.experimental?.combinedFlow || false;
+ const isCombinedFlow = !!options?.experimental?.combinedFlow;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
From 742be9eef06a090ed89f3587bdf18315f40157ec Mon Sep 17 00:00:00 2001
From: Alex Carpenter
Date: Thu, 5 Dec 2024 13:04:59 -0500
Subject: [PATCH 29/29] Update
packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
Co-authored-by: Bryce Kalow
---
packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
index 6e507a7ed9a..63b3748a2a3 100644
--- a/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
+++ b/packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
@@ -35,7 +35,7 @@ function _SignUpContinue() {
const signUp = useCoreSignUp();
const options = useOptions();
const isWithinSignInContext = !!React.useContext(SignInContext);
- const isCombinedFlow = (options.experimental?.combinedFlow && !!isWithinSignInContext) || false;
+ const isCombinedFlow = !!(options.experimental?.combinedFlow && !!isWithinSignInContext);
const isProgressiveSignUp = userSettings.signUp.progressive;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),