11import { useState } from 'react' ;
22import { Head , useForm } from '@inertiajs/react' ;
3- import { Tabs , TabsContent , TabsList , TabsTrigger } from '@/components/ui/tabs' ;
43import { Button } from '@/components/ui/button' ;
5- import { Card , CardContent , CardDescription , CardFooter , CardHeader , CardTitle } from '@/components/ui/card' ;
64import { InputOTP , InputOTPGroup , InputOTPSlot } from '@/components/ui/input-otp' ;
75import { Input } from '@/components/ui/input' ;
86import { Label } from '@/components/ui/label' ;
97import InputError from '@/components/input-error' ;
8+ import AuthLayout from '@/layouts/auth-layout' ;
9+ import TextLink from '@/components/text-link' ;
1010
1111export default function TwoFactorChallenge ( ) {
12- const [ activeTab , setActiveTab ] = useState ( 'code' ) ;
12+ const [ recovery , setRecovery ] = useState ( false ) ;
1313
1414 const { data, setData, post, processing, errors } = useForm ( {
1515 code : '' ,
@@ -27,74 +27,67 @@ export default function TwoFactorChallenge() {
2727 } ;
2828
2929 return (
30- < div className = "flex min-h-screen flex-col items-center justify-center" >
30+ < AuthLayout
31+ title = { recovery ? 'Recovery Code' : 'Authentication Code' }
32+ description = { recovery
33+ ? 'Please confirm access to your account by entering one of your emergency recovery codes.'
34+ : 'Enter the authentication code provided by your authenticator application.' }
35+ >
3136 < Head title = "Two Factor Authentication" />
3237
33- < div className = "w-full max-w-md" >
34- < Card >
35- < CardHeader className = "space-y-1" >
36- < CardTitle className = "text-2xl" > Two Factor Authentication</ CardTitle >
37- < CardDescription >
38- Confirm access to your account by entering the authentication code provided by your authenticator application or one of your recovery codes.
39- </ CardDescription >
40- </ CardHeader >
41- < CardContent >
42- < Tabs defaultValue = "code" value = { activeTab } onValueChange = { setActiveTab } >
43- < TabsList className = "grid w-full grid-cols-2" >
44- < TabsTrigger value = "code" > Code</ TabsTrigger >
45- < TabsTrigger value = "recovery" > Recovery Code</ TabsTrigger >
46- </ TabsList >
47- < TabsContent value = "code" >
48- < form onSubmit = { submitCode } className = "space-y-4 pt-4" >
49- < div className = "grid gap-2" >
50- < Label htmlFor = "code" > Authentication Code</ Label >
51- < InputOTP
52- maxLength = { 6 }
53- value = { data . code }
54- onChange = { ( value ) => setData ( 'code' , value ) }
55- >
56- < InputOTPGroup >
57- { Array . from ( { length : 6 } ) . map ( ( _ , index ) => (
58- < InputOTPSlot key = { index } index = { index } />
59- ) ) }
60- </ InputOTPGroup >
61- </ InputOTP >
62- < InputError message = { errors . code } />
63- </ div >
64- < Button type = "submit" className = "w-full" disabled = { processing } >
65- Verify
66- </ Button >
67- </ form >
68- </ TabsContent >
69- < TabsContent value = "recovery" >
70- < form onSubmit = { submitRecoveryCode } className = "space-y-4 pt-4" >
71- < div className = "grid gap-2" >
72- < Label htmlFor = "recovery_code" > Recovery Code</ Label >
73- < Input
74- id = "recovery_code"
75- type = "text"
76- value = { data . recovery_code }
77- onChange = { ( e ) => setData ( 'recovery_code' , e . target . value ) }
78- className = "block w-full"
79- autoComplete = "one-time-code"
80- placeholder = "Enter recovery code"
81- />
82- < InputError message = { errors . recovery_code } />
83- </ div >
84- < Button type = "submit" className = "w-full" disabled = { processing } >
85- Verify
86- </ Button >
87- </ form >
88- </ TabsContent >
89- </ Tabs >
90- </ CardContent >
91- < CardFooter className = "flex flex-col" >
92- < p className = "mt-4 text-center text-sm text-gray-500" >
93- Lost your device? Please contact your administrator.
94- </ p >
95- </ CardFooter >
96- </ Card >
38+ { ! recovery ? (
39+ < form onSubmit = { submitCode } className = "space-y-4" >
40+ < div className = "flex flex-col items-center justify-center text-center" >
41+ < InputOTP
42+ maxLength = { 6 }
43+ value = { data . code }
44+ onChange = { ( value ) => setData ( 'code' , value ) }
45+ autoFocus
46+ >
47+ < InputOTPGroup >
48+ { Array . from ( { length : 6 } ) . map ( ( _ , index ) => (
49+ < InputOTPSlot key = { index } index = { index } />
50+ ) ) }
51+ </ InputOTPGroup >
52+ </ InputOTP >
53+ < InputError message = { errors . code } />
54+ </ div >
55+ < Button type = "submit" className = "w-full" disabled = { processing } >
56+ Continue
57+ </ Button >
58+ </ form >
59+ ) : (
60+ < form onSubmit = { submitRecoveryCode } className = "space-y-4" >
61+ < Input
62+ id = "recovery_code"
63+ type = "text"
64+ value = { data . recovery_code }
65+ onChange = { ( e ) => setData ( 'recovery_code' , e . target . value ) }
66+ className = "block w-full"
67+ autoComplete = "one-time-code"
68+ placeholder = "Enter recovery code"
69+ required
70+ />
71+ < InputError message = { errors . recovery_code } />
72+ < Button type = "submit" className = "w-full" disabled = { processing } >
73+ Continue
74+ </ Button >
75+ </ form >
76+ ) }
77+
78+ < div className = "space-x-0.5 text-center text-sm leading-5 text-muted-foreground" >
79+ < span className = "opacity-80" > or you can </ span >
80+ < button
81+ type = "button"
82+ className = "font-medium underline opacity-80 cursor-pointer bg-transparent border-0 p-0"
83+ onClick = { ( ) => setRecovery ( ! recovery ) }
84+ >
85+ { recovery
86+ ? 'login using an authentication code'
87+ : 'login using a recovery code' }
88+ </ button >
9789 </ div >
98- </ div >
90+ </ AuthLayout >
9991 ) ;
10092}
93+
0 commit comments