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:
@@ -0,0 +1,58 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { verifySession } from "@/lib/dal";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { ChatWindow } from "@/components/messages/chat-window";
|
||||
|
||||
export default async function ConversationPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ conversationId: string }>;
|
||||
}) {
|
||||
const user = await verifySession();
|
||||
const { conversationId } = await params;
|
||||
const supabase = await createClient();
|
||||
|
||||
const { data: conversation } = await supabase
|
||||
.from("conversations")
|
||||
.select(
|
||||
"id, author_id, merchant_id, needs(title), author:profiles!conversations_author_id_fkey(full_name), merchant:profiles!conversations_merchant_id_fkey(full_name)"
|
||||
)
|
||||
.eq("id", conversationId)
|
||||
.single();
|
||||
|
||||
if (!conversation) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const { data: messages } = await supabase
|
||||
.from("messages")
|
||||
.select("id, conversation_id, sender_id, body, created_at")
|
||||
.eq("conversation_id", conversationId)
|
||||
.order("created_at", { ascending: true });
|
||||
|
||||
const isAuthor = conversation.author_id === user.id;
|
||||
const other = isAuthor ? conversation.merchant : conversation.author;
|
||||
const otherName = Array.isArray(other) ? other[0]?.full_name : other?.full_name;
|
||||
const need = Array.isArray(conversation.needs)
|
||||
? conversation.needs[0]
|
||||
: conversation.needs;
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-8 sm:px-6 sm:py-10">
|
||||
<Link href="/messages" className="text-sm text-muted-foreground underline">
|
||||
← Toutes les conversations
|
||||
</Link>
|
||||
<h1 className="mt-2 text-2xl font-semibold">{otherName ?? "Utilisateur"}</h1>
|
||||
<p className="text-sm text-muted-foreground">{need?.title}</p>
|
||||
|
||||
<div className="mt-6">
|
||||
<ChatWindow
|
||||
conversationId={conversationId}
|
||||
currentUserId={user.id}
|
||||
initialMessages={messages ?? []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import Link from "next/link";
|
||||
import { verifySession } from "@/lib/dal";
|
||||
import { createClient } from "@/lib/supabase/server";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
|
||||
export default async function MessagesPage() {
|
||||
const user = await verifySession();
|
||||
const supabase = await createClient();
|
||||
|
||||
const { data: conversations } = await supabase
|
||||
.from("conversations")
|
||||
.select(
|
||||
"id, created_at, author_id, merchant_id, needs(title), author:profiles!conversations_author_id_fkey(full_name), merchant:profiles!conversations_merchant_id_fkey(full_name)"
|
||||
)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
return (
|
||||
<div className="mx-auto max-w-2xl px-4 py-8 sm:px-6 sm:py-10">
|
||||
<h1 className="text-2xl font-semibold">Messages</h1>
|
||||
|
||||
{!conversations || conversations.length === 0 ? (
|
||||
<p className="mt-6 text-muted-foreground">
|
||||
Aucune conversation pour le moment. Elles apparaissent ici une fois
|
||||
qu'une offre est acceptée.
|
||||
</p>
|
||||
) : (
|
||||
<div className="mt-6 flex flex-col gap-3">
|
||||
{conversations.map((conv) => {
|
||||
const isAuthor = conv.author_id === user.id;
|
||||
const other = isAuthor ? conv.merchant : conv.author;
|
||||
const otherName = Array.isArray(other)
|
||||
? other[0]?.full_name
|
||||
: other?.full_name;
|
||||
const need = Array.isArray(conv.needs) ? conv.needs[0] : conv.needs;
|
||||
|
||||
return (
|
||||
<Link key={conv.id} href={`/messages/${conv.id}`}>
|
||||
<Card className="transition-shadow hover:shadow-md">
|
||||
<CardContent className="min-w-0 py-4">
|
||||
<p className="truncate font-medium">
|
||||
{otherName ?? "Utilisateur"}
|
||||
</p>
|
||||
<p className="truncate text-sm text-muted-foreground">
|
||||
{need?.title}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user