import { useState } from 'react'; import { Card, Title, Text, Stack, Group, Button, TextInput, Table, Badge, ActionIcon, Tooltip, Alert, } from '@mantine/core'; import { IconFingerprint, IconTrash, IconPlus, IconAlertCircle } from '@tabler/icons-react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { notifications } from '@mantine/notifications'; import { startRegistration } from '@simplewebauthn/browser'; import api from '../../services/api'; export function PasskeySettings() { const queryClient = useQueryClient(); const [deviceName, setDeviceName] = useState(''); const [registering, setRegistering] = useState(false); const { data: passkeys = [], isLoading } = useQuery({ queryKey: ['passkeys'], queryFn: async () => { const { data } = await api.get('/auth/passkeys'); return data; }, }); const removeMutation = useMutation({ mutationFn: (id: string) => api.delete(`/auth/passkeys/${id}`), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['passkeys'] }); notifications.show({ message: 'Passkey removed', color: 'orange' }); }, onError: (err: any) => notifications.show({ message: err.response?.data?.message || 'Failed to remove', color: 'red' }), }); const handleRegister = async () => { setRegistering(true); try { // 1. Get registration options from server const { data: options } = await api.post('/auth/passkeys/register-options'); // 2. Create credential via browser WebAuthn API const credential = await startRegistration({ optionsJSON: options }); // 3. Send attestation to server for verification await api.post('/auth/passkeys/register', { response: credential, deviceName: deviceName || 'My Passkey', }); queryClient.invalidateQueries({ queryKey: ['passkeys'] }); setDeviceName(''); notifications.show({ message: 'Passkey registered successfully', color: 'green' }); } catch (err: any) { if (err.name === 'NotAllowedError') { notifications.show({ message: 'Registration was cancelled', color: 'yellow' }); } else { notifications.show({ message: err.response?.data?.message || err.message || 'Registration failed', color: 'red' }); } } finally { setRegistering(false); } }; const webauthnSupported = typeof window !== 'undefined' && !!window.PublicKeyCredential; return (
Passkeys Sign in with your fingerprint, face, or security key
0 ? 'green' : 'gray'} variant="light" size="lg"> {passkeys.length} registered
{!webauthnSupported && ( } mb="md"> Your browser doesn't support WebAuthn passkeys. )} {passkeys.length > 0 && ( Device Created Last Used Actions {passkeys.map((pk: any) => ( {pk.device_name || 'Passkey'} {new Date(pk.created_at).toLocaleDateString()} {pk.last_used_at ? new Date(pk.last_used_at).toLocaleDateString() : 'Never'} removeMutation.mutate(pk.id)}> ))}
)} {webauthnSupported && ( setDeviceName(e.currentTarget.value)} style={{ flex: 1 }} /> )}
); }