Skip to content

Commit f95ba43

Browse files
committed
Add tasksUrl as option
1 parent 9a01afa commit f95ba43

File tree

10 files changed

+63
-14
lines changed

10 files changed

+63
-14
lines changed

.changeset/whole-knives-attend.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
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+
```
15+
16+
**Breaking**: Task key renamed from `'select-organization'` to `'org'`.

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

Lines changed: 17 additions & 2 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',
@@ -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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import type {
3030
AuthenticateWithGoogleOneTapParams,
3131
AuthenticateWithMetamaskParams,
3232
AuthenticateWithOKXWalletParams,
33-
Clerk as ClerkInterface,
3433
ClerkAPIError,
3534
ClerkAuthenticateWithWeb3Params,
35+
Clerk as ClerkInterface,
3636
ClerkOptions,
3737
ClientJSONSnapshot,
3838
ClientResource,
@@ -1317,7 +1317,7 @@ export class Clerk implements ClerkInterface {
13171317
eventBus.emit(events.TokenUpdate, { token: null });
13181318
}
13191319

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

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

Lines changed: 3 additions & 4 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 = options.taskUrls?.[routeKey] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
3131

3232
if (componentNavigationContext) {
3333
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
@@ -38,7 +38,6 @@ 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,

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

Lines changed: 5 additions & 3 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,9 +33,11 @@ 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;
@@ -45,7 +47,7 @@ export function buildSessionTaskRedirectUrl({
4547
routing,
4648
baseUrl,
4749
path,
48-
endpoint: `/tasks/${SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
50+
endpoint: taskUrls?.[task.key] ?? `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[task.key]}`,
4951
authQueryString: null,
5052
});
5153
}

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 {

packages/types/src/session.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,14 @@ export interface PublicUserData {
326326
userId?: string;
327327
}
328328

329+
/**
330+
* Represents a required action that a user must complete
331+
* before their session becomes fully active
332+
*/
329333
export interface SessionTask {
334+
/**
335+
* The unique identifier for the type of task that needs to be completed
336+
*/
330337
key: 'org';
331338
}
332339

0 commit comments

Comments
 (0)