@mohasinac/appkit 3.0.4 → 3.0.6

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 (149) hide show
  1. package/dist/_internal/server/features/auctions/data.d.ts +2 -4
  2. package/dist/_internal/server/features/auctions/data.js +3 -9
  3. package/dist/_internal/server/features/classified/adapters.d.ts +2 -15
  4. package/dist/_internal/server/features/classified/adapters.js +2 -17
  5. package/dist/_internal/server/features/classified/data.d.ts +1 -6
  6. package/dist/_internal/server/features/classified/data.js +2 -10
  7. package/dist/_internal/server/features/digital-code/adapters.d.ts +2 -15
  8. package/dist/_internal/server/features/digital-code/adapters.js +2 -17
  9. package/dist/_internal/server/features/digital-code/data.d.ts +1 -6
  10. package/dist/_internal/server/features/digital-code/data.js +2 -10
  11. package/dist/_internal/server/features/live/adapters.d.ts +2 -15
  12. package/dist/_internal/server/features/live/adapters.js +2 -17
  13. package/dist/_internal/server/features/live/data.d.ts +1 -6
  14. package/dist/_internal/server/features/live/data.js +2 -10
  15. package/dist/_internal/server/features/pre-orders/data.d.ts +2 -4
  16. package/dist/_internal/server/features/pre-orders/data.js +3 -9
  17. package/dist/_internal/server/features/prize-draws/actions.d.ts +2 -0
  18. package/dist/_internal/server/features/prize-draws/actions.js +22 -0
  19. package/dist/_internal/server/features/prize-draws/data.d.ts +1 -17
  20. package/dist/_internal/server/features/prize-draws/data.js +2 -20
  21. package/dist/_internal/server/features/prize-draws/index.d.ts +3 -1
  22. package/dist/_internal/server/features/prize-draws/index.js +2 -0
  23. package/dist/_internal/server/features/prize-draws/service.d.ts +3 -0
  24. package/dist/_internal/server/features/prize-draws/service.js +19 -0
  25. package/dist/_internal/server/features/shared/listing-adapters.d.ts +19 -0
  26. package/dist/_internal/server/features/shared/listing-adapters.js +20 -0
  27. package/dist/_internal/server/features/shared/listing-data-factory.d.ts +9 -0
  28. package/dist/_internal/server/features/shared/listing-data-factory.js +33 -0
  29. package/dist/_internal/server/features/stores/data.d.ts +3 -14
  30. package/dist/_internal/server/features/stores/data.js +6 -44
  31. package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
  32. package/dist/_internal/shared/features/cart/schema.js +3 -0
  33. package/dist/_internal/shared/listing-types/_registry.js +2 -0
  34. package/dist/_internal/shared/listing-types/auction/schema.d.ts +3 -1
  35. package/dist/_internal/shared/listing-types/auction/schema.js +7 -1
  36. package/dist/_internal/shared/listing-types/classified/schema.d.ts +2 -1
  37. package/dist/_internal/shared/listing-types/classified/schema.js +3 -1
  38. package/dist/_internal/shared/listing-types/digital-code/schema.d.ts +3 -1
  39. package/dist/_internal/shared/listing-types/digital-code/schema.js +7 -1
  40. package/dist/_internal/shared/listing-types/live/schema.d.ts +2 -1
  41. package/dist/_internal/shared/listing-types/live/schema.js +3 -1
  42. package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +3 -1
  43. package/dist/_internal/shared/listing-types/pre-order/schema.js +7 -1
  44. package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +3 -1
  45. package/dist/_internal/shared/listing-types/prize-draw/schema.js +13 -1
  46. package/dist/_internal/shared/listing-types/standard/schema.d.ts +2 -1
  47. package/dist/_internal/shared/listing-types/standard/schema.js +4 -1
  48. package/dist/_internal/shared/types/base-document.d.ts +15 -0
  49. package/dist/_internal/shared/types/base-document.js +1 -0
  50. package/dist/_internal/shared/types/index.d.ts +2 -0
  51. package/dist/_internal/shared/types/index.js +1 -0
  52. package/dist/client.d.ts +4 -4
  53. package/dist/client.js +2 -2
  54. package/dist/constants/api-endpoints.d.ts +12 -0
  55. package/dist/constants/api-endpoints.js +4 -0
  56. package/dist/features/addresses/schemas/firestore.d.ts +2 -4
  57. package/dist/features/admin/components/AdminFulfillmentView.d.ts +6 -0
  58. package/dist/features/admin/components/AdminFulfillmentView.js +107 -0
  59. package/dist/features/admin/components/AdminProductEditorView.js +21 -9
  60. package/dist/features/admin/components/AdminProductsView.js +3 -0
  61. package/dist/features/admin/components/DataListingView.d.ts +1 -0
  62. package/dist/features/admin/components/index.d.ts +2 -0
  63. package/dist/features/admin/components/index.js +1 -0
  64. package/dist/features/admin/schemas/firestore.d.ts +3 -8
  65. package/dist/features/auctions/schemas/firestore.d.ts +2 -4
  66. package/dist/features/auctions/schemas/index.d.ts +2 -2
  67. package/dist/features/before-after/schemas/index.d.ts +2 -2
  68. package/dist/features/blog/schemas/firestore.d.ts +2 -4
  69. package/dist/features/blog/schemas/index.d.ts +4 -4
  70. package/dist/features/cart/schemas/firestore.d.ts +2 -4
  71. package/dist/features/categories/schemas/firestore.d.ts +2 -4
  72. package/dist/features/categories/schemas/index.d.ts +4 -4
  73. package/dist/features/events/schemas/firestore.d.ts +2 -4
  74. package/dist/features/events/schemas/index.d.ts +2 -2
  75. package/dist/features/faq/schemas/firestore.d.ts +2 -4
  76. package/dist/features/faq/schemas/index.d.ts +2 -2
  77. package/dist/features/grouped/schemas/index.d.ts +2 -2
  78. package/dist/features/homepage/schemas/firestore.d.ts +4 -12
  79. package/dist/features/homepage/schemas/index.d.ts +2 -2
  80. package/dist/features/messages/schemas/firestore.d.ts +2 -4
  81. package/dist/features/messages/schemas/index.d.ts +2 -2
  82. package/dist/features/orders/repository/orders.repository.d.ts +5 -0
  83. package/dist/features/orders/repository/orders.repository.js +26 -0
  84. package/dist/features/orders/schemas/firestore.d.ts +9 -5
  85. package/dist/features/orders/schemas/firestore.js +3 -0
  86. package/dist/features/payments/schemas/firestore.d.ts +2 -4
  87. package/dist/features/pre-orders/schemas/index.d.ts +2 -2
  88. package/dist/features/products/constants/action-defs.d.ts +16 -0
  89. package/dist/features/products/constants/action-defs.js +16 -0
  90. package/dist/features/products/constants/listing-tabs.d.ts +48 -0
  91. package/dist/features/products/constants/listing-tabs.js +12 -0
  92. package/dist/features/products/repository/products.repository.d.ts +37 -0
  93. package/dist/features/products/repository/products.repository.js +28 -1
  94. package/dist/features/products/schemas/firestore.d.ts +8 -6
  95. package/dist/features/products/schemas/firestore.js +2 -0
  96. package/dist/features/products/schemas/index.d.ts +6 -6
  97. package/dist/features/products/types/index.d.ts +2 -0
  98. package/dist/features/promotions/schemas/firestore.d.ts +2 -4
  99. package/dist/features/promotions/schemas/index.d.ts +4 -4
  100. package/dist/features/reviews/schemas/firestore.d.ts +2 -4
  101. package/dist/features/reviews/schemas/index.d.ts +2 -2
  102. package/dist/features/scams/schemas/firestore.d.ts +2 -4
  103. package/dist/features/scams/schemas/index.d.ts +4 -4
  104. package/dist/features/search/schemas/index.d.ts +2 -2
  105. package/dist/features/seller/components/BarcodeField.d.ts +17 -0
  106. package/dist/features/seller/components/BarcodeField.js +30 -0
  107. package/dist/features/seller/components/FulfillmentView.d.ts +5 -0
  108. package/dist/features/seller/components/FulfillmentView.js +120 -0
  109. package/dist/features/seller/components/PrintCenterView.d.ts +3 -1
  110. package/dist/features/seller/components/PrintCenterView.js +52 -4
  111. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  112. package/dist/features/seller/components/SellerProductShell.js +2 -1
  113. package/dist/features/seller/components/index.d.ts +4 -0
  114. package/dist/features/seller/components/index.js +2 -0
  115. package/dist/features/seller/schemas/index.d.ts +2 -2
  116. package/dist/features/store-extensions/schemas/firestore.d.ts +12 -44
  117. package/dist/features/stores/components/StoreClassifiedsListing.d.ts +5 -0
  118. package/dist/features/stores/components/StoreClassifiedsListing.js +48 -0
  119. package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -0
  120. package/dist/features/stores/components/StoreClassifiedsPageView.js +20 -0
  121. package/dist/features/stores/components/StoreDetailLayoutView.js +23 -2
  122. package/dist/features/stores/components/StoreDigitalCodesListing.d.ts +5 -0
  123. package/dist/features/stores/components/StoreDigitalCodesListing.js +48 -0
  124. package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -0
  125. package/dist/features/stores/components/StoreDigitalCodesPageView.js +20 -0
  126. package/dist/features/stores/components/StoreLiveItemsListing.d.ts +5 -0
  127. package/dist/features/stores/components/StoreLiveItemsListing.js +48 -0
  128. package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -0
  129. package/dist/features/stores/components/StoreLiveItemsPageView.js +20 -0
  130. package/dist/features/stores/components/index.d.ts +9 -0
  131. package/dist/features/stores/components/index.js +3 -0
  132. package/dist/features/stores/schemas/firestore.d.ts +2 -4
  133. package/dist/features/stores/schemas/index.d.ts +4 -4
  134. package/dist/features/support/schemas/firestore.d.ts +2 -4
  135. package/dist/features/support/schemas/index.d.ts +2 -2
  136. package/dist/next/routing/route-map.d.ts +11 -0
  137. package/dist/next/routing/route-map.js +5 -0
  138. package/dist/schemas/registry.d.ts +24 -24
  139. package/dist/seed/grouped-listings-seed-data.d.ts +2 -1
  140. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  141. package/dist/seed/products-standard-seed-data.js +8 -0
  142. package/dist/server-entry.d.ts +3 -0
  143. package/dist/server-entry.js +3 -0
  144. package/dist/server.d.ts +4 -4
  145. package/dist/styles.css +1 -1
  146. package/dist/tailwind-utilities.css +1 -1
  147. package/dist/utils/id-generators.d.ts +8 -0
  148. package/dist/utils/id-generators.js +14 -0
  149. package/package.json +2 -1
@@ -21,9 +21,9 @@ export declare const searchProductItemSchema: z.ZodObject<{
21
21
  isPromoted: z.ZodOptional<z.ZodBoolean>;
22
22
  slug: z.ZodString;
23
23
  }, "strip", z.ZodTypeAny, {
24
- id: string;
25
24
  title: string;
26
25
  slug: string;
26
+ id: string;
27
27
  status?: string | undefined;
28
28
  currency?: string | undefined;
29
29
  featured?: boolean | undefined;
@@ -32,9 +32,9 @@ export declare const searchProductItemSchema: z.ZodObject<{
32
32
  price?: number | undefined;
33
33
  mainImage?: string | undefined;
34
34
  }, {
35
- id: string;
36
35
  title: string;
37
36
  slug: string;
37
+ id: string;
38
38
  status?: string | undefined;
39
39
  currency?: string | undefined;
40
40
  featured?: boolean | undefined;
@@ -0,0 +1,17 @@
1
+ export interface BarcodeFieldProps {
2
+ label?: string;
3
+ value: string;
4
+ onChange: (value: string) => void;
5
+ /** Called when the scanner fires Enter after rapid character input (< 50 ms between keys). */
6
+ onScan?: (value: string) => void;
7
+ placeholder?: string;
8
+ helperText?: string;
9
+ autoFocus?: boolean;
10
+ readOnly?: boolean;
11
+ }
12
+ /**
13
+ * Text field that detects USB/Bluetooth barcode scanner input.
14
+ * Scanners type characters rapidly (< 50 ms per keydown) then send Enter.
15
+ * onScan is called when Enter follows rapid input; manual typing is ignored.
16
+ */
17
+ export declare function BarcodeField({ label, value, onChange, onScan, placeholder, helperText, autoFocus, readOnly, }: BarcodeFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef, useCallback } from "react";
4
+ import { FieldInput } from "../../../ui/forms/FieldInput";
5
+ import { Text } from "../../../ui/components/Typography";
6
+ import { Stack } from "../../../ui/components/Layout";
7
+ /**
8
+ * Text field that detects USB/Bluetooth barcode scanner input.
9
+ * Scanners type characters rapidly (< 50 ms per keydown) then send Enter.
10
+ * onScan is called when Enter follows rapid input; manual typing is ignored.
11
+ */
12
+ export function BarcodeField({ label = "Barcode / Sticker ID", value, onChange, onScan, placeholder = "Scan or type barcode…", helperText, autoFocus, readOnly, }) {
13
+ const lastKeyTime = useRef(0);
14
+ const buffer = useRef("");
15
+ const handleKeyDown = useCallback((e) => {
16
+ const now = Date.now();
17
+ if (e.key === "Enter") {
18
+ e.preventDefault();
19
+ if (buffer.current) {
20
+ onScan?.(buffer.current);
21
+ buffer.current = "";
22
+ }
23
+ return;
24
+ }
25
+ const interval = now - lastKeyTime.current;
26
+ buffer.current = interval < 50 ? buffer.current + e.key : e.key;
27
+ lastKeyTime.current = now;
28
+ }, [onScan]);
29
+ return (_jsxs(Stack, { gap: "xs", children: [_jsx(FieldInput, { name: "barcodeId", label: label, type: "text", value: value, onChange: onChange, onKeyDown: handleKeyDown, placeholder: placeholder, autoFocus: autoFocus, readOnly: readOnly }), helperText && (_jsx(Text, { size: "xs", color: "muted", children: helperText }))] }));
30
+ }
@@ -0,0 +1,5 @@
1
+ export interface FulfillmentViewProps {
2
+ currentUserId?: string;
3
+ currentUserName?: string;
4
+ }
5
+ export declare function FulfillmentView({ currentUserId, }: FulfillmentViewProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,120 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { Heading, Text } from "../../../ui/components/Typography";
5
+ import { Stack, Row } from "../../../ui/components/Layout";
6
+ import { Button } from "../../../ui/components/Button";
7
+ import { Badge } from "../../../ui/components/Badge";
8
+ import { apiClient } from "../../../http";
9
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints";
10
+ import { BarcodeField } from "./BarcodeField";
11
+ import { normalizeError } from "../../../errors/normalize";
12
+ import { useToast } from "../../../ui";
13
+ function statusBadgeVariant(status) {
14
+ if (status === "confirmed")
15
+ return "warning";
16
+ if (status === "processing")
17
+ return "info";
18
+ if (status === "shipped")
19
+ return "success";
20
+ return "default";
21
+ }
22
+ export function FulfillmentView({ currentUserId, }) {
23
+ const [mode, setMode] = useState("scan");
24
+ const [orders, setOrders] = useState([]);
25
+ const [scanValue, setScanValue] = useState("");
26
+ const [scanResult, setScanResult] = useState(null);
27
+ const [scanError, setScanError] = useState(null);
28
+ const [filterMine, setFilterMine] = useState(false);
29
+ const [isLoadingOrders, setIsLoadingOrders] = useState(true);
30
+ const [isConfirming, setIsConfirming] = useState(false);
31
+ const [actionLoadingId, setActionLoadingId] = useState(null);
32
+ const { showToast } = useToast();
33
+ const fetchOrders = useCallback(async () => {
34
+ // toast-intentionally-silent: background data loader — stale list is kept on error
35
+ setIsLoadingOrders(true);
36
+ try {
37
+ const res = await apiClient.get(SELLER_ENDPOINTS.ORDERS_FULFILLMENT);
38
+ setOrders(res.orders ?? []);
39
+ }
40
+ catch {
41
+ // keep stale list on error
42
+ }
43
+ finally {
44
+ setIsLoadingOrders(false);
45
+ }
46
+ }, []);
47
+ useEffect(() => {
48
+ void fetchOrders();
49
+ }, [fetchOrders]);
50
+ const handleScan = useCallback(async (barcode) => {
51
+ setScanError(null);
52
+ setScanResult(null);
53
+ try {
54
+ const product = await apiClient.get(SELLER_ENDPOINTS.PRODUCTS_SCAN(barcode));
55
+ const matchedOrder = orders.find((o) => o.productId === product.id ||
56
+ o.items?.some((i) => i.productId === product.id));
57
+ if (!matchedOrder) {
58
+ setScanError(`No pending order found for barcode "${barcode}".`);
59
+ return;
60
+ }
61
+ setScanResult({ product, order: matchedOrder });
62
+ }
63
+ catch (err) {
64
+ setScanError(normalizeError(err).message);
65
+ }
66
+ }, [orders]);
67
+ const confirmPick = useCallback(async () => {
68
+ if (!scanResult)
69
+ return;
70
+ setIsConfirming(true);
71
+ try {
72
+ await apiClient.patch(SELLER_ENDPOINTS.ORDERS_BY_ID(scanResult.order.id), { markPicked: true });
73
+ setScanResult(null);
74
+ setScanValue("");
75
+ setScanError(null);
76
+ showToast("Order marked as picked!", "success");
77
+ await fetchOrders();
78
+ }
79
+ catch (err) {
80
+ setScanError(normalizeError(err).message);
81
+ }
82
+ finally {
83
+ setIsConfirming(false);
84
+ }
85
+ }, [scanResult, fetchOrders, showToast]);
86
+ const handleMarkPicked = useCallback(async (orderId) => {
87
+ setActionLoadingId(orderId + ":picked");
88
+ try {
89
+ await apiClient.patch(SELLER_ENDPOINTS.ORDERS_BY_ID(orderId), {
90
+ markPicked: true,
91
+ });
92
+ await fetchOrders();
93
+ }
94
+ finally {
95
+ setActionLoadingId(null);
96
+ }
97
+ }, [fetchOrders]);
98
+ const handleMarkPacked = useCallback(async (orderId) => {
99
+ setActionLoadingId(orderId + ":packed");
100
+ try {
101
+ await apiClient.patch(SELLER_ENDPOINTS.ORDERS_BY_ID(orderId), {
102
+ markPacked: true,
103
+ });
104
+ await fetchOrders();
105
+ }
106
+ finally {
107
+ setActionLoadingId(null);
108
+ }
109
+ }, [fetchOrders]);
110
+ const visibleOrders = filterMine
111
+ ? orders.filter((o) => o.assignedWorkerId === currentUserId)
112
+ : orders;
113
+ return (_jsxs(Stack, { gap: "md", padding: "md", children: [_jsxs(Row, { align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsx(Heading, { level: 3, children: "Pick & Pack" }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { onClick: () => setMode("scan"), variant: mode === "scan" ? "primary" : "secondary", size: "sm", children: "Scan Mode" }), _jsx(Button, { onClick: () => setMode("list"), variant: mode === "list" ? "primary" : "secondary", size: "sm", children: "List Mode" })] })] }), mode === "scan" && (_jsxs(Stack, { gap: "md", children: [_jsx(BarcodeField, { autoFocus: true, value: scanValue, onChange: setScanValue, onScan: handleScan, helperText: "Point scanner at an item sticker or type the barcode and press Enter." }), scanError && (_jsx(Text, { color: "error", size: "sm", children: scanError })), scanResult && (_jsxs(Stack, { surface: "card", padding: "md", rounded: "md", gap: "sm", children: [_jsx(Text, { size: "xs", color: "muted", children: "Scanned item" }), _jsx(Heading, { level: 5, children: scanResult.product.title ?? scanResult.product.id }), _jsxs(Row, { gap: "md", wrap: true, children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Order" }), _jsxs(Text, { size: "sm", weight: "medium", children: ["#", scanResult.order.id] })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Buyer" }), _jsx(Text, { size: "sm", weight: "medium", children: scanResult.order.userName })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", children: "Status" }), _jsx(Badge, { variant: statusBadgeVariant(scanResult.order.status), children: scanResult.order.status })] })] }), _jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "primary", onClick: confirmPick, isLoading: isConfirming, children: "Confirm Pick" }), _jsx(Button, { variant: "secondary", onClick: () => {
114
+ setScanResult(null);
115
+ setScanValue("");
116
+ setScanError(null);
117
+ }, children: "Cancel" })] })] })), !scanResult && !scanError && (_jsx(Text, { color: "muted", size: "sm", children: orders.length === 0 && !isLoadingOrders
118
+ ? "No orders waiting — great work!"
119
+ : `${orders.length} order${orders.length === 1 ? "" : "s"} in queue.` }))] })), mode === "list" && (_jsxs(Stack, { gap: "sm", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Button, { variant: filterMine ? "primary" : "secondary", size: "sm", onClick: () => setFilterMine((v) => !v), children: filterMine ? "Showing My Orders" : "My Orders" }), _jsx(Text, { size: "xs", color: "muted", children: isLoadingOrders ? "Loading…" : `${visibleOrders.length} order${visibleOrders.length === 1 ? "" : "s"}` })] }), visibleOrders.length === 0 && !isLoadingOrders && (_jsx(Text, { color: "muted", children: "No orders waiting \u2014 great work!" })), visibleOrders.map((order) => (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "md", gap: "xs", children: [_jsxs(Row, { align: "center", gap: "xs", wrap: true, children: [_jsxs(Text, { size: "sm", weight: "medium", children: ["#", order.id] }), _jsx(Badge, { variant: statusBadgeVariant(order.status), children: order.status }), order.pickedAt && (_jsx(Badge, { variant: "success", children: "Picked" })), order.packedAt && (_jsx(Badge, { variant: "success", children: "Packed" }))] }), _jsxs(Text, { size: "xs", color: "muted", children: [order.userName, order.items && order.items.length > 0 && (_jsxs(_Fragment, { children: [" \u00B7 ", order.items.map((i) => i.productTitle).join(", ")] }))] }), order.assignedWorkerId && (_jsxs(Text, { size: "xs", color: "muted", children: ["Assigned: ", order.assignedWorkerId === currentUserId ? "Me" : order.assignedWorkerId] })), _jsxs(Row, { gap: "xs", children: [!order.pickedAt && (_jsx(Button, { size: "sm", variant: "secondary", onClick: () => handleMarkPicked(order.id), isLoading: actionLoadingId === order.id + ":picked", children: "Mark Picked" })), order.pickedAt && !order.packedAt && (_jsx(Button, { size: "sm", variant: "secondary", onClick: () => handleMarkPacked(order.id), isLoading: actionLoadingId === order.id + ":packed", children: "Mark Packed" }))] })] }, order.id)))] }))] }));
120
+ }
@@ -14,6 +14,7 @@ interface PrintCenterProduct {
14
14
  condition?: string;
15
15
  stockCount?: number;
16
16
  physicalLocation?: string;
17
+ barcodeId?: string;
17
18
  }
18
19
  interface PrintCenterOrder {
19
20
  id: string;
@@ -25,6 +26,7 @@ interface PrintCenterOrder {
25
26
  productName: string;
26
27
  quantity: number;
27
28
  price: number;
29
+ barcodeId?: string;
28
30
  }[];
29
31
  physicalLocation?: string;
30
32
  }
@@ -36,5 +38,5 @@ interface PrintCenterViewProps {
36
38
  initialProducts?: PrintCenterProduct[];
37
39
  initialOrders?: PrintCenterOrder[];
38
40
  }
39
- export declare function PrintCenterView({ store, brandName }: PrintCenterViewProps): import("react/jsx-runtime").JSX.Element;
41
+ export declare function PrintCenterView({ store, brandName, initialProducts, initialOrders, }: PrintCenterViewProps): import("react/jsx-runtime").JSX.Element;
40
42
  export {};
@@ -1,7 +1,55 @@
1
1
  "use client";
2
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import JsBarcode from "jsbarcode";
3
5
  import { Heading, Text } from "../../../ui/components/Typography";
4
- import { Stack } from "@mohasinac/appkit";
5
- export function PrintCenterView({ store, brandName = "LetItRip" }) {
6
- return (_jsxs(Stack, { justify: "center", padding: "y-6xl", className: "text-center", align: "center", gap: "md", children: [_jsxs(Heading, { level: 2, children: [brandName, " Print Center"] }), store && _jsx(Text, { size: "sm", variant: "muted", children: store.storeName }), _jsx(Text, { size: "sm", variant: "muted", children: "Print center features are coming soon." })] }));
6
+ import { Stack, Row } from "../../../ui/components/Layout";
7
+ import { Button } from "../../../ui/components/Button";
8
+ import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
9
+ function ProductLabel({ product }) {
10
+ const svgRef = useRef(null);
11
+ useEffect(() => {
12
+ if (svgRef.current && product.barcodeId) {
13
+ try {
14
+ JsBarcode(svgRef.current, product.barcodeId, {
15
+ format: "CODE128",
16
+ width: 2,
17
+ height: 50,
18
+ displayValue: true,
19
+ fontSize: 12,
20
+ margin: 8,
21
+ });
22
+ }
23
+ catch {
24
+ // Invalid barcode value — render nothing
25
+ }
26
+ }
27
+ }, [product.barcodeId]);
28
+ return (_jsxs(Stack, { surface: "card", padding: "sm", rounded: "sm", gap: "xs", className: "break-inside-avoid text-center w-[200px]", children: [product.barcodeId ? (_jsx("svg", { ref: svgRef })) : (_jsx(Text, { size: "xs", color: "muted", children: "No barcode" })), _jsx(Text, { size: "xs", weight: "medium", truncate: 1, children: product.name }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", (product.price / 100).toLocaleString("en-IN")] }), product.physicalLocation && (_jsx(Text, { size: "xs", color: "muted", children: product.physicalLocation }))] }));
29
+ }
30
+ function PackingSlip({ order, brandName }) {
31
+ return (_jsxs(Stack, { surface: "card", padding: "md", rounded: "sm", className: "break-inside-avoid mb-4 max-w-[480px]", children: [_jsxs(Row, { justify: "between", className: "mb-2", children: [_jsx(Heading, { level: 5, children: brandName }), _jsxs(Text, { size: "xs", color: "muted", children: ["#", order.id] })] }), _jsx(Text, { size: "xs", color: "muted", children: new Date(order.createdAt).toLocaleDateString("en-IN") }), order.buyerDisplayName && _jsx(Text, { size: "sm", weight: "medium", children: order.buyerDisplayName }), order.buyerCity && _jsx(Text, { size: "xs", color: "muted", children: order.buyerCity }), order.physicalLocation && (_jsxs(Text, { size: "xs", color: "muted", children: ["Staging: ", order.physicalLocation] })), _jsx(Stack, { className: "mt-3 border-t pt-2", children: order.items.map((item, i) => (
32
+ // audit-variant-ok: py-1 is vertical-only padding; border-b last:border-b-0 uses pseudo-selector — no Row variant covers these
33
+ _jsxs(Row, { justify: "between", className: "py-1 border-b last:border-b-0", children: [_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", weight: "medium", children: item.productName }), item.barcodeId && _jsx(Text, { size: "xs", color: "muted", children: item.barcodeId })] }), _jsxs(Stack, { className: "text-right", children: [_jsxs(Text, { size: "xs", children: ["\u00D7", item.quantity] }), _jsxs(Text, { size: "xs", color: "muted", children: ["\u20B9", (item.price / 100).toLocaleString("en-IN")] })] })] }, i))) })] }));
34
+ }
35
+ export function PrintCenterView({ store, brandName = "LetItRip", initialProducts = [], initialOrders = [], }) {
36
+ const [tab, setTab] = useState("labels");
37
+ const [selectedProducts, setSelectedProducts] = useState(new Set());
38
+ const [selectedOrders, setSelectedOrders] = useState(new Set());
39
+ const toggleProduct = (id) => setSelectedProducts((prev) => {
40
+ const next = new Set(prev);
41
+ next.has(id) ? next.delete(id) : next.add(id);
42
+ return next;
43
+ });
44
+ const toggleOrder = (id) => setSelectedOrders((prev) => {
45
+ const next = new Set(prev);
46
+ next.has(id) ? next.delete(id) : next.add(id);
47
+ return next;
48
+ });
49
+ const printSelected = () => window.print();
50
+ return (_jsxs(Stack, { gap: "md", padding: "md", children: [_jsx("style", { children: `@media print { .no-print { display: none !important; } }` }), _jsxs(Stack, { className: "no-print", children: [_jsxs(Heading, { level: 3, children: ["Print Center", store ? ` — ${store.storeName}` : ""] }), _jsxs(Row, { gap: "xs", className: "mt-3", children: [_jsx(Button, { onClick: () => setTab("labels"), variant: tab === "labels" ? "primary" : "secondary", size: "sm", children: "Product Labels" }), _jsx(Button, { onClick: () => setTab("slips"), variant: tab === "slips" ? "primary" : "secondary", size: "sm", children: "Packing Slips" })] })] }), tab === "labels" && (_jsxs(_Fragment, { children: [_jsx(Stack, { className: "no-print", children: _jsxs(Stack, { gap: "sm", children: [initialProducts.length === 0 ? (_jsx(Text, { color: "muted", children: "No products available." })) : (initialProducts.map((p) => (_jsx(FieldCheckbox, { name: `product-${p.id}`, label: p.name, checked: selectedProducts.has(p.id), onChange: () => toggleProduct(p.id) }, p.id)))), _jsxs(Button, { onClick: printSelected, disabled: selectedProducts.size === 0, children: ["Print Selected (", selectedProducts.size, ")"] })] }) }), _jsx(Row, { wrap: true, gap: "sm", className: "mt-4", children: initialProducts
51
+ .filter((p) => selectedProducts.has(p.id))
52
+ .map((p) => (_jsx(ProductLabel, { product: p }, p.id))) })] })), tab === "slips" && (_jsxs(_Fragment, { children: [_jsx(Stack, { className: "no-print", children: _jsxs(Stack, { gap: "sm", children: [initialOrders.length === 0 ? (_jsx(Text, { color: "muted", children: "No orders available." })) : (initialOrders.map((o) => (_jsx(FieldCheckbox, { name: `order-${o.id}`, label: `#${o.id}${o.buyerDisplayName ? ` — ${o.buyerDisplayName}` : ""}`, checked: selectedOrders.has(o.id), onChange: () => toggleOrder(o.id) }, o.id)))), _jsxs(Button, { onClick: printSelected, disabled: selectedOrders.size === 0, children: ["Print Selected (", selectedOrders.size, ")"] })] }) }), _jsx(Stack, { gap: "sm", children: initialOrders
53
+ .filter((o) => selectedOrders.has(o.id))
54
+ .map((o) => (_jsx(PackingSlip, { order: o, brandName: brandName }, o.id))) })] }))] }));
7
55
  }
@@ -59,6 +59,8 @@ export interface SellerProductDraft {
59
59
  liveHandlingFee?: number;
60
60
  liveJurisdictions?: string[];
61
61
  liveCites?: boolean;
62
+ /** Barcode ID of the physical sticker on this item. Auto-generated on save if blank. */
63
+ barcodeId?: string;
62
64
  }
63
65
  export interface SellerProductShellProps {
64
66
  mode: "create" | "edit";
@@ -9,6 +9,7 @@ import { Alert, Button, Div, FormField, FormGroup, Heading, Section, Stack, Text
9
9
  import { ImageUpload, MediaUploadField, MediaUploadList, useMediaUpload } from "../../media";
10
10
  import { StoreAddressSelectorCreate } from "../../stores/components/StoreAddressSelectorCreate";
11
11
  import { QuickProductForm } from "./QuickProductForm";
12
+ import { BarcodeField } from "./BarcodeField";
12
13
  import { normalizeError } from "../../../errors/normalize";
13
14
  const sellerProductSchema = z.object({
14
15
  title: z.string().min(1, "Product title is required").max(200),
@@ -39,7 +40,7 @@ function toPaise(rupeeStr) {
39
40
  }
40
41
  // ── Step 1: Basic ─────────────────────────────────────────────────────────────
41
42
  function StepBasic({ values, onChange, renderCategorySelector, renderBrandSelector, renderTemplateSelector, }) {
42
- return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(FormField, { name: "tags", label: "Tags (comma-separated)", type: "text", value: (values.tags ?? []).join(", "), onChange: (v) => onChange({ tags: v.split(",").map((t) => t.trim()).filter(Boolean) }), placeholder: "pokemon, psa9, charizard, holo" })] }));
43
+ return (_jsxs(Stack, { gap: "md", children: [renderTemplateSelector?.({ onApply: onChange }), _jsx(FormField, { name: "title", label: "Title", type: "text", value: values.title ?? "", onChange: (v) => onChange({ title: v }), placeholder: "e.g. Charizard Base Set PSA 9" }), _jsx(FormField, { name: "description", label: "Description", type: "textarea", value: values.description ?? "", onChange: (v) => onChange({ description: v }), placeholder: "Describe your listing in detail\u2026" }), _jsxs(FormGroup, { columns: 2, children: [renderCategorySelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Category" }), renderCategorySelector({ value: values.category ?? "", onChange: (v) => onChange({ category: v }) })] })) : (_jsx(FormField, { name: "category", label: "Category", type: "text", value: values.category ?? "", onChange: (v) => onChange({ category: v }), placeholder: "e.g. Trading Cards" })), _jsx(FormField, { name: "condition", label: "Condition", type: "select", value: values.condition ?? "new", onChange: (v) => onChange({ condition: v }), options: CONDITION_OPTIONS })] }), renderBrandSelector ? (_jsxs(_Fragment, { children: [_jsx(Text, { className: "text-[var(--appkit-color-text)] mb-1", size: "sm", weight: "medium", children: "Brand" }), renderBrandSelector({ value: values.brand ?? "", onChange: (v) => onChange({ brand: v }) })] })) : (_jsx(FormField, { name: "brand", label: "Brand", type: "text", value: values.brand ?? "", onChange: (v) => onChange({ brand: v }), placeholder: "e.g. Pok\u00E9mon Company" })), _jsx(FormField, { name: "tags", label: "Tags (comma-separated)", type: "text", value: (values.tags ?? []).join(", "), onChange: (v) => onChange({ tags: v.split(",").map((t) => t.trim()).filter(Boolean) }), placeholder: "pokemon, psa9, charizard, holo" }), _jsx(BarcodeField, { value: values.barcodeId ?? "", onChange: (v) => onChange({ barcodeId: v || undefined }), onScan: (v) => onChange({ barcodeId: v }), helperText: "Leave blank to auto-generate. Scan a pre-printed sticker to link it." })] }));
43
44
  }
44
45
  // ── Step 2: Media ─────────────────────────────────────────────────────────────
45
46
  function StepMedia({ values, onChange, storeSlug = "store", }) {
@@ -128,3 +128,7 @@ export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
128
128
  export type { SellerDigitalCodesViewProps } from "./SellerDigitalCodesView";
129
129
  export { SellerLiveView } from "./SellerLiveView";
130
130
  export type { SellerLiveViewProps } from "./SellerLiveView";
131
+ export { BarcodeField } from "./BarcodeField";
132
+ export type { BarcodeFieldProps } from "./BarcodeField";
133
+ export { FulfillmentView } from "./FulfillmentView";
134
+ export type { FulfillmentViewProps } from "./FulfillmentView";
@@ -67,3 +67,5 @@ export { SellerBundlesView } from "./SellerBundlesView";
67
67
  export { SellerClassifiedView } from "./SellerClassifiedView";
68
68
  export { SellerDigitalCodesView } from "./SellerDigitalCodesView";
69
69
  export { SellerLiveView } from "./SellerLiveView";
70
+ export { BarcodeField } from "./BarcodeField";
71
+ export { FulfillmentView } from "./FulfillmentView";
@@ -87,8 +87,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
87
87
  }, "strip", z.ZodTypeAny, {
88
88
  status: "pending" | "active" | "rejected" | "suspended";
89
89
  createdAt: string;
90
- id: string;
91
90
  storeName: string;
91
+ id: string;
92
92
  updatedAt: string;
93
93
  isPublic: boolean;
94
94
  storeSlug: string;
@@ -119,8 +119,8 @@ export declare const sellerStoreSchema: z.ZodObject<{
119
119
  }, {
120
120
  status: "pending" | "active" | "rejected" | "suspended";
121
121
  createdAt: string;
122
- id: string;
123
122
  storeName: string;
123
+ id: string;
124
124
  updatedAt: string;
125
125
  isPublic: boolean;
126
126
  storeSlug: string;
@@ -1,4 +1,5 @@
1
1
  import type { FirestoreDocument } from "@mohasinac/appkit";
2
+ import type { BaseDocument } from "../../../_internal/shared/types/base-document";
2
3
  /**
3
4
  * S-STORE Extensions — Firestore Document Types
4
5
  *
@@ -20,8 +21,7 @@ import type { FirestoreDocument } from "@mohasinac/appkit";
20
21
  * 11. storeGoogleConfig — per-store Google Business reviews integration
21
22
  */
22
23
  export type PayoutMethodType = "upi" | "bank" | "card" | "other";
23
- export interface PayoutMethodDocument {
24
- id: string;
24
+ export interface PayoutMethodDocument extends BaseDocument {
25
25
  sellerId: string;
26
26
  storeId: string;
27
27
  type: PayoutMethodType;
@@ -34,15 +34,12 @@ export interface PayoutMethodDocument {
34
34
  accountHolderName?: string;
35
35
  bankName?: string;
36
36
  details?: Record<string, string>;
37
- createdAt: Date;
38
- updatedAt: Date;
39
37
  }
40
38
  export declare const PAYOUT_METHODS_COLLECTION: "payoutMethods";
41
39
  export declare const PAYOUT_METHOD_INDEXED_FIELDS: readonly ["sellerId", "storeId", "type", "isDefault", "isActive", "createdAt"];
42
40
  export declare const DEFAULT_PAYOUT_METHOD_DATA: Partial<PayoutMethodDocument>;
43
41
  export type ShippingMethod = "free" | "flat" | "weight" | "express" | "pickup" | "custom";
44
- export interface ShippingConfigDocument {
45
- id: string;
42
+ export interface ShippingConfigDocument extends BaseDocument {
46
43
  storeId: string;
47
44
  label: string;
48
45
  method: ShippingMethod;
@@ -54,16 +51,13 @@ export interface ShippingConfigDocument {
54
51
  expressSurchargeInPaise?: number;
55
52
  estimatedDays?: number;
56
53
  zones?: string[];
57
- createdAt: Date;
58
- updatedAt: Date;
59
54
  }
60
55
  export declare const SHIPPING_CONFIGS_COLLECTION: "shippingConfigs";
61
56
  export declare const SHIPPING_CONFIG_INDEXED_FIELDS: readonly ["storeId", "method", "isDefault", "isActive", "createdAt"];
62
57
  export declare const DEFAULT_SHIPPING_CONFIG_DATA: Partial<ShippingConfigDocument>;
63
58
  export type AnalyticsScope = "seller" | "admin";
64
59
  export type AnalyticsCardType = "metric" | "line" | "bar" | "pie" | "table" | "custom";
65
- export interface AnalyticsCardDocument {
66
- id: string;
60
+ export interface AnalyticsCardDocument extends BaseDocument {
67
61
  scope: AnalyticsScope;
68
62
  ownerId: string;
69
63
  title: string;
@@ -73,15 +67,12 @@ export interface AnalyticsCardDocument {
73
67
  position: number;
74
68
  isBuiltIn: boolean;
75
69
  isVisible: boolean;
76
- createdAt: Date;
77
- updatedAt: Date;
78
70
  }
79
71
  export declare const ANALYTICS_CARDS_COLLECTION: "analyticsCards";
80
72
  export declare const ANALYTICS_CARD_INDEXED_FIELDS: readonly ["scope", "ownerId", "isVisible", "position", "createdAt"];
81
73
  export declare const DEFAULT_ANALYTICS_CARD_DATA: Partial<AnalyticsCardDocument>;
82
74
  export type AlertOperator = ">" | "<" | ">=" | "<=" | "==" | "!=";
83
- export interface AnalyticsAlertDocument {
84
- id: string;
75
+ export interface AnalyticsAlertDocument extends BaseDocument {
85
76
  scope: AnalyticsScope;
86
77
  ownerId: string;
87
78
  label: string;
@@ -92,14 +83,11 @@ export interface AnalyticsAlertDocument {
92
83
  isActive: boolean;
93
84
  notifyChannels: Array<"in-app" | "email" | "whatsapp">;
94
85
  lastTriggeredAt?: Date;
95
- createdAt: Date;
96
- updatedAt: Date;
97
86
  }
98
87
  export declare const ANALYTICS_ALERTS_COLLECTION: "analyticsAlerts";
99
88
  export declare const ANALYTICS_ALERT_INDEXED_FIELDS: readonly ["scope", "ownerId", "isActive", "metric", "createdAt"];
100
89
  export declare const DEFAULT_ANALYTICS_ALERT_DATA: Partial<AnalyticsAlertDocument>;
101
- export interface StoreCategoryDocument {
102
- id: string;
90
+ export interface StoreCategoryDocument extends BaseDocument {
103
91
  storeId: string;
104
92
  label: string;
105
93
  slug: string;
@@ -108,15 +96,12 @@ export interface StoreCategoryDocument {
108
96
  isActive: boolean;
109
97
  description?: string;
110
98
  coverImageUrl?: string;
111
- createdAt: Date;
112
- updatedAt: Date;
113
99
  }
114
100
  export declare const STORE_CATEGORIES_COLLECTION: "storeCategories";
115
101
  export declare const STORE_CATEGORY_INDEXED_FIELDS: readonly ["storeId", "slug", "isActive", "displayOrder", "createdAt"];
116
102
  export declare const DEFAULT_STORE_CATEGORY_DATA: Partial<StoreCategoryDocument>;
117
103
  export type ListingTemplateType = "standard" | "auction" | "pre-order" | "prize-draw" | "bundle" | "classified" | "digital-code" | "live";
118
- export interface ListingTemplateDocument {
119
- id: string;
104
+ export interface ListingTemplateDocument extends BaseDocument {
120
105
  storeId: string;
121
106
  ownerId: string;
122
107
  name: string;
@@ -127,16 +112,13 @@ export interface ListingTemplateDocument {
127
112
  isShared: boolean;
128
113
  isActive: boolean;
129
114
  usageCount: number;
130
- createdAt: Date;
131
- updatedAt: Date;
132
115
  }
133
116
  export declare const LISTING_TEMPLATES_COLLECTION: "listingTemplates";
134
117
  export declare const LISTING_TEMPLATE_INDEXED_FIELDS: readonly ["storeId", "ownerId", "listingType", "isShared", "isActive", "createdAt"];
135
118
  export declare const DEFAULT_LISTING_TEMPLATE_DATA: Partial<ListingTemplateDocument>;
136
119
  export type ModerationStatus = "pending" | "approved" | "rejected" | "auto-approved";
137
120
  export type ModerationMediaType = "video" | "image" | "rich-text";
138
- export interface ModerationQueueDocument {
139
- id: string;
121
+ export interface ModerationQueueDocument extends BaseDocument {
140
122
  mediaType: ModerationMediaType;
141
123
  mediaUrl?: string;
142
124
  thumbnailUrl?: string;
@@ -149,8 +131,6 @@ export interface ModerationQueueDocument {
149
131
  reviewerId?: string;
150
132
  reviewedAt?: Date;
151
133
  submittedAt: Date;
152
- createdAt: Date;
153
- updatedAt: Date;
154
134
  }
155
135
  export declare const MODERATION_QUEUE_COLLECTION: "moderationQueue";
156
136
  export declare const MODERATION_QUEUE_INDEXED_FIELDS: readonly ["status", "mediaType", "entityType", "entityId", "storeId", "ownerId", "submittedAt", "createdAt"];
@@ -158,8 +138,7 @@ export declare const DEFAULT_MODERATION_QUEUE_DATA: Partial<ModerationQueueDocum
158
138
  export type ReportEntityType = "product" | "store" | "review" | "event" | "user" | "blog" | "comment";
159
139
  export type ReportStatus = "pending" | "under-review" | "actioned" | "dismissed";
160
140
  export type ReportReason = "scam" | "counterfeit" | "prohibited" | "inappropriate" | "harassment" | "spam" | "ip-violation" | "other";
161
- export interface ReportDocument {
162
- id: string;
141
+ export interface ReportDocument extends BaseDocument {
163
142
  entityType: ReportEntityType;
164
143
  entityId: string;
165
144
  reporterId: string;
@@ -171,8 +150,6 @@ export interface ReportDocument {
171
150
  assignedTo?: string;
172
151
  resolution?: string;
173
152
  resolvedAt?: Date;
174
- createdAt: Date;
175
- updatedAt: Date;
176
153
  }
177
154
  export declare const REPORTS_COLLECTION: "reports";
178
155
  export declare const REPORT_INDEXED_FIELDS: readonly ["entityType", "entityId", "reporterId", "reason", "status", "createdAt"];
@@ -186,8 +163,7 @@ export interface ItemRequestReply {
186
163
  isOpInitiatedThread: boolean;
187
164
  createdAt: Date;
188
165
  }
189
- export interface ItemRequestDocument {
190
- id: string;
166
+ export interface ItemRequestDocument extends BaseDocument {
191
167
  opUserId: string;
192
168
  opDisplayName: string;
193
169
  title: string;
@@ -203,14 +179,11 @@ export interface ItemRequestDocument {
203
179
  approvedAt?: Date;
204
180
  approvedBy?: string;
205
181
  closedAt?: Date;
206
- createdAt: Date;
207
- updatedAt: Date;
208
182
  }
209
183
  export declare const ITEM_REQUESTS_COLLECTION: "itemRequests";
210
184
  export declare const ITEM_REQUEST_INDEXED_FIELDS: readonly ["opUserId", "status", "category", "createdAt"];
211
185
  export declare const DEFAULT_ITEM_REQUEST_DATA: Partial<ItemRequestDocument>;
212
- export interface StoreWhatsAppConfigDocument {
213
- id: string;
186
+ export interface StoreWhatsAppConfigDocument extends BaseDocument {
214
187
  storeId: string;
215
188
  isConnected: boolean;
216
189
  isPaid: boolean;
@@ -221,14 +194,11 @@ export interface StoreWhatsAppConfigDocument {
221
194
  welcomeMessage?: string;
222
195
  /** Status of pending WhatsApp Business onboarding (Meta Cloud API). */
223
196
  onboardingStatus?: "pending" | "approved" | "rejected";
224
- createdAt: Date;
225
- updatedAt: Date;
226
197
  }
227
198
  export declare const STORE_WHATSAPP_CONFIG_COLLECTION: "storeWhatsAppConfig";
228
199
  export declare const STORE_WHATSAPP_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected", "isPaid"];
229
200
  export declare const DEFAULT_STORE_WHATSAPP_CONFIG_DATA: Partial<StoreWhatsAppConfigDocument>;
230
- export interface StoreGoogleConfigDocument {
231
- id: string;
201
+ export interface StoreGoogleConfigDocument extends BaseDocument {
232
202
  storeId: string;
233
203
  isConnected: boolean;
234
204
  placeId?: string;
@@ -237,8 +207,6 @@ export interface StoreGoogleConfigDocument {
237
207
  totalReviews?: number;
238
208
  lastSyncedAt?: Date;
239
209
  oauthRefreshToken?: string;
240
- createdAt: Date;
241
- updatedAt: Date;
242
210
  }
243
211
  export declare const STORE_GOOGLE_CONFIG_COLLECTION: "storeGoogleConfig";
244
212
  export declare const STORE_GOOGLE_CONFIG_INDEXED_FIELDS: readonly ["storeId", "isConnected"];
@@ -0,0 +1,5 @@
1
+ export interface StoreClassifiedsListingProps {
2
+ storeId?: string;
3
+ initialData?: any;
4
+ }
5
+ export declare function StoreClassifiedsListing({ storeId, initialData }: StoreClassifiedsListingProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,48 @@
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 { usePendingTable } from "../../../react/hooks/usePendingTable";
6
+ import { useProducts } from "../../products/hooks/useProducts";
7
+ import { Div, FilterDrawer, ListingToolbar, Pagination, Row, Text } from "../../../ui";
8
+ import { ClassifiedFilters } from "../../classified/components/ClassifiedFilters";
9
+ import { InteractiveProductCard } from "../../products/components/InteractiveProductCard";
10
+ import { PRODUCT_FIELDS } from "../../../constants/field-names";
11
+ import { TABLE_KEYS } from "../../../constants/table-keys";
12
+ import { sortBy } from "../../../constants/sort";
13
+ import { ROUTES } from "../../../next";
14
+ const DEFAULT_SORT = sortBy(PRODUCT_FIELDS.CREATED_AT);
15
+ const SORT_OPTIONS = [
16
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT), label: "Newest First" },
17
+ { value: sortBy(PRODUCT_FIELDS.CREATED_AT, "ASC"), label: "Oldest First" },
18
+ { value: sortBy(PRODUCT_FIELDS.PRICE, "ASC"), label: "Price: Low to High" },
19
+ { value: sortBy(PRODUCT_FIELDS.PRICE), label: "Price: High to Low" },
20
+ ];
21
+ const FILTER_KEYS = [TABLE_KEYS.MIN_PRICE, TABLE_KEYS.MAX_PRICE, TABLE_KEYS.NEGOTIABLE, TABLE_KEYS.ACCEPTS_SHIPPING];
22
+ export function StoreClassifiedsListing({ storeId, initialData }) {
23
+ const table = useUrlTable({ defaults: { pageSize: "24", sort: DEFAULT_SORT } });
24
+ const [searchInput, setSearchInput] = useState(table.get("q") || "");
25
+ const [filterOpen, setFilterOpen] = useState(false);
26
+ const { pendingTable, filterActiveCount, onFilterApply, onFilterClear, onResetAll, onFilterReset } = usePendingTable(table, FILTER_KEYS);
27
+ const openFilters = useCallback(() => { onFilterReset(); setFilterOpen(true); }, [onFilterReset]);
28
+ const applyFilters = useCallback(() => { onFilterApply(); setFilterOpen(false); }, [onFilterApply]);
29
+ const resetAll = useCallback(() => { onResetAll({ q: "", sort: "" }); setSearchInput(""); }, [onResetAll]);
30
+ const hasActiveState = !!table.get("q") || table.get("sort") !== DEFAULT_SORT || filterActiveCount > 0;
31
+ const params = {
32
+ q: table.get("q") || undefined,
33
+ minPrice: table.get(TABLE_KEYS.MIN_PRICE) ? Number(table.get(TABLE_KEYS.MIN_PRICE)) : undefined,
34
+ maxPrice: table.get(TABLE_KEYS.MAX_PRICE) ? Number(table.get(TABLE_KEYS.MAX_PRICE)) : undefined,
35
+ sort: table.get("sort") || DEFAULT_SORT,
36
+ page: table.getNumber("page", 1),
37
+ perPage: table.getNumber("pageSize", 24),
38
+ storeId: storeId || undefined,
39
+ listingType: "classified",
40
+ };
41
+ const { products, totalPages, page, isLoading } = useProducts(params, { initialData });
42
+ const gridClass = "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4";
43
+ return (_jsxs(Div, { className: "min-h-[200px]", children: [_jsx(ListingToolbar, { filterCount: filterActiveCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search store classifieds\u2026", onSearchChange: setSearchInput, onSearchCommit: () => table.set("q", searchInput.trim()), sortValue: table.get("sort") || DEFAULT_SORT, sortOptions: SORT_OPTIONS, onSortChange: (v) => table.set("sort", v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(Row, { border: "bottom", className: "sticky top-[calc(var(--header-height,0px)+44px)] z-10 backdrop-blur-sm", surface: "default", padding: "toolbar", justify: "center", children: _jsx(Pagination, { currentPage: page, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) })), _jsx(Div, { padding: "y-lg", children: _jsx(Div, { className: gridClass, children: isLoading
44
+ ? Array.from({ length: 8 }).map((_, i) => _jsx(Div, { surface: "muted", rounded: "xl", className: "aspect-square animate-pulse" }, i))
45
+ : products.length === 0
46
+ ? _jsx(Div, { paddingY: "y-4xl", className: "col-span-full text-center", children: _jsx(Text, { color: "muted", children: "No classified listings in this store yet." }) })
47
+ : products.map((p) => (_jsx(InteractiveProductCard, { product: p, href: String(ROUTES.PUBLIC.CLASSIFIED_DETAIL(p.slug ?? p.id)) }, p.id))) }) }), _jsx(FilterDrawer, { open: filterOpen, onOpen: openFilters, onClose: () => setFilterOpen(false), onApply: applyFilters, onReset: onFilterClear, activeCount: filterActiveCount, hideTrigger: true, children: _jsx(ClassifiedFilters, { table: pendingTable, currencyPrefix: "\u20B9" }) })] }));
48
+ }
@@ -0,0 +1,4 @@
1
+ export interface StoreClassifiedsPageViewProps {
2
+ storeSlug: string;
3
+ }
4
+ export declare function StoreClassifiedsPageView({ storeSlug }: StoreClassifiedsPageViewProps): Promise<import("react/jsx-runtime").JSX.Element | null>;