@@ -24,9 +24,15 @@ import { Trash } from 'lucide-react';
24
24
import { format } from 'date-fns' ;
25
25
import useLoading from '@/src/hooks/use-loading' ;
26
26
import { startRegistration } from '@simplewebauthn/browser' ;
27
+ import { useRouter } from 'next/navigation' ;
28
+ import { DeleteAllSessions } from './_components/session-modals' ;
29
+ import { useQueryClient } from '@tanstack/react-query' ;
27
30
// import { PasskeyNameModal } from './_components/passkey-modals';
28
31
29
32
export default function Page ( ) {
33
+ const queryClient = useQueryClient ( ) ;
34
+ const router = useRouter ( ) ;
35
+
30
36
const {
31
37
data : initData ,
32
38
isLoading : isInitDataLoading ,
@@ -82,6 +88,9 @@ export default function Page() {
82
88
}
83
89
) ;
84
90
91
+ const [ DeleteAllSessionsModalRoot , openDeleteAllSessionsModal ] =
92
+ useAwaitableModal ( DeleteAllSessions , { verificationToken : '' } ) ;
93
+
85
94
// const [PasskeyNameModalRoot, openPasskeyNameModal] = useAwaitableModal(
86
95
// PasskeyNameModal,
87
96
// {}
@@ -122,6 +131,9 @@ export default function Page() {
122
131
}
123
132
) ;
124
133
134
+ const { mutateAsync : logoutSingle } =
135
+ api . account . security . deleteSession . useMutation ( ) ;
136
+
125
137
async function waitForVerification ( ) {
126
138
if ( ! initData ) throw new Error ( 'No init data' ) ;
127
139
if ( verificationToken ) return verificationToken ;
@@ -310,6 +322,59 @@ export default function Page() {
310
322
Add New Passkey
311
323
</ Button >
312
324
</ div >
325
+
326
+ < div className = "flex flex-col gap-3" >
327
+ < span className = "text-lg font-bold" > Sessions</ span >
328
+ < div className = "flex flex-wrap gap-2" >
329
+ { initData ?. sessions . map ( ( session ) => (
330
+ < div
331
+ key = { session . publicId }
332
+ className = "bg-muted flex items-center justify-center gap-2 rounded border px-2 py-1" >
333
+ < div className = "flex flex-col" >
334
+ < span >
335
+ { session . device } - { session . os }
336
+ </ span >
337
+ < span className = "text-muted-foreground text-xs" >
338
+ { format ( session . createdAt , ' HH:mm, do MMM yyyy' ) }
339
+ </ span >
340
+ </ div >
341
+ < div >
342
+ < IconButton
343
+ size = "2"
344
+ variant = "soft"
345
+ onClick = { async ( ) => {
346
+ const token = await waitForVerification ( ) ;
347
+ if ( ! token ) return ;
348
+ await logoutSingle ( {
349
+ sessionPublicId : session . publicId ,
350
+ verificationToken : verificationToken ?? token
351
+ } )
352
+ . then ( ( ) => refreshSecurityData ( ) )
353
+ . catch ( ( ) => null ) ;
354
+ } } >
355
+ < Trash size = { 16 } />
356
+ </ IconButton >
357
+ </ div >
358
+ </ div >
359
+ ) ) }
360
+ </ div >
361
+ < Button
362
+ className = "w-fit"
363
+ onClick = { async ( ) => {
364
+ const token = await waitForVerification ( ) ;
365
+ if ( ! token ) return ;
366
+ await openDeleteAllSessionsModal ( {
367
+ verificationToken : verificationToken ?? token
368
+ } )
369
+ . then ( ( ) => {
370
+ queryClient . removeQueries ( ) ;
371
+ router . replace ( '/' ) ;
372
+ } )
373
+ . catch ( ( ) => null ) ;
374
+ } } >
375
+ Logout of All Sessions
376
+ </ Button >
377
+ </ div >
313
378
</ div >
314
379
) }
315
380
@@ -319,6 +384,7 @@ export default function Page() {
319
384
< RecoveryModalRoot />
320
385
< DeletePasskeyModalRoot />
321
386
{ /* <PasskeyNameModalRoot /> */ }
387
+ < DeleteAllSessionsModalRoot />
322
388
</ Flex >
323
389
) ;
324
390
}
0 commit comments