Repository Public
1// -----------------------------------------------------------------------------2// Auto-Ka — Agrégateur de voitures usagées à vendre (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/Profil.tsx : profil utilisateur — carte de membre Groupe KA (KA-ID),5// profil Groupe KA (lu du hub, lecture seule), informations du compte,6// déconnexion7// -----------------------------------------------------------------------------8import { useEffect, useState } from "react";9import { useNavigate } from "react-router-dom";10import { Me, Socials, fetchMe, logout } from "../api";1112const fmtEpoch = (ts: number | null | undefined): string => {13 if (!ts) return "—";14 return new Date(ts * 1000).toLocaleDateString("fr-CA", {15 day: "numeric", month: "long", year: "numeric",16 }).replace(/^1 /, "1ᵉʳ ");17};1819const SOCIAL_BASE: Record<string, string> = {20 instagram: "https://www.instagram.com/",21 facebook: "https://www.facebook.com/",22 x: "https://x.com/",23 linkedin: "https://www.linkedin.com/in/",24 tiktok: "https://www.tiktok.com/@",25 youtube: "https://www.youtube.com/@",26};2728const SOCIAL_FIELDS: { key: keyof Socials; label: string }[] = [29 { key: "instagram", label: "Instagram" },30 { key: "facebook", label: "Facebook" },31 { key: "x", label: "X (Twitter)" },32 { key: "linkedin", label: "LinkedIn" },33 { key: "tiktok", label: "TikTok" },34 { key: "youtube", label: "YouTube" },35];3637/** « @pseudo » ou « pseudo » -> URL complète de la plateforme ; URL laissée telle quelle */38function socialUrl(key: string, value: string): string {39 const v = value.trim();40 if (/^https?:\/\//i.test(v)) return v;41 if (key === "website") return `https://${v}`;42 return (SOCIAL_BASE[key] ?? "https://") + v.replace(/^@/, "");43}4445/** Profil géré au HUB Groupe KA — LECTURE SEULE (l'édition se fait sur46 * groupe-ka.com/compte, une seule saisie pour toutes les plateformes). */47function HubProfileSection({ me }: { me: Me }) {48 const socialLinks = SOCIAL_FIELDS.filter((s) => (me.socials?.[s.key] ?? "").trim());49 return (50 <section className="hub-profile">51 <h3>Mon profil Groupe KA</h3>52 {me.bio && <p className="hub-bio">{me.bio}</p>}53 <div className="hub-meta">54 {(me.job_title || me.company) && (55 <span className="hub-chip">56 {[me.job_title, me.company].filter(Boolean).join(" · ")}57 </span>58 )}59 {me.city && <span className="hub-chip">{me.city}</span>}60 {me.age != null && <span className="hub-chip">{me.age} ans</span>}61 {me.website && (62 <a className="hub-chip" href={socialUrl("website", me.website)}63 target="_blank" rel="noopener noreferrer">Site web ↗</a>64 )}65 {socialLinks.map((s) => (66 <a key={s.key} className="hub-chip"67 href={socialUrl(s.key, me.socials![s.key]!)}68 target="_blank" rel="noopener noreferrer" title={s.label}>69 {s.label}70 </a>71 ))}72 </div>73 <div>74 <a className="btn" href="https://www.groupe-ka.com/compte"75 target="_blank" rel="noopener noreferrer">76 Modifier mon profil sur groupe-ka.com ↗77 </a>78 </div>79 <p className="hub-hint">80 Votre profil est géré au niveau du groupe : une seule saisie, visible81 sur les huit plateformes du groupe.82 </p>83 </section>84 );85}8687export default function ProfilPage() {88 const [me, setMe] = useState<Me | null | undefined>(undefined); // undefined = chargement89 const [copied, setCopied] = useState(false);90 const nav = useNavigate();9192 useEffect(() => {93 fetchMe().then((r) => setMe(r.user));94 }, []);9596 const copyKaId = async () => {97 if (!me?.ka_id) return;98 try {99 await navigator.clipboard.writeText(me.ka_id);100 setCopied(true);101 setTimeout(() => setCopied(false), 1800);102 } catch { /* presse-papiers indisponible : tant pis */ }103 };104105 if (me === undefined) {106 return <div className="container profil"><div className="notice">Chargement…</div></div>;107 }108109 if (me === null) {110 return (111 <div className="container profil">112 <span className="kicker">Mon compte</span>113 <h1>Connectez-vous pour <em>votre profil</em>.</h1>114 <p className="lede">115 Connectez-vous avec votre <b>KA ID</b> — l'identifiant de membre116 du Groupe KA, le même sur toutes les plateformes du groupe.117 </p>118 <div className="profil-actions">119 <a className="btn login-cta" href="/api/auth/ka/login?next=%2Fprofil">120 Se connecter avec KA ID121 </a>122 <a123 className="btn ghost login-cta"124 href="https://www.groupe-ka.com/connexion"125 target="_blank"126 rel="noopener noreferrer"127 >128 Créer un compte sur groupe-ka.com ↗129 </a>130 </div>131 </div>132 );133 }134135 return (136 <div className="container profil">137 <span className="kicker">Mon compte</span>138 <h1>139 {me.name ? <>Salut, <em>{me.name.split(" ")[0]}</em>.</>140 : <>Votre <em>profil</em>.</>}141 </h1>142143 {/* ——— Carte de membre Groupe KA ——— */}144 <div className="pc" role="img" aria-label={`Carte de membre ${me.ka_id}`}>145 <div className="pc-watermark" aria-hidden="true">KA</div>146 <div className="pc-head">147 <span className="pc-brand">Groupe <span className="pc-ka">KA</span></span>148 <span className="pc-label">Carte de membre · Groupe KA</span>149 </div>150 <div className="pc-id-block">151 <span className="pc-id-label">KA-ID</span>152 <span className="pc-id">{me.ka_id}</span>153 </div>154 <div className="pc-foot">155 <div className="pc-holder">156 <span className="pc-holder-name">{me.name || me.email}</span>157 {me.role_label && (158 <span className="pc-role-badge">{me.role_label}</span>159 )}160 <span className="pc-holder-since">Membre depuis le {fmtEpoch(me.created)}</span>161 </div>162 {me.picture && (163 <img className="pc-avatar" src={me.picture} alt="" referrerPolicy="no-referrer" />164 )}165 </div>166 <div className="pc-strip" aria-hidden="true">167 {Array.from({ length: 28 }).map((_, i) => <i key={i} />)}168 </div>169 </div>170171 <button className={`btn ghost pc-copy ${copied ? "ok" : ""}`} onClick={copyKaId}>172 {copied ? "✓ Copié" : "Copier mon KA-ID"}173 </button>174175 {/* ——— Informations ——— */}176 <section className="profil-grid">177 <div className="pg-item">178 <span className="pg-label">Nom</span>179 <span className="pg-value">{me.name || "—"}</span>180 </div>181 <div className="pg-item">182 <span className="pg-label">Courriel</span>183 <span className="pg-value">{me.email || "—"}</span>184 </div>185 <div className="pg-item">186 <span className="pg-label">Identifiant membre</span>187 <span className="pg-value mono">{me.ka_id}</span>188 </div>189 <div className="pg-item">190 <span className="pg-label">Connexion</span>191 <span className="pg-value">KA ID (groupe-ka.com)</span>192 </div>193 <div className="pg-item">194 <span className="pg-label">Membre depuis</span>195 <span className="pg-value">{fmtEpoch(me.created)}</span>196 </div>197 </section>198199 {me.profile_source === "groupe-ka" && <HubProfileSection me={me} />}200201 <p className="profil-note">202 Votre <b>KA-ID</b> est votre identifiant unique dans l'écosystème{" "}203 <a href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer">204 Groupe KA205 </a>{" "}206 — il vous suit sur toutes les plateformes du groupe. Auto-Ka ne conserve207 que les informations de ce profil ; rien d'autre, et jamais revendues.208 </p>209210 {/* ——— Actions ——— */}211 <div className="profil-actions">212 <button213 className="btn ghost"214 onClick={async () => { await logout(); nav("/"); window.location.reload(); }}215 >216 Se déconnecter217 </button>218 </div>219 </div>220 );221}222