11import type { SignInResource , Web3Strategy } from '@clerk/types' ;
2- import { assertEvent , fromPromise , not , sendTo , setup } from 'xstate' ;
2+ import { assertEvent , enqueueActions , fromPromise , not , sendTo , setup } from 'xstate' ;
33
44import { SIGN_IN_DEFAULT_BASE_PATH } from '~/internals/constants' ;
55import { ClerkElementsRuntimeError } from '~/internals/errors' ;
@@ -10,10 +10,14 @@ import { assertActorEventError } from '~/internals/machines/utils/assert';
1010import type { SignInRouterMachineActorRef } from './router.types' ;
1111import type { SignInStartSchema } from './start.types' ;
1212
13+ const DISABLEABLE_FIELDS = [ 'emailAddress' , 'phoneNumber' ] as const ;
14+
1315export type TSignInStartMachine = typeof SignInStartMachine ;
1416
1517export const SignInStartMachineId = 'SignInStart' ;
1618
19+ type AttemptParams = { strategy : 'ticket' ; ticket : string } | { strategy ?: never ; ticket ?: never } ;
20+
1721export const SignInStartMachine = setup ( {
1822 actors : {
1923 attemptPasskey : fromPromise <
@@ -38,33 +42,51 @@ export const SignInStartMachine = setup({
3842 throw new ClerkElementsRuntimeError ( `Unsupported Web3 strategy: ${ strategy } ` ) ;
3943 } ,
4044 ) ,
41- attempt : fromPromise < SignInResource , { parent : SignInRouterMachineActorRef ; fields : FormFields } > (
42- ( { input : { fields, parent } } ) => {
43- const clerk = parent . getSnapshot ( ) . context . clerk ;
45+ attempt : fromPromise <
46+ SignInResource ,
47+ { parent : SignInRouterMachineActorRef ; fields : FormFields ; params ?: AttemptParams }
48+ > ( ( { input : { fields, parent, params } } ) => {
49+ const clerk = parent . getSnapshot ( ) . context . clerk ;
4450
45- const password = fields . get ( 'password' ) ;
46- const identifier = fields . get ( 'identifier' ) ;
51+ const password = fields . get ( 'password' ) ;
52+ const identifier = fields . get ( 'identifier' ) ;
4753
48- const passwordParams = password ?. value
49- ? {
50- password : password . value ,
51- strategy : 'password' ,
52- }
53- : { } ;
54+ const passwordParams = password ?. value
55+ ? {
56+ password : password . value ,
57+ strategy : 'password' ,
58+ }
59+ : { } ;
5460
55- return clerk . client . signIn . create ( {
56- identifier : ( identifier ?. value as string ) || '' ,
57- ...passwordParams ,
58- } ) ;
59- } ,
60- ) ,
61+ return clerk . client . signIn . create ( {
62+ ...passwordParams ,
63+ ...( params ?. ticket
64+ ? params
65+ : {
66+ identifier : ( identifier ?. value as string ) ?? '' ,
67+ } ) ,
68+ } ) ;
69+ } ) ,
6170 } ,
6271 actions : {
6372 sendToNext : ( { context, event } ) => {
6473 // @ts -expect-error -- We're calling this in onDone, and event.output exists on the actor done event
6574 return context . parent . send ( { type : 'NEXT' , resource : event ?. output } ) ;
6675 } ,
6776 sendToLoading,
77+ setFormDisabledTicketFields : enqueueActions ( ( { context, enqueue } ) => {
78+ if ( ! context . ticket ) {
79+ return ;
80+ }
81+
82+ const currentFields = context . formRef . getSnapshot ( ) . context . fields ;
83+
84+ for ( const name of DISABLEABLE_FIELDS ) {
85+ if ( currentFields . has ( name ) ) {
86+ enqueue . sendTo ( context . formRef , { type : 'FIELD.DISABLE' , field : { name } } ) ;
87+ }
88+ }
89+ } ) ,
6890 setFormErrors : sendTo (
6991 ( { context } ) => context . formRef ,
7092 ( { event } ) => {
@@ -77,6 +99,7 @@ export const SignInStartMachine = setup({
7799 ) ,
78100 } ,
79101 guards : {
102+ hasTicket : ( { context } ) => Boolean ( context . ticket ) ,
80103 isExampleMode : ( { context } ) => Boolean ( context . parent . getSnapshot ( ) . context . exampleMode ) ,
81104 } ,
82105 types : { } as SignInStartSchema ,
@@ -87,9 +110,22 @@ export const SignInStartMachine = setup({
87110 parent : input . parent ,
88111 formRef : input . formRef ,
89112 loadingStep : 'start' ,
113+ ticket : input . ticket ,
90114 } ) ,
91- initial : 'Pending ' ,
115+ initial : 'Init ' ,
92116 states : {
117+ Init : {
118+ description : 'Handle ticket, if present; Else, default to Pending state.' ,
119+ always : [
120+ {
121+ guard : 'hasTicket' ,
122+ target : 'Attempting' ,
123+ } ,
124+ {
125+ target : 'Pending' ,
126+ } ,
127+ ] ,
128+ } ,
93129 Pending : {
94130 tags : [ 'state:pending' ] ,
95131 description : 'Waiting for user input' ,
@@ -122,15 +158,28 @@ export const SignInStartMachine = setup({
122158 invoke : {
123159 id : 'attempt' ,
124160 src : 'attempt' ,
125- input : ( { context } ) => ( {
126- parent : context . parent ,
127- fields : context . formRef . getSnapshot ( ) . context . fields ,
128- } ) ,
161+ input : ( { context } ) => {
162+ // Standard fields
163+ const defaultParams = {
164+ fields : context . formRef . getSnapshot ( ) . context . fields ,
165+ parent : context . parent ,
166+ } ;
167+
168+ // Handle ticket-specific flows
169+ const params : AttemptParams = context . ticket
170+ ? {
171+ strategy : 'ticket' ,
172+ ticket : context . ticket ,
173+ }
174+ : { } ;
175+
176+ return { ...defaultParams , params } ;
177+ } ,
129178 onDone : {
130- actions : [ 'sendToNext' , 'sendToLoading' ] ,
179+ actions : [ 'setFormDisabledTicketFields' , ' sendToNext', 'sendToLoading' ] ,
131180 } ,
132181 onError : {
133- actions : [ 'setFormErrors' , 'sendToLoading' ] ,
182+ actions : [ 'setFormDisabledTicketFields' , ' setFormErrors', 'sendToLoading' ] ,
134183 target : 'Pending' ,
135184 } ,
136185 } ,
0 commit comments