fix: telegram link

This commit is contained in:
2026-05-12 21:39:59 +02:00
parent d13a980447
commit ee0abcd223
+195 -69
View File
@@ -1,36 +1,59 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Navbar from '../../components/Navbar';
import { isUserAuthenticated, extractUsernameFromToken, getMyProfile, updateMyProfile, getTelegramStatus, generateTelegramLinkToken, unlinkTelegram } from '../../api/api';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import Navbar from "../../components/Navbar";
import {
faUser, faMapMarkerAlt, faPhone, faCommentDots,
faSave, faCheckCircle, faExclamationTriangle, faPaperPlane, faUnlink, faTimes, faLock,
} from '@fortawesome/free-solid-svg-icons';
import './ProfilePage.css';
isUserAuthenticated,
extractUsernameFromToken,
getMyProfile,
updateMyProfile,
getTelegramStatus,
generateTelegramLinkToken,
unlinkTelegram,
} from "../../api/api";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faUser,
faMapMarkerAlt,
faPhone,
faCommentDots,
faSave,
faCheckCircle,
faExclamationTriangle,
faPaperPlane,
faUnlink,
faTimes,
faLock,
} from "@fortawesome/free-solid-svg-icons";
import "./ProfilePage.css";
const STORAGE_ADDRESS = 'profile_default_address';
const STORAGE_PHONE = 'profile_default_phone';
const STORAGE_SIGNAL = 'profile_signal_pseudo';
const STORAGE_ADDRESS = "profile_default_address";
const STORAGE_PHONE = "profile_default_phone";
const STORAGE_SIGNAL = "profile_signal_pseudo";
export default function ProfilePage() {
const navigate = useNavigate();
// Données compte (backend)
const [nom, setNom] = useState('');
const [prenom, setPrenom] = useState('');
const [telephone, setTelephone] = useState('');
const [nom, setNom] = useState("");
const [prenom, setPrenom] = useState("");
const [telephone, setTelephone] = useState("");
const [loadingProfile, setLoadingProfile] = useState(true);
// Données locales (localStorage)
const [defaultAddress, setDefaultAddress] = useState(() => localStorage.getItem(STORAGE_ADDRESS) ?? '');
const [defaultPhone, setDefaultPhone] = useState(() => localStorage.getItem(STORAGE_PHONE) ?? '');
const [signalPseudo, setSignalPseudo] = useState(() => localStorage.getItem(STORAGE_SIGNAL) ?? '');
const [defaultAddress, setDefaultAddress] = useState(
() => localStorage.getItem(STORAGE_ADDRESS) ?? "",
);
const [defaultPhone, setDefaultPhone] = useState(
() => localStorage.getItem(STORAGE_PHONE) ?? "",
);
const [signalPseudo, setSignalPseudo] = useState(
() => localStorage.getItem(STORAGE_SIGNAL) ?? "",
);
// Feedback
const [savingContact, setSavingContact] = useState(false);
const [successMsg, setSuccessMsg] = useState('');
const [errorMsg, setErrorMsg] = useState('');
const [successMsg, setSuccessMsg] = useState("");
const [errorMsg, setErrorMsg] = useState("");
// Telegram
const [tgLinked, setTgLinked] = useState(false);
@@ -40,24 +63,30 @@ export default function ProfilePage() {
// Modal confirmation infos par défaut
const [showSaveModal, setShowSaveModal] = useState(false);
const username = extractUsernameFromToken() ?? '';
const username = extractUsernameFromToken() ?? "";
useEffect(() => {
if (!isUserAuthenticated()) {
navigate('/login/client', { replace: true });
navigate("/login/client", { replace: true });
return;
}
// Statut Telegram
getTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); });
getTelegramStatus().then((s) => {
setTgLinked(s.linked);
setTgEnabled(s.enabled);
});
// Charger depuis backend
getMyProfile().then((res) => {
if (res.success && res.client) {
setNom(res.client.nom ?? '');
setPrenom(res.client.prenom ?? '');
setTelephone(res.client.telephone ?? '');
setNom(res.client.nom ?? "");
setPrenom(res.client.prenom ?? "");
setTelephone(res.client.telephone ?? "");
// Initialiser le téléphone par défaut si pas encore défini
if (!localStorage.getItem(STORAGE_PHONE) && res.client.telephone) {
if (
!localStorage.getItem(STORAGE_PHONE) &&
res.client.telephone
) {
setDefaultPhone(res.client.telephone);
}
}
@@ -67,13 +96,13 @@ export default function ProfilePage() {
const showSuccess = (msg: string) => {
setSuccessMsg(msg);
setErrorMsg('');
setTimeout(() => setSuccessMsg(''), 3000);
setErrorMsg("");
setTimeout(() => setSuccessMsg(""), 3000);
};
const showError = (msg: string) => {
setErrorMsg(msg);
setSuccessMsg('');
setTimeout(() => setErrorMsg(''), 4000);
setSuccessMsg("");
setTimeout(() => setErrorMsg(""), 4000);
};
const saveLocal = () => {
@@ -81,35 +110,62 @@ 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');
showSuccess("Informations par défaut enregistrées");
};
const handleLinkTelegram = async () => {
setTgLoading(true);
// ✅ Ouvrir AVANT le await
const newWindow = window.open("", "_blank");
const res = await generateTelegramLinkToken();
setTgLoading(false);
if (res.error || !res.link_url) {
showError(res.error || 'Service Telegram non disponible');
newWindow?.close();
showError(res.error || "Service Telegram non disponible");
return;
}
window.open(res.link_url, '_blank');
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
if (isIOS) {
const botUsername = res.link_url.split("t.me/")[1]?.split("?")[0];
const token = new URL(res.link_url).searchParams.get("start");
newWindow!.location.href = `tg://resolve?domain=${botUsername}&start=${token}`;
setTimeout(() => {
newWindow!.location.href = res.link_url!;
}, 1500);
} else {
newWindow!.location.href = res.link_url;
}
};
const handleUnlinkTelegram = async () => {
if (!window.confirm('Délier votre compte Telegram ? Vous ne recevrez plus de notifications.')) return;
if (
!window.confirm(
"Délier votre compte Telegram ? Vous ne recevrez plus de notifications.",
)
)
return;
await unlinkTelegram();
setTgLinked(false);
showSuccess('Compte Telegram délié');
showSuccess("Compte Telegram délié");
};
const saveContact = async () => {
setSavingContact(true);
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);
if (res.success) {
showSuccess('Profil mis à jour');
showSuccess("Profil mis à jour");
} else {
showError(res.message ?? 'Erreur lors de la mise à jour');
showError(res.message ?? "Erreur lors de la mise à jour");
}
};
@@ -118,7 +174,9 @@ export default function ProfilePage() {
<>
<Navbar />
<div className="profile-container">
<div className="profile-loading"><div className="spinner" /></div>
<div className="profile-loading">
<div className="spinner" />
</div>
</div>
</>
);
@@ -145,14 +203,18 @@ export default function ProfilePage() {
)}
{errorMsg && (
<div className="profile-alert profile-alert--error">
<FontAwesomeIcon icon={faExclamationTriangle} /> {errorMsg}
<FontAwesomeIcon icon={faExclamationTriangle} />{" "}
{errorMsg}
</div>
)}
{/* Section compte */}
<div className="profile-card">
<h2 className="profile-card-title">
<FontAwesomeIcon icon={faUser} className="profile-card-icon" />
<FontAwesomeIcon
icon={faUser}
className="profile-card-icon"
/>
Mon compte
</h2>
<div className="profile-fields">
@@ -186,23 +248,38 @@ export default function ProfilePage() {
/>
</div>
</div>
<button className="profile-btn" onClick={saveContact} disabled={savingContact}>
<button
className="profile-btn"
onClick={saveContact}
disabled={savingContact}
>
<FontAwesomeIcon icon={faSave} />
{savingContact ? ' Enregistrement...' : ' Enregistrer le compte'}
{savingContact
? " Enregistrement..."
: " Enregistrer le compte"}
</button>
<button className="profile-btn profile-btn--secondary" onClick={() => navigate('/user/change-password')} style={{ marginTop: '0.6rem' }}>
<FontAwesomeIcon icon={faLock} /> Changer le mot de passe
<button
className="profile-btn profile-btn--secondary"
onClick={() => navigate("/user/change-password")}
style={{ marginTop: "0.6rem" }}
>
<FontAwesomeIcon icon={faLock} /> Changer le mot de
passe
</button>
</div>
{/* Section adresse par défaut */}
<div className="profile-card">
<h2 className="profile-card-title">
<FontAwesomeIcon icon={faMapMarkerAlt} className="profile-card-icon profile-card-icon--address" />
<FontAwesomeIcon
icon={faMapMarkerAlt}
className="profile-card-icon profile-card-icon--address"
/>
Adresse par défaut
</h2>
<p className="profile-hint">
Sera pré-remplie dans le formulaire de commande. Vous pourrez la modifier si vous n'êtes pas à cette adresse.
Sera pré-remplie dans le formulaire de commande. Vous
pourrez la modifier si vous n'êtes pas à cette adresse.
</p>
<div className="profile-group">
<label>Adresse</label>
@@ -218,11 +295,15 @@ export default function ProfilePage() {
{/* Section contact commande */}
<div className="profile-card">
<h2 className="profile-card-title">
<FontAwesomeIcon icon={faPhone} className="profile-card-icon profile-card-icon--phone" />
<FontAwesomeIcon
icon={faPhone}
className="profile-card-icon profile-card-icon--phone"
/>
Contact livraison
</h2>
<p className="profile-hint">
Numéro utilisé par le livreur lors de la livraison. Peut être différent du numéro de votre compte.
Numéro utilisé par le livreur lors de la livraison. Peut
être différent du numéro de votre compte.
</p>
<div className="profile-group">
<label>Téléphone par défaut</label>
@@ -233,9 +314,15 @@ export default function ProfilePage() {
placeholder="+33 6 12 34 56 78"
/>
</div>
<div className="profile-group" style={{ marginTop: '1rem' }}>
<div
className="profile-group"
style={{ marginTop: "1rem" }}
>
<label>
<FontAwesomeIcon icon={faCommentDots} style={{ marginRight: '0.4rem' }} />
<FontAwesomeIcon
icon={faCommentDots}
style={{ marginRight: "0.4rem" }}
/>
Pseudo Signal (optionnel)
</label>
<input
@@ -245,33 +332,52 @@ export default function ProfilePage() {
placeholder="@votre.pseudo.signal"
/>
</div>
<button className="profile-btn profile-btn--secondary" onClick={() => setShowSaveModal(true)}>
<FontAwesomeIcon icon={faSave} /> Enregistrer les infos par défaut
<button
className="profile-btn profile-btn--secondary"
onClick={() => setShowSaveModal(true)}
>
<FontAwesomeIcon icon={faSave} /> Enregistrer les infos
par défaut
</button>
</div>
{/* Section Telegram */}
{tgEnabled && (
<div className="profile-card">
<h2 className="profile-card-title">
<FontAwesomeIcon icon={faPaperPlane} className="profile-card-icon profile-card-icon--telegram" />
<FontAwesomeIcon
icon={faPaperPlane}
className="profile-card-icon profile-card-icon--telegram"
/>
Notifications Telegram
</h2>
<p className="profile-hint">
Recevez vos notifications sur Telegram, même quand le site est fermé.
Recevez vos notifications sur Telegram, même quand
le site est fermé.
</p>
{tgLinked ? (
<div className="profile-telegram-linked">
<span className="profile-telegram-status">
<FontAwesomeIcon icon={faCheckCircle} /> Compte Telegram lié
<FontAwesomeIcon icon={faCheckCircle} />{" "}
Compte Telegram lié
</span>
<button className="profile-btn profile-btn--danger" onClick={handleUnlinkTelegram}>
<FontAwesomeIcon icon={faUnlink} /> Délier Telegram
<button
className="profile-btn profile-btn--danger"
onClick={handleUnlinkTelegram}
>
<FontAwesomeIcon icon={faUnlink} /> Délier
Telegram
</button>
</div>
) : (
<button className="profile-btn profile-btn--telegram" onClick={handleLinkTelegram} disabled={tgLoading}>
<button
className="profile-btn profile-btn--telegram"
onClick={handleLinkTelegram}
disabled={tgLoading}
>
<FontAwesomeIcon icon={faPaperPlane} />
{tgLoading ? ' Génération du lien...' : ' Lier mon compte Telegram'}
{tgLoading
? " Génération du lien..."
: " Lier mon compte Telegram"}
</button>
)}
</div>
@@ -279,24 +385,44 @@ export default function ProfilePage() {
</div>
{showSaveModal && (
<div className="profile-modal-overlay" onClick={() => setShowSaveModal(false)}>
<div className="profile-modal" onClick={(e) => e.stopPropagation()}>
<button className="profile-modal-close" onClick={() => setShowSaveModal(false)}>
<div
className="profile-modal-overlay"
onClick={() => setShowSaveModal(false)}
>
<div
className="profile-modal"
onClick={(e) => e.stopPropagation()}
>
<button
className="profile-modal-close"
onClick={() => setShowSaveModal(false)}
>
<FontAwesomeIcon icon={faTimes} />
</button>
<div className="profile-modal-icon">
<FontAwesomeIcon icon={faSave} />
</div>
<h3 className="profile-modal-title">Enregistrer les infos par défaut ?</h3>
<h3 className="profile-modal-title">
Enregistrer les infos par défaut ?
</h3>
<p className="profile-modal-body">
Adresse, téléphone de livraison et pseudo Signal seront sauvegardés localement et pré-remplis lors de vos prochaines commandes.
Adresse, téléphone de livraison et pseudo Signal
seront sauvegardés localement et pré-remplis lors de
vos prochaines commandes.
</p>
<div className="profile-modal-actions">
<button className="profile-modal-btn profile-modal-btn--cancel" onClick={() => setShowSaveModal(false)}>
<button
className="profile-modal-btn profile-modal-btn--cancel"
onClick={() => setShowSaveModal(false)}
>
Annuler
</button>
<button className="profile-modal-btn profile-modal-btn--confirm" onClick={saveLocal}>
<FontAwesomeIcon icon={faCheckCircle} /> Confirmer
<button
className="profile-modal-btn profile-modal-btn--confirm"
onClick={saveLocal}
>
<FontAwesomeIcon icon={faCheckCircle} />{" "}
Confirmer
</button>
</div>
</div>