chore: build

This commit is contained in:
2026-06-12 15:18:33 +02:00
parent 2c3b5cd7d2
commit cc15901d93
3 changed files with 177 additions and 51 deletions
@@ -216,3 +216,109 @@
font-size: 0.85rem;
font-weight: 400 !important;
}
/* Modal feedback */
.cp-modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 1rem;
}
.cp-modal {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 1rem;
padding: 2rem 1.75rem 1.75rem;
width: 100%;
max-width: 22rem;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.75rem;
text-align: center;
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
}
.cp-modal-close {
position: absolute;
top: 0.75rem;
right: 0.75rem;
background: transparent;
border: none;
color: var(--text-muted);
cursor: pointer;
padding: 0.25rem;
display: flex;
align-items: center;
border-radius: 0.375rem;
transition: color 0.2s, background 0.2s;
}
.cp-modal-close:hover { color: var(--text); background: var(--bg); }
.cp-modal-icon {
width: 3.5rem;
height: 3.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.25rem;
}
.cp-modal-icon--success {
background: rgba(34, 197, 94, 0.12);
border: 2px solid rgba(34, 197, 94, 0.35);
color: #4ade80;
}
.cp-modal-icon--error {
background: rgba(239, 68, 68, 0.12);
border: 2px solid rgba(239, 68, 68, 0.35);
color: #f87171;
}
.cp-modal-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--text);
margin: 0;
}
.cp-modal-body {
font-size: 0.9rem;
color: var(--text-muted);
margin: 0;
line-height: 1.5;
}
.cp-modal-btn {
margin-top: 0.5rem;
width: 100%;
padding: 0.7rem 1rem;
border: none;
border-radius: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
transition: opacity 0.2s;
}
.cp-modal-btn:hover { opacity: 0.88; }
.cp-modal-btn--success {
background: linear-gradient(135deg, #7c3aed, #6d28d9);
color: #fff;
box-shadow: 0 4px 14px rgba(109, 40, 217, 0.4);
}
.cp-modal-btn--error {
background: rgba(239, 68, 68, 0.12);
color: #f87171;
border: 1px solid rgba(239, 68, 68, 0.3);
}
@@ -1,5 +1,5 @@
import { useState } from "react";
import { Lock, Eye, EyeOff, ShieldCheck } from "lucide-react";
import { Lock, Eye, EyeOff, ShieldCheck, CheckCircle, AlertTriangle, X } from "lucide-react";
import "./ChangePassword.css";
import { changePassword } from "../../api/api";
import { useNavigate } from "react-router-dom";
@@ -14,8 +14,7 @@ const ChangePasswordPage = () => {
const [showNew, setShowNew] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const [success, setSuccess] = useState(false);
const [modal, setModal] = useState<{ show: boolean; type: 'success' | 'error'; message: string }>({ show: false, type: 'success', message: '' });
const validate = (): string | null => {
if (!currentPassword) return "Le mot de passe actuel est requis";
@@ -28,11 +27,10 @@ const ChangePasswordPage = () => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setError("");
const validationError = validate();
if (validationError) {
setError(validationError);
setModal({ show: true, type: 'error', message: validationError });
return;
}
@@ -40,16 +38,23 @@ const ChangePasswordPage = () => {
try {
const result = await changePassword(currentPassword, newPassword);
if (result.success) {
setSuccess(true);
setTimeout(() => navigate("/user/accueil"), 1800);
setModal({ show: true, type: 'success', message: 'Mot de passe mis à jour avec succès !' });
} else {
setError(result.message);
setModal({ show: true, type: 'error', message: result.message || 'Erreur lors du changement de mot de passe.' });
}
} finally {
setIsLoading(false);
}
};
const handleCloseModal = () => {
if (modal.type === 'success') {
navigate("/user/accueil");
} else {
setModal(m => ({ ...m, show: false }));
}
};
return (
<div className="cp-container">
<div className="cp-content">
@@ -65,20 +70,7 @@ const ChangePasswordPage = () => {
</div>
<div className="cp-card">
{success ? (
<div className="cp-success">
<span className="cp-success-icon"></span>
<p>Mot de passe mis à jour avec succès !</p>
<p className="cp-success-sub">Redirection en cours</p>
</div>
) : (
<form className="cp-form" onSubmit={handleSubmit}>
{error && (
<div className="cp-error-banner">
{error}
</div>
)}
{/* Mot de passe actuel */}
<div className="cp-form-group">
<label className="cp-label">Mot de passe actuel</label>
@@ -180,10 +172,32 @@ const ChangePasswordPage = () => {
{isLoading ? "Mise à jour…" : "Confirmer le nouveau mot de passe"}
</button>
</form>
)}
</div>
</div>
</div>
{modal.show && (
<div className="cp-modal-overlay" onClick={handleCloseModal}>
<div className="cp-modal" onClick={(e) => e.stopPropagation()}>
<button className="cp-modal-close" onClick={handleCloseModal}>
<X size={18} />
</button>
<div className={`cp-modal-icon ${modal.type === 'error' ? 'cp-modal-icon--error' : 'cp-modal-icon--success'}`}>
{modal.type === 'error'
? <AlertTriangle size={28} />
: <CheckCircle size={28} />
}
</div>
<h3 className="cp-modal-title">
{modal.type === 'error' ? 'Erreur' : 'Succès'}
</h3>
<p className="cp-modal-body">{modal.message}</p>
<button className={`cp-modal-btn ${modal.type === 'error' ? 'cp-modal-btn--error' : 'cp-modal-btn--success'}`} onClick={handleCloseModal}>
{modal.type === 'success' ? <><CheckCircle size={16} /> Continuer</> : <><X size={16} /> Fermer</>}
</button>
</div>
</div>
)}
);
};
+35 -29
View File
@@ -28,9 +28,10 @@ export default function ProfilePage() {
const [signalPseudo, setSignalPseudo] = useState(() => localStorage.getItem(STORAGE_SIGNAL) ?? '');
// Feedback
const [savingContact, setSavingContact] = useState(false);
const [successMsg, setSuccessMsg] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [savingContact, setSavingContact] = useState(false);
const [feedbackModal, setFeedbackModal] = useState<{
show: boolean; type: 'success' | 'error'; title: string; body: string;
}>({ show: false, type: 'success', title: '', body: '' });
// Telegram
const [tgLinked, setTgLinked] = useState(false);
@@ -78,20 +79,13 @@ export default function ProfilePage() {
});
}, [navigate]);
const showSuccess = (msg: string) => {
setSuccessMsg(msg);
setErrorMsg('');
setTimeout(() => setSuccessMsg(''), 3000);
};
const showError = (msg: string) => {
setErrorMsg(msg);
setSuccessMsg('');
setTimeout(() => setErrorMsg(''), 4000);
const showFeedback = (type: 'success' | 'error', title: string, body: string) => {
setFeedbackModal({ show: true, type, title, body });
};
const saveAddress = () => {
localStorage.setItem(STORAGE_ADDRESS, defaultAddress.trim());
showSuccess('Adresse par défaut enregistrée');
showFeedback('success', 'Adresse enregistrée', 'Votre adresse par défaut a été sauvegardée et sera pré-remplie à votre prochaine commande.');
};
const saveLocal = () => {
@@ -99,7 +93,7 @@ export default function ProfilePage() {
localStorage.setItem(STORAGE_PHONE, defaultPhone.trim());
localStorage.setItem(STORAGE_SIGNAL, signalPseudo.trim());
setShowSaveModal(false);
showSuccess('Informations par faut enregistrées');
showFeedback('success', 'Infos enregistrées', 'Adresse, téléphone et pseudo Signal sauvegardés. Ils seront pré-remplis à votre prochaine commande.');
};
const handleLinkTelegram = async () => {
@@ -132,9 +126,9 @@ export default function ProfilePage() {
setTwoFALoading(false);
if (res.success) {
setTwoFAEnabled(newVal);
showSuccess(newVal ? 'Double authentification activée' : 'Double authentification désactivée');
showFeedback('success', newVal ? '2FA activée' : '2FA désactivée', newVal ? 'La double authentification est maintenant active. Un code vous sera envoyé sur Telegram à chaque connexion.' : 'La double authentification a été désactivée.');
} else {
showError(res.error || 'Erreur lors de la modification');
showFeedback('error', 'Erreur', res.error || 'Erreur lors de la modification');
}
};
@@ -143,9 +137,9 @@ export default function ProfilePage() {
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
setSavingContact(false);
if (res.success) {
showSuccess('Profil mis à jour');
showFeedback('success', 'Profil mis à jour', 'Vos informations de compte ont été enregistrées avec succès.');
} else {
showError(res.message ?? 'Erreur lors de la mise à jour');
showFeedback('error', 'Erreur', res.message ?? 'Erreur lors de la mise à jour du profil.');
}
};
@@ -174,17 +168,6 @@ export default function ProfilePage() {
</div>
</div>
{successMsg && (
<div className="profile-alert profile-alert--success">
<FontAwesomeIcon icon={faCheckCircle} /> {successMsg}
</div>
)}
{errorMsg && (
<div className="profile-alert profile-alert--error">
<FontAwesomeIcon icon={faExclamationTriangle} /> {errorMsg}
</div>
)}
{/* Section compte */}
<div className="profile-card">
<h2 className="profile-card-title">
@@ -403,6 +386,29 @@ export default function ProfilePage() {
</div>
)}
{feedbackModal.show && (
<div className="profile-modal-overlay" onClick={() => setFeedbackModal(m => ({ ...m, show: false }))}>
<div className="profile-modal" onClick={(e) => e.stopPropagation()}>
<button className="profile-modal-close" onClick={() => setFeedbackModal(m => ({ ...m, show: false }))}>
<FontAwesomeIcon icon={faTimes} />
</button>
<div className={`profile-modal-icon ${feedbackModal.type === 'error' ? 'profile-modal-icon--danger' : 'profile-modal-icon--success'}`}>
<FontAwesomeIcon icon={feedbackModal.type === 'error' ? faExclamationTriangle : faCheckCircle} />
</div>
<h3 className="profile-modal-title">{feedbackModal.title}</h3>
<p className="profile-modal-body">{feedbackModal.body}</p>
<div className="profile-modal-actions">
<button
className={`profile-modal-btn ${feedbackModal.type === 'error' ? 'profile-modal-btn--danger' : 'profile-modal-btn--confirm'}`}
onClick={() => setFeedbackModal(m => ({ ...m, show: false }))}
>
<FontAwesomeIcon icon={faCheckCircle} /> Fermer
</button>
</div>
</div>
</div>
)}
{showUnlinkSuccessModal && (
<div className="profile-modal-overlay" onClick={() => setShowUnlinkSuccessModal(false)}>
<div className="profile-modal" onClick={(e) => e.stopPropagation()}>