TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
1import Link from "next/link";2import { ListingCardView } from "@/components/listing-card";3import { Section } from "@/components/ui";4import { dbAvailable, meta } from "@/lib/db";5import { featured, regionMetrics, topPlaces } from "@/lib/queries";6import { fmtInt, fmtPrice } from "@/lib/format";7import { IconCalc, IconChart, IconHome, IconPin, IconSpark } from "@/components/icons";8import { HomeTabs } from "./home-tabs";910export const dynamic = "force-dynamic";1112export default function Home() {13 const ok = dbAvailable();14 const sale = ok ? featured("sale", 8) : []; const rent = ok ? featured("rent", 4) : [];15 const nSale = ok ? Number(meta("listings_sale")) : 0; const nRent = ok ? Number(meta("listings_rent")) : 0;16 const regions = ok ? regionMetrics("sale").slice(0, 8) : []; const cities = ok ? topPlaces("city", "sale", 12) : [];17 return (18 <>19 <section className="relative overflow-hidden">20 <div className="absolute inset-0 -z-10 bg-[radial-gradient(1200px_500px_at_70%_-10%,#fdeef0_0%,transparent_60%)]" />21 <div className="mx-auto max-w-7xl px-4 pt-10 pb-8 md:pt-16 md:pb-14 grid md:grid-cols-[1.1fr_1fr] gap-8 items-center">22 <div>23 {ok && (24 <p className="mb-4 inline-flex flex-wrap items-baseline gap-x-2 rounded-card border border-line bg-white/80 px-4 py-2.5 shadow-card" aria-live="polite">25 <span className="tv-price text-3xl md:text-4xl text-brand">{fmtInt(nSale + nRent)}</span>26 <span className="text-sm md:text-base font-semibold text-ink">propriétés à vendre et à louer au Québec, réunies ici</span>27 <span className="basis-full text-xs text-ink-3">{fmtInt(nSale)} à vendre · {fmtInt(nRent)} à louer · mis à jour chaque nuit</span>28 </p>29 )}30 <h1 className="text-4xl md:text-6xl font-extrabold tracking-tight leading-[1.02]">L'immobilier,<br /><span className="text-brand">enfin réuni.</span></h1>31 <p className="mt-4 text-ink-2 text-lg max-w-lg">Achetez, louez et découvrez la juste valeur des propriétés au Québec. Annonces, données de marché, comparables et estimation transparente, au même endroit.</p>32 <div className="mt-6 tv-card p-3 md:p-4"><HomeTabs /></div>33 {!ok && <p className="mt-3 text-xs text-ink-3">Données en cours de chargement…</p>}34 </div>35 <div className="hidden md:grid grid-cols-2 gap-3">36 {sale.slice(0, 4).map((c, i) => <ListingCardView key={c.uid} c={c} priority={i < 2} />)}37 </div>38 </div>39 </section>4041 <section className="mx-auto max-w-7xl px-4 -mt-2 grid grid-cols-2 md:grid-cols-4 gap-3">42 {[{ href: "/recherche?marche=achat", Icon: IconHome, t: "Maisons à vendre", d: `${fmtInt(nSale)} propriétés` }, { href: "/recherche?marche=location", Icon: IconPin, t: "Logements à louer", d: `${fmtInt(nRent)} appartements et condos` }, { href: "/estimer", Icon: IconChart, t: "Estimations", d: "Basées sur de vraies ventes" }, { href: "/carte", Icon: IconSpark, t: "Partout au Québec", d: "Carte et secteurs interactifs" }].map(({ href, Icon, t, d }) => (43 <Link key={href} href={href} className="tv-card p-4 flex items-center gap-3 hover:border-brand/40 transition"><span className="p-2 rounded-sm bg-brand-soft text-brand-dark"><Icon /></span><span><span className="block font-semibold text-sm">{t}</span><span className="block text-xs text-ink-3">{d}</span></span></Link>44 ))}45 </section>4647 {ok && (48 <Section title="Propriétés en vedette" action={{ href: "/recherche?marche=achat", label: "Voir tout" }} className="mt-12">49 <div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">{sale.slice(0, 8).map((c) => <ListingCardView key={c.uid} c={c} />)}</div>50 </Section>51 )}52 {ok && rent.length > 0 && (53 <Section title="Nouveautés à louer" action={{ href: "/recherche?marche=location", label: "Voir tout" }} className="mt-12">54 <div className="grid grid-cols-2 md:grid-cols-4 gap-3 md:gap-4">{rent.map((c) => <ListingCardView key={c.uid} c={c} />)}</div>55 </Section>56 )}57 {ok && (58 <Section title="Le marché par région" action={{ href: "/marche", label: "Toutes les données" }} className="mt-12">59 <div className="grid grid-cols-2 md:grid-cols-4 gap-3">60 {regions.map((r) => (61 <Link key={r.scope_value} href={`/recherche?marche=achat®ion=${encodeURIComponent(r.scope_value)}`} className="tv-card p-4 hover:border-brand/40 transition">62 <p className="font-semibold text-sm truncate">{r.scope_value}</p>63 <p className="tv-price text-xl mt-1">{fmtPrice(r.median_price)}</p>64 <p className="text-xs text-ink-3">prix médian demandé · {fmtInt(r.n)} annonces</p>65 </Link>66 ))}67 </div>68 <div className="mt-4 flex flex-wrap gap-2">{cities.map((c) => <Link key={c.name} href={`/recherche?marche=achat&ville=${encodeURIComponent(c.name)}`} className="tv-chip">{c.name} <span className="text-ink-3">{fmtInt(c.n_sale)}</span></Link>)}</div>69 </Section>70 )}7172 <section className="mx-auto max-w-7xl px-4 mt-14">73 <div className="tv-card p-6 md:p-10 grid md:grid-cols-[1fr_auto] gap-6 items-center bg-[linear-gradient(135deg,#fff_0%,#fdeef0_100%)]">74 <div>75 <h2 className="text-2xl md:text-3xl font-extrabold tracking-tight">De meilleures décisions immobilières pour un meilleur chez-soi au Québec.</h2>76 <p className="mt-2 text-ink-2 max-w-2xl">Toveli réunit l'essentiel : des annonces reliées à leur source, des données de marché, des comparables réels, un calculateur de financement et un assistant IA qui cite ses sources et dit ce qu'il ne sait pas.</p>77 <ul className="mt-4 grid sm:grid-cols-2 gap-2 text-sm text-ink-2">78 {["Des milliers de propriétés du Québec, à vendre et à louer", "Estimations avec fourchette, méthode et niveau de confiance", "Recherche par carte et par quartier", "Assistant IA ancré dans les données, jamais inventé"].map((t) => <li key={t} className="flex gap-2"><span className="text-brand">✓</span>{t}</li>)}79 </ul>80 </div>81 <div className="flex flex-col gap-2"><Link href="/estimer" className="tv-btn tv-btn-primary"><IconChart width={18} height={18} /> Estimer une propriété</Link><Link href="/financement" className="tv-btn tv-btn-ghost"><IconCalc width={18} height={18} /> Calculer un budget</Link></div>82 </div>83 </section>84 </>85 );86}87