Skip to content

Commit

Permalink
feat(elements): Support SignIn.Captcha and sso-callback step (#4523)
Browse files Browse the repository at this point in the history
  • Loading branch information
BRKalow authored Nov 8, 2024
1 parent cc24c81 commit f030f6f
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 5 deletions.
5 changes: 5 additions & 0 deletions .changeset/rich-badgers-pump.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/elements': minor
---

Introduce support for `<SignIn.Captcha />` and `<SignIn.Step name='sso-callback'>`. This allows rendering of a CAPTCHA widget when a sign in attempt is transferred to a sign up attempt.
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ export default function SignInPage() {
<CustomSubmit>Update Password</CustomSubmit>
</div>
</SignIn.Step>
<SignIn.Step name='sso-callback'></SignIn.Step>
</div>
</SignIn.Root>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,6 @@ export const handleRedirectCallback = fromCallback<AnyEventObject, HandleRedirec

void loadedClerk.handleRedirectCallback(
{
signInForceRedirectUrl: ClerkJSNavigationEvent.complete,
signInFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
signUpForceRedirectUrl: ClerkJSNavigationEvent.complete,
signUpFallbackRedirectUrl: ClerkJSNavigationEvent.complete,
continueSignUpUrl: ClerkJSNavigationEvent.continue,
firstFactorUrl: ClerkJSNavigationEvent.signIn,
resetPasswordUrl: ClerkJSNavigationEvent.resetPassword,
Expand Down
70 changes: 70 additions & 0 deletions packages/elements/src/react/sign-in/captcha.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';

import { CAPTCHA_ELEMENT_ID } from '~/internals/constants';
import { ClerkElementsRuntimeError } from '~/internals/errors';

import { useActiveTags } from '../hooks';
import { SignInRouterCtx } from './context';

export type SignInCaptchaElement = React.ElementRef<'div'>;

type CaptchaElementProps = Omit<
React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>,
'id' | 'children'
>;

export type SignInCaptchaProps =
| ({
asChild: true;
/* Must only be a self-closing element/component */
children: React.ReactElement;
} & CaptchaElementProps)
| ({ asChild?: false; children?: undefined } & CaptchaElementProps);

/**
* The `<SignIn.Captcha>` component is used to render the Cloudflare Turnstile widget. It must be used within the `<SignIn.Step name="sso-callback">` component.
*
* If utilizing the `asChild` prop, the component must be a self-closing element or component. Any children passed to the immediate child component of <SignIn.Captcha> will be ignored.
*
* @param {boolean} [asChild] - If true, `<Captcha />` will render as its child element, passing along any necessary props.
*
* @example
* <SignIn.Root>
* <SignIn.Step name="sso-callback">
* <SignIn.Captcha />
* </SignIn.Step>
* </SignIn.Root>
*
* @example
* <SignIn.Root>
* <SignIn.Step name="sso-callback">
* <SignIn.Captcha asChild>
* <aside/>
* </SignIn.Captcha>
* </SignIn.Step>
* </SignIn.Root>
*/

export const SignInCaptcha = React.forwardRef<SignInCaptchaElement, SignInCaptchaProps>(
({ asChild, children, ...rest }, forwardedRef) => {
const routerRef = SignInRouterCtx.useActorRef();
const activeState = useActiveTags(routerRef, 'step:callback');

if (!activeState) {
throw new ClerkElementsRuntimeError(
'<Captcha> must be used within the <SignIn.Step name="sso-callback"> component.',
);
}

const Comp = asChild ? Slot : 'div';

return (
<Comp
id={CAPTCHA_ELEMENT_ID}
{...rest}
ref={forwardedRef}
/>
);
},
);
13 changes: 13 additions & 0 deletions packages/elements/src/react/sign-in/sso-callback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { PropsWithChildren } from 'react';

import { useActiveTags } from '~/react/hooks';
import { SignInRouterCtx } from '~/react/sign-in/context';

export type SignInSSOCallbackProps = PropsWithChildren;

export function SignInSSOCallback({ children }: SignInSSOCallbackProps) {
const routerRef = SignInRouterCtx.useActorRef();
const activeState = useActiveTags(routerRef, 'step:callback');

return activeState ? children : null;
}
8 changes: 7 additions & 1 deletion packages/elements/src/react/sign-in/step.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ClerkElementsRuntimeError } from '~/internals/errors';
import { SignInChooseSession, type SignInChooseSessionProps } from './choose-session';
import { SignInChooseStrategy, type SignInChooseStrategyProps, SignInForgotPassword } from './choose-strategy';
import { SignInResetPassword, type SignInResetPasswordProps } from './reset-password';
import type { SignInSSOCallbackProps } from './sso-callback';
import { SignInSSOCallback } from './sso-callback';
import { SignInStart, type SignInStartProps } from './start';
import { SignInVerifications, type SignInVerificationsProps } from './verifications';

Expand All @@ -16,6 +18,7 @@ export const SIGN_IN_STEPS = {
'choose-session': 'choose-session',
'forgot-password': 'forgot-password',
'reset-password': 'reset-password',
'sso-callback': 'sso-callback',
} as const;

export type TSignInStep = (typeof SIGN_IN_STEPS)[keyof typeof SIGN_IN_STEPS];
Expand All @@ -26,7 +29,8 @@ export type SignInStepProps =
| StepWithProps<'verifications', SignInVerificationsProps>
| StepWithProps<'choose-strategy' | 'forgot-password', SignInChooseStrategyProps>
| StepWithProps<'reset-password', SignInResetPasswordProps>
| StepWithProps<'choose-session', SignInChooseSessionProps>;
| StepWithProps<'choose-session', SignInChooseSessionProps>
| StepWithProps<'sso-callback', SignInSSOCallbackProps>;

/**
* Render different steps of the sign-in flow. Initially the `'start'` step is rendered. Once a sign-in attempt has been created, `'verifications'` will be displayed. If during that verification step the user decides to choose a different method of signing in or verifying, the `'choose-strategy'` step will be displayed.
Expand Down Expand Up @@ -63,6 +67,8 @@ export function SignInStep(props: SignInStepProps) {
return <SignInResetPassword {...props} />;
case SIGN_IN_STEPS['choose-session']:
return <SignInChooseSession {...props} />;
case SIGN_IN_STEPS['sso-callback']:
return <SignInSSOCallback {...props} />;
default:
throw new ClerkElementsRuntimeError(`Invalid step name. Use: ${Object.keys(SIGN_IN_STEPS).join(',')}.`);
}
Expand Down

0 comments on commit f030f6f

Please sign in to comment.