Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(clerk-js): Launch sign-in-or-up flow #4788

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { LocalStorageBroadcastChannel } from '@clerk/shared/localStorageBroadcas
import { logger } from '@clerk/shared/logger';
import { isHttpOrHttps, isValidProxyUrl, proxyUrlToAbsoluteURL } from '@clerk/shared/proxy';
import { eventPrebuiltComponentMounted, TelemetryCollector } from '@clerk/shared/telemetry';
import { addClerkPrefix, stripScheme } from '@clerk/shared/url';
import { addClerkPrefix, isAbsoluteUrl, stripScheme } from '@clerk/shared/url';
import { handleValueOrFn, noop } from '@clerk/shared/utils';
import type {
__internal_UserVerificationModalProps,
Expand Down Expand Up @@ -365,8 +365,8 @@ export class Clerk implements ClerkInterface {
}
};

#isCombinedFlow(): boolean {
return this.#options.experimental?.combinedFlow && this.#options.signInUrl === this.#options.signUpUrl;
#isCombinedSignInOrUpFlow(): boolean {
return Boolean(!this.#options.signUpUrl && this.#options.signInUrl && !isAbsoluteUrl(this.#options.signInUrl));
}

public signOut: SignOut = async (callbackOrOptions?: SignOutCallback | SignOutOptions, options?: SignOutOptions) => {
Expand Down Expand Up @@ -2112,13 +2112,17 @@ export class Clerk implements ClerkInterface {
return '';
}

const signInOrUpUrl = this.#options[key] || this.environment.displayConfig[key];
let signInOrUpUrl = this.#options[key] || this.environment.displayConfig[key];
if (this.#isCombinedSignInOrUpFlow()) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- The isCombinedSignInOrUpFlow() function checks for the existence of signInUrl
signInOrUpUrl = this.#options.signInUrl!;
}
const redirectUrls = new RedirectUrls(this.#options, options).toSearchParams();
const initValues = new URLSearchParams(_initValues || {});
const url = buildURL(
{
base: signInOrUpUrl,
hashPath: this.#isCombinedFlow() && key === 'signUpUrl' ? '/create' : '',
hashPath: this.#isCombinedSignInOrUpFlow() && key === 'signUpUrl' ? '/create' : '',
hashSearchParams: [initValues, redirectUrls],
},
{ stringify: true },
Expand Down
2 changes: 1 addition & 1 deletion packages/clerk-js/src/ui/common/redirects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function buildEmailLinkRedirectUrl(
baseUrl: string | undefined = '',
): string {
const { routing, authQueryString, path } = ctx;
const isCombinedFlow = '__experimental' in ctx && ctx.__experimental?.combinedProps;
const isCombinedFlow = 'isCombinedFlow' in ctx && ctx.isCombinedFlow;
return buildRedirectUrl({
routing,
baseUrl,
Expand Down
4 changes: 1 addition & 3 deletions packages/clerk-js/src/ui/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../
import {
SignInContext,
SignUpContext,
useOptions,
useSignInContext,
useSignUpContext,
withCoreSessionSwitchGuard,
Expand Down Expand Up @@ -37,7 +36,6 @@ function RedirectToSignIn() {
function SignInRoutes(): JSX.Element {
const signInContext = useSignInContext();
const signUpContext = useSignUpContext();
const options = useOptions();

return (
<Flow.Root flow='signIn'>
Expand Down Expand Up @@ -76,7 +74,7 @@ function SignInRoutes(): JSX.Element {
redirectUrl='../factor-two'
/>
</Route>
{options.experimental?.combinedFlow && (
{signInContext.isCombinedFlow && (
<Route path='create'>
<Route
path='verify-email-address'
Expand Down
6 changes: 2 additions & 4 deletions packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils';
import type { SignInStartIdentifier } from '../../common';
import { getIdentifierControlDisplayValues, groupIdentifiers, withRedirectToAfterSignIn } from '../../common';
import { buildSSOCallbackURL } from '../../common/redirects';
import { useCoreSignIn, useEnvironment, useOptions, useSignInContext } from '../../contexts';
import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts';
import { Col, descriptors, Flow, localizationKeys } from '../../customizables';
import {
Card,
Expand Down Expand Up @@ -66,10 +66,8 @@ export function _SignInStart(): JSX.Element {
const { displayConfig, userSettings } = useEnvironment();
const signIn = useCoreSignIn();
const { navigate } = useRouter();
const options = useOptions();
const ctx = useSignInContext();
const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx;
const isCombinedFlow = !!options?.experimental?.combinedFlow;
const { afterSignInUrl, signUpUrl, waitlistUrl, isCombinedFlow } = ctx;
const supportEmail = useSupportEmail();
const identifierAttributes = useMemo<SignInStartIdentifier[]>(
() => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers),
Expand Down
13 changes: 9 additions & 4 deletions packages/clerk-js/src/ui/components/SignUp/SignUpContinue.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useClerk } from '@clerk/shared/react';
import React, { useEffect, useMemo } from 'react';

import { SignInContext, useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { SignInContext, useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys } from '../../customizables';
import {
Card,
Expand Down Expand Up @@ -31,11 +31,16 @@ function _SignUpContinue() {
const { navigate } = useRouter();
const { displayConfig, userSettings } = useEnvironment();
const { attributes } = userSettings;
const { afterSignUpUrl, signInUrl, unsafeMetadata, initialValues = {} } = useSignUpContext();
const {
afterSignUpUrl,
signInUrl,
unsafeMetadata,
initialValues = {},
isCombinedFlow: _isCombinedFlow,
} = useSignUpContext();
const signUp = useCoreSignUp();
const options = useOptions();
const isWithinSignInContext = !!React.useContext(SignInContext);
const isCombinedFlow = !!(options.experimental?.combinedFlow && !!isWithinSignInContext);
const isCombinedFlow = !!(_isCombinedFlow && !!isWithinSignInContext);
const isProgressiveSignUp = userSettings.signUp.progressive;
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState<ActiveIdentifier>(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
Expand Down
7 changes: 3 additions & 4 deletions packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import React from 'react';
import { ERROR_CODES, SIGN_UP_MODES } from '../../../core/constants';
import { getClerkQueryParam, removeClerkQueryParam } from '../../../utils/getClerkQueryParam';
import { buildSSOCallbackURL, withRedirectToAfterSignUp } from '../../common';
import { SignInContext, useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts';
import { SignInContext, useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts';
import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables';
import {
Card,
Expand Down Expand Up @@ -39,10 +39,9 @@ function _SignUpStart(): JSX.Element {
const { attributes } = userSettings;
const { setActive } = useClerk();
const ctx = useSignUpContext();
const options = useOptions();
const isWithinSignInContext = !!React.useContext(SignInContext);
const { afterSignUpUrl, signInUrl, unsafeMetadata } = ctx;
const isCombinedFlow = !!(options.experimental?.combinedFlow && !!isWithinSignInContext);
const { afterSignUpUrl, signInUrl, unsafeMetadata, isCombinedFlow: _isCombinedFlow } = ctx;
const isCombinedFlow = !!(_isCombinedFlow && !!isWithinSignInContext);
const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState<ActiveIdentifier>(
getInitialActiveIdentifier(attributes, userSettings.signUp.progressive),
);
Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/src/ui/contexts/components/SignIn.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useClerk } from '@clerk/shared/react';
import { isAbsoluteUrl } from '@clerk/shared/url';
import { createContext, useContext, useMemo } from 'react';

import { SIGN_IN_INITIAL_VALUE_KEYS } from '../../../core/constants';
Expand All @@ -21,6 +22,7 @@ export type SignInContextType = SignInCtx & {
afterSignInUrl: string;
transferable: boolean;
waitlistUrl: string;
isCombinedFlow: boolean;
};

export const SignInContext = createContext<SignInCtx | null>(null);
Expand All @@ -32,6 +34,7 @@ export const useSignInContext = (): SignInContextType => {
const { queryParams, queryString } = useRouter();
const options = useOptions();
const clerk = useClerk();
const isCombinedFlow = Boolean(!options.signUpUrl && options.signInUrl && !isAbsoluteUrl(options.signInUrl));

if (context === null || context.componentName !== 'SignIn') {
throw new Error(`Clerk: useSignInContext called outside of the mounted SignIn component.`);
Expand Down Expand Up @@ -96,5 +99,6 @@ export const useSignInContext = (): SignInContextType => {
queryParams,
initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
authQueryString: redirectUrls.toSearchParams().toString(),
isCombinedFlow,
};
};
4 changes: 4 additions & 0 deletions packages/clerk-js/src/ui/contexts/components/SignUp.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { useClerk } from '@clerk/shared/react';
import { isAbsoluteUrl } from '@clerk/shared/url';
import { createContext, useContext, useMemo } from 'react';

import { SIGN_UP_INITIAL_VALUE_KEYS } from '../../../core/constants';
Expand All @@ -20,6 +21,7 @@ export type SignUpContextType = SignUpCtx & {
afterSignUpUrl: string;
afterSignInUrl: string;
waitlistUrl: string;
isCombinedFlow: boolean;
};

export const SignUpContext = createContext<SignUpCtx | null>(null);
Expand All @@ -31,6 +33,7 @@ export const useSignUpContext = (): SignUpContextType => {
const { queryParams, queryString } = useRouter();
const options = useOptions();
const clerk = useClerk();
const isCombinedFlow = Boolean(!options.signUpUrl && options.signInUrl && !isAbsoluteUrl(options.signInUrl));

const initialValuesFromQueryParams = useMemo(
() => getInitialValuesFromQueryParams(queryString, SIGN_UP_INITIAL_VALUE_KEYS),
Expand Down Expand Up @@ -87,5 +90,6 @@ export const useSignUpContext = (): SignUpContextType => {
queryParams,
initialValues: { ...ctx.initialValues, ...initialValuesFromQueryParams },
authQueryString: redirectUrls.toSearchParams().toString(),
isCombinedFlow,
};
};
44 changes: 0 additions & 44 deletions packages/types/src/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -895,50 +895,6 @@ export type RoutingOptions =
| { path?: never; routing?: Extract<RoutingStrategy, 'hash' | 'virtual'> };

export type SignInProps = RoutingOptions & {
/**
* Full URL or path to navigate after successful sign in.
* This value has precedence over other redirect props, environment variables or search params.
* Use this prop to override the redirect URL when needed.
* @default undefined
*/
forceRedirectUrl?: string | null;
/**
* Full URL or path to navigate after successful sign in.
* This value is used when no other redirect props, environment variables or search params are present.
* @default undefined
*/
fallbackRedirectUrl?: string | null;
/**
* Full URL or path to for the sign up process.
* Used to fill the "Sign up" link in the SignUp component.
*/
signUpUrl?: string;
/**
* Customisation options to fully match the Clerk components to your own brand.
* These options serve as overrides and will be merged with the global `appearance`
* prop of ClerkProvider (if one is provided)
*/
appearance?: SignInTheme;
/**
* Initial values that are used to prefill the sign in form.
*/
initialValues?: SignInInitialValues;
/**
* Enable experimental flags to gain access to new features. These flags are not guaranteed to be stable and may change drastically in between patch or minor versions.
*/
__experimental?: Record<string, any> & { newComponents?: boolean; combinedProps?: SignInCombinedProps };
/**
* Full URL or path to for the waitlist process.
* Used to fill the "Join waitlist" link in the SignUp component.
*/
waitlistUrl?: string;
} & TransferableOption &
SignUpForceRedirectUrl &
SignUpFallbackRedirectUrl &
LegacyRedirectProps &
AfterSignOutUrl;

export type SignInCombinedProps = RoutingOptions & {
/**
* Full URL or path to navigate after successful sign in.
* This value has precedence over other redirect props, environment variables or search params.
Expand Down
Loading