diff --git a/apps/web/src/app/[orgShortCode]/settings/user/security/_components/session-modals.tsx b/apps/web/src/app/[orgShortCode]/settings/user/security/_components/session-modals.tsx new file mode 100644 index 00000000..8e0f66c0 --- /dev/null +++ b/apps/web/src/app/[orgShortCode]/settings/user/security/_components/session-modals.tsx @@ -0,0 +1,47 @@ +import { type ModalComponent } from '@/src/hooks/use-awaitable-modal'; +import { api } from '@/src/lib/trpc'; +import { Button, Dialog } from '@radix-ui/themes'; + +export function DeleteAllSessions({ + open, + onResolve, + onClose, + verificationToken +}: ModalComponent<{ verificationToken: string }>) { + const { + mutateAsync: logoutAll, + isLoading: loggingOut, + error + } = api.account.security.deleteAllSessions.useMutation(); + return ( + + + + Delete All Sessions + + + This will log you out of all devices and sessions including the + current device. Are you sure you want to continue? + +
{error?.message}
+
+ + +
+
+
+ ); +} diff --git a/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx b/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx index 33dfde05..8e9936e0 100644 --- a/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx +++ b/apps/web/src/app/[orgShortCode]/settings/user/security/page.tsx @@ -24,9 +24,15 @@ import { Trash } from 'lucide-react'; import { format } from 'date-fns'; import useLoading from '@/src/hooks/use-loading'; import { startRegistration } from '@simplewebauthn/browser'; +import { useRouter } from 'next/navigation'; +import { DeleteAllSessions } from './_components/session-modals'; +import { useQueryClient } from '@tanstack/react-query'; // import { PasskeyNameModal } from './_components/passkey-modals'; export default function Page() { + const queryClient = useQueryClient(); + const router = useRouter(); + const { data: initData, isLoading: isInitDataLoading, @@ -82,6 +88,9 @@ export default function Page() { } ); + const [DeleteAllSessionsModalRoot, openDeleteAllSessionsModal] = + useAwaitableModal(DeleteAllSessions, { verificationToken: '' }); + // const [PasskeyNameModalRoot, openPasskeyNameModal] = useAwaitableModal( // PasskeyNameModal, // {} @@ -122,6 +131,9 @@ export default function Page() { } ); + const { mutateAsync: logoutSingle } = + api.account.security.deleteSession.useMutation(); + async function waitForVerification() { if (!initData) throw new Error('No init data'); if (verificationToken) return verificationToken; @@ -310,6 +322,59 @@ export default function Page() { Add New Passkey + +
+ Sessions +
+ {initData?.sessions.map((session) => ( +
+
+ + {session.device} - {session.os} + + + {format(session.createdAt, ' HH:mm, do MMM yyyy')} + +
+
+ { + const token = await waitForVerification(); + if (!token) return; + await logoutSingle({ + sessionPublicId: session.publicId, + verificationToken: verificationToken ?? token + }) + .then(() => refreshSecurityData()) + .catch(() => null); + }}> + + +
+
+ ))} +
+ +
)} @@ -319,6 +384,7 @@ export default function Page() { {/* */} + ); }