diff --git a/.changeset/strange-owls-behave.md b/.changeset/strange-owls-behave.md new file mode 100644 index 00000000000..c30fea5ef93 --- /dev/null +++ b/.changeset/strange-owls-behave.md @@ -0,0 +1,19 @@ +--- +'@clerk/clerk-js': patch +'@clerk/types': patch +--- + +Construct urls based on context in +- Deprecate `afterSwitchOrganizationUrl` +- Introduce `afterSelectOrganizationUrl` & `afterSelectPersonalUrl` + +`afterSelectOrganizationUrl` accepts +- Full URL -> 'https://clerk.com/' +- relative path -> '/organizations' +- relative path -> with param '/organizations/:id' +- function that returns a string -> (org) => `/org/${org.slug}` +`afterSelectPersonalUrl` accepts +- Full URL -> 'https://clerk.com/' +- relative path -> '/users' +- relative path -> with param '/users/:username' +- function that returns a string -> (user) => `/users/${user.id}` diff --git a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx index 51820cf05eb..e665f348097 100644 --- a/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx +++ b/packages/clerk-js/src/ui/components/OrganizationSwitcher/OrganizationSwitcherPopover.tsx @@ -60,12 +60,24 @@ export const OrganizationSwitcherPopover = React.forwardRef { - return card.runAsync(() => setActive({ organization, beforeEmit: navigateAfterSwitchOrganization })).then(close); + return card + .runAsync(() => + setActive({ + organization, + beforeEmit: () => + navigateAfterSwitchOrganization({ + organization: organization, + }), + }), + ) + .then(close); }; const handlePersonalWorkspaceClicked = () => { return card - .runAsync(() => setActive({ organization: null, beforeEmit: navigateAfterSwitchOrganization })) + .runAsync(() => + setActive({ organization: null, beforeEmit: () => navigateAfterSwitchOrganization({ user: user }) }), + ) .then(close); }; diff --git a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx index 6a24f195cf7..ca532b5b256 100644 --- a/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx +++ b/packages/clerk-js/src/ui/contexts/ClerkUIComponentsContext.tsx @@ -1,4 +1,4 @@ -import type { OrganizationResource } from '@clerk/types'; +import type { OrganizationResource, UserResource } from '@clerk/types'; import React from 'react'; import { buildAuthQueryString, buildURL, pickRedirectionProp } from '../../utils'; @@ -16,6 +16,23 @@ import type { UserProfileCtx, } from '../types'; +const createDynamicParamParser = + ({ regex }: { regex: RegExp }) => + >({ urlWithParam, entity }: { urlWithParam: string; entity: T }) => { + const match = regex.exec(urlWithParam); + + if (match) { + const key = match[1]; + if (key in entity) { + const value = entity[key] as string; + return urlWithParam.replace(match[0], value); + } + } + return urlWithParam; + }; + +const parse = createDynamicParamParser({ regex: /:(\w+)/ }); + export const ComponentContext = React.createContext(null); export type SignUpContextType = SignUpCtx & { @@ -231,8 +248,45 @@ export const useOrganizationSwitcherContext = () => { const navigateCreateOrganization = () => navigate(ctx.createOrganizationUrl || displayConfig.createOrganizationUrl); const navigateOrganizationProfile = () => navigate(ctx.organizationProfileUrl || displayConfig.organizationProfileUrl); - const navigateAfterSwitchOrganization = () => - ctx.afterSwitchOrganizationUrl ? navigate(ctx.afterSwitchOrganizationUrl) : Promise.resolve(); + + const navigateAfterSwitchOrganization = ({ + organization, + user, + }: { + organization?: OrganizationResource; + user?: UserResource; + }) => { + // Continue to support afterSwitchOrganizationUrl + if (ctx.afterSwitchOrganizationUrl) { + return navigate(ctx.afterSwitchOrganizationUrl); + } + + if (typeof ctx.afterSelectPersonalUrl === 'function' && user) { + return navigate(ctx.afterSelectPersonalUrl(user)); + } + + if (typeof ctx.afterSelectOrganizationUrl === 'function' && organization) { + return navigate(ctx.afterSelectOrganizationUrl(organization)); + } + + if (ctx.afterSelectPersonalUrl && user) { + const parsedUrl = parse({ + urlWithParam: ctx.afterSelectPersonalUrl as string, + entity: user, + }); + return navigate(parsedUrl); + } + + if (ctx.afterSelectOrganizationUrl && organization) { + const parsedUrl = parse({ + urlWithParam: ctx.afterSelectOrganizationUrl as string, + entity: organization, + }); + return navigate(parsedUrl); + } + + return Promise.resolve(); + }; return { ...ctx, @@ -267,21 +321,6 @@ export const useOrganizationProfileContext = () => { }; }; -const createDynamicParamParser = - ({ regex }: { regex: RegExp }) => - >({ urlWithParam, entity }: { urlWithParam: string; entity: T }) => { - const match = regex.exec(urlWithParam); - - if (match) { - const key = match[1]; - if (key in entity) { - const value = entity[key] as string; - return urlWithParam.replace(match[0], value); - } - } - return urlWithParam; - }; - export const useCreateOrganizationContext = () => { const { componentName, ...ctx } = (React.useContext(ComponentContext) || {}) as CreateOrganizationCtx; const { navigate } = useRouter(); @@ -296,8 +335,6 @@ export const useCreateOrganizationContext = () => { return navigate(ctx.afterCreateOrganizationUrl(organization)); } - const parse = createDynamicParamParser({ regex: /:(\w+)/ }); - if (ctx.afterCreateOrganizationUrl) { const parsedUrl = parse({ urlWithParam: ctx.afterCreateOrganizationUrl,