Skip to content

Commit 413468c

Browse files
authored
fix(clerk-js): Add error handling for setActive with stale organization data (#6550)
1 parent 6daa584 commit 413468c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+313
-128
lines changed

.changeset/tall-ears-worry.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@clerk/localizations': patch
3+
'@clerk/clerk-js': patch
4+
'@clerk/shared': patch
5+
'@clerk/types': patch
6+
---
7+
8+
Add error handling for `setActive` with stale organization data

packages/clerk-js/src/ui/components/OrganizationList/OrganizationListPage.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ const CreateOrganizationButton = ({
4949
};
5050

5151
export const OrganizationListPage = withCardStateProvider(() => {
52-
const card = useCardState();
5352
const { userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
5453
const isLoading = userMemberships?.isLoading || userInvitations?.isLoading || userSuggestions?.isLoading;
5554
const hasAnyData = !!(userMemberships?.count || userInvitations?.count || userSuggestions?.count);
@@ -59,7 +58,6 @@ export const OrganizationListPage = withCardStateProvider(() => {
5958
return (
6059
<Card.Root>
6160
<Card.Content sx={t => ({ padding: `${t.space.$8} ${t.space.$none} ${t.space.$none}` })}>
62-
<Card.Alert sx={t => ({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error}</Card.Alert>
6361
{isLoading && (
6462
<Flex
6563
direction={'row'}
@@ -86,6 +84,7 @@ export const OrganizationListPage = withCardStateProvider(() => {
8684
});
8785

8886
const OrganizationListFlows = ({ showListInitially }: { showListInitially: boolean }) => {
87+
const card = useCardState();
8988
const { navigateAfterCreateOrganization, skipInvitationScreen, hideSlug } = useOrganizationListContext();
9089
const [isCreateOrganizationFlow, setCreateOrganizationFlow] = useState(!showListInitially);
9190
return (
@@ -95,30 +94,35 @@ const OrganizationListFlows = ({ showListInitially }: { showListInitially: boole
9594
)}
9695

9796
{isCreateOrganizationFlow && (
98-
<Box
99-
sx={t => ({
100-
padding: `${t.space.$none} ${t.space.$5} ${t.space.$5}`,
101-
})}
102-
>
103-
<CreateOrganizationForm
104-
flow='organizationList'
105-
startPage={{ headerTitle: localizationKeys('organizationList.createOrganization') }}
106-
skipInvitationScreen={skipInvitationScreen}
107-
navigateAfterCreateOrganization={org =>
108-
navigateAfterCreateOrganization(org).then(() => setCreateOrganizationFlow(false))
109-
}
110-
onCancel={
111-
showListInitially && isCreateOrganizationFlow ? () => setCreateOrganizationFlow(false) : undefined
112-
}
113-
hideSlug={hideSlug}
114-
/>
115-
</Box>
97+
<>
98+
<Card.Alert sx={t => ({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error}</Card.Alert>
99+
100+
<Box
101+
sx={t => ({
102+
padding: `${t.space.$none} ${t.space.$5} ${t.space.$5}`,
103+
})}
104+
>
105+
<CreateOrganizationForm
106+
flow='organizationList'
107+
startPage={{ headerTitle: localizationKeys('organizationList.createOrganization') }}
108+
skipInvitationScreen={skipInvitationScreen}
109+
navigateAfterCreateOrganization={org =>
110+
navigateAfterCreateOrganization(org).then(() => setCreateOrganizationFlow(false))
111+
}
112+
onCancel={
113+
showListInitially && isCreateOrganizationFlow ? () => setCreateOrganizationFlow(false) : undefined
114+
}
115+
hideSlug={hideSlug}
116+
/>
117+
</Box>
118+
</>
116119
)}
117120
</>
118121
);
119122
};
120123

121124
export const OrganizationListPageList = (props: { onCreateOrganizationClick: () => void }) => {
125+
const card = useCardState();
122126
const environment = useEnvironment();
123127

124128
const { ref, userMemberships, userSuggestions, userInvitations } = useOrganizationListInView();
@@ -128,6 +132,8 @@ export const OrganizationListPageList = (props: { onCreateOrganizationClick: ()
128132
const hasNextPage = userMemberships?.hasNextPage || userInvitations?.hasNextPage || userSuggestions?.hasNextPage;
129133

130134
const onCreateOrganizationClick = () => {
135+
// Clear error originated from the list when switching to form
136+
card.setError(undefined);
131137
props.onCreateOrganizationClick();
132138
};
133139

@@ -154,6 +160,7 @@ export const OrganizationListPageList = (props: { onCreateOrganizationClick: ()
154160
})}
155161
/>
156162
</Header.Root>
163+
<Card.Alert sx={t => ({ margin: `${t.space.$none} ${t.space.$5}` })}>{card.error}</Card.Alert>
157164
<Col elementDescriptor={descriptors.main}>
158165
<PreviewListItems>
159166
<Actions role='menu'>

packages/clerk-js/src/ui/components/OrganizationList/UserMembershipList.tsx

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,66 @@
11
import { useOrganizationList, useUser } from '@clerk/shared/react';
22
import type { OrganizationResource } from '@clerk/types';
33

4+
import { isClerkAPIResponseError } from '@/index.headless';
45
import { sharedMainIdentifierSx } from '@/ui/common/organizations/OrganizationPreview';
6+
import { localizationKeys, useLocalizations } from '@/ui/customizables';
57
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
68
import { OrganizationPreview } from '@/ui/elements/OrganizationPreview';
79
import { PersonalWorkspacePreview } from '@/ui/elements/PersonalWorkspacePreview';
10+
import { handleError } from '@/ui/utils/errorHandler';
811

912
import { useOrganizationListContext } from '../../contexts';
10-
import { localizationKeys } from '../../localization';
1113
import { OrganizationListPreviewButton } from './shared';
1214

13-
export const MembershipPreview = withCardStateProvider((props: { organization: OrganizationResource }) => {
15+
export const MembershipPreview = (props: { organization: OrganizationResource }) => {
16+
const { user } = useUser();
1417
const card = useCardState();
1518
const { navigateAfterSelectOrganization } = useOrganizationListContext();
19+
const { t } = useLocalizations();
1620
const { isLoaded, setActive } = useOrganizationList();
1721

1822
if (!isLoaded) {
1923
return null;
2024
}
25+
2126
const handleOrganizationClicked = (organization: OrganizationResource) => {
2227
return card.runAsync(async () => {
23-
await setActive({
24-
organization,
25-
});
28+
try {
29+
await setActive({
30+
organization,
31+
});
2632

27-
await navigateAfterSelectOrganization(organization);
33+
await navigateAfterSelectOrganization(organization);
34+
} catch (err) {
35+
if (!isClerkAPIResponseError(err)) {
36+
handleError(err, [], card.setError);
37+
return;
38+
}
39+
40+
switch (err.errors?.[0]?.code) {
41+
case 'organization_not_found_or_unauthorized':
42+
case 'not_a_member_in_organization': {
43+
if (user?.createOrganizationEnabled) {
44+
card.setError(t(localizationKeys('unstable__errors.organization_not_found_or_unauthorized')));
45+
} else {
46+
card.setError(
47+
t(
48+
localizationKeys(
49+
'unstable__errors.organization_not_found_or_unauthorized_with_create_organization_disabled',
50+
),
51+
),
52+
);
53+
}
54+
break;
55+
}
56+
default: {
57+
handleError(err, [], card.setError);
58+
}
59+
}
60+
}
2861
});
2962
};
63+
3064
return (
3165
<OrganizationListPreviewButton onClick={() => handleOrganizationClicked(props.organization)}>
3266
<OrganizationPreview
@@ -36,7 +70,8 @@ export const MembershipPreview = withCardStateProvider((props: { organization: O
3670
/>
3771
</OrganizationListPreviewButton>
3872
);
39-
});
73+
};
74+
4075
export const PersonalAccountPreview = withCardStateProvider(() => {
4176
const card = useCardState();
4277
const { hidePersonal, navigateAfterSelectPersonal } = useOrganizationListContext();

0 commit comments

Comments
 (0)