"use client"; import { useState, useEffect, useCallback } from "react"; import { useAuth } from "@/lib/AuthContext"; import { useRouter, useParams } from "next/navigation"; import { ArrowLeft, Star, Loader2, Check, Clock, RefreshCw, ShoppingCart, User, MessageCircle, ChevronRight, } from "lucide-react"; import ErrorFeedback from "@/components/ErrorFeedback"; // ── Types ───────────────────────────────────────────────────────────────────── interface Package { name: string; description: string; price: number; deliveryDays: number; revisions: number; } interface Seller { id: string; name: string; email: string; avatar: string | null; } interface Reviewer { id: string; name: string; avatar: string | null; } interface Review { id: string; rating: number; title: string | null; text: string; createdAt: string; reviewer: Reviewer; } interface ListingDetail { id: string; title: string; description: string; category: string; subcategory: string | null; priceFlh: number; packages: Package[] | null; tags: string[] | null; images: string[] | null; deliveryDays: number | null; rating: number; reviewCount: number; salesCount: number; seller: Seller; reviews: Review[]; _count: { purchases: number }; status: string; createdAt: string; } // ── Helpers ──────────────────────────────────────────────────────────────────── const CATEGORY_EMOJI: Record = { "Web Development": "🌐", "Mobile Development": "📱", "Content Writing": "✍️", "Brand Identity": "🎨", "SEO/Marketing": "📈", "Video Production": "🎬", default: "📦", }; const CATEGORY_COLORS: Record = { "Web Development": "from-blue-600/40 to-blue-900/30", "Mobile Development": "from-purple-600/40 to-purple-900/30", "Content Writing": "from-emerald-600/40 to-emerald-900/30", "Brand Identity": "from-amber-600/40 to-amber-900/30", "SEO/Marketing": "from-red-600/40 to-red-900/30", "Video Production": "from-pink-600/40 to-pink-900/30", default: "from-gray-600/40 to-gray-900/30", }; function getInitials(name: string): string { return name .split(" ") .map((n) => n.charAt(0).toUpperCase()) .slice(0, 2) .join(""); } function formatFlh(amount: number): string { return amount.toLocaleString() + " FLH"; } function timeAgo(dateStr: string): string { const now = Date.now(); const then = new Date(dateStr).getTime(); const diffSec = Math.floor((now - then) / 1000); if (diffSec < 60) return "just now"; const diffMin = Math.floor(diffSec / 60); if (diffMin < 60) return `${diffMin}m ago`; const diffHr = Math.floor(diffMin / 60); if (diffHr < 24) return `${diffHr}h ago`; const diffDay = Math.floor(diffHr / 24); if (diffDay < 7) return `${diffDay}d ago`; return new Date(dateStr).toLocaleDateString(); } // ── Component ───────────────────────────────────────────────────────────────── export default function ListingDetailPage() { const { user, token, loading } = useAuth(); const router = useRouter(); const params = useParams(); const id = params.id as string; const [listing, setListing] = useState(null); const [loadingData, setLoadingData] = useState(true); const [error, setError] = useState(null); const [selectedPackage, setSelectedPackage] = useState(null); const [ordering, setOrdering] = useState(false); const [orderSuccess, setOrderSuccess] = useState(false); const [orderError, setOrderError] = useState(null); // ── Auth redirect ─────────────────────────────────────────────────────── useEffect(() => { if (!loading && !token) { router.push("/auth"); } }, [loading, token, router]); // ── Fetch listing ──────────────────────────────────────────────────────── const fetchListing = useCallback(async () => { if (!id) return; setLoadingData(true); setError(null); try { const res = await fetch(`/mobile/api/souq/listings/${id}`); if (!res.ok) { const data = await res.json().catch(() => ({})); throw new Error(data.error || `Failed to load listing (${res.status})`); } const data = await res.json(); setListing(data.listing); } catch (err: unknown) { const message = err instanceof Error ? err.message : "Failed to load listing"; setError(message); } finally { setLoadingData(false); } }, [id]); useEffect(() => { if (id) { fetchListing(); } }, [id, fetchListing]); // ── Auto-select first package ─────────────────────────────────────────── useEffect(() => { if (listing?.packages && listing.packages.length > 0 && !selectedPackage) { setSelectedPackage(listing.packages[0]); } }, [listing, selectedPackage]); // ── Place order ────────────────────────────────────────────────────────── const handleContinue = async () => { if (!token) { router.push("/auth"); return; } if (!selectedPackage || !listing) return; setOrdering(true); setOrderError(null); try { const res = await fetch("/mobile/api/souq/orders", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, }, body: JSON.stringify({ listingId: listing.id, packageName: selectedPackage.name, }), }); const data = await res.json(); if (!res.ok) { throw new Error(data.error || "Failed to place order"); } setOrderSuccess(true); setTimeout(() => { router.push("/souq/orders"); }, 2000); } catch (err: unknown) { const message = err instanceof Error ? err.message : "Failed to place order"; setOrderError(message); setTimeout(() => setOrderError(null), 5000); } finally { setOrdering(false); } }; // ── Loading (auth check) ──────────────────────────────────────────────── if (loading) { return (
); } if (!user) return null; // ── Data loading state ────────────────────────────────────────────────── if (loadingData) { return (
{/* Header */}

Loading listing...

); } // ── Error state ───────────────────────────────────────────────────────── if (error) { return (

Listing

); } // ── Not found state ────────────────────────────────────────────────────── if (!listing) { return (

Listing

Listing not found

); } // ── Derived data ──────────────────────────────────────────────────────── const packages = listing.packages || []; const firstImage = listing.images?.[0] || null; const categoryEmoji = CATEGORY_EMOJI[listing.category] || CATEGORY_EMOJI.default; const categoryColor = CATEGORY_COLORS[listing.category] || CATEGORY_COLORS.default; const totalPrice = selectedPackage ? Math.round(selectedPackage.price * 1.015) : 0; const sellerInitials = getInitials(listing.seller.name); return (
{/* ── Header ──────────────────────────────────────────────────────── */}

{listing.title}

{/* ── Content ─────────────────────────────────────────────────────── */}
{/* ── Image Area ────────────────────────────────────────────────── */} {firstImage ? (
{listing.title} { // Hide broken image, show fallback (e.target as HTMLElement).style.display = "none"; const fallback = (e.target as HTMLElement) .nextElementSibling as HTMLElement | null; if (fallback) fallback.style.display = "flex"; }} />
{categoryEmoji}
) : (
{categoryEmoji}
)} {/* ── Title & Meta ───────────────────────────────────────────────── */}

{listing.title}

{/* Category badge */}
{listing.category} {listing.subcategory && ( {listing.subcategory} )}
{/* Rating row */}
{listing.rating.toFixed(1)}
({listing.reviewCount} {listing.reviewCount === 1 ? "review" : "reviews"}) · {listing.salesCount} {listing.salesCount === 1 ? "sale" : "sales"}
{/* Seller row */}
{/* ── Description ────────────────────────────────────────────────── */}

About This Listing

{listing.description}

{/* ── Packages ────────────────────────────────────────────────────── */} {packages.length > 0 && (

Select a Package

{packages.map((pkg) => { const isSelected = selectedPackage?.name === pkg.name; const feeAmount = Math.round(pkg.price * 0.015); return ( ); })}
)} {/* ── Seller Info Section ─────────────────────────────────────────── */}

About the Seller

{listing.seller.avatar ? ( {listing.seller.name} ) : (
{sellerInitials}
)}

{listing.seller.name}

Member since{" "} {new Date(listing.createdAt).toLocaleDateString("en-US", { month: "long", year: "numeric", })}

{/* Seller stats */}
{listing.rating.toFixed(1)}

Rating

{listing.reviewCount}

{listing.reviewCount === 1 ? "Review" : "Reviews"}

{listing.salesCount}

{listing.salesCount === 1 ? "Sale" : "Sales"}

{/* ── Reviews ─────────────────────────────────────────────────────── */} {listing.reviews && listing.reviews.length > 0 && (

Reviews ({listing.reviews.length})

{listing.reviews.map((review) => (
{/* Reviewer info */}
{review.reviewer.avatar ? ( {review.reviewer.name} ) : (
{getInitials(review.reviewer.name)}
)}

{review.reviewer.name}

{Array.from({ length: 5 }).map((_, i) => ( ))}
{timeAgo(review.createdAt)}
{/* Review title */} {review.title && (

{review.title}

)} {/* Review text */}

{review.text}

))}
)} {/* Bottom spacer */}
{/* ── Fixed Bottom Order Bar ───────────────────────────────────────── */}
{/* Safe area inset wrapper */}
{/* Success toast */} {orderSuccess && (
Order placed! Redirecting...
)} {/* Error toast */} {orderError && (
setOrderError(null)} context="order" />
)}
{/* Price info */}

{selectedPackage ? selectedPackage.name : "No package selected"}

{selectedPackage ? formatFlh(totalPrice) : "—"}

{selectedPackage && (

{formatFlh(selectedPackage.price)} + 1.5% fee

)}
{/* Continue button */}
); }