Skip to content

Commit

Permalink
chore(elements): Introduce Strategies (#2497)
Browse files Browse the repository at this point in the history
* feat(elements): First Factor interim commit

* feat(elements): Introduce Strategies

* chore(elements): Introduce Strategies

* chore(elements): Cleanup commented code

* chore(elements): Remove actor wrapper around determineStartingSignInFactor

---------

Co-authored-by: Bryce Kalow <bryce@clerk.dev>
  • Loading branch information
tmilewski and BRKalow committed Jan 6, 2024
1 parent 5edd2df commit 55f955f
Show file tree
Hide file tree
Showing 18 changed files with 464 additions and 121 deletions.
2 changes: 2 additions & 0 deletions .changeset/soft-apples-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
163 changes: 130 additions & 33 deletions packages/elements/examples/nextjs/app/sign-in/[[...sign-in]]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import {
SignInFactorTwo,
SignInSSOCallback,
SignInStart,
SignInStrategies,
SignInStrategy,
SocialProviders,
Submit,
} from '@clerk/elements';
Expand All @@ -19,6 +21,7 @@ import type { CSSProperties } from 'react';
import { forwardRef } from 'react';

import { Debug } from '@/components/debug';
import { H1, H2, H3, HR, P } from '@/components/design';

const BUTTON_BGS: Record<string, string> = {
github: 'rgba(23 23 23)',
Expand Down Expand Up @@ -50,7 +53,7 @@ export default function SignInPage() {
<div className='m-auto w-max text-sm'>
<SignInStart>
<div className='flex flex-col items-center justify-center gap-12'>
<h1 className='text-xl mb-6 font-mono'>START</h1>
<H1>START</H1>
<div className='flex flex-col gap-3'>
<SocialProviders
render={provider => {
Expand Down Expand Up @@ -82,7 +85,7 @@ export default function SignInPage() {
/>
</div>

<hr className='w-full border-foreground opacity-10' />
<HR />

<div className='flex gap-6 flex-col'>
<Field
Expand All @@ -108,47 +111,141 @@ export default function SignInPage() {
</Field>

<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Sign In
Sign In with Email/Password
</Submit>

<HR />

<Field
name='identifier'
className='flex flex-col gap-4'
>
<div className='flex gap-4 justify-between items-center'>
<Label>Phone</Label>
<Input
type='identifier'
className='bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500'
asChild
>
<input type='tel' />
</Input>
</div>

<Errors
render={({ code, message }) => (
<CustomError
code={code}
message={message}
/>
)}
/>
</Field>

<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Sign In with Phone Number
</Submit>
</div>
</div>
</SignInStart>

<SignInFactorOne>
<div className='flex flex-col items-center justify-center gap-12'>
<h1 className='text-xl mb-6 font-mono'>FIRST FACTOR</h1>

<Field
name='password'
className='flex flex-col gap-4'
>
<div className='flex gap-4 justify-between items-center'>
<Label>Password</Label>
<Input
type='password'
className='bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500'
<SignInStrategies>
<div className='flex gap-6 flex-col'>
<H1>STRATEGIES (FIRST/SECOND FACTOR)</H1>

<H2>
<SignInFactorOne>First Factor</SignInFactorOne>
<SignInFactorTwo>Second Factor</SignInFactorTwo>
</H2>

<SignInStrategy name='password'>
<Field
name='password'
className='flex flex-col gap-4'
>
<div className='flex gap-4 justify-between items-center'>
<Label>Password</Label>
<Input
type='password'
className='bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500'
/>
</div>

<Errors
render={({ code, message }) => (
<CustomError
code={code}
message={message}
/>
)}
/>
</Field>

<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Sign In
</Submit>
</SignInStrategy>

<SignInStrategy name='phone_code'>
<Field
name='code'
className='flex flex-col gap-4'
>
<div className='flex gap-4 justify-between items-center'>
<Label>Phone Code</Label>
<Input
type='code'
className='bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500'
/>
</div>

<Errors
render={({ code, message }) => (
<CustomError
code={code}
message={message}
/>
)}
/>
</div>
</Field>

<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Sign In
</Submit>
</SignInStrategy>

<SignInStrategy name='reset_password_email_code'>
<H3>Verify your email</H3>

<P>Please check your email for a verification code...</P>

<Errors
render={({ code, message }) => (
<CustomError
code={code}
message={message}
<Field
name='code'
className='flex flex-col gap-4'
>
<div className='flex gap-4 justify-between items-center'>
<Label>Phone Code</Label>
<Input
type='code'
className='bg-tertiary rounded-sm px-2 py-1 border border-foreground data-[invalid]:border-red-500'
/>
)}
/>
</Field>
</div>

<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Sign In
</Submit>
</div>
</SignInFactorOne>
<Errors
render={({ code, message }) => (
<CustomError
code={code}
message={message}
/>
)}
/>
</Field>

<SignInFactorTwo>
<p>Factor two child</p>
</SignInFactorTwo>
<Submit className='px-4 py-2 b-1 bg-blue-950 bg-opacity-20 hover:bg-opacity-10 active:bg-opacity-5 rounded-md dark:bg-opacity-100 dark:hover:bg-opacity-80 dark:active:bg-opacity-50 transition'>
Verify
</Submit>
</SignInStrategy>
</div>
</SignInStrategies>

<SignInSSOCallback />
</div>
Expand Down
8 changes: 4 additions & 4 deletions packages/elements/examples/nextjs/components/debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ function LogButtons() {
Log Fields
</Button>
<Button onClick={() => console.dir(ref.getSnapshot().context)}>Log Context</Button>
<Button onClick={() => console.dir(ref.getSnapshot().context.clerk.__unstable__environment)}>
Log Environment
</Button>
<Button onClick={() => console.dir(ref.getSnapshot().context.currentFactor)}>Log Current Factor</Button>
<Button onClick={() => console.dir(ref.getSnapshot().context.environment)}>Log Environment</Button>
<Button onClick={() => console.dir(ref.getSnapshot().context.resource)}>Log Resource</Button>
</>
);
}

export function Debug() {
return (
<div className='absolute text-xs flex flex-col p-4 gap-4 bottom-0 w-screen justify-center bg-secondary border-tertiary border-t max-w-full overflow-clip'>
<div className='absolute text-xs flex flex-col p-4 gap-4 bottom-0 w-screen justify-center bg-secondary border-tertiary border-t'>
<ActiveState />

<div className='flex gap-4 bottom-0 w-screen justify-center'>
Expand Down
36 changes: 36 additions & 0 deletions packages/elements/examples/nextjs/components/design.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { ComponentPropsWithoutRef } from 'react';

export const H1 = (props: ComponentPropsWithoutRef<'h1'>) => (
<h1 // eslint-disable-line jsx-a11y/heading-has-content
className='text-xl mb-6 font-mono'
{...props}
/>
);

export const H2 = (props: ComponentPropsWithoutRef<'h2'>) => (
<h2 // eslint-disable-line jsx-a11y/heading-has-content
className='text-l mb-4 font-mono'
{...props}
/>
);

export const H3 = (props: ComponentPropsWithoutRef<'h3'>) => (
<h3 // eslint-disable-line jsx-a11y/heading-has-content
className='text-m mb-3 font-mono'
{...props}
/>
);

export const P = (props: ComponentPropsWithoutRef<'p'>) => (
<p
className='text-sm font-mono'
{...props}
/>
);

export const HR = (props: ComponentPropsWithoutRef<'hr'>) => (
<hr
className='w-full border-foreground opacity-10'
{...props}
/>
);
2 changes: 1 addition & 1 deletion packages/elements/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"lint": "eslint src/",
"lint:attw": "attw --pack .",
"lint:publint": "publint",
"test": "jest --passWithNoTests",
"test": "jest",
"test:cache:clear": "jest --clearCache --useStderr"
},
"dependencies": {
Expand Down
25 changes: 25 additions & 0 deletions packages/elements/src/common/strategy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { FormProps } from '@radix-ui/react-form';
import type { PropsWithChildren } from 'react';

import { useSignInFlowSelector } from '../internals/machines/sign-in.context';
import { Form } from './form';

// ================= STRATEGIES ================= //

type StrategiesProps = FormProps;
const Strategies = Form;

// ================= STRATEGY ================= //

type StrategyProps = PropsWithChildren<{ name: string }>;

function Strategy({ children, name }: StrategyProps) {
// TODO: Make generic
const active = useSignInFlowSelector(state => state.context.currentFactor?.strategy === name);
return active ? children : null;
}

// ================= EXPORTS ================= //

export { Strategies, Strategy };
export type { StrategiesProps, StrategyProps };
10 changes: 9 additions & 1 deletion packages/elements/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ export { Errors, Field, Form, Input, Label, Submit } from './common/form';
export { SocialProviders } from './common/social-providers';

/** Sign In Components */
export { SignIn, SignInStart, SignInFactorOne, SignInFactorTwo, SignInSSOCallback } from './sign-in';
export {
SignIn,
SignInStart,
SignInFactorOne,
SignInFactorTwo,
SignInSSOCallback,
SignInStrategies,
SignInStrategy,
} from './sign-in';

/** Hooks */
export { useSignInFlow, useSignInFlowSelector } from './internals/machines/sign-in.context';
Expand Down
25 changes: 2 additions & 23 deletions packages/elements/src/internals/machines/sign-in.actors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@ import type {
EnvironmentResource,
HandleOAuthCallbackParams,
HandleSamlCallbackParams,
PreferredSignInStrategy,
PrepareFirstFactorParams,
PrepareSecondFactorParams,
SignInFactor,
SignInFirstFactor,
SignInResource,
} from '@clerk/types';
Expand All @@ -18,7 +16,6 @@ import { ClerkElementsRuntimeError } from '../errors/error';
import type { ClerkRouter } from '../router';
import type { SignInMachineContext } from './sign-in.machine';
import type { WithClerk, WithClient, WithParams } from './sign-in.types';
import { determineStartingSignInFactor } from './sign-in.utils';
import { assertIsDefined } from './utils/assert';

// ================= createSignIn ================= //
Expand Down Expand Up @@ -62,24 +59,6 @@ export const authenticateWithRedirect = fromPromise<void, AuthenticateWithRedire
},
);

// ================= determineStartingFirstFactor ================= //

export type DetermineStartingFirstFactorInput = {
supportedFactors: SignInFirstFactor[];
identifier: string | null;
preferredStrategy: PreferredSignInStrategy | undefined;
};

export const determineStartingFirstFactor = fromPromise<SignInFactor | null, DetermineStartingFirstFactorInput>(
async ({ input: { supportedFactors, identifier, preferredStrategy = 'password' } }) => {
try {
return Promise.resolve(determineStartingSignInFactor(supportedFactors, identifier, preferredStrategy));
} catch (e) {
return Promise.reject(e);
}
},
);

// ================= prepareFirstFactor ================= //

export type PrepareFirstFactorInput = WithClient<WithParams<PrepareFirstFactorParams | null>>;
Expand Down Expand Up @@ -153,8 +132,8 @@ export const handleSSOCallback = fromPromise<unknown, HandleSSOCallbackInput>(as
return input.clerk.handleRedirectCallback(
{
afterSignInUrl: input.clerk.buildAfterSignInUrl(),
firstFactorUrl: '../factor-one',
secondFactorUrl: '../factor-two',
firstFactorUrl: '../',
secondFactorUrl: '../',
...input.params,
},
// @ts-expect-error - Align on return typing. `void` vs `Promise<unknown>`
Expand Down

0 comments on commit 55f955f

Please sign in to comment.