Initial commit: WaMarket MVP

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.
This commit is contained in:
2026-07-30 14:33:17 +00:00
commit af81124d48
82 changed files with 16824 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import Link from "next/link";
import Image from "next/image";
import { Badge } from "@/components/ui/badge";
import { Card, CardContent } from "@/components/ui/card";
type NeedCardData = {
id: string;
title: string;
city: string;
region: string | null;
budget_min: number | null;
budget_max: number | null;
categories: { name: string } | { name: string }[] | null;
need_images: { storage_path: string; position: number }[] | null;
};
export function NeedCard({
need,
getImageUrl,
}: {
need: NeedCardData;
getImageUrl: (path: string) => string;
}) {
const category = Array.isArray(need.categories)
? need.categories[0]
: need.categories;
const images = [...(need.need_images ?? [])].sort(
(a, b) => a.position - b.position
);
const cover = images[0];
return (
<Link href={`/needs/${need.id}`}>
<Card className="h-full transition-shadow hover:shadow-md">
{cover && (
<div className="relative h-40 w-full overflow-hidden rounded-t-xl">
<Image
src={getImageUrl(cover.storage_path)}
alt={need.title}
fill
className="object-cover"
/>
</div>
)}
<CardContent className="flex flex-col gap-2 pt-4">
{category?.name && (
<Badge variant="secondary" className="w-fit">
{category.name}
</Badge>
)}
<h3 className="line-clamp-2 font-medium">{need.title}</h3>
<p className="text-sm text-muted-foreground">
{need.city}
{need.region ? `, ${need.region}` : ""}
</p>
{(need.budget_min || need.budget_max) && (
<p className="text-sm">
{need.budget_min ?? "?"} {need.budget_max ?? "?"}
</p>
)}
</CardContent>
</Card>
</Link>
);
}