Files
softgrey af81124d48 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.
2026-07-30 14:33:17 +00:00

110 lines
3.2 KiB
TypeScript

import Image from "next/image";
import { acceptOffer } from "@/lib/actions/offers";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
type Offer = {
id: string;
title: string;
description: string;
price: number;
status: string;
merchant_id: string;
profiles: { full_name: string | null } | { full_name: string | null }[] | null;
offer_images: { storage_path: string; position: number }[] | null;
};
const STATUS_LABEL: Record<string, string> = {
pending: "En attente",
accepted: "Acceptée",
rejected: "Refusée",
withdrawn: "Retirée",
};
export function OfferList({
offers,
needId,
needStatus,
isOwner,
getImageUrl,
}: {
offers: Offer[];
needId: string;
needStatus: string;
isOwner: boolean;
getImageUrl: (path: string) => string;
}) {
if (offers.length === 0) {
return (
<p className="mt-4 text-sm text-muted-foreground">
Aucune offre reçue pour le moment.
</p>
);
}
return (
<div className="mt-4 flex flex-col gap-4">
{offers.map((offer) => {
const merchant = Array.isArray(offer.profiles)
? offer.profiles[0]
: offer.profiles;
const images = [...(offer.offer_images ?? [])].sort(
(a, b) => a.position - b.position
);
return (
<Card key={offer.id}>
<CardContent className="flex flex-col gap-2 pt-4">
<div className="flex items-center justify-between">
<span className="font-medium">
{merchant?.full_name ?? "Commerçant"}
</span>
<Badge
variant={offer.status === "accepted" ? "default" : "outline"}
>
{STATUS_LABEL[offer.status] ?? offer.status}
</Badge>
</div>
<h4 className="font-medium">{offer.title}</h4>
<p className="text-sm text-muted-foreground">
{offer.description}
</p>
<p className="font-semibold">{offer.price} </p>
{images.length > 0 && (
<div className="mt-1 flex flex-wrap gap-2">
{images.map((img) => (
<div
key={img.storage_path}
className="relative h-16 w-16 overflow-hidden rounded-md border"
>
<Image
src={getImageUrl(img.storage_path)}
alt={offer.title}
fill
className="object-cover"
/>
</div>
))}
</div>
)}
{isOwner && needStatus === "open" && offer.status === "pending" && (
<form
action={acceptOffer.bind(null, offer.id, needId)}
className="mt-2"
>
<Button type="submit" size="sm">
Accepter cette offre
</Button>
</form>
)}
</CardContent>
</Card>
);
})}
</div>
);
}