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 { useState, useEffect } from "react";
import { useNavigate } from 'react-router-dom'; import { useNavigate } from "react-router-dom";
import Navbar from '../../components/Navbar'; import Navbar from "../../components/Navbar";
import { isUserAuthenticated, extractUsernameFromToken, getMyProfile, updateMyProfile, getTelegramStatus, generateTelegramLinkToken, unlinkTelegram } from '../../api/api';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faUser, faMapMarkerAlt, faPhone, faCommentDots, isUserAuthenticated,
faSave, faCheckCircle, faExclamationTriangle, faPaperPlane, faUnlink, faTimes, faLock, extractUsernameFromToken,
} from '@fortawesome/free-solid-svg-icons'; getMyProfile,
import './ProfilePage.css'; 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_ADDRESS = "profile_default_address";
const STORAGE_PHONE = 'profile_default_phone'; const STORAGE_PHONE = "profile_default_phone";
const STORAGE_SIGNAL = 'profile_signal_pseudo'; const STORAGE_SIGNAL = "profile_signal_pseudo";
export default function ProfilePage() { export default function ProfilePage() {
const navigate = useNavigate(); const navigate = useNavigate();
// Données compte (backend) // Données compte (backend)
const [nom, setNom] = useState(''); const [nom, setNom] = useState("");
const [prenom, setPrenom] = useState(''); const [prenom, setPrenom] = useState("");
const [telephone, setTelephone] = useState(''); const [telephone, setTelephone] = useState("");
const [loadingProfile, setLoadingProfile] = useState(true); const [loadingProfile, setLoadingProfile] = useState(true);
// Données locales (localStorage) // Données locales (localStorage)
const [defaultAddress, setDefaultAddress] = useState(() => localStorage.getItem(STORAGE_ADDRESS) ?? ''); const [defaultAddress, setDefaultAddress] = useState(
const [defaultPhone, setDefaultPhone] = useState(() => localStorage.getItem(STORAGE_PHONE) ?? ''); () => localStorage.getItem(STORAGE_ADDRESS) ?? "",
const [signalPseudo, setSignalPseudo] = useState(() => localStorage.getItem(STORAGE_SIGNAL) ?? ''); );
const [defaultPhone, setDefaultPhone] = useState(
() => localStorage.getItem(STORAGE_PHONE) ?? "",
);
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 [successMsg, setSuccessMsg] = useState("");
const [errorMsg, setErrorMsg] = useState(''); const [errorMsg, setErrorMsg] = useState("");
// Telegram // Telegram
const [tgLinked, setTgLinked] = useState(false); const [tgLinked, setTgLinked] = useState(false);
@@ -40,24 +63,30 @@ export default function ProfilePage() {
// Modal confirmation infos par défaut // Modal confirmation infos par défaut
const [showSaveModal, setShowSaveModal] = useState(false); const [showSaveModal, setShowSaveModal] = useState(false);
const username = extractUsernameFromToken() ?? ''; const username = extractUsernameFromToken() ?? "";
useEffect(() => { useEffect(() => {
if (!isUserAuthenticated()) { if (!isUserAuthenticated()) {
navigate('/login/client', { replace: true }); navigate("/login/client", { replace: true });
return; return;
} }
// Statut Telegram // Statut Telegram
getTelegramStatus().then((s) => { setTgLinked(s.linked); setTgEnabled(s.enabled); }); getTelegramStatus().then((s) => {
setTgLinked(s.linked);
setTgEnabled(s.enabled);
});
// Charger depuis backend // Charger depuis backend
getMyProfile().then((res) => { getMyProfile().then((res) => {
if (res.success && res.client) { if (res.success && res.client) {
setNom(res.client.nom ?? ''); setNom(res.client.nom ?? "");
setPrenom(res.client.prenom ?? ''); setPrenom(res.client.prenom ?? "");
setTelephone(res.client.telephone ?? ''); setTelephone(res.client.telephone ?? "");
// Initialiser le téléphone par défaut si pas encore défini // 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); setDefaultPhone(res.client.telephone);
} }
} }
@@ -67,13 +96,13 @@ export default function ProfilePage() {
const showSuccess = (msg: string) => { const showSuccess = (msg: string) => {
setSuccessMsg(msg); setSuccessMsg(msg);
setErrorMsg(''); setErrorMsg("");
setTimeout(() => setSuccessMsg(''), 3000); setTimeout(() => setSuccessMsg(""), 3000);
}; };
const showError = (msg: string) => { const showError = (msg: string) => {
setErrorMsg(msg); setErrorMsg(msg);
setSuccessMsg(''); setSuccessMsg("");
setTimeout(() => setErrorMsg(''), 4000); setTimeout(() => setErrorMsg(""), 4000);
}; };
const saveLocal = () => { const saveLocal = () => {
@@ -81,35 +110,62 @@ 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'); showSuccess("Informations par défaut enregistrées");
}; };
const handleLinkTelegram = async () => { const handleLinkTelegram = async () => {
setTgLoading(true); setTgLoading(true);
// ✅ Ouvrir AVANT le await
const newWindow = window.open("", "_blank");
const res = await generateTelegramLinkToken(); const res = await generateTelegramLinkToken();
setTgLoading(false); setTgLoading(false);
if (res.error || !res.link_url) { if (res.error || !res.link_url) {
showError(res.error || 'Service Telegram non disponible'); newWindow?.close();
showError(res.error || "Service Telegram non disponible");
return; 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 () => { 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(); await unlinkTelegram();
setTgLinked(false); setTgLinked(false);
showSuccess('Compte Telegram délié'); showSuccess("Compte Telegram délié");
}; };
const saveContact = async () => { const saveContact = async () => {
setSavingContact(true); 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); setSavingContact(false);
if (res.success) { if (res.success) {
showSuccess('Profil mis à jour'); showSuccess("Profil mis à jour");
} else { } 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 /> <Navbar />
<div className="profile-container"> <div className="profile-container">
<div className="profile-loading"><div className="spinner" /></div> <div className="profile-loading">
<div className="spinner" />
</div>
</div> </div>
</> </>
); );
@@ -145,14 +203,18 @@ export default function ProfilePage() {
)} )}
{errorMsg && ( {errorMsg && (
<div className="profile-alert profile-alert--error"> <div className="profile-alert profile-alert--error">
<FontAwesomeIcon icon={faExclamationTriangle} /> {errorMsg} <FontAwesomeIcon icon={faExclamationTriangle} />{" "}
{errorMsg}
</div> </div>
)} )}
{/* Section compte */} {/* Section compte */}
<div className="profile-card"> <div className="profile-card">
<h2 className="profile-card-title"> <h2 className="profile-card-title">
<FontAwesomeIcon icon={faUser} className="profile-card-icon" /> <FontAwesomeIcon
icon={faUser}
className="profile-card-icon"
/>
Mon compte Mon compte
</h2> </h2>
<div className="profile-fields"> <div className="profile-fields">
@@ -186,23 +248,38 @@ export default function ProfilePage() {
/> />
</div> </div>
</div> </div>
<button className="profile-btn" onClick={saveContact} disabled={savingContact}> <button
className="profile-btn"
onClick={saveContact}
disabled={savingContact}
>
<FontAwesomeIcon icon={faSave} /> <FontAwesomeIcon icon={faSave} />
{savingContact ? ' Enregistrement...' : ' Enregistrer le compte'} {savingContact
? " Enregistrement..."
: " Enregistrer le compte"}
</button> </button>
<button className="profile-btn profile-btn--secondary" onClick={() => navigate('/user/change-password')} style={{ marginTop: '0.6rem' }}> <button
<FontAwesomeIcon icon={faLock} /> Changer le mot de passe className="profile-btn profile-btn--secondary"
onClick={() => navigate("/user/change-password")}
style={{ marginTop: "0.6rem" }}
>
<FontAwesomeIcon icon={faLock} /> Changer le mot de
passe
</button> </button>
</div> </div>
{/* Section adresse par défaut */} {/* Section adresse par défaut */}
<div className="profile-card"> <div className="profile-card">
<h2 className="profile-card-title"> <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 Adresse par défaut
</h2> </h2>
<p className="profile-hint"> <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> </p>
<div className="profile-group"> <div className="profile-group">
<label>Adresse</label> <label>Adresse</label>
@@ -218,11 +295,15 @@ export default function ProfilePage() {
{/* Section contact commande */} {/* Section contact commande */}
<div className="profile-card"> <div className="profile-card">
<h2 className="profile-card-title"> <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 Contact livraison
</h2> </h2>
<p className="profile-hint"> <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> </p>
<div className="profile-group"> <div className="profile-group">
<label>Téléphone par défaut</label> <label>Téléphone par défaut</label>
@@ -233,9 +314,15 @@ export default function ProfilePage() {
placeholder="+33 6 12 34 56 78" placeholder="+33 6 12 34 56 78"
/> />
</div> </div>
<div className="profile-group" style={{ marginTop: '1rem' }}> <div
className="profile-group"
style={{ marginTop: "1rem" }}
>
<label> <label>
<FontAwesomeIcon icon={faCommentDots} style={{ marginRight: '0.4rem' }} /> <FontAwesomeIcon
icon={faCommentDots}
style={{ marginRight: "0.4rem" }}
/>
Pseudo Signal (optionnel) Pseudo Signal (optionnel)
</label> </label>
<input <input
@@ -245,33 +332,52 @@ export default function ProfilePage() {
placeholder="@votre.pseudo.signal" placeholder="@votre.pseudo.signal"
/> />
</div> </div>
<button className="profile-btn profile-btn--secondary" onClick={() => setShowSaveModal(true)}> <button
<FontAwesomeIcon icon={faSave} /> Enregistrer les infos par défaut className="profile-btn profile-btn--secondary"
onClick={() => setShowSaveModal(true)}
>
<FontAwesomeIcon icon={faSave} /> Enregistrer les infos
par défaut
</button> </button>
</div> </div>
{/* Section Telegram */} {/* Section Telegram */}
{tgEnabled && ( {tgEnabled && (
<div className="profile-card"> <div className="profile-card">
<h2 className="profile-card-title"> <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 Notifications Telegram
</h2> </h2>
<p className="profile-hint"> <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> </p>
{tgLinked ? ( {tgLinked ? (
<div className="profile-telegram-linked"> <div className="profile-telegram-linked">
<span className="profile-telegram-status"> <span className="profile-telegram-status">
<FontAwesomeIcon icon={faCheckCircle} /> Compte Telegram lié <FontAwesomeIcon icon={faCheckCircle} />{" "}
Compte Telegram lié
</span> </span>
<button className="profile-btn profile-btn--danger" onClick={handleUnlinkTelegram}> <button
<FontAwesomeIcon icon={faUnlink} /> Délier Telegram className="profile-btn profile-btn--danger"
onClick={handleUnlinkTelegram}
>
<FontAwesomeIcon icon={faUnlink} /> Délier
Telegram
</button> </button>
</div> </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} /> <FontAwesomeIcon icon={faPaperPlane} />
{tgLoading ? ' Génération du lien...' : ' Lier mon compte Telegram'} {tgLoading
? " Génération du lien..."
: " Lier mon compte Telegram"}
</button> </button>
)} )}
</div> </div>
@@ -279,24 +385,44 @@ export default function ProfilePage() {
</div> </div>
{showSaveModal && ( {showSaveModal && (
<div className="profile-modal-overlay" onClick={() => setShowSaveModal(false)}> <div
<div className="profile-modal" onClick={(e) => e.stopPropagation()}> className="profile-modal-overlay"
<button className="profile-modal-close" onClick={() => setShowSaveModal(false)}> onClick={() => setShowSaveModal(false)}
>
<div
className="profile-modal"
onClick={(e) => e.stopPropagation()}
>
<button
className="profile-modal-close"
onClick={() => setShowSaveModal(false)}
>
<FontAwesomeIcon icon={faTimes} /> <FontAwesomeIcon icon={faTimes} />
</button> </button>
<div className="profile-modal-icon"> <div className="profile-modal-icon">
<FontAwesomeIcon icon={faSave} /> <FontAwesomeIcon icon={faSave} />
</div> </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"> <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> </p>
<div className="profile-modal-actions"> <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 Annuler
</button> </button>
<button className="profile-modal-btn profile-modal-btn--confirm" onClick={saveLocal}> <button
<FontAwesomeIcon icon={faCheckCircle} /> Confirmer className="profile-modal-btn profile-modal-btn--confirm"
onClick={saveLocal}
>
<FontAwesomeIcon icon={faCheckCircle} />{" "}
Confirmer
</button> </button>
</div> </div>
</div> </div>