Skip to content

Commit

Permalink
feat(elements): Support fallback prop for flow roots (#3168)
Browse files Browse the repository at this point in the history
* feat(elements): Support fallback prop for flow roots

* chore(elements): Add fallbacks to example app

* chore(elements): Rename SET_CLERK to CLERK.SET
  • Loading branch information
brkalow authored Apr 12, 2024
1 parent 7cb1241 commit 6ca6256
Show file tree
Hide file tree
Showing 15 changed files with 249 additions and 62 deletions.
2 changes: 2 additions & 0 deletions .changeset/witty-schools-occur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,71 @@ export default function SignInPage() {
const [continueWithEmail, setContinueWithEmail] = useState(false);

return (
<SignIn>
<SignIn
fallback={
<div className='h-dvh flex flex-col justify-center items-center bg-zinc-950 text-white gap-10'>
<div className='text-center'>
<H1>Sign In</H1>
<p className='text-base text-zinc-400'>
Don&apos;t have an account?{' '}
<Link
href='/sign-up'
className='no-underline hover:underline'
>
Sign Up
</Link>
</p>
</div>
<div className='flex flex-col items-center gap-12 w-96'>
<GlobalError className='block text-red-400 font-mono' />

<div className='flex flex-col gap-2 self-stretch'>
<CustomProvider provider='github'>Continue with GitHub</CustomProvider>
<CustomProvider provider='google'>Continue with Google</CustomProvider>
<CustomProvider provider='metamask'>Continue with Metamask</CustomProvider>
</div>

{continueWithEmail ? (
<>
<Field
className='flex flex-col gap-4 w-full'
name='identifier'
>
{fieldState => (
<>
<Label className='sr-only'>Email</Label>
<Input
className={`bg-[rgb(12,12,12)] border-[rgb(37,37,37)] border rounded w-full placeholder-[rgb(100,100,100)] px-4 py-2 ${
fieldState === 'invalid' && 'border-red-500'
}`}
placeholder='Enter your email address'
/>
<FieldError className='block text-red-400 font-mono w-full' />
</>
)}
</Field>

<CustomSubmit>
<Loading>
{isLoading =>
isLoading ? (
<>
<Spinner /> Loading...
</>
) : (
'Sign in with Email'
)
}
</Loading>
</CustomSubmit>
</>
) : (
<TextButton onClick={() => setContinueWithEmail(true)}>Continue with Email</TextButton>
)}
</div>
</div>
}
>
<div className='h-dvh flex flex-col justify-center items-center bg-zinc-950 text-white gap-10'>
<div className='text-center'>
<H1>Sign In</H1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,70 @@ function CustomSubmit({ children }: ComponentProps<'button'>) {

export default function SignUpPage() {
return (
<SignUp>
<SignUp
fallback={
<div className='m-auto w-max text-sm'>
<div className='flex flex-col items-center justify-center gap-12'>
<H1>Sign Up</H1>

<div className='flex flex-col items-stretch justify-center gap-2'>
<Provider
name='github'
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'
>
<ProviderIcon className='invert' />
Sign In with GitHub
</Provider>

<Provider
name='google'
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'
>
<ProviderIcon />
Sign In with Google
</Provider>

<Provider
name='metamask'
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'
>
<ProviderIcon />
Sign In with Metamask
</Provider>
</div>

<Hr />

<GlobalError className='block text-red-400 font-mono' />

<div className='flex gap-6 flex-col'>
<CustomField
label='Email'
name='emailAddress'
/>

<CustomField
label='Phone Number'
name='phoneNumber'
/>
<CustomSubmit>
<Loading>
{isLoading =>
isLoading ? (
<>
<Spinner /> Loading...
</>
) : (
'Sign Up'
)
}
</Loading>
</CustomSubmit>
</div>
</div>
</div>
}
>
<div className='m-auto w-max text-sm'>
<Step name='start'>
<div className='flex flex-col items-center justify-center gap-12'>
Expand Down Expand Up @@ -86,19 +149,20 @@ export default function SignUpPage() {

<Step name='continue'>
<H1>Please enter additional information:</H1>

<GlobalError className='block text-red-400 font-mono' />

<CustomField
label='Password'
name='password'
/>

<CustomField
alwaysShow
label='Phone Number'
name='phoneNumber'
/>
<CustomField
label='Username'
name='username'
/>

<CustomSubmit>
<Loading>
Expand Down
2 changes: 1 addition & 1 deletion packages/elements/examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"clsx": "^2.0.0",
"framer-motion": "^11.0.2",
"geist": "^1.2.2",
"next": "~14.1.0",
"next": "^14.1.4",
"react": "^18",
"react-dom": "^18"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const SignInRouterMachine = setup({
guards: {
hasAuthenticatedViaClerkJS: ({ context }) =>
Boolean(context.clerk.client.signIn.status === null && context.clerk.client.lastActiveSessionId),
hasResource: ({ context }) => Boolean(context.clerk.client.signIn.status),
hasResource: ({ context }) => Boolean(context.clerk?.client?.signIn?.status),

isLoggedInAndSingleSession: and(['isLoggedIn', 'isSingleSessionMode']),
isActivePathRoot: isCurrentPath('/'),
Expand Down Expand Up @@ -144,6 +144,11 @@ export const SignInRouterMachine = setup({
},
})),
},
'CLERK.SET': {
actions: assign(({ event }) => ({
clerk: event.clerk,
})),
},
},
states: {
Idle: {
Expand All @@ -167,7 +172,6 @@ export const SignInRouterMachine = setup({
systemId: ThirdPartyMachineId,
input: ({ context, self }) => ({
basePath: context.router?.basePath ?? SIGN_IN_DEFAULT_BASE_PATH,
environment: context.clerk.__unstable__environment,
flow: 'signIn',
parent: self,
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type {
BaseRouterRedirectEvent,
BaseRouterRouteRegisterEvent,
BaseRouterRouteUnregisterEvent,
BaseRouterSetClerkEvent,
BaseRouterStartEvent,
BaseRouterTransferEvent,
} from '~/internals/machines/types';
Expand Down Expand Up @@ -52,6 +53,7 @@ export type SignInRouterErrorEvent = BaseRouterErrorEvent;
export type SignInRouterTransferEvent = BaseRouterTransferEvent;
export type SignInRouterRedirectEvent = BaseRouterRedirectEvent;
export type SignInRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications'>;
export type SignInRouterSetClerkEvent = BaseRouterSetClerkEvent;

export interface SignInRouterInitEvent extends BaseRouterInput {
type: 'INIT';
Expand Down Expand Up @@ -81,7 +83,8 @@ export type SignInRouterEvents =
| SignInRouterRouteEvents
| SignInRouterRedirectEvent
| SignInVerificationFactorUpdateEvent
| SignInRouterLoadingEvent;
| SignInRouterLoadingEvent
| BaseRouterSetClerkEvent;

// ---------------------------------- Context ---------------------------------- //

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const isCurrentPath =
const needsStatus =
(status: SignUpStatus) =>
({ context, event }: { context: SignUpRouterContext; event?: SignUpRouterEvents }, _?: NonReducibleUnknown) =>
(event as SignUpRouterNextEvent)?.resource?.status === status || context.clerk.client.signUp.status === status;
(event as SignUpRouterNextEvent)?.resource?.status === status || context.clerk?.client?.signUp?.status === status;

export const SignUpRouterMachineId = 'SignUpRouter';
export type TSignUpRouterMachine = typeof SignUpRouterMachine;
Expand Down Expand Up @@ -68,8 +68,8 @@ export const SignUpRouterMachine = setup({
transfer: ({ context }) => context.router?.push(context.clerk.buildSignInUrl()),
},
guards: {
areFieldsMissing: ({ context }) => context.clerk.client.signUp.missingFields.length > 0,
areFieldsUnverified: ({ context }) => context.clerk.client.signUp.unverifiedFields.length > 0,
areFieldsMissing: ({ context }) => context.clerk?.client?.signUp?.missingFields?.length > 0,
areFieldsUnverified: ({ context }) => context.clerk?.client?.signUp?.unverifiedFields?.length > 0,

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

return (
(resource?.status === 'complete' && Boolean(resource?.createdSessionId)) ||
(signUp.status === 'complete' && Boolean(signUp.createdSessionId))
(signUp?.status === 'complete' && Boolean(signUp?.createdSessionId))
);
},
isStatusMissingRequirements: needsStatus('missing_requirements'),
Expand Down Expand Up @@ -160,6 +160,11 @@ export const SignUpRouterMachine = setup({
},
})),
},
'CLERK.SET': {
actions: assign(({ event }) => ({
clerk: event.clerk,
})),
},
},
states: {
Idle: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
BaseRouterRedirectEvent,
BaseRouterRouteRegisterEvent,
BaseRouterRouteUnregisterEvent,
BaseRouterSetClerkEvent,
BaseRouterStartEvent,
BaseRouterTransferEvent,
} from '~/internals/machines/types';
Expand Down Expand Up @@ -47,6 +48,7 @@ export type SignUpRouterErrorEvent = BaseRouterErrorEvent;
export type SignUpRouterTransferEvent = BaseRouterTransferEvent;
export type SignUpRouterRedirectEvent = BaseRouterRedirectEvent;
export type SignUpRouterLoadingEvent = BaseRouterLoadingEvent<'start' | 'verifications' | 'continue'>;
export type SignUpRouterSetClerkEvent = BaseRouterSetClerkEvent;

export interface SignUpRouterInitEvent extends BaseRouterInput {
type: 'INIT';
Expand All @@ -71,7 +73,8 @@ export type SignUpRouterEvents =
| SignUpRouterTransferEvent
| SignUpRouterRedirectEvent
| SignUpRouterRouteEvents
| SignUpRouterLoadingEvent;
| SignUpRouterLoadingEvent
| SignUpRouterSetClerkEvent;

// ---------------------------------- Delays ---------------------------------- //

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { assertEvent, assign, log, sendParent, setup } from 'xstate';
import { SSO_CALLBACK_PATH_ROUTE } from '~/internals/constants';
import { sendToLoading } from '~/internals/machines/shared.actions';
import { assertActorEventError } from '~/internals/machines/utils/assert';
import { getEnabledThirdPartyProviders } from '~/utils/third-party-strategies';

import { handleRedirectCallback, redirect } from './actors';
import type { ThirdPartyMachineSchema } from './types';
Expand Down Expand Up @@ -47,7 +46,6 @@ export const ThirdPartyMachine = setup({
basePath: input.basePath,
flow: input.flow,
parent: input.parent,
thirdPartyProviders: getEnabledThirdPartyProviders(input.environment),
loadingStep: 'strategy',
}),
initial: 'Idle',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { AuthenticateWithRedirectParams, EnvironmentResource } from '@clerk/types';
import type { AuthenticateWithRedirectParams } from '@clerk/types';
import type { SetOptional } from 'type-fest';
import type { AnyActorRef } from 'xstate';

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

type Flow = 'signIn' | 'signUp';

Expand All @@ -25,7 +24,6 @@ export interface ThirdPartyMachineContext {
activeStrategy: string | null; // TODO: Update type
basePath: string;
flow: Flow;
thirdPartyProviders: EnabledThirdPartyProviders;
parent: AnyActorRef; // TODO: Fix circular dependency
loadingStep: 'strategy';
}
Expand All @@ -34,7 +32,6 @@ export interface ThirdPartyMachineContext {

export interface ThirdPartyMachineInput {
basePath: string;
environment: EnvironmentResource | null | undefined;
flow: Flow;
parent: AnyActorRef; // TODO: Fix circular dependency
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type BaseRouterRouteUnregisterEvent<T extends string> = { type: 'ROUTE.UN
export type BaseRouterRedirectOauthEvent = { type: 'AUTHENTICATE.OAUTH'; strategy: OAuthStrategy };
export type BaseRouterRedirectSamlEvent = { type: 'AUTHENTICATE.SAML'; strategy?: SamlStrategy };
export type BaseRouterRedirectWeb3Event = { type: 'AUTHENTICATE.WEB3'; strategy: Web3Strategy };
export type BaseRouterSetClerkEvent = { type: 'CLERK.SET'; clerk: LoadedClerk };

export type BaseRouterRedirectEvent =
| BaseRouterRedirectOauthEvent
Expand Down
Loading

0 comments on commit 6ca6256

Please sign in to comment.