Skip to content

Commit 5139312

Browse files
LauraBeatrisiagodahlem
authored andcommitted
refactor: Use OrganizationList but without revalidating in place
1 parent e2cb4ba commit 5139312

File tree

12 files changed

+57
-145
lines changed

12 files changed

+57
-145
lines changed

.changeset/bright-cats-lay.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
---
22
'@clerk/clerk-js': minor
3-
'@clerk/types': minor
43
---
54

6-
Export `<TaskSelectOrganization />` component.
5+
Introduce `<TaskSelectOrganization />` component.
76

87
It allows you to eject the organization selection task flow from the default `SignIn` and `SignUp` components and render it on custom URL paths using `taskUrls`.
98

.changeset/vast-places-tap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/types': patch
3+
---
4+
5+
Add TypeScript types for `<TaskSelectOrganization />` component.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import type {
3030
AuthenticateWithGoogleOneTapParams,
3131
AuthenticateWithMetamaskParams,
3232
AuthenticateWithOKXWalletParams,
33+
Clerk as ClerkInterface,
3334
ClerkAPIError,
3435
ClerkAuthenticateWithWeb3Params,
35-
Clerk as ClerkInterface,
3636
ClerkOptions,
3737
ClientJSONSnapshot,
3838
ClientResource,

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,23 +27,25 @@ export function navigateToTask(
2727
routeKey: keyof typeof INTERNAL_SESSION_TASK_ROUTE_BY_KEY,
2828
{ componentNavigationContext, globalNavigate, options, environment }: NavigateToTaskOptions,
2929
) {
30-
const taskRoute = `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
30+
const customTaskUrl = options?.taskUrls?.[routeKey];
31+
const internalTaskRoute = `/tasks/${INTERNAL_SESSION_TASK_ROUTE_BY_KEY[routeKey]}`;
3132

32-
if (componentNavigationContext) {
33-
return componentNavigationContext.navigate(componentNavigationContext.indexPath + taskRoute);
33+
if (componentNavigationContext && !customTaskUrl) {
34+
return componentNavigationContext.navigate(componentNavigationContext.indexPath + internalTaskRoute);
3435
}
3536

3637
const signInUrl = options['signInUrl'] || environment.displayConfig.signInUrl;
3738
const signUpUrl = options['signUpUrl'] || environment.displayConfig.signUpUrl;
3839
const isReferrerSignUpUrl = window.location.href.startsWith(signUpUrl);
3940

40-
const sessionTaskUrl = buildURL(
41-
{
42-
base: isReferrerSignUpUrl ? signUpUrl : signInUrl,
43-
hashPath: taskRoute,
44-
},
45-
{ stringify: true },
41+
return globalNavigate(
42+
customTaskUrl ??
43+
buildURL(
44+
{
45+
base: isReferrerSignUpUrl ? signUpUrl : signInUrl,
46+
hashPath: internalTaskRoute,
47+
},
48+
{ stringify: true },
49+
),
4650
);
47-
48-
return globalNavigate(options.taskUrls?.[routeKey] ?? sessionTaskUrl);
4951
}

packages/clerk-js/src/ui/components/CreateOrganization/CreateOrganizationForm.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,15 +90,15 @@ export const CreateOrganizationForm = withCardStateProvider((props: CreateOrgani
9090
lastCreatedOrganizationRef.current = organization;
9191
await setActive({ organization });
9292

93-
void userMemberships.revalidate?.();
94-
9593
if (sessionTasksContext) {
9694
await clerk.__internal_navigateToTaskIfAvailable({
9795
redirectUrlComplete: sessionTasksContext.redirectUrlComplete,
9896
});
9997
return;
10098
}
10199

100+
void userMemberships.revalidate?.();
101+
102102
if (props.skipInvitationScreen ?? organization.maxAllowedMemberships === 1) {
103103
return completeFlow();
104104
}

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import { LoadingCardContainer } from '@/ui/elements/LoadingCard';
88

99
import { INTERNAL_SESSION_TASK_ROUTE_BY_KEY } from '../../../core/sessionTasks';
1010
import { SignInContext, SignUpContext } from '../../../ui/contexts';
11-
import { SessionTasksContext, useSessionTasksContext } from '../../contexts/components/SessionTasks';
11+
import {
12+
SessionTasksContext,
13+
TaskSelectOrganizationContext,
14+
useSessionTasksContext,
15+
} from '../../contexts/components/SessionTasks';
1216
import { Route, Switch, useRouter } from '../../router';
1317
import { TaskSelectOrganization } from './tasks/TaskSelectOrganization';
1418

@@ -36,10 +40,16 @@ const SessionTasksStart = () => {
3640
};
3741

3842
function SessionTaskRoutes(): JSX.Element {
43+
const ctx = useSessionTasksContext();
44+
3945
return (
4046
<Switch>
4147
<Route path={INTERNAL_SESSION_TASK_ROUTE_BY_KEY['select-organization']}>
42-
<TaskSelectOrganization />
48+
<TaskSelectOrganizationContext.Provider
49+
value={{ componentName: 'TaskSelectOrganization', redirectUrlComplete: ctx.redirectUrlComplete }}
50+
>
51+
<TaskSelectOrganization />
52+
</TaskSelectOrganizationContext.Provider>
4353
</Route>
4454
<Route index>
4555
<SessionTasksStart />
Lines changed: 6 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,11 @@
1-
import { useOrganizationList } from '@clerk/shared/react/index';
2-
import type { PropsWithChildren } from 'react';
3-
import { useContext, useEffect, useRef, useState } from 'react';
4-
51
import { OrganizationListContext } from '@/ui/contexts';
6-
import { TaskSelectOrganizationContext, useSessionTasksContext } from '@/ui/contexts/components/SessionTasks';
7-
import { Card } from '@/ui/elements/Card';
8-
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
2+
import { useTaskSelectOrganizationContext } from '@/ui/contexts/components/SessionTasks';
3+
import { withCardStateProvider } from '@/ui/elements/contexts';
94

10-
import { Box, descriptors, Flex, Flow, localizationKeys, Spinner } from '../../../customizables';
11-
import { CreateOrganizationForm } from '../../CreateOrganization/CreateOrganizationForm';
12-
import { OrganizationListPageList } from '../../OrganizationList/OrganizationListPage';
13-
import { organizationListParams } from '../../OrganizationSwitcher/utils';
5+
import { OrganizationList } from '../../OrganizationList';
146

157
export const TaskSelectOrganization = withCardStateProvider(() => {
16-
const { userMemberships, userInvitations, userSuggestions } = useOrganizationList(organizationListParams);
17-
const currentFlow = useRef<'create-organization' | 'organization-selection'>();
18-
19-
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
20-
const hasData = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
21-
22-
if (isLoading) {
23-
return (
24-
<FlowCard>
25-
<FlowLoadingState />
26-
</FlowCard>
27-
);
28-
}
29-
30-
// Only show the organization selection page if organizations exist when the component first mounts.
31-
// This prevents unwanted screen transitions that could occur from data revalidation,
32-
// such as when a user accepts an organization invitation and the membership list updates.
33-
if (hasData || currentFlow.current === 'organization-selection') {
34-
return <OrganizationSelectionPage currentFlow={currentFlow} />;
35-
}
36-
37-
return <CreateOrganizationPage currentFlow={currentFlow} />;
38-
});
39-
40-
type CommonPageProps = {
41-
currentFlow: React.MutableRefObject<'create-organization' | 'organization-selection' | undefined>;
42-
};
43-
44-
const OrganizationSelectionPage = ({ currentFlow }: CommonPageProps) => {
45-
const [showCreateOrganizationForm, setShowCreateOrganizationForm] = useState(false);
46-
const ctx = useContext(TaskSelectOrganizationContext);
47-
48-
useEffect(() => {
49-
currentFlow.current = 'organization-selection';
50-
}, [currentFlow]);
8+
const ctx = useTaskSelectOrganizationContext();
519

5210
return (
5311
<OrganizationListContext.Provider
@@ -57,80 +15,7 @@ const OrganizationSelectionPage = ({ currentFlow }: CommonPageProps) => {
5715
appearance: ctx?.appearance,
5816
}}
5917
>
60-
<FlowCard>
61-
{showCreateOrganizationForm ? (
62-
<Box
63-
sx={t => ({
64-
padding: `${t.space.$none} ${t.space.$5} ${t.space.$5}`,
65-
})}
66-
>
67-
<CreateOrganizationForm
68-
flow='default'
69-
startPage={{ headerTitle: localizationKeys('organizationList.createOrganization') }}
70-
skipInvitationScreen
71-
onCancel={() => setShowCreateOrganizationForm(false)}
72-
/>
73-
</Box>
74-
) : (
75-
<OrganizationListPageList onCreateOrganizationClick={() => setShowCreateOrganizationForm(true)} />
76-
)}
77-
</FlowCard>
18+
<OrganizationList />
7819
</OrganizationListContext.Provider>
7920
);
80-
};
81-
82-
const CreateOrganizationPage = ({ currentFlow }: CommonPageProps) => {
83-
useEffect(() => {
84-
currentFlow.current = 'create-organization';
85-
}, [currentFlow]);
86-
87-
return (
88-
<FlowCard>
89-
<Box
90-
sx={t => ({
91-
padding: `${t.space.$none} ${t.space.$5} ${t.space.$5}`,
92-
})}
93-
>
94-
<CreateOrganizationForm
95-
flow='default'
96-
startPage={{ headerTitle: localizationKeys('organizationList.createOrganization') }}
97-
skipInvitationScreen
98-
/>
99-
</Box>
100-
</FlowCard>
101-
);
102-
};
103-
104-
const FlowCard = ({ children }: PropsWithChildren) => {
105-
const card = useCardState();
106-
const { currentTaskContainer } = useSessionTasksContext();
107-
108-
return (
109-
<Flow.Root flow='taskSelectOrganization'>
110-
<Card.Root ref={currentTaskContainer}>
111-
<Card.Content sx={t => ({ padding: `${t.space.$8} ${t.space.$none} ${t.space.$none}` })}>
112-
<Card.Alert sx={t => ({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error}</Card.Alert>
113-
{children}
114-
</Card.Content>
115-
<Card.Footer />
116-
</Card.Root>
117-
</Flow.Root>
118-
);
119-
};
120-
121-
const FlowLoadingState = () => (
122-
<Flex
123-
direction='row'
124-
center
125-
sx={t => ({
126-
height: '100%',
127-
minHeight: t.sizes.$60,
128-
})}
129-
>
130-
<Spinner
131-
size='xl'
132-
colorScheme='primary'
133-
elementDescriptor={descriptors.spinner}
134-
/>
135-
</Flex>
136-
);
21+
});

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,15 @@ export const useSessionTasksContext = (): SessionTasksCtx => {
1515
};
1616

1717
export const TaskSelectOrganizationContext = createContext<TaskSelectOrganizationCtx | null>(null);
18+
19+
export const useTaskSelectOrganizationContext = (): TaskSelectOrganizationCtx => {
20+
const context = useContext(TaskSelectOrganizationContext);
21+
22+
if (context === null) {
23+
throw new Error(
24+
'Clerk: useTaskSelectOrganizationContext called outside of the mounted TaskSelectOrganization component.',
25+
);
26+
}
27+
28+
return context;
29+
};

packages/react-router/src/__tests__/__snapshots__/exports.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ exports[`root public exports > should not change unexpectedly 1`] = `
2929
"SignUpButton",
3030
"SignedIn",
3131
"SignedOut",
32+
"TaskSelectOrganization",
3233
"UserButton",
3334
"UserProfile",
3435
"Waitlist",
35-
"TaskSelectOrganization",
3636
"__experimental_CheckoutProvider",
3737
"__experimental_PaymentElement",
3838
"__experimental_PaymentElementProvider",

packages/remix/src/__tests__/__snapshots__/exports.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ exports[`root public exports > should not change unexpectedly 1`] = `
3030
"SignUpButton",
3131
"SignedIn",
3232
"SignedOut",
33+
"TaskSelectOrganization",
3334
"UserButton",
3435
"UserProfile",
3536
"Waitlist",
36-
"TaskSelectOrganization",
3737
"WithClerkState",
3838
"__experimental_CheckoutProvider",
3939
"__experimental_PaymentElement",

0 commit comments

Comments
 (0)