Skip to content

Commit d2d1a49

Browse files
authored
Merge branch 'main' into jacek/fix-core3-oauth-retry
2 parents 381bb43 + a1635f0 commit d2d1a49

27 files changed

Lines changed: 1269 additions & 97 deletions

.changeset/four-wombats-clean.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@clerk/clerk-js": minor
3+
"@clerk/ui": minor
4+
---
5+
6+
Removed unused internal OAuthConsent prop.

.changeset/lucky-tables-learn.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@clerk/ui': patch
3+
---
4+
5+
Add wizard steps for the `<__experimental_ConfigureSSO />` component

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
"@clerk/backend": "workspace:*",
7878
"@clerk/shared": "workspace:*",
7979
"@clerk/testing": "workspace:*",
80-
"@commitlint/cli": "^20.5.0",
80+
"@commitlint/cli": "^20.5.2",
8181
"@commitlint/config-conventional": "^20.5.0",
8282
"@eslint/eslintrc": "^3.3.5",
8383
"@eslint/js": "9.31.0",

packages/clerk-js/sandbox/app.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,6 @@ void (async () => {
486486
scopes,
487487
oauthClientId: 'Wg9fP2d0pSFXCZ1u',
488488
redirectUrl: searchParams.get('redirect_uri') ?? 'http://localhost:4000/oauth/callback',
489-
__internal_enableOrgSelection: true,
490489
},
491490
);
492491
},

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,15 @@ export class Clerk implements ClerkInterface {
14571457
return;
14581458
}
14591459

1460+
if (noUserExists(this)) {
1461+
if (this.#instanceType === 'development') {
1462+
throw new ClerkRuntimeError(warnings.cannotRenderConfigureSSOComponentWhenUserDoesNotExist, {
1463+
code: CANNOT_RENDER_USER_MISSING_ERROR_CODE,
1464+
});
1465+
}
1466+
return;
1467+
}
1468+
14601469
this.assertComponentsReady(this.#clerkUI);
14611470
const component = 'ConfigureSSO';
14621471
void this.#clerkUI

packages/shared/src/internal/clerk-js/warnings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ const warnings = {
6464
'The <APIKeys/> component cannot be rendered when organization API keys are disabled. Since organization API keys are disabled, this is no-op.',
6565
cannotRenderOAuthConsentComponentWhenUserDoesNotExist:
6666
'<OAuthConsent/> cannot render unless a user is signed in. Since no user is signed in, this is no-op.',
67+
cannotRenderConfigureSSOComponentWhenUserDoesNotExist:
68+
'<ConfigureSSO/> cannot render unless a user is signed in. Since no user is signed in, this is no-op.',
6769
cannotRenderConfigureSSOComponentWhenDisabled:
6870
'The <ConfigureSSO/> component cannot be rendered when self-serve SSO is disabled. Visit `https://dashboard.clerk.com` to enable the feature. Since self-serve SSO is disabled, this is no-op.',
6971
cannotRenderConfigureSSOComponentWhenEmailAddressDisabled:

packages/ui/src/components/ConfigureSSO/ConfigureSSO.tsx

Lines changed: 102 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
import { useOrganization } from '@clerk/shared/react/index';
1+
import { __internal_useUserEnterpriseConnections, useOrganization, useUser } from '@clerk/shared/react';
22
import type { __experimental_ConfigureSSOProps } from '@clerk/shared/types';
33
import React from 'react';
44

55
import { useEnvironment, withCoreUserGuard } from '@/contexts';
6-
import { Box, Col, Flex, Flow, Icon, localizationKeys, Text, useAppearance } from '@/customizables';
6+
import { Box, Col, descriptors, Flex, Flow, Icon, localizationKeys, Text, useAppearance } from '@/customizables';
77
import { ApplicationLogo } from '@/elements/ApplicationLogo';
88
import { withCardStateProvider } from '@/elements/contexts';
99
import { NavBar, NavbarContextProvider } from '@/elements/Navbar';
1010
import { ProfileCard } from '@/elements/ProfileCard';
1111
import { BoxIcon } from '@/icons';
1212
import { Route, Switch } from '@/router';
1313

14+
import { ConfigureSSOFlowProvider } from './ConfigureSSOContext';
15+
import { ConfigureCreateApp, ConfirmationStep, ProvideEmail, TestConfigurationStep, VerifyDomainStep } from './steps';
16+
import { ConfigureSSOWizard } from './wizard';
17+
1418
const ConfigureSSOInternal = () => {
1519
return (
1620
<Flow.Root flow='configureSSO'>
17-
<Flow.Part>
18-
<Switch>
19-
<Route>
20-
<AuthenticatedContent />
21-
</Route>
22-
</Switch>
23-
</Flow.Part>
21+
<Switch>
22+
<Route>
23+
<AuthenticatedContent />
24+
</Route>
25+
</Switch>
2426
</Flow.Root>
2527
);
2628
};
@@ -32,6 +34,11 @@ const AuthenticatedContent = withCoreUserGuard(() => {
3234
const { parsedOptions } = useAppearance();
3335
const hasLogo = Boolean(parsedOptions.logoImageUrl || logoImageUrl);
3436

37+
const { data: enterpriseConnections, isLoading: isLoadingEnterpriseConnections } =
38+
__internal_useUserEnterpriseConnections({ enabled: true });
39+
// Currently FAPI only supports one enterprise connection per user
40+
const enterpriseConnection = enterpriseConnections?.[0];
41+
3542
return (
3643
<ProfileCard.Root
3744
sx={t => ({ display: 'grid', gridTemplateColumns: '1fr 3fr', height: t.sizes.$176, overflow: 'hidden' })}
@@ -89,12 +96,97 @@ const AuthenticatedContent = withCoreUserGuard(() => {
8996
routes={[]}
9097
contentRef={contentRef}
9198
/>
92-
<ProfileCard.Content contentRef={contentRef} />
99+
<Col
100+
ref={contentRef}
101+
elementDescriptor={descriptors.scrollBox}
102+
sx={t => ({
103+
backgroundColor: t.colors.$colorBackground,
104+
position: 'relative',
105+
borderRadius: t.radii.$lg,
106+
width: '100%',
107+
overflow: 'hidden',
108+
borderWidth: t.borderWidths.$normal,
109+
borderStyle: t.borderStyles.$solid,
110+
borderColor: t.colors.$borderAlpha150,
111+
marginBlock: '-1px',
112+
marginInlineEnd: '-1px',
113+
flex: 1,
114+
})}
115+
>
116+
<ConfigureSSOFlowProvider
117+
enterpriseConnection={enterpriseConnection}
118+
isLoading={isLoadingEnterpriseConnections}
119+
>
120+
<ConfigureSSOSteps />
121+
</ConfigureSSOFlowProvider>
122+
</Col>
93123
</NavbarContextProvider>
94124
</ProfileCard.Root>
95125
);
96126
});
97127

128+
const ConfigureSSOSteps = () => {
129+
const { user } = useUser();
130+
131+
const primaryEmailAddress = user?.primaryEmailAddress;
132+
133+
return (
134+
<ConfigureSSOWizard>
135+
<ConfigureSSOWizard.Step
136+
id='verify-email-domain'
137+
path='verify-email-domain'
138+
label='Verify domain'
139+
>
140+
<ConfigureSSOWizard>
141+
{!primaryEmailAddress && (
142+
<ConfigureSSOWizard.Step
143+
id='provide-email'
144+
path='provide-email'
145+
>
146+
<ProvideEmail />
147+
</ConfigureSSOWizard.Step>
148+
)}
149+
<ConfigureSSOWizard.Step
150+
id='verify-domain'
151+
path='verify-domain'
152+
>
153+
<VerifyDomainStep />
154+
</ConfigureSSOWizard.Step>
155+
</ConfigureSSOWizard>
156+
</ConfigureSSOWizard.Step>
157+
<ConfigureSSOWizard.Step
158+
id='configure'
159+
path='configure'
160+
label='Configure'
161+
>
162+
<ConfigureSSOWizard>
163+
{/* TODO: Implement configure steps */}
164+
<ConfigureSSOWizard.Step
165+
id='create-app'
166+
path='create-app'
167+
>
168+
<ConfigureCreateApp />
169+
</ConfigureSSOWizard.Step>
170+
</ConfigureSSOWizard>
171+
</ConfigureSSOWizard.Step>
172+
<ConfigureSSOWizard.Step
173+
id='test'
174+
path='test'
175+
label='Test'
176+
>
177+
<TestConfigurationStep />
178+
</ConfigureSSOWizard.Step>
179+
<ConfigureSSOWizard.Step
180+
id='confirmation'
181+
path='confirmation'
182+
label='Confirmation'
183+
>
184+
<ConfirmationStep />
185+
</ConfigureSSOWizard.Step>
186+
</ConfigureSSOWizard>
187+
);
188+
};
189+
98190
const OrganizationSidebarSubtitle = () => {
99191
const { organization } = useOrganization();
100192

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type { EnterpriseConnectionResource } from '@clerk/shared/types';
2+
import React, { type PropsWithChildren } from 'react';
3+
4+
/**
5+
* Shared form state for the ConfigureSSO wizard, persisted across steps
6+
*/
7+
export interface ConfigureSSOData {
8+
/**
9+
* The enterprise connection from the user's primary email address domain
10+
*/
11+
enterpriseConnection: EnterpriseConnectionResource | undefined;
12+
}
13+
14+
export interface ConfigureSSOContextValue extends ConfigureSSOData {
15+
/**
16+
* `true` while the parent is still fetching the user's enterprise
17+
* connection
18+
*/
19+
isLoading: boolean;
20+
}
21+
22+
interface ConfigureSSOFlowProviderProps {
23+
enterpriseConnection: EnterpriseConnectionResource | undefined;
24+
isLoading: boolean;
25+
}
26+
27+
const ConfigureSSOFlowContext = React.createContext<ConfigureSSOContextValue | null>(null);
28+
ConfigureSSOFlowContext.displayName = 'ConfigureSSOFlowContext';
29+
30+
export const ConfigureSSOFlowProvider = ({
31+
enterpriseConnection,
32+
isLoading,
33+
children,
34+
}: PropsWithChildren<ConfigureSSOFlowProviderProps>): JSX.Element => {
35+
const value = React.useMemo<ConfigureSSOContextValue>(
36+
() => ({
37+
enterpriseConnection,
38+
isLoading,
39+
}),
40+
[enterpriseConnection, isLoading],
41+
);
42+
43+
return <ConfigureSSOFlowContext.Provider value={value}>{children}</ConfigureSSOFlowContext.Provider>;
44+
};
45+
46+
export const useConfigureSSOFlow = (): ConfigureSSOContextValue => {
47+
const ctx = React.useContext(ConfigureSSOFlowContext);
48+
if (!ctx) {
49+
throw new Error('useConfigureSSOFlow called outside <ConfigureSSOFlowProvider>.');
50+
}
51+
return ctx;
52+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Flow, Text } from '@/customizables';
2+
3+
import { useConfigureSSOWizard, useRegisterContinueAction } from '../wizard';
4+
import { StepLayout } from './StepLayout';
5+
6+
export const ConfigureCreateApp = (): JSX.Element => {
7+
const { goNext } = useConfigureSSOWizard();
8+
9+
useRegisterContinueAction({
10+
handler: () => goNext(),
11+
});
12+
13+
return (
14+
<Flow.Part part='configureCreateApp'>
15+
<StepLayout
16+
title='Configure Okta Workforce'
17+
subtitle='Create a new enterprise application in your Okta Dashboard.'
18+
>
19+
<Text>UI goes here</Text>
20+
</StepLayout>
21+
</Flow.Part>
22+
);
23+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { Flow, Text } from '@/customizables';
2+
3+
import { StepLayout } from './StepLayout';
4+
5+
export const ConfirmationStep = (): JSX.Element => {
6+
return (
7+
<Flow.Part part='sso-confirmation'>
8+
<StepLayout>
9+
<Text>UI goes here</Text>
10+
</StepLayout>
11+
</Flow.Part>
12+
);
13+
};

0 commit comments

Comments
 (0)