feat(frontend): profile page (display name, email, change password)
This commit is contained in:
87
packages/frontend/src/pages/Profile.tsx
Normal file
87
packages/frontend/src/pages/Profile.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { authApi } from '../api/auth.js';
|
||||||
|
import { ApiClientError } from '../api/client.js';
|
||||||
|
import { useAuth } from '../stores/authStore.js';
|
||||||
|
|
||||||
|
export function ProfilePage() {
|
||||||
|
const { user, refreshMe } = useAuth();
|
||||||
|
const [displayName, setDisplayName] = useState(user?.displayName ?? '');
|
||||||
|
const [email, setEmail] = useState(user?.email ?? '');
|
||||||
|
const [profileBusy, setProfileBusy] = useState(false);
|
||||||
|
const [profileMsg, setProfileMsg] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [currentPassword, setCurrentPassword] = useState('');
|
||||||
|
const [newPassword, setNewPassword] = useState('');
|
||||||
|
const [pwBusy, setPwBusy] = useState(false);
|
||||||
|
const [pwMsg, setPwMsg] = useState<string | null>(null);
|
||||||
|
const [pwErr, setPwErr] = useState<string | null>(null);
|
||||||
|
|
||||||
|
async function saveProfile(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setProfileBusy(true); setProfileMsg(null);
|
||||||
|
try {
|
||||||
|
const updates: { displayName?: string; email?: string } = {};
|
||||||
|
if (displayName !== user?.displayName) updates.displayName = displayName;
|
||||||
|
if (email !== user?.email) updates.email = email;
|
||||||
|
if (Object.keys(updates).length === 0) { setProfileMsg('Geen wijzigingen.'); return; }
|
||||||
|
await authApi.updateProfile(updates);
|
||||||
|
await refreshMe();
|
||||||
|
setProfileMsg(updates.email ? 'Profiel opgeslagen. Bevestig je nieuwe e-mailadres via de link in de mail.' : 'Profiel opgeslagen.');
|
||||||
|
} catch (err) {
|
||||||
|
setProfileMsg(err instanceof ApiClientError ? err.message : 'Opslaan mislukt.');
|
||||||
|
} finally { setProfileBusy(false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function changePassword(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
setPwBusy(true); setPwMsg(null); setPwErr(null);
|
||||||
|
try {
|
||||||
|
await authApi.changePassword({ currentPassword, newPassword });
|
||||||
|
setPwMsg('Wachtwoord gewijzigd.');
|
||||||
|
setCurrentPassword(''); setNewPassword('');
|
||||||
|
} catch (err) {
|
||||||
|
setPwErr(err instanceof ApiClientError ? err.message : 'Wijzigen mislukt.');
|
||||||
|
} finally { setPwBusy(false); }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mx-auto max-w-2xl space-y-6">
|
||||||
|
<header>
|
||||||
|
<h1 className="font-display text-3xl font-bold">Profiel</h1>
|
||||||
|
<p className="text-sm text-slate-500">Beheer je naam, e-mailadres en wachtwoord.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<form onSubmit={saveProfile} className="surface space-y-4 p-6">
|
||||||
|
<h2 className="font-display text-lg font-bold">Gegevens</h2>
|
||||||
|
<label className="block text-sm">
|
||||||
|
<span className="mb-1 block font-medium">Naam</span>
|
||||||
|
<input className="input-field" value={displayName} onChange={(e) => setDisplayName(e.target.value)} />
|
||||||
|
</label>
|
||||||
|
<label className="block text-sm">
|
||||||
|
<span className="mb-1 block font-medium">E-mailadres</span>
|
||||||
|
<input type="email" className="input-field" value={email} onChange={(e) => setEmail(e.target.value)} />
|
||||||
|
{user.pendingEmail && <span className="mt-1 block text-xs text-amber-700">In afwachting: {user.pendingEmail}</span>}
|
||||||
|
</label>
|
||||||
|
{profileMsg && <p className="text-sm">{profileMsg}</p>}
|
||||||
|
<button className="btn-primary" disabled={profileBusy}>{profileBusy ? 'Bezig…' : 'Opslaan'}</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form onSubmit={changePassword} className="surface space-y-4 p-6">
|
||||||
|
<h2 className="font-display text-lg font-bold">Wachtwoord</h2>
|
||||||
|
<label className="block text-sm">
|
||||||
|
<span className="mb-1 block font-medium">Huidig wachtwoord</span>
|
||||||
|
<input type="password" className="input-field" required value={currentPassword} onChange={(e) => setCurrentPassword(e.target.value)} autoComplete="current-password" />
|
||||||
|
</label>
|
||||||
|
<label className="block text-sm">
|
||||||
|
<span className="mb-1 block font-medium">Nieuw wachtwoord (min. 8 tekens)</span>
|
||||||
|
<input type="password" minLength={8} className="input-field" required value={newPassword} onChange={(e) => setNewPassword(e.target.value)} autoComplete="new-password" />
|
||||||
|
</label>
|
||||||
|
{pwMsg && <p className="text-sm text-success-700">{pwMsg}</p>}
|
||||||
|
{pwErr && <p className="text-sm text-danger-700">{pwErr}</p>}
|
||||||
|
<button className="btn-primary" disabled={pwBusy}>{pwBusy ? 'Bezig…' : 'Wachtwoord wijzigen'}</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user