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
1.8 KiB
TypeScript
66 lines
1.8 KiB
TypeScript
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>
|
||
);
|
||
}
|