Lou·Ka — tous les logements à louer du Québec, un seul endroit.
HTML 98.9%
Python 0.6%
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