Skip to content

Commit a7071b0

Browse files
committed
Show CTA when connection is already created
1 parent e8dde74 commit a7071b0

2 files changed

Lines changed: 70 additions & 19 deletions

File tree

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ProfileCard } from '@/elements/ProfileCard';
1111
import { BoxIcon } from '@/icons';
1212
import { Route, Switch } from '@/router';
1313

14-
import { ConfigureSSOFlowProvider } from './ConfigureSSOContext';
14+
import { ConfigureSSOFlowProvider, useConfigureSSOFlow } from './ConfigureSSOContext';
1515
import {
1616
ConfigureCreateApp,
1717
ConfirmationStep,
@@ -144,18 +144,25 @@ const AuthenticatedContent = withCoreUserGuard(() => {
144144

145145
const ConfigureSSOSteps = () => {
146146
const { user } = useUser();
147+
const { enterpriseConnection } = useConfigureSSOFlow();
147148

148149
const hasEmailAddress = Boolean(user?.emailAddresses?.length);
150+
// The provider can only be picked once; if a connection already
151+
// exists for this user we drop the step from the wizard entirely
152+
// so it never shows in the breadcrumb and is not routable
153+
const hasEnterpriseConnection = Boolean(enterpriseConnection);
149154

150155
return (
151156
<ConfigureSSOWizard>
152-
<ConfigureSSOWizard.Step
153-
id='select-provider'
154-
path='select-provider'
155-
label='Select provider'
156-
>
157-
<SelectProviderStep />
158-
</ConfigureSSOWizard.Step>
157+
{!hasEnterpriseConnection && (
158+
<ConfigureSSOWizard.Step
159+
id='select-provider'
160+
path='select-provider'
161+
label='Select provider'
162+
>
163+
<SelectProviderStep />
164+
</ConfigureSSOWizard.Step>
165+
)}
159166
<ConfigureSSOWizard.Step
160167
id='verify-email-domain'
161168
path='verify-email-domain'

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

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,38 @@
1-
import { useReverification, useUser } from '@clerk/shared/react';
1+
import { useOrganization, useReverification, useUser } from '@clerk/shared/react';
22
import React from 'react';
33

44
import { Col, Flow, Heading, Icon, Input, localizationKeys, Text, useLocalizations } from '@/customizables';
55
import { useFieldOTP } from '@/elements/CodeControl';
66
import { useCardState } from '@/elements/contexts';
77
import { Form } from '@/elements/Form';
8-
import { DuotoneAtSymbol } from '@/icons';
8+
import { DuotoneAtSymbol, ExclamationTriangle } from '@/icons';
99
import { handleError } from '@/utils/errorHandler';
1010

11+
import { useConfigureSSOFlow } from '../ConfigureSSOContext';
1112
import { useConfigureSSOWizard, useRegisterContinueAction } from '../wizard';
1213
import { StepLayout } from './StepLayout';
1314

1415
export const VerifyDomainStep = (): JSX.Element | null => {
1516
const { goNext, goToStep } = useConfigureSSOWizard();
17+
const { enterpriseConnection } = useConfigureSSOFlow();
1618
const card = useCardState();
1719
const { t } = useLocalizations();
1820
const { user } = useUser();
21+
const { organization } = useOrganization();
1922

2023
const emailToVerify =
2124
user?.primaryEmailAddress ?? user?.emailAddresses?.find(e => e.verification.status !== 'verified');
2225
const isVerified = emailToVerify?.verification.status === 'verified';
2326
const isAlreadyPrimary = Boolean(emailToVerify && emailToVerify.id === user?.primaryEmailAddressId);
2427

28+
// The user's domain is already wired to an enterprise connection that
29+
// doesn't belong to the org they're currently configuring. They can't
30+
// take it over from here — they need the existing app's owner to
31+
// re-configure (or share) the connection
32+
const isDomainTakenByOtherOrg = Boolean(
33+
isVerified && enterpriseConnection && enterpriseConnection.organizationId !== (organization?.id ?? null),
34+
);
35+
2536
const prepareEmailVerification = useReverification(() =>
2637
emailToVerify?.prepareVerification({ strategy: 'email_code' }),
2738
);
@@ -62,20 +73,27 @@ export const VerifyDomainStep = (): JSX.Element | null => {
6273

6374
const { values, length } = otp.otpControl.otpInputProps;
6475
const isCodeComplete = values.filter(Boolean).length === length;
65-
const showVerifiedView = isVerified && !codeSubmittedRef.current;
76+
const showVerifiedView = isVerified && !codeSubmittedRef.current && !isDomainTakenByOtherOrg;
6677

6778
useRegisterContinueAction(
68-
showVerifiedView
79+
isDomainTakenByOtherOrg
6980
? {
7081
handler: () => {
71-
void goNext();
82+
// No-op: there's no path forward from this state
7283
},
84+
isDisabled: true,
7385
}
74-
: {
75-
handler: otp.onFakeContinue,
76-
isDisabled: !isCodeComplete,
77-
isLoading: otp.isLoading,
78-
},
86+
: showVerifiedView
87+
? {
88+
handler: () => {
89+
void goNext();
90+
},
91+
}
92+
: {
93+
handler: otp.onFakeContinue,
94+
isDisabled: !isCodeComplete,
95+
isLoading: otp.isLoading,
96+
},
7997
);
8098

8199
React.useEffect(() => {
@@ -114,7 +132,33 @@ export const VerifyDomainStep = (): JSX.Element | null => {
114132
paddingBlock: t.space.$8,
115133
})}
116134
>
117-
{showVerifiedView ? (
135+
{isDomainTakenByOtherOrg ? (
136+
<>
137+
<Icon
138+
icon={ExclamationTriangle}
139+
sx={t => ({
140+
width: t.sizes.$8,
141+
height: t.sizes.$8,
142+
color: t.colors.$neutralAlpha600,
143+
})}
144+
/>
145+
<Col sx={t => ({ gap: t.space.$1, textAlign: 'center', maxWidth: t.sizes.$66 })}>
146+
<Heading
147+
textVariant='h1'
148+
sx={t => ({ color: t.colors.$colorForeground, fontSize: t.fontSizes.$sm })}
149+
>
150+
That domain is already used for a different enterprise connection.
151+
</Heading>
152+
<Text
153+
as='p'
154+
variant='body'
155+
sx={t => ({ color: t.colors.$colorMutedForeground })}
156+
>
157+
Contact the application owner to configure an SSO connection using the same domain.
158+
</Text>
159+
</Col>
160+
</>
161+
) : showVerifiedView ? (
118162
<>
119163
<Icon
120164
icon={DuotoneAtSymbol}

0 commit comments

Comments
 (0)