| import { useState } from 'react' | |
| import { apiFetch } from '../api' | |
| import { useAuth } from '../context/AuthContext' | |
| import { useHideCaretWhileTyping } from '../hooks/useHideCaretWhileTyping' | |
| type Props = { onClose: () => void } | |
| export function AccountModal({ onClose }: Props) { | |
| const { onFormTextInput, onTextareaBlur } = useHideCaretWhileTyping() | |
| const { user, refreshUser, signOut } = useAuth() | |
| const [firstName, setFirstName] = useState(user?.firstName || '') | |
| const [lastName, setLastName] = useState(user?.lastName || '') | |
| const [email, setEmail] = useState(user?.email || '') | |
| const [password, setPassword] = useState('') | |
| const [err, setErr] = useState('') | |
| const save = async () => { | |
| setErr('') | |
| const body: Record<string, string> = { firstName, lastName, email } | |
| if (password.trim()) body.password = password | |
| const r = await apiFetch('/api/auth/me', { | |
| method: 'PATCH', | |
| body: JSON.stringify(body), | |
| }) | |
| if (!r.ok) { | |
| setErr((await r.json().catch(() => ({}))).detail || 'Update failed') | |
| return | |
| } | |
| await refreshUser() | |
| onClose() | |
| } | |
| const deleteAccount = async () => { | |
| if (!confirm('Delete your account and server-stored profile? This cannot be undone.')) return | |
| const r = await apiFetch('/api/auth/me', { method: 'DELETE' }) | |
| if (r.ok) { | |
| signOut() | |
| onClose() | |
| } | |
| } | |
| return ( | |
| <div className="modal-overlay" role="dialog" aria-modal> | |
| <div className="modal-box"> | |
| <h2>Account</h2> | |
| {err && <p style={{ color: '#b91c1c', fontSize: '0.9rem' }}>{err}</p>} | |
| <div className="field"> | |
| <label>First name</label> | |
| <input | |
| value={firstName} | |
| onChange={(e) => setFirstName(e.target.value)} | |
| onInput={onFormTextInput} | |
| onBlur={onTextareaBlur} | |
| /> | |
| </div> | |
| <div className="field"> | |
| <label>Last name</label> | |
| <input | |
| value={lastName} | |
| onChange={(e) => setLastName(e.target.value)} | |
| onInput={onFormTextInput} | |
| onBlur={onTextareaBlur} | |
| /> | |
| </div> | |
| <div className="field"> | |
| <label>Email</label> | |
| <input | |
| type="email" | |
| value={email} | |
| onChange={(e) => setEmail(e.target.value)} | |
| onInput={onFormTextInput} | |
| onBlur={onTextareaBlur} | |
| /> | |
| </div> | |
| <div className="field"> | |
| <label>New password (optional)</label> | |
| <input | |
| type="password" | |
| autoComplete="new-password" | |
| value={password} | |
| onChange={(e) => setPassword(e.target.value)} | |
| onInput={onFormTextInput} | |
| onBlur={onTextareaBlur} | |
| placeholder="Leave blank to keep current" | |
| /> | |
| </div> | |
| <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'space-between', flexWrap: 'wrap' }}> | |
| <button type="button" className="btn btn-secondary" onClick={deleteAccount}> | |
| Delete account | |
| </button> | |
| <div style={{ display: 'flex', gap: '0.5rem' }}> | |
| <button type="button" className="btn btn-secondary" onClick={onClose}> | |
| Cancel | |
| </button> | |
| <button type="button" className="btn btn-primary" onClick={save}> | |
| Save | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ) | |
| } | |