@mohasinac/appkit 2.7.50 → 2.7.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.d.ts +1 -0
  2. package/dist/_internal/server/jobs/core/__tests__/couponExpiry.test.js +65 -0
  3. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.d.ts +1 -0
  4. package/dist/_internal/server/jobs/core/__tests__/listingProcessor.test.js +163 -0
  5. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.d.ts +1 -0
  6. package/dist/_internal/server/jobs/core/__tests__/mediaTmpCleanup.test.js +115 -0
  7. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.d.ts +1 -0
  8. package/dist/_internal/server/jobs/core/__tests__/pendingOrderTimeout.test.js +96 -0
  9. package/dist/client.d.ts +29 -6
  10. package/dist/client.js +16 -4
  11. package/dist/constants/api-endpoints.d.ts +42 -0
  12. package/dist/constants/api-endpoints.js +14 -0
  13. package/dist/features/_guide-cls.d.ts +13 -0
  14. package/dist/features/_guide-cls.js +14 -0
  15. package/dist/features/about/components/HelpPageView.js +29 -26
  16. package/dist/features/account/components/BuyerAccountGuideView.d.ts +1 -0
  17. package/dist/features/account/components/BuyerAccountGuideView.js +39 -0
  18. package/dist/features/account/components/BuyerAuctionsGuideView.d.ts +1 -0
  19. package/dist/features/account/components/BuyerAuctionsGuideView.js +38 -0
  20. package/dist/features/account/components/BuyerOrdersGuideView.d.ts +1 -0
  21. package/dist/features/account/components/BuyerOrdersGuideView.js +48 -0
  22. package/dist/features/account/components/BuyerShoppingGuideView.d.ts +1 -0
  23. package/dist/features/account/components/BuyerShoppingGuideView.js +38 -0
  24. package/dist/features/admin/components/AdminAnalyticsGuideView.d.ts +1 -0
  25. package/dist/features/admin/components/AdminAnalyticsGuideView.js +24 -0
  26. package/dist/features/admin/components/AdminBlogEditorView.js +100 -77
  27. package/dist/features/admin/components/AdminBundleEditorView.js +20 -14
  28. package/dist/features/admin/components/AdminBundlesView.d.ts +1 -3
  29. package/dist/features/admin/components/AdminBundlesView.js +173 -52
  30. package/dist/features/admin/components/AdminCatalogGuideView.d.ts +1 -0
  31. package/dist/features/admin/components/AdminCatalogGuideView.js +28 -0
  32. package/dist/features/admin/components/AdminContentGuideView.d.ts +1 -0
  33. package/dist/features/admin/components/AdminContentGuideView.js +36 -0
  34. package/dist/features/admin/components/AdminCouponEditorView.js +1 -1
  35. package/dist/features/admin/components/AdminGuideHubView.d.ts +5 -0
  36. package/dist/features/admin/components/AdminGuideHubView.js +79 -0
  37. package/dist/features/admin/components/AdminOrdersGuideView.d.ts +1 -0
  38. package/dist/features/admin/components/AdminOrdersGuideView.js +44 -0
  39. package/dist/features/admin/components/AdminProductsView.js +8 -3
  40. package/dist/features/admin/components/AdminSiteConfigGuideView.d.ts +1 -0
  41. package/dist/features/admin/components/AdminSiteConfigGuideView.js +21 -0
  42. package/dist/features/admin/components/AdminStoresGuideView.d.ts +1 -0
  43. package/dist/features/admin/components/AdminStoresGuideView.js +32 -0
  44. package/dist/features/admin/components/AdminTeamGuideView.d.ts +1 -0
  45. package/dist/features/admin/components/AdminTeamGuideView.js +33 -0
  46. package/dist/features/admin/components/AdminTrustGuideView.d.ts +1 -0
  47. package/dist/features/admin/components/AdminTrustGuideView.js +40 -0
  48. package/dist/features/admin/components/AdminUsersGuideView.d.ts +1 -0
  49. package/dist/features/admin/components/AdminUsersGuideView.js +38 -0
  50. package/dist/features/categories/components/CategoryBundlesListing.js +3 -33
  51. package/dist/features/categories/components/index.d.ts +0 -2
  52. package/dist/features/categories/components/index.js +0 -1
  53. package/dist/features/events/components/AdminEventEditorView.js +223 -269
  54. package/dist/features/grouped/repository/grouped-listings.repository.d.ts +9 -0
  55. package/dist/features/grouped/repository/grouped-listings.repository.js +12 -0
  56. package/dist/features/media/upload/MediaUploadField.js +1 -1
  57. package/dist/features/pre-orders/components/PreOrdersIndexListing.js +3 -1
  58. package/dist/features/products/components/AuctionsIndexListing.js +3 -1
  59. package/dist/features/products/components/MarketplaceBundleCard.d.ts +21 -0
  60. package/dist/features/products/components/MarketplaceBundleCard.js +56 -0
  61. package/dist/features/products/components/ProductsIndexListing.js +3 -1
  62. package/dist/features/products/components/index.d.ts +2 -4
  63. package/dist/features/products/components/index.js +1 -2
  64. package/dist/features/promotions/schemas/firestore.js +1 -1
  65. package/dist/features/reviews/components/index.d.ts +0 -2
  66. package/dist/features/reviews/components/index.js +0 -1
  67. package/dist/features/seller/components/BrandInlineSelect.js +23 -3
  68. package/dist/features/seller/components/CategoryInlineSelect.js +20 -3
  69. package/dist/features/seller/components/SellerAnalyticsAlertsView.d.ts +6 -0
  70. package/dist/features/seller/components/SellerAnalyticsAlertsView.js +124 -0
  71. package/dist/features/seller/components/SellerAnalyticsView.d.ts +2 -0
  72. package/dist/features/seller/components/SellerAnalyticsView.js +17 -9
  73. package/dist/features/seller/components/SellerBundlesView.d.ts +5 -0
  74. package/dist/features/seller/components/SellerBundlesView.js +104 -0
  75. package/dist/features/seller/components/SellerClassifiedView.d.ts +5 -0
  76. package/dist/features/seller/components/SellerClassifiedView.js +113 -0
  77. package/dist/features/seller/components/SellerDashboardView.js +19 -7
  78. package/dist/features/seller/components/SellerDigitalCodesView.d.ts +5 -0
  79. package/dist/features/seller/components/SellerDigitalCodesView.js +115 -0
  80. package/dist/features/seller/components/SellerGoogleReviewsView.d.ts +14 -0
  81. package/dist/features/seller/components/SellerGoogleReviewsView.js +95 -0
  82. package/dist/features/seller/components/SellerGroupedListingsView.d.ts +6 -0
  83. package/dist/features/seller/components/SellerGroupedListingsView.js +112 -0
  84. package/dist/features/seller/components/SellerLiveView.d.ts +5 -0
  85. package/dist/features/seller/components/SellerLiveView.js +113 -0
  86. package/dist/features/seller/components/SellerOrdersView.js +18 -2
  87. package/dist/features/seller/components/SellerPayoutMethodsView.d.ts +7 -0
  88. package/dist/features/seller/components/SellerPayoutMethodsView.js +120 -0
  89. package/dist/features/seller/components/SellerPayoutSettingsView.js +50 -12
  90. package/dist/features/seller/components/SellerProductShell.d.ts +1 -1
  91. package/dist/features/seller/components/SellerProductShell.js +9 -7
  92. package/dist/features/seller/components/SellerProductsFilterDrawer.js +3 -1
  93. package/dist/features/seller/components/SellerProductsView.js +48 -54
  94. package/dist/features/seller/components/SellerShippingConfigsView.d.ts +7 -0
  95. package/dist/features/seller/components/SellerShippingConfigsView.js +149 -0
  96. package/dist/features/seller/components/SellerShippingView.js +37 -10
  97. package/dist/features/seller/components/SellerStoreCategoriesView.d.ts +9 -0
  98. package/dist/features/seller/components/SellerStoreCategoriesView.js +122 -0
  99. package/dist/features/seller/components/SellerStorefrontView.d.ts +0 -7
  100. package/dist/features/seller/components/SellerStorefrontView.js +26 -29
  101. package/dist/features/seller/components/SellerTemplatesView.d.ts +10 -0
  102. package/dist/features/seller/components/SellerTemplatesView.js +265 -0
  103. package/dist/features/seller/components/index.d.ts +26 -0
  104. package/dist/features/seller/components/index.js +13 -0
  105. package/dist/features/stores/api/[storeSlug]/reviews/route.js +49 -7
  106. package/dist/features/stores/components/StoreCapabilitiesGuideView.d.ts +3 -0
  107. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +101 -0
  108. package/dist/features/stores/components/StoreFinanceGuideView.d.ts +3 -0
  109. package/dist/features/stores/components/StoreFinanceGuideView.js +79 -0
  110. package/dist/features/stores/components/StoreGuideHubView.d.ts +5 -0
  111. package/dist/features/stores/components/StoreGuideHubView.js +89 -0
  112. package/dist/features/stores/components/StoreListingsGuideView.d.ts +3 -0
  113. package/dist/features/stores/components/StoreListingsGuideView.js +151 -0
  114. package/dist/features/stores/components/StoreOrdersGuideView.d.ts +3 -0
  115. package/dist/features/stores/components/StoreOrdersGuideView.js +122 -0
  116. package/dist/features/stores/components/StoreReviewsListing.js +100 -23
  117. package/dist/features/stores/components/StoreSettingsGuideView.d.ts +3 -0
  118. package/dist/features/stores/components/StoreSettingsGuideView.js +56 -0
  119. package/dist/features/stores/components/index.d.ts +12 -1
  120. package/dist/features/stores/components/index.js +6 -1
  121. package/dist/features/stores/hooks/useStores.d.ts +5 -0
  122. package/dist/features/stores/hooks/useStores.js +10 -0
  123. package/dist/features/stores/manifest.js +1 -1
  124. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +13 -2
  125. package/dist/index.d.ts +54 -9
  126. package/dist/index.js +58 -15
  127. package/dist/next/routing/route-map.d.ts +55 -0
  128. package/dist/next/routing/route-map.js +25 -0
  129. package/dist/providers/db-firebase/__tests__/filter-aliases.test.d.ts +1 -0
  130. package/dist/providers/db-firebase/__tests__/filter-aliases.test.js +93 -0
  131. package/dist/providers/db-firebase/sieve.d.ts +49 -0
  132. package/dist/providers/db-firebase/sieve.js +61 -8
  133. package/dist/repositories/index.d.ts +1 -0
  134. package/dist/repositories/index.js +2 -0
  135. package/dist/tailwind-utilities.css +1 -1
  136. package/dist/ui/components/ListingToolbar.d.ts +8 -1
  137. package/dist/ui/components/ListingToolbar.js +4 -2
  138. package/dist/ui/components/__tests__/BulkActionBar.test.d.ts +1 -0
  139. package/dist/ui/components/__tests__/BulkActionBar.test.js +96 -0
  140. package/dist/ui/components/__tests__/ListingToolbar.test.d.ts +1 -0
  141. package/dist/ui/components/__tests__/ListingToolbar.test.js +125 -0
  142. package/dist/ui/index.d.ts +1 -1
  143. package/package.json +8 -2
@@ -1,40 +1,142 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- /**
4
- * AdminBundlesView S-SBUNI-4 2026-05-13.
5
- *
6
- * Simple admin list for categoryType:"bundle" rows. Fetches /api/admin/bundles
7
- * on mount, renders a table with name + price + member-count + stock + status,
8
- * and surfaces edit / new CTAs via consumer-provided href builders.
9
- *
10
- * Intentionally lighter than AdminCategoriesView (no ListingToolbar / SideDrawer /
11
- * panel-url sync) because bundles are admin-only + low cardinality. Upgrade
12
- * paths to the full pattern remain open if volume grows.
13
- */
14
- import { useCallback, useEffect, useState } from "react";
15
- import Link from "next/link";
16
- import { Badge, Button, Container, Div, Heading, Row, Section, Stack, Text, useToast, } from "../../../ui";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Plus, X } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { Badge, BulkActionBar, Button, Heading, ListingToolbar, Pagination, Stack, Text, useToast, } from "../../../ui";
8
+ import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { toRecordArray, toRelativeDate, toStringValue, useAdminListingData, } from "../hooks/useAdminListingData";
10
+ import { DataTable } from "./DataTable";
17
11
  import { BUNDLE_COPY, BUNDLE_STOCK_VARIANT, } from "../../../_internal/shared/features/categories/bundle-copy";
18
12
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
19
- const STOCK_LIST_LABEL = {
20
- in_stock: BUNDLE_COPY.stockBadge.listVariantInStock,
21
- out_of_stock: BUNDLE_COPY.stockBadge.listVariantOutOfStock,
22
- };
13
+ const PAGE_SIZE = 25;
14
+ const FILTER_KEYS = ["isActive", "bundleStockStatus"];
15
+ const DEFAULT_SORT = "name";
16
+ const SORT_OPTIONS = [
17
+ { value: "name", label: "Name A–Z" },
18
+ { value: "-name", label: "Name Z–A" },
19
+ { value: "-bundlePriceInPaise", label: "Price high→low" },
20
+ { value: "bundlePriceInPaise", label: "Price low→high" },
21
+ { value: "-createdAt", label: "Newest" },
22
+ { value: "createdAt", label: "Oldest" },
23
+ ];
23
24
  function formatPrice(paise) {
24
25
  if (typeof paise !== "number" || paise <= 0)
25
26
  return "—";
26
27
  return `₹${Math.round(paise / 100).toLocaleString("en-IN")}`;
27
28
  }
28
- export function AdminBundlesView({ getEditHref, newHref, }) {
29
- const [bundles, setBundles] = useState([]);
30
- const [loading, setLoading] = useState(true);
31
- const [error, setError] = useState(null);
29
+ const COLUMNS = [
30
+ {
31
+ key: "primary",
32
+ header: "Name",
33
+ render: (row) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "font-medium text-zinc-900 dark:text-zinc-100", children: row.primary }), _jsx(Text, { className: "text-xs text-zinc-500 dark:text-zinc-400", children: row.secondary })] })),
34
+ },
35
+ { key: "price", header: "Price", className: "w-28" },
36
+ { key: "members", header: "Members", className: "w-24" },
37
+ {
38
+ key: "stockStatus",
39
+ header: "Stock",
40
+ className: "w-28",
41
+ render: (row) => (_jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[row.stockStatus] ?? "default", children: row.stockStatus === "in_stock"
42
+ ? BUNDLE_COPY.stockBadge.listVariantInStock
43
+ : BUNDLE_COPY.stockBadge.listVariantOutOfStock })),
44
+ },
45
+ {
46
+ key: "status",
47
+ header: "Status",
48
+ className: "w-24",
49
+ render: (row) => (_jsx(Badge, { variant: row.isActive ? "success" : "default", children: row.status })),
50
+ },
51
+ { key: "updatedAt", header: "Updated", className: "w-28" },
52
+ ];
53
+ function BundlesFilterDrawer({ filterOpen, setFilterOpen, activeFilterCount, clearFilters, pendingFilters, setPendingFilters, applyFilters, }) {
54
+ if (!filterOpen)
55
+ return null;
56
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-40 bg-black/40", "aria-hidden": "true", onClick: () => setFilterOpen(false) }), _jsxs("div", { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-white dark:bg-slate-900 shadow-2xl", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: [_jsx("span", { className: "text-base font-semibold text-zinc-900 dark:text-zinc-100", children: "Filters" }), _jsxs("div", { className: "flex items-center gap-2", children: [activeFilterCount > 0 && (_jsx("button", { type: "button", onClick: clearFilters, className: "text-xs text-zinc-500 hover:text-rose-500 dark:text-zinc-400 transition-colors", children: "Clear all" })), _jsx("button", { type: "button", onClick: () => setFilterOpen(false), "aria-label": "Close", className: "rounded-lg p-1.5 text-zinc-500 hover:bg-zinc-100 dark:hover:bg-slate-800 transition-colors", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsxs("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Status" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
57
+ { label: "All", value: "" },
58
+ { label: "Active", value: "true" },
59
+ { label: "Inactive", value: "false" },
60
+ ].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, isActive: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.isActive || "") === opt.value
61
+ ? "bg-primary text-white border-primary"
62
+ : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-widest text-zinc-500 dark:text-zinc-400", children: "Stock" }), _jsx("div", { className: "flex flex-wrap gap-2", children: [
63
+ { label: "All", value: "" },
64
+ { label: "Sold out", value: "out_of_stock" },
65
+ ].map((opt) => (_jsx("button", { type: "button", onClick: () => setPendingFilters((p) => ({ ...p, bundleStockStatus: opt.value })), className: `rounded-full px-3 py-1 text-xs font-medium border transition-colors ${(pendingFilters.bundleStockStatus || "") === opt.value
66
+ ? "bg-primary text-white border-primary"
67
+ : "border-zinc-300 dark:border-slate-600 text-zinc-700 dark:text-zinc-300 hover:bg-zinc-50 dark:hover:bg-slate-800"}`, children: opt.label }, opt.label))) })] })] }), _jsx("div", { className: "border-t border-zinc-200 dark:border-slate-700 px-4 py-3.5", children: _jsxs("button", { type: "button", onClick: applyFilters, className: "w-full rounded-lg bg-primary py-2.5 text-sm font-semibold text-white hover:bg-primary-600 transition-colors active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }));
68
+ }
69
+ export function AdminBundlesView({ getEditHref, newHref }) {
70
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
71
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
72
+ const [filterOpen, setFilterOpen] = useState(false);
73
+ const [pendingFilters, setPendingFilters] = useState(() => Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
32
74
  const [rebuildingId, setRebuildingId] = useState(null);
33
75
  const toast = useToast();
76
+ const openFilters = useCallback(() => {
77
+ setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, table.get(k)])));
78
+ setFilterOpen(true);
79
+ }, [table]);
80
+ const applyFilters = useCallback(() => {
81
+ const updates = { page: "1" };
82
+ for (const k of FILTER_KEYS)
83
+ updates[k] = pendingFilters[k] ?? "";
84
+ table.setMany(updates);
85
+ setFilterOpen(false);
86
+ }, [pendingFilters, table]);
87
+ const clearFilters = useCallback(() => {
88
+ setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
89
+ }, []);
90
+ const resetAll = useCallback(() => {
91
+ const updates = { q: "", sort: "" };
92
+ for (const k of FILTER_KEYS)
93
+ updates[k] = "";
94
+ table.setMany(updates);
95
+ setSearchInput("");
96
+ }, [table]);
97
+ const commitSearch = useCallback(() => {
98
+ table.set("q", searchInput.trim());
99
+ }, [searchInput, table]);
100
+ const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
101
+ const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
102
+ const filterParts = [];
103
+ const isActiveRaw = table.get("isActive");
104
+ if (isActiveRaw)
105
+ filterParts.push(`isActive==${isActiveRaw}`);
106
+ const stockStatusRaw = table.get("bundleStockStatus");
107
+ if (stockStatusRaw)
108
+ filterParts.push(`bundleStockStatus==${stockStatusRaw}`);
109
+ const filters = filterParts.join(",") || undefined;
110
+ const { rows, total, isLoading, errorMessage, refetch } = useAdminListingData({
111
+ queryKey: ["admin", "bundles", "listing"],
112
+ endpoint: ADMIN_ENDPOINTS.BUNDLES,
113
+ page: table.getNumber("page", 1),
114
+ pageSize: PAGE_SIZE,
115
+ sorts: table.get("sort") || DEFAULT_SORT,
116
+ filters,
117
+ q: table.get("q") || undefined,
118
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
119
+ id: toStringValue(item.id, `bundle-${index}`),
120
+ primary: toStringValue(item.name, "Untitled bundle"),
121
+ secondary: toStringValue(item.slug, "no-slug"),
122
+ price: formatPrice(item.bundlePriceInPaise),
123
+ members: String(Array.isArray(item.bundleProductIds) ? item.bundleProductIds.length : 0),
124
+ stockStatus: toStringValue(item.bundleStockStatus, "in_stock"),
125
+ isActive: item.isActive === true,
126
+ status: item.isActive === true
127
+ ? BUNDLE_COPY.adminList.activeBadge
128
+ : BUNDLE_COPY.adminList.inactiveBadge,
129
+ updatedAt: toRelativeDate(item.updatedAt ?? item.createdAt),
130
+ })),
131
+ getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
132
+ });
133
+ const currentPage = table.getNumber("page", 1);
134
+ const totalPages = Math.ceil(total / PAGE_SIZE);
135
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
34
136
  const handleRebuild = useCallback(async (bundleId) => {
35
137
  setRebuildingId(bundleId);
36
138
  try {
37
- const res = await fetch(`/api/admin/bundles/${encodeURIComponent(bundleId)}/rebuild`, { method: "POST" });
139
+ const res = await fetch(ADMIN_ENDPOINTS.BUNDLE_REBUILD(bundleId), { method: "POST" });
38
140
  if (!res.ok)
39
141
  throw new Error("Rebuild failed");
40
142
  toast.showToast("Bundle stock rebuilt.", "success");
@@ -44,33 +146,52 @@ export function AdminBundlesView({ getEditHref, newHref, }) {
44
146
  }
45
147
  finally {
46
148
  setRebuildingId(null);
149
+ refetch();
47
150
  }
48
- }, [toast]);
49
- const load = useCallback(async () => {
50
- setLoading(true);
51
- setError(null);
52
- try {
53
- const res = await fetch(`/api/admin/bundles?activeOnly=false&limit=200`);
54
- if (!res.ok)
55
- throw new Error(`Load failed: ${res.status}`);
56
- const json = (await res.json());
57
- setBundles(json?.data?.items ?? []);
58
- }
59
- catch (err) {
60
- setError(err instanceof Error ? err.message : "Failed to load");
61
- }
62
- finally {
63
- setLoading(false);
64
- }
65
- }, []);
66
- useEffect(() => {
67
- load();
68
- }, [load]);
69
- return (_jsx(Section, { className: "py-10", children: _jsx(Container, { size: "xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Row, { gap: "sm", align: "center", justify: "between", className: "flex-wrap", children: [_jsx(Heading, { level: 1, className: "text-2xl font-semibold text-zinc-900 dark:text-zinc-100", children: BUNDLE_COPY.adminListTitle }), _jsx(Button, { asChild: true, variant: "primary", children: _jsx(Link, { href: newHref, children: BUNDLE_COPY.adminList.newButton }) })] }), error && (_jsx(Text, { color: "danger", role: "alert", children: error })), loading ? (_jsx(Text, { children: BUNDLE_COPY.adminList.loading })) : bundles.length === 0 ? (_jsx(Div, { className: "rounded-2xl border border-dashed border-zinc-200 py-16 text-center dark:border-zinc-700", children: _jsx(Text, { color: "muted", children: BUNDLE_COPY.adminList.empty }) })) : (_jsx(Div, { className: "overflow-x-auto rounded-lg border border-zinc-200 dark:border-zinc-700", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-zinc-50 text-left dark:bg-zinc-900", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 font-semibold", children: BUNDLE_COPY.adminList.columns.name }), _jsx("th", { className: "px-3 py-2 font-semibold", children: BUNDLE_COPY.adminList.columns.price }), _jsx("th", { className: "px-3 py-2 font-semibold", children: BUNDLE_COPY.adminList.columns.members }), _jsx("th", { className: "px-3 py-2 font-semibold", children: BUNDLE_COPY.adminList.columns.stock }), _jsx("th", { className: "px-3 py-2 font-semibold", children: BUNDLE_COPY.adminList.columns.status }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { children: bundles.map((b) => {
70
- const stockKey = b.bundleStockStatus ?? "in_stock";
71
- const memberCount = b.bundleProductIds?.length ?? 0;
72
- return (_jsxs("tr", { className: "border-t border-zinc-100 dark:border-zinc-800", children: [_jsx("td", { className: "px-3 py-2", children: _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "medium", children: b.name }), _jsx(Text, { size: "xs", color: "muted", children: b.slug })] }) }), _jsx("td", { className: "px-3 py-2", children: formatPrice(b.bundlePriceInPaise) }), _jsx("td", { className: "px-3 py-2", children: memberCount }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: BUNDLE_STOCK_VARIANT[stockKey], children: STOCK_LIST_LABEL[stockKey] }) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: b.isActive ? "success" : "default", children: b.isActive
73
- ? BUNDLE_COPY.adminList.activeBadge
74
- : BUNDLE_COPY.adminList.inactiveBadge }) }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsxs(Row, { gap: "xs", justify: "end", children: [_jsx(Button, { variant: "ghost", size: "sm", isLoading: rebuildingId === b.id, disabled: rebuildingId === b.id, onClick: () => handleRebuild(b.id), children: ACTIONS.ADMIN["rebuild-bundle"].label }), _jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsx(Link, { href: getEditHref({ id: b.id }), children: BUNDLE_COPY.adminList.editLabel }) })] }) })] }, b.id));
75
- }) })] }) }))] }) }) }));
151
+ }, [refetch, toast]);
152
+ const bulkActions = [
153
+ {
154
+ id: "activate",
155
+ label: "Activate",
156
+ variant: "primary",
157
+ onClick: async () => {
158
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
159
+ method: "PUT",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify({ isActive: true }),
162
+ })));
163
+ selection.clearSelection();
164
+ refetch();
165
+ },
166
+ },
167
+ {
168
+ id: "deactivate",
169
+ label: "Deactivate",
170
+ variant: "secondary",
171
+ onClick: async () => {
172
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), {
173
+ method: "PUT",
174
+ headers: { "Content-Type": "application/json" },
175
+ body: JSON.stringify({ isActive: false }),
176
+ })));
177
+ selection.clearSelection();
178
+ refetch();
179
+ },
180
+ },
181
+ {
182
+ id: "delete",
183
+ label: "Delete",
184
+ variant: "danger",
185
+ onClick: async () => {
186
+ await Promise.all(selection.selectedIds.map((id) => fetch(ADMIN_ENDPOINTS.BUNDLE_BY_ID(id), { method: "DELETE" })));
187
+ selection.clearSelection();
188
+ refetch();
189
+ },
190
+ },
191
+ ];
192
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Bundles" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search bundles by name or slug\u2026", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => {
193
+ table.set("sort", v);
194
+ }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsx(Button, { asChild: true, size: "sm", variant: "primary", children: _jsxs("a", { href: newHref, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), BUNDLE_COPY.adminList.newButton] }) }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: bulkActions }), totalPages > 1 && (_jsx("div", { className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 flex justify-center bg-white/95 dark:bg-slate-900/95 backdrop-blur-sm border-b border-zinc-200 dark:border-slate-700 px-3 py-1.5", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsxs("div", { className: "py-4 px-3 sm:px-4", children: [errorMessage && (_jsx("div", { className: "mb-4 rounded-xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700 dark:border-red-900/60 dark:bg-red-950/40 dark:text-red-200", children: errorMessage })), _jsx(DataTable, { columns: COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: BUNDLE_COPY.adminList.empty, getRowHref: getEditHref, selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => next
195
+ ? selection.setSelectedIds(rows.map((r) => r.id))
196
+ : selection.clearSelection(), renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", isLoading: rebuildingId === row.id, disabled: rebuildingId === row.id, onClick: () => void handleRebuild(row.id), children: ACTIONS.ADMIN["rebuild-bundle"].label })) })] }), _jsx(BundlesFilterDrawer, { filterOpen: filterOpen, setFilterOpen: setFilterOpen, activeFilterCount: activeFilterCount, clearFilters: clearFilters, pendingFilters: pendingFilters, setPendingFilters: setPendingFilters, applyFilters: applyFilters })] }));
76
197
  }
@@ -0,0 +1 @@
1
+ export declare function AdminCatalogGuideView(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,28 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Package, FolderTree, Tag, Star, AlertTriangle } from "lucide-react";
3
+ import { Div, Heading, Text, Section, Alert } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminCatalogGuideView() {
6
+ return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(Package, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Catalog" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Products, categories, brands, and reviews \u2014 how the LetItRip catalog is structured and managed." })] }), [
7
+ {
8
+ Icon: Package, title: "Product Management",
9
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Listing types" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "standard" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "product-" }), "), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "auction" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "auction-" }), "), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "pre-order" }), " (prefix ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "preorder-" }), ")."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Admin vs store creation" }), ": Admins can create products on behalf of any store via the store picker in AdminProductEditorView."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status lifecycle" }), ": DRAFT \u2192 PUBLISHED \u2192 ARCHIVED. Published products appear in search. Archived products are hidden but not deleted."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "J13 rule" }), ": Always use ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "listingType" }), " field \u2014 the legacy ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isAuction" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isPreOrder" }), " booleans have been removed. All queries use ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "where(\"listingType\", \"==\", x)" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Media URLs" }), ": Never write raw Firebase Storage URLs to Firestore. All product image URLs must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/[slug]" }), " proxy format."] })] })),
10
+ },
11
+ {
12
+ Icon: FolderTree, title: "Category Taxonomy",
13
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "3-tier system" }), ": Root (tier 1) \u2192 Subcategory (tier 2) \u2192 Leaf (tier 3). Only leaf categories can be assigned to products."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Key fields" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "parentId" }), " points to the direct parent; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "rootId" }), " always points to the tier-1 root; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "path" }), " stores the full hierarchy path."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isLeaf" }), ": Must be ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "true" }), " on any category that can be selected in the product form. Tier-1 and tier-2 categories must have ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isLeaf: false" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Slug prefix" }), ": All category slugs start with ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "category-" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Adding a new root" }), ": Rare. Requires: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "display.icon" }), ", decision on ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "showOnHomepage" }), ", and a senior admin sign-off."] })] })),
14
+ },
15
+ {
16
+ Icon: Tag, title: "Brands",
17
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Slug prefix" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "brand-" }), ". Slugs are immutable after products reference them."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "displayOrder" }), ": Controls the sort order on public brand pages. Lower numbers appear first."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isActive: false" }), ": Hides the brand from public discovery but preserves historical product links. Use this instead of deleting a brand."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "logoURL / bannerURL" }), ": Must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/" }), " proxy \u2014 never raw Storage URLs."] })] })),
18
+ },
19
+ {
20
+ Icon: Star, title: "Reviews",
21
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isVerifiedPurchase" }), ": Set automatically by the system after order DELIVERED. Never manually toggle this field."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "sellerResponse" }), ": Written by the seller via their store dashboard. Admins should not edit seller responses \u2014 only remove clearly violating ones."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "When to delete vs hide" }), ": Delete for violations (PII, slurs, spam). Hide (move to unlisted) if a dispute is pending resolution."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "helpfulCount" }), ": Read-only \u2014 incremented by buyer votes. Do not manually edit this field."] })] })),
22
+ },
23
+ {
24
+ Icon: AlertTriangle, title: "Common Admin Mistakes",
25
+ content: (_jsx(Alert, { variant: "warning", children: _jsxs("ul", { className: "space-y-1 text-sm", children: [_jsx("li", { children: "\u2717 Publishing a product with no images \u2014 buyers cannot evaluate it." }), _jsxs("li", { children: ["\u2717 Creating a subcategory without setting ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "rootId" }), " \u2014 category tree breaks."] }), _jsxs("li", { children: ["\u2717 Setting ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "parentId" }), " of a root category to anything other than ", _jsx("code", { className: "text-xs bg-amber-100 dark:bg-amber-900/30 px-1 rounded", children: "null" }), " \u2014 creates circular hierarchy."] }), _jsx("li", { children: "\u2717 Duplicating brand slugs \u2014 Firestore document ID collision, second write silently overwrites the first." }), _jsx("li", { children: "\u2717 Writing raw Firebase Storage URLs to Firestore \u2014 breaks the media watermark proxy and CDN caching." })] }) })),
26
+ },
27
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
28
+ }
@@ -0,0 +1 @@
1
+ export declare function AdminContentGuideView(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { FileText, Calendar, BookOpen, Layout, Grid, Megaphone, Mail } from "lucide-react";
3
+ import { Div, Heading, Text, Section, Alert } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminContentGuideView() {
6
+ return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(FileText, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Content & Marketing" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Blog posts, events, FAQs, carousel, homepage sections, ads, and newsletter on LetItRip." })] }), [
7
+ {
8
+ Icon: FileText, title: "Blog Posts",
9
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Statuses" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "draft" }), " (not public), ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "published" }), " (live, indexed)."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "readTimeMinutes" }), ": Auto-calculated from content word count (200 wpm). Override manually if the post has heavy media."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "youtubeId" }), ": Optional embedded YouTube video. Use only the video ID (e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "dQw4w9WgXcQ" }), "), not the full URL."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Media in body" }), ": All images in rich text must use the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "/api/media/" }), " proxy URL, not raw Firebase Storage links."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "isFeatured" }), ": Sets the post as featured in the blog listing. Different from the homepage featured section \u2014 that is driven by a separate ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "homepageSections" }), " config."] })] })),
10
+ },
11
+ {
12
+ Icon: Calendar, title: "Events",
13
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Event types" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "sale" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "offer" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "poll" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "survey" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "feedback" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "raffle" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "spin_wheel" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status" }), ": Computed from ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "startsAt" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "endsAt" }), " (upcoming \u2192 active \u2192 ended). Do not set a status field manually \u2014 it will be overwritten."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "stats.totalEntries vs approvedEntries" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "totalEntries" }), " = all submitted (including waitlisted); ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "approvedEntries" }), " = CONFIRMED only."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Cancelling an entry" }), ": Set status to CANCELLED in the entry doc. Do not delete entries \u2014 audit trail is needed for raffle fairness."] })] })),
14
+ },
15
+ {
16
+ Icon: BookOpen, title: "FAQs",
17
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Categories" }), " and where they appear: Shipping (help + footer), Returns (help), Payments (help), Auctions (help), Pre-orders (help). Set ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "showOnHomepage: true" }), " for up to 5 FAQs shown on the public home page."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "searchTokens[]" }), ": Must be manually filled \u2014 drives the FAQ search feature. Include synonyms, misspellings, and related keywords."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "priority + order" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "priority" }), " sorts FAQs within their category; ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order" }), " is the global absolute position. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isPinned: true" }), " floats the FAQ above non-pinned items in its category."] })] })),
18
+ },
19
+ {
20
+ Icon: Layout, title: "Carousel Slides",
21
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "MAX_ACTIVE_SLIDES = 5" }), ": You cannot activate a 6th slide. Deactivate an existing slide first."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "background.type" }), ": ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "image" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "video" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "color" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "gradient" }), ". Do not have 2 active video slides simultaneously \u2014 auto-playing two videos degrades mobile performance."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Reordering" }), ": Drag slides in AdminCarouselView to reorder. The ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order" }), " field is updated automatically."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "cards[]" }), ": Each slide contains an array of content cards displayed as an overlay. Max 3 cards per slide recommended for readability."] })] })),
22
+ },
23
+ {
24
+ Icon: Grid, title: "Homepage Sections",
25
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "21 section types" }), ": product-grid, brand-grid, category-grid, event-highlight, blog-preview, etc. The full list and config shapes are in ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "appkit/src/features/homepage/schemas/firestore.ts" }), "."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "order" }), ": Controls render sequence on the public homepage. Lower numbers appear higher on the page."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "enabled: false" }), ": Hides the section without deleting it \u2014 useful for temporary removal."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Never edit Firestore directly" }), ": Always use AdminSectionsView. The config shapes are validated on write \u2014 direct Firestore edits can produce invalid shapes that crash the home page render."] })] })),
26
+ },
27
+ {
28
+ Icon: Megaphone, title: "Ads",
29
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: ["Ad slot keys correspond to specific locations on the page (e.g. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "homepage-banner-top" }), ", ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "sidebar-right" }), ")."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "isActive" }), " controls immediate visibility. ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "startDate" }), "/", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "endDate" }), " schedule automatic activation/deactivation."] }), _jsxs("li", { children: [_jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "impressions" }), " and ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "clicks" }), " are read-only counters \u2014 incremented by the ad-serving system. Do not edit manually."] })] })),
30
+ },
31
+ {
32
+ Icon: Mail, title: "Newsletter",
33
+ content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: ["Subscribers are stored in the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "newsletter" }), " collection. The admin panel supports subscriber export and campaign send."] }), _jsx(Alert, { variant: "warning", children: "Legal obligation: every email sent must include a working unsubscribe link. Under the Indian IT Act and CAN-SPAM, unsubscribe requests must be honoured within 10 business days. Never send to unsubscribed addresses. All newsletter exports are de-identified by default." })] })),
34
+ },
35
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
36
+ }
@@ -55,7 +55,7 @@ export function AdminCouponEditorView({ couponId, onSaved, onDeleted, embedded,
55
55
  // validity
56
56
  const [startDate, setStartDate] = React.useState(new Date().toISOString().split("T")[0]);
57
57
  const [endDate, setEndDate] = React.useState("");
58
- const [isActive, setIsActive] = React.useState(false);
58
+ const [isActive, setIsActive] = React.useState(true);
59
59
  // restrictions
60
60
  const [firstTimeOnly, setFirstTimeOnly] = React.useState(false);
61
61
  const [combinable, setCombinable] = React.useState(false);
@@ -0,0 +1,5 @@
1
+ export interface AdminGuideHubViewProps {
2
+ permissions?: string[];
3
+ isFullAdmin?: boolean;
4
+ }
5
+ export declare function AdminGuideHubView({ permissions, isFullAdmin }: AdminGuideHubViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, } from "lucide-react";
3
+ import { Div, Heading, Text, Section, Alert } from "../../../ui";
4
+ import { ROUTES } from "../../../next/routing/route-map";
5
+ const GUIDE_CARDS = [
6
+ {
7
+ Icon: Users,
8
+ title: "Users & Accounts",
9
+ description: "Roles, search, user editor, sessions management, employee accounts, and PII handling.",
10
+ href: String(ROUTES.ADMIN.GUIDE_USERS),
11
+ permission: "admin:users:read",
12
+ },
13
+ {
14
+ Icon: Package,
15
+ title: "Catalog",
16
+ description: "Products, listing types, categories (3-tier taxonomy), brands, and reviews.",
17
+ href: String(ROUTES.ADMIN.GUIDE_CATALOG),
18
+ permission: "admin:products:read",
19
+ },
20
+ {
21
+ Icon: Store,
22
+ title: "Stores & Sellers",
23
+ description: "Store lifecycle, identity architecture, capabilities, addresses, and suspension.",
24
+ href: String(ROUTES.ADMIN.GUIDE_STORES),
25
+ permission: "admin:stores:read",
26
+ },
27
+ {
28
+ Icon: ShoppingBag,
29
+ title: "Orders & Finance",
30
+ description: "Order statuses, ID format, dispute intervention, payouts, returns, and commission math.",
31
+ href: String(ROUTES.ADMIN.GUIDE_ORDERS),
32
+ permission: "admin:orders:read",
33
+ },
34
+ {
35
+ Icon: FileText,
36
+ title: "Content & Marketing",
37
+ description: "Blog posts, events, FAQs, carousel slides, homepage sections, ads, and newsletter.",
38
+ href: String(ROUTES.ADMIN.GUIDE_CONTENT),
39
+ permission: "admin:blog:read",
40
+ },
41
+ {
42
+ Icon: Settings,
43
+ title: "Site Configuration",
44
+ description: "Site settings groups, feature flags, integrations, auction config, and legal pages.",
45
+ href: String(ROUTES.ADMIN.GUIDE_SITE),
46
+ permission: "admin:site:read",
47
+ },
48
+ {
49
+ Icon: Users,
50
+ title: "Team & Permissions",
51
+ description: "Permission groups, employee invite flow, role vs permission difference, and RBAC model.",
52
+ href: String(ROUTES.ADMIN.GUIDE_TEAM),
53
+ permission: "admin:team:read",
54
+ },
55
+ {
56
+ Icon: BarChart2,
57
+ title: "Analytics",
58
+ description: "Revenue dashboard, order funnel, product performance, and store-level metrics.",
59
+ href: String(ROUTES.ADMIN.GUIDE_ANALYTICS),
60
+ permission: "admin:analytics:view",
61
+ },
62
+ {
63
+ Icon: Shield,
64
+ title: "Trust & Safety",
65
+ description: "Bans (soft/hard), scam registry, support tickets, moderation queue, and reports.",
66
+ href: String(ROUTES.ADMIN.GUIDE_TRUST),
67
+ permission: "admin:moderation:read",
68
+ },
69
+ ];
70
+ export function AdminGuideHubView({ permissions = [], isFullAdmin = false }) {
71
+ const hasPermission = (perm) => isFullAdmin || permissions.includes(perm);
72
+ const visibleCards = GUIDE_CARDS.filter((c) => hasPermission(c.permission));
73
+ const hasAny = visibleCards.length > 0;
74
+ return (_jsxs(Div, { className: "space-y-8 pb-10", children: [_jsx(Section, { className: "rounded-2xl overflow-hidden border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] shadow-sm", children: _jsxs(Div, { className: "px-6 py-8 md:px-10", style: {
75
+ background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 60%,var(--appkit-color-secondary-400,#7c3aed) 100%)",
76
+ }, children: [_jsxs(Div, { className: "flex items-center gap-3 mb-3", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center", children: _jsx(BookOpen, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-white/80 uppercase tracking-widest", children: "Admin & Employee Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-white mb-2", children: "LetItRip Internal Guide" }), _jsx(Text, { className: "text-white/80 text-base", children: "Everything you need to operate and manage LetItRip \u2014 India's largest collectibles marketplace." })] }) }), _jsx(Alert, { variant: "info", compact: true, children: "These guides describe expected platform behaviour. Real access to each section is enforced independently by the permission system \u2014 this page is reference only." }), !hasAny && (_jsx(Alert, { variant: "info", title: "Limited access", children: "Your permission set doesn't include read access to any admin sections yet. Contact your admin to request the permissions you need." })), hasAny && (_jsxs(Section, { children: [_jsx(Heading, { level: 2, className: "text-lg font-semibold text-[var(--appkit-color-text)] mb-4", children: "Guides" }), _jsx(Div, { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: visibleCards.map(({ Icon, title, description, href }) => (_jsxs("a", { href: href, className: "group flex flex-col gap-3 rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-5 hover:border-[var(--appkit-color-primary)] hover:shadow-md transition-all", children: [_jsxs(Div, { className: "flex items-center gap-3", children: [_jsx(Div, { className: "flex-shrink-0 w-9 h-9 rounded-lg flex items-center justify-center", style: {
77
+ background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)",
78
+ }, children: _jsx(Icon, { className: "w-4 h-4 text-white" }) }), _jsx(Text, { className: "font-semibold text-[var(--appkit-color-text)] group-hover:text-[var(--appkit-color-primary)] transition-colors", children: title })] }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] leading-relaxed", children: description }), _jsx(Text, { className: "text-xs font-medium text-[var(--appkit-color-primary)] mt-auto", children: "Read guide \u2192" })] }, title))) })] })), _jsx(Section, { className: "rounded-2xl border border-amber-200 bg-amber-50 dark:bg-amber-950/20 dark:border-amber-800 p-6", children: _jsxs(Div, { className: "flex items-start gap-3", children: [_jsx(AlertTriangle, { className: "w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5" }), _jsxs(Div, { children: [_jsx(Text, { className: "font-semibold text-amber-800 dark:text-amber-300 mb-1", children: "Remember: with great access comes responsibility" }), _jsx(Text, { className: "text-sm text-amber-700 dark:text-amber-400", children: "Admin actions are logged. Never access user PII (email, phone) without a legitimate support reason. Never grant capabilities or permissions without documented approval. When in doubt, escalate to a senior admin." })] })] }) })] }));
79
+ }
@@ -0,0 +1 @@
1
+ export declare function AdminOrdersGuideView(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { ShoppingBag, Hash, Scale, Wallet, RotateCcw, DollarSign, Gavel } from "lucide-react";
3
+ import { Div, Heading, Text, Section } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminOrdersGuideView() {
6
+ return (_jsxs(Div, { className: "space-y-8 pb-10 max-w-3xl mx-auto", children: [_jsxs(Section, { children: [_jsxs(Div, { className: "flex items-center gap-3 mb-2", children: [_jsx(Div, { className: "flex-shrink-0 w-10 h-10 rounded-xl flex items-center justify-center", style: { background: "linear-gradient(135deg,var(--appkit-color-primary-700,#4f46e5) 0%,var(--appkit-color-cobalt,#2563eb) 100%)" }, children: _jsx(ShoppingBag, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-widest", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-2xl md:text-3xl font-bold text-[var(--appkit-color-text)] mb-2", children: "Orders & Finance" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Order statuses, payouts, disputes, returns, and commission math on LetItRip." })] }), [
7
+ {
8
+ Icon: ShoppingBag, title: "Order Statuses",
9
+ content: (_jsxs(Div, { className: "space-y-2 text-sm", children: [[
10
+ ["PENDING", "Payment received. Can still be cancelled by buyer or admin."],
11
+ ["PROCESSING", "Seller has acknowledged and is packing."],
12
+ ["SHIPPED", "Dispatched. Tracking number provided by seller."],
13
+ ["DELIVERED", "Carrier confirmed delivery."],
14
+ ["RETURN_REQUESTED", "Buyer opened a return. Seller has 3 days to respond."],
15
+ ["REFUNDED", "Return accepted and refund issued via Razorpay."],
16
+ ["CANCELLED", "Cancelled before shipping. Payment returned."],
17
+ ].map(([status, note]) => (_jsxs(Div, { className: "flex gap-3", children: [_jsx(Text, { className: "flex-shrink-0 font-mono font-semibold text-[var(--appkit-color-primary)] w-40 text-xs", children: status }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: note })] }, status))), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] pt-2", children: "Admin-only transitions: force-cancel (any status before DELIVERED), force-refund (post-DELIVERED dispute)." })] })),
18
+ },
19
+ {
20
+ Icon: Hash, title: "Order ID Format",
21
+ content: (_jsxs(_Fragment, { children: [_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-3", children: ["Format: ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 py-0.5 rounded", children: "order-{itemCount}-{YYYYMMDD}-{rand6}" })] }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["The item count in the ID enables quick triage \u2014 a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order-8-..." }), " is a large multi-item order that needs more careful handling than a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "order-1-..." }), ". Search by order ID in the admin panel using the full ID string."] })] })),
22
+ },
23
+ {
24
+ Icon: Scale, title: "Intervening in a Dispute",
25
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "Admin steps in when: seller is unresponsive for 3+ days on a return, or buyer escalates via support ticket." }), _jsxs("li", { children: ["Admin dispute tools: ", _jsx("strong", { children: "force-accept return" }), " (moves to REFUNDED and withholds seller payout), ", _jsx("strong", { children: "platform credit" }), " (future feature)."] }), _jsx("li", { children: "Document your reasoning in the support ticket before taking any force action." })] })),
26
+ },
27
+ {
28
+ Icon: Wallet, title: "Payouts",
29
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Payout cycle" }), ": Weekly. All DELIVERED orders (minus platform commission) are batched into a single payout per store."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "Status flow" }), ": PENDING \u2192 PROCESSING \u2192 PAID / FAILED."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "FAILED payout" }), ": Check the store's ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "payoutDetails" }), " \u2014 UPI VPA or bank details may be stale. Contact the seller via their store email before retrying."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "ordersIncluded" }), ": Array of order IDs bundled in this payout batch \u2014 use to audit which orders are covered."] })] })),
30
+ },
31
+ {
32
+ Icon: RotateCcw, title: "Return Requests",
33
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "Admin can view the return reason and attached photos on the order detail page." }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "When to override a seller rejection" }), ": Only when photographic evidence clearly supports the buyer's claim of a significant not-as-described defect. Do not override for buyer's remorse."] }), _jsx("li", { children: "After admin override: refund is issued automatically via Razorpay (3\u20137 business days for card, 1\u20132 for UPI). Seller payout for that order is withheld." })] })),
34
+ },
35
+ {
36
+ Icon: DollarSign, title: "Commission & Settlement Math",
37
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "platformFee" }), ": Set in ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "siteSettings.fees.platformFee" }), " (percentage). Applied to the order's item subtotal."] }), _jsxs("li", { children: [_jsx("strong", { className: "text-[var(--appkit-color-text)]", children: "customCommissionRate" }), ": Stored on ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "StoreDocument" }), " for stores with the ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "lower_commission_rate" }), " capability. Overrides the platform default for that store."] }), _jsx("li", { children: "Payout = order subtotal \u00D7 (1 \u2212 commission rate). Shipping fees are passed through to the seller at cost." })] })),
38
+ },
39
+ {
40
+ Icon: Gavel, title: "Auction Settlement",
41
+ content: (_jsxs("ul", { className: GC.listMuted, children: [_jsx("li", { children: "When an auction ends and the winner does not pay within 48 hours: the order auto-cancels and the system notifies the next-highest bidder." }), _jsx("li", { children: "If the auto-cancel system fails (rare), admin can manually cancel from the order detail page and trigger the next-bidder flow via a support action." }), _jsxs("li", { children: ["Repeat non-paying winners can receive a ", _jsx("code", { className: "text-xs bg-[var(--appkit-color-border)] px-1 rounded", children: "place_bids" }), " soft ban from the Trust & Safety section."] })] })),
42
+ },
43
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { className: "rounded-2xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] overflow-hidden", children: [_jsxs(Div, { className: "flex items-center gap-3 px-6 py-4 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, className: "text-base font-semibold text-[var(--appkit-color-text)]", children: title })] }), _jsx(Div, { className: "px-6 py-5", children: content })] }, title)))] }));
44
+ }
@@ -86,7 +86,7 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
86
86
  setPendingFilters(Object.fromEntries(FILTER_KEYS.map((k) => [k, ""])));
87
87
  }, []);
88
88
  const resetAll = useCallback(() => {
89
- const updates = { q: "", sort: "" };
89
+ const updates = { q: "", sort: "", showSold: "" };
90
90
  for (const k of FILTER_KEYS)
91
91
  updates[k] = "";
92
92
  table.setMany(updates);
@@ -95,9 +95,12 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
95
95
  const commitSearch = useCallback(() => {
96
96
  table.set("q", searchInput.trim());
97
97
  }, [searchInput, table]);
98
+ const showSold = table.get("showSold") === "true";
98
99
  const activeFilterCount = FILTER_KEYS.filter((k) => !!table.get(k)).length;
99
- const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0;
100
+ const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || activeFilterCount > 0 || showSold;
100
101
  const filterParts = [];
102
+ if (!showSold)
103
+ filterParts.push("isSold==false");
101
104
  const statusRaw = table.get("status");
102
105
  if (statusRaw && statusRaw !== "All")
103
106
  filterParts.push(`status==${statusRaw}`);
@@ -176,7 +179,9 @@ export function AdminProductsView({ children, actionHref, getRowHref, ...props }
176
179
  className: "w-56",
177
180
  render: (row) => (_jsx("div", { className: "flex flex-wrap gap-x-3 gap-y-1", onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), role: "presentation", children: FLAG_DEFS.map(({ key, label }) => (_jsx(Toggle, { size: "sm", label: label, checked: !!row[key], onChange: (v) => { void handleToggle(row.id, key, v); } }, key))) })),
178
181
  };
179
- return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Products" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
182
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(Heading, { level: 1, className: "sr-only", children: "Products" }), _jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products, SKUs, or seller names", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState, toggles: [
183
+ { label: "Show sold", active: showSold, onChange: (next) => table.set("showSold", next ? "true" : "") },
184
+ ], extra: _jsxs(Button, { size: "sm", onClick: openCreatePanel, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), "Add Product"] }) }), _jsx(BulkActionBar, { selectedCount: selection.selectedCount, onClearSelection: selection.clearSelection, actions: [
180
185
  { id: "feature", label: "Toggle Featured", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
181
186
  void handleToggle(id, "featured", !rows.find(r => r.id === id)?.featured); selection.clearSelection(); } },
182
187
  { id: "promote", label: "Toggle Promoted", variant: "secondary", onClick: () => { for (const id of selection.selectedIds)
@@ -0,0 +1 @@
1
+ export declare function AdminSiteConfigGuideView(): import("react/jsx-runtime").JSX.Element;