@mohasinac/appkit 2.7.50 → 2.7.53

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
@@ -0,0 +1,104 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Plus } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
8
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ROUTES } from "../../..";
10
+ import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
11
+ import { TABLE_KEYS } from "../../../constants/table-keys";
12
+ const PAGE_SIZE = 25;
13
+ const DEFAULT_SORT = "-createdAt";
14
+ const SORT_OPTIONS = [
15
+ { value: "-createdAt", label: "Newest" },
16
+ { value: "createdAt", label: "Oldest" },
17
+ { value: "productTitle", label: "Name A–Z" },
18
+ { value: "-productTitle", label: "Name Z–A" },
19
+ { value: "price", label: "Price: Low–High" },
20
+ { value: "-price", label: "Price: High–Low" },
21
+ ];
22
+ const COLUMNS = [
23
+ {
24
+ key: "title",
25
+ header: "Bundle",
26
+ render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
27
+ },
28
+ {
29
+ key: "price",
30
+ header: "Price",
31
+ render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
32
+ },
33
+ {
34
+ key: "itemCount",
35
+ header: "Items",
36
+ render: (row) => (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-violet-100 text-violet-700 dark:bg-violet-900/40 dark:text-violet-300 tabular-nums", children: row.itemCount })),
37
+ },
38
+ {
39
+ key: "status",
40
+ header: "Status",
41
+ render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
42
+ ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
43
+ : "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
44
+ },
45
+ {
46
+ key: "createdAt",
47
+ header: "Created",
48
+ render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
49
+ },
50
+ ];
51
+ export function SellerBundlesView({ onCreateClick, onBulkDelete, }) {
52
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
53
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
54
+ const [view] = useState("table");
55
+ const commitSearch = useCallback(() => {
56
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
57
+ }, [searchInput, table]);
58
+ const resetAll = useCallback(() => {
59
+ table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
60
+ setSearchInput("");
61
+ }, [table]);
62
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
63
+ const { rows, total, isLoading, errorMessage } = useSellerListingData({
64
+ queryKey: ["seller", "bundles"],
65
+ endpoint: SELLER_ENDPOINTS.PRODUCTS,
66
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
67
+ pageSize: PAGE_SIZE,
68
+ sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
69
+ filters: "listingType==bundle",
70
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
71
+ mapRows: (response) => toRecordArray(response.products).map((item, index) => ({
72
+ id: toStringValue(item.id, `bundle-${index}`),
73
+ raw: item,
74
+ title: toStringValue(item.productTitle ?? item.title, "Untitled bundle"),
75
+ price: toRupees(item.price),
76
+ itemCount: Array.isArray(item.bundleProductIds)
77
+ ? item.bundleProductIds.length
78
+ : Number(item.bundleItemCount ?? 0),
79
+ status: toStringValue(item.status, "draft"),
80
+ createdAt: toRelativeDate(item.createdAt),
81
+ })),
82
+ getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
83
+ });
84
+ const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
85
+ const totalPages = Math.ceil(total / PAGE_SIZE);
86
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
87
+ const bulkActions = onBulkDelete
88
+ ? [{
89
+ id: "bulk-delete",
90
+ label: "Delete selected",
91
+ variant: "danger",
92
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
93
+ }]
94
+ : [];
95
+ const handleCreate = useCallback(() => {
96
+ if (onCreateClick) {
97
+ onCreateClick();
98
+ }
99
+ else {
100
+ window.location.href = String(ROUTES.STORE.PRODUCTS_NEW);
101
+ }
102
+ }, [onCreateClick]);
103
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search bundles...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Bundle" })] }) }), 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) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No bundles yet \u2014 create a bundle to group multiple products together" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
104
+ }
@@ -0,0 +1,5 @@
1
+ export interface SellerClassifiedViewProps {
2
+ onCreateClick?: () => void;
3
+ onBulkDelete?: (ids: string[]) => Promise<void>;
4
+ }
5
+ export declare function SellerClassifiedView({ onCreateClick, onBulkDelete, }: SellerClassifiedViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,113 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Plus } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
8
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ROUTES } from "../../..";
10
+ import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
11
+ import { TABLE_KEYS } from "../../../constants/table-keys";
12
+ const PAGE_SIZE = 25;
13
+ const DEFAULT_SORT = "-createdAt";
14
+ const SORT_OPTIONS = [
15
+ { value: "-createdAt", label: "Newest" },
16
+ { value: "createdAt", label: "Oldest" },
17
+ { value: "productTitle", label: "Name A–Z" },
18
+ { value: "price", label: "Price: Low–High" },
19
+ { value: "-price", label: "Price: High–Low" },
20
+ ];
21
+ const COLUMNS = [
22
+ {
23
+ key: "title",
24
+ header: "Listing",
25
+ render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
26
+ },
27
+ {
28
+ key: "price",
29
+ header: "Price",
30
+ render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
31
+ },
32
+ {
33
+ key: "city",
34
+ header: "Location",
35
+ render: (row) => (_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.city || "—" })),
36
+ },
37
+ {
38
+ key: "acceptsShipping",
39
+ header: "Shipping",
40
+ render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${row.acceptsShipping
41
+ ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
42
+ : "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.acceptsShipping ? "Ships" : "Meetup only" })),
43
+ },
44
+ {
45
+ key: "status",
46
+ header: "Status",
47
+ render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
48
+ ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
49
+ : "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
50
+ },
51
+ {
52
+ key: "createdAt",
53
+ header: "Created",
54
+ render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
55
+ },
56
+ ];
57
+ export function SellerClassifiedView({ onCreateClick, onBulkDelete, }) {
58
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
59
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
60
+ const [view] = useState("table");
61
+ const commitSearch = useCallback(() => {
62
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
63
+ }, [searchInput, table]);
64
+ const resetAll = useCallback(() => {
65
+ table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
66
+ setSearchInput("");
67
+ }, [table]);
68
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
69
+ const { rows, total, isLoading, errorMessage } = useSellerListingData({
70
+ queryKey: ["seller", "classified"],
71
+ endpoint: SELLER_ENDPOINTS.PRODUCTS,
72
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
73
+ pageSize: PAGE_SIZE,
74
+ sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
75
+ filters: "listingType==classified",
76
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
77
+ mapRows: (response) => toRecordArray(response.products).map((item, index) => {
78
+ const classified = (item.classified ?? {});
79
+ const meetupArea = (classified.meetupArea ?? {});
80
+ return {
81
+ id: toStringValue(item.id, `classified-${index}`),
82
+ raw: item,
83
+ title: toStringValue(item.productTitle ?? item.title, "Untitled"),
84
+ price: toRupees(item.price),
85
+ city: toStringValue(meetupArea.city ?? classified.city, ""),
86
+ acceptsShipping: Boolean(classified.acceptsShipping),
87
+ status: toStringValue(item.status, "draft"),
88
+ createdAt: toRelativeDate(item.createdAt),
89
+ };
90
+ }),
91
+ getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
92
+ });
93
+ const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
94
+ const totalPages = Math.ceil(total / PAGE_SIZE);
95
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
96
+ const bulkActions = onBulkDelete
97
+ ? [{
98
+ id: "bulk-delete",
99
+ label: "Delete selected",
100
+ variant: "danger",
101
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
102
+ }]
103
+ : [];
104
+ const handleCreate = useCallback(() => {
105
+ if (onCreateClick) {
106
+ onCreateClick();
107
+ }
108
+ else {
109
+ window.location.href = String(ROUTES.STORE.CLASSIFIED_NEW);
110
+ }
111
+ }, [onCreateClick]);
112
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search classified listings...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Classified" })] }) }), 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) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No classified listings yet \u2014 post your first buy/sell/trade ad" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
113
+ }
@@ -1,11 +1,23 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { StackedViewShell } from "../../../ui";
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { StackedViewShell, Div, Text } from "../../../ui";
4
+ function DefaultStatsPlaceholder({ isLoading }) {
5
+ return (_jsx("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] p-5", children: isLoading ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-3 w-16 animate-pulse rounded bg-[var(--appkit-color-border)]" }), _jsx("div", { className: "mt-3 h-6 w-20 animate-pulse rounded bg-[var(--appkit-color-border)]" })] })) : (_jsxs(_Fragment, { children: [_jsxs(Text, { size: "xs", variant: "secondary", className: "uppercase tracking-widest font-semibold", children: ["Stat ", i] }), _jsx(Text, { size: "xl", className: "mt-2 font-bold tabular-nums", children: "\u2014" })] })) }, i))) }));
6
+ }
7
+ function DefaultQuickActionsPlaceholder() {
8
+ return (_jsx("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-4", children: [1, 2, 3, 4].map((i) => (_jsx(Div, { className: "h-12 animate-pulse rounded-xl border border-[var(--appkit-color-border)] bg-[var(--appkit-color-border)]" }, i))) }));
9
+ }
3
10
  export function SellerDashboardView({ labels = {}, renderStats, renderQuickActions, renderRecentListings, renderRevenueChart, renderTopProducts, isLoading = false, ...rest }) {
11
+ const stats = (renderStats ?? ((busy) => _jsx(DefaultStatsPlaceholder, { isLoading: busy })))(isLoading);
12
+ const quickActions = (renderQuickActions ?? (() => _jsx(DefaultQuickActionsPlaceholder, {})))();
13
+ const revenueChart = (renderRevenueChart ?? (() => null))();
14
+ const topProducts = (renderTopProducts ?? (() => null))();
15
+ const recentListings = (renderRecentListings ?? (() => null))();
4
16
  return (_jsx(StackedViewShell, { portal: "seller", ...rest, title: labels.title, sections: [
5
- renderStats?.(isLoading),
6
- renderQuickActions?.(),
7
- renderRevenueChart?.(),
8
- renderTopProducts?.(),
9
- renderRecentListings?.(),
17
+ stats,
18
+ quickActions,
19
+ revenueChart,
20
+ topProducts,
21
+ recentListings,
10
22
  ] }));
11
23
  }
@@ -0,0 +1,5 @@
1
+ export interface SellerDigitalCodesViewProps {
2
+ onCreateClick?: () => void;
3
+ onBulkDelete?: (ids: string[]) => Promise<void>;
4
+ }
5
+ export declare function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }: SellerDigitalCodesViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,115 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { Plus } from "lucide-react";
5
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
6
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
7
+ import { BulkActionBar, Button, DataTable, Div, ListingToolbar, Pagination, Text, } from "../../../ui";
8
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
9
+ import { ROUTES } from "../../..";
10
+ import { toRecordArray, toRelativeDate, toRupees, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
11
+ import { TABLE_KEYS } from "../../../constants/table-keys";
12
+ const PAGE_SIZE = 25;
13
+ const DEFAULT_SORT = "-createdAt";
14
+ const SORT_OPTIONS = [
15
+ { value: "-createdAt", label: "Newest" },
16
+ { value: "createdAt", label: "Oldest" },
17
+ { value: "productTitle", label: "Name A–Z" },
18
+ { value: "price", label: "Price: Low–High" },
19
+ ];
20
+ const COLUMNS = [
21
+ {
22
+ key: "title",
23
+ header: "Product",
24
+ render: (row) => _jsx(Text, { className: "text-sm font-medium", children: row.title }),
25
+ },
26
+ {
27
+ key: "price",
28
+ header: "Price",
29
+ render: (row) => _jsx(Text, { className: "text-sm tabular-nums", children: row.price }),
30
+ },
31
+ {
32
+ key: "codePoolSize",
33
+ header: "Total Codes",
34
+ render: (row) => (_jsx(Text, { className: "text-sm tabular-nums", children: row.codePoolSize !== null ? row.codePoolSize : "—" })),
35
+ },
36
+ {
37
+ key: "codesAvailable",
38
+ header: "Available",
39
+ render: (row) => (_jsx(Text, { className: "text-sm tabular-nums", children: row.codesAvailable !== null ? row.codesAvailable : "—" })),
40
+ },
41
+ {
42
+ key: "deliveryMethod",
43
+ header: "Delivery",
44
+ render: (row) => (_jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400 capitalize", children: row.deliveryMethod.replace(/-/g, " ") })),
45
+ },
46
+ {
47
+ key: "status",
48
+ header: "Status",
49
+ render: (row) => (_jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${row.status === "active"
50
+ ? "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300"
51
+ : "bg-zinc-100 text-zinc-600 dark:bg-slate-800 dark:text-slate-400"}`, children: row.status })),
52
+ },
53
+ {
54
+ key: "createdAt",
55
+ header: "Created",
56
+ render: (row) => _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: row.createdAt }),
57
+ },
58
+ ];
59
+ export function SellerDigitalCodesView({ onCreateClick, onBulkDelete, }) {
60
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
61
+ const [searchInput, setSearchInput] = useState(table.get(TABLE_KEYS.QUERY) || "");
62
+ const [view] = useState("table");
63
+ const commitSearch = useCallback(() => {
64
+ table.set(TABLE_KEYS.QUERY, searchInput.trim());
65
+ }, [searchInput, table]);
66
+ const resetAll = useCallback(() => {
67
+ table.setMany({ [TABLE_KEYS.QUERY]: "", [TABLE_KEYS.SORT]: "" });
68
+ setSearchInput("");
69
+ }, [table]);
70
+ const hasActiveState = !!table.get(TABLE_KEYS.QUERY) || table.get(TABLE_KEYS.SORT) !== DEFAULT_SORT;
71
+ const { rows, total, isLoading, errorMessage } = useSellerListingData({
72
+ queryKey: ["seller", "digital-codes"],
73
+ endpoint: SELLER_ENDPOINTS.PRODUCTS,
74
+ page: table.getNumber(TABLE_KEYS.PAGE, 1),
75
+ pageSize: PAGE_SIZE,
76
+ sorts: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT,
77
+ filters: "listingType==digital-code",
78
+ q: table.get(TABLE_KEYS.QUERY) || undefined,
79
+ mapRows: (response) => toRecordArray(response.products).map((item, index) => {
80
+ const dc = (item.digitalCode ?? {});
81
+ return {
82
+ id: toStringValue(item.id, `dc-${index}`),
83
+ raw: item,
84
+ title: toStringValue(item.productTitle ?? item.title, "Untitled"),
85
+ price: toRupees(item.price),
86
+ codePoolSize: typeof dc.codePoolSize === "number" ? dc.codePoolSize : null,
87
+ codesAvailable: typeof dc.codesAvailable === "number" ? dc.codesAvailable : null,
88
+ deliveryMethod: toStringValue(dc.codeDeliveryMethod, "auto-claim"),
89
+ status: toStringValue(item.status, "draft"),
90
+ createdAt: toRelativeDate(item.createdAt),
91
+ };
92
+ }),
93
+ getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
94
+ });
95
+ const currentPage = table.getNumber(TABLE_KEYS.PAGE, 1);
96
+ const totalPages = Math.ceil(total / PAGE_SIZE);
97
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
98
+ const bulkActions = onBulkDelete
99
+ ? [{
100
+ id: "bulk-delete",
101
+ label: "Delete selected",
102
+ variant: "danger",
103
+ onClick: async () => { await onBulkDelete(selection.selectedIds); selection.clearSelection(); },
104
+ }]
105
+ : [];
106
+ const handleCreate = useCallback(() => {
107
+ if (onCreateClick) {
108
+ onCreateClick();
109
+ }
110
+ else {
111
+ window.location.href = String(ROUTES.STORE.DIGITAL_CODES_NEW);
112
+ }
113
+ }, [onCreateClick]);
114
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: 0, searchValue: searchInput, searchPlaceholder: "Search digital code products...", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get(TABLE_KEYS.SORT) || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set(TABLE_KEYS.SORT, v); }, showTableView: true, view: view, onViewChange: () => { }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: _jsxs(Button, { size: "sm", onClick: handleCreate, className: "flex items-center gap-1.5", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { children: "New Digital Code" })] }) }), 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) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _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 dark:bg-red-950/40 dark:border-red-900/60 px-4 py-3 text-sm text-red-700 dark:text-red-200", children: errorMessage })), isLoading ? (_jsx(Div, { className: "space-y-2", children: Array.from({ length: 5 }).map((_, i) => (_jsx(Div, { className: "h-14 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) })) : rows.length === 0 ? (_jsx(Div, { className: "py-16 text-center", children: _jsx(Text, { className: "text-zinc-400 dark:text-zinc-500", children: "No digital code products yet \u2014 sell Steam keys, gift cards, and more" }) })) : (_jsx(DataTable, { columns: COLUMNS, data: rows, keyExtractor: (r) => r.id, selectable: bulkActions.length > 0, selectedIds: selection.selectedIds, onSelectionChange: (ids) => selection.setSelectedIds(ids) }))] })] }));
115
+ }
@@ -0,0 +1,14 @@
1
+ interface GoogleConfigDraft {
2
+ placeId: string;
3
+ businessName: string;
4
+ isConnected: boolean;
5
+ averageRating?: number;
6
+ totalReviews?: number;
7
+ lastSyncedAt?: string;
8
+ }
9
+ export interface SellerGoogleReviewsViewProps {
10
+ onSave?: (data: Partial<GoogleConfigDraft>) => Promise<void>;
11
+ onSync?: () => Promise<void>;
12
+ }
13
+ export declare function SellerGoogleReviewsView({ onSave, onSync, }: SellerGoogleReviewsViewProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
@@ -0,0 +1,95 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { RefreshCw } from "lucide-react";
5
+ import { Alert, Button, Div, FormField, Heading, Section, Stack, Text, Toggle, } from "../../../ui";
6
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
7
+ const EMPTY_DRAFT = {
8
+ placeId: "",
9
+ businessName: "",
10
+ isConnected: false,
11
+ };
12
+ export function SellerGoogleReviewsView({ onSave, onSync, }) {
13
+ const [draft, setDraft] = useState(EMPTY_DRAFT);
14
+ const [loading, setLoading] = useState(true);
15
+ const [saving, setSaving] = useState(false);
16
+ const [syncing, setSyncing] = useState(false);
17
+ const [saveMessage, setSaveMessage] = useState(null);
18
+ useEffect(() => {
19
+ fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS, { credentials: "include" })
20
+ .then((r) => r.json())
21
+ .then((j) => {
22
+ const data = j?.data ?? j ?? {};
23
+ setDraft({
24
+ placeId: String(data.placeId ?? ""),
25
+ businessName: String(data.businessName ?? ""),
26
+ isConnected: Boolean(data.isConnected),
27
+ averageRating: typeof data.averageRating === "number" ? data.averageRating : undefined,
28
+ totalReviews: typeof data.totalReviews === "number" ? data.totalReviews : undefined,
29
+ lastSyncedAt: data.lastSyncedAt ? String(data.lastSyncedAt) : undefined,
30
+ });
31
+ })
32
+ .catch(() => { })
33
+ .finally(() => setLoading(false));
34
+ }, []);
35
+ const handleSave = useCallback(async () => {
36
+ setSaving(true);
37
+ setSaveMessage(null);
38
+ try {
39
+ if (onSave) {
40
+ await onSave(draft);
41
+ }
42
+ else {
43
+ const res = await fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS, {
44
+ method: "PUT",
45
+ headers: { "Content-Type": "application/json" },
46
+ credentials: "include",
47
+ body: JSON.stringify({
48
+ placeId: draft.placeId.trim() || undefined,
49
+ businessName: draft.businessName.trim() || undefined,
50
+ isConnected: draft.isConnected,
51
+ }),
52
+ });
53
+ setSaveMessage({ text: res.ok ? "Settings saved." : "Save failed.", ok: res.ok });
54
+ }
55
+ }
56
+ catch {
57
+ setSaveMessage({ text: "Save failed. Please try again.", ok: false });
58
+ }
59
+ finally {
60
+ setSaving(false);
61
+ }
62
+ }, [draft, onSave]);
63
+ const handleSync = useCallback(async () => {
64
+ setSyncing(true);
65
+ setSaveMessage(null);
66
+ try {
67
+ if (onSync) {
68
+ await onSync();
69
+ }
70
+ else {
71
+ const res = await fetch(SELLER_ENDPOINTS.GOOGLE_REVIEWS_SYNC, {
72
+ method: "POST",
73
+ credentials: "include",
74
+ });
75
+ setSaveMessage({ text: res.ok ? "Sync queued." : "Sync failed.", ok: res.ok });
76
+ }
77
+ }
78
+ catch {
79
+ setSaveMessage({ text: "Sync failed. Please try again.", ok: false });
80
+ }
81
+ finally {
82
+ setSyncing(false);
83
+ }
84
+ }, [onSync]);
85
+ if (loading) {
86
+ return (_jsx("div", { className: "space-y-4 py-6 px-4", children: Array.from({ length: 3 }).map((_, i) => (_jsx("div", { className: "h-12 animate-pulse rounded-xl border border-zinc-100 dark:border-slate-700 bg-zinc-50 dark:bg-slate-800" }, i))) }));
87
+ }
88
+ const formattedLastSync = draft.lastSyncedAt
89
+ ? new Date(draft.lastSyncedAt).toLocaleString("en-IN", {
90
+ dateStyle: "medium",
91
+ timeStyle: "short",
92
+ })
93
+ : "Never";
94
+ return (_jsx("div", { className: "py-4 px-3 sm:px-4 max-w-2xl", children: _jsxs(Stack, { gap: "lg", children: [_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-1", children: "Google Business Settings" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mb-4", children: "Connect your Google Business profile to sync reviews to your storefront." }), _jsxs(Stack, { gap: "md", children: [_jsx(Toggle, { checked: draft.isConnected, onChange: (v) => setDraft((d) => ({ ...d, isConnected: v })), label: "Show Google reviews on my store page" }), _jsx(FormField, { name: "placeId", label: "Google Place ID", type: "text", value: draft.placeId, onChange: (v) => setDraft((d) => ({ ...d, placeId: v })), placeholder: "ChIJ\u2026", helpText: "Find your Place ID at developers.google.com/maps/documentation/places/web-service/place-id" }), _jsx(FormField, { name: "businessName", label: "Business name", type: "text", value: draft.businessName, onChange: (v) => setDraft((d) => ({ ...d, businessName: v })), placeholder: "Pok\u00E9mon Palace" })] })] }), (draft.averageRating !== undefined || draft.totalReviews !== undefined) && (_jsxs(Section, { children: [_jsx(Heading, { level: 3, className: "mb-3", children: "Review Stats" }), _jsxs("div", { className: "flex flex-wrap gap-6", children: [draft.averageRating !== undefined && (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Avg rating" }), _jsx(Text, { className: "text-2xl font-bold tabular-nums", children: draft.averageRating.toFixed(1) })] })), draft.totalReviews !== undefined && (_jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Total reviews" }), _jsx(Text, { className: "text-2xl font-bold tabular-nums", children: draft.totalReviews })] })), _jsxs(Div, { children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] uppercase tracking-wide", children: "Last synced" }), _jsx(Text, { className: "text-sm", children: formattedLastSync })] })] })] })), saveMessage && (_jsx(Alert, { variant: saveMessage.ok ? "success" : "error", children: saveMessage.text })), _jsxs("div", { className: "flex items-center justify-between gap-3 border-t border-[var(--appkit-color-border)] pt-4", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: handleSync, isLoading: syncing, disabled: saving || !draft.placeId.trim(), className: "flex items-center gap-1.5", children: [_jsx(RefreshCw, { className: "h-3.5 w-3.5" }), "Sync now"] }), _jsx(Button, { variant: "primary", size: "sm", onClick: handleSave, isLoading: saving, disabled: syncing, children: "Save Settings" })] })] }) }));
95
+ }
@@ -0,0 +1,6 @@
1
+ export interface SellerGroupedListingsViewProps {
2
+ onCreateClick?: () => void;
3
+ onEditClick?: (id: string) => void;
4
+ onDeleteClick?: (id: string) => void;
5
+ }
6
+ export declare function SellerGroupedListingsView({ onCreateClick, onEditClick, onDeleteClick, }: SellerGroupedListingsViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from "react";
4
+ import { useUrlTable } from "../../../react/hooks/useUrlTable";
5
+ import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
6
+ import { Badge, BulkActionBar, Button, ListingToolbar, Pagination } from "../../../ui";
7
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
8
+ import { toRecordArray, toRelativeDate, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
9
+ import { DataTable } from "../../admin/components/DataTable";
10
+ // ---------------------------------------------------------------------------
11
+ // Config
12
+ // ---------------------------------------------------------------------------
13
+ const PAGE_SIZE = 25;
14
+ const DEFAULT_SORT = "-createdAt";
15
+ const SORT_OPTIONS = [
16
+ { value: "-createdAt", label: "Newest" },
17
+ { value: "createdAt", label: "Oldest" },
18
+ { value: "title", label: "Title A–Z" },
19
+ { value: "-title", label: "Title Z–A" },
20
+ ];
21
+ const THEME_LABELS = {
22
+ related: "Related",
23
+ character: "Character",
24
+ lineage: "Lineage",
25
+ set: "Set",
26
+ generic: "Generic",
27
+ };
28
+ const VISIBILITY_BADGE = {
29
+ visible: "success",
30
+ hidden: "warning",
31
+ };
32
+ // ---------------------------------------------------------------------------
33
+ // Columns
34
+ // ---------------------------------------------------------------------------
35
+ const COLUMNS = [
36
+ {
37
+ key: "title",
38
+ header: "Title",
39
+ render: (r) => _jsx("span", { className: "font-medium text-zinc-900 dark:text-zinc-100", children: r.title }),
40
+ },
41
+ {
42
+ key: "groupTheme",
43
+ header: "Theme",
44
+ render: (r) => (_jsx(Badge, { variant: "default", children: THEME_LABELS[r.groupTheme] ?? r.groupTheme })),
45
+ },
46
+ {
47
+ key: "productCount",
48
+ header: "Products",
49
+ render: (r) => _jsx("span", { className: "text-sm text-zinc-600 dark:text-zinc-400", children: r.productCount }),
50
+ },
51
+ {
52
+ key: "visibilityStatus",
53
+ header: "Visibility",
54
+ render: (r) => (_jsx(Badge, { variant: VISIBILITY_BADGE[r.visibilityStatus] ?? "default", children: r.visibilityStatus === "visible" ? "Visible" : "Hidden" })),
55
+ },
56
+ {
57
+ key: "isActive",
58
+ header: "Status",
59
+ render: (r) => (_jsx(Badge, { variant: r.isActive ? "success" : "default", children: r.isActive ? "Active" : "Inactive" })),
60
+ },
61
+ {
62
+ key: "createdAt",
63
+ header: "Created",
64
+ render: (r) => _jsx("span", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: r.createdAt }),
65
+ },
66
+ ];
67
+ export function SellerGroupedListingsView({ onCreateClick, onEditClick, onDeleteClick, }) {
68
+ const table = useUrlTable({ defaults: { pageSize: String(PAGE_SIZE), sort: DEFAULT_SORT } });
69
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
70
+ const resetAll = useCallback(() => {
71
+ table.setMany({ q: "", sort: "" });
72
+ setSearchInput("");
73
+ }, [table]);
74
+ const commitSearch = useCallback(() => {
75
+ table.set("q", searchInput.trim());
76
+ }, [searchInput, table]);
77
+ const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT;
78
+ const { rows, total, isLoading, errorMessage } = useSellerListingData({
79
+ queryKey: ["seller", "grouped-listings"],
80
+ endpoint: SELLER_ENDPOINTS.GROUPED_LISTINGS,
81
+ page: table.getNumber("page", 1),
82
+ pageSize: PAGE_SIZE,
83
+ sorts: table.get("sort") || DEFAULT_SORT,
84
+ q: table.get("q") || undefined,
85
+ mapRows: (response) => toRecordArray(response.items).map((item, index) => ({
86
+ id: toStringValue(item.id, `group-${index}`),
87
+ title: toStringValue(item.title, "Untitled group"),
88
+ groupTheme: toStringValue(item.groupTheme, "generic"),
89
+ productCount: Array.isArray(item.productIds) ? item.productIds.length : 0,
90
+ isActive: item.isActive === true,
91
+ visibilityStatus: toStringValue(item.visibilityStatus, "hidden"),
92
+ createdAt: toRelativeDate(item.createdAt),
93
+ })),
94
+ getTotal: (response, mappedRows) => typeof response.meta?.total === "number" ? response.meta.total : mappedRows.length,
95
+ });
96
+ const currentPage = table.getNumber("page", 1);
97
+ const totalPages = Math.ceil(total / PAGE_SIZE);
98
+ const selection = useBulkSelection({ items: rows, keyExtractor: (r) => r.id });
99
+ const bulkActions = [
100
+ {
101
+ id: "delete",
102
+ label: "Delete selected",
103
+ variant: "danger",
104
+ onClick: () => {
105
+ for (const id of selection.selectedIds)
106
+ onDeleteClick?.(id);
107
+ selection.clearSelection();
108
+ },
109
+ },
110
+ ];
111
+ return (_jsxs("div", { className: "min-h-screen", children: [_jsx(ListingToolbar, { searchValue: searchInput, searchPlaceholder: "Search grouped listings", onSearchChange: setSearchInput, onSearchCommit: commitSearch, sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => { table.set("sort", v); }, onResetAll: resetAll, hasActiveState: hasActiveState, extra: onCreateClick ? (_jsx(Button, { size: "sm", onClick: onCreateClick, children: "+ New Group" })) : null }), selection.selectedCount > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), 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) }) })), _jsx(DataTable, { columns: COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: errorMessage ?? "No grouped listings yet", selectedIds: selection.selectedIdSet, onToggleSelect: (id, _selected) => selection.toggle(id), onToggleSelectAll: (_next) => selection.toggleAll(), renderRowActions: (row) => (_jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => onEditClick?.(row.id), children: "Edit" }), _jsx(Button, { size: "sm", variant: "ghost", onClick: () => onDeleteClick?.(row.id), children: "Delete" })] })) })] }));
112
+ }
@@ -0,0 +1,5 @@
1
+ export interface SellerLiveViewProps {
2
+ onCreateClick?: () => void;
3
+ onBulkDelete?: (ids: string[]) => Promise<void>;
4
+ }
5
+ export declare function SellerLiveView({ onCreateClick, onBulkDelete, }: SellerLiveViewProps): import("react/jsx-runtime").JSX.Element;