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.
70 lines
2.3 KiB
TypeScript
70 lines
2.3 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";
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
const STATUS_LABEL: Record<string, string> = {
|
|
open: "Ouvert",
|
|
closed: "Fermé",
|
|
fulfilled: "Pourvu",
|
|
};
|
|
|
|
export default async function MyNeedsPage() {
|
|
const user = await verifySession();
|
|
const supabase = await createClient();
|
|
|
|
const { data: needs } = await supabase
|
|
.from("needs")
|
|
.select("id, title, city, status, created_at, categories(name)")
|
|
.eq("author_id", user.id)
|
|
.order("created_at", { ascending: false });
|
|
|
|
return (
|
|
<div>
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-2xl font-semibold">Mes besoins</h1>
|
|
<Button asChild>
|
|
<Link href="/needs/new">Nouveau besoin</Link>
|
|
</Button>
|
|
</div>
|
|
|
|
{!needs || needs.length === 0 ? (
|
|
<p className="mt-6 text-muted-foreground">
|
|
Tu n'as pas encore publié de besoin.
|
|
</p>
|
|
) : (
|
|
<div className="mt-6 flex flex-col gap-3">
|
|
{needs.map((need) => {
|
|
const category = Array.isArray(need.categories)
|
|
? need.categories[0]
|
|
: need.categories;
|
|
return (
|
|
<Link key={need.id} href={`/needs/${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">{need.title}</p>
|
|
<p className="text-sm text-muted-foreground">
|
|
{need.city}
|
|
{category?.name ? ` · ${category.name}` : ""}
|
|
</p>
|
|
</div>
|
|
<Badge
|
|
className="w-fit"
|
|
variant={need.status === "open" ? "default" : "outline"}
|
|
>
|
|
{STATUS_LABEL[need.status] ?? need.status}
|
|
</Badge>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|