From 4766abcf207f664eb5caad2080de771d7d1e17b2 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 11 Oct 2024 12:46:10 -0500 Subject: [PATCH 1/3] Fix progressive sign up ticket flow --- .../internals/machines/form/form.machine.ts | 2 +- .../machines/shared/shared.actions.ts | 4 ++-- .../internals/machines/shared/shared.types.ts | 5 ++++ .../machines/sign-up/router.machine.ts | 10 +++++--- .../machines/sign-up/start.machine.ts | 23 +++++++++++++++++-- .../internals/machines/sign-up/start.types.ts | 3 ++- .../react/sign-up/context/router.context.ts | 3 ++- 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/packages/elements/src/internals/machines/form/form.machine.ts b/packages/elements/src/internals/machines/form/form.machine.ts index 25da38daae9..28393b59f1f 100644 --- a/packages/elements/src/internals/machines/form/form.machine.ts +++ b/packages/elements/src/internals/machines/form/form.machine.ts @@ -169,7 +169,7 @@ export const FormMachine = setup({ if (field) { field.checked = event.field.checked; - field.disabled = event.field.disabled || false; + field.disabled = event.field.disabled ?? field.disabled; field.value = event.field.value; context.fields.set(event.field.name, field); diff --git a/packages/elements/src/internals/machines/shared/shared.actions.ts b/packages/elements/src/internals/machines/shared/shared.actions.ts index 18ce93aa281..33fd00af02d 100644 --- a/packages/elements/src/internals/machines/shared/shared.actions.ts +++ b/packages/elements/src/internals/machines/shared/shared.actions.ts @@ -12,7 +12,7 @@ import type { SignUpContinueContext, SignUpContinueEvents, SignUpStartContext, - SignUpStartRedirectEvent, + SignUpStartEvents, SignUpVerificationContext, SignUpVerificationEvents, } from '~/internals/machines/sign-up'; @@ -33,7 +33,7 @@ type SendToLoadingProps = { | SignInVerificationEvents | SignInResetPasswordEvents | ThirdPartyMachineEvent - | SignUpStartRedirectEvent + | SignUpStartEvents | SignUpContinueEvents | SignUpVerificationEvents; }; diff --git a/packages/elements/src/internals/machines/shared/shared.types.ts b/packages/elements/src/internals/machines/shared/shared.types.ts index 22eac7d24e9..6add45dfe40 100644 --- a/packages/elements/src/internals/machines/shared/shared.types.ts +++ b/packages/elements/src/internals/machines/shared/shared.types.ts @@ -6,6 +6,9 @@ import type { SignInStrategy, } from '@clerk/types'; import type { SetRequired, Simplify } from 'type-fest'; +import type { ActorRefFrom } from 'xstate'; + +import type { FormMachine } from '../form'; export type WithClerk> = { clerk: LoadedClerk } & T; export type WithClient> = { client: LoadedClerk['client'] } & T; @@ -33,3 +36,5 @@ export type AuthenticateWithRedirectSamlParams = Simplify< // ================= Strategies ================= // export type SignInStrategyName = SignInStrategy | 'oauth' | 'web3'; + +export type SetFormEvent = { type: 'SET_FORM'; formRef: ActorRefFrom }; 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 84cfe139ba6..93f9b887a22 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -16,6 +16,7 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; +import { SignInStartMachineId } from '../sign-in'; import { SignUpContinueMachine } from './continue.machine'; import type { SignUpRouterContext, @@ -329,7 +330,7 @@ export const SignUpRouterMachine = setup({ tags: ['step:start'], exit: 'clearFormErrors', invoke: { - id: 'start', + id: SignInStartMachineId, src: 'startMachine', input: ({ context, self }) => ({ basePath: context.router?.basePath, @@ -343,8 +344,10 @@ export const SignUpRouterMachine = setup({ }, on: { 'RESET.STEP': { - target: 'Start', - reenter: true, + actions: enqueueActions(({ enqueue, context }) => { + enqueue('clearFormErrors'); + enqueue.sendTo(SignInStartMachineId, { type: 'SET_FORM', formRef: context.formRef }); + }), }, NEXT: [ { @@ -355,6 +358,7 @@ export const SignUpRouterMachine = setup({ guard: and(['hasTicket', 'statusNeedsContinue']), actions: { type: 'navigateInternal', params: { path: '/' } }, target: 'Start', + reenter: true, }, { guard: 'statusNeedsVerification', diff --git a/packages/elements/src/internals/machines/sign-up/start.machine.ts b/packages/elements/src/internals/machines/sign-up/start.machine.ts index 3bec675f0ea..6e31223798a 100644 --- a/packages/elements/src/internals/machines/sign-up/start.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/start.machine.ts @@ -1,9 +1,11 @@ import type { SignUpResource, Web3Strategy } from '@clerk/types'; -import { assertEvent, enqueueActions, fromPromise, not, sendTo, setup } from 'xstate'; +import type { DoneActorEvent } from 'xstate'; +import { and, assertEvent, assign, enqueueActions, fromPromise, not, sendTo, setup } from 'xstate'; import { SIGN_UP_DEFAULT_BASE_PATH } from '~/internals/constants'; import { ClerkElementsRuntimeError } from '~/internals/errors'; import type { FormFields } from '~/internals/machines/form'; +import type { SetFormEvent } from '~/internals/machines/shared'; import { sendToLoading } from '~/internals/machines/shared'; import { fieldsToSignUpParams } from '~/internals/machines/sign-up/utils'; import { ThirdPartyMachine } from '~/internals/machines/third-party'; @@ -48,8 +50,14 @@ export const SignUpStartMachine = setup({ thirdParty: ThirdPartyMachine, }, actions: { - sendToNext: ({ context }) => context.parent.send({ type: 'NEXT' }), + sendToNext: ({ context, event }) => + context.parent.send({ type: 'NEXT', resource: (event as unknown as DoneActorEvent).output }), sendToLoading, + setFormRef: assign(({ event }) => { + return { + formRef: (event as unknown as SetFormEvent).formRef, + }; + }), setFormDisabledTicketFields: enqueueActions(({ context, enqueue }) => { if (!context.ticket) { return; @@ -90,6 +98,8 @@ export const SignUpStartMachine = setup({ }, }, guards: { + isMissingRequirements: ({ context }) => + context.parent.getSnapshot().context.clerk?.client?.signUp?.status === 'missing_requirements', hasTicket: ({ context }) => Boolean(context.ticket), isExampleMode: ({ context }) => Boolean(context.parent.getSnapshot().context.exampleMode), }, @@ -105,11 +115,20 @@ export const SignUpStartMachine = setup({ }), entry: 'setDefaultFormValues', initial: 'Init', + on: { + SET_FORM: { + actions: 'setFormRef', + }, + }, states: { Init: { description: 'Handle ticket, if present; Else, default to Pending state. Per tickets, `Attempting` makes a `signUp.create` request allowing for an incomplete sign up to contain progressively filled fields on the Start step.', always: [ + { + guard: and(['hasTicket', 'isMissingRequirements']), + target: 'Pending', + }, { guard: 'hasTicket', target: 'Attempting', diff --git a/packages/elements/src/internals/machines/sign-up/start.types.ts b/packages/elements/src/internals/machines/sign-up/start.types.ts index 0cf78efdc66..435db4cd3df 100644 --- a/packages/elements/src/internals/machines/sign-up/start.types.ts +++ b/packages/elements/src/internals/machines/sign-up/start.types.ts @@ -4,6 +4,7 @@ import type { ActorRefFrom, ErrorActorEvent } from 'xstate'; import type { FormMachine } from '~/internals/machines/form'; +import type { SetFormEvent } from '../shared'; import type { SignInRouterMachineActorRef } from './router.types'; // ---------------------------------- Tags ---------------------------------- // @@ -24,7 +25,7 @@ export type SignUpStartRedirectEvent = | SignUpStartRedirectSamlEvent | SignUpStartRedirectWeb3Event; -export type SignUpStartEvents = ErrorActorEvent | SignUpStartSubmitEvent | SignUpStartRedirectEvent; +export type SignUpStartEvents = ErrorActorEvent | SignUpStartSubmitEvent | SignUpStartRedirectEvent | SetFormEvent; // ---------------------------------- Input ---------------------------------- // diff --git a/packages/elements/src/react/sign-up/context/router.context.ts b/packages/elements/src/react/sign-up/context/router.context.ts index 76d105256dc..4abcf9e2e4e 100644 --- a/packages/elements/src/react/sign-up/context/router.context.ts +++ b/packages/elements/src/react/sign-up/context/router.context.ts @@ -1,5 +1,6 @@ import type { ActorRefFrom, AnyActorRef, AnyStateMachine, SnapshotFrom } from 'xstate'; +import { SignInStartMachineId } from '~/internals/machines/sign-in'; import type { TSignUpContinueMachine, TSignUpRouterMachine, @@ -16,6 +17,6 @@ function useSignUpStep>(name: str return SignUpRouterCtx.useSelector(state => state.children[name] as AnyActorRef) as T; } -export const useSignUpStartStep = () => useSignUpStep('start'); +export const useSignUpStartStep = () => useSignUpStep(SignInStartMachineId); export const useSignUpContinueStep = () => useSignUpStep('continue'); export const useSignUpVerificationStep = () => useSignUpStep('verification'); From 3aa6b574048f7115da46a8b099cf83f01f951577 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 11 Oct 2024 13:02:43 -0500 Subject: [PATCH 2/3] adds changeset --- .changeset/tender-badgers-rule.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tender-badgers-rule.md diff --git a/.changeset/tender-badgers-rule.md b/.changeset/tender-badgers-rule.md new file mode 100644 index 00000000000..c9c2990c560 --- /dev/null +++ b/.changeset/tender-badgers-rule.md @@ -0,0 +1,5 @@ +--- +"@clerk/elements": patch +--- + +Fixes a bug during a ticket-based sign-up where the form could not be submitted if additional fields were needed. From 8d3279e52d95e462e8c0d240b1faf5b0a1379386 Mon Sep 17 00:00:00 2001 From: Bryce Kalow Date: Fri, 11 Oct 2024 13:46:50 -0500 Subject: [PATCH 3/3] Align machine ID with other steps --- .../internals/machines/sign-up/router.machine.ts | 5 ++--- .../src/react/sign-up/context/router.context.ts | 13 ++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) 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 93f9b887a22..9f18b4c7dcf 100644 --- a/packages/elements/src/internals/machines/sign-up/router.machine.ts +++ b/packages/elements/src/internals/machines/sign-up/router.machine.ts @@ -16,7 +16,6 @@ import { ClerkElementsError, ClerkElementsRuntimeError } from '~/internals/error import { ThirdPartyMachine, ThirdPartyMachineId } from '~/internals/machines/third-party'; import { shouldUseVirtualRouting } from '~/internals/machines/utils/next'; -import { SignInStartMachineId } from '../sign-in'; import { SignUpContinueMachine } from './continue.machine'; import type { SignUpRouterContext, @@ -330,7 +329,7 @@ export const SignUpRouterMachine = setup({ tags: ['step:start'], exit: 'clearFormErrors', invoke: { - id: SignInStartMachineId, + id: 'start', src: 'startMachine', input: ({ context, self }) => ({ basePath: context.router?.basePath, @@ -346,7 +345,7 @@ export const SignUpRouterMachine = setup({ 'RESET.STEP': { actions: enqueueActions(({ enqueue, context }) => { enqueue('clearFormErrors'); - enqueue.sendTo(SignInStartMachineId, { type: 'SET_FORM', formRef: context.formRef }); + enqueue.sendTo('start', { type: 'SET_FORM', formRef: context.formRef }); }), }, NEXT: [ diff --git a/packages/elements/src/react/sign-up/context/router.context.ts b/packages/elements/src/react/sign-up/context/router.context.ts index 4abcf9e2e4e..0f347d98c84 100644 --- a/packages/elements/src/react/sign-up/context/router.context.ts +++ b/packages/elements/src/react/sign-up/context/router.context.ts @@ -1,11 +1,10 @@ import type { ActorRefFrom, AnyActorRef, AnyStateMachine, SnapshotFrom } from 'xstate'; -import { SignInStartMachineId } from '~/internals/machines/sign-in'; -import type { - TSignUpContinueMachine, - TSignUpRouterMachine, - TSignUpStartMachine, - TSignUpVerificationMachine, +import { + type TSignUpContinueMachine, + type TSignUpRouterMachine, + type TSignUpStartMachine, + type TSignUpVerificationMachine, } from '~/internals/machines/sign-up'; import { createContextFromActorRef } from '~/react/utils/create-context-from-actor-ref'; @@ -17,6 +16,6 @@ function useSignUpStep>(name: str return SignUpRouterCtx.useSelector(state => state.children[name] as AnyActorRef) as T; } -export const useSignUpStartStep = () => useSignUpStep(SignInStartMachineId); +export const useSignUpStartStep = () => useSignUpStep('start'); export const useSignUpContinueStep = () => useSignUpStep('continue'); export const useSignUpVerificationStep = () => useSignUpStep('verification');