@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.
- package/dist/_internal/server/features/auctions/data.d.ts +2 -4
- package/dist/_internal/server/features/auctions/data.js +3 -9
- package/dist/_internal/server/features/classified/adapters.d.ts +2 -15
- package/dist/_internal/server/features/classified/adapters.js +2 -17
- package/dist/_internal/server/features/classified/data.d.ts +1 -6
- package/dist/_internal/server/features/classified/data.js +2 -10
- package/dist/_internal/server/features/digital-code/adapters.d.ts +2 -15
- package/dist/_internal/server/features/digital-code/adapters.js +2 -17
- package/dist/_internal/server/features/digital-code/data.d.ts +1 -6
- package/dist/_internal/server/features/digital-code/data.js +2 -10
- package/dist/_internal/server/features/live/adapters.d.ts +2 -15
- package/dist/_internal/server/features/live/adapters.js +2 -17
- package/dist/_internal/server/features/live/data.d.ts +1 -6
- package/dist/_internal/server/features/live/data.js +2 -10
- package/dist/_internal/server/features/pre-orders/data.d.ts +2 -4
- package/dist/_internal/server/features/pre-orders/data.js +3 -9
- package/dist/_internal/server/features/prize-draws/actions.d.ts +2 -0
- package/dist/_internal/server/features/prize-draws/actions.js +22 -0
- package/dist/_internal/server/features/prize-draws/data.d.ts +1 -17
- package/dist/_internal/server/features/prize-draws/data.js +2 -20
- package/dist/_internal/server/features/prize-draws/index.d.ts +3 -1
- package/dist/_internal/server/features/prize-draws/index.js +2 -0
- package/dist/_internal/server/features/prize-draws/service.d.ts +3 -0
- package/dist/_internal/server/features/prize-draws/service.js +19 -0
- package/dist/_internal/server/features/shared/listing-adapters.d.ts +19 -0
- package/dist/_internal/server/features/shared/listing-adapters.js +20 -0
- package/dist/_internal/server/features/shared/listing-data-factory.d.ts +9 -0
- package/dist/_internal/server/features/shared/listing-data-factory.js +33 -0
- package/dist/_internal/server/features/stores/data.d.ts +3 -14
- package/dist/_internal/server/features/stores/data.js +6 -44
- package/dist/_internal/shared/features/cart/schema.d.ts +8 -8
- package/dist/_internal/shared/features/cart/schema.js +3 -0
- package/dist/_internal/shared/listing-types/_registry.js +2 -0
- package/dist/_internal/shared/listing-types/auction/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/auction/schema.js +7 -1
- package/dist/_internal/shared/listing-types/classified/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/classified/schema.js +3 -1
- package/dist/_internal/shared/listing-types/digital-code/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/digital-code/schema.js +7 -1
- package/dist/_internal/shared/listing-types/live/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/live/schema.js +3 -1
- package/dist/_internal/shared/listing-types/pre-order/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/pre-order/schema.js +7 -1
- package/dist/_internal/shared/listing-types/prize-draw/schema.d.ts +3 -1
- package/dist/_internal/shared/listing-types/prize-draw/schema.js +13 -1
- package/dist/_internal/shared/listing-types/standard/schema.d.ts +2 -1
- package/dist/_internal/shared/listing-types/standard/schema.js +4 -1
- package/dist/_internal/shared/types/base-document.d.ts +15 -0
- package/dist/_internal/shared/types/base-document.js +1 -0
- package/dist/_internal/shared/types/index.d.ts +2 -0
- package/dist/_internal/shared/types/index.js +1 -0
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/constants/api-endpoints.d.ts +12 -0
- package/dist/constants/api-endpoints.js +4 -0
- package/dist/features/addresses/schemas/firestore.d.ts +2 -4
- package/dist/features/admin/components/AdminFulfillmentView.d.ts +6 -0
- package/dist/features/admin/components/AdminFulfillmentView.js +107 -0
- package/dist/features/admin/components/AdminProductEditorView.js +21 -9
- package/dist/features/admin/components/AdminProductsView.js +3 -0
- package/dist/features/admin/components/DataListingView.d.ts +1 -0
- package/dist/features/admin/components/index.d.ts +2 -0
- package/dist/features/admin/components/index.js +1 -0
- package/dist/features/admin/schemas/firestore.d.ts +3 -8
- package/dist/features/auctions/schemas/firestore.d.ts +2 -4
- package/dist/features/auctions/schemas/index.d.ts +2 -2
- package/dist/features/before-after/schemas/index.d.ts +2 -2
- package/dist/features/blog/schemas/firestore.d.ts +2 -4
- package/dist/features/blog/schemas/index.d.ts +4 -4
- package/dist/features/cart/schemas/firestore.d.ts +2 -4
- package/dist/features/categories/schemas/firestore.d.ts +2 -4
- package/dist/features/categories/schemas/index.d.ts +4 -4
- package/dist/features/events/schemas/firestore.d.ts +2 -4
- package/dist/features/events/schemas/index.d.ts +2 -2
- package/dist/features/faq/schemas/firestore.d.ts +2 -4
- package/dist/features/faq/schemas/index.d.ts +2 -2
- package/dist/features/grouped/schemas/index.d.ts +2 -2
- package/dist/features/homepage/schemas/firestore.d.ts +4 -12
- package/dist/features/homepage/schemas/index.d.ts +2 -2
- package/dist/features/messages/schemas/firestore.d.ts +2 -4
- package/dist/features/messages/schemas/index.d.ts +2 -2
- package/dist/features/orders/repository/orders.repository.d.ts +5 -0
- package/dist/features/orders/repository/orders.repository.js +26 -0
- package/dist/features/orders/schemas/firestore.d.ts +9 -5
- package/dist/features/orders/schemas/firestore.js +3 -0
- package/dist/features/payments/schemas/firestore.d.ts +2 -4
- package/dist/features/pre-orders/schemas/index.d.ts +2 -2
- package/dist/features/products/constants/action-defs.d.ts +16 -0
- package/dist/features/products/constants/action-defs.js +16 -0
- package/dist/features/products/constants/listing-tabs.d.ts +48 -0
- package/dist/features/products/constants/listing-tabs.js +12 -0
- package/dist/features/products/repository/products.repository.d.ts +37 -0
- package/dist/features/products/repository/products.repository.js +28 -1
- package/dist/features/products/schemas/firestore.d.ts +8 -6
- package/dist/features/products/schemas/firestore.js +2 -0
- package/dist/features/products/schemas/index.d.ts +6 -6
- package/dist/features/products/types/index.d.ts +2 -0
- package/dist/features/promotions/schemas/firestore.d.ts +2 -4
- package/dist/features/promotions/schemas/index.d.ts +4 -4
- package/dist/features/reviews/schemas/firestore.d.ts +2 -4
- package/dist/features/reviews/schemas/index.d.ts +2 -2
- package/dist/features/scams/schemas/firestore.d.ts +2 -4
- package/dist/features/scams/schemas/index.d.ts +4 -4
- package/dist/features/search/schemas/index.d.ts +2 -2
- package/dist/features/seller/components/BarcodeField.d.ts +17 -0
- package/dist/features/seller/components/BarcodeField.js +30 -0
- package/dist/features/seller/components/FulfillmentView.d.ts +5 -0
- package/dist/features/seller/components/FulfillmentView.js +120 -0
- package/dist/features/seller/components/PrintCenterView.d.ts +3 -1
- package/dist/features/seller/components/PrintCenterView.js +52 -4
- package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
- package/dist/features/seller/components/SellerProductShell.js +2 -1
- package/dist/features/seller/components/index.d.ts +4 -0
- package/dist/features/seller/components/index.js +2 -0
- package/dist/features/seller/schemas/index.d.ts +2 -2
- package/dist/features/store-extensions/schemas/firestore.d.ts +12 -44
- package/dist/features/stores/components/StoreClassifiedsListing.d.ts +5 -0
- package/dist/features/stores/components/StoreClassifiedsListing.js +48 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreClassifiedsPageView.js +20 -0
- package/dist/features/stores/components/StoreDetailLayoutView.js +23 -2
- package/dist/features/stores/components/StoreDigitalCodesListing.d.ts +5 -0
- package/dist/features/stores/components/StoreDigitalCodesListing.js +48 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreDigitalCodesPageView.js +20 -0
- package/dist/features/stores/components/StoreLiveItemsListing.d.ts +5 -0
- package/dist/features/stores/components/StoreLiveItemsListing.js +48 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.d.ts +4 -0
- package/dist/features/stores/components/StoreLiveItemsPageView.js +20 -0
- package/dist/features/stores/components/index.d.ts +9 -0
- package/dist/features/stores/components/index.js +3 -0
- package/dist/features/stores/schemas/firestore.d.ts +2 -4
- package/dist/features/stores/schemas/index.d.ts +4 -4
- package/dist/features/support/schemas/firestore.d.ts +2 -4
- package/dist/features/support/schemas/index.d.ts +2 -2
- package/dist/next/routing/route-map.d.ts +11 -0
- package/dist/next/routing/route-map.js +5 -0
- package/dist/schemas/registry.d.ts +24 -24
- package/dist/seed/grouped-listings-seed-data.d.ts +2 -1
- package/dist/seed/products-preorders-seed-data.d.ts +2 -1
- package/dist/seed/products-standard-seed-data.js +8 -0
- package/dist/server-entry.d.ts +3 -0
- package/dist/server-entry.js +3 -0
- package/dist/server.d.ts +4 -4
- package/dist/styles.css +1 -1
- package/dist/tailwind-utilities.css +1 -1
- package/dist/utils/id-generators.d.ts +8 -0
- package/dist/utils/id-generators.js +14 -0
- 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,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,
|
|
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 "
|
|
5
|
-
|
|
6
|
-
|
|
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,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
|
+
}
|