Skip to content

Commit 6ca6256

Browse files
authored
feat(elements): Support fallback prop for flow roots (clerk#3168)
* feat(elements): Support fallback prop for flow roots * chore(elements): Add fallbacks to example app * chore(elements): Rename SET_CLERK to CLERK.SET
1 parent 7cb1241 commit 6ca6256

15 files changed

Lines changed: 249 additions & 62 deletions

File tree

.changeset/witty-schools-occur.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,71 @@ export default function SignInPage() {
106106
const [continueWithEmail, setContinueWithEmail] = useState(false);
107107

108108
return (
109-
<SignIn>
109+
<SignIn
110+
fallback={
111+
<div className='h-dvh flex flex-col justify-center items-center bg-zinc-950 text-white gap-10'>
112+
<div className='text-center'>
113+
<H1>Sign In</H1>
114+
<p className='text-base text-zinc-400'>
115+
Don&apos;t have an account?{' '}
116+
<Link
117+
href='/sign-up'
118+
className='no-underline hover:underline'
119+
>
120+
Sign Up
121+
</Link>
122+
</p>
123+
</div>
124+
<div className='flex flex-col items-center gap-12 w-96'>
125+
<GlobalError className='block text-red-400 font-mono' />
126+
127+
<div className='flex flex-col gap-2 self-stretch'>
128+
<CustomProvider provider='github'>Continue with GitHub</CustomProvider>
129+
<CustomProvider provider='google'>Continue with Google</CustomProvider>
130+
<CustomProvider provider='metamask'>Continue with Metamask</CustomProvider>
131+
</div>
132+
133+
{continueWithEmail ? (
134+
<>
135+
<Field
136+
className='flex flex-col gap-4 w-full'
137+
name='identifier'
138+
>
139+
{fieldState => (
140+
<>
141+
<Label className='sr-only'>Email</Label>
142+
<Input
143+
className={`bg-[rgb(12,12,12)] border-[rgb(37,37,37)] border rounded w-full placeholder-[rgb(100,100,100)] px-4 py-2 ${
144+
fieldState === 'invalid' && 'border-red-500'
145+
}`}
146+
placeholder='Enter your email address'
147+
/>
148+
<FieldError className='block text-red-400 font-mono w-full' />
149+
</>
150+
)}
151+
</Field>
152+
153+
<CustomSubmit>
154+
<Loading>
155+
{isLoading =>
156+
isLoading ? (
157+
<>
158+
<Spinner /> Loading...
159+
</>
160+
) : (
161+
'Sign in with Email'
162+
)
163+
}
164+
</Loading>
165+
</CustomSubmit>
166+
</>
167+
) : (
168+
<TextButton onClick={() => setContinueWithEmail(true)}>Continue with Email</TextButton>
169+
)}
170+
</div>
171+
</div>
172+
}
173+
>
110174
<div className='h-dvh flex flex-col justify-center items-center bg-zinc-950 text-white gap-10'>
111175
<div className='text-center'>
112176
<H1>Sign In</H1>

packages/elements/examples/nextjs/app/sign-up/[[...sign-up]]/page.tsx

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,70 @@ function CustomSubmit({ children }: ComponentProps<'button'>) {
2121

2222
export default function SignUpPage() {
2323
return (
24-
<SignUp>
24+
<SignUp
25+
fallback={
26+
<div className='m-auto w-max text-sm'>
27+
<div className='flex flex-col items-center justify-center gap-12'>
28+
<H1>Sign Up</H1>
29+
30+
<div className='flex flex-col items-stretch justify-center gap-2'>
31+
<Provider
32+
name='github'
33+
className='flex items-center justify-center gap-4 text-white rounded bg-[#171717] px-4 py-3 text-sm shadow-sm ring-1 ring-black/[0.06] transition-all hover:bg-opacity-80'
34+
>
35+
<ProviderIcon className='invert' />
36+
Sign In with GitHub
37+
</Provider>
38+
39+
<Provider
40+
name='google'
41+
className='flex items-center justify-center gap-4 text-white rounded bg-[#333f61] px-4 py-3 text-sm shadow-sm ring-1 ring-black/[0.06] transition-all hover:bg-opacity-80'
42+
>
43+
<ProviderIcon />
44+
Sign In with Google
45+
</Provider>
46+
47+
<Provider
48+
name='metamask'
49+
className='flex items-center justify-center gap-4 text-[#161616] rounded bg-white px-4 py-3 text-sm shadow-sm ring-1 ring-black/[0.06] transition-all hover:bg-opacity-80'
50+
>
51+
<ProviderIcon />
52+
Sign In with Metamask
53+
</Provider>
54+
</div>
55+
56+
<Hr />
57+
58+
<GlobalError className='block text-red-400 font-mono' />
59+
60+
<div className='flex gap-6 flex-col'>
61+
<CustomField
62+
label='Email'
63+
name='emailAddress'
64+
/>
65+
66+
<CustomField
67+
label='Phone Number'
68+
name='phoneNumber'
69+
/>
70+
<CustomSubmit>
71+
<Loading>
72+
{isLoading =>
73+
isLoading ? (
74+
<>
75+
<Spinner /> Loading...
76+
</>
77+
) : (
78+
'Sign Up'
79+
)
80+
}
81+
</Loading>
82+
</CustomSubmit>
83+
</div>
84+
</div>
85+
</div>
86+
}
87+
>
2588
<div className='m-auto w-max text-sm'>
2689
<Step name='start'>
2790
<div className='flex flex-col items-center justify-center gap-12'>
@@ -86,19 +149,20 @@ export default function SignUpPage() {
86149

87150
<Step name='continue'>
88151
<H1>Please enter additional information:</H1>
89-
90152
<GlobalError className='block text-red-400 font-mono' />
91-
92153
<CustomField
93154
label='Password'
94155
name='password'
95156
/>
96-
97157
<CustomField
98158
alwaysShow
99159
label='Phone Number'
100160
name='phoneNumber'
101161
/>
162+
<CustomField
163+
label='Username'
164+
name='username'
165+
/>
102166

103167
<CustomSubmit>
104168
<Loading>

packages/elements/examples/nextjs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"clsx": "^2.0.0",
1818
"framer-motion": "^11.0.2",
1919
"geist": "^1.2.2",
20-
"next": "~14.1.0",
20+
"next": "^14.1.4",
2121
"react": "^18",
2222
"react-dom": "^18"
2323
},

packages/elements/src/internals/machines/sign-in/machines/router.machine.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const SignInRouterMachine = setup({
6767
guards: {
6868
hasAuthenticatedViaClerkJS: ({ context }) =>
6969
Boolean(context.clerk.client.signIn.status === null && context.clerk.client.lastActiveSessionId),
70-
hasResource: ({ context }) => Boolean(context.clerk.client.signIn.status),
70+
hasResource: ({ context }) => Boolean(context.clerk?.client?.signIn?.status),
7171

7272
isLoggedInAndSingleSession: and(['isLoggedIn', 'isSingleSessionMode']),
7373
isActivePathRoot: isCurrentPath('/'),
@@ -144,6 +144,11 @@ export const SignInRouterMachine = setup({
144144
},
145145
})),
146146
},
147+
'CLERK.SET': {
148+
actions: assign(({ event }) => ({
149+
clerk: event.clerk,
150+
})),
151+
},
147152
},
148153
states: {
149154
Idle: {
@@ -167,7 +172,6 @@ export const SignInRouterMachine = setup({
167172
systemId: ThirdPartyMachineId,
168173
input: ({ context, self }) => ({
169174
basePath: context.router?.basePath ?? SIGN_IN_DEFAULT_BASE_PATH,
170-
environment: context.clerk.__unstable__environment,
171175
flow: 'signIn',
172176
parent: self,
173177
}),

packages/elements/src/internals/machines/sign-in/types/router.types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
BaseRouterRedirectEvent,
1313
BaseRouterRouteRegisterEvent,
1414
BaseRouterRouteUnregisterEvent,
15+
BaseRouterSetClerkEvent,
1516
BaseRouterStartEvent,
1617
BaseRouterTransferEvent,
1718
} from '~/internals/machines/types';
@@ -52,6 +53,7 @@ export type SignInRouterErrorEvent = BaseRouterErrorEvent;
5253
export type SignInRouterTransferEvent = BaseRouterTransferEvent;
5354
export type SignInRouterRedirectEvent = BaseRouterRedirectEvent;
5455
export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications'>;
56+
export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent;
5557

5658
export interface SignInRouterInitEvent extends BaseRouterInput {
5759
type: 'INIT';
@@ -81,7 +83,8 @@ export type SignInRouterEvents =
8183
| SignInRouterRouteEvents
8284
| SignInRouterRedirectEvent
8385
| SignInVerificationFactorUpdateEvent
84-
| SignInRouterLoadingEvent;
86+
| SignInRouterLoadingEvent
87+
| BaseRouterSetClerkEvent;
8588

8689
// ---------------------------------- Context ---------------------------------- //
8790

packages/elements/src/internals/machines/sign-up/machines/router.machine.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const isCurrentPath =
2828
const needsStatus =
2929
(status: SignUpStatus) =>
3030
({ context, event }: { context: SignUpRouterContext; event?: SignUpRouterEvents }, _?: NonReducibleUnknown) =>
31-
(event as SignUpRouterNextEvent)?.resource?.status === status || context.clerk.client.signUp.status === status;
31+
(event as SignUpRouterNextEvent)?.resource?.status === status || context.clerk?.client?.signUp?.status === status;
3232

3333
export const SignUpRouterMachineId = 'SignUpRouter';
3434
export type TSignUpRouterMachine = typeof SignUpRouterMachine;
@@ -68,8 +68,8 @@ export const SignUpRouterMachine = setup({
6868
transfer: ({ context }) => context.router?.push(context.clerk.buildSignInUrl()),
6969
},
7070
guards: {
71-
areFieldsMissing: ({ context }) => context.clerk.client.signUp.missingFields.length > 0,
72-
areFieldsUnverified: ({ context }) => context.clerk.client.signUp.unverifiedFields.length > 0,
71+
areFieldsMissing: ({ context }) => context.clerk?.client?.signUp?.missingFields?.length > 0,
72+
areFieldsUnverified: ({ context }) => context.clerk?.client?.signUp?.unverifiedFields?.length > 0,
7373

7474
hasAuthenticatedViaClerkJS: ({ context }) =>
7575
Boolean(context.clerk.client.signUp.status === null && context.clerk.client.lastActiveSessionId),
@@ -85,11 +85,11 @@ export const SignUpRouterMachine = setup({
8585
isStatusAbandoned: needsStatus('abandoned'),
8686
isStatusComplete: ({ context, event }) => {
8787
const resource = (event as SignUpRouterNextEvent)?.resource;
88-
const signUp = context.clerk.client.signUp;
88+
const signUp = context.clerk?.client?.signUp;
8989

9090
return (
9191
(resource?.status === 'complete' && Boolean(resource?.createdSessionId)) ||
92-
(signUp.status === 'complete' && Boolean(signUp.createdSessionId))
92+
(signUp?.status === 'complete' && Boolean(signUp?.createdSessionId))
9393
);
9494
},
9595
isStatusMissingRequirements: needsStatus('missing_requirements'),
@@ -160,6 +160,11 @@ export const SignUpRouterMachine = setup({
160160
},
161161
})),
162162
},
163+
'CLERK.SET': {
164+
actions: assign(({ event }) => ({
165+
clerk: event.clerk,
166+
})),
167+
},
163168
},
164169
states: {
165170
Idle: {

packages/elements/src/internals/machines/sign-up/types/router.types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import type {
1111
BaseRouterRedirectEvent,
1212
BaseRouterRouteRegisterEvent,
1313
BaseRouterRouteUnregisterEvent,
14+
BaseRouterSetClerkEvent,
1415
BaseRouterStartEvent,
1516
BaseRouterTransferEvent,
1617
} from '~/internals/machines/types';
@@ -47,6 +48,7 @@ export type SignUpRouterErrorEvent = BaseRouterErrorEvent;
4748
export type SignUpRouterTransferEvent = BaseRouterTransferEvent;
4849
export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent;
4950
export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>;
51+
export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent;
5052

5153
export interface SignUpRouterInitEvent extends BaseRouterInput {
5254
type: 'INIT';
@@ -71,7 +73,8 @@ export type SignUpRouterEvents =
7173
| SignUpRouterTransferEvent
7274
| SignUpRouterRedirectEvent
7375
| SignUpRouterRouteEvents
74-
| SignUpRouterLoadingEvent;
76+
| SignUpRouterLoadingEvent
77+
| SignUpRouterSetClerkEvent;
7578

7679
// ---------------------------------- Delays ---------------------------------- //
7780

packages/elements/src/internals/machines/third-party/machine.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { assertEvent, assign, log, sendParent, setup } from 'xstate';
44
import { SSO_CALLBACK_PATH_ROUTE } from '~/internals/constants';
55
import { sendToLoading } from '~/internals/machines/shared.actions';
66
import { assertActorEventError } from '~/internals/machines/utils/assert';
7-
import { getEnabledThirdPartyProviders } from '~/utils/third-party-strategies';
87

98
import { handleRedirectCallback, redirect } from './actors';
109
import type { ThirdPartyMachineSchema } from './types';
@@ -47,7 +46,6 @@ export const ThirdPartyMachine = setup({
4746
basePath: input.basePath,
4847
flow: input.flow,
4948
parent: input.parent,
50-
thirdPartyProviders: getEnabledThirdPartyProviders(input.environment),
5149
loadingStep: 'strategy',
5250
}),
5351
initial: 'Idle',

packages/elements/src/internals/machines/third-party/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import type { AuthenticateWithRedirectParams, EnvironmentResource } from '@clerk/types';
1+
import type { AuthenticateWithRedirectParams } from '@clerk/types';
22
import type { SetOptional } from 'type-fest';
33
import type { AnyActorRef } from 'xstate';
44

55
import type { ClerkJSNavigationEvent } from '~/internals/machines/utils/clerkjs';
6-
import type { EnabledThirdPartyProviders } from '~/utils/third-party-strategies';
76

87
type Flow = 'signIn' | 'signUp';
98

@@ -25,7 +24,6 @@ export interface ThirdPartyMachineContext {
2524
activeStrategy: string | null; // TODO: Update type
2625
basePath: string;
2726
flow: Flow;
28-
thirdPartyProviders: EnabledThirdPartyProviders;
2927
parent: AnyActorRef; // TODO: Fix circular dependency
3028
loadingStep: 'strategy';
3129
}
@@ -34,7 +32,6 @@ export interface ThirdPartyMachineContext {
3432

3533
export interface ThirdPartyMachineInput {
3634
basePath: string;
37-
environment: EnvironmentResource | null | undefined;
3835
flow: Flow;
3936
parent: AnyActorRef; // TODO: Fix circular dependency
4037
}

0 commit comments

Comments
 (0)