Initial commit
This commit is contained in:
92
src/pages/ProfilePage.tsx
Normal file
92
src/pages/ProfilePage.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import { AtSign, KeyRound, Save, ShieldCheck, UserRound } from 'lucide-react'
|
||||
import { FormEvent, useState } from 'react'
|
||||
import { Spinner } from '../components/ui'
|
||||
import { useAuth } from '../context/AuthContext'
|
||||
import { useToast } from '../context/ToastContext'
|
||||
import { api } from '../lib/api'
|
||||
import { displayName, getInitials } from '../lib/format'
|
||||
|
||||
export function ProfilePage() {
|
||||
const { user, refreshProfile } = useAuth()
|
||||
const { showToast } = useToast()
|
||||
const [firstName, setFirstName] = useState(user?.firstName ?? '')
|
||||
const [lastName, setLastName] = useState(user?.lastName ?? '')
|
||||
const [email, setEmail] = useState(user?.email ?? '')
|
||||
const [currentPassword, setCurrentPassword] = useState('')
|
||||
const [newPassword, setNewPassword] = useState('')
|
||||
const [savingProfile, setSavingProfile] = useState(false)
|
||||
const [savingPassword, setSavingPassword] = useState(false)
|
||||
const [profileError, setProfileError] = useState('')
|
||||
const [passwordError, setPasswordError] = useState('')
|
||||
|
||||
const handleProfile = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
setSavingProfile(true)
|
||||
setProfileError('')
|
||||
try {
|
||||
await api.updateProfile({ email: email.trim(), firstName: firstName.trim(), lastName: lastName.trim() })
|
||||
await refreshProfile()
|
||||
showToast('success', 'Profile saved', 'Your account details are up to date.')
|
||||
} catch (caught) {
|
||||
setProfileError(caught instanceof Error ? caught.message : 'Your profile could not be saved.')
|
||||
} finally {
|
||||
setSavingProfile(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePassword = async (event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
setSavingPassword(true)
|
||||
setPasswordError('')
|
||||
try {
|
||||
await api.updateProfile({ currentPassword, newPassword })
|
||||
setCurrentPassword('')
|
||||
setNewPassword('')
|
||||
showToast('success', 'Password updated', 'Use your new password the next time you sign in.')
|
||||
} catch (caught) {
|
||||
setPasswordError(caught instanceof Error ? caught.message : 'Your password could not be changed.')
|
||||
} finally {
|
||||
setSavingPassword(false)
|
||||
}
|
||||
}
|
||||
|
||||
const isAdmin = user?.roles.some((role) => role === 'Site Admin' || role === 'Admin')
|
||||
|
||||
return (
|
||||
<div className="page page--profile">
|
||||
<header className="page-header"><div><p className="eyebrow">Your Keeply account</p><h1>Profile & settings</h1><p>Keep your personal details and sign-in information current.</p></div></header>
|
||||
|
||||
<div className="profile-layout">
|
||||
<aside className="profile-summary-card">
|
||||
<span className="avatar avatar--profile">{getInitials(user?.firstName, user?.lastName, user?.email)}</span>
|
||||
<h2>{displayName(user?.firstName, user?.lastName, user?.email)}</h2>
|
||||
<p>{user?.email}</p>
|
||||
<span className={`role-label ${isAdmin ? 'role-label--admin' : ''}`}>{isAdmin ? <ShieldCheck size={14} /> : <UserRound size={14} />}{isAdmin ? 'Site administrator' : 'Pantry member'}</span>
|
||||
<div className="profile-summary-card__meta"><span>Account ID</span><code>{user?.id}</code></div>
|
||||
</aside>
|
||||
|
||||
<div className="settings-stack">
|
||||
<section className="settings-card">
|
||||
<header><span><UserRound size={20} /></span><div><h2>Personal details</h2><p>Used across your household and pantry.</p></div></header>
|
||||
<form onSubmit={(event) => void handleProfile(event)}>
|
||||
<div className="settings-card__body">
|
||||
<div className="form-row"><label className="field"><span>First name</span><input value={firstName} onChange={(event) => setFirstName(event.target.value)} required /></label><label className="field"><span>Last name</span><input value={lastName} onChange={(event) => setLastName(event.target.value)} required /></label></div>
|
||||
<label className="field"><span>Email address</span><div className="input-with-icon"><AtSign size={17} /><input type="email" value={email} onChange={(event) => setEmail(event.target.value)} required /></div></label>
|
||||
{profileError && <div className="form-error" role="alert">{profileError}</div>}
|
||||
</div>
|
||||
<footer><button className="button button--primary" disabled={savingProfile}>{savingProfile ? <Spinner /> : <Save size={17} />}Save details</button></footer>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section className="settings-card">
|
||||
<header><span><KeyRound size={20} /></span><div><h2>Change password</h2><p>Choose at least 8 characters with upper, lower and a number.</p></div></header>
|
||||
<form onSubmit={(event) => void handlePassword(event)}>
|
||||
<div className="settings-card__body"><label className="field"><span>Current password</span><input type="password" value={currentPassword} onChange={(event) => setCurrentPassword(event.target.value)} autoComplete="current-password" required /></label><label className="field"><span>New password</span><input type="password" value={newPassword} onChange={(event) => setNewPassword(event.target.value)} autoComplete="new-password" minLength={8} required /></label>{passwordError && <div className="form-error" role="alert">{passwordError}</div>}</div>
|
||||
<footer><button className="button button--secondary" disabled={savingPassword}>{savingPassword && <Spinner />}Update password</button></footer>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user