'use client'; import { useState, useEffect } from 'react'; import { Box, Typography, Card, CardContent, Button, Dialog, DialogTitle, DialogContent, DialogActions, Alert, CircularProgress, List, ListItem, ListItemText, ListItemSecondaryAction, IconButton, Divider, TextField, Chip, } from '@mui/material'; import { Fingerprint, Add, Delete, Edit, CheckCircle, Warning, } from '@mui/icons-material'; import { biometricApi, type BiometricCredential } from '@/lib/api/biometric'; import { startRegistration } from '@simplewebauthn/browser'; import { motion } from 'framer-motion'; import { formatDistanceToNow } from 'date-fns'; export function BiometricSettings() { const [credentials, setCredentials] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [successMessage, setSuccessMessage] = useState(null); const [isSupported, setIsSupported] = useState(false); const [isPlatformAvailable, setIsPlatformAvailable] = useState(false); // Add credential dialog const [addDialogOpen, setAddDialogOpen] = useState(false); const [isAdding, setIsAdding] = useState(false); const [credentialName, setCredentialName] = useState(''); // Edit credential dialog const [editDialogOpen, setEditDialogOpen] = useState(false); const [credentialToEdit, setCredentialToEdit] = useState(null); const [editName, setEditName] = useState(''); const [isEditing, setIsEditing] = useState(false); // Delete credential dialog const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [credentialToDelete, setCredentialToDelete] = useState(null); const [isDeleting, setIsDeleting] = useState(false); useEffect(() => { checkSupport(); loadCredentials(); }, []); const checkSupport = async () => { const supported = biometricApi.isSupported(); setIsSupported(supported); if (supported) { const available = await biometricApi.isPlatformAuthenticatorAvailable(); setIsPlatformAvailable(available); } }; const loadCredentials = async () => { try { setIsLoading(true); const response = await biometricApi.getCredentials(); setCredentials(response.credentials); setError(null); } catch (err: any) { console.error('Failed to load biometric credentials:', err); setError('Failed to load biometric credentials'); } finally { setIsLoading(false); } }; const handleAddCredential = async () => { try { setIsAdding(true); setError(null); // Get registration options from server const options = await biometricApi.getRegistrationOptions(credentialName || undefined); // Start WebAuthn registration (triggers Face ID/Touch ID/Windows Hello) const registrationResponse = await startRegistration(options); // Send response to server for verification await biometricApi.verifyRegistration(registrationResponse, credentialName || undefined); setSuccessMessage('Biometric credential added successfully!'); setAddDialogOpen(false); setCredentialName(''); await loadCredentials(); } catch (err: any) { console.error('Failed to add biometric credential:', err); if (err.name === 'NotAllowedError') { setError('Biometric authentication was cancelled'); } else if (err.name === 'NotSupportedError') { setError('Biometric authentication is not supported on this device'); } else { setError(err.response?.data?.message || err.message || 'Failed to add biometric credential'); } } finally { setIsAdding(false); } }; const handleEditCredential = async () => { if (!credentialToEdit || !editName.trim()) return; try { setIsEditing(true); await biometricApi.updateCredentialName(credentialToEdit.id, editName.trim()); setSuccessMessage('Credential name updated successfully'); setEditDialogOpen(false); setCredentialToEdit(null); setEditName(''); await loadCredentials(); } catch (err: any) { console.error('Failed to update credential name:', err); setError(err.response?.data?.message || 'Failed to update credential name'); } finally { setIsEditing(false); } }; const handleDeleteCredential = async () => { if (!credentialToDelete) return; try { setIsDeleting(true); await biometricApi.deleteCredential(credentialToDelete.id); setSuccessMessage('Biometric credential removed successfully'); setDeleteDialogOpen(false); setCredentialToDelete(null); await loadCredentials(); } catch (err: any) { console.error('Failed to delete credential:', err); setError(err.response?.data?.message || 'Failed to delete credential'); } finally { setIsDeleting(false); } }; if (isLoading) { return ( ); } if (!isSupported) { return ( Biometric Authentication Biometric authentication is not supported in your browser. Please use a modern browser like Chrome, Safari, or Edge. ); } if (!isPlatformAvailable) { return ( Biometric Authentication }> No biometric authenticator found on this device. To use Face ID, Touch ID, or Windows Hello, please ensure your device has biometric hardware enabled. ); } return ( <> Biometric Authentication Use Face ID, Touch ID, Windows Hello, or other biometric methods to sign in securely without a password. {error && ( setError(null)}> {error} )} {successMessage && ( setSuccessMessage(null)}> {successMessage} )} {credentials.length === 0 ? ( No biometric credentials enrolled. Add one to enable biometric sign-in. ) : ( {credentials.map((credential, index) => ( {index > 0 && } {credential.friendlyName || 'Unnamed Credential'} {credential.backedUp && ( } /> )} } secondary={ Device Type: {credential.deviceType || 'Unknown'} Added: {formatDistanceToNow(new Date(credential.createdAt))} ago {credential.lastUsed && ( Last used: {formatDistanceToNow(new Date(credential.lastUsed))} ago )} } /> { setCredentialToEdit(credential); setEditName(credential.friendlyName || ''); setEditDialogOpen(true); }} > { setCredentialToDelete(credential); setDeleteDialogOpen(true); }} color="error" > ))} )} {/* Add Credential Dialog */} !isAdding && setAddDialogOpen(false)} maxWidth="sm" fullWidth> Add Biometric Credential You'll be prompted to use your device's biometric authentication (Face ID, Touch ID, Windows Hello, etc.) to create a new credential. setCredentialName(e.target.value)} fullWidth disabled={isAdding} placeholder="e.g., My Laptop, iPhone 15" helperText="Give this credential a friendly name to identify it later" /> {/* Edit Credential Dialog */} !isEditing && setEditDialogOpen(false)} maxWidth="sm" fullWidth > Edit Credential Name setEditName(e.target.value)} fullWidth disabled={isEditing} autoFocus sx={{ mt: 1 }} /> {/* Delete Credential Dialog */} !isDeleting && setDeleteDialogOpen(false)} maxWidth="sm" fullWidth > Remove Biometric Credential? This will remove the biometric credential from your account. You won't be able to use it to sign in. {credentialToDelete && ( Credential: {credentialToDelete.friendlyName || 'Unnamed Credential'} )} ); }