Skip to content

Commit c61855c

Browse files
authored
chore(clerk-js,types): Add taskUrls as option (#6373)
1 parent a04a8f5 commit c61855c

File tree

11 files changed

+72
-21
lines changed

11 files changed

+72
-21
lines changed

.changeset/whole-knives-attend.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@clerk/clerk-js': patch
3+
'@clerk/types': patch
4+
---
5+
6+
Add `taskUrls` option to customize task flow URLs:
7+
8+
```tsx
9+
<ClerkProvider
10+
taskUrls={{
11+
'org': '/my-custom-org-selector'
12+
}}
13+
/>
14+
```

.typedoc/__tests__/__snapshots__/file-structure.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ exports[`Typedoc output > should have a deliberate file structure 1`] = `
106106
"types/server-get-token.mdx",
107107
"types/session-resource.mdx",
108108
"types/session-status-claim.mdx",
109+
"types/session-task.mdx",
109110
"types/session-verification-level.mdx",
110111
"types/session-verification-types.mdx",
111112
"types/set-active-params.mdx",

packages/clerk-js/src/core/__tests__/clerk.test.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2331,7 +2331,7 @@ describe('Clerk singleton', () => {
23312331
});
23322332
});
23332333

2334-
describe('nextTask', () => {
2334+
describe('navigateToTask', () => {
23352335
describe('with `pending` session status', () => {
23362336
const mockSession = {
23372337
id: '1',
@@ -2350,7 +2350,7 @@ describe('Clerk singleton', () => {
23502350
reload: jest.fn(() => Promise.resolve(mockSession)),
23512351
};
23522352

2353-
beforeAll(() => {
2353+
beforeEach(() => {
23542354
mockResource.touch.mockReturnValueOnce(Promise.resolve());
23552355
mockClientFetch.mockReturnValue(Promise.resolve({ signedInSessions: [mockResource] }));
23562356
});
@@ -2360,7 +2360,7 @@ describe('Clerk singleton', () => {
23602360
mockResource.touch.mockReset();
23612361
});
23622362

2363-
it('navigates to next task', async () => {
2363+
it('navigates to next task with default internal routing for AIOs', async () => {
23642364
const sut = new Clerk(productionPublishableKey);
23652365
await sut.load(mockedLoadOptions);
23662366

@@ -2369,6 +2369,21 @@ describe('Clerk singleton', () => {
23692369

23702370
expect(mockNavigate.mock.calls[0][0]).toBe('/sign-in#/tasks/add-organization');
23712371
});
2372+
2373+
it('navigates to next task with custom routing from clerk options', async () => {
2374+
const sut = new Clerk(productionPublishableKey);
2375+
await sut.load({
2376+
...mockedLoadOptions,
2377+
taskUrls: {
2378+
org: '/onboarding/select-organization',
2379+
},
2380+
});
2381+
2382+
await sut.setActive({ session: mockResource as any as PendingSessionResource });
2383+
await sut.__internal_navigateToTaskIfAvailable();
2384+
2385+
expect(mockNavigate.mock.calls[0][0]).toBe('/onboarding/select-organization');
2386+
});
23722387
});
23732388

23742389
describe('with `active` session status', () => {

packages/clerk-js/src/core/clerk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1318,7 +1318,7 @@ export class Clerk implements ClerkInterface {
13181318
eventBus.emit(events.TokenUpdate, { token: null });
13191319
}
13201320

1321-
// Only triggers navigation for internal AIO components routing
1321+
// Only triggers navigation for internal AIO components routing or custom URLs
13221322
const shouldNavigateOnSetActive = this.#componentNavigationContext;
13231323
if (newSession?.currentTask && shouldNavigateOnSetActive) {
13241324
await navigateToTask(session.currentTask.key, {

packages/clerk-js/src/core/sessionTasks.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type {
77

88
import { buildURL } from '../utils';
99

10-
export const SESSION_TASK_ROUTE_BY_KEY: Record<SessionTask['key'], string> = {
10+
export const INTERNAL_SESSION_TASK_ROUTE_BY_KEY: Record<SessionTask['key'], string> = {
1111
org: 'add-organization',
1212
} as const;
1313

@@ -24,10 +24,10 @@ interface NavigateToTaskOptions {
2424
* @internal
2525
*/
2626
export function navigateToTask(
27-
routeKey: keyof typeof SESSION_TASK_ROUTE_BY_KEY,
27+
routeKey: keyof typeof INTERNAL_SESSION_TASK_ROUTE_BY_KEY,
2828
{ componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
2929
) {
30-
const taskRoute = `/tasks/${SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
30+
const taskRoute = `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
3131

3232
if (componentNavigationContext) {
3333
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
@@ -38,13 +38,12 @@ export function navigateToTask(
3838
const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
3939

4040
const sessionTaskUrl = buildURL(
41-
// TODO - Accept custom URL option for custom flows in order to eject out of `signInUrl/signUpUrl`
4241
{
4342
base: isReferrerSignUpUrl ? signUpUrl : signInUrl,
4443
hashPath: taskRoute,
4544
},
4645
{ stringify: true },
4746
);
4847

49-
return globalNavigate(sessionTaskUrl);
48+
return globalNavigate(options.taskUrls?.[routeKey] ?? sessionTaskUrl);
5049
}

packages/clerk-js/src/ui/common/redirects.ts

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { SessionTask } from '@clerk/types';
1+
import type { ClerkOptions, SessionTask } from '@clerk/types';
22

3-
import { SESSION_TASK_ROUTE_BY_KEY } from '../../core/sessionTasks';
3+
import { INTERNAL_SESSION_TASK_ROUTE_BY_KEY } from '../../core/sessionTasks';
44
import { buildURL } from '../../utils/url';
55
import type { SignInContextType, SignUpContextType, UserProfileContextType } from './../contexts';
66

@@ -33,21 +33,26 @@ export function buildSessionTaskRedirectUrl({
3333
path,
3434
baseUrl,
3535
task,
36+
taskUrls,
3637
}: Pick<SignInContextType | SignUpContextType, 'routing' | 'path'> & {
3738
baseUrl: string;
3839
task?: SessionTask;
40+
taskUrls?: ClerkOptions['taskUrls'];
3941
}) {
4042
if (!task) {
4143
return null;
4244
}
4345

44-
return buildRedirectUrl({
45-
routing,
46-
baseUrl,
47-
path,
48-
endpoint: `/tasks/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
49-
authQueryString: null,
50-
});
46+
return (
47+
taskUrls?.[task.key] ??
48+
buildRedirectUrl({
49+
routing,
50+
baseUrl,
51+
path,
52+
endpoint: `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
53+
authQueryString: null,
54+
})
55+
);
5156
}
5257

5358
export function buildSSOCallbackURL(

packages/clerk-js/src/ui/components/SessionTasks/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { Card } from '@/ui/elements/Card';
66
import { withCardStateProvider } from '@/ui/elements/contexts';
77
import { LoadingCardContainer } from '@/ui/elements/LoadingCard';
88

9-
import { SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
9+
import { INTERNAL_SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
1010
import { SignInContext, SignUpContext } from '../../../ui/contexts';
1111
import { SessionTasksContext, useSessionTasksContext } from '../../contexts/components/SessionTasks';
1212
import { Route, Switch, useRouter } from '../../router';
@@ -38,7 +38,7 @@ const SessionTasksStart = () => {
3838
function SessionTaskRoutes(): JSX.Element {
3939
return (
4040
<Switch>
41-
<Route path={SESSION_TASK_ROUTE_BY_KEY['org']}>
41+
<Route path={INTERNAL_SESSION_TASK_ROUTE_BY_KEY['org']}>
4242
<ForceOrganizationSelectionTask />
4343
</Route>
4444
<Route index>

packages/clerk-js/src/ui/contexts/components/SignIn.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export const useSignInContext = (): SignInContextType => {
126126
path: ctx.path,
127127
routing: ctx.routing,
128128
baseUrl: signInUrl,
129+
taskUrls: clerk.__internal_getOption('taskUrls'),
129130
});
130131

131132
return {

packages/clerk-js/src/ui/contexts/components/SignUp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export const useSignUpContext = (): SignUpContextType => {
121121
path: ctx.path,
122122
routing: ctx.routing,
123123
baseUrl: signUpUrl,
124+
taskUrls: clerk.__internal_getOption('taskUrls'),
124125
});
125126

126127
return {

packages/types/src/clerk.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import type {
4848
SignUpFallbackRedirectUrl,
4949
SignUpForceRedirectUrl,
5050
} from './redirects';
51-
import type { PendingSessionOptions, SignedInSessionResource } from './session';
51+
import type { PendingSessionOptions, SessionTask, SignedInSessionResource } from './session';
5252
import type { SessionVerificationLevel } from './sessionVerification';
5353
import type { SignInResource } from './signIn';
5454
import type { SignUpResource } from './signUp';
@@ -1050,6 +1050,14 @@ export type ClerkOptions = PendingSessionOptions &
10501050
* @internal
10511051
*/
10521052
__internal_keyless_dismissPrompt?: (() => Promise<void>) | null;
1053+
1054+
/**
1055+
* Customize the URL paths users are redirected to after sign-in or sign-up when specific
1056+
* session tasks need to be completed.
1057+
*
1058+
* @default undefined - Uses Clerk's default task flow URLs
1059+
*/
1060+
taskUrls?: Record<SessionTask['key'], string>;
10531061
};
10541062

10551063
export interface NavigateOptions {

0 commit comments

Comments
 (0)