22
33import { type LoginUser } from '@repo/dally/user'
44import { Button } from '@repo/ui/button'
5+ import { UserAuthProvider } from '@repo/codegen/src/schema'
56import SimpleForm from '@repo/ui/simple-form'
67import { ArrowRightCircle , Github , KeyRoundIcon } from 'lucide-react'
78import { signIn , type SignInResponse } from 'next-auth/react'
@@ -22,6 +23,8 @@ import { isValidEmail } from '@/lib/validators'
2223import { OPENLANE_WEBSITE_URL } from '@/constants'
2324import { cn } from '@repo/ui/lib/utils'
2425import { sanitizeLoginRedirect } from '@/lib/auth/utils/redirect'
26+ import { recordLastLoginMethod , getLastLoginMethod } from '@/lib/auth/utils/last-login-method'
27+ import { LastUsedBadge } from './last-used-badge'
2528
2629export const LoginPage = ( ) => {
2730 const { separator, buttons, form, input } = loginStyles ( )
@@ -45,6 +48,7 @@ export const LoginPage = () => {
4548 const searchParams = useSearchParams ( )
4649 const token = searchParams ?. get ( 'token' )
4750 const redirect = searchParams ?. get ( 'redirect' )
51+ const emailParam = searchParams ?. get ( 'email' )
4852 const urlErrorMessage = searchParams . get ( 'error' )
4953 const showLoginError = ! signInLoading && signInError
5054
@@ -75,23 +79,26 @@ export const LoginPage = () => {
7579 } , [ webfingerResponse , usePasswordInsteadOfSSO ] )
7680
7781 const shouldShowSSOButton = useCallback ( ( ) : boolean => {
78- if ( ! webfingerResponse ) {
82+ if ( ! webfingerResponse || ! webfingerResponse . success ) {
7983 return false
8084 }
8185
82- // only show SSO button when it is enforced
83- if ( webfingerResponse . enforced && webfingerResponse . provider !== 'NONE' && webfingerResponse . organization_id ) {
86+ // show the SSO button whenever the org has an identity provider configured
87+ if ( webfingerResponse . provider && webfingerResponse . provider !== 'NONE' && webfingerResponse . organization_id ) {
8488 // but if the user is the org admin and chooses to use password, don't show SSO button
8589 if ( webfingerResponse . is_org_owner && usePasswordInsteadOfSSO ) {
8690 return false
8791 }
88- return webfingerResponse . success
92+ return true
8993 }
9094
91- // don't show SSO button when SSO is not enforced
95+ // don't show SSO button when no identity provider is configured
9296 return false
9397 } , [ webfingerResponse , usePasswordInsteadOfSSO ] )
9498
99+ // the method the user most recently signed in with, remembered per-device
100+ const [ lastUsedProvider , setLastUsedProvider ] = useState < UserAuthProvider | null > ( null )
101+
95102 const shouldShowToggleOption = useCallback ( ( ) : boolean => {
96103 return Boolean ( webfingerResponse ?. enforced && webfingerResponse ?. is_org_owner && webfingerResponse ?. provider !== 'NONE' && webfingerResponse ?. organization_id )
97104 } , [ webfingerResponse ] )
@@ -115,6 +122,7 @@ export const LoginPage = () => {
115122 const data = await response . json ( )
116123
117124 if ( response . ok && data . success && data . redirect_uri ) {
125+ recordLastLoginMethod ( UserAuthProvider . OIDC )
118126 window . location . href = data . redirect_uri
119127 return true
120128 }
@@ -176,6 +184,10 @@ export const LoginPage = () => {
176184 }
177185 } , [ ] )
178186
187+ useEffect ( ( ) => {
188+ setLastUsedProvider ( getLastLoginMethod ( ) )
189+ } , [ ] )
190+
179191 const redirectUrl = useMemo ( ( ) => {
180192 if ( token ) {
181193 return `/invite?token=${ token } `
@@ -188,7 +200,8 @@ export const LoginPage = () => {
188200 setSignInError ( false )
189201
190202 try {
191- if ( shouldShowSSOButton ( ) ) {
203+ // only block credential submit when SSO is the only available method
204+ if ( shouldShowSSOButton ( ) && ! shouldShowPasswordField ( ) ) {
192205 return
193206 }
194207
@@ -217,6 +230,7 @@ export const LoginPage = () => {
217230 } )
218231
219232 if ( res . ok && ! res . error ) {
233+ recordLastLoginMethod ( UserAuthProvider . CREDENTIALS )
220234 router . push ( redirectUrl )
221235 } else {
222236 let errMsg = 'There was an error. Please try again.'
@@ -251,6 +265,7 @@ export const LoginPage = () => {
251265
252266 const github = async ( ) => {
253267 setDirectOAuthCookie ( )
268+ recordLastLoginMethod ( UserAuthProvider . GITHUB )
254269
255270 await signIn ( 'github' , {
256271 redirectTo : redirectUrl ,
@@ -259,6 +274,7 @@ export const LoginPage = () => {
259274
260275 const google = async ( ) => {
261276 setDirectOAuthCookie ( )
277+ recordLastLoginMethod ( UserAuthProvider . GOOGLE )
262278
263279 await signIn ( 'google' , {
264280 redirectTo : redirectUrl ,
@@ -292,6 +308,7 @@ export const LoginPage = () => {
292308
293309 if ( verificationResult . success ) {
294310 setDirectOAuthCookie ( )
311+ recordLastLoginMethod ( UserAuthProvider . WEBAUTHN )
295312 await signIn ( 'passkey' , {
296313 callbackUrl : redirectUrl ,
297314 email : email || '' ,
@@ -329,6 +346,14 @@ export const LoginPage = () => {
329346 }
330347 } , [ urlErrorMessage , router ] )
331348
349+ // prefill the email from an invite link and resolve sign-in methods on load
350+ useEffect ( ( ) => {
351+ if ( emailParam && isValidEmail ( emailParam ) ) {
352+ setEmail ( emailParam )
353+ checkLoginMethods ( emailParam )
354+ }
355+ } , [ emailParam , checkLoginMethods ] )
356+
332357 return (
333358 < >
334359 < div className = "flex flex-col self-center text-center" >
@@ -342,18 +367,27 @@ export const LoginPage = () => {
342367 </ div >
343368 ) }
344369
345- < div className = { cn ( buttons ( ) , 'flex justify-center mt-[32px]' ) } >
346- < Button variant = "secondary" className = "!py-1.5 !px-5" size = "md" icon = { < GoogleIcon /> } iconPosition = "left" onClick = { ( ) => google ( ) } disabled = { signInLoading } >
347- < p className = "text-sm font-normal" > Google</ p >
348- </ Button >
370+ < div className = { cn ( buttons ( ) , 'flex justify-center items-center mt-[32px]' ) } >
371+ < div className = "relative" >
372+ < LastUsedBadge provider = { UserAuthProvider . GOOGLE } lastUsedProvider = { lastUsedProvider } floating />
373+ < Button variant = "secondary" className = "!py-1.5 !px-5 " size = "md" icon = { < GoogleIcon /> } iconPosition = "left" onClick = { ( ) => google ( ) } disabled = { signInLoading } >
374+ < p className = "text-sm font-normal" > Google</ p >
375+ </ Button >
376+ </ div >
349377
350- < Button variant = "secondary" className = "!py-1.5 !px-5" size = "md" icon = { < Github className = "text-input-text" /> } iconPosition = "left" onClick = { ( ) => github ( ) } disabled = { signInLoading } >
351- < p className = "text-sm font-normal" > GitHub</ p >
352- </ Button >
378+ < div className = "relative" >
379+ < LastUsedBadge provider = { UserAuthProvider . GITHUB } lastUsedProvider = { lastUsedProvider } floating />
380+ < Button variant = "secondary" className = "!py-1.5 !px-5 " size = "md" icon = { < Github className = "text-input-text" /> } iconPosition = "left" onClick = { ( ) => github ( ) } disabled = { signInLoading } >
381+ < p className = "text-sm font-normal" > GitHub</ p >
382+ </ Button >
383+ </ div >
353384
354- < Button variant = "secondary" className = "!py-1.5 !px-5" icon = { < KeyRoundIcon className = "text-input-text" /> } iconPosition = "left" onClick = { ( ) => passKeySignIn ( ) } disabled = { signInLoading } >
355- < p className = "text-sm font-normal" > Passkey</ p >
356- </ Button >
385+ < div className = "relative" >
386+ < LastUsedBadge provider = { UserAuthProvider . WEBAUTHN } lastUsedProvider = { lastUsedProvider } floating />
387+ < Button variant = "secondary" className = "!py-1.5 !px-5 " icon = { < KeyRoundIcon className = "text-input-text" /> } iconPosition = "left" onClick = { ( ) => passKeySignIn ( ) } disabled = { signInLoading } >
388+ < p className = "text-sm font-normal" > Passkey</ p >
389+ </ Button >
390+ </ div >
357391 </ div >
358392
359393 < Separator label = "or" login className = { cn ( separator ( ) , 'text-muted-foreground' ) } />
@@ -379,8 +413,9 @@ export const LoginPage = () => {
379413 } }
380414 >
381415 < div className = { input ( ) } >
382- < div className = "flex items-center justify-between" >
416+ < div className = "flex items-center justify-between items-centeer " >
383417 < p className = "text-sm" > Email</ p >
418+ < LastUsedBadge provider = { UserAuthProvider . CREDENTIALS } lastUsedProvider = { lastUsedProvider } />
384419 { shouldShowSSOButton ( ) && shouldShowToggleOption ( ) && (
385420 < button
386421 type = "button"
@@ -392,15 +427,23 @@ export const LoginPage = () => {
392427 ) }
393428 </ div >
394429
395- < Input type = "email" variant = "light" name = "username" placeholder = "Enter your email" className = { `bg-transparent ${ showLoginError ? 'border border-toast-error-icon' : '' } ` } />
430+ < Input
431+ type = "email"
432+ variant = "light"
433+ name = "username"
434+ placeholder = "Enter your email"
435+ defaultValue = { emailParam ?? undefined }
436+ className = { `bg-transparent ${ showLoginError ? 'border border-toast-error-icon' : '' } ` }
437+ />
396438 { showLoginError && < span className = "text-xs text-toast-error-icon text-left" > { signInErrorMessage } </ span > }
397439 </ div >
398440
399441 { shouldShowSSOButton ( ) && (
400- < div className = "flex flex-col" >
442+ < div className = "relative flex flex-col mt-[16px]" >
443+ < LastUsedBadge provider = { UserAuthProvider . OIDC } lastUsedProvider = { lastUsedProvider } floating />
401444 < Button
402445 variant = "primary"
403- className = "mt-[16px] p-4 flex justify-center items-center text-center rounded-md text-sm h-[36px] font-bold"
446+ className = "p-4 flex justify-center items-center text-center rounded-md text-sm h-[36px] font-bold"
404447 type = "button"
405448 onClick = { handleSSOLogin }
406449 disabled = { signInLoading || webfingerLoading }
@@ -425,9 +468,12 @@ export const LoginPage = () => {
425468 </ div >
426469 < PasswordInput variant = "light" name = "password" placeholder = "Enter your password" autoComplete = "current-password" className = "bg-transparent !text-text" />
427470 </ div >
428- < Button variant = "primary" className = "mt-[16px] p-4 flex justify-center items-center text-center rounded-md text-sm h-[36px] font-bold" type = "submit" disabled = { signInLoading } >
429- < span > Login</ span >
430- </ Button >
471+ < div className = "flex flex-col" >
472+ < Button variant = "primary" className = "mt-[16px] p-4 flex justify-center items-center text-center rounded-md text-sm h-[36px] font-bold" type = "submit" disabled = { signInLoading } >
473+ { ' ' }
474+ < span > Login</ span >
475+ </ Button >
476+ </ div >
431477 </ >
432478 }
433479
0 commit comments