1
1
import InputError from '@/components/input-error' ;
2
+ import { Alert , AlertDescription , AlertTitle } from '@/components/ui/alert' ;
2
3
import { Button } from '@/components/ui/button' ;
3
4
import { Dialog , DialogContent , DialogDescription , DialogHeader , DialogTitle } from '@/components/ui/dialog' ;
4
5
import { InputOTP , InputOTPGroup , InputOTPSlot } from '@/components/ui/input-otp' ;
@@ -7,7 +8,7 @@ import { OTP_MAX_LENGTH } from '@/hooks/use-two-factor-auth';
7
8
import { confirm } from '@/routes/two-factor' ;
8
9
import { Form } from '@inertiajs/react' ;
9
10
import { 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' ;
11
12
import { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
12
13
13
14
function GridScanIcon ( ) {
@@ -35,63 +36,79 @@ function TwoFactorSetupStep({
35
36
manualSetupKey,
36
37
buttonText,
37
38
onNextStep,
39
+ errors,
38
40
} : {
39
41
qrCodeSvg : string | null ;
40
42
manualSetupKey : string | null ;
41
43
buttonText : string ;
42
44
onNextStep : ( ) => void ;
45
+ errors : string [ ] ;
43
46
} ) {
44
47
const [ copiedText , copy ] = useClipboard ( ) ;
45
48
const IconComponent = copiedText === manualSetupKey ? Check : Copy ;
46
49
47
50
return (
48
51
< >
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 >
58
75
</ div >
59
- ) }
60
- </ div >
61
- </ div >
76
+ </ div >
62
77
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 >
68
83
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 >
73
88
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
+ ) }
79
108
</ 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
+ ) }
95
112
</ >
96
113
) ;
97
114
}
@@ -153,6 +170,7 @@ interface TwoFactorSetupModalProps {
153
170
manualSetupKey : string | null ;
154
171
clearSetupData : ( ) => void ;
155
172
fetchSetupData : ( ) => Promise < void > ;
173
+ errors : string [ ] ;
156
174
}
157
175
158
176
export default function TwoFactorSetupModal ( {
@@ -164,6 +182,7 @@ export default function TwoFactorSetupModal({
164
182
manualSetupKey,
165
183
clearSetupData,
166
184
fetchSetupData,
185
+ errors,
167
186
} : TwoFactorSetupModalProps ) {
168
187
const [ showVerificationStep , setShowVerificationStep ] = useState < boolean > ( false ) ;
169
188
@@ -239,6 +258,7 @@ export default function TwoFactorSetupModal({
239
258
manualSetupKey = { manualSetupKey }
240
259
buttonText = { modalConfig . buttonText }
241
260
onNextStep = { handleModalNextStep }
261
+ errors = { errors }
242
262
/>
243
263
) }
244
264
</ div >
0 commit comments