|
1 | | -import { useReverification, useUser } from '@clerk/shared/react'; |
| 1 | +import { useOrganization, useReverification, useUser } from '@clerk/shared/react'; |
2 | 2 | import React from 'react'; |
3 | 3 |
|
4 | 4 | import { Col, Flow, Heading, Icon, Input, localizationKeys, Text, useLocalizations } from '@/customizables'; |
5 | 5 | import { useFieldOTP } from '@/elements/CodeControl'; |
6 | 6 | import { useCardState } from '@/elements/contexts'; |
7 | 7 | import { Form } from '@/elements/Form'; |
8 | | -import { DuotoneAtSymbol } from '@/icons'; |
| 8 | +import { DuotoneAtSymbol, ExclamationTriangle } from '@/icons'; |
9 | 9 | import { handleError } from '@/utils/errorHandler'; |
10 | 10 |
|
| 11 | +import { useConfigureSSOFlow } from '../ConfigureSSOContext'; |
11 | 12 | import { useConfigureSSOWizard, useRegisterContinueAction } from '../wizard'; |
12 | 13 | import { StepLayout } from './StepLayout'; |
13 | 14 |
|
14 | 15 | export const VerifyDomainStep = (): JSX.Element | null => { |
15 | 16 | const { goNext, goToStep } = useConfigureSSOWizard(); |
| 17 | + const { enterpriseConnection } = useConfigureSSOFlow(); |
16 | 18 | const card = useCardState(); |
17 | 19 | const { t } = useLocalizations(); |
18 | 20 | const { user } = useUser(); |
| 21 | + const { organization } = useOrganization(); |
19 | 22 |
|
20 | 23 | const emailToVerify = |
21 | 24 | user?.primaryEmailAddress ?? user?.emailAddresses?.find(e => e.verification.status !== 'verified'); |
22 | 25 | const isVerified = emailToVerify?.verification.status === 'verified'; |
23 | 26 | const isAlreadyPrimary = Boolean(emailToVerify && emailToVerify.id === user?.primaryEmailAddressId); |
24 | 27 |
|
| 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 | + |
25 | 36 | const prepareEmailVerification = useReverification(() => |
26 | 37 | emailToVerify?.prepareVerification({ strategy: 'email_code' }), |
27 | 38 | ); |
@@ -62,20 +73,27 @@ export const VerifyDomainStep = (): JSX.Element | null => { |
62 | 73 |
|
63 | 74 | const { values, length } = otp.otpControl.otpInputProps; |
64 | 75 | const isCodeComplete = values.filter(Boolean).length === length; |
65 | | - const showVerifiedView = isVerified && !codeSubmittedRef.current; |
| 76 | + const showVerifiedView = isVerified && !codeSubmittedRef.current && !isDomainTakenByOtherOrg; |
66 | 77 |
|
67 | 78 | useRegisterContinueAction( |
68 | | - showVerifiedView |
| 79 | + isDomainTakenByOtherOrg |
69 | 80 | ? { |
70 | 81 | handler: () => { |
71 | | - void goNext(); |
| 82 | + // No-op: there's no path forward from this state |
72 | 83 | }, |
| 84 | + isDisabled: true, |
73 | 85 | } |
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 | + }, |
79 | 97 | ); |
80 | 98 |
|
81 | 99 | React.useEffect(() => { |
@@ -114,7 +132,33 @@ export const VerifyDomainStep = (): JSX.Element | null => { |
114 | 132 | paddingBlock: t.space.$8, |
115 | 133 | })} |
116 | 134 | > |
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 ? ( |
118 | 162 | <> |
119 | 163 | <Icon |
120 | 164 | icon={DuotoneAtSymbol} |
|
0 commit comments