chore: build
This commit is contained in:
@@ -216,3 +216,109 @@
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 400 !important;
|
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 { 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.css";
|
||||||
import { changePassword } from "../../api/api";
|
import { changePassword } from "../../api/api";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
@@ -14,8 +14,7 @@ const ChangePasswordPage = () => {
|
|||||||
const [showNew, setShowNew] = useState(false);
|
const [showNew, setShowNew] = useState(false);
|
||||||
const [showConfirm, setShowConfirm] = useState(false);
|
const [showConfirm, setShowConfirm] = useState(false);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [modal, setModal] = useState<{ show: boolean; type: 'success' | 'error'; message: string }>({ show: false, type: 'success', message: '' });
|
||||||
const [success, setSuccess] = useState(false);
|
|
||||||
|
|
||||||
const validate = (): string | null => {
|
const validate = (): string | null => {
|
||||||
if (!currentPassword) return "Le mot de passe actuel est requis";
|
if (!currentPassword) return "Le mot de passe actuel est requis";
|
||||||
@@ -28,11 +27,10 @@ const ChangePasswordPage = () => {
|
|||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setError("");
|
|
||||||
|
|
||||||
const validationError = validate();
|
const validationError = validate();
|
||||||
if (validationError) {
|
if (validationError) {
|
||||||
setError(validationError);
|
setModal({ show: true, type: 'error', message: validationError });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,16 +38,23 @@ const ChangePasswordPage = () => {
|
|||||||
try {
|
try {
|
||||||
const result = await changePassword(currentPassword, newPassword);
|
const result = await changePassword(currentPassword, newPassword);
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
setSuccess(true);
|
setModal({ show: true, type: 'success', message: 'Mot de passe mis à jour avec succès !' });
|
||||||
setTimeout(() => navigate("/user/accueil"), 1800);
|
|
||||||
} else {
|
} else {
|
||||||
setError(result.message);
|
setModal({ show: true, type: 'error', message: result.message || 'Erreur lors du changement de mot de passe.' });
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCloseModal = () => {
|
||||||
|
if (modal.type === 'success') {
|
||||||
|
navigate("/user/accueil");
|
||||||
|
} else {
|
||||||
|
setModal(m => ({ ...m, show: false }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="cp-container">
|
<div className="cp-container">
|
||||||
<div className="cp-content">
|
<div className="cp-content">
|
||||||
@@ -65,20 +70,7 @@ const ChangePasswordPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="cp-card">
|
<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}>
|
<form className="cp-form" onSubmit={handleSubmit}>
|
||||||
{error && (
|
|
||||||
<div className="cp-error-banner">
|
|
||||||
⚠️ {error}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Mot de passe actuel */}
|
{/* Mot de passe actuel */}
|
||||||
<div className="cp-form-group">
|
<div className="cp-form-group">
|
||||||
<label className="cp-label">Mot de passe actuel</label>
|
<label className="cp-label">Mot de passe actuel</label>
|
||||||
@@ -180,10 +172,32 @@ const ChangePasswordPage = () => {
|
|||||||
{isLoading ? "Mise à jour…" : "Confirmer le nouveau mot de passe"}
|
{isLoading ? "Mise à jour…" : "Confirmer le nouveau mot de passe"}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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) ?? '');
|
const [signalPseudo, setSignalPseudo] = useState(() => localStorage.getItem(STORAGE_SIGNAL) ?? '');
|
||||||
|
|
||||||
// Feedback
|
// Feedback
|
||||||
const [savingContact, setSavingContact] = useState(false);
|
const [savingContact, setSavingContact] = useState(false);
|
||||||
const [successMsg, setSuccessMsg] = useState('');
|
const [feedbackModal, setFeedbackModal] = useState<{
|
||||||
const [errorMsg, setErrorMsg] = useState('');
|
show: boolean; type: 'success' | 'error'; title: string; body: string;
|
||||||
|
}>({ show: false, type: 'success', title: '', body: '' });
|
||||||
|
|
||||||
// Telegram
|
// Telegram
|
||||||
const [tgLinked, setTgLinked] = useState(false);
|
const [tgLinked, setTgLinked] = useState(false);
|
||||||
@@ -78,20 +79,13 @@ export default function ProfilePage() {
|
|||||||
});
|
});
|
||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
const showSuccess = (msg: string) => {
|
const showFeedback = (type: 'success' | 'error', title: string, body: string) => {
|
||||||
setSuccessMsg(msg);
|
setFeedbackModal({ show: true, type, title, body });
|
||||||
setErrorMsg('');
|
|
||||||
setTimeout(() => setSuccessMsg(''), 3000);
|
|
||||||
};
|
|
||||||
const showError = (msg: string) => {
|
|
||||||
setErrorMsg(msg);
|
|
||||||
setSuccessMsg('');
|
|
||||||
setTimeout(() => setErrorMsg(''), 4000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveAddress = () => {
|
const saveAddress = () => {
|
||||||
localStorage.setItem(STORAGE_ADDRESS, defaultAddress.trim());
|
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 = () => {
|
const saveLocal = () => {
|
||||||
@@ -99,7 +93,7 @@ export default function ProfilePage() {
|
|||||||
localStorage.setItem(STORAGE_PHONE, defaultPhone.trim());
|
localStorage.setItem(STORAGE_PHONE, defaultPhone.trim());
|
||||||
localStorage.setItem(STORAGE_SIGNAL, signalPseudo.trim());
|
localStorage.setItem(STORAGE_SIGNAL, signalPseudo.trim());
|
||||||
setShowSaveModal(false);
|
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 () => {
|
const handleLinkTelegram = async () => {
|
||||||
@@ -132,9 +126,9 @@ export default function ProfilePage() {
|
|||||||
setTwoFALoading(false);
|
setTwoFALoading(false);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
setTwoFAEnabled(newVal);
|
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 {
|
} 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() });
|
const res = await updateMyProfile({ nom: nom.trim(), prenom: prenom.trim(), telephone: telephone.trim() });
|
||||||
setSavingContact(false);
|
setSavingContact(false);
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
showSuccess('Profil mis à jour');
|
showFeedback('success', 'Profil mis à jour', 'Vos informations de compte ont été enregistrées avec succès.');
|
||||||
} else {
|
} 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>
|
||||||
</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 */}
|
{/* Section compte */}
|
||||||
<div className="profile-card">
|
<div className="profile-card">
|
||||||
<h2 className="profile-card-title">
|
<h2 className="profile-card-title">
|
||||||
@@ -403,6 +386,29 @@ export default function ProfilePage() {
|
|||||||
</div>
|
</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 && (
|
{showUnlinkSuccessModal && (
|
||||||
<div className="profile-modal-overlay" onClick={() => setShowUnlinkSuccessModal(false)}>
|
<div className="profile-modal-overlay" onClick={() => setShowUnlinkSuccessModal(false)}>
|
||||||
<div className="profile-modal" onClick={(e) => e.stopPropagation()}>
|
<div className="profile-modal" onClick={(e) => e.stopPropagation()}>
|
||||||
|
|||||||
Reference in New Issue
Block a user