diff --git a/.changeset/pretty-scissors-thank.md b/.changeset/pretty-scissors-thank.md new file mode 100644 index 00000000000..4197aea1643 --- /dev/null +++ b/.changeset/pretty-scissors-thank.md @@ -0,0 +1,9 @@ +--- +'@clerk/clerk-js': major +'@clerk/clerk-react': major +'@clerk/types': patch +--- + +Drop `redirectToHome` redirect method in favour of `redirectToAfterSignUp` or `redirectToAfterSignIn`. + +When the `` and `` components are rendered while a user is already logged in, they will now redirect to the configured `afterSignIn` and `afterSignUp` URLs, respectively. Previously, the redirect URL was set to the home URL configured in the dashboard. diff --git a/packages/clerk-js/src/core/clerk.redirects.test.ts b/packages/clerk-js/src/core/clerk.redirects.test.ts index 360e0a7ada5..f8dc0fe1167 100644 --- a/packages/clerk-js/src/core/clerk.redirects.test.ts +++ b/packages/clerk-js/src/core/clerk.redirects.test.ts @@ -95,7 +95,7 @@ describe('Clerk singleton - Redirects', () => { mockNavigate = jest.fn((to: string) => Promise.resolve(to)); }); - describe('.redirectTo(SignUp|SignIn|UserProfile|Home|CreateOrganization|OrganizationProfile)', () => { + describe('.redirectTo(SignUp|SignIn|UserProfile|AfterSignIn|AfterSignUp|CreateOrganization|OrganizationProfile)', () => { let clerkForProductionInstance: Clerk; let clerkForDevelopmentInstance: Clerk; @@ -152,12 +152,20 @@ describe('Clerk singleton - Redirects', () => { expect(mockNavigate).toHaveBeenNthCalledWith(2, '/user-profile'); }); - it('redirects to home', async () => { - await clerkForProductionInstance.redirectToHome(); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/home'); + it('redirects to afterSignUp', async () => { + await clerkForProductionInstance.redirectToAfterSignUp(); + expect(mockNavigate).toHaveBeenNthCalledWith(1, '/'); - await clerkForDevelopmentInstance.redirectToHome(); - expect(mockNavigate).toHaveBeenNthCalledWith(2, '/home'); + await clerkForDevelopmentInstance.redirectToAfterSignUp(); + expect(mockNavigate).toHaveBeenNthCalledWith(2, '/'); + }); + + it('redirects to afterSignIn', async () => { + await clerkForProductionInstance.redirectToAfterSignIn(); + expect(mockNavigate).toHaveBeenNthCalledWith(1, '/'); + + await clerkForDevelopmentInstance.redirectToAfterSignIn(); + expect(mockNavigate).toHaveBeenNthCalledWith(2, '/'); }); it('redirects to create organization', async () => { @@ -242,14 +250,6 @@ describe('Clerk singleton - Redirects', () => { expect(mockHref).toHaveBeenNthCalledWith(2, 'http://another-test.host/user-profile#__clerk_db_jwt[deadbeef]'); }); - it('redirects to home', async () => { - await clerkForProductionInstance.redirectToHome(); - expect(mockHref).toHaveBeenNthCalledWith(1, 'http://another-test.host/home'); - - await clerkForDevelopmentInstance.redirectToHome(); - expect(mockHref).toHaveBeenNthCalledWith(2, 'http://another-test.host/home#__clerk_db_jwt[deadbeef]'); - }); - it('redirects to create organization', async () => { await clerkForProductionInstance.redirectToCreateOrganization(); expect(mockHref).toHaveBeenNthCalledWith(1, 'http://another-test.host/create-organization'); diff --git a/packages/clerk-js/src/core/clerk.ts b/packages/clerk-js/src/core/clerk.ts index f831e805d07..aba153b77dc 100644 --- a/packages/clerk-js/src/core/clerk.ts +++ b/packages/clerk-js/src/core/clerk.ts @@ -327,7 +327,8 @@ export class Clerk implements ClerkInterface { public openSignIn = (props?: SignInProps): void => { this.assertComponentsReady(this.#componentControls); if (sessionExistsAndSingleSessionModeEnabled(this, this.#environment) && this.#instanceType === 'development') { - return console.info(warnings.cannotOpenSignUpOrSignUp); + console.info(warnings.cannotOpenSignUpOrSignUp); + return; } void this.#componentControls .ensureMounted({ preloadHint: 'SignIn' }) @@ -372,7 +373,8 @@ export class Clerk implements ClerkInterface { public openOrganizationProfile = (props?: OrganizationProfileProps): void => { this.assertComponentsReady(this.#componentControls); if (noOrganizationExists(this) && this.#instanceType === 'development') { - return console.info(warnings.cannotOpenOrgProfile); + console.info(warnings.cannotOpenOrgProfile); + return; } void this.#componentControls .ensureMounted({ preloadHint: 'OrganizationProfile' }) @@ -442,6 +444,10 @@ export class Clerk implements ClerkInterface { public mountUserProfile = (node: HTMLDivElement, props?: UserProfileProps): void => { this.assertComponentsReady(this.#componentControls); + if (noUserExists(this) && this.#instanceType === 'development') { + console.info(warnings.cannotRenderComponentWhenUserDoesNotExist); + return; + } void this.#componentControls.ensureMounted({ preloadHint: 'UserProfile' }).then(controls => controls.mountComponent({ name: 'UserProfile', @@ -465,6 +471,10 @@ export class Clerk implements ClerkInterface { public mountOrganizationProfile = (node: HTMLDivElement, props?: OrganizationProfileProps) => { this.assertComponentsReady(this.#componentControls); + if (noOrganizationExists(this) && this.#instanceType === 'development') { + console.info(warnings.cannotRenderComponentWhenOrgDoesNotExist); + return; + } void this.#componentControls.ensureMounted({ preloadHint: 'OrganizationProfile' }).then(controls => controls.mountComponent({ name: 'OrganizationProfile', @@ -736,6 +746,22 @@ export class Clerk implements ClerkInterface { return this.buildUrlWithAuth(this.#environment.displayConfig.homeUrl); } + public buildAfterSignInUrl(): string { + if (!this.#options.afterSignInUrl) { + return '/'; + } + + return this.buildUrlWithAuth(this.#options.afterSignInUrl); + } + + public buildAfterSignUpUrl(): string { + if (!this.#options.afterSignUpUrl) { + return '/'; + } + + return this.buildUrlWithAuth(this.#options.afterSignUpUrl); + } + public buildCreateOrganizationUrl(): string { if (!this.#environment || !this.#environment.displayConfig) { return ''; @@ -812,9 +838,16 @@ export class Clerk implements ClerkInterface { return; }; - public redirectToHome = async (): Promise => { + public redirectToAfterSignIn = async (): Promise => { + if (inBrowser()) { + return this.navigate(this.buildAfterSignInUrl()); + } + return; + }; + + public redirectToAfterSignUp = async (): Promise => { if (inBrowser()) { - return this.navigate(this.buildHomeUrl()); + return this.navigate(this.buildAfterSignUpUrl()); } return; }; diff --git a/packages/clerk-js/src/core/warnings.ts b/packages/clerk-js/src/core/warnings.ts index 34e5855e5b1..26e957ad588 100644 --- a/packages/clerk-js/src/core/warnings.ts +++ b/packages/clerk-js/src/core/warnings.ts @@ -5,9 +5,13 @@ const formatWarning = (msg: string) => { const warnings = { cannotRenderComponentWhenSessionExists: 'The and components cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the Home URL instead.', + cannotRenderSignUpComponentWhenSessionExists: + 'The component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the value setted in `afterSignUp` URL instead.', + cannotRenderSignInComponentWhenSessionExists: + 'The component cannot render when a user is already signed in, unless the application allows multiple sessions. Since a user is signed in and this application only allows a single session, Clerk is redirecting to the `afterSignIn` URL instead.', cannotRenderComponentWhenUserDoesNotExist: - ' cannot render unless a user is signed in. Since no user is signed in, Clerk is redirecting to the Home URL instead. (This notice only appears in development.)', - cannotRenderComponentWhenOrgDoesNotExist: ` cannot render unless an organization is active. Since no organization is currently active, Clerk is redirecting to the Home URL instead.`, + ' cannot render unless a user is signed in. Since no user is signed in, this is no-op.', + cannotRenderComponentWhenOrgDoesNotExist: ` cannot render unless an organization is active. Since no organization is currently active, this is no-op.`, cannotOpenOrgProfile: 'The OrganizationProfile cannot render unless an organization is active. Since no organization is currently active, this is no-op.', cannotOpenUserProfile: diff --git a/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx new file mode 100644 index 00000000000..e823455c560 --- /dev/null +++ b/packages/clerk-js/src/ui/common/__tests__/withRedirect.test.tsx @@ -0,0 +1,43 @@ +import React from 'react'; + +import { render } from '../../../testUtils'; +import { bindCreateFixtures } from '../../utils/test/createFixtures'; +import { withRedirect } from '../withRedirect'; + +const { createFixtures } = bindCreateFixtures('SignIn'); + +describe('withRedirect', () => { + it('redirects to the redirect url provided when condition is met', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({}); + }); + + const WithHOC = withRedirect( + () => <>, + () => true, + () => '/', + 'Redirecting to /', + ); + + render(, { wrapper }); + + expect(fixtures.router.navigate).toHaveBeenCalledWith('/'); + }); + + it('does no redirects to the redirect url provided when the condition is not met', async () => { + const { wrapper, fixtures } = await createFixtures(f => { + f.withUser({}); + }); + + const WithHOC = withRedirect( + () => <>, + () => false, + () => '/', + 'Redirecting to /', + ); + + render(, { wrapper }); + + expect(fixtures.router.navigate).not.toHaveBeenCalledWith('/'); + }); +}); diff --git a/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx b/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx deleted file mode 100644 index 8dd9d54f6a0..00000000000 --- a/packages/clerk-js/src/ui/common/__tests__/withRedirectToHome.test.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; - -import { render, screen } from '../../../testUtils'; -import { bindCreateFixtures } from '../../utils/test/createFixtures'; -import { - withRedirectToHomeOrganizationGuard, - withRedirectToHomeSingleSessionGuard, - withRedirectToHomeUserGuard, -} from '../withRedirectToHome'; - -const { createFixtures } = bindCreateFixtures('SignIn'); - -describe('withRedirectToHome', () => { - describe('withRedirectToHomeSingleSessionGuard', () => { - it('redirects if a session is present and single session mode is enabled', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({}); - }); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is a session is not present', async () => { - const { wrapper } = await createFixtures(); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - - it('renders the children if multi session mode is enabled and a session is present', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({}); - f.withMultiSessionMode(); - }); - - const WithHOC = withRedirectToHomeSingleSessionGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); - - describe('redirectToHomeUserGuard', () => { - it('redirects if no user is present', async () => { - const { wrapper, fixtures } = await createFixtures(); - - const WithHOC = withRedirectToHomeUserGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is a user is present', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({}); - }); - - const WithHOC = withRedirectToHomeUserGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); - - describe('withRedirectToHomeOrganizationGuard', () => { - it('redirects if no organization is active', async () => { - const { wrapper, fixtures } = await createFixtures(f => { - f.withUser({}); - f.withOrganizations(); - }); - - const WithHOC = withRedirectToHomeOrganizationGuard(() => <>); - - render(, { wrapper }); - - expect(fixtures.router.navigate).toHaveBeenCalledWith(fixtures.environment.displayConfig.homeUrl); - }); - - it('renders the children if is an organization is active', async () => { - const { wrapper } = await createFixtures(f => { - f.withUser({ organization_memberships: ['Org1'] }); - f.withOrganizations(); - }); - - const WithHOC = withRedirectToHomeOrganizationGuard(() => <>test); - - render(, { wrapper }); - - screen.getByText('test'); - }); - }); -}); diff --git a/packages/clerk-js/src/ui/common/index.ts b/packages/clerk-js/src/ui/common/index.ts index b08b98ac791..028056802ab 100644 --- a/packages/clerk-js/src/ui/common/index.ts +++ b/packages/clerk-js/src/ui/common/index.ts @@ -6,7 +6,7 @@ export * from './Gate'; export * from './InfiniteListSpinner'; export * from './redirects'; export * from './verification'; -export * from './withRedirectToHome'; +export * from './withRedirect'; export * from './SSOCallback'; export * from './EmailLinkVerify'; export * from './EmailLinkStatusCard'; diff --git a/packages/clerk-js/src/ui/common/withRedirect.tsx b/packages/clerk-js/src/ui/common/withRedirect.tsx new file mode 100644 index 00000000000..43ec9b95172 --- /dev/null +++ b/packages/clerk-js/src/ui/common/withRedirect.tsx @@ -0,0 +1,99 @@ +import { isDevelopmentFromPublishableKey } from '@clerk/shared/keys'; +import { useClerk } from '@clerk/shared/react'; +import type { Clerk, ClerkOptions, EnvironmentResource } from '@clerk/types'; +import type { ComponentType } from 'react'; +import React from 'react'; + +import { warnings } from '../../core/warnings'; +import type { ComponentGuard } from '../../utils'; +import { sessionExistsAndSingleSessionModeEnabled } from '../../utils'; +import { useEnvironment, useOptions, useSignInContext, useSignUpContext } from '../contexts'; +import { useRouter } from '../router'; +import type { AvailableComponentProps } from '../types'; + +type RedirectUrl = (opts: { clerk: Clerk; environment: EnvironmentResource; options: ClerkOptions }) => string; + +export function withRedirect

( + Component: ComponentType

, + condition: ComponentGuard, + redirectUrl: RedirectUrl, + warning?: string, +): (props: P) => null | JSX.Element { + const displayName = Component.displayName || Component.name || 'Component'; + Component.displayName = displayName; + + const HOC = (props: P) => { + const { navigate } = useRouter(); + const clerk = useClerk(); + const environment = useEnvironment(); + const options = useOptions(); + + const shouldRedirect = condition(clerk, environment, options); + React.useEffect(() => { + if (shouldRedirect) { + if (warning && isDevelopmentFromPublishableKey(clerk.publishableKey)) { + console.info(warning); + } + // TODO: Fix this properly + // eslint-disable-next-line @typescript-eslint/no-floating-promises + navigate(redirectUrl({ clerk, environment, options })); + } + }, []); + + if (shouldRedirect) { + return null; + } + + return ; + }; + + HOC.displayName = `withRedirect(${displayName})`; + + return HOC; +} + +export const withRedirectToAfterSignIn =

(Component: ComponentType

) => { + const displayName = Component.displayName || Component.name || 'Component'; + Component.displayName = displayName; + + const HOC = (props: P) => { + const signInCtx = useSignInContext(); + return withRedirect( + Component, + sessionExistsAndSingleSessionModeEnabled, + ({ clerk }) => signInCtx.afterSignInUrl || clerk.buildAfterSignInUrl(), + warnings.cannotRenderSignInComponentWhenSessionExists, + )(props); + }; + + HOC.displayName = `withRedirectToAfterSignIn(${displayName})`; + + return HOC; +}; + +export const withRedirectToAfterSignUp =

(Component: ComponentType

) => { + const displayName = Component.displayName || Component.name || 'Component'; + Component.displayName = displayName; + + const HOC = (props: P) => { + const signUpCtx = useSignUpContext(); + return withRedirect( + Component, + sessionExistsAndSingleSessionModeEnabled, + ({ clerk }) => signUpCtx.afterSignUpUrl || clerk.buildAfterSignUpUrl(), + warnings.cannotRenderSignUpComponentWhenSessionExists, + )(props); + }; + + HOC.displayName = `withRedirectToAfterSignUp(${displayName})`; + + return HOC; +}; + +export const withRedirectToHomeSingleSessionGuard =

(Component: ComponentType

) => + withRedirect( + Component, + sessionExistsAndSingleSessionModeEnabled, + ({ environment }) => environment.displayConfig.homeUrl, + warnings.cannotRenderComponentWhenSessionExists, + ); diff --git a/packages/clerk-js/src/ui/common/withRedirectToHome.tsx b/packages/clerk-js/src/ui/common/withRedirectToHome.tsx deleted file mode 100644 index 9309dfe231d..00000000000 --- a/packages/clerk-js/src/ui/common/withRedirectToHome.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { useClerk } from '@clerk/shared/react'; -import type { ComponentType } from 'react'; -import React from 'react'; - -import { warnings } from '../../core/warnings'; -import type { ComponentGuard } from '../../utils'; -import { noOrganizationExists, noUserExists, sessionExistsAndSingleSessionModeEnabled } from '../../utils'; -import { useEnvironment, useOptions } from '../contexts'; -import { useRouter } from '../router'; -import type { AvailableComponentProps } from '../types'; - -function withRedirectToHome

( - Component: ComponentType

, - condition: ComponentGuard, - warning?: string, -): (props: P) => null | JSX.Element { - const displayName = Component.displayName || Component.name || 'Component'; - Component.displayName = displayName; - - const HOC = (props: P) => { - const { navigate } = useRouter(); - const clerk = useClerk(); - const environment = useEnvironment(); - const options = useOptions(); - - const shouldRedirect = condition(clerk, environment, options); - React.useEffect(() => { - if (shouldRedirect) { - if (warning && environment.displayConfig.instanceEnvironmentType === 'development') { - console.info(warning); - } - // TODO: Fix this properly - // eslint-disable-next-line @typescript-eslint/no-floating-promises - navigate(environment.displayConfig.homeUrl); - } - }, []); - - if (shouldRedirect) { - return null; - } - - return ; - }; - - HOC.displayName = `withRedirectToHome(${displayName})`; - - return HOC; -} - -export const withRedirectToHomeSingleSessionGuard =

(Component: ComponentType

) => - withRedirectToHome( - Component, - sessionExistsAndSingleSessionModeEnabled, - warnings.cannotRenderComponentWhenSessionExists, - ); - -export const withRedirectToHomeUserGuard =

(Component: ComponentType

) => - withRedirectToHome(Component, noUserExists, warnings.cannotRenderComponentWhenUserDoesNotExist); - -export const withRedirectToHomeOrganizationGuard =

(Component: ComponentType

) => - withRedirectToHome(Component, noOrganizationExists, warnings.cannotRenderComponentWhenOrgDoesNotExist); diff --git a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx index 8560fd9c38e..439ada76a6e 100644 --- a/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationProfile/OrganizationProfile.tsx @@ -2,7 +2,7 @@ import { useOrganization } from '@clerk/shared/react'; import type { OrganizationProfileModalProps, OrganizationProfileProps } from '@clerk/types'; import React from 'react'; -import { withOrganizationsEnabledGuard, withRedirectToHomeOrganizationGuard } from '../../common'; +import { withOrganizationsEnabledGuard } from '../../common'; import { ComponentContext, withCoreUserGuard } from '../../contexts'; import { Flow } from '../../customizables'; import { ProfileCard, withCardStateProvider } from '../../elements'; @@ -44,10 +44,12 @@ const AuthenticatedRoutes = withCoreUserGuard(() => { ); }); -export const OrganizationProfile = withRedirectToHomeOrganizationGuard( - withOrganizationsEnabledGuard(withCardStateProvider(_OrganizationProfile), 'OrganizationProfile', { +export const OrganizationProfile = withOrganizationsEnabledGuard( + withCardStateProvider(_OrganizationProfile), + 'OrganizationProfile', + { mode: 'redirect', - }), + }, ); export const OrganizationProfileModal = (props: OrganizationProfileModalProps): JSX.Element => { diff --git a/packages/clerk-js/src/ui/components/SignIn/ResetPassword.tsx b/packages/clerk-js/src/ui/components/SignIn/ResetPassword.tsx index f189a36b7f5..2b7af25d843 100644 --- a/packages/clerk-js/src/ui/components/SignIn/ResetPassword.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/ResetPassword.tsx @@ -1,7 +1,6 @@ import React from 'react'; import { clerkInvalidFAPIResponse } from '../../../core/errors'; -import { withRedirectToHomeSingleSessionGuard } from '../../common'; import { useCoreSignIn, useEnvironment } from '../../contexts'; import { Col, descriptors, localizationKeys, useLocalizations } from '../../customizables'; import { Card, CardAlert, Form, Header, useCardState, withCardStateProvider } from '../../elements'; @@ -148,4 +147,4 @@ export const _ResetPassword = () => { ); }; -export const ResetPassword = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_ResetPassword)); +export const ResetPassword = withCardStateProvider(_ResetPassword); diff --git a/packages/clerk-js/src/ui/components/SignIn/ResetPasswordSuccess.tsx b/packages/clerk-js/src/ui/components/SignIn/ResetPasswordSuccess.tsx index 97298a62720..5032954f3c3 100644 --- a/packages/clerk-js/src/ui/components/SignIn/ResetPasswordSuccess.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/ResetPasswordSuccess.tsx @@ -1,4 +1,3 @@ -import { withRedirectToHomeSingleSessionGuard } from '../../common'; import { Col, descriptors, localizationKeys, Spinner, Text } from '../../customizables'; import { Card, CardAlert, Header, useCardState, withCardStateProvider } from '../../elements'; import { useSetSessionWithTimeout } from '../../hooks/useSetSessionWithTimeout'; @@ -33,4 +32,4 @@ export const _ResetPasswordSuccess = () => { ); }; -export const ResetPasswordSuccess = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_ResetPasswordSuccess)); +export const ResetPasswordSuccess = withCardStateProvider(_ResetPasswordSuccess); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInAccountSwitcher.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInAccountSwitcher.tsx index 6f716e37087..5d1ccc4fb3e 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInAccountSwitcher.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInAccountSwitcher.tsx @@ -1,4 +1,4 @@ -import { withRedirectToHomeSingleSessionGuard } from '../../common'; +import { withRedirectToAfterSignIn } from '../../common'; import { useEnvironment, useSignInContext } from '../../contexts'; import { Col, descriptors, Flow, Icon } from '../../customizables'; import { Card, CardAlert, Header, PreviewButton, UserPreview, withCardStateProvider } from '../../elements'; @@ -77,6 +77,4 @@ const _SignInAccountSwitcher = () => { ); }; -export const SignInAccountSwitcher = withRedirectToHomeSingleSessionGuard( - withCardStateProvider(_SignInAccountSwitcher), -); +export const SignInAccountSwitcher = withRedirectToAfterSignIn(withCardStateProvider(_SignInAccountSwitcher)); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx index 804fe73186c..2169bbd101b 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorOne.tsx @@ -1,7 +1,7 @@ import type { ResetPasswordCodeFactor, SignInFactor } from '@clerk/types'; import React from 'react'; -import { withRedirectToHomeSingleSessionGuard } from '../../common'; +import { withRedirectToAfterSignIn } from '../../common'; import { useCoreSignIn, useEnvironment } from '../../contexts'; import { ErrorCard, LoadingCard, withCardStateProvider } from '../../elements'; import { useAlternativeStrategies } from '../../hooks/useAlternativeStrategies'; @@ -197,4 +197,4 @@ export function _SignInFactorOne(): JSX.Element { } } -export const SignInFactorOne = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInFactorOne)); +export const SignInFactorOne = withRedirectToAfterSignIn(withCardStateProvider(_SignInFactorOne)); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx index 59294503010..460589cf1e9 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInFactorTwo.tsx @@ -1,7 +1,7 @@ import type { SignInFactor } from '@clerk/types'; import React from 'react'; -import { withRedirectToHomeSingleSessionGuard } from '../../common'; +import { withRedirectToAfterSignIn } from '../../common'; import { useCoreSignIn } from '../../contexts'; import { LoadingCard, withCardStateProvider } from '../../elements'; import { SignInFactorTwoAlternativeMethods } from './SignInFactorTwoAlternativeMethods'; @@ -81,4 +81,4 @@ export function _SignInFactorTwo(): JSX.Element { } } -export const SignInFactorTwo = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInFactorTwo)); +export const SignInFactorTwo = withRedirectToAfterSignIn(withCardStateProvider(_SignInFactorTwo)); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInSSOCallback.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInSSOCallback.tsx index c1bad1c3664..07bfaed52d2 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInSSOCallback.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInSSOCallback.tsx @@ -1,3 +1,3 @@ -import { SSOCallback, withRedirectToHomeSingleSessionGuard } from '../../common'; +import { SSOCallback, withRedirectToAfterSignIn } from '../../common'; -export const SignInSSOCallback = withRedirectToHomeSingleSessionGuard(SSOCallback); +export const SignInSSOCallback = withRedirectToAfterSignIn(SSOCallback); diff --git a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx index 18271f82330..0d9f9f4a915 100644 --- a/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx +++ b/packages/clerk-js/src/ui/components/SignIn/SignInStart.tsx @@ -6,11 +6,7 @@ import { ERROR_CODES } from '../../../core/constants'; import { clerkInvalidFAPIResponse } from '../../../core/errors'; import { getClerkQueryParam } from '../../../utils'; import type { SignInStartIdentifier } from '../../common'; -import { - getIdentifierControlDisplayValues, - groupIdentifiers, - withRedirectToHomeSingleSessionGuard, -} from '../../common'; +import { getIdentifierControlDisplayValues, groupIdentifiers, withRedirectToAfterSignIn } from '../../common'; import { buildSSOCallbackURL } from '../../common/redirects'; import { useCoreSignIn, useEnvironment, useSignInContext } from '../../contexts'; import { Col, descriptors, Flow, localizationKeys } from '../../customizables'; @@ -390,4 +386,4 @@ const InstantPasswordRow = ({ field }: { field?: FormControlState<'password'> }) ); }; -export const SignInStart = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignInStart)); +export const SignInStart = withRedirectToAfterSignIn(withCardStateProvider(_SignInStart)); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpSSOCallback.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpSSOCallback.tsx index a17cff10c54..a72a27627fa 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpSSOCallback.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpSSOCallback.tsx @@ -1,3 +1,3 @@ -import { SSOCallback, withRedirectToHomeSingleSessionGuard } from '../../common'; +import { SSOCallback, withRedirectToAfterSignUp } from '../../common'; -export const SignUpSSOCallback = withRedirectToHomeSingleSessionGuard(SSOCallback); +export const SignUpSSOCallback = withRedirectToAfterSignUp(SSOCallback); diff --git a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx index 79fafd6967c..a1e1b4e6d0d 100644 --- a/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx +++ b/packages/clerk-js/src/ui/components/SignUp/SignUpStart.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { ERROR_CODES } from '../../../core/constants'; import { getClerkQueryParam } from '../../../utils/getClerkQueryParam'; -import { buildSSOCallbackURL, withRedirectToHomeSingleSessionGuard } from '../../common'; +import { buildSSOCallbackURL, withRedirectToAfterSignUp } from '../../common'; import { useCoreSignUp, useEnvironment, useSignUpContext } from '../../contexts'; import { descriptors, Flex, Flow, localizationKeys, useAppearance, useLocalizations } from '../../customizables'; import { @@ -289,4 +289,4 @@ function _SignUpStart(): JSX.Element { ); } -export const SignUpStart = withRedirectToHomeSingleSessionGuard(withCardStateProvider(_SignUpStart)); +export const SignUpStart = withRedirectToAfterSignUp(withCardStateProvider(_SignUpStart)); diff --git a/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx b/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx index 8a586db1fbf..9cbf9c1eebf 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/UserProfile.tsx @@ -1,7 +1,6 @@ import type { UserProfileModalProps, UserProfileProps } from '@clerk/types'; import React from 'react'; -import { withRedirectToHomeUserGuard } from '../../common'; import { ComponentContext, withCoreUserGuard } from '../../contexts'; import { Flow } from '../../customizables'; import { ProfileCard, withCardStateProvider } from '../../elements'; @@ -51,7 +50,7 @@ const AuthenticatedRoutes = withCoreUserGuard(() => { ); }); -export const UserProfile = withRedirectToHomeUserGuard(withCardStateProvider(_UserProfile)); +export const UserProfile = withCardStateProvider(_UserProfile); export const UserProfileModal = (props: UserProfileModalProps): JSX.Element => { const userProfileProps: UserProfileCtx = { diff --git a/packages/react/src/isomorphicClerk.ts b/packages/react/src/isomorphicClerk.ts index fa3e04a6b47..98c2a29cb5c 100644 --- a/packages/react/src/isomorphicClerk.ts +++ b/packages/react/src/isomorphicClerk.ts @@ -73,7 +73,8 @@ type IsomorphicLoadedClerk = Without< | 'buildUserProfileUrl' | 'buildCreateOrganizationUrl' | 'buildOrganizationProfileUrl' - | 'buildHomeUrl' + | 'buildAfterSignUpUrl' + | 'buildAfterSignInUrl' | 'buildUrlWithAuth' | 'handleRedirectCallback' | 'handleUnauthenticated' @@ -111,10 +112,11 @@ type IsomorphicLoadedClerk = Without< // TODO: Align return type buildOrganizationProfileUrl: () => string | void; // TODO: Align return type - buildHomeUrl: () => string | void; - // TODO: Align return type buildUrlWithAuth: (to: string, opts?: BuildUrlWithAuthParams | undefined) => string | void; - + // TODO: Align return type + buildAfterSignInUrl: () => string | void; + // TODO: Align return type + buildAfterSignUpUrl: () => string | void; // TODO: Align optional props mountUserButton: (node: HTMLDivElement, props: UserButtonProps) => void; mountOrganizationList: (node: HTMLDivElement, props: OrganizationListProps) => void; @@ -260,6 +262,24 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; + buildAfterSignInUrl = (): string | void => { + const callback = () => this.clerkjs?.buildAfterSignInUrl() || ''; + if (this.clerkjs && this.#loaded) { + return callback(); + } else { + this.premountMethodCalls.set('buildAfterSignInUrl', callback); + } + }; + + buildAfterSignUpUrl = (): string | void => { + const callback = () => this.clerkjs?.buildAfterSignUpUrl() || ''; + if (this.clerkjs && this.#loaded) { + return callback(); + } else { + this.premountMethodCalls.set('buildAfterSignUpUrl', callback); + } + }; + buildUserProfileUrl = (): string | void => { const callback = () => this.clerkjs?.buildUserProfileUrl() || ''; if (this.clerkjs && this.#loaded) { @@ -287,15 +307,6 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - buildHomeUrl = (): string | void => { - const callback = () => this.clerkjs?.buildHomeUrl() || ''; - if (this.clerkjs && this.#loaded) { - return callback(); - } else { - this.premountMethodCalls.set('buildHomeUrl', callback); - } - }; - buildUrlWithAuth = (to: string, opts?: BuildUrlWithAuthParams | undefined): string | void => { const callback = () => this.clerkjs?.buildUrlWithAuth(to, opts) || ''; if (this.clerkjs && this.#loaded) { @@ -808,13 +819,21 @@ export class IsomorphicClerk implements IsomorphicLoadedClerk { } }; - redirectToHome = async (): Promise => { - const callback = () => this.clerkjs?.redirectToHome(); + redirectToAfterSignUp = (): void => { + const callback = () => this.clerkjs?.redirectToAfterSignUp(); if (this.clerkjs && this.#loaded) { return callback(); } else { - this.premountMethodCalls.set('redirectToHome', callback); - return; + this.premountMethodCalls.set('redirectToAfterSignUp', callback); + } + }; + + redirectToAfterSignIn = (): void => { + const callback = () => this.clerkjs?.redirectToAfterSignIn(); + if (this.clerkjs && this.#loaded) { + callback(); + } else { + this.premountMethodCalls.set('redirectToAfterSignIn', callback); } }; diff --git a/packages/shared/src/__tests__/keys.test.ts b/packages/shared/src/__tests__/keys.test.ts index dcc3226c259..fe34268614e 100644 --- a/packages/shared/src/__tests__/keys.test.ts +++ b/packages/shared/src/__tests__/keys.test.ts @@ -1,7 +1,9 @@ import { buildPublishableKey, createDevOrStagingUrlCache, + isDevelopmentFromPublishableKey, isDevelopmentFromSecretKey, + isProductionFromPublishableKey, isProductionFromSecretKey, isPublishableKey, parsePublishableKey, @@ -105,6 +107,34 @@ describe('isDevOrStagingUrl(url)', () => { }); }); +describe('isDevelopmentFromPublishableKey(key)', () => { + const cases: Array<[string, boolean]> = [ + ['pk_live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk', false], + ['pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk', true], + ['live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk', false], + ['test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk', true], + ]; + + test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { + const result = isDevelopmentFromPublishableKey(publishableKeyStr); + expect(result).toEqual(expected); + }); +}); + +describe('isProductionFromPublishableKey(key)', () => { + const cases: Array<[string, boolean]> = [ + ['pk_live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk', true], + ['pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk', false], + ['live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk', true], + ['test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk', false], + ]; + + test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { + const result = isProductionFromPublishableKey(publishableKeyStr); + expect(result).toEqual(expected); + }); +}); + describe('isDevelopmentFromSecretKey(key)', () => { const cases: Array<[string, boolean]> = [ ['sk_live_Y2xlcmsuY2xlcmsuZGV2JA==', false], @@ -113,8 +143,8 @@ describe('isDevelopmentFromSecretKey(key)', () => { ['test_Y2xlcmsuY2xlcmsuZGV2JA==', true], ]; - test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { - const result = isDevelopmentFromSecretKey(publishableKeyStr); + test.each(cases)('given %p as a secret key string, returns %p', (secretKeyStr, expected) => { + const result = isDevelopmentFromSecretKey(secretKeyStr); expect(result).toEqual(expected); }); }); @@ -127,8 +157,8 @@ describe('isProductionFromSecretKey(key)', () => { ['test_Y2xlcmsuY2xlcmsuZGV2JA==', false], ]; - test.each(cases)('given %p as a publishable key string, returns %p', (publishableKeyStr, expected) => { - const result = isProductionFromSecretKey(publishableKeyStr); + test.each(cases)('given %p as a secret key string, returns %p', (secretKeyStr, expected) => { + const result = isProductionFromSecretKey(secretKeyStr); expect(result).toEqual(expected); }); }); diff --git a/packages/shared/src/keys.ts b/packages/shared/src/keys.ts index 238b512132b..876a696d37f 100644 --- a/packages/shared/src/keys.ts +++ b/packages/shared/src/keys.ts @@ -93,6 +93,14 @@ export function createDevOrStagingUrlCache() { }; } +export function isDevelopmentFromPublishableKey(apiKey: string): boolean { + return apiKey.startsWith('test_') || apiKey.startsWith('pk_test_'); +} + +export function isProductionFromPublishableKey(apiKey: string): boolean { + return apiKey.startsWith('live_') || apiKey.startsWith('pk_live_'); +} + export function isDevelopmentFromSecretKey(apiKey: string): boolean { return apiKey.startsWith('test_') || apiKey.startsWith('sk_test_'); } diff --git a/packages/types/src/clerk.ts b/packages/types/src/clerk.ts index d18b3d26f58..962dcbdafc3 100644 --- a/packages/types/src/clerk.ts +++ b/packages/types/src/clerk.ts @@ -342,9 +342,14 @@ export interface Clerk { buildOrganizationProfileUrl(): string; /** - * Returns the configured home URL of the instance. + * Returns the configured afterSignIn url of the instance. */ - buildHomeUrl(): string; + buildAfterSignInUrl(): string; + + /** + * Returns the configured afterSignIn url of the instance. + */ + buildAfterSignUpUrl(): string; /** * @@ -384,9 +389,14 @@ export interface Clerk { redirectToCreateOrganization: () => Promise; /** - * Redirects to the configured home URL of the instance. + * Redirects to the configured afterSignIn URL. + */ + redirectToAfterSignIn: () => void; + + /** + * Redirects to the configured afterSignUp URL. */ - redirectToHome: () => Promise; + redirectToAfterSignUp: () => void; /** * Completes an OAuth or SAML redirection flow started by