"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import type { Asset, MarketListing, MarketNegotiation } from "@/payload-types"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { formatAmount } from "@/lib/banking/format"; import { acceptMarketOffer, cancelMarketOffer, counterMarketOffer, rejectMarketOffer, } from "@/app/(frontend)/logistics/market/actions"; import { HandshakeIcon } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { PatienceMeter } from "@/components/frontend/market/PatienceMeter"; import { NegotiationHistoryDialog } from "@/components/frontend/market/NegotiationHistoryDialog"; const STATUS_LABELS: Record = { open: "Open", accepted: "Accepted", rejected: "Rejected", closed: "Closed", cancelled: "Cancelled", expired: "Expired", }; function listingOf(negotiation: MarketNegotiation): MarketListing | null { return typeof negotiation.listing === "object" && negotiation.listing ? (negotiation.listing as MarketListing) : null; } function assetName(listing: MarketListing | null): string { if (!listing) return "Unknown item"; const asset = listing.asset as Asset; return typeof asset === "object" && asset ? asset.name : `Asset #${listing.asset}`; } function counterpartyLabel( negotiation: MarketNegotiation, role: "buyer" | "seller", ): string { if (role === "buyer") { const listing = listingOf(negotiation); if (listing?.isAutoGenerated) { const npc = listing.npc as unknown; if (typeof npc === "object" && npc && "name" in (npc as object)) { return (npc as { name: string }).name; } return "Market vendor"; } const seller = listing?.seller as unknown; if (typeof seller === "object" && seller && "username" in (seller as object)) { return (seller as { username?: string; displayName?: string }).displayName || (seller as { username?: string }).username || "Seller"; } return "Seller"; } const buyer = negotiation.buyer as unknown; if (typeof buyer === "object" && buyer && "username" in (buyer as object)) { return (buyer as { username?: string }).username ?? "Buyer"; } return "Buyer"; } function NegotiationRow({ negotiation, role, currencyLabel, }: { negotiation: MarketNegotiation; role: "buyer" | "seller"; currencyLabel: string | null; }) { const router = useRouter(); const [busy, setBusy] = useState(null); const [counterOpen, setCounterOpen] = useState(false); const [counter, setCounter] = useState(""); const [error, setError] = useState(null); const listing = listingOf(negotiation); const isOpen = negotiation.status === "open"; const theirProposal = (role === "buyer" && negotiation.proposedBy === "seller") || (role === "seller" && negotiation.proposedBy === "buyer"); const fmt = (n: number) => formatAmount(n, { name: currencyLabel ?? undefined }); async function run(action: () => Promise<{ success: boolean; error?: string }>, key: string) { setBusy(key); setError(null); const res = await action(); if (res.success) { if (key === "accept") { const qty = negotiation.quantity ?? listing?.quantity ?? 1; toast.success("Deal closed", { description: `Sold ${qty}× at ${fmt(negotiation.amount ?? 0)} each — total ${fmt((negotiation.amount ?? 0) * qty)}.`, }); } router.refresh(); } else { setError(res.error ?? "Something went wrong."); } setBusy(null); } function handleCounter() { const value = Number(counter); if (!Number.isFinite(value) || value <= 0) { setError("Enter a valid amount."); return; } void run(() => counterMarketOffer(negotiation.id, value), "counter"); } const showActions = isOpen && theirProposal; return (
{assetName(listing)} {counterpartyLabel(negotiation, role)} · {listing?.quantity ?? "?"}× in stock
{STATUS_LABELS[negotiation.status] ?? negotiation.status}
{fmt(negotiation.amount ?? 0)} {negotiation.quantity ? `${negotiation.quantity}× · ` : ""} {theirProposal ? role === "buyer" ? "their counteroffer" : "buyer's offer" : "your offer"} {negotiation.quantity ? ( <> {" · total "} {fmt((negotiation.amount ?? 0) * negotiation.quantity)} ) : null}
{role === "buyer" && listing?.isAutoGenerated && isOpen && ( )} {isOpen && theirProposal && (
)} {isOpen && role === "buyer" && !theirProposal && ( )} {counterOpen && (
setCounter(e.target.value)} placeholder="Your counter" />
)} {error &&

{error}

}
); } interface NegotiationPanelProps { negotiations: MarketNegotiation[]; currentUserId: number; currencyLabel: string | null; } export function NegotiationPanel({ negotiations, currentUserId, currencyLabel }: NegotiationPanelProps) { const open = negotiations.filter((n) => n.status === "open"); const closed = negotiations.filter((n) => n.status !== "open"); if (negotiations.length === 0) return null; return (

Negotiations

{open.length > 0 && ( {open.length} open )}
{open.length > 0 && (
{open.map((negotiation) => { const role: "buyer" | "seller" = negotiation.buyer && (typeof negotiation.buyer === "object" ? negotiation.buyer.id : negotiation.buyer) === currentUserId ? "buyer" : "seller"; return ( ); })}
)} {closed.length > 0 && (
{closed.slice(0, 6).map((negotiation) => ( {assetName(listingOf(negotiation))} · {STATUS_LABELS[negotiation.status] ?? negotiation.status} ))} {closed.length > 6 && ( +{closed.length - 6} more )}
)}
); }