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%
7.7 KB · 219 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/Gestion.tsx : tableau de bord gestionnaire — réclamer SA page (source)5//   puis la personnaliser (accroche, description, coordonnées, logo). La page6//   publique correspondante vit sur /g/{source}.7// -----------------------------------------------------------------------------8import { useEffect, useMemo, useState } from "react";9import { Link } from "react-router-dom";10import {11  Source, claimOrg, fetchMyOrg, fetchSources, updateOrg,12} from "../api";13import { useAccount } from "../account";14import { IcoBuilding } from "../components/Icons";1516interface OrgMine {17  source: string | null;18  name?: string;19  active_listings?: number;20  profile?: Record<string, string>;21}2223export default function GestionPage() {24  const { me, loaded, refresh } = useAccount();25  const [org, setOrg] = useState<OrgMine | null>(null);26  const [sources, setSources] = useState<Source[]>([]);27  const [pick, setPick] = useState("");28  const [saving, setSaving] = useState(false);29  const [saved, setSaved] = useState(false);30  const [error, setError] = useState("");31  const [form, setForm] = useState({32    tagline: "", description: "", website: "", phone: "", email: "", logo_url: "",33  });3435  useEffect(() => {36    if (me) fetchMyOrg().then((o) => {37      setOrg(o);38      const p = o.profile ?? {};39      setForm({40        tagline: p.tagline ?? "", description: p.description ?? "",41        website: p.website ?? "", phone: p.phone ?? "",42        email: p.email ?? "", logo_url: p.logo_url ?? "",43      });44    }).catch(() => setOrg({ source: null }));45  }, [me]);4647  useEffect(() => {48    if (me && org && !org.source) {49      fetchSources().then((r) => setSources(r.sources)).catch(() => {});50    }51  }, [me, org]);5253  const claimable = useMemo(54    () => sources55      .filter((s) => s.connector && s.active_listings > 0)56      .sort((a, b) => a.name.localeCompare(b.name, "fr")),57    [sources]);5859  if (loaded && !me) {60    return (61      <div className="container profil">62        <span className="kicker">Espace gestionnaire</span>63        <h1>Connectez-vous pour gérer <span className="hl">votre page</span>.</h1>64        <a className="btn btn-primary" href="/api/auth/ka/login">65          Se connecter avec KA ID66        </a>67      </div>68    );69  }7071  if (me && me.role !== "gestionnaire") {72    return (73      <div className="container profil">74        <span className="kicker">Espace gestionnaire</span>75        <h1>Réservé aux <span className="hl">gestionnaires</span>.</h1>76        <p className="lede">77          Votre compte est en mode locataire. Si vous gérez des logements78          affichés sur Lou-Ka, changez de profil.79        </p>80        <Link className="btn btn-primary" to="/bienvenue">Changer de profil</Link>81      </div>82    );83  }8485  const doClaim = async () => {86    if (!pick) return;87    setError("");88    const res = await claimOrg(pick);89    if (!res.ok) {90      setError(res.status === 40991        ? "Cette page est déjà réclamée par un autre compte."92        : "Impossible de réclamer cette page.");93      return;94    }95    refresh();96    const o = await fetchMyOrg();97    setOrg(o);98  };99100  const doSave = async () => {101    setSaving(true);102    setError("");103    const res = await updateOrg(form);104    setSaving(false);105    if (!res.ok) { setError("Enregistrement impossible — réessayez."); return; }106    setSaved(true);107    setTimeout(() => setSaved(false), 2000);108  };109110  return (111    <div className="container profil">112      <span className="kicker">Espace gestionnaire</span>113114      {org && !org.source && (115        <>116          <h1>Réclamez la page de <span className="hl">votre gestion</span>.</h1>117          <p className="lede">118            Choisissez votre organisation parmi les sources affichées sur119            Lou-Ka. Vous pourrez ensuite personnaliser sa page publique —120            votre inventaire y est déjà relié automatiquement.121          </p>122          <div className="claim-row">123            <select value={pick} onChange={(e) => setPick(e.target.value)}>124              <option value="">— Votre gestion immobilière —</option>125              {claimable.map((s) => (126                <option key={s.id} value={s.id}>127                  {s.name} ({s.active_listings} logements)128                </option>129              ))}130            </select>131            <button className="btn btn-primary" disabled={!pick} onClick={doClaim}>132              Réclamer cette page133            </button>134          </div>135          {error && <p className="claim-error">{error}</p>}136          <p className="profil-note">137            Vous ne trouvez pas votre organisation ? Écrivez-nous à{" "}138            <a href="mailto:contact@groupe-ka.com">contact@groupe-ka.com</a> et139            nous ajouterons votre site comme source.140          </p>141        </>142      )}143144      {org && org.source && (145        <>146          <h1><span className="hl">{org.name}</span></h1>147          <div className="org-head">148            <span className="org-stat">149              <IcoBuilding size={15} /> <b>{org.active_listings ?? 0}</b> logements actifs reliés150            </span>151            <Link className="btn btn-ghost" to={`/g/${org.source}`}>152              Voir ma page publique ↗153            </Link>154          </div>155156          <section className="org-form">157            <h3>Personnaliser ma page</h3>158            <label>159              <span>Accroche (140 caractères)</span>160              <input161                value={form.tagline} maxLength={140}162                placeholder="Ex. Des logements bien entretenus à Québec depuis 1998"163                onChange={(e) => setForm({ ...form, tagline: e.target.value })}164              />165            </label>166            <label>167              <span>Description</span>168              <textarea169                value={form.description} rows={5} maxLength={2000}170                placeholder="Présentez votre gestion : secteurs, services, valeurs…"171                onChange={(e) => setForm({ ...form, description: e.target.value })}172              />173            </label>174            <div className="org-form-row">175              <label>176                <span>Site web</span>177                <input178                  value={form.website} placeholder="https://…"179                  onChange={(e) => setForm({ ...form, website: e.target.value })}180                />181              </label>182              <label>183                <span>Téléphone</span>184                <input185                  value={form.phone} placeholder="418 555-0123"186                  onChange={(e) => setForm({ ...form, phone: e.target.value })}187                />188              </label>189            </div>190            <div className="org-form-row">191              <label>192                <span>Courriel public</span>193                <input194                  value={form.email} placeholder="location@…"195                  onChange={(e) => setForm({ ...form, email: e.target.value })}196                />197              </label>198              <label>199                <span>Logo (URL d'image)</span>200                <input201                  value={form.logo_url} placeholder="https://…/logo.png"202                  onChange={(e) => setForm({ ...form, logo_url: e.target.value })}203                />204              </label>205            </div>206            {error && <p className="claim-error">{error}</p>}207            <button208              className={`btn btn-primary ${saved ? "pc-copy ok" : ""}`}209              disabled={saving} onClick={doSave}210            >211              {saved ? "✓ Enregistré" : saving ? "Enregistrement…" : "Enregistrer"}212            </button>213          </section>214        </>215      )}216    </div>217  );218}219