11import { useReverification , useSession , useUser } from '@clerk/shared/react' ;
22import type { EmailAddressResource } from '@clerk/shared/types' ;
3- import { useCallback , useEffect , useRef , useState } from 'react' ;
3+ import React , { useCallback , useEffect , useRef , useState } from 'react' ;
44
55import {
66 Col ,
@@ -42,6 +42,11 @@ export const VerifyDomainStep = (): JSX.Element => {
4242 ) ;
4343
4444 const wasVerifiedOnMountRef = useRef ( isVerified ) ;
45+ const emailAddressRef = useRef < EmailAddressResource | undefined > ( emailToVerify ) ;
46+ const preExistingEmailIdRef = useRef < string | undefined > ( emailToVerify ?. id ) ;
47+ const initialInnerStepIdRef = useRef < 'provide-email' | 'verify-email-address' > (
48+ emailToVerify ? 'verify-email-address' : 'provide-email' ,
49+ ) ;
4550
4651 if ( isDomainTakenByOtherOrg ) {
4752 const conflictingDomain = enterpriseConnection ?. domains [ 0 ] as string ;
@@ -122,7 +127,7 @@ export const VerifyDomainStep = (): JSX.Element => {
122127 elementDescriptor = { descriptors . configureSSOStep }
123128 elementId = { descriptors . configureSSOStep . setId ( 'verify-domain' ) }
124129 >
125- < Wizard >
130+ < Wizard initialStepId = { initialInnerStepIdRef . current } >
126131 < Step . Header
127132 title = { t ( localizationKeys ( 'configureSSO.verifyEmailDomainStep.title' ) ) }
128133 description = { t ( localizationKeys ( 'configureSSO.verifyEmailDomainStep.subtitle' ) ) }
@@ -132,11 +137,14 @@ export const VerifyDomainStep = (): JSX.Element => {
132137
133138 < Step . Body >
134139 < Wizard . Step id = 'provide-email' >
135- < ProvideEmailStep />
140+ < ProvideEmailStep
141+ emailAddressRef = { emailAddressRef }
142+ preExistingEmailIdRef = { preExistingEmailIdRef }
143+ />
136144 </ Wizard . Step >
137145
138146 < Wizard . Step id = 'verify-email-address' >
139- < EnterVerificationCodeStep emailToVerify = { emailToVerify } />
147+ < EnterVerificationCodeStep emailAddressRef = { emailAddressRef } />
140148 </ Wizard . Step >
141149 </ Step . Body >
142150 </ Wizard >
@@ -157,12 +165,21 @@ const InnerStepCounter = (): JSX.Element => {
157165
158166const isEmail = ( str : string ) => / ^ \S + @ \S + \. \S + $ / . test ( str ) ;
159167
160- export const ProvideEmailStep = ( ) : JSX . Element => {
161- const { goNext, goPrev, isFirstStep } = useWizard ( ) ;
168+ type ProvideEmailStepProps = {
169+ emailAddressRef : React . MutableRefObject < EmailAddressResource | undefined > ;
170+ preExistingEmailIdRef : React . MutableRefObject < string | undefined > ;
171+ } ;
172+
173+ const normalizeEmail = ( value : string ) : string => value . trim ( ) . toLowerCase ( ) ;
174+
175+ export const ProvideEmailStep = ( { emailAddressRef, preExistingEmailIdRef } : ProvideEmailStepProps ) : JSX . Element => {
176+ const { goNext, goPrev } = useWizard ( ) ;
162177 const { user } = useUser ( ) ;
163178 const card = useCardState ( ) ;
164179 const { t } = useLocalizations ( ) ;
165- const [ email , setEmail ] = useState ( '' ) ;
180+ // Pre-fill with whatever email the parent is currently tracking so navigating back from the
181+ // verify step shows the user what they previously submitted instead of an empty field.
182+ const [ email , setEmail ] = useState ( ( ) => emailAddressRef . current ?. emailAddress ?? '' ) ;
166183 const createEmailAddress = useReverification ( ( value : string ) => user ?. createEmailAddress ( { email : value } ) ) ;
167184
168185 const canSubmit = isEmail ( email ) && ! card . isLoading ;
@@ -171,18 +188,41 @@ export const ProvideEmailStep = (): JSX.Element => {
171188 return ;
172189 }
173190
191+ const current = emailAddressRef . current ;
192+ const submittedEmail = email . trim ( ) ;
193+
194+ // Same email address as previously submitted, skip the flow
195+ if ( current && normalizeEmail ( current . emailAddress ) === normalizeEmail ( submittedEmail ) ) {
196+ await goNext ( ) ;
197+ return ;
198+ }
199+
174200 card . setError ( undefined ) ;
175201 card . setLoading ( ) ;
176202
177203 try {
178- await createEmailAddress ( email ) ;
204+ const created = await createEmailAddress ( submittedEmail ) ;
205+ const previous = current ;
206+ emailAddressRef . current = created ?? undefined ;
207+
208+ // Clean up the previous in-flight address so the user doesn't accumulate orphans on
209+ // their account
210+ if ( previous && previous . id !== preExistingEmailIdRef . current && previous . id !== created ?. id ) {
211+ try {
212+ await previous . destroy ( ) ;
213+ } catch {
214+ // A leftover unverified address is preferable to surfacing a cleanup
215+ // error after a successful create.
216+ }
217+ }
218+
179219 await goNext ( ) ;
180220 } catch ( err ) {
181221 handleError ( err as Error , [ ] , card . setError ) ;
182222 } finally {
183223 card . setIdle ( ) ;
184224 }
185- } , [ canSubmit , email , createEmailAddress , card , goNext ] ) ;
225+ } , [ canSubmit , email , createEmailAddress , card , goNext , emailAddressRef , preExistingEmailIdRef ] ) ;
186226
187227 return (
188228 < >
@@ -255,7 +295,7 @@ export const ProvideEmailStep = (): JSX.Element => {
255295 < Step . Footer >
256296 < Step . Footer . Previous
257297 onClick = { ( ) => goPrev ( ) }
258- isDisabled = { isFirstStep }
298+ isDisabled
259299 />
260300 < Step . Footer . Continue
261301 onClick = { handleSubmit }
@@ -268,22 +308,26 @@ export const ProvideEmailStep = (): JSX.Element => {
268308} ;
269309
270310export const EnterVerificationCodeStep = ( {
271- emailToVerify ,
311+ emailAddressRef ,
272312} : {
273- emailToVerify ?: EmailAddressResource ;
313+ emailAddressRef : React . MutableRefObject < EmailAddressResource | undefined > ;
274314} ) : JSX . Element | null => {
275315 const { user } = useUser ( ) ;
276316 const { provider, createEnterpriseConnection } = useConfigureSSO ( ) ;
277317 const card = useCardState ( ) ;
278- const { goNext, goPrev, isFirstStep } = useWizard ( ) ;
318+ const { goNext, goPrev } = useWizard ( ) ;
319+ const primaryEmailAddress = user ?. primaryEmailAddress ;
279320
321+ const emailToVerify = emailAddressRef . current ;
280322 const isVerified = emailToVerify ?. verification . status === 'verified' ;
281323 const isPrimary = emailToVerify ?. id === user ?. primaryEmailAddressId ;
282324
283325 const prepareEmailVerification = useReverification ( ( ) =>
284- emailToVerify ?. prepareVerification ( { strategy : 'email_code' } ) ,
326+ emailAddressRef . current ?. prepareVerification ( { strategy : 'email_code' } ) ,
327+ ) ;
328+ const attemptEmailVerification = useReverification ( ( code : string ) =>
329+ emailAddressRef . current ?. attemptVerification ( { code } ) ,
285330 ) ;
286- const attemptEmailVerification = useReverification ( ( code : string ) => emailToVerify ?. attemptVerification ( { code } ) ) ;
287331 const setPrimaryEmailAddress = useReverification ( ( emailAddressId : string ) =>
288332 user ?. update ( { primaryEmailAddressId : emailAddressId } ) ,
289333 ) ;
@@ -303,9 +347,10 @@ export const EnterVerificationCodeStep = ({
303347 void prepare ( ) ;
304348 } ,
305349 onResolve : async ( ) => {
306- if ( emailToVerify && ! isPrimary ) {
350+ const target = emailAddressRef . current ;
351+ if ( target && ! isPrimary ) {
307352 try {
308- await setPrimaryEmailAddress ( emailToVerify . id ) ;
353+ await setPrimaryEmailAddress ( target . id ) ;
309354 } catch ( err ) {
310355 handleError ( err as Error , [ ] , card . setError ) ;
311356 return ;
@@ -318,7 +363,7 @@ export const EnterVerificationCodeStep = ({
318363 }
319364
320365 try {
321- await createEnterpriseConnection ( provider ) ;
366+ await createEnterpriseConnection ( provider , emailToVerify ) ;
322367 } catch ( err ) {
323368 handleError ( err as Error , [ ] , card . setError ) ;
324369 return ;
@@ -328,10 +373,12 @@ export const EnterVerificationCodeStep = ({
328373 } ,
329374 } ) ;
330375
376+ // Send a code on mount, but only when the target address is not already verified
331377 useEffect ( ( ) => {
332378 if ( emailToVerify && ! isVerified ) {
333379 void prepare ( ) ;
334380 }
381+ // eslint-disable-next-line react-hooks/exhaustive-deps
335382 } , [ ] ) ;
336383
337384 if ( ! emailToVerify ) {
@@ -374,7 +421,7 @@ export const EnterVerificationCodeStep = ({
374421 < Step . Footer >
375422 < Step . Footer . Previous
376423 onClick = { ( ) => goPrev ( ) }
377- isDisabled = { isFirstStep }
424+ isDisabled = { ! ! primaryEmailAddress }
378425 />
379426 < Step . Footer . Continue
380427 onClick = { ( ) => goNext ( ) }
0 commit comments