11'use client' ;
22
3- import { useState , useEffect } from 'react' ;
43import { Card , CardContent , CardDescription , CardHeader , CardTitle } from '@/components/ui/card' ;
5- import { Button } from '@/components/ui/button' ;
64import { Alert , AlertDescription } from '@/components/ui/alert' ;
7- import { useToast } from '@/hooks/use-toast' ;
85import { apiClient } from '@/lib/api/client' ;
96import { TestnetBadge } from '@/components/TestnetBadge' ;
107import { Label } from '@/components/ui/label' ;
@@ -14,108 +11,6 @@ export default function SettingsPage() {
1411 const isTestnet = apiClient . isTestnetMode ( ) ;
1512 const usdTestAssetCode = process . env . NEXT_PUBLIC_USD_TEST_ASSET_CODE || 'USDTEST' ;
1613
17- const getAuthToken = ( ) : string | null => {
18- if ( typeof window === 'undefined' ) return null ;
19- return localStorage . getItem ( 'auth_token' ) || localStorage . getItem ( 'pi_access_token' ) ;
20- } ;
21-
22- const loadPasskeys = async ( ) => {
23- setIsLoading ( true ) ;
24- setError ( null ) ;
25- try {
26- const token = getAuthToken ( ) ;
27- if ( ! token ) {
28- setError ( 'Not authenticated' ) ;
29- setIsLoading ( false ) ;
30- return ;
31- }
32-
33- const response = await fetch ( '/api/auth/passkeys' , {
34- method : 'GET' ,
35- headers : {
36- 'Authorization' : `Bearer ${ token } ` ,
37- 'Content-Type' : 'application/json' ,
38- } ,
39- } ) ;
40-
41- const data = await response . json ( ) ;
42-
43- if ( response . ok && data . success && data . data ) {
44- setPasskeys ( data . data . passkeys || [ ] ) ;
45- } else {
46- setError ( data . error || 'Failed to load passkeys' ) ;
47- }
48- } catch ( err ) {
49- setError ( err instanceof Error ? err . message : 'Failed to load passkeys' ) ;
50- } finally {
51- setIsLoading ( false ) ;
52- }
53- } ;
54-
55- const handleDeletePasskey = async ( credentialId : string ) => {
56- if ( passkeys . length <= 1 ) {
57- toast ( {
58- title : 'Cannot Delete' ,
59- description : 'You must have at least one passkey. Please register another passkey first.' ,
60- variant : 'destructive' ,
61- } ) ;
62- return ;
63- }
64-
65- if ( ! confirm ( 'Are you sure you want to delete this passkey? You will not be able to use this device to log in anymore.' ) ) {
66- return ;
67- }
68-
69- setDeletingId ( credentialId ) ;
70- try {
71- const token = getAuthToken ( ) ;
72- if ( ! token ) {
73- toast ( {
74- title : 'Delete Failed' ,
75- description : 'Not authenticated' ,
76- variant : 'destructive' ,
77- } ) ;
78- return ;
79- }
80-
81- const response = await fetch ( `/api/auth/passkeys/${ credentialId } ` , {
82- method : 'DELETE' ,
83- headers : {
84- 'Authorization' : `Bearer ${ token } ` ,
85- 'Content-Type' : 'application/json' ,
86- } ,
87- } ) ;
88-
89- const data = await response . json ( ) ;
90-
91- if ( response . ok && data . success ) {
92- toast ( {
93- title : 'Passkey Deleted' ,
94- description : 'The passkey has been successfully deleted.' ,
95- } ) ;
96- await loadPasskeys ( ) ;
97- } else {
98- toast ( {
99- title : 'Delete Failed' ,
100- description : data . error || 'Failed to delete passkey' ,
101- variant : 'destructive' ,
102- } ) ;
103- }
104- } catch ( err ) {
105- toast ( {
106- title : 'Delete Failed' ,
107- description : err instanceof Error ? err . message : 'Failed to delete passkey' ,
108- variant : 'destructive' ,
109- } ) ;
110- } finally {
111- setDeletingId ( null ) ;
112- }
113- } ;
114-
115- useEffect ( ( ) => {
116- loadPasskeys ( ) ;
117- // eslint-disable-next-line react-hooks/exhaustive-deps
118- } , [ ] ) ;
11914
12015 return (
12116 < div className = "container mx-auto py-4 sm:py-8 px-4 max-w-4xl" >
@@ -124,99 +19,6 @@ export default function SettingsPage() {
12419 < p className = "text-sm sm:text-base text-muted-foreground" > Manage your account settings and security</ p >
12520 </ div >
12621
127- < Card className = "border-border/50 bg-card/50 backdrop-blur-sm" >
128- < CardHeader >
129- < div className = "flex items-center gap-2" >
130- < Shield className = "h-5 w-5" />
131- < CardTitle > Passkeys</ CardTitle >
132- </ div >
133- < CardDescription >
134- Manage your passkeys for different devices. Each device can have its own passkey for secure authentication.
135- </ CardDescription >
136- </ CardHeader >
137- < CardContent >
138- { error && (
139- < Alert variant = "destructive" className = "mb-4" >
140- < AlertCircle className = "h-4 w-4" />
141- < AlertDescription > { error } </ AlertDescription >
142- </ Alert >
143- ) }
144-
145- { isLoading ? (
146- < div className = "flex items-center justify-center py-8" >
147- < Loader2 className = "h-6 w-6 animate-spin text-muted-foreground" />
148- </ div >
149- ) : passkeys . length === 0 ? (
150- < Alert >
151- < AlertCircle className = "h-4 w-4" />
152- < AlertDescription > No passkeys found. This should not happen. Please contact support.</ AlertDescription >
153- </ Alert >
154- ) : (
155- < div className = "space-y-4" >
156- { passkeys . map ( ( passkey ) => (
157- < div
158- key = { passkey . id }
159- className = "flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 transition-colors"
160- >
161- < div className = "flex items-start gap-4 flex-1" >
162- < div className = "p-2 bg-primary/10 rounded-lg" >
163- < Monitor className = "h-5 w-5 text-primary" />
164- </ div >
165- < div className = "flex-1" >
166- < div className = "font-medium mb-1" >
167- { passkey . deviceName || 'Unknown Device' }
168- </ div >
169- < div className = "text-sm text-muted-foreground space-y-1" >
170- < div className = "flex items-center gap-2" >
171- < Calendar className = "h-3 w-3" />
172- < span > Created: { formatDate ( passkey . createdAt ) } </ span >
173- </ div >
174- { passkey . lastUsedAt && (
175- < div className = "flex items-center gap-2" >
176- < Calendar className = "h-3 w-3" />
177- < span > Last used: { formatDate ( passkey . lastUsedAt ) } </ span >
178- </ div >
179- ) }
180- </ div >
181- < div className = "text-xs text-muted-foreground mt-2 font-mono" >
182- ID: { passkey . credentialId . substring ( 0 , 20 ) } ...
183- </ div >
184- </ div >
185- </ div >
186- < Button
187- variant = "destructive"
188- size = "sm"
189- onClick = { ( ) => handleDeletePasskey ( passkey . credentialId ) }
190- disabled = { deletingId === passkey . credentialId || passkeys . length <= 1 }
191- >
192- { deletingId === passkey . credentialId ? (
193- < >
194- < Loader2 className = "h-4 w-4 mr-2 animate-spin" />
195- Deleting...
196- </ >
197- ) : (
198- < >
199- < Trash2 className = "h-4 w-4 mr-2" />
200- Delete
201- </ >
202- ) }
203- </ Button >
204- </ div >
205- ) ) }
206-
207- { passkeys . length <= 1 && (
208- < Alert >
209- < AlertCircle className = "h-4 w-4" />
210- < AlertDescription >
211- You have only one passkey. Register another passkey on a different device before deleting this one.
212- </ AlertDescription >
213- </ Alert >
214- ) }
215- </ div >
216- ) }
217- </ CardContent >
218- </ Card >
219-
22022 { /* Testnet Configuration Section */ }
22123 { isTestnet && (
22224 < Card className = "mt-4 sm:mt-6 border-border/50 bg-card/50 backdrop-blur-sm" >
0 commit comments