@@ -4,7 +4,11 @@ import { Flex, Heading, Spinner, Text, Switch, Button } from '@radix-ui/themes';
4
4
import { useEffect , useState } from 'react' ;
5
5
import { api } from '@/src/lib/trpc' ;
6
6
import { VerificationModal } from './_components/verification-modal' ;
7
- import { PasswordModal , TOTPModal } from './_components/reset-modals' ;
7
+ import {
8
+ PasswordModal ,
9
+ TOTPModal ,
10
+ RecoveryCodeModal
11
+ } from './_components/reset-modals' ;
8
12
import useAwaitableModal from '@/src/hooks/use-awaitable-modal' ;
9
13
import { toast } from 'sonner' ;
10
14
@@ -47,6 +51,14 @@ export default function Page() {
47
51
verificationToken : ''
48
52
} ) ;
49
53
54
+ const [ RecoveryModalRoot , openRecoveryModal ] = useAwaitableModal (
55
+ RecoveryCodeModal ,
56
+ {
57
+ verificationToken : '' ,
58
+ mode : 'reset'
59
+ }
60
+ ) ;
61
+
50
62
async function waitForVerification ( ) {
51
63
if ( ! initData ) throw new Error ( 'No init data' ) ;
52
64
if ( verificationToken ) return verificationToken ;
@@ -93,85 +105,108 @@ export default function Page() {
93
105
) }
94
106
95
107
{ ! isInitDataLoading && initData && (
96
- < Flex
97
- className = "my-4"
98
- direction = "column"
99
- gap = "5" >
100
- < Text
101
- as = "label"
102
- size = "3"
103
- weight = "medium" >
104
- < Flex
105
- gap = "2"
106
- align = "center" >
107
- Enable Password and 2FA Login
108
- < Switch
109
- size = "2"
110
- checked = { isPassword2FaEnabled }
111
- disabled = {
112
- isDisablingLegacySecurity ||
113
- ! ( isPassword2FaEnabled && canDisableLegacySecurity )
114
- }
115
- onCheckedChange = { async ( ) => {
116
- const token = await waitForVerification ( ) ;
117
- if ( ! token ) return ;
118
-
119
- if ( isPassword2FaEnabled ) {
120
- disableLegacySecurity ( {
121
- verificationToken : verificationToken ?? token
122
- } ) ;
123
- await refreshSecurityData ( ) ;
124
- setIsPassword2FaEnabled ( false ) ;
125
- } else {
126
- const passwordSet = await openPasswordModal ( {
108
+ < div className = "my-4 flex flex-col gap-5" >
109
+ < div className = "flex flex-col gap-3" >
110
+ < span className = "text-lg font-bold" > Legacy Security</ span >
111
+ < Text
112
+ as = "label"
113
+ size = "3"
114
+ weight = "medium" >
115
+ < Flex
116
+ gap = "2"
117
+ align = "center" >
118
+ Enable Password and 2FA Login
119
+ < Switch
120
+ size = "2"
121
+ checked = { isPassword2FaEnabled }
122
+ disabled = {
123
+ isDisablingLegacySecurity ||
124
+ ! ( isPassword2FaEnabled && canDisableLegacySecurity )
125
+ }
126
+ onCheckedChange = { async ( ) => {
127
+ const token = await waitForVerification ( ) ;
128
+ if ( ! token ) return ;
129
+
130
+ if ( isPassword2FaEnabled ) {
131
+ disableLegacySecurity ( {
132
+ verificationToken : verificationToken ?? token
133
+ } ) ;
134
+ await refreshSecurityData ( ) ;
135
+ setIsPassword2FaEnabled ( false ) ;
136
+ } else {
137
+ const passwordSet = await openPasswordModal ( {
138
+ verificationToken : verificationToken ?? token
139
+ } ) . catch ( ( ) => false ) ;
140
+ const otpSet = await openTOTPModal ( {
141
+ verificationToken : verificationToken ?? token
142
+ } ) . catch ( ( ) => false ) ;
143
+ if ( ! passwordSet || ! otpSet ) return ;
144
+ await refreshSecurityData ( ) ;
145
+ setIsPassword2FaEnabled ( true ) ;
146
+ }
147
+ } }
148
+ />
149
+ { isDisablingLegacySecurity && < Spinner loading /> }
150
+ </ Flex >
151
+ </ Text >
152
+
153
+ < div className = "flex gap-2" >
154
+ { initData ?. passwordSet && (
155
+ < Button
156
+ onClick = { async ( ) => {
157
+ const token = await waitForVerification ( ) ;
158
+ if ( ! token ) return ;
159
+ await openPasswordModal ( {
127
160
verificationToken : verificationToken ?? token
128
- } ) . catch ( ( ) => false ) ;
129
- const otpSet = await openTOTPModal ( {
161
+ } ) . catch ( ( ) => null ) ;
162
+ } } >
163
+ Reset Password
164
+ </ Button >
165
+ ) }
166
+
167
+ { initData ?. twoFactorEnabled && (
168
+ < Button
169
+ onClick = { async ( ) => {
170
+ const token = await waitForVerification ( ) ;
171
+ if ( ! token ) return ;
172
+ await openTOTPModal ( {
130
173
verificationToken : verificationToken ?? token
131
- } ) . catch ( ( ) => false ) ;
132
- if ( ! passwordSet || ! otpSet ) return ;
133
- await refreshSecurityData ( ) ;
134
- setIsPassword2FaEnabled ( true ) ;
135
- }
136
- } }
137
- />
138
- { isDisablingLegacySecurity && < Spinner loading /> }
139
- </ Flex >
140
- </ Text >
141
-
142
- < div className = "flex gap-2" >
143
- { initData ?. passwordSet && (
144
- < Button
145
- onClick = { async ( ) => {
146
- const token = await waitForVerification ( ) ;
147
- if ( ! token ) return ;
148
- await openPasswordModal ( {
149
- verificationToken : verificationToken ?? token
150
- } ) . catch ( ( ) => null ) ;
151
- } } >
152
- Reset Password
153
- </ Button >
154
- ) }
155
-
156
- { initData ?. twoFactorEnabled && (
174
+ } ) . catch ( ( ) => null ) ;
175
+ } } >
176
+ Reset 2FA
177
+ </ Button >
178
+ ) }
179
+ </ div >
180
+ </ div >
181
+ { ( initData . recoveryCodeSet || isPassword2FaEnabled ) && (
182
+ < div className = "flex flex-col gap-3" >
183
+ < span className = "text-lg font-bold" > Account Recovery</ span >
157
184
< Button
185
+ className = "w-fit"
158
186
onClick = { async ( ) => {
159
187
const token = await waitForVerification ( ) ;
160
188
if ( ! token ) return ;
161
- await openTOTPModal ( {
162
- verificationToken : verificationToken ?? token
189
+ await openRecoveryModal ( {
190
+ verificationToken : verificationToken ?? token ,
191
+ mode : isPassword2FaEnabled ? 'reset' : 'disable'
163
192
} ) . catch ( ( ) => null ) ;
193
+ await refreshSecurityData ( ) ;
164
194
} } >
165
- Reset 2FA
195
+ { initData . recoveryCodeSet
196
+ ? isPassword2FaEnabled
197
+ ? 'Reset Recovery Code'
198
+ : 'Disable Recovery Code'
199
+ : 'Setup Recovery' }
166
200
</ Button >
167
- ) }
168
- </ div >
169
- </ Flex >
201
+ </ div >
202
+ ) }
203
+ </ div >
170
204
) }
171
205
172
206
< VerificationModalRoot />
173
207
< PasswordModalRoot />
174
208
< TOTPModalRoot />
209
+ < RecoveryModalRoot />
175
210
</ Flex >
176
211
) ;
177
212
}
0 commit comments