Feature: Suppression de son compte

This commit is contained in:
2026-07-03 19:49:32 +02:00
parent 89190c4561
commit 2281894802
6 changed files with 169 additions and 5 deletions
+6 -2
View File
@@ -54,8 +54,12 @@ export const api = {
headers: { 'Content-Type': 'application/json', ...authHeaders() },
body: JSON.stringify(body),
}).then(handle),
del: (path) =>
fetch(BASE + path, { method: 'DELETE', headers: authHeaders() }).then(handle),
del: (path, body) =>
fetch(BASE + path, {
method: 'DELETE',
headers: body ? { 'Content-Type': 'application/json', ...authHeaders() } : authHeaders(),
...(body ? { body: JSON.stringify(body) } : {}),
}).then(handle),
upload: (path, formData) =>
fetch(BASE + path, { method: 'POST', body: formData, headers: authHeaders() }).then(handle),
postForm: (path, formData) =>
+6 -1
View File
@@ -67,10 +67,15 @@ export function AuthProvider({ children }) {
return r.user;
};
const deleteAccount = async (password) => {
await api.del('/auth/me', { password });
logout();
};
const isAdmin = user?.role === 'admin';
return (
<AuthCtx.Provider value={{ token, user, loading, login, completeLogin, register, logout, updateUser, isAdmin }}>
<AuthCtx.Provider value={{ token, user, loading, login, completeLogin, register, logout, updateUser, deleteAccount, isAdmin }}>
{children}
</AuthCtx.Provider>
);
+9 -1
View File
@@ -1,5 +1,5 @@
import { useState, useEffect, useRef } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useSearchParams } from 'react-router-dom';
import { useAuth } from '../context/AuthContext.jsx';
import { api } from '../api.js';
import AuthBgCol from '../components/AuthBgCol.jsx';
@@ -28,6 +28,8 @@ function AppHeader({ appInfo }) {
export default function Login() {
const { login, completeLogin } = useAuth();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const accountDeleted = searchParams.get('deleted') === '1';
const [appInfo, setAppInfo] = useState({ appName: 'Crowdlending Tracker', iconUrl: null, allowRegistration: true });
@@ -193,6 +195,12 @@ export default function Login() {
<form onSubmit={submit} style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<ErrBox msg={err} />
{accountDeleted && !err && (
<div style={{ padding: '10px 14px', borderRadius: 8, fontSize: 14, background: 'var(--success-bg, #f0fdf4)', color: 'var(--success, #16a34a)', border: '1px solid #bbf7d0' }}>
Votre compte a été supprimé avec succès.
</div>
)}
{unverified && (
<div style={{ padding: '12px 14px', borderRadius: 8, fontSize: 13, background: 'var(--warning-bg, #fffbeb)', color: 'var(--warning-text, #92400e)', border: '1px solid var(--warning-border, #fcd34d)', lineHeight: 1.5 }}>
<strong>Email non vérifié.</strong> Vérifiez votre boîte mail et cliquez sur le lien reçu.
+70 -1
View File
@@ -713,6 +713,75 @@ function TrustedDevicesSection() {
);
}
/* ── Suppression définitive du compte ────────────────────────── */
function DeleteAccountSection() {
const { deleteAccount } = useAuth();
const navigate = useNavigate();
const [confirming, setConfirming] = useState(false);
const [password, setPassword] = useState('');
const [err, setErr] = useState(null);
const [busy, setBusy] = useState(false);
const submit = async (e) => {
e.preventDefault();
setErr(null); setBusy(true);
try {
await deleteAccount(password);
navigate('/login?deleted=1', { replace: true });
} catch (e) {
setErr(e.message || 'Une erreur est survenue.');
} finally { setBusy(false); }
};
return (
<div className="card" style={{ marginTop: 20, border: '1px solid var(--danger,#dc2626)' }}>
<h3 style={{ margin: '0 0 4px', color: 'var(--danger,#dc2626)' }}>Supprimer mon compte</h3>
<p className="text-muted" style={{ margin: '0 0 16px', fontSize: 'var(--fs-sm)' }}>
Cette action est <strong>définitive et irréversible</strong>. Toutes vos données seront immédiatement
supprimées : plateformes, investissements, remboursements, dépôts/retraits, comptes courants,
préférences et historique. Il ne sera pas possible de les récupérer.
</p>
{err && <div className="error" style={{ marginBottom: 12 }}>{err}</div>}
{!confirming && (
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<button type="button" className="danger"
onClick={() => setConfirming(true)}>
Supprimer mon compte
</button>
</div>
)}
{confirming && (
<form onSubmit={submit} style={{
marginTop: 16, paddingTop: 16, borderTop: '1px solid var(--border)',
display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 400,
}}>
<p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}>
Confirmez votre mot de passe pour supprimer définitivement votre compte.
</p>
<div>
<label>Mot de passe actuel</label>
<PasswordInput required autoComplete="current-password" placeholder="••••••••"
value={password} onChange={e => setPassword(e.target.value)} />
</div>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 12 }}>
<button type="submit" className="danger"
disabled={busy || !password}>
{busy ? 'Suppression…' : 'Confirmer la suppression définitive'}
</button>
<button type="button" onClick={() => { setConfirming(false); setPassword(''); setErr(null); }}
style={{ background: 'none', border: 'none', color: 'var(--text-muted)', fontSize: 13, cursor: 'pointer', textDecoration: 'underline', padding: 0 }}>
Annuler
</button>
</div>
</form>
)}
</div>
);
}
/* ── Page principale ─────────────────────────────────────────── */
export default function MonCompte() {
const { search } = useLocation();
@@ -747,7 +816,7 @@ export default function MonCompte() {
{/* ── Contenu ─────────────────────────────────────── */}
<div className="account-content account-content-center">
<div className="account-content-narrow">
{section === 'profil' && <AccountForm />}
{section === 'profil' && <><AccountForm /><DeleteAccountSection /></>}
{section === 'securite' && <><SecurityForm /><TwoFASection user={user} /><TrustedDevicesSection /></>}
</div>
</div>