chore: build
This commit is contained in:
@@ -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>
|
||||
)}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 dé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()}>
|
||||
|
||||
Reference in New Issue
Block a user