From 77847488e4bd7bdf259f28f77e81edfdd4c55dfb Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Fri, 3 May 2024 15:13:53 -0400 Subject: [PATCH 1/8] chore(elements): Add changeset --- .changeset/neat-eels-wonder.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/neat-eels-wonder.md diff --git a/.changeset/neat-eels-wonder.md b/.changeset/neat-eels-wonder.md new file mode 100644 index 00000000000..69316f59f35 --- /dev/null +++ b/.changeset/neat-eels-wonder.md @@ -0,0 +1,5 @@ +--- +'@clerk/elements': patch +--- + +Handle Sign In xstate child machines via invoke vs spawn and React useEffect resolving standing race conditions From a396f264c8e49c0494b397fb58807f00638971a4 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 7 May 2024 15:47:08 -0400 Subject: [PATCH 2/8] Apply suggestions from code review Co-authored-by: Bryce Kalow --- .changeset/neat-eels-wonder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/neat-eels-wonder.md b/.changeset/neat-eels-wonder.md index 69316f59f35..857f5793006 100644 --- a/.changeset/neat-eels-wonder.md +++ b/.changeset/neat-eels-wonder.md @@ -2,4 +2,4 @@ '@clerk/elements': patch --- -Handle Sign In xstate child machines via invoke vs spawn and React useEffect resolving standing race conditions +Refactors internal logic to avoid reliance on `useEffect`. This resolves potential for race conditions as a result of functionality coupled to component renders. From ca95d846d7de740c05f942c7baa58e10da55be2e Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 7 May 2024 15:41:15 -0400 Subject: [PATCH 3/8] feat(elements): Add `complete` step to Sign Up and Sign In. [SDK-1712] --- .changeset/nervous-fans-do.md | 16 ++++++++++++ .../app/sign-in/[[...sign-in]]/page.tsx | 4 +++ .../app/sign-up/[[...sign-up]]/page.tsx | 6 ++++- .../machines/sign-in/router.machine.ts | 26 ++++++++++++++----- .../machines/sign-in/router.types.ts | 1 + .../machines/sign-up/router.machine.ts | 13 ++++++++++ .../machines/sign-up/router.types.ts | 1 + .../elements/src/react/common/loading.tsx | 4 +++ .../elements/src/react/sign-in/complete.tsx | 13 ++++++++++ packages/elements/src/react/sign-in/root.tsx | 9 ++++--- packages/elements/src/react/sign-in/step.tsx | 11 ++++++-- .../elements/src/react/sign-up/complete.tsx | 13 ++++++++++ packages/elements/src/react/sign-up/root.tsx | 9 ++++--- packages/elements/src/react/sign-up/step.tsx | 11 ++++++-- 14 files changed, 119 insertions(+), 18 deletions(-) create mode 100644 .changeset/nervous-fans-do.md create mode 100644 packages/elements/src/react/sign-in/complete.tsx create mode 100644 packages/elements/src/react/sign-up/complete.tsx diff --git a/.changeset/nervous-fans-do.md b/.changeset/nervous-fans-do.md new file mode 100644 index 00000000000..18c011262fb --- /dev/null +++ b/.changeset/nervous-fans-do.md @@ -0,0 +1,16 @@ +--- +'@clerk/elements': patch +--- + +Add a `complete` step to Sign In and Sign Up flows for when a user is being set as active and has yet to be redirected to the desired URL. + +```tsx + + Welcome back! You're being redirected... + +``` +```tsx + + Welcome! You're being redirected... +` +``` diff --git a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx index dfaafd36a21..7380097b5d0 100644 --- a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx @@ -387,6 +387,10 @@ export default function SignInPage() { Update Password + + +

You're being logged in!

+
); diff --git a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx index 1b9197f9c05..c64b7b83328 100644 --- a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx @@ -4,7 +4,7 @@ import * as Clerk from '@clerk/elements/common'; import * as SignUp from '@clerk/elements/sign-up'; import type { ComponentProps } from 'react'; -import { H1, HR as Hr, P } from '@/components/design'; +import { H1, H3, HR as Hr, P } from '@/components/design'; import { CustomField } from '@/components/form'; import { Spinner } from '@/components/spinner'; @@ -197,6 +197,10 @@ export default function SignUpPage() { + + +

You're being logged in!

+
); diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts index b8ea5b6a15b..85b07c32a92 100644 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/router.machine.ts @@ -221,6 +221,11 @@ export const SignInRouterMachine = setup({ guard: 'needsCallback', target: 'Callback', }, + { + guard: 'isComplete', + actions: 'setActive', + target: 'Complete', + }, { guard: 'isLoggedInAndSingleSession', actions: [ @@ -280,7 +285,7 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Start', + target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -318,7 +323,7 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Start', + target: 'Complete', }, { guard: 'statusNeedsSecondFactor', @@ -385,7 +390,7 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Start', + target: 'Complete', }, { guard: 'statusNeedsNewPassword', @@ -413,7 +418,7 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Start', + target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -429,7 +434,7 @@ export const SignInRouterMachine = setup({ }, }, Callback: { - tags: 'route:callback', + tags: 'route:complete', entry: sendTo(ThirdPartyMachineId, { type: 'CALLBACK' }), on: { NEXT: [ @@ -439,9 +444,9 @@ export const SignInRouterMachine = setup({ target: 'Start', }, { - guard: or(['isComplete', 'hasAuthenticatedViaClerkJS']), + guard: or(['isLoggedIn', 'isComplete', 'hasAuthenticatedViaClerkJS']), actions: 'setActive', - target: 'Start', + target: 'Complete', }, { guard: 'statusNeedsIdentifier', @@ -465,6 +470,13 @@ export const SignInRouterMachine = setup({ ], }, }, + Complete: { + tags: 'route:complete', + entry: 'clearFormErrors', + after: { + 5000: 'Start', + }, + }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts index 50a9257d6da..77a7d06b34e 100644 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ b/packages/elements/src/internals/machines/sign-in/router.types.ts @@ -28,6 +28,7 @@ export const SignInRouterRoutes = { forgotPassword: 'route:forgot-password', resetPassword: 'route:reset-password', chooseStrategy: 'route:choose-strategy', + complete: 'route:complete', } as const; export type SignInRouterRoutes = keyof typeof SignInRouterRoutes; diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts index 9359ff57862..817b1563d34 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -264,6 +264,7 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', + target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -297,6 +298,7 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', + target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -327,6 +329,7 @@ export const SignUpRouterMachine = setup({ type: 'setActive', params: { sessionId: context.router?.searchParams().get(SEARCH_PARAMS.createdSession) }, }), + target: 'Complete', }, { guard: { type: 'hasClerkStatus', params: { status: 'verified' } }, @@ -342,6 +345,7 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', + target: 'Complete', }, { guard: 'statusNeedsContinue', @@ -359,11 +363,13 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', + target: 'Complete', }, { description: 'Handle a case where the user has already been authenticated via ClerkJS', guard: 'hasAuthenticatedViaClerkJS', actions: { type: 'setActive', params: { useLastActiveSession: true } }, + target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -382,6 +388,13 @@ export const SignUpRouterMachine = setup({ ], }, }, + Complete: { + tags: 'route:complete', + entry: 'clearFormErrors', + after: { + 5000: 'Start', + }, + }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts index 8c359f7e7ea..e14b0d40ef5 100644 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ b/packages/elements/src/internals/machines/sign-up/router.types.ts @@ -23,6 +23,7 @@ export const SignUpRouterRoutes = { verification: 'route:verification', callback: 'route:callback', error: 'route:error', + complete: 'route:complete', } as const; export type SignUpRouterRoutes = keyof typeof SignUpRouterRoutes; diff --git a/packages/elements/src/react/common/loading.tsx b/packages/elements/src/react/common/loading.tsx index 60483f5fef4..298a16bce3f 100644 --- a/packages/elements/src/react/common/loading.tsx +++ b/packages/elements/src/react/common/loading.tsx @@ -189,6 +189,8 @@ function SignInLoading({ children, scope, routerRef }: SignInLoadingProps) { returnValue = isForgotPasswordLoading; } else if (computedScope === 'step:reset-password') { returnValue = isResetPasswordLoading; + } else if (computedScope === 'step:complete') { + returnValue = false; } else if (computedScope.startsWith('provider:')) { const computedStrategy = mapScopeToStrategy(computedScope); returnValue = isStrategyLoading && strategy === computedStrategy; @@ -249,6 +251,8 @@ function SignUpLoading({ children, scope, routerRef }: SignUpLoadingProps) { returnValue = isVerificationsLoading; } else if (computedScope === 'step:continue') { returnValue = isContinueLoading; + } else if (computedScope === 'step:complete') { + returnValue = false; // Not necessary for complete step } else if (computedScope.startsWith('provider:')) { const computedStrategy = mapScopeToStrategy(computedScope); returnValue = isStrategyLoading && strategy === computedStrategy; diff --git a/packages/elements/src/react/sign-in/complete.tsx b/packages/elements/src/react/sign-in/complete.tsx new file mode 100644 index 00000000000..160b559c253 --- /dev/null +++ b/packages/elements/src/react/sign-in/complete.tsx @@ -0,0 +1,13 @@ +import { useActiveTags } from '~/react/hooks'; +import { SignInRouterCtx } from '~/react/sign-in/context'; + +export type SignInCompleteProps = { + children: React.ReactNode; +}; + +export function SignInComplete({ children }: SignInCompleteProps) { + const routerRef = SignInRouterCtx.useActorRef(); + const activeState = useActiveTags(routerRef, 'route:complete'); + + return activeState ? <>{children} : null; +} diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index 7453eb7d156..8a782caa7e2 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -94,6 +94,7 @@ export function SignInRoot({ // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js const router = useNextRouter(); + const isRootPath = path === router.pathname(); return ( - -
{fallback}
-
+ {isRootPath ? ( + +
{fallback}
+
+ ) : null} {children}
diff --git a/packages/elements/src/react/sign-in/step.tsx b/packages/elements/src/react/sign-in/step.tsx index 36e04cbe7f1..d4b85badd40 100644 --- a/packages/elements/src/react/sign-in/step.tsx +++ b/packages/elements/src/react/sign-in/step.tsx @@ -5,6 +5,8 @@ import { ClerkElementsRuntimeError } from '~/internals/errors'; import type { SignInChooseStrategyProps } from './choose-strategy'; import { SignInChooseStrategy, SignInForgotPassword } from './choose-strategy'; +import type { SignInCompleteProps } from './complete'; +import { SignInComplete } from './complete'; import type { SignInResetPasswordProps } from './reset-password'; import { SignInResetPassword } from './reset-password'; import type { SignInStartProps } from './start'; @@ -18,6 +20,7 @@ export const SIGN_IN_STEPS = { 'choose-strategy': 'choose-strategy', 'forgot-password': 'forgot-password', 'reset-password': 'reset-password', + complete: 'complete', } as const; export type TSignInStep = (typeof SIGN_IN_STEPS)[keyof typeof SIGN_IN_STEPS]; @@ -27,14 +30,15 @@ export type SignInStepProps = | StepWithProps<'start', SignInStartProps> | StepWithProps<'verifications', SignInVerificationsProps> | StepWithProps<'choose-strategy' | 'forgot-password', SignInChooseStrategyProps> - | StepWithProps<'reset-password', SignInResetPasswordProps>; + | StepWithProps<'reset-password', SignInResetPasswordProps> + | StepWithProps<'complete', SignInCompleteProps>; /** * Render different steps of the sign-in flow. Initially the `'start'` step is rendered. Once a sign-in attempt has been created, `'verifications'` will be displayed. If during that verification step the user decides to choose a different method of signing in or verifying, the `'choose-strategy'` step will be displayed. * * You typically want to place fields like username, password, or social providers in the `'start'` step. The `'verifications'` step is used to verify the user's credentials like password or MFA. Once the user has been verified, the sign-in attempt will be completed. * - * @param {string} name - Step name. Use `'start'`, `'verifications'`, `'choose-strategy'`, `'reset-password'`, or `'forgot-password'`. + * @param {string} name - Step name. Use `'start'`, `'verifications'`, `'choose-strategy'`, `'reset-password'`, `'forgot-password'`, or `'complete'`. * * @example * @@ -43,6 +47,7 @@ export type SignInStepProps = * * * + * * */ export function SignInStep(props: SignInStepProps) { @@ -61,6 +66,8 @@ export function SignInStep(props: SignInStepProps) { return ; case SIGN_IN_STEPS['reset-password']: return ; + case SIGN_IN_STEPS['complete']: + return ; default: throw new ClerkElementsRuntimeError(`Invalid step name. Use: ${Object.keys(SIGN_IN_STEPS).join(',')}.`); } diff --git a/packages/elements/src/react/sign-up/complete.tsx b/packages/elements/src/react/sign-up/complete.tsx new file mode 100644 index 00000000000..83406495c0b --- /dev/null +++ b/packages/elements/src/react/sign-up/complete.tsx @@ -0,0 +1,13 @@ +import { useActiveTags } from '~/react/hooks'; +import { SignUpRouterCtx } from '~/react/sign-up/context'; + +export type SignUpCompleteProps = { + children: React.ReactNode; +}; + +export function SignUpComplete({ children }: SignUpCompleteProps) { + const routerRef = SignUpRouterCtx.useActorRef(); + const activeState = useActiveTags(routerRef, 'route:complete'); + + return activeState ? <>{children} : null; +} diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index 99380cd0144..6ef2883fe78 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -91,6 +91,7 @@ export function SignUpRoot({ // TODO: eventually we'll rely on the framework SDK to specify its host router, but for now we'll default to Next.js const router = useNextRouter(); + const isRootPath = path === router.pathname(); return ( - -
{fallback}
-
+ {isRootPath ? ( + +
{fallback}
+
+ ) : null} {children}
diff --git a/packages/elements/src/react/sign-up/step.tsx b/packages/elements/src/react/sign-up/step.tsx index cd7c7788e1b..94f9820473c 100644 --- a/packages/elements/src/react/sign-up/step.tsx +++ b/packages/elements/src/react/sign-up/step.tsx @@ -3,6 +3,8 @@ import { eventComponentMounted } from '@clerk/shared/telemetry'; import { ClerkElementsRuntimeError } from '~/internals/errors'; +import type { SignUpCompleteProps } from './complete'; +import { SignUpComplete } from './complete'; import type { SignUpContinueProps } from './continue'; import { SignUpContinue } from './continue'; import type { SignUpStartProps } from './start'; @@ -14,6 +16,7 @@ export const SIGN_UP_STEPS = { start: 'start', continue: 'continue', verifications: 'verifications', + complete: 'complete', } as const; export type TSignUpStep = (typeof SIGN_UP_STEPS)[keyof typeof SIGN_UP_STEPS]; @@ -22,20 +25,22 @@ type StepWithProps = { name: N } & T; export type SignUpStepProps = | StepWithProps<'start', SignUpStartProps> | StepWithProps<'continue', SignUpContinueProps> - | StepWithProps<'verifications', SignUpVerificationsProps>; + | StepWithProps<'verifications', SignUpVerificationsProps> + | StepWithProps<'complete', SignUpCompleteProps>; /** * Render different steps of the sign-up flow. Initially the `'start'` step is rendered. Optionally, you can render additional fields in the `'continue'` step. Once a sign-up attempt has been created, `'verifications'` will be displayed. * * You typically want to place fields like username, password, or social providers in the `'start'` step. The `'continue'` step can hold inputs for username, first name/last name or other metadata. The `'verifications'` step is used to verify the user's information like an email verification. Once the user has been verified, the sign-up attempt will be completed. * - * @param {string} name - Step name. Use `'start'`, `'continue'`, or `'verifications'`. + * @param {string} name - Step name. Use `'start'`, `'continue'`, `'verifications'`, or `'complete'`. * * @example * * * * + * * */ export function SignUpStep(props: SignUpStepProps) { @@ -50,6 +55,8 @@ export function SignUpStep(props: SignUpStepProps) { return ; case SIGN_UP_STEPS.verifications: return ; + case SIGN_UP_STEPS.complete: + return ; default: throw new ClerkElementsRuntimeError(`Invalid step name. Use 'start', 'continue', or 'verifications'.`); } From fcc3e739441da0057493b585fe778846c783fd8d Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Tue, 7 May 2024 15:50:57 -0400 Subject: [PATCH 4/8] chore(elements): Remove comment --- packages/elements/src/react/common/loading.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elements/src/react/common/loading.tsx b/packages/elements/src/react/common/loading.tsx index 298a16bce3f..480a2f95b1f 100644 --- a/packages/elements/src/react/common/loading.tsx +++ b/packages/elements/src/react/common/loading.tsx @@ -252,7 +252,7 @@ function SignUpLoading({ children, scope, routerRef }: SignUpLoadingProps) { } else if (computedScope === 'step:continue') { returnValue = isContinueLoading; } else if (computedScope === 'step:complete') { - returnValue = false; // Not necessary for complete step + returnValue = false; } else if (computedScope.startsWith('provider:')) { const computedStrategy = mapScopeToStrategy(computedScope); returnValue = isStrategyLoading && strategy === computedStrategy; From 7e74b2749c28b31aca3e4fcb4b315a4359962e72 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Mon, 13 May 2024 15:00:16 -0400 Subject: [PATCH 5/8] chore(elements): Remove complete step --- .changeset/neat-eels-wonder.md | 5 ----- .changeset/nervous-fans-do.md | 14 ++------------ .../nextjs/app/sign-in/[[...sign-in]]/page.tsx | 4 ---- .../nextjs/app/sign-up/[[...sign-up]]/page.tsx | 6 +----- .../internals/machines/sign-in/router.machine.ts | 2 +- .../src/internals/machines/sign-in/router.types.ts | 1 - .../src/internals/machines/sign-up/router.types.ts | 1 - packages/elements/src/react/common/loading.tsx | 4 ---- packages/elements/src/react/sign-in/complete.tsx | 13 ------------- packages/elements/src/react/sign-in/step.tsx | 11 ++--------- packages/elements/src/react/sign-up/complete.tsx | 13 ------------- packages/elements/src/react/sign-up/step.tsx | 9 +-------- 12 files changed, 7 insertions(+), 76 deletions(-) delete mode 100644 .changeset/neat-eels-wonder.md delete mode 100644 packages/elements/src/react/sign-in/complete.tsx delete mode 100644 packages/elements/src/react/sign-up/complete.tsx diff --git a/.changeset/neat-eels-wonder.md b/.changeset/neat-eels-wonder.md deleted file mode 100644 index 857f5793006..00000000000 --- a/.changeset/neat-eels-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@clerk/elements': patch ---- - -Refactors internal logic to avoid reliance on `useEffect`. This resolves potential for race conditions as a result of functionality coupled to component renders. diff --git a/.changeset/nervous-fans-do.md b/.changeset/nervous-fans-do.md index 18c011262fb..841454ae233 100644 --- a/.changeset/nervous-fans-do.md +++ b/.changeset/nervous-fans-do.md @@ -2,15 +2,5 @@ '@clerk/elements': patch --- -Add a `complete` step to Sign In and Sign Up flows for when a user is being set as active and has yet to be redirected to the desired URL. - -```tsx - - Welcome back! You're being redirected... - -``` -```tsx - - Welcome! You're being redirected... -` -``` +- The sign-up/in fallback prop isn't presented if the user isn't visiting the root (start) route. +- Upon completion of sign-up/in, don't immediately return to the start step. diff --git a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx index 7380097b5d0..dfaafd36a21 100644 --- a/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx @@ -387,10 +387,6 @@ export default function SignInPage() { Update Password - - -

You're being logged in!

-
); diff --git a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx index c64b7b83328..1b9197f9c05 100644 --- a/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx +++ b/packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx @@ -4,7 +4,7 @@ import * as Clerk from '@clerk/elements/common'; import * as SignUp from '@clerk/elements/sign-up'; import type { ComponentProps } from 'react'; -import { H1, H3, HR as Hr, P } from '@/components/design'; +import { H1, HR as Hr, P } from '@/components/design'; import { CustomField } from '@/components/form'; import { Spinner } from '@/components/spinner'; @@ -197,10 +197,6 @@ export default function SignUpPage() { - - -

You're being logged in!

-
); diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts index 85b07c32a92..31af06866ab 100644 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/router.machine.ts @@ -434,7 +434,7 @@ export const SignInRouterMachine = setup({ }, }, Callback: { - tags: 'route:complete', + tags: 'route:callback', entry: sendTo(ThirdPartyMachineId, { type: 'CALLBACK' }), on: { NEXT: [ diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts index 77a7d06b34e..50a9257d6da 100644 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ b/packages/elements/src/internals/machines/sign-in/router.types.ts @@ -28,7 +28,6 @@ export const SignInRouterRoutes = { forgotPassword: 'route:forgot-password', resetPassword: 'route:reset-password', chooseStrategy: 'route:choose-strategy', - complete: 'route:complete', } as const; export type SignInRouterRoutes = keyof typeof SignInRouterRoutes; diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts index e14b0d40ef5..8c359f7e7ea 100644 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ b/packages/elements/src/internals/machines/sign-up/router.types.ts @@ -23,7 +23,6 @@ export const SignUpRouterRoutes = { verification: 'route:verification', callback: 'route:callback', error: 'route:error', - complete: 'route:complete', } as const; export type SignUpRouterRoutes = keyof typeof SignUpRouterRoutes; diff --git a/packages/elements/src/react/common/loading.tsx b/packages/elements/src/react/common/loading.tsx index 480a2f95b1f..60483f5fef4 100644 --- a/packages/elements/src/react/common/loading.tsx +++ b/packages/elements/src/react/common/loading.tsx @@ -189,8 +189,6 @@ function SignInLoading({ children, scope, routerRef }: SignInLoadingProps) { returnValue = isForgotPasswordLoading; } else if (computedScope === 'step:reset-password') { returnValue = isResetPasswordLoading; - } else if (computedScope === 'step:complete') { - returnValue = false; } else if (computedScope.startsWith('provider:')) { const computedStrategy = mapScopeToStrategy(computedScope); returnValue = isStrategyLoading && strategy === computedStrategy; @@ -251,8 +249,6 @@ function SignUpLoading({ children, scope, routerRef }: SignUpLoadingProps) { returnValue = isVerificationsLoading; } else if (computedScope === 'step:continue') { returnValue = isContinueLoading; - } else if (computedScope === 'step:complete') { - returnValue = false; } else if (computedScope.startsWith('provider:')) { const computedStrategy = mapScopeToStrategy(computedScope); returnValue = isStrategyLoading && strategy === computedStrategy; diff --git a/packages/elements/src/react/sign-in/complete.tsx b/packages/elements/src/react/sign-in/complete.tsx deleted file mode 100644 index 160b559c253..00000000000 --- a/packages/elements/src/react/sign-in/complete.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useActiveTags } from '~/react/hooks'; -import { SignInRouterCtx } from '~/react/sign-in/context'; - -export type SignInCompleteProps = { - children: React.ReactNode; -}; - -export function SignInComplete({ children }: SignInCompleteProps) { - const routerRef = SignInRouterCtx.useActorRef(); - const activeState = useActiveTags(routerRef, 'route:complete'); - - return activeState ? <>{children} : null; -} diff --git a/packages/elements/src/react/sign-in/step.tsx b/packages/elements/src/react/sign-in/step.tsx index d4b85badd40..36e04cbe7f1 100644 --- a/packages/elements/src/react/sign-in/step.tsx +++ b/packages/elements/src/react/sign-in/step.tsx @@ -5,8 +5,6 @@ import { ClerkElementsRuntimeError } from '~/internals/errors'; import type { SignInChooseStrategyProps } from './choose-strategy'; import { SignInChooseStrategy, SignInForgotPassword } from './choose-strategy'; -import type { SignInCompleteProps } from './complete'; -import { SignInComplete } from './complete'; import type { SignInResetPasswordProps } from './reset-password'; import { SignInResetPassword } from './reset-password'; import type { SignInStartProps } from './start'; @@ -20,7 +18,6 @@ export const SIGN_IN_STEPS = { 'choose-strategy': 'choose-strategy', 'forgot-password': 'forgot-password', 'reset-password': 'reset-password', - complete: 'complete', } as const; export type TSignInStep = (typeof SIGN_IN_STEPS)[keyof typeof SIGN_IN_STEPS]; @@ -30,15 +27,14 @@ export type SignInStepProps = | StepWithProps<'start', SignInStartProps> | StepWithProps<'verifications', SignInVerificationsProps> | StepWithProps<'choose-strategy' | 'forgot-password', SignInChooseStrategyProps> - | StepWithProps<'reset-password', SignInResetPasswordProps> - | StepWithProps<'complete', SignInCompleteProps>; + | StepWithProps<'reset-password', SignInResetPasswordProps>; /** * Render different steps of the sign-in flow. Initially the `'start'` step is rendered. Once a sign-in attempt has been created, `'verifications'` will be displayed. If during that verification step the user decides to choose a different method of signing in or verifying, the `'choose-strategy'` step will be displayed. * * You typically want to place fields like username, password, or social providers in the `'start'` step. The `'verifications'` step is used to verify the user's credentials like password or MFA. Once the user has been verified, the sign-in attempt will be completed. * - * @param {string} name - Step name. Use `'start'`, `'verifications'`, `'choose-strategy'`, `'reset-password'`, `'forgot-password'`, or `'complete'`. + * @param {string} name - Step name. Use `'start'`, `'verifications'`, `'choose-strategy'`, `'reset-password'`, or `'forgot-password'`. * * @example * @@ -47,7 +43,6 @@ export type SignInStepProps = * * * - * * */ export function SignInStep(props: SignInStepProps) { @@ -66,8 +61,6 @@ export function SignInStep(props: SignInStepProps) { return ; case SIGN_IN_STEPS['reset-password']: return ; - case SIGN_IN_STEPS['complete']: - return ; default: throw new ClerkElementsRuntimeError(`Invalid step name. Use: ${Object.keys(SIGN_IN_STEPS).join(',')}.`); } diff --git a/packages/elements/src/react/sign-up/complete.tsx b/packages/elements/src/react/sign-up/complete.tsx deleted file mode 100644 index 83406495c0b..00000000000 --- a/packages/elements/src/react/sign-up/complete.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useActiveTags } from '~/react/hooks'; -import { SignUpRouterCtx } from '~/react/sign-up/context'; - -export type SignUpCompleteProps = { - children: React.ReactNode; -}; - -export function SignUpComplete({ children }: SignUpCompleteProps) { - const routerRef = SignUpRouterCtx.useActorRef(); - const activeState = useActiveTags(routerRef, 'route:complete'); - - return activeState ? <>{children} : null; -} diff --git a/packages/elements/src/react/sign-up/step.tsx b/packages/elements/src/react/sign-up/step.tsx index 94f9820473c..9132a0f85fc 100644 --- a/packages/elements/src/react/sign-up/step.tsx +++ b/packages/elements/src/react/sign-up/step.tsx @@ -3,8 +3,6 @@ import { eventComponentMounted } from '@clerk/shared/telemetry'; import { ClerkElementsRuntimeError } from '~/internals/errors'; -import type { SignUpCompleteProps } from './complete'; -import { SignUpComplete } from './complete'; import type { SignUpContinueProps } from './continue'; import { SignUpContinue } from './continue'; import type { SignUpStartProps } from './start'; @@ -16,7 +14,6 @@ export const SIGN_UP_STEPS = { start: 'start', continue: 'continue', verifications: 'verifications', - complete: 'complete', } as const; export type TSignUpStep = (typeof SIGN_UP_STEPS)[keyof typeof SIGN_UP_STEPS]; @@ -25,8 +22,7 @@ type StepWithProps = { name: N } & T; export type SignUpStepProps = | StepWithProps<'start', SignUpStartProps> | StepWithProps<'continue', SignUpContinueProps> - | StepWithProps<'verifications', SignUpVerificationsProps> - | StepWithProps<'complete', SignUpCompleteProps>; + | StepWithProps<'verifications', SignUpVerificationsProps>; /** * Render different steps of the sign-up flow. Initially the `'start'` step is rendered. Optionally, you can render additional fields in the `'continue'` step. Once a sign-up attempt has been created, `'verifications'` will be displayed. @@ -40,7 +36,6 @@ export type SignUpStepProps = * * * - * * */ export function SignUpStep(props: SignUpStepProps) { @@ -55,8 +50,6 @@ export function SignUpStep(props: SignUpStepProps) { return ; case SIGN_UP_STEPS.verifications: return ; - case SIGN_UP_STEPS.complete: - return ; default: throw new ClerkElementsRuntimeError(`Invalid step name. Use 'start', 'continue', or 'verifications'.`); } From b1aad9c896d0c5287cb39b3432489448328157b7 Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Mon, 13 May 2024 17:17:58 -0400 Subject: [PATCH 6/8] chore(elements): Update sign-in Step docs --- packages/elements/src/react/sign-up/step.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elements/src/react/sign-up/step.tsx b/packages/elements/src/react/sign-up/step.tsx index 9132a0f85fc..cd7c7788e1b 100644 --- a/packages/elements/src/react/sign-up/step.tsx +++ b/packages/elements/src/react/sign-up/step.tsx @@ -29,7 +29,7 @@ export type SignUpStepProps = * * You typically want to place fields like username, password, or social providers in the `'start'` step. The `'continue'` step can hold inputs for username, first name/last name or other metadata. The `'verifications'` step is used to verify the user's information like an email verification. Once the user has been verified, the sign-up attempt will be completed. * - * @param {string} name - Step name. Use `'start'`, `'continue'`, `'verifications'`, or `'complete'`. + * @param {string} name - Step name. Use `'start'`, `'continue'`, or `'verifications'`. * * @example * From a60c229738642789292e1caba87f8f15435be34a Mon Sep 17 00:00:00 2001 From: Tom Milewski Date: Mon, 13 May 2024 17:19:38 -0400 Subject: [PATCH 7/8] fix(elements): Fix follow-on initialization discrepancies (#3377) * fix(elements): Follow-on machine initialization [SDK-1746] * chore(elements): Add changeset * chore(elements): Remove unnecessary TS comment * chore(elements): Add line item to changeset --- .changeset/odd-coins-carry.md | 7 ++++ package-lock.json | 8 ++-- packages/elements/package.json | 2 +- .../machines/sign-in/router.machine.ts | 24 ++++-------- .../machines/sign-in/router.types.ts | 3 ++ .../machines/sign-up/router.machine.ts | 37 ++++++++----------- .../machines/sign-up/router.types.ts | 3 ++ .../internals/machines/types/router.types.ts | 1 + packages/elements/src/react/sign-in/root.tsx | 4 +- .../src/react/sign-in/verifications.tsx | 2 +- packages/elements/src/react/sign-up/root.tsx | 3 +- 11 files changed, 48 insertions(+), 46 deletions(-) create mode 100644 .changeset/odd-coins-carry.md diff --git a/.changeset/odd-coins-carry.md b/.changeset/odd-coins-carry.md new file mode 100644 index 00000000000..54f4427c7ae --- /dev/null +++ b/.changeset/odd-coins-carry.md @@ -0,0 +1,7 @@ +--- +'@clerk/elements': patch +--- + +- Bump XState from version 5.12.0 to 5.13.0 +- We now maintain your current state while completing authentication redirecting rather than transitioning to a "Complete" step. +- Fixes Sign In and Sign Up machines not appropriately attaching new form machines when returning to the Sign In or Sign Up pages. diff --git a/package-lock.json b/package-lock.json index 012dd48c1be..9599a2b80e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38410,7 +38410,7 @@ "@statelyai/inspect": "^0.3.0", "@xstate/react": "^4.1.1", "client-only": "^0.0.1", - "xstate": "^5.12.0" + "xstate": "^5.13.0" }, "devDependencies": { "@clerk/clerk-react": "5.0.4", @@ -38629,9 +38629,9 @@ } }, "packages/elements/node_modules/xstate": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.12.0.tgz", - "integrity": "sha512-4W/Hj553mwVnTLQ1itc3rni/cGtM5OkjyavTjaxCelc0ZZKE/ks6tYllc98KbekIoUrEPm4CJH/wTB5p5pPGEw==", + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.13.0.tgz", + "integrity": "sha512-Z0om784N5u8sAzUvQJBa32jiTCIGGF/2ZsmKkerQEqeeUktAeOMK20FIHFUMywC4GcAkNksSvaeX7lwoRNXPEQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/xstate" diff --git a/packages/elements/package.json b/packages/elements/package.json index be75485282b..17f3c2d6792 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -74,7 +74,7 @@ "@statelyai/inspect": "^0.3.0", "@xstate/react": "^4.1.1", "client-only": "^0.0.1", - "xstate": "^5.12.0" + "xstate": "^5.13.0" }, "devDependencies": { "@clerk/clerk-react": "5.0.4", diff --git a/packages/elements/src/internals/machines/sign-in/router.machine.ts b/packages/elements/src/internals/machines/sign-in/router.machine.ts index 31af06866ab..8356414e455 100644 --- a/packages/elements/src/internals/machines/sign-in/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-in/router.machine.ts @@ -13,6 +13,7 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; +import { FormMachine } from '../form'; import { SignInResetPasswordMachine } from './reset-password.machine'; import type { SignInRouterContext, @@ -41,6 +42,7 @@ export const SignInRouterMachineId = 'SignInRouter'; export const SignInRouterMachine = setup({ actors: { firstFactorMachine: SignInFirstFactorMachine, + formMachine: FormMachine, resetPasswordMachine: SignInResetPasswordMachine, startMachine: SignInStartMachine, secondFactorMachine: SignInSecondFactorMachine, @@ -60,8 +62,8 @@ export const SignInRouterMachine = setup({ }, navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), raiseNext: raise({ type: 'NEXT' }), - setActive({ context, event }) { - if (context.exampleMode) return; + setActive: enqueueActions(({ enqueue, check, context, event }) => { + if (check('isExampleMode')) return; const lastActiveSessionId = context.clerk.client.lastActiveSessionId; const createdSessionId = ((event as SignInRouterNextEvent)?.resource || context.clerk.client.signIn) @@ -71,7 +73,9 @@ export const SignInRouterMachine = setup({ const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignInUrl()); void context.clerk.setActive({ session, beforeEmit }); - }, + + enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay. + }), setError: assign({ error: (_, { error }: { error?: ClerkElementsError }) => { if (error) return error; @@ -182,6 +186,7 @@ export const SignInRouterMachine = setup({ }, })), }, + RESET: '.Idle', }, states: { Idle: { @@ -224,7 +229,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'isLoggedInAndSingleSession', @@ -285,7 +289,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -323,7 +326,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsSecondFactor', @@ -390,7 +392,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsNewPassword', @@ -418,7 +419,6 @@ export const SignInRouterMachine = setup({ { guard: 'isComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsFirstFactor', @@ -446,7 +446,6 @@ export const SignInRouterMachine = setup({ { guard: or(['isLoggedIn', 'isComplete', 'hasAuthenticatedViaClerkJS']), actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsIdentifier', @@ -470,13 +469,6 @@ export const SignInRouterMachine = setup({ ], }, }, - Complete: { - tags: 'route:complete', - entry: 'clearFormErrors', - after: { - 5000: 'Start', - }, - }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-in/router.types.ts b/packages/elements/src/internals/machines/sign-in/router.types.ts index 50a9257d6da..ada057c95c9 100644 --- a/packages/elements/src/internals/machines/sign-in/router.types.ts +++ b/packages/elements/src/internals/machines/sign-in/router.types.ts @@ -10,6 +10,7 @@ import type { BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, + BaseRouterResetEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -54,6 +55,7 @@ export type SignInRouterForgotPasswordEvent = { type: 'NAVIGATE.FORGOT_PASSWORD' export type SignInRouterErrorEvent = BaseRouterErrorEvent; export type SignInRouterTransferEvent = BaseRouterTransferEvent; export type SignInRouterRedirectEvent = BaseRouterRedirectEvent; +export type SignInRouterResetEvent = BaseRouterResetEvent; export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'reset-password'>; export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent; export type SignInRouterSubmitEvent = { type: 'SUBMIT' }; @@ -77,6 +79,7 @@ export type SignInRouterEvents = | SignInRouterErrorEvent | SignInRouterTransferEvent | SignInRouterRedirectEvent + | SignInRouterResetEvent | SignInVerificationFactorUpdateEvent | SignInRouterLoadingEvent | SignInRouterSetClerkEvent diff --git a/packages/elements/src/internals/machines/sign-up/router.machine.ts b/packages/elements/src/internals/machines/sign-up/router.machine.ts index 817b1563d34..8659db35265 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -1,7 +1,7 @@ import { joinURL } from '@clerk/shared/url'; import type { SignUpStatus, VerificationStatus } from '@clerk/types'; import type { NonReducibleUnknown } from 'xstate'; -import { and, assign, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate'; +import { and, assign, enqueueActions, log, not, or, raise, sendTo, setup, spawnChild } from 'xstate'; import { ERROR_CODES, @@ -59,15 +59,20 @@ export const SignUpRouterMachine = setup({ }, navigateExternal: ({ context }, { path }: { path: string }) => context.router?.push(path), raiseNext: raise({ type: 'NEXT' }), - setActive({ context, event }, params?: { sessionId?: string; useLastActiveSession?: boolean }) { - const session = - params?.sessionId || - (params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) || - ((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId; + setActive: (_, params?: { sessionId?: string; useLastActiveSession?: boolean }) => + enqueueActions(({ enqueue, check, context, event }) => { + if (check('isExampleMode')) return; - const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl()); - void context.clerk.setActive({ session, beforeEmit }); - }, + const session = + params?.sessionId || + (params?.useLastActiveSession && context.clerk.client.lastActiveSessionId) || + ((event as SignUpRouterNextEvent)?.resource || context.clerk.client.signUp).createdSessionId; + + const beforeEmit = () => context.router?.push(context.clerk.buildAfterSignUpUrl()); + void context.clerk.setActive({ session, beforeEmit }); + + enqueue.raise({ type: 'RESET' }, { delay: 2000 }); // Reset machine after 2s delay. + }), setError: assign({ error: (_, { error }: { error?: ClerkElementsError }) => { if (error) return error; @@ -182,6 +187,7 @@ export const SignUpRouterMachine = setup({ }, })), }, + RESET: '.Idle', }, states: { Idle: { @@ -264,7 +270,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -298,7 +303,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -329,7 +333,6 @@ export const SignUpRouterMachine = setup({ type: 'setActive', params: { sessionId: context.router?.searchParams().get(SEARCH_PARAMS.createdSession) }, }), - target: 'Complete', }, { guard: { type: 'hasClerkStatus', params: { status: 'verified' } }, @@ -345,7 +348,6 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { guard: 'statusNeedsContinue', @@ -363,13 +365,11 @@ export const SignUpRouterMachine = setup({ { guard: 'isStatusComplete', actions: 'setActive', - target: 'Complete', }, { description: 'Handle a case where the user has already been authenticated via ClerkJS', guard: 'hasAuthenticatedViaClerkJS', actions: { type: 'setActive', params: { useLastActiveSession: true } }, - target: 'Complete', }, { guard: 'statusNeedsVerification', @@ -388,13 +388,6 @@ export const SignUpRouterMachine = setup({ ], }, }, - Complete: { - tags: 'route:complete', - entry: 'clearFormErrors', - after: { - 5000: 'Start', - }, - }, Error: { tags: 'route:error', on: { diff --git a/packages/elements/src/internals/machines/sign-up/router.types.ts b/packages/elements/src/internals/machines/sign-up/router.types.ts index 8c359f7e7ea..2ea4fd4b0b6 100644 --- a/packages/elements/src/internals/machines/sign-up/router.types.ts +++ b/packages/elements/src/internals/machines/sign-up/router.types.ts @@ -10,6 +10,7 @@ import type { BaseRouterNextEvent, BaseRouterPrevEvent, BaseRouterRedirectEvent, + BaseRouterResetEvent, BaseRouterSetClerkEvent, BaseRouterStartEvent, BaseRouterTransferEvent, @@ -46,6 +47,7 @@ export type SignUpRouterPrevEvent = BaseRouterPrevEvent; export type SignUpRouterErrorEvent = BaseRouterErrorEvent; export type SignUpRouterTransferEvent = BaseRouterTransferEvent; export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent; +export type SignUpRouterResetEvent = BaseRouterResetEvent; export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>; export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent; @@ -64,6 +66,7 @@ export type SignUpRouterEvents = | SignUpRouterErrorEvent | SignUpRouterTransferEvent | SignUpRouterRedirectEvent + | SignUpRouterResetEvent | SignUpRouterLoadingEvent | SignUpRouterSetClerkEvent; diff --git a/packages/elements/src/internals/machines/types/router.types.ts b/packages/elements/src/internals/machines/types/router.types.ts index a4d6e7bf489..904fb56dd4e 100644 --- a/packages/elements/src/internals/machines/types/router.types.ts +++ b/packages/elements/src/internals/machines/types/router.types.ts @@ -17,6 +17,7 @@ export type BaseRouterLoadingStep = 'start' | 'verifications' | 'continue' | 're export type BaseRouterNextEvent = { type: 'NEXT'; resource?: T }; export type BaseRouterPrevEvent = { type: 'NAVIGATE.PREVIOUS' }; export type BaseRouterStartEvent = { type: 'NAVIGATE.START' }; +export type BaseRouterResetEvent = { type: 'RESET' }; export type BaseRouterErrorEvent = { type: 'ERROR'; error: Error }; export type BaseRouterTransferEvent = { type: 'TRANSFER' }; export type BaseRouterLoadingEvent = ( diff --git a/packages/elements/src/react/sign-in/root.tsx b/packages/elements/src/react/sign-in/root.tsx index 8a782caa7e2..1d12978205a 100644 --- a/packages/elements/src/react/sign-in/root.tsx +++ b/packages/elements/src/react/sign-in/root.tsx @@ -44,7 +44,9 @@ function SignInFlowProvider({ children, exampleMode }: SignInFlowProviderProps) actor.send(evt); } }); - }, [clerk, exampleMode, formRef, router]); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clerk, exampleMode, formRef?.id, !!router]); return {children}; } diff --git a/packages/elements/src/react/sign-in/verifications.tsx b/packages/elements/src/react/sign-in/verifications.tsx index a2682fa2b6e..6b3d647ae5d 100644 --- a/packages/elements/src/react/sign-in/verifications.tsx +++ b/packages/elements/src/react/sign-in/verifications.tsx @@ -80,7 +80,7 @@ export function SignInStrategy({ children, name }: SignInStrategyProps) { } return () => { - if (factorCtx) { + if (factorCtx?.getSnapshot().status === 'active') { factorCtx.send({ type: 'STRATEGY.UNREGISTER', factor: name as unknown as SignInFactor }); } }; diff --git a/packages/elements/src/react/sign-up/root.tsx b/packages/elements/src/react/sign-up/root.tsx index 6ef2883fe78..bf2c9c198e3 100644 --- a/packages/elements/src/react/sign-up/root.tsx +++ b/packages/elements/src/react/sign-up/root.tsx @@ -46,7 +46,8 @@ function SignUpFlowProvider({ children, exampleMode }: SignUpFlowProviderProps) ref.send(evt); } }); - }, [clerk, exampleMode, formRef, router]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [clerk, exampleMode, formRef?.id, !!router]); return isReady ? {children} : null; } From 197815013e043256b519d0366ac3d244be985a39 Mon Sep 17 00:00:00 2001 From: LekoArts Date: Tue, 14 May 2024 10:33:36 +0200 Subject: [PATCH 8/8] chore(repo): Update changeset --- .changeset/nervous-fans-do.md | 6 ------ .changeset/odd-coins-carry.md | 9 ++++++--- 2 files changed, 6 insertions(+), 9 deletions(-) delete mode 100644 .changeset/nervous-fans-do.md diff --git a/.changeset/nervous-fans-do.md b/.changeset/nervous-fans-do.md deleted file mode 100644 index 841454ae233..00000000000 --- a/.changeset/nervous-fans-do.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@clerk/elements': patch ---- - -- The sign-up/in fallback prop isn't presented if the user isn't visiting the root (start) route. -- Upon completion of sign-up/in, don't immediately return to the start step. diff --git a/.changeset/odd-coins-carry.md b/.changeset/odd-coins-carry.md index 54f4427c7ae..d2e7f8d811e 100644 --- a/.changeset/odd-coins-carry.md +++ b/.changeset/odd-coins-carry.md @@ -2,6 +2,9 @@ '@clerk/elements': patch --- -- Bump XState from version 5.12.0 to 5.13.0 -- We now maintain your current state while completing authentication redirecting rather than transitioning to a "Complete" step. -- Fixes Sign In and Sign Up machines not appropriately attaching new form machines when returning to the Sign In or Sign Up pages. +This release includes various smaller fixes and one dependency update: + +- `xstate` was updated from `5.12.0` to `5.13.0` +- Previously, the contents of the `fallback` prop were sometimes shown even if the user wasn't on the `start` step. This bug is fixed now. +- Upon completion of an sign-in/sign-up attempt, don't immediately return to the `start` step. This fixes the issue of a "flash of content" that could e.g. be seen during sign-in with OAuth providers. +- Some underlying fixes in Clerk Elements' XState logic were applied to make sure that during a sign-in/sign-up attempt the state is properly maintained. For example, if you visit an already completed attempt (some step of that flow) it now properly keeps track of that state.