SPB Git

Repository Public

Profil.tsx 8.9 KB 239 lines tsx

1// -----------------------------------------------------------------------------2// Food-Ka — Agrégateur de produits d'épicerie (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//   informations du compte, actions (déconnexion, confidentialité)6// -----------------------------------------------------------------------------7import { useEffect, useState } from "react";8import { Link, useNavigate } from "react-router-dom";9import { Me, Socials, fetchAuth, logout } from "../api";1011const fmtEpoch = (ts: number | null | undefined): string => {12  if (!ts) return "—";13  return new Date(ts * 1000).toLocaleDateString("fr-CA", {14    day: "numeric", month: "long", year: "numeric",15  }).replace(/^1 /, "1ᵉʳ ");16};1718const providerLabel = (p: string): string =>19  p === "google" ? "KA ID — via Google"20  : p === "email" || p === "password" ? "KA ID — courriel et mot de passe"21  : "KA ID (groupe-ka.com)";2223// --- Profil Groupe KA (géré au hub — lecture seule ici) -----------------------24const SOCIAL_BASE: Record<string, string> = {25  instagram: "https://www.instagram.com/",26  facebook: "https://www.facebook.com/",27  x: "https://x.com/",28  linkedin: "https://www.linkedin.com/in/",29  tiktok: "https://www.tiktok.com/@",30  youtube: "https://www.youtube.com/@",31};3233/** « @pseudo » ou « pseudo » -> URL complète de la plateforme ; URL laissée telle quelle */34function socialUrl(key: string, value: string): string {35  const v = value.trim();36  if (/^https?:\/\//i.test(v)) return v;37  if (key === "website") return `https://${v}`;38  return (SOCIAL_BASE[key] ?? "https://") + v.replace(/^@/, "");39}4041const SOCIAL_FIELDS: { key: keyof Socials; label: string }[] = [42  { key: "instagram", label: "Instagram" },43  { key: "facebook", label: "Facebook" },44  { key: "x", label: "X (Twitter)" },45  { key: "linkedin", label: "LinkedIn" },46  { key: "tiktok", label: "TikTok" },47  { key: "youtube", label: "YouTube" },48];4950/** Profil géré au HUB Groupe KA — LECTURE SEULE (l'édition se fait sur51 *  groupe-ka.com/compte, une seule saisie pour les huit plateformes). */52function HubProfileSection({ me }: { me: Me }) {53  const socialLinks = SOCIAL_FIELDS.filter((s) => (me.socials?.[s.key] ?? "").trim());54  return (55    <section className="hub-profile">56      <h3>Mon profil Groupe KA</h3>57      {me.bio && <p className="hub-bio">{me.bio}</p>}58      <div className="pub-meta">59        {(me.job_title || me.company) && (60          <span className="stat-chip">61            {[me.job_title, me.company].filter(Boolean).join(" · ")}62          </span>63        )}64        {me.city && <span className="stat-chip">{me.city}</span>}65        {me.age != null && <span className="stat-chip">{me.age} ans</span>}66        {me.website && (67          <a className="stat-chip" href={socialUrl("website", me.website)}68             target="_blank" rel="noopener noreferrer">Site web ↗</a>69        )}70        {socialLinks.map((s) => (71          <a key={s.key} className="stat-chip"72             href={socialUrl(s.key, me.socials![s.key]!)}73             target="_blank" rel="noopener noreferrer" title={s.label}>74            {s.label}75          </a>76        ))}77      </div>78      <div>79        <a className="btn btn-primary"80           href="https://www.groupe-ka.com/compte"81           target="_blank" rel="noopener noreferrer">82          Modifier mon profil sur groupe-ka.com ↗83        </a>84      </div>85      <p className="pe-hint">86        Votre profil est géré au niveau du groupe : une seule saisie, visible87        sur les huit plateformes du groupe.88      </p>89    </section>90  );91}9293export default function ProfilPage() {94  const [me, setMe] = useState<Me | null | undefined>(undefined); // undefined = chargement95  const [copied, setCopied] = useState(false);96  const nav = useNavigate();9798  useEffect(() => {99    fetchAuth().then(({ user }) => setMe(user));100  }, []);101102  const copyKaId = async () => {103    if (!me?.ka_id) return;104    try {105      await navigator.clipboard.writeText(me.ka_id);106      setCopied(true);107      setTimeout(() => setCopied(false), 1800);108    } catch { /* presse-papiers indisponible : tant pis */ }109  };110111  if (me === undefined) {112    return <div className="container profil"><div className="notice">Chargement…</div></div>;113  }114115  if (me === null) {116    return (117      <div className="container profil">118        <span className="kicker">Mon compte</span>119        <h1>Connectez-vous pour <span className="hl">votre profil</span>.</h1>120        <p className="lede">121          Connectez-vous avec votre <b>KA ID</b> — l'identifiant de membre122          unique du Groupe KA, valide sur toutes les plateformes du groupe.123        </p>124        <div className="profil-actions">125          <a className="btn btn-primary" href="/api/auth/ka/login?next=/profil">126            Se connecter avec KA ID127          </a>128          <a129            className="btn btn-ghost"130            href="https://www.groupe-ka.com/connexion"131            target="_blank"132            rel="noopener noreferrer"133          >134            Créer un compte ↗135          </a>136        </div>137      </div>138    );139  }140141  return (142    <div className="container profil">143      <span className="kicker">Mon compte</span>144      <h1>145        {me.name ? <>Salut, <span className="hl">{me.name.split(" ")[0]}</span>.</>146                 : <>Votre <span className="hl">profil</span>.</>}147      </h1>148149      {/* ——— Carte de membre Groupe KA ——— */}150      <div className="pc" role="img" aria-label={`Carte de membre ${me.ka_id}`}>151        <div className="pc-watermark" aria-hidden="true">KA</div>152        <div className="pc-head">153          <span className="pc-brand">Groupe <span className="pc-ka">KA</span></span>154          <span className="pc-label">Carte de membre · Groupe KA</span>155        </div>156        <div className="pc-id-block">157          <span className="pc-id-label">KA-ID</span>158          <span className="pc-id">{me.ka_id}</span>159        </div>160        <div className="pc-foot">161          <div className="pc-holder">162            <span className="pc-holder-name">{me.name || me.email}</span>163            {me.role_label && (164              <span className="pc-role-badge">{me.role_label}</span>165            )}166            <span className="pc-holder-since">Membre depuis le {fmtEpoch(me.created_at)}</span>167          </div>168          {me.picture && (169            <img className="pc-avatar" src={me.picture} alt="" referrerPolicy="no-referrer" />170          )}171        </div>172        <div className="pc-strip" aria-hidden="true">173          {Array.from({ length: 28 }).map((_, i) => <i key={i} />)}174        </div>175      </div>176177      <button className={`btn btn-ghost pc-copy ${copied ? "ok" : ""}`} onClick={copyKaId}>178        {copied ? "✓ Copié" : "Copier mon KA-ID"}179      </button>180181      {/* ——— Informations ——— */}182      <section className="profil-grid">183        <div className="pg-item">184          <span className="pg-label">Nom</span>185          <span className="pg-value">{me.name || "—"}</span>186        </div>187        <div className="pg-item">188          <span className="pg-label">Courriel</span>189          <span className="pg-value">{me.email || "—"}</span>190        </div>191        <div className="pg-item">192          <span className="pg-label">Identifiant membre</span>193          <span className="pg-value mono">{me.ka_id}</span>194        </div>195        <div className="pg-item">196          <span className="pg-label">Connexion</span>197          <span className="pg-value">{providerLabel(me.provider)}</span>198        </div>199        <div className="pg-item">200          <span className="pg-label">Membre depuis</span>201          <span className="pg-value">{fmtEpoch(me.created_at)}</span>202        </div>203        <div className="pg-item">204          <span className="pg-label">Dernière connexion</span>205          <span className="pg-value">{fmtEpoch(me.last_login)}</span>206        </div>207      </section>208209      {me.profile_source === "groupe-ka" && <HubProfileSection me={me} />}210211      <p className="profil-note">212        Votre <b>KA-ID</b> est votre identifiant unique dans l'écosystème{" "}213        <a href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer">214          Groupe KA215        </a>{" "}216        — le même sur toutes les plateformes du groupe. Food·Ka ne conserve que217        les informations de ce profil ; rien d'autre, et jamais revendues.218      </p>219220      {/* ——— Actions ——— */}221      <div className="profil-actions">222        <button223          className="btn btn-ghost"224          onClick={async () => { await logout(); nav("/"); window.location.reload(); }}225        >226          Se déconnecter227        </button>228        <button229          className="btn btn-ghost"230          onClick={() => window.dispatchEvent(new Event("foodka:openConsent"))}231        >232          Gérer mes témoins233        </button>234        <Link className="btn btn-ghost" to="/confidentialite">Confidentialité</Link>235      </div>236    </div>237  );238}239
Files main
Menu

Theme