"use client" import { useEffect, useState } from "react" import { useParams, useRouter } from "next/navigation" import { ArrowLeft, Building2, Globe, Mail, Loader2, AlertCircle, Package, Plus, Edit, Trash2 } from "lucide-react" import Image from "next/image" import { useTheme } from "@/components/ThemeProvider" import { cn } from "@/lib/utils" interface StoreSummary { id: string businessName: string shopifyDomain: string | null shopifyStoreName: string | null email?: string | null user: { name: string | null email: string | null } } interface ConnectionDetail { id: string status: "PENDING" | "CONNECTED" | "REJECTED" createdAt: string store: StoreSummary } interface Product { id: string title: string description: string | null price: number sku: string | null imageUrl: string | null } interface Offer { id: string price: number quantity: number createdAt: string product: Product store: { id: string businessName: string } } interface SupplierProduct { id: string title: string description: string | null price: number sku: string | null imageUrl: string | null // Flags and metadata for synced products from vendors isSynced?: boolean syncId?: string shopifyProductId?: string | null vendor?: { id: string businessName: string } | null availableQuantity?: number } export default function SupplierVendorDetailPage() { const params = useParams<{ connectionId: string }>() const router = useRouter() const [connection, setConnection] = useState(null) const [offers, setOffers] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState("") const [success, setSuccess] = useState("") const [showProvideModal, setShowProvideModal] = useState(false) const [products, setProducts] = useState([]) const [syncedProducts, setSyncedProducts] = useState([]) const [productsLoading, setProductsLoading] = useState(false) const [selectedProductId, setSelectedProductId] = useState("") const [priceInput, setPriceInput] = useState("") const [quantityInput, setQuantityInput] = useState("") const [submittingOffer, setSubmittingOffer] = useState(false) const [editingSyncedId, setEditingSyncedId] = useState("") const [syncedPriceInput, setSyncedPriceInput] = useState("") const [syncedQuantityInput, setSyncedQuantityInput] = useState("") const [updatingSynced, setUpdatingSynced] = useState(false) const [editingOffer, setEditingOffer] = useState(null) const [editOfferPrice, setEditOfferPrice] = useState("") const [editOfferQuantity, setEditOfferQuantity] = useState("") const [updatingOffer, setUpdatingOffer] = useState(false) const [withdrawingOffer, setWithdrawingOffer] = useState(null) const { theme } = useTheme() const isLight = theme === "light" const connectionId = params?.connectionId useEffect(() => { if (!connectionId) return fetchData() }, [connectionId]) useEffect(() => { if (success) { const timer = setTimeout(() => setSuccess(""), 5000) return () => clearTimeout(timer) } }, [success]) useEffect(() => { if (error) { const timer = setTimeout(() => setError(""), 5000) return () => clearTimeout(timer) } }, [error]) const fetchData = async () => { try { setLoading(true) setError("") const [connRes, offersRes, productsRes] = await Promise.all([ fetch(`/api/supplier/vendors/${connectionId}`), fetch(`/api/supplier/vendors/${connectionId}/offers`), fetch("/api/supplier/products") ]) if (!connRes.ok) { const data = await connRes.json().catch(() => ({})) throw new Error(data.message || "Failed to load vendor") } const connData = await connRes.json() setConnection(connData.connection) if (offersRes.ok) { const offersData = await offersRes.json() setOffers(offersData.offers || []) } else { setOffers([]) } // Load synced products for this specific vendor (store) if (productsRes.ok) { const productsData = await productsRes.json() const allProducts: SupplierProduct[] = productsData.products || [] const storeId = connData.connection?.store?.id if (storeId) { const syncedForVendor = allProducts.filter( (p) => p.isSynced && p.vendor && p.vendor.id === storeId ) setSyncedProducts(syncedForVendor) } else { setSyncedProducts([]) } } else { setSyncedProducts([]) } } catch (err) { setError(err instanceof Error ? err.message : "Failed to load vendor") } finally { setLoading(false) } } const openProvideModal = async () => { setShowProvideModal(true) setError("") setSuccess("") setSelectedProductId("") setPriceInput("") setQuantityInput("") try { setProductsLoading(true) const res = await fetch("/api/supplier/products") if (!res.ok) throw new Error("Failed to load products") const data = await res.json() const allProducts: SupplierProduct[] = (data.products || []).filter( (p: SupplierProduct) => !p.isSynced ) // Filter out products already offered to this vendor const offeredProductIds = new Set(offers.map((o) => o.product.id)) const availableProducts = allProducts.filter((p) => !offeredProductIds.has(p.id)) setProducts(availableProducts) } catch (err) { setError(err instanceof Error ? err.message : "Failed to load products") } finally { setProductsLoading(false) } } const handleCreateOffer = async (e: React.FormEvent) => { e.preventDefault() if (!connectionId || !selectedProductId) return const price = parseFloat(priceInput) const quantity = parseInt(quantityInput, 10) if (Number.isNaN(price) || Number.isNaN(quantity)) { setError("Please enter valid price and quantity") return } setSubmittingOffer(true) setError("") try { const res = await fetch(`/api/supplier/vendors/${connectionId}/offers`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ productId: selectedProductId, price, quantity }) }) const data = await res.json() if (!res.ok) { throw new Error(data.message || "Failed to provide product") } setSuccess(data.message || "Product provided to vendor successfully") setShowProvideModal(false) setSelectedProductId("") setPriceInput("") setQuantityInput("") fetchData() } catch (err) { setError(err instanceof Error ? err.message : "Failed to provide product") } finally { setSubmittingOffer(false) } } const openEditOfferModal = (offer: Offer) => { setEditingOffer(offer) setEditOfferPrice(offer.price.toString()) setEditOfferQuantity(offer.quantity.toString()) setError("") setSuccess("") } const handleUpdateOffer = async () => { if (!editingOffer) return const price = parseFloat(editOfferPrice) const quantity = parseInt(editOfferQuantity, 10) if (Number.isNaN(price) || price < 0 || Number.isNaN(quantity) || quantity <= 0) { setError("Please enter a valid non-negative price and quantity greater than zero") return } setUpdatingOffer(true) setError("") setSuccess("") try { const res = await fetch(`/api/supplier/vendors/offers/${editingOffer.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ price, quantity }) }) const data = await res.json() if (!res.ok) { throw new Error(data.message || "Failed to update offer") } setSuccess(data.message || "Offer updated successfully") setEditingOffer(null) fetchData() } catch (err) { setError(err instanceof Error ? err.message : "Failed to update offer") } finally { setUpdatingOffer(false) } } const handleWithdrawOffer = (offer: Offer) => { setWithdrawingOffer(offer) setError("") setSuccess("") } const handleConfirmWithdraw = async () => { if (!withdrawingOffer) return setError("") setSuccess("") try { const res = await fetch(`/api/supplier/vendors/offers/${withdrawingOffer.id}`, { method: "DELETE" }) const data = await res.json().catch(() => ({})) if (!res.ok) { throw new Error(data.message || "Failed to withdraw offer") } setSuccess(data.message || "Offer withdrawn successfully") setOffers((prev) => prev.filter((o) => o.id !== withdrawingOffer.id)) setWithdrawingOffer(null) } catch (err) { setError(err instanceof Error ? err.message : "Failed to withdraw offer") } } const handleStartEditSynced = (product: SupplierProduct) => { setEditingSyncedId(product.id) setSyncedPriceInput(product.price.toString()) setSyncedQuantityInput( product.availableQuantity !== undefined ? product.availableQuantity.toString() : "" ) setError("") setSuccess("") } const handleCancelEditSynced = () => { setEditingSyncedId("") setSyncedPriceInput("") setSyncedQuantityInput("") } const handleSaveSynced = async () => { if (!editingSyncedId) return const product = syncedProducts.find((p) => p.id === editingSyncedId) if (!product) return const price = parseFloat(syncedPriceInput) const quantity = parseInt(syncedQuantityInput, 10) if (Number.isNaN(price) || price < 0) { setError("Please enter a valid non-negative price") return } if (Number.isNaN(quantity) || quantity <= 0) { setError("Please enter a valid quantity greater than zero") return } setUpdatingSynced(true) setError("") setSuccess("") try { const res = await fetch(`/api/supplier/products/${editingSyncedId}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title: product.title, description: product.description, price, sku: product.sku, imageUrl: product.imageUrl, quantity }) }) const data = await res.json().catch(() => ({})) if (!res.ok) { throw new Error(data.message || "Failed to update synced product") } setSyncedProducts((prev) => prev.map((p) => p.id === editingSyncedId ? { ...p, price, availableQuantity: quantity } : p ) ) setSuccess(data.message || "Synced product updated successfully") setEditingSyncedId("") setSyncedPriceInput("") setSyncedQuantityInput("") } catch (err) { setError(err instanceof Error ? err.message : "Failed to update synced product") } finally { setUpdatingSynced(false) } } const selectedProduct = products.find((p) => p.id === selectedProductId) || null return (

Vendor Details

View store information and provide products to this vendor.

{success && (
{success}
)} {error && (
{error}
)} {loading || !connection ? (
) : ( <>

{connection.store.businessName}

{connection.store.shopifyStoreName && (

{connection.store.shopifyStoreName}

)}
{connection.store.user.email}
{connection.store.shopifyDomain && (
{connection.store.shopifyDomain}
)}

Connected on{" "} {new Date(connection.createdAt).toLocaleDateString()}

{/* Synced products from this vendor (via product sync requests) */} {syncedProducts.length > 0 && (

Synced products from this vendor

{syncedProducts.length} product {syncedProducts.length > 1 ? "s" : ""}
{syncedProducts.map((product) => { const isEditing = editingSyncedId === product.id return (
{product.imageUrl ? ( {product.title} ) : (
)}

{product.title}

{product.sku && ( SKU: {product.sku} )} {product.description && ( {product.description} )}

Current price

{isEditing ? (
$ setSyncedPriceInput(e.target.value) } className="flex-1 px-2 py-1 bg-white/5 border border-white/10 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
) : (

${product.price.toFixed(2)}

)}

Available amount

{isEditing ? ( setSyncedQuantityInput( e.target.value ) } className="w-full px-2 py-1 bg-white/5 border border-white/10 rounded-lg text-sm text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" /> ) : (

{product.availableQuantity ?? "—"}

)}
{isEditing ? ( <> ) : ( )}
) })}
)}

Products provided to this vendor

{offers.length === 0 ? (

No products have been provided to this vendor yet.

Click "Provide Product" to send your first product.

) : (
{offers.map((offer) => (
{offer.product.imageUrl ? ( {offer.product.title} ) : (
)}

{offer.product.title}

{offer.product.sku && (

SKU: {offer.product.sku}

)}

Provided on{" "} {new Date(offer.createdAt).toLocaleDateString()}

Price for this vendor

${offer.price.toFixed(2)}

Quantity

{offer.quantity}

))}
)}
)} {showProvideModal && (

Provide product to {connection?.store.businessName}

{productsLoading ? (
Loading products...
) : products.length === 0 ? (

You have no available products to provide. Create a product in your Products tab first.

) : (
{products.map((product) => ( ))}
)}
{!selectedProduct && (

Select a product to configure pricing

Choose a product from your catalog on the left. Price and total items will appear here after you make a selection.

)} {selectedProduct && ( <>
$ setPriceInput(e.target.value)} className="flex-1 px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" placeholder="0.00" />

Base catalog price: ${selectedProduct.price.toFixed(2)}

setQuantityInput(e.target.value)} className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" placeholder="e.g. 100" />

Preview: {selectedProduct.title}

{selectedProduct.description && (

{selectedProduct.description}

)}
)}
)} {editingOffer && (

Edit offer

Update price or quantity for{" "} {editingOffer.product.title}{" "} for{" "} {connection?.store.businessName} .

$ setEditOfferPrice(e.target.value)} className="flex-1 px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
setEditOfferQuantity(e.target.value)} className="w-full px-3 py-2 bg-white/5 border border-white/10 rounded-lg text-white focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500/50" />
)} {withdrawingOffer && (

Withdraw product

You are about to withdraw{" "} {withdrawingOffer.product.title} {" "} from{" "} {connection?.store.businessName} . This product will no longer appear in their New Products list.

)}
) }