SPB Git forge

spb/lou-ka

Public

Lou·Ka — tous les logements à louer du Québec, un seul endroit.

232commits 1branches 0releases
172.9 MBsize
maindefault branch
2 days agolast push
HTML 98.9% Python 0.6%
6.6 KB · 167 lines tsx
Raw Blame History
1// -----------------------------------------------------------------------------2// Lou-Ka — Agrégateur de logements à louer (province de Québec)3// Auteur : Simon-Pierre Boucher — contact@spboucher.ai4// pages/PublicProfile.tsx : profil membre PUBLIC — /u/{ka_id}5//   Carte de membre partageable (nom, avatar, ancienneté, KA-ID) — jamais le6//   courriel. Visible seulement si le membre a activé son profil public.7// -----------------------------------------------------------------------------8import { useEffect, useState } from "react";9import { Link, useParams } from "react-router-dom";10import { PublicProfile, Socials, fetchPublicProfile } from "../api";11import {12  IcoCompass, IcoFacebook, IcoInstagram, IcoLinkedIn, IcoTikTok, IcoX,13  IcoYouTube,14} from "../components/Icons";1516const SOCIAL_META: { key: keyof Socials; label: string; icon: JSX.Element }[] = [17  { key: "instagram", label: "Instagram", icon: <IcoInstagram size={17} /> },18  { key: "facebook", label: "Facebook", icon: <IcoFacebook size={17} /> },19  { key: "x", label: "X (Twitter)", icon: <IcoX size={17} /> },20  { key: "linkedin", label: "LinkedIn", icon: <IcoLinkedIn size={17} /> },21  { key: "tiktok", label: "TikTok", icon: <IcoTikTok size={17} /> },22  { key: "youtube", label: "YouTube", icon: <IcoYouTube size={17} /> },23];2425const SOCIAL_BASE: Record<string, string> = {26  instagram: "https://www.instagram.com/",27  facebook: "https://www.facebook.com/",28  x: "https://x.com/",29  linkedin: "https://www.linkedin.com/in/",30  tiktok: "https://www.tiktok.com/@",31  youtube: "https://www.youtube.com/@",32};3334/** « @pseudo » ou « pseudo » -> URL complète de la plateforme ; URL laissée telle quelle */35function socialUrl(key: string, value: string): string {36  const v = value.trim();37  if (/^https?:\/\//i.test(v)) return v;38  if (key === "website") return `https://${v}`;39  return (SOCIAL_BASE[key] ?? "https://") + v.replace(/^@/, "");40}4142const fmtEpoch = (ts: number | null | undefined): string => {43  if (!ts) return "—";44  return new Date(ts * 1000).toLocaleDateString("fr-CA", {45    day: "numeric", month: "long", year: "numeric",46  }).replace(/^1 /, "1ᵉʳ ");47};4849export default function PublicProfilePage() {50  const { kaId = "" } = useParams();51  const [profile, setProfile] = useState<PublicProfile | null | undefined>(undefined);5253  useEffect(() => {54    fetchPublicProfile(kaId).then(setProfile).catch(() => setProfile(null));55  }, [kaId]);5657  if (profile === undefined) {58    return <div className="container profil"><div className="notice">Chargement…</div></div>;59  }6061  if (profile === null) {62    return (63      <div className="container notice">64        <div className="big"><IcoCompass size={40} /></div>65        <h2>Profil introuvable</h2>66        <p>Ce membre n'existe pas, ou son profil n'est pas public.</p>67        <Link className="btn btn-primary" to="/">Explorer les logements</Link>68      </div>69    );70  }7172  return (73    <div className="container profil">74      <span className="kicker">Profil membre</span>75      <h1>76        <span className="hl">{profile.name || "Membre Lou-Ka"}</span>77      </h1>7879      <div className="pc" role="img" aria-label={`Carte de membre ${profile.ka_id}`}>80        <div className="pc-watermark" aria-hidden="true">KA</div>81        <div className="pc-head">82          <span className="pc-brand">Groupe <span className="pc-ka">KA</span></span>83          <span className="pc-label">Carte de membre · Groupe KA</span>84        </div>85        <div className="pc-id-block">86          <span className="pc-id-label">KA-ID</span>87          <span className="pc-id">{profile.ka_id}</span>88        </div>89        <div className="pc-foot">90          <div className="pc-holder">91            <span className="pc-holder-name">{profile.name || "Membre Lou-Ka"}</span>92            {profile.role_label && (93              <span className="pc-role-badge">{profile.role_label}</span>94            )}95            <span className="pc-holder-since">Membre depuis le {fmtEpoch(profile.created_at)}</span>96          </div>97          {profile.picture && (98            <img className="pc-avatar" src={profile.picture} alt="" referrerPolicy="no-referrer" />99          )}100        </div>101        <div className="pc-strip" aria-hidden="true">102          {Array.from({ length: 28 }).map((_, i) => <i key={i} />)}103        </div>104      </div>105106      {(profile.bio || profile.city || profile.role || profile.job_title107        || profile.company || profile.role_label) && (108        <section className="pub-about">109          {profile.bio && <p className="pub-bio">{profile.bio}</p>}110          <div className="pub-meta">111            {(profile.job_title || profile.company) && (112              <span className="stat-chip">113                {[profile.job_title, profile.company].filter(Boolean).join(" · ")}114              </span>115            )}116            {profile.role_label && (117              <span className="stat-chip">{profile.role_label}</span>118            )}119            {profile.city && <span className="stat-chip">{profile.city}</span>}120            {profile.age != null && (121              <span className="stat-chip">{profile.age} ans</span>122            )}123            {profile.role === "gestionnaire" && (124              <span className="stat-chip">Gestionnaire de logements</span>125            )}126            {profile.role === "locataire" && (127              <span className="stat-chip">À la recherche d'un logement</span>128            )}129            {profile.website && (130              <a className="stat-chip" href={socialUrl("website", profile.website)}131                 target="_blank" rel="noopener noreferrer">Site web ↗</a>132            )}133          </div>134        </section>135      )}136137      {Object.keys(profile.socials || {}).length > 0 && (138        <div className="pub-socials">139          {SOCIAL_META.filter((s) => profile.socials[s.key]).map((s) => (140            <a141              key={s.key}142              className="pub-social"143              href={socialUrl(s.key, profile.socials[s.key]!)}144              target="_blank" rel="noopener noreferrer"145              aria-label={s.label} title={s.label}146            >147              {s.icon}148            </a>149          ))}150        </div>151      )}152153      <p className="profil-note">154        Membre de <b>Lou-Ka</b>, l'agrégateur de logements à louer du{" "}155        <a href="https://www.groupe-ka.com" target="_blank" rel="noopener noreferrer">156          Groupe KA157        </a>.158      </p>159160      <div className="profil-actions">161        <Link className="btn btn-primary" to="/">Explorer les logements</Link>162        <a className="btn btn-ghost" href="https://www.groupe-ka.com/connexion" target="_blank" rel="noopener noreferrer">Créer mon compte</a>163      </div>164    </div>165  );166}167