11import InputError from '@/components/input-error' ;
2+ import { Alert , AlertDescription , AlertTitle } from '@/components/ui/alert' ;
23import { Button } from '@/components/ui/button' ;
34import { Dialog , DialogContent , DialogDescription , DialogHeader , DialogTitle } from '@/components/ui/dialog' ;
45import { InputOTP , InputOTPGroup , InputOTPSlot } from '@/components/ui/input-otp' ;
@@ -7,7 +8,7 @@ import { OTP_MAX_LENGTH } from '@/hooks/use-two-factor-auth';
78import { confirm } from '@/routes/two-factor' ;
89import { Form } from '@inertiajs/react' ;
910import { REGEXP_ONLY_DIGITS } from 'input-otp' ;
10- import { Check , Copy , Loader2 , ScanLine } from 'lucide-react' ;
11+ import { AlertCircleIcon , Check , Copy , Loader2 , ScanLine } from 'lucide-react' ;
1112import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
1213
1314function GridScanIcon ( ) {
@@ -35,63 +36,79 @@ function TwoFactorSetupStep({
3536 manualSetupKey,
3637 buttonText,
3738 onNextStep,
39+ errors,
3840} : {
3941 qrCodeSvg : string | null ;
4042 manualSetupKey : string | null ;
4143 buttonText : string ;
4244 onNextStep : ( ) => void ;
45+ errors : string [ ] ;
4346} ) {
4447 const [ copiedText , copy ] = useClipboard ( ) ;
4548 const IconComponent = copiedText === manualSetupKey ? Check : Copy ;
4649
4750 return (
4851 < >
49- < div className = "mx-auto flex max-w-md overflow-hidden" >
50- < div className = "mx-auto aspect-square w-64 rounded-lg border border-border" >
51- { qrCodeSvg ? (
52- < div className = "z-10 p-5" >
53- < div className = "flex size-full items-center justify-center" dangerouslySetInnerHTML = { { __html : qrCodeSvg } } />
54- </ div >
55- ) : (
56- < div className = "absolute inset-0 z-10 flex animate-pulse items-center justify-center bg-background" >
57- < Loader2 className = "size-6 animate-spin" />
52+ { errors ?. length ? (
53+ < Alert variant = "destructive" >
54+ < AlertCircleIcon />
55+ < AlertTitle > Something went wrong.</ AlertTitle >
56+ < AlertDescription >
57+ < ul className = "list-inside list-disc text-sm" >
58+ { Array . from ( new Set ( errors ) ) . map ( ( error , index ) => (
59+ < li key = { index } > { error } </ li >
60+ ) ) }
61+ </ ul >
62+ </ AlertDescription >
63+ </ Alert >
64+ ) : (
65+ < >
66+ < div className = "mx-auto flex max-w-md overflow-hidden" >
67+ < div className = "mx-auto aspect-square w-64 rounded-lg border border-border" >
68+ < div className = "z-10 flex h-full w-full items-center justify-center p-5" >
69+ { qrCodeSvg ? (
70+ < div dangerouslySetInnerHTML = { { __html : qrCodeSvg } } />
71+ ) : (
72+ < Loader2 className = "flex size-4 animate-spin" />
73+ ) }
74+ </ div >
5875 </ div >
59- ) }
60- </ div >
61- </ div >
76+ </ div >
6277
63- < div className = "flex w-full space-x-5" >
64- < Button className = "w-full" onClick = { onNextStep } >
65- { buttonText }
66- </ Button >
67- </ div >
78+ < div className = "flex w-full space-x-5" >
79+ < Button className = "w-full" onClick = { onNextStep } >
80+ { buttonText }
81+ </ Button >
82+ </ div >
6883
69- < div className = "relative flex w-full items-center justify-center" >
70- < div className = "absolute inset-0 top-1/2 h-px w-full bg-border" />
71- < span className = "relative bg-card px-2 py-1" > or, enter the code manually</ span >
72- </ div >
84+ < div className = "relative flex w-full items-center justify-center" >
85+ < div className = "absolute inset-0 top-1/2 h-px w-full bg-border" />
86+ < span className = "relative bg-card px-2 py-1" > or, enter the code manually</ span >
87+ </ div >
7388
74- < div className = "flex w-full space-x-2" >
75- < div className = "flex w-full items-stretch overflow-hidden rounded-xl border border-border" >
76- { ! manualSetupKey ? (
77- < div className = "flex h-full w-full items-center justify-center bg-muted p-3" >
78- < Loader2 className = "size-4 animate-spin" />
89+ < div className = "flex w-full space-x-2" >
90+ < div className = "flex w-full items-stretch overflow-hidden rounded-xl border border-border" >
91+ { ! manualSetupKey ? (
92+ < div className = "flex h-full w-full items-center justify-center bg-muted p-3" >
93+ < Loader2 className = "size-4 animate-spin" />
94+ </ div >
95+ ) : (
96+ < >
97+ < input
98+ type = "text"
99+ readOnly
100+ value = { manualSetupKey }
101+ className = "h-full w-full bg-background p-3 text-foreground outline-none"
102+ />
103+ < button onClick = { ( ) => copy ( manualSetupKey ) } className = "border-l border-border px-3 hover:bg-muted" >
104+ < IconComponent className = "w-4" />
105+ </ button >
106+ </ >
107+ ) }
79108 </ div >
80- ) : (
81- < >
82- < input
83- type = "text"
84- readOnly
85- value = { manualSetupKey }
86- className = "h-full w-full bg-background p-3 text-foreground outline-none"
87- />
88- < button onClick = { ( ) => copy ( manualSetupKey ) } className = "border-l border-border px-3 hover:bg-muted" >
89- < IconComponent className = "w-4" />
90- </ button >
91- </ >
92- ) }
93- </ div >
94- </ div >
109+ </ div >
110+ </ >
111+ ) }
95112 </ >
96113 ) ;
97114}
@@ -153,6 +170,7 @@ interface TwoFactorSetupModalProps {
153170 manualSetupKey : string | null ;
154171 clearSetupData : ( ) => void ;
155172 fetchSetupData : ( ) => Promise < void > ;
173+ errors : string [ ] ;
156174}
157175
158176export default function TwoFactorSetupModal ( {
@@ -164,6 +182,7 @@ export default function TwoFactorSetupModal({
164182 manualSetupKey,
165183 clearSetupData,
166184 fetchSetupData,
185+ errors,
167186} : TwoFactorSetupModalProps ) {
168187 const [ showVerificationStep , setShowVerificationStep ] = useState < boolean > ( false ) ;
169188
@@ -239,6 +258,7 @@ export default function TwoFactorSetupModal({
239258 manualSetupKey = { manualSetupKey }
240259 buttonText = { modalConfig . buttonText }
241260 onNextStep = { handleModalNextStep }
261+ errors = { errors }
242262 />
243263 ) }
244264 </ div >
0 commit comments