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,