diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index b3da62b7da..2ba50052d0 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -353,6 +353,10 @@ export class Clerk implements ClerkInterface { } }; + #isCombinedFlow(): boolean { + return this.#options.experimental?.combinedFlow && this.#options.signInUrl === this.#options.signUpUrl; + } + public signOut: SignOut = async (callbackOrOptions?: SignOutCallback | SignOutOptions, options?: SignOutOptions) => { if (!this.client || this.client.sessions.length === 0) { return; @@ -1981,10 +1985,18 @@ export class Clerk implements ClerkInterface { if (!key || !this.loaded || !this.environment || !this.environment.displayConfig) { return ''; } + const signInOrUpUrl = this.#options[key] || this.environment.displayConfig[key]; const redirectUrls = new RedirectUrls(this.#options, options).toSearchParams(); const initValues = new URLSearchParams(_initValues || {}); - const url = buildURL({ base: signInOrUpUrl, hashSearchParams: [initValues, redirectUrls] }, { stringify: true }); + const url = buildURL( + { + base: signInOrUpUrl, + hashPath: this.#isCombinedFlow() && key === 'signUpUrl' ? '/create' : '', + hashSearchParams: [initValues, redirectUrls], + }, + { stringify: true }, + ); return this.buildUrlWithAuth(url); }; diff --git a/packages/clerk-js/src/core/constants.ts b/packages/clerk-js/src/core/constants.ts index ae34145cb6..488568c4e6 100644 --- a/packages/clerk-js/src/core/constants.ts +++ b/packages/clerk-js/src/core/constants.ts @@ -32,6 +32,7 @@ export const ERROR_CODES = { ENTERPRISE_SSO_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'enterprise_sso_email_address_domain_mismatch', ENTERPRISE_SSO_HOSTED_DOMAIN_MISMATCH: 'enterprise_sso_hosted_domain_mismatch', SAML_EMAIL_ADDRESS_DOMAIN_MISMATCH: 'saml_email_address_domain_mismatch', + INVITATION_ACCOUNT_NOT_EXISTS: 'invitation_account_not_exists', } as const; export const SIGN_IN_INITIAL_VALUE_KEYS = ['email_address', 'phone_number', 'username']; diff --git a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx index 4a4bfc99f6..968ab8526e 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignIn.tsx @@ -6,6 +6,7 @@ import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '../../ import { SignInContext, SignUpContext, + useOptions, useSignInContext, useSignUpContext, withCoreSessionSwitchGuard, @@ -36,6 +37,7 @@ function RedirectToSignIn() { function SignInRoutes(): JSX.Element { const signInContext = useSignInContext(); const signUpContext = useSignUpContext(); + const options = useOptions(); return ( @@ -74,7 +76,7 @@ function SignInRoutes(): JSX.Element { redirectUrl='../factor-two' /> - {signInContext.__experimental?.combinedFlow && ( + {options.experimental?.combinedFlow && ( diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index b5f5bade1c..90026ed5e9 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -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, useSignInContext } from '../../contexts'; +import { useCoreSignIn, useEnvironment, useOptions, useSignInContext } from '../../contexts'; import { Col, descriptors, Flow, localizationKeys } from '../../customizables'; import { Card, @@ -65,9 +65,10 @@ export function _SignInStart(): JSX.Element { const { displayConfig, userSettings } = useEnvironment(); const signIn = useCoreSignIn(); const { navigate } = useRouter(); + const options = useOptions(); const ctx = useSignInContext(); - const { afterSignInUrl, signInUrl, signUpUrl, waitlistUrl, __experimental } = ctx; - const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false; + const { afterSignInUrl, signUpUrl, waitlistUrl } = ctx; + const isCombinedFlow = (options?.experimental?.combinedFlow && options.signInUrl === options.signUpUrl) || false; const supportEmail = useSupportEmail(); const identifierAttributes = useMemo( () => groupIdentifiers(userSettings.enabledFirstFactorIdentifiers), @@ -327,18 +328,24 @@ export function _SignInStart(): JSX.Element { (e: ClerkAPIError) => e.code === ERROR_CODES.INVALID_STRATEGY_FOR_USER || e.code === ERROR_CODES.FORM_PASSWORD_INCORRECT, ); + const alreadySignedInError: ClerkAPIError = e.errors.find( (e: ClerkAPIError) => e.code === 'identifier_already_signed_in', ); + const accountDoesNotExistError: ClerkAPIError = e.errors.find( + (e: ClerkAPIError) => + e.code === ERROR_CODES.INVITATION_ACCOUNT_NOT_EXISTS || e.code === ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND, + ); if (instantPasswordError) { await signInWithFields(identifierField); } else if (alreadySignedInError) { const sid = alreadySignedInError.meta!.sessionId!; await clerk.setActive({ session: sid, redirectUrl: afterSignInUrl }); - } else { - if (isCombinedFlow && userSettings.signUp.mode === SIGN_UP_MODES.WAITLIST) { - const attribute = getSignUpAttributeFromIdentifier(identifierField); + } else if (isCombinedFlow && accountDoesNotExistError) { + const attribute = getSignUpAttributeFromIdentifier(identifierField); + + if (userSettings.signUp.mode === SIGN_UP_MODES.WAITLIST) { const waitlistUrl = clerk.buildWaitlistUrl( attribute === 'emailAddress' ? { @@ -350,11 +357,14 @@ export function _SignInStart(): JSX.Element { ); return navigate(waitlistUrl); } - if (isCombinedFlow) { - const attribute = getSignUpAttributeFromIdentifier(identifierField); - clerk.client.signUp[attribute] = identifierField.value; - return navigate('create'); + + clerk.client.signUp[attribute] = identifierField.value; + const paramsToForward = new URLSearchParams(); + if (organizationTicket) { + paramsToForward.set('__clerk_ticket', organizationTicket); } + return navigate(`create?${paramsToForward.toString()}`); + } else { handleError(e, [identifierField, instantPasswordField], card.setError); } }; diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 49bd7fab0f..d5758243c9 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -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 { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts'; +import { useCoreSignUp, useEnvironment, useOptions, useSignUpContext } from '../../contexts'; import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables'; import { Card, @@ -39,8 +39,9 @@ function _SignUpStart(): JSX.Element { const { attributes } = userSettings; const { setActive } = useClerk(); const ctx = useSignUpContext(); - const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata, __experimental } = ctx; - const isCombinedFlow = (__experimental?.combinedFlow && signInUrl === signUpUrl) || false; + const options = useOptions(); + const { afterSignUpUrl, signInUrl, signUpUrl, unsafeMetadata } = ctx; + const isCombinedFlow = (options.experimental?.combinedFlow && signInUrl === signUpUrl) || false; const [activeCommIdentifierType, setActiveCommIdentifierType] = React.useState( getInitialActiveIdentifier(attributes, userSettings.signUp.progressive), ); diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index 785791bb2d..7a76641557 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -741,6 +741,7 @@ export type ClerkOptions = ClerkOptionsNavigation & { persistClient: boolean; rethrowOfflineNetworkErrors: boolean; + combinedFlow: boolean; }, Record >; @@ -896,7 +897,7 @@ export type SignInProps = RoutingOptions & { /** * 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 & { newComponents?: boolean; combinedFlow?: boolean; signUpProps?: SignUpProps }; + __experimental?: Record & { newComponents?: boolean; signUpProps?: SignUpProps }; /** * Full URL or path to for the waitlist process. * Used to fill the "Join waitlist" link in the SignUp component.