"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import AddBusinessModal from "@/components/AddBusinessModal"; import BusinessCardList from "@/components/BusinessCardList"; import ConfirmDialog from "@/components/ConfirmDialog"; import MakeSpecialModal from "@/components/MakeSpecialModal"; import { deleteMarketplaceBusiness, listBusinesses, updateMarketplaceBusiness, } from "@/services/marketplace"; import { getBusinessProfile, getMeshkeeBusiness, } from "@/services/meshkee-businesses"; import { searchMeshkeeCategories, type MeshkeeCategory, } from "@/services/meshkee-categories"; import type { BusinessStatus, MarketplaceBusiness } from "@/types/business"; import styles from "./BusinessesPage.module.css"; type FilterKey = "all" | BusinessStatus; const FILTERS: { key: FilterKey; label: string }[] = [ { key: "all", label: "All" }, { key: "pending_review", label: "Pending" }, { key: "published", label: "Published" }, { key: "draft", label: "Draft" }, { key: "hidden", label: "Hidden" }, { key: "rejected", label: "Rejected" }, ]; function categoryAncestors( categoryId: string | undefined, byId: Map, ): string[] { if (!categoryId) return []; const names: string[] = []; const seen = new Set(); let current = byId.get(categoryId); while (current && !seen.has(current.id)) { seen.add(current.id); names.unshift(current.name); current = current.parentId ? byId.get(current.parentId) : undefined; } return names; } function lastTwoCategoryLevels(trail: string[]) { if (trail.length === 0) return undefined; return trail.slice(-2).join(" / "); } async function enrichListings( items: MarketplaceBusiness[], ): Promise { const categories = await searchMeshkeeCategories("").catch( () => [] as MeshkeeCategory[], ); const byId = new Map(categories.map((category) => [category.id, category])); return Promise.all( items.map(async (item) => { try { const id = String(item.meshkeeBusinessId); const [meshkee, profile] = await Promise.all([ getMeshkeeBusiness(id), getBusinessProfile(id), ]); const primaryCategoryId = profile.profile.categoryIds[0] ?? meshkee.categories[0]?.id; const trail = categoryAncestors(primaryCategoryId, byId); return { ...item, name: meshkee.name, categoryName: trail[trail.length - 1] ?? meshkee.categories[0]?.name, categoryTrail: lastTwoCategoryLevels(trail), logoUrl: profile.profile.logoUrl ?? meshkee.logoUrl ?? undefined, bannerUrl: profile.profile.bannerUrl ?? meshkee.bannerUrl ?? undefined, }; } catch { return { ...item, name: item.slug }; } }), ); } function isFilterKey(value: string | null): value is FilterKey { if (!value) return false; return FILTERS.some((item) => item.key === value); } export default function BusinessesPage() { const router = useRouter(); const searchParams = useSearchParams(); const initialFilter = isFilterKey(searchParams.get("status")) ? (searchParams.get("status") as FilterKey) : "all"; const [filter, setFilter] = useState(initialFilter); const [items, setItems] = useState([]); const [total, setTotal] = useState(0); const [error, setError] = useState(""); const [loading, setLoading] = useState(true); const [modalOpen, setModalOpen] = useState(false); const [removing, setRemoving] = useState(null); const [rejecting, setRejecting] = useState(null); const [makingSpecial, setMakingSpecial] = useState(null); const [busyId, setBusyId] = useState(null); const reload = useCallback(async () => { setLoading(true); setError(""); try { const data = await listBusinesses({ status: "all", limit: 100 }); const enriched = await enrichListings(data.items); setItems(enriched); setTotal(data.total); } catch (err) { setError( err instanceof Error ? err.message : "Could not load businesses from the API.", ); setItems([]); setTotal(0); } finally { setLoading(false); } }, []); useEffect(() => { void reload(); }, [reload]); useEffect(() => { const next = isFilterKey(searchParams.get("status")) ? (searchParams.get("status") as FilterKey) : "all"; setFilter(next); }, [searchParams]); const counts = useMemo(() => { const next: Record = { all: items.length, draft: 0, pending_review: 0, published: 0, hidden: 0, rejected: 0, }; for (const item of items) { next[item.status] += 1; } return next; }, [items]); const visible = useMemo(() => { if (filter === "all") return items; return items.filter((item) => item.status === filter); }, [items, filter]); function selectFilter(next: FilterKey) { setFilter(next); const url = new URL(window.location.href); if (next === "all") url.searchParams.delete("status"); else url.searchParams.set("status", next); router.replace(`${url.pathname}${url.search}`); } async function setStatus(business: MarketplaceBusiness, status: BusinessStatus) { setBusyId(business.id); setError(""); try { await updateMarketplaceBusiness(business.id, { status }); await reload(); } catch (err) { setError( err instanceof Error ? err.message : "Could not update listing status.", ); } finally { setBusyId(null); } } const removingBusy = Boolean(removing && busyId === removing.id); const rejectingBusy = Boolean(rejecting && busyId === rejecting.id); const removingName = removing?.name || removing?.slug || "this business"; const rejectingName = rejecting?.name || rejecting?.slug || "this business"; return (

Businesses

Review submissions, publish listings, and manage the marketplace gate.

{loading ? "Loading…" : filter === "all" ? `${total} total` : `${visible.length} shown · ${total} total`}

{FILTERS.map((item) => { const active = filter === item.key; const count = counts[item.key]; return ( ); })}
{error ? (

API unavailable

{error}

Start the marketplace backend on port 3001, then refresh.

) : loading ? (
Loading businesses…
) : ( void setStatus(business, "published")} onReject={setRejecting} onDisable={async (business) => { const nextStatus = business.status === "hidden" ? "published" : "hidden"; await setStatus(business, nextStatus); }} onRemove={setRemoving} onMakeSpecial={setMakingSpecial} onMetaTag={(business) => { const lines = [ `Name: ${business.name || business.slug}`, `Slug: /${business.slug}`, `Category: ${business.categoryName || "Uncategorized"}`, `Status: ${business.status}`, `Featured: ${business.isFeatured ? "yes" : "no"}`, ].join("\n"); window.alert(lines); }} /> )} setModalOpen(false)} onConnected={() => { void reload(); }} /> setMakingSpecial(null)} /> { if (!rejectingBusy) setRejecting(null); }} onConfirm={async () => { if (!rejecting) return; await setStatus(rejecting, "rejected"); setRejecting(null); }} /> { if (!removingBusy) setRemoving(null); }} onConfirm={async () => { if (!removing) return; setBusyId(removing.id); setError(""); try { await deleteMarketplaceBusiness(removing.id); setRemoving(null); await reload(); } catch (err) { setError( err instanceof Error ? err.message : "Could not remove listing.", ); } finally { setBusyId(null); } }} />
); }