@@ -39,7 +39,11 @@ import { useSupportEmail } from '../../hooks/useSupportEmail';
3939import { useTotalEnabledAuthMethods } from '../../hooks/useTotalEnabledAuthMethods' ;
4040import { useRouter } from '../../router' ;
4141import { handleCombinedFlowTransfer } from './handleCombinedFlowTransfer' ;
42- import { hasMultipleEnterpriseConnections , useHandleAuthenticateWithPasskey } from './shared' ;
42+ import {
43+ hasMultipleEnterpriseConnections ,
44+ SIGN_IN_RESET_PASSWORD_INTENT_PARAM ,
45+ useHandleAuthenticateWithPasskey ,
46+ } from './shared' ;
4347import { SignInAlternativePhoneCodePhoneNumberCard } from './SignInAlternativePhoneCodePhoneNumberCard' ;
4448import { SignInSocialButtons } from './SignInSocialButtons' ;
4549import {
@@ -347,7 +351,10 @@ function SignInStartInternal(): JSX.Element {
347351 } ) ;
348352 } ;
349353
350- const signInWithFields = async ( ...fields : Array < FormControlState < string > > ) => {
354+ const signInWithFields = async (
355+ fields : Array < FormControlState < string > > ,
356+ options ?: { resetPasswordIntent ?: boolean } ,
357+ ) => {
351358 // If the user has already selected an alternative phone code provider, we use that.
352359 const preferredAlternativePhoneChannel =
353360 alternativePhoneCodeProvider ?. channel ||
@@ -385,6 +392,11 @@ function SignInStartInternal(): JSX.Element {
385392 break ;
386393 case 'needs_first_factor' : {
387394 if ( ! hasOnlyEnterpriseSSOFirstFactors ( res ) || hasMultipleEnterpriseConnections ( res . supportedFirstFactors ) ) {
395+ if ( options ?. resetPasswordIntent ) {
396+ return navigate ( 'factor-one' , {
397+ searchParams : new URLSearchParams ( { [ SIGN_IN_RESET_PASSWORD_INTENT_PARAM ] : 'true' } ) ,
398+ } ) ;
399+ }
388400 return navigate ( 'factor-one' ) ;
389401 }
390402
@@ -447,7 +459,7 @@ function SignInStartInternal(): JSX.Element {
447459 ) ;
448460
449461 if ( instantPasswordError ) {
450- await signInWithFields ( identifierField ) ;
462+ await signInWithFields ( [ identifierField ] ) ;
451463 } else if ( sessionAlreadyExistsError ) {
452464 await clerk . setActive ( {
453465 session : clerk . client . lastActiveSessionId ,
@@ -514,7 +526,18 @@ function SignInStartInternal(): JSX.Element {
514526
515527 const handleFirstPartySubmit = async ( e : React . FormEvent < HTMLFormElement > ) => {
516528 e . preventDefault ( ) ;
517- return signInWithFields ( identifierField , instantPasswordField ) ;
529+ return signInWithFields ( [ identifierField , instantPasswordField ] ) ;
530+ } ;
531+
532+ const handleForgotPasswordClick : React . MouseEventHandler = e => {
533+ e . preventDefault ( ) ;
534+ // Surface the same native required-field validation as the Continue button
535+ // when the identifier is missing
536+ const form = e . currentTarget . closest ( 'form' ) ;
537+ if ( form && ! form . reportValidity ( ) ) {
538+ return ;
539+ }
540+ void signInWithFields ( [ identifierField ] , { resetPasswordIntent : true } ) ;
518541 } ;
519542
520543 const DynamicField = useMemo ( ( ) => {
@@ -600,7 +623,10 @@ function SignInStartInternal(): JSX.Element {
600623 isLastAuthenticationStrategy = { isIdentifierLastAuthenticationStrategy }
601624 />
602625 </ Form . ControlRow >
603- < InstantPasswordRow field = { passwordBasedInstance ? instantPasswordField : undefined } />
626+ < InstantPasswordRow
627+ field = { passwordBasedInstance ? instantPasswordField : undefined }
628+ onForgotPasswordClick = { handleForgotPasswordClick }
629+ />
604630 </ Col >
605631 < Col center >
606632 < CaptchaElement />
@@ -663,7 +689,13 @@ const hasOnlyEnterpriseSSOFirstFactors = (signIn: SignInResource): boolean => {
663689 return signIn . supportedFirstFactors . every ( ff => ff . strategy === 'enterprise_sso' ) ;
664690} ;
665691
666- const InstantPasswordRow = ( { field } : { field ?: FormControlState < 'password' > } ) => {
692+ const InstantPasswordRow = ( {
693+ field,
694+ onForgotPasswordClick,
695+ } : {
696+ field ?: FormControlState < 'password' > ;
697+ onForgotPasswordClick ?: React . MouseEventHandler ;
698+ } ) => {
667699 const [ autofilled , setAutofilled ] = useState ( false ) ;
668700 const ref = useRef < HTMLInputElement > ( null ) ;
669701 const show = ! ! ( autofilled || field ?. value ) ;
@@ -706,6 +738,8 @@ const InstantPasswordRow = ({ field }: { field?: FormControlState<'password'> })
706738 >
707739 < Form . PasswordInput
708740 { ...field . props }
741+ actionLabel = { show ? localizationKeys ( 'formFieldAction__forgotPassword' ) : undefined }
742+ onActionClicked = { show ? onForgotPasswordClick : undefined }
709743 ref = { ref }
710744 tabIndex = { show ? undefined : - 1 }
711745 />
0 commit comments