@mohasinac/appkit 2.7.49 → 2.7.50
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.
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.d.ts +19 -0
- package/dist/_internal/client/features/layout/SidebarCollapseToggle.js +15 -0
- package/dist/_internal/client/features/layout/index.d.ts +2 -0
- package/dist/_internal/client/features/layout/index.js +1 -0
- package/dist/_internal/server/features/checkout/actions.js +25 -2
- package/dist/_internal/server/features/promotions/actions.js +3 -1
- package/dist/_internal/shared/actions/action-registry.js +149 -0
- package/dist/client.d.ts +16 -0
- package/dist/client.js +11 -0
- package/dist/features/account/components/UserSidebar.js +2 -1
- package/dist/features/admin/components/AdminBlogEditorView.js +6 -2
- package/dist/features/admin/components/AdminSidebar.js +2 -1
- package/dist/features/auth/repository/session.repository.js +16 -1
- package/dist/features/cart/hooks/useAddToCart.d.ts +2 -0
- package/dist/features/cart/hooks/useAddToCart.js +2 -0
- package/dist/features/cart/hooks/useGuestCart.d.ts +2 -0
- package/dist/features/cart/utils/guest-cart.d.ts +7 -0
- package/dist/features/cart/utils/pending-ops.d.ts +6 -0
- package/dist/features/categories/components/CategoryBundlesListing.js +9 -1
- package/dist/features/categories/components/CategoryProductsListing.js +6 -10
- package/dist/features/categories/schemas/firestore.d.ts +9 -0
- package/dist/features/events/components/EventRaffleWinnerView.d.ts +9 -1
- package/dist/features/events/components/EventRaffleWinnerView.js +3 -3
- package/dist/features/events/components/SpinWheelView.js +3 -3
- package/dist/features/layout/BottomNavLayout.d.ts +5 -0
- package/dist/features/layout/BottomNavLayout.js +21 -0
- package/dist/features/layout/TitleBar.js +7 -1
- package/dist/features/layout/TitleBarLayout.d.ts +8 -2
- package/dist/features/layout/TitleBarLayout.js +11 -7
- package/dist/features/orders/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrdersIndexListing.js +9 -5
- package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
- package/dist/features/products/components/PrizeDrawEntryActions.d.ts +3 -1
- package/dist/features/products/components/PrizeDrawEntryActions.js +8 -12
- package/dist/features/products/components/ProductDetailActions.d.ts +5 -1
- package/dist/features/products/components/ProductDetailActions.js +3 -1
- package/dist/features/products/components/ProductDetailPageView.d.ts +2 -0
- package/dist/features/products/components/ProductDetailPageView.js +4 -0
- package/dist/features/products/components/ProductGrid.js +3 -3
- package/dist/features/products/components/ProductsIndexListing.js +15 -8
- package/dist/features/promotions/components/CouponCard.d.ts +11 -1
- package/dist/features/promotions/components/CouponCard.js +36 -2
- package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +27 -0
- package/dist/features/promotions/repository/claimed-coupons.repository.js +115 -0
- package/dist/features/promotions/schemas/firestore.d.ts +34 -0
- package/dist/features/promotions/schemas/firestore.js +13 -0
- package/dist/features/seller/components/SellerAddressesView.js +3 -2
- package/dist/features/seller/components/SellerBidsView.js +33 -3
- package/dist/features/seller/components/SellerOrdersView.js +35 -0
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +26 -4
- package/dist/features/seller/components/SellerProductsCards.d.ts +19 -0
- package/dist/features/seller/components/SellerProductsCards.js +19 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.d.ts +14 -0
- package/dist/features/seller/components/SellerProductsFilterDrawer.js +20 -0
- package/dist/features/seller/components/SellerProductsView.d.ts +3 -1
- package/dist/features/seller/components/SellerProductsView.js +68 -19
- package/dist/features/seller/components/SellerReviewsView.js +67 -4
- package/dist/features/seller/components/SellerSidebar.js +2 -1
- package/dist/features/seller/components/seller-products-styles.d.ts +7 -0
- package/dist/features/seller/components/seller-products-styles.js +14 -0
- package/dist/features/shell/FormShell.d.ts +7 -1
- package/dist/features/shell/FormShell.js +5 -2
- package/dist/features/store-extensions/index.d.ts +4 -0
- package/dist/features/store-extensions/index.js +4 -0
- package/dist/features/store-extensions/repository/rbac.repositories.d.ts +23 -0
- package/dist/features/store-extensions/repository/rbac.repositories.js +32 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +91 -0
- package/dist/features/store-extensions/repository/store-extensions.repositories.js +127 -0
- package/dist/features/store-extensions/schemas/firestore.d.ts +244 -0
- package/dist/features/store-extensions/schemas/firestore.js +158 -0
- package/dist/features/store-extensions/schemas/rbac.d.ts +65 -0
- package/dist/features/store-extensions/schemas/rbac.js +43 -0
- package/dist/features/stores/components/InteractiveStoreCard.js +7 -3
- package/dist/features/stores/components/StoreProductsListing.js +6 -10
- package/dist/features/stores/schemas/firestore.d.ts +2 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +8 -0
- package/dist/next/routing/route-map.d.ts +79 -0
- package/dist/next/routing/route-map.js +34 -0
- package/dist/react/hooks/useFormStatePreservation.d.ts +17 -0
- package/dist/react/hooks/useFormStatePreservation.js +62 -0
- package/dist/react/hooks/useInlineRowEdit.d.ts +24 -0
- package/dist/react/hooks/useInlineRowEdit.js +68 -0
- package/dist/react/index.d.ts +4 -0
- package/dist/react/index.js +4 -0
- package/dist/repositories/index.d.ts +4 -0
- package/dist/repositories/index.js +5 -0
- package/dist/seed/actions/demo-seed-actions.d.ts +1 -1
- package/dist/seed/claimed-coupons-seed-data.d.ts +13 -0
- package/dist/seed/claimed-coupons-seed-data.js +79 -0
- package/dist/seed/index.d.ts +2 -0
- package/dist/seed/index.js +3 -0
- package/dist/seed/manifest.js +17 -0
- package/dist/seed/store-extensions-seed-data.d.ts +19 -0
- package/dist/seed/store-extensions-seed-data.js +421 -0
- package/dist/tailwind-utilities.css +1 -1
- package/dist/ui/components/Button.style.css +20 -12
- package/dist/ui/components/ClaimCouponButton.d.ts +28 -0
- package/dist/ui/components/ClaimCouponButton.js +89 -0
- package/dist/ui/components/QuickCreateModal.d.ts +15 -0
- package/dist/ui/components/QuickCreateModal.js +48 -0
- package/dist/ui/components/QuickCreateModal.style.css +84 -0
- package/dist/ui/components/StickyBottomBar.d.ts +22 -0
- package/dist/ui/components/StickyBottomBar.js +20 -0
- package/dist/ui/components/VacationBanner.d.ts +11 -0
- package/dist/ui/components/VacationBanner.js +16 -0
- package/dist/ui/index.d.ts +8 -0
- package/dist/ui/index.js +4 -0
- package/package.json +1 -1
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useCallback } from "react";
|
|
4
4
|
import { useActionDispatch } from "../../../react/hooks/use-action-dispatch";
|
|
5
|
-
import {
|
|
5
|
+
import { Pencil, Trash2, Printer, MapPin } from "lucide-react";
|
|
6
6
|
import { PhysicalLocationModal } from "./PhysicalLocationModal";
|
|
7
7
|
import { useUrlTable } from "../../../react/hooks/useUrlTable";
|
|
8
8
|
import { useBulkSelection } from "../../../react/hooks/useBulkSelection";
|
|
9
|
-
import { Alert, Badge, BulkActionBar, Button, Div,
|
|
9
|
+
import { Alert, Badge, BulkActionBar, Button, Div, ListingToolbar, ListingViewShell, Pagination, Row, Span, Text } from "../../../ui";
|
|
10
10
|
import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
11
11
|
import { SELLER_PRODUCT_STATUS_TABS } from "../../admin/constants/filter-tabs";
|
|
12
12
|
import { ROUTES } from "../../../constants";
|
|
@@ -14,8 +14,20 @@ import { normalizeListingType } from "../../products/utils/listing-type";
|
|
|
14
14
|
import { toRecordArray, toRelativeDate, toStringValue, useSellerListingData, } from "../hooks/useSellerListingData";
|
|
15
15
|
import { DataTable } from "../../admin/components/DataTable";
|
|
16
16
|
import { ACTIONS } from "../../../_internal/shared/actions/action-registry";
|
|
17
|
+
import { SellerProductsCards } from "./SellerProductsCards";
|
|
18
|
+
import { SellerProductsFilterDrawer } from "./SellerProductsFilterDrawer";
|
|
19
|
+
import { KIND_BADGE_VARIANT, } from "./seller-products-styles";
|
|
17
20
|
const PAGE_SIZE = 25;
|
|
18
|
-
const FILTER_KEYS = [
|
|
21
|
+
const FILTER_KEYS = [
|
|
22
|
+
"status",
|
|
23
|
+
"category",
|
|
24
|
+
"brand",
|
|
25
|
+
"condition",
|
|
26
|
+
"minPrice",
|
|
27
|
+
"maxPrice",
|
|
28
|
+
"tags",
|
|
29
|
+
"badges",
|
|
30
|
+
];
|
|
19
31
|
const DEFAULT_SORT = "-createdAt";
|
|
20
32
|
const SORT_OPTIONS = [
|
|
21
33
|
{ value: "-createdAt", label: "Newest" },
|
|
@@ -26,6 +38,23 @@ const SORT_OPTIONS = [
|
|
|
26
38
|
{ value: "price", label: "Price Low" },
|
|
27
39
|
];
|
|
28
40
|
const STATUS_OPTIONS = SELLER_PRODUCT_STATUS_TABS;
|
|
41
|
+
// S-STORE-2-A — `<TypeDropdown>` replaces the legacy chip strip. Same width on
|
|
42
|
+
// mobile, narrower on desktop. Drives the `?listingType=` query param.
|
|
43
|
+
function TypeDropdown({ active, onChange, }) {
|
|
44
|
+
const options = [
|
|
45
|
+
{ value: "all", label: "All listings" },
|
|
46
|
+
{ value: "standard", label: "Standard" },
|
|
47
|
+
{ value: "auction", label: "Auction" },
|
|
48
|
+
{ value: "pre-order", label: "Pre-order" },
|
|
49
|
+
{ value: "prize-draw", label: "Prize Draw" },
|
|
50
|
+
{ value: "bundle", label: "Bundle" },
|
|
51
|
+
{ value: "classified", label: "Classified" },
|
|
52
|
+
{ value: "digital-code", label: "Digital Code" },
|
|
53
|
+
{ value: "live", label: "Live" },
|
|
54
|
+
];
|
|
55
|
+
return (_jsxs(Row, { className: "gap-2 px-3 lg:px-4 py-2 items-center border-b border-[var(--appkit-color-border)]", children: [_jsx(Text, { className: "text-xs font-semibold uppercase tracking-wide text-[var(--appkit-color-text-muted)]", children: "Listing type" }), _jsx("select", { value: active, onChange: (e) => onChange(e.target.value), className: "rounded border border-[var(--appkit-color-border)] bg-transparent px-2 py-1 text-sm sm:max-w-xs", "aria-label": "Filter by listing type", children: options.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value))) })] }));
|
|
56
|
+
}
|
|
57
|
+
// Kept for backward-compat callers; thin wrapper around TypeDropdown.
|
|
29
58
|
function TypeChips({ active, onChange, }) {
|
|
30
59
|
const chips = [
|
|
31
60
|
{ kind: "all", label: "All" },
|
|
@@ -33,6 +62,10 @@ function TypeChips({ active, onChange, }) {
|
|
|
33
62
|
{ kind: "auction", label: "Auction" },
|
|
34
63
|
{ kind: "pre-order", label: "Pre-order" },
|
|
35
64
|
{ kind: "prize-draw", label: "Prize Draw" },
|
|
65
|
+
{ kind: "bundle", label: "Bundle" },
|
|
66
|
+
{ kind: "classified", label: "Classified" },
|
|
67
|
+
{ kind: "digital-code", label: "Digital Code" },
|
|
68
|
+
{ kind: "live", label: "Live" },
|
|
36
69
|
];
|
|
37
70
|
return (_jsx(Row, { className: "gap-2 px-3 lg:px-4 py-2 overflow-x-auto border-b border-[var(--appkit-color-border)]", children: chips.map(({ kind, label }) => (_jsx("button", { type: "button", onClick: () => onChange(kind), className: [
|
|
38
71
|
"flex-shrink-0 rounded-full px-3 py-1 text-xs font-medium border transition-colors",
|
|
@@ -41,12 +74,6 @@ function TypeChips({ active, onChange, }) {
|
|
|
41
74
|
: "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-[var(--appkit-color-primary)] hover:text-[var(--appkit-color-primary)]",
|
|
42
75
|
].join(" "), children: label }, kind))) }));
|
|
43
76
|
}
|
|
44
|
-
const KIND_BADGE_VARIANT = {
|
|
45
|
-
auction: "warning",
|
|
46
|
-
"pre-order": "secondary",
|
|
47
|
-
"prize-draw": "primary",
|
|
48
|
-
standard: "default",
|
|
49
|
-
};
|
|
50
77
|
const PRODUCT_COLUMNS = [
|
|
51
78
|
{
|
|
52
79
|
key: "thumbnail",
|
|
@@ -93,7 +120,7 @@ const PRODUCT_COLUMNS = [
|
|
|
93
120
|
render: (row) => row.physicalLocation ? (_jsxs(Span, { className: "text-xs font-mono text-[var(--appkit-color-text-muted)]", children: [row.physicalLocation.zone, "/", row.physicalLocation.shelf, "/", row.physicalLocation.bin] })) : (_jsx(Span, { className: "text-xs text-[var(--appkit-color-text-faint)]", children: "\u2014" })),
|
|
94
121
|
},
|
|
95
122
|
];
|
|
96
|
-
export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
123
|
+
export function SellerProductsView({ onDeleteProduct, onCreateClick, children, ...props }) {
|
|
97
124
|
const hasChildren = React.Children.count(children) > 0;
|
|
98
125
|
const [view, setView] = useState("table");
|
|
99
126
|
const dispatch = useActionDispatch();
|
|
@@ -204,6 +231,16 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
|
204
231
|
: String(ROUTES.STORE.PRODUCTS_EDIT(row.id));
|
|
205
232
|
void dispatch({ type: "NAVIGATE", href });
|
|
206
233
|
};
|
|
234
|
+
// S-STORE-2-D — row click navigates to public detail/preview, NOT edit.
|
|
235
|
+
// Edit is only available via the per-row "..." action menu.
|
|
236
|
+
const handleRowClick = (row) => {
|
|
237
|
+
const href = row.listingKind === "auction"
|
|
238
|
+
? `/auctions/${row.id}`
|
|
239
|
+
: row.listingKind === "pre-order"
|
|
240
|
+
? `/pre-orders/${row.id}`
|
|
241
|
+
: `/products/${row.id}`;
|
|
242
|
+
void dispatch({ type: "NAVIGATE", href });
|
|
243
|
+
};
|
|
207
244
|
const handleDelete = async (row) => {
|
|
208
245
|
if (!onDeleteProduct)
|
|
209
246
|
return;
|
|
@@ -215,6 +252,18 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
|
215
252
|
setDeletingId(null);
|
|
216
253
|
}
|
|
217
254
|
};
|
|
255
|
+
// S-STORE-2-C — Duplicate verb. Server-side endpoint is /api/store/products/[id]/duplicate.
|
|
256
|
+
const handleDuplicate = async (row) => {
|
|
257
|
+
const res = await fetch(`/api/store/products/${row.id}/duplicate`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
}).catch(() => null);
|
|
260
|
+
if (res && res.ok) {
|
|
261
|
+
const json = await res.json().catch(() => null);
|
|
262
|
+
const newId = json?.data?.id;
|
|
263
|
+
if (newId)
|
|
264
|
+
handleEdit({ ...row, id: newId });
|
|
265
|
+
}
|
|
266
|
+
};
|
|
218
267
|
const handleBulkPrintLabels = useCallback(() => {
|
|
219
268
|
const ids = selection.selectedIds.join(",");
|
|
220
269
|
void dispatch({
|
|
@@ -244,15 +293,15 @@ export function SellerProductsView({ onDeleteProduct, children, ...props }) {
|
|
|
244
293
|
onClick: () => setSetLocationOpen(true),
|
|
245
294
|
},
|
|
246
295
|
];
|
|
247
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", 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 }), _jsx(
|
|
296
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search products by name\u2026", 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: onCreateClick ? (_jsx(Button, { variant: "primary", size: "sm", onClick: onCreateClick, children: "+ New Listing" })) : null }), _jsx(TypeDropdown, { active: listingKind, onChange: handleKindChange }), totalPages > 1 && (_jsx(Div, { className: "sticky z-10 flex justify-center bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)] px-3 py-1.5", style: { top: "calc(var(--header-height, 0px) + 44px)" }, children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), selection.selectedIds.length > 0 && (_jsx(Div, { className: "sticky z-20 px-3 lg:px-4 py-2 bg-[var(--appkit-color-surface)]/95 backdrop-blur-sm border-b border-[var(--appkit-color-border)]", style: { top: "calc(var(--header-height, 0px) + 88px)" }, children: _jsx(BulkActionBar, { selectedCount: selection.selectedIds.length, onClearSelection: selection.clearSelection, actions: bulkActions }) })), _jsxs(Div, { className: "py-4 px-3 lg:px-4", children: [errorMessage && (_jsx(Alert, { variant: "error", className: "mb-4", children: errorMessage })), view !== "table" && (_jsx(SellerProductsCards, { view: view, rows: rows, isLoading: isLoading, listingKind: listingKind, selectedIds: selection.selectedIdSet, toggle: selection.toggle, onEdit: handleEdit, onDuplicate: (row) => void handleDuplicate(row), onDelete: onDeleteProduct ? (row) => void handleDelete(row) : undefined })), view === "table" && (_jsx(DataTable, { columns: PRODUCT_COLUMNS, rows: rows, isLoading: isLoading, emptyLabel: listingKind !== "all"
|
|
248
297
|
? `No ${listingKind} listings found`
|
|
249
|
-
: "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => selection.toggleAll(), getRowHref: (row) =>
|
|
250
|
-
|
|
298
|
+
: "No products listed yet", selectedIds: selection.selectedIdSet, onToggleSelect: selection.toggle, onToggleSelectAll: (next) => selection.toggleAll(), getRowHref: (row) =>
|
|
299
|
+
// S-STORE-2-D — row click → public detail/preview, not edit.
|
|
300
|
+
row.listingKind === "auction"
|
|
301
|
+
? `/auctions/${row.id}`
|
|
251
302
|
: row.listingKind === "pre-order"
|
|
252
|
-
?
|
|
253
|
-
: row.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
? (row) => (_jsxs(Row, { className: "gap-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); handleEdit(row); }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); void handleDelete(row); }, disabled: deletingId === row.id, className: "text-[var(--appkit-color-error)] hover:bg-[var(--appkit-color-border-subtle)]", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }))
|
|
257
|
-
: undefined })] }), filterOpen && (_jsxs(_Fragment, { children: [_jsx(Div, { role: "presentation", className: "fixed inset-0 z-40 bg-black/40", onClick: () => setFilterOpen(false) }), _jsxs(Div, { className: "fixed inset-y-0 left-0 z-50 flex w-80 flex-col bg-[var(--appkit-color-surface)] shadow-2xl", children: [_jsxs(Row, { justify: "between", className: "border-b border-[var(--appkit-color-border)] px-4 py-3.5", children: [_jsx(Text, { className: "text-base font-semibold text-[var(--appkit-color-text)]", children: "Filters" }), _jsxs(Row, { className: "gap-2", children: [activeFilterCount > 0 && (_jsx(Button, { variant: "ghost", size: "sm", onClick: clearFilters, className: "text-xs text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-error)]", children: "Clear all" })), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFilterOpen(false), "aria-label": "Close filters", children: _jsx(X, { className: "h-5 w-5" }) })] })] }), _jsx(Div, { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5", children: _jsx(FilterChipGroup, { label: "Status", tabs: STATUS_OPTIONS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) }) }), _jsx(Div, { className: "border-t border-[var(--appkit-color-border)] px-4 py-3.5", children: _jsxs(Button, { variant: "primary", onClick: applyFilters, className: "w-full rounded-lg py-2.5 active:scale-[0.98]", children: ["Apply Filters", activeFilterCount > 0 ? ` (${activeFilterCount})` : ""] }) })] })] }))] }), setLocationOpen && (_jsx(PhysicalLocationModal, { count: selection.selectedIds.length, onSave: handleSetLocation, onClose: () => setSetLocationOpen(false) }))] }));
|
|
303
|
+
? `/pre-orders/${row.id}`
|
|
304
|
+
: `/products/${row.id}`, renderRowActions: onDeleteProduct
|
|
305
|
+
? (row) => (_jsxs(Row, { className: "gap-1", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); handleEdit(row); }, "aria-label": ACTIONS.STORE["edit-listing"].ariaLabel, children: _jsx(Pencil, { className: "w-4 h-4" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: (e) => { e.stopPropagation(); void handleDuplicate(row); }, "aria-label": "Duplicate listing", title: "Duplicate", children: "\u29C9" }), _jsx(Button, { variant: "ghost", size: "sm", action: ACTIONS.STORE["delete-listing"], onClick: (e) => { e.stopPropagation(); void handleDelete(row); }, disabled: deletingId === row.id, className: "text-[var(--appkit-color-error)] hover:bg-[var(--appkit-color-border-subtle)]", children: _jsx(Trash2, { className: "w-4 h-4" }) })] }))
|
|
306
|
+
: undefined }))] }), _jsx(SellerProductsFilterDrawer, { isOpen: filterOpen, pendingFilters: pendingFilters, statusOptions: STATUS_OPTIONS, activeFilterCount: activeFilterCount, onChange: setPendingFilters, onClear: clearFilters, onApply: applyFilters, onClose: () => setFilterOpen(false) })] }), setLocationOpen && (_jsx(PhysicalLocationModal, { count: selection.selectedIds.length, onSave: handleSetLocation, onClose: () => setSetLocationOpen(false) }))] }));
|
|
258
307
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
import { Alert, Badge, Button, Div, Select, SideDrawer, Stack, Text, } from "../../../ui";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
4
|
+
import { Alert, Badge, BulkActionBar, Button, Checkbox, Div, Modal, Row, Select, SideDrawer, Stack, Text, Textarea, } from "../../../ui";
|
|
5
5
|
import { StackedViewShell } from "../../../ui";
|
|
6
6
|
const STAR = "★";
|
|
7
7
|
const EMPTY_STAR = "☆";
|
|
@@ -30,6 +30,62 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
|
|
|
30
30
|
const [replyText, setReplyText] = useState("");
|
|
31
31
|
const [replySaving, setReplySaving] = useState(false);
|
|
32
32
|
const [replyError, setReplyError] = useState(null);
|
|
33
|
+
// S-STORE-4-C — bulk reply + contest + feedback selection
|
|
34
|
+
const [selectedIds, setSelectedIds] = useState(new Set());
|
|
35
|
+
const [bulkReplyOpen, setBulkReplyOpen] = useState(false);
|
|
36
|
+
const [bulkReplyText, setBulkReplyText] = useState("");
|
|
37
|
+
const [bulkSaving, setBulkSaving] = useState(false);
|
|
38
|
+
const [contestTarget, setContestTarget] = useState(null);
|
|
39
|
+
const [contestReason, setContestReason] = useState("");
|
|
40
|
+
const [feedbackTarget, setFeedbackTarget] = useState(null);
|
|
41
|
+
const [feedbackText, setFeedbackText] = useState("");
|
|
42
|
+
const toggleSelected = useCallback((id) => {
|
|
43
|
+
setSelectedIds((prev) => {
|
|
44
|
+
const next = new Set(prev);
|
|
45
|
+
next.has(id) ? next.delete(id) : next.add(id);
|
|
46
|
+
return next;
|
|
47
|
+
});
|
|
48
|
+
}, []);
|
|
49
|
+
const clearSelection = useCallback(() => setSelectedIds(new Set()), []);
|
|
50
|
+
const submitBulkReply = useCallback(async () => {
|
|
51
|
+
if (!bulkReplyText.trim() || selectedIds.size === 0)
|
|
52
|
+
return;
|
|
53
|
+
setBulkSaving(true);
|
|
54
|
+
await Promise.all(Array.from(selectedIds).map((id) => fetch(`${replyApiBase}/${id}/reply`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({ reply: bulkReplyText }),
|
|
58
|
+
}).catch(() => null)));
|
|
59
|
+
setBulkSaving(false);
|
|
60
|
+
setBulkReplyOpen(false);
|
|
61
|
+
setBulkReplyText("");
|
|
62
|
+
clearSelection();
|
|
63
|
+
fetchReviewsRef.current?.();
|
|
64
|
+
}, [bulkReplyText, selectedIds, replyApiBase, clearSelection]);
|
|
65
|
+
const submitContest = useCallback(async () => {
|
|
66
|
+
if (!contestTarget)
|
|
67
|
+
return;
|
|
68
|
+
await fetch(`/api/store/reviews/${contestTarget.id}/contest`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers: { "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify({ reason: contestReason }),
|
|
72
|
+
}).catch(() => null);
|
|
73
|
+
setContestTarget(null);
|
|
74
|
+
setContestReason("");
|
|
75
|
+
fetchReviewsRef.current?.();
|
|
76
|
+
}, [contestTarget, contestReason]);
|
|
77
|
+
const submitFeedback = useCallback(async () => {
|
|
78
|
+
if (!feedbackTarget)
|
|
79
|
+
return;
|
|
80
|
+
await fetch(`/api/store/reviews/${feedbackTarget.id}/feedback`, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({ feedback: feedbackText }),
|
|
84
|
+
}).catch(() => null);
|
|
85
|
+
setFeedbackTarget(null);
|
|
86
|
+
setFeedbackText("");
|
|
87
|
+
}, [feedbackTarget, feedbackText]);
|
|
88
|
+
const fetchReviewsRef = useMemo(() => ({ current: null }), []);
|
|
33
89
|
const fetchReviews = useCallback(async () => {
|
|
34
90
|
setLoading(true);
|
|
35
91
|
setError(null);
|
|
@@ -54,6 +110,7 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
|
|
|
54
110
|
}
|
|
55
111
|
}, [reviewsApiBase, rating, replied, page]);
|
|
56
112
|
useEffect(() => { fetchReviews(); }, [fetchReviews]);
|
|
113
|
+
useEffect(() => { fetchReviewsRef.current = fetchReviews; }, [fetchReviews, fetchReviewsRef]);
|
|
57
114
|
const openReply = (review) => {
|
|
58
115
|
setReplyTarget(review);
|
|
59
116
|
setReplyText(review.sellerReply ?? "");
|
|
@@ -95,6 +152,12 @@ export function SellerReviewsView({ reviewsApiBase = "/api/store/reviews", reply
|
|
|
95
152
|
{ value: "", label: "All reply statuses" },
|
|
96
153
|
{ value: "true", label: "Store replied" },
|
|
97
154
|
{ value: "false", label: "Awaiting reply" },
|
|
98
|
-
] }), meta && (_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] ml-auto", children: [meta.total, " review", meta.total !== 1 ? "s" : ""] }))] }), error && _jsx(Alert, { variant: "error", children: error }),
|
|
99
|
-
|
|
155
|
+
] }), meta && (_jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] ml-auto", children: [meta.total, " review", meta.total !== 1 ? "s" : ""] }))] }), error && _jsx(Alert, { variant: "error", children: error }), selectedIds.size > 0 && (_jsx(BulkActionBar, { selectedCount: selectedIds.size, onClearSelection: clearSelection, actions: [
|
|
156
|
+
{
|
|
157
|
+
id: "bulk-reply",
|
|
158
|
+
label: "Reply to selected",
|
|
159
|
+
onClick: () => setBulkReplyOpen(true),
|
|
160
|
+
},
|
|
161
|
+
] })), loading ? (_jsx(Div, { className: "py-8 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Loading reviews\u2026" }) })) : reviews.length === 0 ? (_jsx(Div, { className: "py-12 text-center", children: _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "No reviews found." }) })) : (_jsx(Stack, { gap: "md", children: reviews.map((review) => (_jsx(Div, { className: "p-4 rounded-lg border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", children: _jsxs(Div, { className: "flex items-start justify-between gap-3 flex-wrap", children: [_jsxs(Row, { className: "gap-3 items-start flex-1 min-w-0", children: [_jsx(Checkbox, { checked: selectedIds.has(review.id), onChange: () => toggleSelected(review.id), "aria-label": "Select review" }), _jsxs(Div, { className: "flex-1 min-w-0", children: [_jsx(Text, { className: "font-medium truncate", children: review.productTitle }), _jsxs(Div, { className: "flex items-center gap-2 mt-1 flex-wrap", children: [_jsx(Stars, { rating: review.rating }), _jsxs(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: ["by ", review.userName] }), review.verified && _jsx(Badge, { variant: "success", children: "Verified" }), statusBadge(review.status), _jsx(Badge, { variant: review.sellerReply ? "success" : "warning", children: review.sellerReply ? "Store replied" : "Awaiting store reply" })] }), review.title && _jsx(Text, { className: "mt-2 font-medium", children: review.title }), _jsx(Text, { className: "mt-1 text-sm text-[var(--appkit-color-text-secondary)] line-clamp-3", children: review.comment }), review.sellerReply && (_jsxs(Div, { className: "mt-2 pl-3 border-l-2 border-[var(--appkit-color-primary)]", children: [_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)]", children: "Store reply:" }), _jsx(Text, { className: "text-sm", children: review.sellerReply })] }))] })] }), _jsxs(Row, { className: "gap-2 flex-shrink-0", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => openReply(review), children: review.sellerReply ? "Edit Reply" : "Reply" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setContestTarget(review), children: "Contest" }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setFeedbackTarget(review), children: "Feedback" })] })] }) }, review.id))) })), meta && meta.totalPages > 1 && (_jsxs(Div, { className: "flex items-center justify-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => Math.max(1, p - 1)), disabled: page <= 1 || loading, children: "Previous" }), _jsxs(Text, { className: "text-sm", children: ["Page ", meta.page, " of ", meta.totalPages] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setPage((p) => p + 1), disabled: !meta.hasMore || loading, children: "Next" })] }))] }, "reviews"),
|
|
162
|
+
] }), _jsx(SideDrawer, { isOpen: !!replyTarget, onClose: () => setReplyTarget(null), title: replyTarget?.sellerReply ? "Edit Reply" : "Reply to Review", mode: "create", children: _jsxs(Stack, { gap: "md", className: "p-4", children: [replyTarget && (_jsxs(Div, { className: "p-3 rounded bg-[var(--appkit-color-surface-muted)]", children: [_jsx(Stars, { rating: replyTarget.rating }), _jsx(Text, { className: "text-sm mt-1", children: replyTarget.comment })] })), replyError && _jsx(Alert, { variant: "error", children: replyError }), _jsxs(Div, { children: [_jsx(Text, { className: "text-sm font-medium mb-1.5", children: "Store reply" }), _jsx("textarea", { className: "w-full rounded-md border border-[var(--appkit-color-border)] bg-[var(--appkit-color-input-bg,var(--appkit-color-surface))] p-2.5 text-sm resize-none focus:outline-none focus:ring-2 focus:ring-[var(--appkit-color-primary)] dark:bg-[var(--appkit-color-surface-dark,#1e2330)] dark:border-[var(--appkit-color-border-dark)]", rows: 5, value: replyText, onChange: (e) => setReplyText(e.target.value), maxLength: 1000, placeholder: "Write your response to this review\u2026" }), _jsxs(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] text-right mt-1", children: [replyText.length, "/1000"] })] }), _jsxs(Div, { className: "flex gap-2 justify-end", children: [_jsx(Button, { variant: "outline", onClick: () => setReplyTarget(null), disabled: replySaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: handleReplySave, disabled: replySaving || !replyText.trim(), isLoading: replySaving, children: replyTarget?.sellerReply ? "Update Reply" : "Post Reply" })] })] }) }), _jsx(Modal, { isOpen: bulkReplyOpen, onClose: () => setBulkReplyOpen(false), title: `Bulk reply to ${selectedIds.size} review${selectedIds.size === 1 ? "" : "s"}`, actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setBulkReplyOpen(false), disabled: bulkSaving, children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitBulkReply(), disabled: !bulkReplyText.trim() || bulkSaving, isLoading: bulkSaving, children: "Send reply" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "The same reply will be posted on all selected reviews." }), _jsx(Textarea, { value: bulkReplyText, onChange: (e) => setBulkReplyText(e.target.value), rows: 5, placeholder: "Thanks for your review\u2026", label: "Reply" })] }) }), _jsx(Modal, { isOpen: !!contestTarget, onClose: () => setContestTarget(null), title: "Contest this review", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setContestTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitContest(), disabled: !contestReason.trim(), children: "Submit" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Flag this review for admin investigation. Provide a clear reason \u2014 fake, abusive, off-topic, etc." }), _jsx(Textarea, { value: contestReason, onChange: (e) => setContestReason(e.target.value), rows: 4, label: "Reason" })] }) }), _jsx(Modal, { isOpen: !!feedbackTarget, onClose: () => setFeedbackTarget(null), title: "Send feedback to buyer", actions: _jsxs(Row, { className: "gap-2", children: [_jsx(Button, { variant: "ghost", onClick: () => setFeedbackTarget(null), children: "Cancel" }), _jsx(Button, { variant: "primary", onClick: () => void submitFeedback(), disabled: !feedbackText.trim(), children: "Send" })] }), children: _jsxs(Stack, { gap: "md", className: "p-1", children: [_jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)]", children: "Private message sent to the buyer's notification inbox. Does not appear on the public review." }), _jsx(Textarea, { value: feedbackText, onChange: (e) => setFeedbackText(e.target.value), rows: 4, label: "Feedback" })] }) })] }));
|
|
100
163
|
}
|
|
@@ -5,6 +5,7 @@ import { createPortal } from "react-dom";
|
|
|
5
5
|
import Link from "next/link";
|
|
6
6
|
import { Div, Li, Nav, Row, Span, Text, Ul } from "../../../ui";
|
|
7
7
|
import { BottomSheet } from "../../layout/BottomSheet";
|
|
8
|
+
import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
|
|
8
9
|
const CLS_STORE_AVATAR = "h-8 w-8 rounded-md bg-cover bg-center flex-shrink-0";
|
|
9
10
|
const CLS_STORE_FALLBACK = "flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-md bg-primary/10 text-sm font-bold text-primary";
|
|
10
11
|
const CLS_STORE_NAME = "text-sm font-semibold text-zinc-900 dark:text-zinc-100 truncate";
|
|
@@ -56,7 +57,7 @@ export function StoreSidebar({ items, groups, activeHref, storeName, storeLogoUR
|
|
|
56
57
|
height: "calc(100vh - var(--header-height, 3.5rem))",
|
|
57
58
|
width: "18rem",
|
|
58
59
|
transform: desktopOpen ? "translateX(0)" : "translateX(calc(-100% + 1.25rem))",
|
|
59
|
-
}, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-700 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": storeName, className: CLS_STORE_AVATAR, style: { backgroundImage: `url(${storeLogoURL})` } })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName?.[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName || panelTitle })] }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent })] }), _jsx(
|
|
60
|
+
}, children: [_jsxs("div", { className: "flex-1 bg-white dark:bg-slate-950 border-r border-zinc-200 dark:border-slate-700 flex flex-col overflow-hidden shadow-xl", children: [_jsx("div", { className: "px-4 py-3.5 border-b border-zinc-100 dark:border-slate-800 shrink-0", children: _jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [storeLogoURL ? (_jsx(Div, { role: "img", "aria-label": storeName, className: CLS_STORE_AVATAR, style: { backgroundImage: `url(${storeLogoURL})` } })) : (_jsx(Div, { className: CLS_STORE_FALLBACK, children: storeName?.[0]?.toUpperCase() })), _jsx(Text, { className: CLS_STORE_NAME, children: storeName || panelTitle })] }) }), _jsx("div", { className: "flex-1 overflow-y-auto", children: navContent })] }), _jsx(SidebarCollapseToggle, { expanded: desktopOpen, onToggle: handleToggle })] }), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
|
|
60
61
|
}
|
|
61
62
|
return (_jsxs(_Fragment, { children: [mounted && mobileOpen &&
|
|
62
63
|
createPortal(_jsx(DrawerPanel, { title: panelTitle, onClose: close, children: navContent }), document.body), _jsx("div", { className: "md:hidden", children: _jsx(BottomSheet, { open: mobileOpen, onClose: close, title: panelTitle, children: groups ? (_jsx(GroupsContent, { groups: groups, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) : (_jsx(FlatContent, { items: items, activeHref: activeHref, storeName: storeName, storeLogoURL: storeLogoURL, onItemClick: close })) }) })] }));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const INPUT_CLS = "w-full rounded border border-[var(--appkit-color-border)] bg-transparent px-2 py-1.5 text-sm";
|
|
2
|
+
export declare const FILTER_LABEL_CLS = "text-xs font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-wide";
|
|
3
|
+
export declare const CARD_BORDER = "border-[var(--appkit-color-border)]";
|
|
4
|
+
export declare const CARD_BORDER_ACTIVE = "border-[var(--appkit-color-primary)]";
|
|
5
|
+
export declare const CARD_GRID_CLS = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3";
|
|
6
|
+
export declare const CARD_LIST_CLS = "flex flex-col gap-2";
|
|
7
|
+
export declare const KIND_BADGE_VARIANT: Record<string, "default" | "primary" | "secondary" | "success" | "warning" | "danger">;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// S-STORE — shared style constants for SellerProductsView and its sub-components.
|
|
2
|
+
// Token-based — no raw hex / breakpoint literals.
|
|
3
|
+
export const INPUT_CLS = "w-full rounded border border-[var(--appkit-color-border)] bg-transparent px-2 py-1.5 text-sm";
|
|
4
|
+
export const FILTER_LABEL_CLS = "text-xs font-semibold text-[var(--appkit-color-text-muted)] uppercase tracking-wide";
|
|
5
|
+
export const CARD_BORDER = "border-[var(--appkit-color-border)]";
|
|
6
|
+
export const CARD_BORDER_ACTIVE = "border-[var(--appkit-color-primary)]";
|
|
7
|
+
export const CARD_GRID_CLS = "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3";
|
|
8
|
+
export const CARD_LIST_CLS = "flex flex-col gap-2";
|
|
9
|
+
export const KIND_BADGE_VARIANT = {
|
|
10
|
+
auction: "warning",
|
|
11
|
+
"pre-order": "secondary",
|
|
12
|
+
"prize-draw": "primary",
|
|
13
|
+
standard: "default",
|
|
14
|
+
};
|
|
@@ -26,6 +26,12 @@ export interface FormShellProps {
|
|
|
26
26
|
* A draft banner and "← Back to Edit" button are injected automatically.
|
|
27
27
|
*/
|
|
28
28
|
previewSlot?: () => ReactNode;
|
|
29
|
+
/**
|
|
30
|
+
* S-STORE-3-D — when true AND previewSlot is provided, desktop renders a
|
|
31
|
+
* 60/40 split with form on the left and live preview on the right.
|
|
32
|
+
* On mobile the preview reverts to a toggleable modal-style view.
|
|
33
|
+
*/
|
|
34
|
+
splitPreview?: boolean;
|
|
29
35
|
children: ReactNode;
|
|
30
36
|
}
|
|
31
37
|
export interface UseFormShellResult {
|
|
@@ -34,4 +40,4 @@ export interface UseFormShellResult {
|
|
|
34
40
|
markClean: () => void;
|
|
35
41
|
}
|
|
36
42
|
export declare function useFormShell(): UseFormShellResult;
|
|
37
|
-
export declare function FormShell({ isOpen, onClose, title, breadcrumb, isDirty, isLoading, sections, onSaveDraft, onPublish, saveLabel, publishLabel, renderBottomBar, previewSlot, children, }: FormShellProps): import("react/jsx-runtime").JSX.Element | null;
|
|
43
|
+
export declare function FormShell({ isOpen, onClose, title, breadcrumb, isDirty, isLoading, sections, onSaveDraft, onPublish, saveLabel, publishLabel, renderBottomBar, previewSlot, splitPreview, children, }: FormShellProps): import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -15,7 +15,7 @@ export function useFormShell() {
|
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
17
|
const FOCUSABLE = 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
|
|
18
|
-
export function FormShell({ isOpen, onClose, title, breadcrumb, isDirty = false, isLoading = false, sections, onSaveDraft, onPublish, saveLabel = FORM_ACTION_META[FORM_ACTION_ID.SAVE_DRAFT].label, publishLabel = FORM_ACTION_META[FORM_ACTION_ID.PUBLISH].label, renderBottomBar, previewSlot, children, }) {
|
|
18
|
+
export function FormShell({ isOpen, onClose, title, breadcrumb, isDirty = false, isLoading = false, sections, onSaveDraft, onPublish, saveLabel = FORM_ACTION_META[FORM_ACTION_ID.SAVE_DRAFT].label, publishLabel = FORM_ACTION_META[FORM_ACTION_ID.PUBLISH].label, renderBottomBar, previewSlot, splitPreview = false, children, }) {
|
|
19
19
|
const panelRef = useRef(null);
|
|
20
20
|
const bodyRef = useRef(null);
|
|
21
21
|
const [showUnsaved, setShowUnsaved] = useState(false);
|
|
@@ -102,5 +102,8 @@ export function FormShell({ isOpen, onClose, title, breadcrumb, isDirty = false,
|
|
|
102
102
|
};
|
|
103
103
|
if (!isOpen)
|
|
104
104
|
return null;
|
|
105
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 bg-black/50 backdrop-blur-[2px]", style: { zIndex: "calc(var(--appkit-z-modal) - 1)" }, "aria-hidden": "true", onClick: attemptClose }), _jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-label": title, className: "fixed top-0 bottom-0 left-0 right-0 lg:left-[10%] lg:right-[10%] flex flex-col bg-[var(--appkit-color-surface)] shadow-2xl", style: { zIndex: "var(--appkit-z-modal)" }, children: [_jsxs("div", { className: "flex-shrink-0 sticky top-0 z-10 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-5 py-3 flex items-center gap-3", children: [previewMode ? (_jsxs("button", { type: "button", onClick: () => setPreviewMode(false), "aria-label": "Back to edit", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors flex-shrink-0 flex items-center gap-1.5 text-sm", children: [_jsx(ArrowLeft, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: "Back to Edit" })] })) : (_jsx("button", { type: "button", onClick: attemptClose, "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors flex-shrink-0", children: _jsx(X, { className: "w-5 h-5" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [breadcrumb && (_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate mb-0.5", children: breadcrumb })), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)] truncate", children: previewMode ? `Preview — ${title}` : title })] }), _jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: previewMode ? null : (_jsxs(_Fragment, { children: [previewSlot && (_jsxs("button", { type: "button", onClick: () => setPreviewMode(true), className: "flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors border border-[var(--appkit-color-border)]", children: [_jsx(Eye, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: "Preview" })] })), onSaveDraft && (_jsx(Button, { variant: "outline", size: "sm", onClick: handleSaveDraft, disabled: isLoading || savingDraft || publishing, isLoading: savingDraft, children: saveLabel })), onPublish && (_jsx(Button, { variant: "primary", size: "sm", onClick: handlePublish, disabled: isLoading || savingDraft || publishing, isLoading: publishing, children: publishLabel }))] })) })] }), _jsxs("div", { className: "flex flex-1 overflow-hidden", children: [sections && sections.length > 0 && !previewMode && (_jsx("nav", { "aria-label": "Form sections", className: "hidden lg:flex flex-col flex-shrink-0 w-48 border-r border-[var(--appkit-color-border)] py-4 px-3 gap-1 overflow-y-auto", children: sections.map((sec) => (_jsx("button", { type: "button", onClick: () => scrollToSection(sec.id), className: "text-left text-sm px-3 py-2 rounded-lg text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: sec.label }, sec.id))) })), sections && sections.length > 0 && !previewMode && (_jsx("div", { className: "lg:hidden fixed top-[var(--form-shell-topbar-h,57px)] left-0 right-0 z-10 flex overflow-x-auto gap-1 px-5 py-2 bg-[var(--appkit-color-surface)] border-b border-[var(--appkit-color-border)]", children: sections.map((sec) => (_jsx("button", { type: "button", onClick: () => scrollToSection(sec.id), className: "flex-shrink-0 text-xs px-3 py-1.5 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-[var(--appkit-color-primary)] hover:text-[var(--appkit-color-primary)] transition-colors whitespace-nowrap", children: sec.label }, sec.id))) })), _jsx("div", { ref: bodyRef, className: classNames("flex-1 overflow-y-auto", sections && sections.length > 0 && !previewMode ? "pt-0 lg:pt-0" : ""), children: previewMode && previewSlot ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sticky top-0 z-10 flex items-center gap-2 bg-[var(--appkit-color-warning-surface)] border-b border-[var(--appkit-color-warning)] px-4 py-2 text-sm text-[var(--appkit-color-warning-text,var(--appkit-color-warning))]", children: [_jsx(Eye, { className: "w-4 h-4 flex-shrink-0" }), _jsx("span", { children: "Preview \u2014 not visible to buyers until published" })] }), _jsx("div", { className: "py-4", children: previewSlot() })] })) :
|
|
105
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 bg-black/50 backdrop-blur-[2px]", style: { zIndex: "calc(var(--appkit-z-modal) - 1)" }, "aria-hidden": "true", onClick: attemptClose }), _jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "true", "aria-label": title, className: "fixed top-0 bottom-0 left-0 right-0 lg:left-[10%] lg:right-[10%] flex flex-col bg-[var(--appkit-color-surface)] shadow-2xl", style: { zIndex: "var(--appkit-z-modal)" }, children: [_jsxs("div", { className: "flex-shrink-0 sticky top-0 z-10 border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-5 py-3 flex items-center gap-3", children: [previewMode ? (_jsxs("button", { type: "button", onClick: () => setPreviewMode(false), "aria-label": "Back to edit", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors flex-shrink-0 flex items-center gap-1.5 text-sm", children: [_jsx(ArrowLeft, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: "Back to Edit" })] })) : (_jsx("button", { type: "button", onClick: attemptClose, "aria-label": "Close", className: "rounded-lg p-1.5 text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors flex-shrink-0", children: _jsx(X, { className: "w-5 h-5" }) })), _jsxs("div", { className: "flex-1 min-w-0", children: [breadcrumb && (_jsx(Text, { className: "text-xs text-[var(--appkit-color-text-muted)] truncate mb-0.5", children: breadcrumb })), _jsx(Text, { className: "text-sm font-semibold text-[var(--appkit-color-text)] truncate", children: previewMode ? `Preview — ${title}` : title })] }), _jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: previewMode ? null : (_jsxs(_Fragment, { children: [previewSlot && (_jsxs("button", { type: "button", onClick: () => setPreviewMode(true), className: "flex items-center gap-1.5 rounded-lg px-3 py-1.5 text-sm text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors border border-[var(--appkit-color-border)]", children: [_jsx(Eye, { className: "w-4 h-4" }), _jsx("span", { className: "hidden sm:inline", children: "Preview" })] })), onSaveDraft && (_jsx(Button, { variant: "outline", size: "sm", onClick: handleSaveDraft, disabled: isLoading || savingDraft || publishing, isLoading: savingDraft, children: saveLabel })), onPublish && (_jsx(Button, { variant: "primary", size: "sm", onClick: handlePublish, disabled: isLoading || savingDraft || publishing, isLoading: publishing, children: publishLabel }))] })) })] }), _jsxs("div", { className: "flex flex-1 overflow-hidden", children: [sections && sections.length > 0 && !previewMode && (_jsx("nav", { "aria-label": "Form sections", className: "hidden lg:flex flex-col flex-shrink-0 w-48 border-r border-[var(--appkit-color-border)] py-4 px-3 gap-1 overflow-y-auto", children: sections.map((sec) => (_jsx("button", { type: "button", onClick: () => scrollToSection(sec.id), className: "text-left text-sm px-3 py-2 rounded-lg text-[var(--appkit-color-text-muted)] hover:text-[var(--appkit-color-text)] hover:bg-[var(--appkit-color-border-subtle)] transition-colors", children: sec.label }, sec.id))) })), sections && sections.length > 0 && !previewMode && (_jsx("div", { className: "lg:hidden fixed top-[var(--form-shell-topbar-h,57px)] left-0 right-0 z-10 flex overflow-x-auto gap-1 px-5 py-2 bg-[var(--appkit-color-surface)] border-b border-[var(--appkit-color-border)]", children: sections.map((sec) => (_jsx("button", { type: "button", onClick: () => scrollToSection(sec.id), className: "flex-shrink-0 text-xs px-3 py-1.5 rounded-full border border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:border-[var(--appkit-color-primary)] hover:text-[var(--appkit-color-primary)] transition-colors whitespace-nowrap", children: sec.label }, sec.id))) })), _jsx("div", { ref: bodyRef, className: classNames("flex-1 overflow-y-auto", sections && sections.length > 0 && !previewMode ? "pt-0 lg:pt-0" : ""), children: previewMode && previewSlot ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sticky top-0 z-10 flex items-center gap-2 bg-[var(--appkit-color-warning-surface)] border-b border-[var(--appkit-color-warning)] px-4 py-2 text-sm text-[var(--appkit-color-warning-text,var(--appkit-color-warning))]", children: [_jsx(Eye, { className: "w-4 h-4 flex-shrink-0" }), _jsx("span", { children: "Preview \u2014 not visible to buyers until published" })] }), _jsx("div", { className: "py-4", children: previewSlot() })] })) : splitPreview && previewSlot ? (
|
|
106
|
+
// S-STORE-3-D — desktop 60/40 split: form left, preview right.
|
|
107
|
+
// Below `lg`, falls back to single-column form (preview-as-modal via existing toggle).
|
|
108
|
+
_jsxs("div", { className: "lg:grid lg:grid-cols-[3fr_2fr] lg:gap-6 lg:px-6 lg:py-6 lg:max-w-[1400px] lg:mx-auto", children: [_jsx("div", { className: "max-w-3xl mx-auto px-5 py-6 sm:px-6 lg:max-w-none lg:px-0 lg:py-0", children: children }), _jsxs("div", { className: "hidden lg:block sticky lg:top-4 lg:self-start lg:max-h-[calc(100vh-6rem)] overflow-y-auto border border-[var(--appkit-color-border)] rounded-lg bg-[var(--appkit-color-surface-raised)] p-4", children: [_jsxs("div", { className: "flex items-center gap-2 mb-3 text-xs font-semibold uppercase tracking-wide text-[var(--appkit-color-text-muted)]", children: [_jsx(Eye, { className: "w-3.5 h-3.5" }), _jsx("span", { children: "Live preview" })] }), previewSlot()] })] })) : (_jsx("div", { className: "max-w-3xl mx-auto px-5 py-6 sm:px-6", children: children })) })] }), !previewMode && renderBottomBar ? (renderBottomBar()) : !previewMode && (onSaveDraft || onPublish) ? (_jsxs("div", { className: "flex-shrink-0 sticky bottom-0 z-10 border-t border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)] px-5 py-3 flex items-center justify-between", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: attemptClose, disabled: isLoading, children: FORM_ACTION_META[FORM_ACTION_ID.DISCARD].label }), _jsxs("div", { className: "flex items-center gap-2", children: [onSaveDraft && (_jsx(Button, { variant: "outline", size: "sm", onClick: handleSaveDraft, disabled: isLoading || savingDraft || publishing, isLoading: savingDraft, children: saveLabel })), onPublish && (_jsxs(Button, { variant: "primary", size: "sm", onClick: handlePublish, disabled: isLoading || savingDraft || publishing, isLoading: publishing, children: [publishLabel, " \u2192"] }))] })] })) : null] }), showUnsaved && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 bg-black/60", style: { zIndex: "calc(var(--appkit-z-modal) + 5)" }, onClick: () => setShowUnsaved(false) }), _jsxs("div", { className: "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] max-w-sm bg-[var(--appkit-color-surface)] rounded-xl shadow-2xl p-6", style: { zIndex: "calc(var(--appkit-z-modal) + 5)" }, children: [_jsxs("div", { className: "flex gap-3 mb-4", children: [_jsx("span", { className: "flex-shrink-0 w-10 h-10 rounded-full bg-[var(--appkit-color-warning-surface)] flex items-center justify-center", children: _jsx(AlertTriangle, { className: "w-5 h-5 text-[var(--appkit-color-warning)]" }) }), _jsxs(_Fragment, { children: [_jsx(Text, { className: "font-semibold text-[var(--appkit-color-text)]", children: "Unsaved changes" }), _jsx(Text, { className: "text-sm text-[var(--appkit-color-text-muted)] mt-1", children: "You have unsaved changes. Leave without saving?" })] })] }), _jsxs("div", { className: "flex gap-2 justify-end", children: [_jsx(Button, { variant: "outline", size: "sm", onClick: () => setShowUnsaved(false), children: "Stay" }), _jsx(Button, { variant: "danger", size: "sm", onClick: () => { setShowUnsaved(false); onClose(); }, children: "Leave" })] })] })] }))] }));
|
|
106
109
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { type AdminNotificationDocument, type CustomRoleDocument, type RoleOverrideDocument } from "../schemas/rbac";
|
|
3
|
+
export declare class RoleOverridesRepository extends BaseRepository<RoleOverrideDocument> {
|
|
4
|
+
constructor();
|
|
5
|
+
listForUser(userId: string): Promise<{
|
|
6
|
+
items: RoleOverrideDocument[];
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export declare class CustomRolesRepository extends BaseRepository<CustomRoleDocument> {
|
|
10
|
+
constructor();
|
|
11
|
+
listActive(): Promise<{
|
|
12
|
+
items: CustomRoleDocument[];
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare class AdminNotificationsRepository extends BaseRepository<AdminNotificationDocument> {
|
|
16
|
+
constructor();
|
|
17
|
+
listUnread(): Promise<{
|
|
18
|
+
items: AdminNotificationDocument[];
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare const roleOverridesRepository: RoleOverridesRepository;
|
|
22
|
+
export declare const customRolesRepository: CustomRolesRepository;
|
|
23
|
+
export declare const adminNotificationsRepository: AdminNotificationsRepository;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
2
|
+
import { ADMIN_NOTIFICATIONS_COLLECTION, CUSTOM_ROLES_COLLECTION, ROLE_OVERRIDES_COLLECTION, } from "../schemas/rbac";
|
|
3
|
+
export class RoleOverridesRepository extends BaseRepository {
|
|
4
|
+
constructor() {
|
|
5
|
+
super(ROLE_OVERRIDES_COLLECTION);
|
|
6
|
+
}
|
|
7
|
+
async listForUser(userId) {
|
|
8
|
+
const items = await this.findBy("userId", userId);
|
|
9
|
+
return { items };
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class CustomRolesRepository extends BaseRepository {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(CUSTOM_ROLES_COLLECTION);
|
|
15
|
+
}
|
|
16
|
+
async listActive() {
|
|
17
|
+
const all = await this.findAll(50);
|
|
18
|
+
return { items: all.filter((d) => d.isActive !== false) };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AdminNotificationsRepository extends BaseRepository {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(ADMIN_NOTIFICATIONS_COLLECTION);
|
|
24
|
+
}
|
|
25
|
+
async listUnread() {
|
|
26
|
+
const items = await this.findBy("isRead", false);
|
|
27
|
+
return { items };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const roleOverridesRepository = new RoleOverridesRepository();
|
|
31
|
+
export const customRolesRepository = new CustomRolesRepository();
|
|
32
|
+
export const adminNotificationsRepository = new AdminNotificationsRepository();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-STORE Extension Repositories
|
|
3
|
+
*
|
|
4
|
+
* 11 lightweight BaseRepository subclasses. None of these collections store
|
|
5
|
+
* PII directly so no `createWithId` override is required (Pattern #9). Uses
|
|
6
|
+
* the BaseRepository public API only: `findById`, `findBy`, `findAll`,
|
|
7
|
+
* `create`, `update`, `delete`.
|
|
8
|
+
*/
|
|
9
|
+
import { BaseRepository } from "../../../providers/db-firebase";
|
|
10
|
+
import { type AnalyticsAlertDocument, type AnalyticsCardDocument, type ItemRequestDocument, type ListingTemplateDocument, type ModerationQueueDocument, type PayoutMethodDocument, type ReportDocument, type ShippingConfigDocument, type StoreCategoryDocument, type StoreGoogleConfigDocument, type StoreWhatsAppConfigDocument } from "../schemas/firestore";
|
|
11
|
+
export declare class PayoutMethodsRepository extends BaseRepository<PayoutMethodDocument> {
|
|
12
|
+
constructor();
|
|
13
|
+
listByStore(storeId: string): Promise<{
|
|
14
|
+
items: PayoutMethodDocument[];
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare class ShippingConfigsRepository extends BaseRepository<ShippingConfigDocument> {
|
|
18
|
+
constructor();
|
|
19
|
+
listByStore(storeId: string): Promise<{
|
|
20
|
+
items: ShippingConfigDocument[];
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare class AnalyticsCardsRepository extends BaseRepository<AnalyticsCardDocument> {
|
|
24
|
+
constructor();
|
|
25
|
+
listForOwner(scope: "seller" | "admin", ownerId: string): Promise<{
|
|
26
|
+
items: AnalyticsCardDocument[];
|
|
27
|
+
}>;
|
|
28
|
+
}
|
|
29
|
+
export declare class AnalyticsAlertsRepository extends BaseRepository<AnalyticsAlertDocument> {
|
|
30
|
+
constructor();
|
|
31
|
+
listForOwner(scope: "seller" | "admin", ownerId: string): Promise<{
|
|
32
|
+
items: AnalyticsAlertDocument[];
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export declare class StoreCategoriesRepository extends BaseRepository<StoreCategoryDocument> {
|
|
36
|
+
constructor();
|
|
37
|
+
listByStore(storeId: string): Promise<{
|
|
38
|
+
items: StoreCategoryDocument[];
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
export declare class ListingTemplatesRepository extends BaseRepository<ListingTemplateDocument> {
|
|
42
|
+
constructor();
|
|
43
|
+
listByStore(storeId: string): Promise<{
|
|
44
|
+
items: ListingTemplateDocument[];
|
|
45
|
+
}>;
|
|
46
|
+
}
|
|
47
|
+
export declare class ModerationQueueRepository extends BaseRepository<ModerationQueueDocument> {
|
|
48
|
+
constructor();
|
|
49
|
+
listPending(): Promise<{
|
|
50
|
+
items: ModerationQueueDocument[];
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export declare class ReportsRepository extends BaseRepository<ReportDocument> {
|
|
54
|
+
constructor();
|
|
55
|
+
listForEntity(entityType: string, entityId: string): Promise<{
|
|
56
|
+
items: ReportDocument[];
|
|
57
|
+
}>;
|
|
58
|
+
listPending(): Promise<{
|
|
59
|
+
items: ReportDocument[];
|
|
60
|
+
}>;
|
|
61
|
+
}
|
|
62
|
+
export declare class ItemRequestsRepository extends BaseRepository<ItemRequestDocument> {
|
|
63
|
+
constructor();
|
|
64
|
+
listOpen(opts?: {
|
|
65
|
+
limit?: number;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
items: ItemRequestDocument[];
|
|
68
|
+
}>;
|
|
69
|
+
listByOwner(opUserId: string): Promise<{
|
|
70
|
+
items: ItemRequestDocument[];
|
|
71
|
+
}>;
|
|
72
|
+
}
|
|
73
|
+
export declare class StoreWhatsAppConfigRepository extends BaseRepository<StoreWhatsAppConfigDocument> {
|
|
74
|
+
constructor();
|
|
75
|
+
getByStore(storeId: string): Promise<StoreWhatsAppConfigDocument | null>;
|
|
76
|
+
}
|
|
77
|
+
export declare class StoreGoogleConfigRepository extends BaseRepository<StoreGoogleConfigDocument> {
|
|
78
|
+
constructor();
|
|
79
|
+
getByStore(storeId: string): Promise<StoreGoogleConfigDocument | null>;
|
|
80
|
+
}
|
|
81
|
+
export declare const payoutMethodsRepository: PayoutMethodsRepository;
|
|
82
|
+
export declare const shippingConfigsRepository: ShippingConfigsRepository;
|
|
83
|
+
export declare const analyticsCardsRepository: AnalyticsCardsRepository;
|
|
84
|
+
export declare const analyticsAlertsRepository: AnalyticsAlertsRepository;
|
|
85
|
+
export declare const storeCategoriesRepository: StoreCategoriesRepository;
|
|
86
|
+
export declare const listingTemplatesRepository: ListingTemplatesRepository;
|
|
87
|
+
export declare const moderationQueueRepository: ModerationQueueRepository;
|
|
88
|
+
export declare const reportsRepository: ReportsRepository;
|
|
89
|
+
export declare const itemRequestsRepository: ItemRequestsRepository;
|
|
90
|
+
export declare const storeWhatsAppConfigRepository: StoreWhatsAppConfigRepository;
|
|
91
|
+
export declare const storeGoogleConfigRepository: StoreGoogleConfigRepository;
|