Marketplace inversée (Next.js 16 App Router + Supabase Cloud) : auth, schéma + RLS, publication de besoins avec upload d'images, feed avec filtres, offres, acceptation via RPC, messagerie temps réel, dashboards, et Dockerfile pour déploiement Coolify.
66 lines
2.2 KiB
TypeScript
66 lines
2.2 KiB
TypeScript
import Link from "next/link";
|
|
import { verifySession } from "@/lib/dal";
|
|
import { createClient } from "@/lib/supabase/server";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Card, CardContent } from "@/components/ui/card";
|
|
|
|
const STATUS_LABEL: Record<string, string> = {
|
|
pending: "En attente",
|
|
accepted: "Acceptée",
|
|
rejected: "Refusée",
|
|
withdrawn: "Retirée",
|
|
};
|
|
|
|
export default async function MyOffersPage() {
|
|
const user = await verifySession();
|
|
const supabase = await createClient();
|
|
|
|
const { data: offers } = await supabase
|
|
.from("offers")
|
|
.select("id, title, price, status, created_at, need_id, needs(title, city)")
|
|
.eq("merchant_id", user.id)
|
|
.order("created_at", { ascending: false });
|
|
|
|
return (
|
|
<div>
|
|
<h1 className="text-2xl font-semibold">Mes offres</h1>
|
|
|
|
{!offers || offers.length === 0 ? (
|
|
<p className="mt-6 text-muted-foreground">
|
|
Tu n'as pas encore envoyé d'offre.
|
|
</p>
|
|
) : (
|
|
<div className="mt-6 flex flex-col gap-3">
|
|
{offers.map((offer) => {
|
|
const need = Array.isArray(offer.needs)
|
|
? offer.needs[0]
|
|
: offer.needs;
|
|
return (
|
|
<Link key={offer.id} href={`/needs/${offer.need_id}`}>
|
|
<Card className="transition-shadow hover:shadow-md">
|
|
<CardContent className="flex flex-col gap-2 py-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div className="min-w-0">
|
|
<p className="truncate font-medium">{offer.title}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{need?.title} · {need?.city} · {offer.price} €
|
|
</p>
|
|
</div>
|
|
<Badge
|
|
className="w-fit"
|
|
variant={
|
|
offer.status === "accepted" ? "default" : "outline"
|
|
}
|
|
>
|
|
{STATUS_LABEL[offer.status] ?? offer.status}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|