TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1import Link from "next/link";2import { requireUser } from "@/lib/auth/session";3import { myListings } from "@/lib/listings/service";4import { STATUS_LABEL, PROPERTY_TYPE_LABEL } from "@/lib/listings/types";5import { mediaUrl } from "@/lib/listings/media";6import { fmtPrice, fmtDate, fmtInt } from "@/lib/format";7import { Empty } from "@/components/ui";8import { ListingRowActions } from "./row-actions";9export const dynamic = "force-dynamic";10const TONE: Record<string, string> = { PUBLISHED: "bg-good-soft text-good", DRAFT: "bg-surface-2 text-ink-2", PENDING_REVIEW: "bg-warn-soft text-warn", PAUSED: "bg-info-soft text-info", REJECTED: "bg-brand-soft text-brand-dark", ARCHIVED: "bg-surface-2 text-ink-3", SOLD: "bg-surface-2 text-ink-3", RENTED: "bg-surface-2 text-ink-3" };11export default async function MesAnnonces() {12 const u = await requireUser(); const rows = await myListings(u.id);13 return (14 <div>15 <div className="flex items-end justify-between gap-3 mb-4"><h1 className="text-2xl md:text-3xl font-extrabold tracking-tight">Mes annonces</h1><Link href="/membre/annonces/nouvelle" className="tv-btn tv-btn-primary">+ Nouvelle annonce</Link></div>16 {rows.length === 0 ? <Empty title="Aucune annonce pour l'instant" hint="Publiez un logement à louer ou une propriété à vendre en quelques étapes. Les brouillons sont sauvegardés automatiquement." action={{ href: "/membre/annonces/nouvelle", label: "Publier une propriété" }} /> : (17 <ul className="space-y-3">{rows.map(({ l, p, mediaCount, cover }) => (18 <li key={l.id} className="tv-card p-3 flex gap-3">19 <Link href={`/membre/annonces/${l.id}`} className="w-28 h-20 shrink-0 rounded-sm overflow-hidden bg-surface-2 relative">{cover ? <img src={mediaUrl(cover, 320)} alt="" className="absolute inset-0 w-full h-full object-cover" /> : <span className="absolute inset-0 grid place-items-center text-[11px] text-ink-3">Sans photo</span>}</Link>20 <div className="min-w-0 flex-1">21 <div className="flex flex-wrap items-center gap-2"><span className={`tv-badge ${TONE[l.status]}`}>{STATUS_LABEL[l.status]}</span><span className="text-xs text-ink-3">{l.listingType === "RENT" ? "À louer" : "À vendre"} · {PROPERTY_TYPE_LABEL[p.propertyType]}</span></div>22 <Link href={`/membre/annonces/${l.id}`} className="block font-semibold truncate mt-1 hover:underline">{l.title ?? p.canonicalAddress ?? "Annonce sans titre"}</Link>23 <p className="text-xs text-ink-2 truncate">{p.canonicalAddress ? `${p.canonicalAddress}, ` : ""}{p.city ?? "Municipalité à préciser"} · {fmtPrice(l.priceAmount != null ? Number(l.priceAmount) : null, { rent: l.listingType === "RENT" })} · {mediaCount} photo{mediaCount > 1 ? "s" : ""} · {fmtInt(l.viewsCount)} vues</p>24 {l.moderationNote && l.status !== "PUBLISHED" && <p className="text-xs text-warn mt-1">Vérification : {l.moderationNote}</p>}25 <p className="text-[11px] text-ink-3 mt-1">Modifiée le {fmtDate(l.updatedAt.toISOString())}{l.publicationAt ? ` · publiée le ${fmtDate(l.publicationAt.toISOString())}` : ""}{l.expiresAt && l.status === "PUBLISHED" ? ` · expire le ${fmtDate(l.expiresAt.toISOString())}` : ""}</p>26 </div>27 <ListingRowActions id={l.id} slug={l.slug} status={l.status} listingType={l.listingType} />28 </li>))}</ul>29 )}30 </div>31 );32}33