@mohasinac/appkit 2.4.3 → 2.4.5
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/constants/api-endpoints.d.ts +6 -0
- package/dist/constants/api-endpoints.js +2 -0
- package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +1 -0
- package/dist/features/admin/components/AdminSublistingCategoriesView.js +56 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.d.ts +7 -0
- package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +83 -0
- package/dist/features/admin/components/index.d.ts +3 -0
- package/dist/features/admin/components/index.js +2 -0
- package/dist/features/auctions/components/AuctionDetailPageView.js +9 -1
- package/dist/features/auctions/schemas/index.d.ts +2 -2
- package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
- package/dist/features/products/components/CustomFieldsEditor.d.ts +8 -0
- package/dist/features/products/components/CustomFieldsEditor.js +33 -0
- package/dist/features/products/components/CustomSectionTabContent.d.ts +4 -0
- package/dist/features/products/components/CustomSectionTabContent.js +13 -0
- package/dist/features/products/components/CustomSectionsEditor.d.ts +6 -0
- package/dist/features/products/components/CustomSectionsEditor.js +27 -0
- package/dist/features/products/components/ProductDetailPageView.js +9 -1
- package/dist/features/products/components/ProductForm.js +4 -2
- package/dist/features/products/components/ProductTabsShell.d.ts +8 -1
- package/dist/features/products/components/ProductTabsShell.js +19 -9
- package/dist/features/products/components/SublistingCarouselSection.d.ts +6 -0
- package/dist/features/products/components/SublistingCarouselSection.js +53 -0
- package/dist/features/products/components/SublistingCategorySelect.d.ts +7 -0
- package/dist/features/products/components/SublistingCategorySelect.js +40 -0
- package/dist/features/products/components/index.d.ts +6 -1
- package/dist/features/products/components/index.js +3 -0
- package/dist/features/products/repository/sublisting-categories.repository.d.ts +16 -0
- package/dist/features/products/repository/sublisting-categories.repository.js +126 -0
- package/dist/features/products/schemas/firestore.d.ts +20 -2
- package/dist/features/products/schemas/firestore.js +8 -0
- package/dist/features/products/schemas/index.d.ts +147 -56
- package/dist/features/products/schemas/index.js +24 -0
- package/dist/features/products/schemas/sublisting-categories.d.ts +45 -0
- package/dist/features/products/schemas/sublisting-categories.js +16 -0
- package/dist/features/products/types/index.d.ts +5 -0
- package/dist/features/search/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +8 -1
- package/dist/next/routing/route-map.d.ts +6 -0
- package/dist/next/routing/route-map.js +3 -0
- package/dist/repositories/index.d.ts +2 -0
- package/dist/repositories/index.js +1 -0
- package/dist/seed/sublisting-categories-seed-data.d.ts +5 -5
- package/dist/seed/sublisting-categories-seed-data.js +81 -237
- package/dist/styles.css +1 -0
- package/dist/tailwind-input.css +4 -0
- package/dist/tailwind-utilities.css +1 -0
- package/package.json +5 -4
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import Link from "next/link";
|
|
5
|
+
import { ROUTES } from "../../../next";
|
|
6
|
+
import { Row, Text } from "../../../ui";
|
|
7
|
+
import { formatCurrency } from "../../../utils/number.formatter";
|
|
8
|
+
function getHref(listing) {
|
|
9
|
+
const slug = listing.slug ?? listing.id;
|
|
10
|
+
if (listing.isAuction)
|
|
11
|
+
return String(ROUTES.PUBLIC.AUCTION_DETAIL(slug));
|
|
12
|
+
if (listing.isPreOrder)
|
|
13
|
+
return String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(slug));
|
|
14
|
+
return String(ROUTES.PUBLIC.PRODUCT_DETAIL(slug));
|
|
15
|
+
}
|
|
16
|
+
function ListingThumb({ listing, isCurrent, }) {
|
|
17
|
+
const image = listing.images?.[0] ?? listing.mainImage ?? "";
|
|
18
|
+
const href = getHref(listing);
|
|
19
|
+
const price = formatCurrency(listing.price, listing.currency ?? "INR");
|
|
20
|
+
return (_jsxs(Link, { href: href, "aria-label": listing.title, className: "flex flex-col items-center gap-1.5 flex-shrink-0 w-16 group", children: [_jsx("div", { className: `w-14 h-14 rounded-full overflow-hidden border-2 transition-all ${isCurrent
|
|
21
|
+
? "border-[var(--appkit-color-primary,#6366f1)] ring-2 ring-[var(--appkit-color-primary,#6366f1)]/30"
|
|
22
|
+
: "border-zinc-200 dark:border-zinc-700 group-hover:border-[var(--appkit-color-primary,#6366f1)]"}`, children: image ? (
|
|
23
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
24
|
+
_jsx("img", { src: image, alt: listing.title, className: "w-full h-full object-cover", loading: "lazy" })) : (_jsx("div", { className: "w-full h-full bg-zinc-100 dark:bg-zinc-800 flex items-center justify-center text-zinc-400 text-xs", children: "\u25EF" })) }), _jsx(Text, { className: "text-[10px] text-center text-zinc-600 dark:text-zinc-400 leading-tight line-clamp-2 w-full", children: listing.title }), _jsx(Text, { className: "text-[10px] font-semibold text-zinc-800 dark:text-zinc-200", children: price })] }));
|
|
25
|
+
}
|
|
26
|
+
export function SublistingCarouselSection({ sublistingCategoryId, currentListingId }) {
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [category, setCategory] = useState(null);
|
|
29
|
+
const [listings, setListings] = useState([]);
|
|
30
|
+
const [loading, setLoading] = useState(false);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!sublistingCategoryId)
|
|
33
|
+
return;
|
|
34
|
+
setLoading(true);
|
|
35
|
+
fetch(`/api/sublisting-categories/${encodeURIComponent(sublistingCategoryId)}`)
|
|
36
|
+
.then((r) => r.json())
|
|
37
|
+
.then((res) => {
|
|
38
|
+
setCategory(res.data?.category ?? null);
|
|
39
|
+
setListings(res.data?.listings ?? []);
|
|
40
|
+
})
|
|
41
|
+
.catch(() => { })
|
|
42
|
+
.finally(() => setLoading(false));
|
|
43
|
+
}, [sublistingCategoryId]);
|
|
44
|
+
if (!sublistingCategoryId || loading || (!category && !loading))
|
|
45
|
+
return null;
|
|
46
|
+
if (listings.length <= 1)
|
|
47
|
+
return null;
|
|
48
|
+
const others = listings.filter((l) => l.id !== currentListingId);
|
|
49
|
+
if (others.length === 0)
|
|
50
|
+
return null;
|
|
51
|
+
const label = category?.name ?? "More listings like this";
|
|
52
|
+
return (_jsxs("div", { className: "rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50/60 dark:bg-zinc-800/40 overflow-hidden", children: [_jsxs("button", { type: "button", onClick: () => setOpen((v) => !v), className: "w-full flex items-center justify-between px-4 py-3 text-left hover:bg-zinc-100/70 dark:hover:bg-zinc-800/70 transition-colors", "aria-expanded": open, children: [_jsxs(Row, { align: "center", gap: "xs", children: [_jsx("span", { className: "text-xs text-zinc-400 dark:text-zinc-500 mr-1", children: open ? "▼" : "▶" }), _jsxs(Text, { className: "text-sm font-medium text-zinc-800 dark:text-zinc-200", children: ["More listings like this:", " ", _jsx("span", { className: "text-[var(--appkit-color-primary,#6366f1)]", children: label })] }), _jsx("span", { className: "ml-1 rounded-full bg-zinc-200 dark:bg-zinc-700 px-2 py-0.5 text-xs text-zinc-600 dark:text-zinc-400", children: listings.length })] }), category && (_jsx(Link, { href: String(ROUTES.PUBLIC.SUBLISTING_CATEGORY(category.slug)), onClick: (e) => e.stopPropagation(), className: "text-xs text-[var(--appkit-color-primary,#6366f1)] hover:underline ml-3 flex-shrink-0", children: "View all \u2192" }))] }), open && (_jsx("div", { className: "px-4 pb-4 pt-1 overflow-x-auto", children: _jsx("div", { className: "flex gap-3 min-w-0", children: listings.map((listing) => (_jsx(ListingThumb, { listing: listing, isCurrent: listing.id === currentListingId }, listing.id))) }) }))] }));
|
|
53
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { Select, Stack, Text } from "../../../ui";
|
|
5
|
+
import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
|
|
6
|
+
import { apiClient } from "../../../http";
|
|
7
|
+
export function SublistingCategorySelect({ value, onChange, disabled }) {
|
|
8
|
+
const [options, setOptions] = useState([]);
|
|
9
|
+
const [loading, setLoading] = useState(true);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
let cancelled = false;
|
|
12
|
+
apiClient
|
|
13
|
+
.get(`${ADMIN_ENDPOINTS.SUBLISTING_CATEGORIES}?pageSize=200&sorts=name`)
|
|
14
|
+
.then((res) => {
|
|
15
|
+
if (cancelled)
|
|
16
|
+
return;
|
|
17
|
+
const items = res?.data?.items ?? [];
|
|
18
|
+
const opts = [
|
|
19
|
+
{ value: "", label: "— None —" },
|
|
20
|
+
...items.map((item) => ({
|
|
21
|
+
value: String(item.id ?? ""),
|
|
22
|
+
label: item.itemCode
|
|
23
|
+
? `${item.name} (${item.itemCode})`
|
|
24
|
+
: String(item.name ?? ""),
|
|
25
|
+
})),
|
|
26
|
+
];
|
|
27
|
+
setOptions(opts);
|
|
28
|
+
})
|
|
29
|
+
.catch(() => {
|
|
30
|
+
if (!cancelled)
|
|
31
|
+
setOptions([{ value: "", label: "— None —" }]);
|
|
32
|
+
})
|
|
33
|
+
.finally(() => {
|
|
34
|
+
if (!cancelled)
|
|
35
|
+
setLoading(false);
|
|
36
|
+
});
|
|
37
|
+
return () => { cancelled = true; };
|
|
38
|
+
}, []);
|
|
39
|
+
return (_jsxs(Stack, { gap: "xs", children: [_jsx(Text, { className: "text-sm font-medium text-zinc-900 dark:text-zinc-100", children: "Sub-listing category" }), _jsx(Select, { options: loading ? [{ value: "", label: "Loading…" }] : options, value: value, onChange: (e) => onChange(e.target.value), disabled: disabled || loading, "aria-label": "Sub-listing category" }), _jsx(Text, { className: "text-xs text-zinc-400 dark:text-zinc-500", children: "Groups this listing with others for the same collectible (e.g. \u201CBase Set Charizard 108/120\u201D)." })] }));
|
|
40
|
+
}
|
|
@@ -37,5 +37,10 @@ export type { RelatedProductsProps } from "./RelatedProducts";
|
|
|
37
37
|
export { ProductGalleryClient } from "./ProductGalleryClient";
|
|
38
38
|
export type { ProductGalleryClientProps } from "./ProductGalleryClient";
|
|
39
39
|
export { ProductTabsShell } from "./ProductTabsShell";
|
|
40
|
-
export type { ProductTabsShellProps } from "./ProductTabsShell";
|
|
40
|
+
export type { ProductTabsShellProps, CustomTabDef } from "./ProductTabsShell";
|
|
41
|
+
export { CustomFieldsEditor } from "./CustomFieldsEditor";
|
|
42
|
+
export type { CustomFieldsEditorProps } from "./CustomFieldsEditor";
|
|
43
|
+
export { CustomSectionsEditor } from "./CustomSectionsEditor";
|
|
44
|
+
export type { CustomSectionsEditorProps } from "./CustomSectionsEditor";
|
|
45
|
+
export { CustomSectionTabContent } from "./CustomSectionTabContent";
|
|
41
46
|
export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
|
|
@@ -19,4 +19,7 @@ export { MakeOfferButton } from "./MakeOfferButton";
|
|
|
19
19
|
export { RelatedProducts } from "./RelatedProducts";
|
|
20
20
|
export { ProductGalleryClient } from "./ProductGalleryClient";
|
|
21
21
|
export { ProductTabsShell } from "./ProductTabsShell";
|
|
22
|
+
export { CustomFieldsEditor } from "./CustomFieldsEditor";
|
|
23
|
+
export { CustomSectionsEditor } from "./CustomSectionsEditor";
|
|
24
|
+
export { CustomSectionTabContent } from "./CustomSectionTabContent";
|
|
22
25
|
export { RelatedProductsCarousel } from "./RelatedProductsCarousel";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseRepository, type FirebaseSieveFields, type FirebaseSieveResult, type SieveModel } from "../../../providers/db-firebase";
|
|
2
|
+
import { type SublistingCategoryCreateInput, type SublistingCategoryDocument, type SublistingCategoryUpdateInput } from "../schemas/sublisting-categories";
|
|
3
|
+
export declare class SublistingCategoriesRepository extends BaseRepository<SublistingCategoryDocument> {
|
|
4
|
+
static readonly SIEVE_FIELDS: FirebaseSieveFields;
|
|
5
|
+
constructor();
|
|
6
|
+
generateId(name: string): string;
|
|
7
|
+
list(model: SieveModel): Promise<FirebaseSieveResult<SublistingCategoryDocument>>;
|
|
8
|
+
findBySlug(slug: string): Promise<SublistingCategoryDocument | null>;
|
|
9
|
+
create(input: SublistingCategoryCreateInput): Promise<SublistingCategoryDocument>;
|
|
10
|
+
update(id: string, input: SublistingCategoryUpdateInput): Promise<SublistingCategoryDocument>;
|
|
11
|
+
delete(id: string): Promise<void>;
|
|
12
|
+
/** Returns products/auctions/pre-orders with matching sublistingCategoryId, ordered by price asc. */
|
|
13
|
+
getListingsByCategoryId(categoryId: string, limit?: number): Promise<Record<string, unknown>[]>;
|
|
14
|
+
incrementProductCount(id: string, delta: number): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export declare const sublistingCategoriesRepository: SublistingCategoriesRepository;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { DatabaseError } from "../../../errors";
|
|
2
|
+
import { BaseRepository, prepareForFirestore, } from "../../../providers/db-firebase";
|
|
3
|
+
import { SUBLISTING_CATEGORIES_COLLECTION, SUBLISTING_CATEGORY_PREFIX, } from "../schemas/sublisting-categories";
|
|
4
|
+
import { PRODUCT_COLLECTION } from "../schemas/firestore";
|
|
5
|
+
function slugify(name) {
|
|
6
|
+
return name
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.trim()
|
|
9
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
10
|
+
.replace(/^-+|-+$/g, "");
|
|
11
|
+
}
|
|
12
|
+
export class SublistingCategoriesRepository extends BaseRepository {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(SUBLISTING_CATEGORIES_COLLECTION);
|
|
15
|
+
}
|
|
16
|
+
generateId(name) {
|
|
17
|
+
const base = slugify(name);
|
|
18
|
+
return base.startsWith(SUBLISTING_CATEGORY_PREFIX)
|
|
19
|
+
? base
|
|
20
|
+
: `${SUBLISTING_CATEGORY_PREFIX}${base}`;
|
|
21
|
+
}
|
|
22
|
+
async list(model) {
|
|
23
|
+
return this.sieveQuery(model, SublistingCategoriesRepository.SIEVE_FIELDS);
|
|
24
|
+
}
|
|
25
|
+
async findBySlug(slug) {
|
|
26
|
+
try {
|
|
27
|
+
const snapshot = await this.db
|
|
28
|
+
.collection(this.collection)
|
|
29
|
+
.where("slug", "==", slug)
|
|
30
|
+
.limit(1)
|
|
31
|
+
.get();
|
|
32
|
+
if (snapshot.empty)
|
|
33
|
+
return null;
|
|
34
|
+
return this.mapDoc(snapshot.docs[0]);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
throw new DatabaseError(`Failed to find sublisting category by slug: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async create(input) {
|
|
41
|
+
try {
|
|
42
|
+
const id = this.generateId(input.name);
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const doc = {
|
|
45
|
+
...input,
|
|
46
|
+
slug: id,
|
|
47
|
+
productCount: 0,
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
};
|
|
51
|
+
await this.db
|
|
52
|
+
.collection(this.collection)
|
|
53
|
+
.doc(id)
|
|
54
|
+
.set(prepareForFirestore(doc));
|
|
55
|
+
return this.findByIdOrFail(id);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
throw new DatabaseError(`Failed to create sublisting category: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async update(id, input) {
|
|
62
|
+
try {
|
|
63
|
+
await this.db
|
|
64
|
+
.collection(this.collection)
|
|
65
|
+
.doc(id)
|
|
66
|
+
.update({ ...input, updatedAt: new Date() });
|
|
67
|
+
return this.findByIdOrFail(id);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
throw new DatabaseError(`Failed to update sublisting category: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async delete(id) {
|
|
74
|
+
try {
|
|
75
|
+
const batch = this.db.batch();
|
|
76
|
+
// Unlink all member products before deleting the category
|
|
77
|
+
const membersSnap = await this.db
|
|
78
|
+
.collection(PRODUCT_COLLECTION)
|
|
79
|
+
.where("sublistingCategoryId", "==", id)
|
|
80
|
+
.get();
|
|
81
|
+
for (const doc of membersSnap.docs) {
|
|
82
|
+
batch.update(doc.ref, { sublistingCategoryId: null, updatedAt: new Date() });
|
|
83
|
+
}
|
|
84
|
+
batch.delete(this.db.collection(this.collection).doc(id));
|
|
85
|
+
await batch.commit();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
throw new DatabaseError(`Failed to delete sublisting category: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** Returns products/auctions/pre-orders with matching sublistingCategoryId, ordered by price asc. */
|
|
92
|
+
async getListingsByCategoryId(categoryId, limit = 20) {
|
|
93
|
+
try {
|
|
94
|
+
const snap = await this.db
|
|
95
|
+
.collection(PRODUCT_COLLECTION)
|
|
96
|
+
.where("sublistingCategoryId", "==", categoryId)
|
|
97
|
+
.where("status", "==", "published")
|
|
98
|
+
.orderBy("price", "asc")
|
|
99
|
+
.limit(limit)
|
|
100
|
+
.get();
|
|
101
|
+
return snap.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
throw new DatabaseError(`Failed to get listings for category ${categoryId}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async incrementProductCount(id, delta) {
|
|
108
|
+
try {
|
|
109
|
+
const { increment } = await import("../../../contracts/field-ops");
|
|
110
|
+
await this.db
|
|
111
|
+
.collection(this.collection)
|
|
112
|
+
.doc(id)
|
|
113
|
+
.update({ productCount: increment(delta), updatedAt: new Date() });
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
// Fire-and-forget — count drift is acceptable; a nightly job can reconcile
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
SublistingCategoriesRepository.SIEVE_FIELDS = {
|
|
121
|
+
name: { canFilter: true, canSort: true },
|
|
122
|
+
slug: { canFilter: true, canSort: false },
|
|
123
|
+
productCount: { canFilter: false, canSort: true },
|
|
124
|
+
createdAt: { canFilter: false, canSort: true },
|
|
125
|
+
};
|
|
126
|
+
export const sublistingCategoriesRepository = new SublistingCategoriesRepository();
|
|
@@ -15,6 +15,21 @@ export interface ProductSpecification {
|
|
|
15
15
|
value: string;
|
|
16
16
|
unit?: string;
|
|
17
17
|
}
|
|
18
|
+
export type CustomFieldType = "text" | "number" | "boolean" | "url";
|
|
19
|
+
export interface CustomField {
|
|
20
|
+
key: string;
|
|
21
|
+
type: CustomFieldType;
|
|
22
|
+
value: string;
|
|
23
|
+
unit?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare const MAX_CUSTOM_FIELDS = 50;
|
|
26
|
+
export declare const MAX_CUSTOM_SECTIONS = 3;
|
|
27
|
+
export interface CustomSection {
|
|
28
|
+
id: string;
|
|
29
|
+
title: string;
|
|
30
|
+
text?: string;
|
|
31
|
+
fields?: CustomField[];
|
|
32
|
+
}
|
|
18
33
|
export interface ProductDocument {
|
|
19
34
|
id: string;
|
|
20
35
|
title: string;
|
|
@@ -84,6 +99,9 @@ export interface ProductDocument {
|
|
|
84
99
|
howToUse?: string[];
|
|
85
100
|
allowOffers?: boolean;
|
|
86
101
|
minOfferPercent?: number;
|
|
102
|
+
customFields?: CustomField[];
|
|
103
|
+
customSections?: CustomSection[];
|
|
104
|
+
sublistingCategoryId?: string;
|
|
87
105
|
createdAt: Date;
|
|
88
106
|
updatedAt: Date;
|
|
89
107
|
}
|
|
@@ -99,8 +117,8 @@ export declare const ProductStatusValues: {
|
|
|
99
117
|
export declare const PRODUCT_COLLECTION: "products";
|
|
100
118
|
export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "featured", "isAuction", "isPreOrder", "isPromoted", "isOnSale", "isSold", "createdAt"];
|
|
101
119
|
export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
|
|
102
|
-
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["id", "title", "description", "category", "subcategory", "brand", "price", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "isAuction", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "createdAt"];
|
|
103
|
-
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "subcategory", "brand", "price", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords"];
|
|
120
|
+
export declare const PRODUCT_PUBLIC_FIELDS: readonly ["id", "title", "description", "category", "subcategory", "brand", "price", "currency", "stockQuantity", "availableQuantity", "images", "status", "storeName", "featured", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "isAuction", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "createdAt"];
|
|
121
|
+
export declare const PRODUCT_UPDATABLE_FIELDS: readonly ["title", "description", "category", "subcategory", "brand", "price", "stockQuantity", "images", "status", "tags", "specifications", "features", "shippingInfo", "returnPolicy", "pickupAddressId", "condition", "insurance", "shippingPaidBy", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "reservePrice", "buyNowPrice", "minBidIncrement", "isPreOrder", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId"];
|
|
104
122
|
export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
|
|
105
123
|
export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
|
|
106
124
|
export type ProductAdminUpdateInput = Partial<Omit<ProductDocument, "id" | "createdAt">>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Products Firestore Document Types & Constants
|
|
3
3
|
*/
|
|
4
4
|
import { generateProductId, generateAuctionId, generatePreOrderId, } from "../../../utils/id-generators";
|
|
5
|
+
export const MAX_CUSTOM_FIELDS = 50;
|
|
6
|
+
export const MAX_CUSTOM_SECTIONS = 3;
|
|
5
7
|
/** Runtime-accessible product status values — use instead of bare string literals. */
|
|
6
8
|
export const ProductStatusValues = {
|
|
7
9
|
DRAFT: "draft",
|
|
@@ -94,6 +96,9 @@ export const PRODUCT_PUBLIC_FIELDS = [
|
|
|
94
96
|
"seoDescription",
|
|
95
97
|
"seoKeywords",
|
|
96
98
|
"viewCount",
|
|
99
|
+
"customFields",
|
|
100
|
+
"customSections",
|
|
101
|
+
"sublistingCategoryId",
|
|
97
102
|
"createdAt",
|
|
98
103
|
];
|
|
99
104
|
export const PRODUCT_UPDATABLE_FIELDS = [
|
|
@@ -133,6 +138,9 @@ export const PRODUCT_UPDATABLE_FIELDS = [
|
|
|
133
138
|
"seoTitle",
|
|
134
139
|
"seoDescription",
|
|
135
140
|
"seoKeywords",
|
|
141
|
+
"customFields",
|
|
142
|
+
"customSections",
|
|
143
|
+
"sublistingCategoryId",
|
|
136
144
|
];
|
|
137
145
|
export const productQueryHelpers = {
|
|
138
146
|
byStore: (storeId) => ["storeId", "==", storeId],
|
|
@@ -174,6 +174,63 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
174
174
|
pickupAddressId: z.ZodOptional<z.ZodString>;
|
|
175
175
|
insurance: z.ZodOptional<z.ZodBoolean>;
|
|
176
176
|
insuranceCost: z.ZodOptional<z.ZodNumber>;
|
|
177
|
+
customFields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
178
|
+
key: z.ZodString;
|
|
179
|
+
type: z.ZodEnum<["text", "number", "boolean", "url"]>;
|
|
180
|
+
value: z.ZodString;
|
|
181
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
182
|
+
}, "strip", z.ZodTypeAny, {
|
|
183
|
+
value: string;
|
|
184
|
+
type: "number" | "boolean" | "text" | "url";
|
|
185
|
+
key: string;
|
|
186
|
+
unit?: string | undefined;
|
|
187
|
+
}, {
|
|
188
|
+
value: string;
|
|
189
|
+
type: "number" | "boolean" | "text" | "url";
|
|
190
|
+
key: string;
|
|
191
|
+
unit?: string | undefined;
|
|
192
|
+
}>, "many">>;
|
|
193
|
+
customSections: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
194
|
+
id: z.ZodString;
|
|
195
|
+
title: z.ZodString;
|
|
196
|
+
text: z.ZodOptional<z.ZodString>;
|
|
197
|
+
fields: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
198
|
+
key: z.ZodString;
|
|
199
|
+
type: z.ZodEnum<["text", "number", "boolean", "url"]>;
|
|
200
|
+
value: z.ZodString;
|
|
201
|
+
unit: z.ZodOptional<z.ZodString>;
|
|
202
|
+
}, "strip", z.ZodTypeAny, {
|
|
203
|
+
value: string;
|
|
204
|
+
type: "number" | "boolean" | "text" | "url";
|
|
205
|
+
key: string;
|
|
206
|
+
unit?: string | undefined;
|
|
207
|
+
}, {
|
|
208
|
+
value: string;
|
|
209
|
+
type: "number" | "boolean" | "text" | "url";
|
|
210
|
+
key: string;
|
|
211
|
+
unit?: string | undefined;
|
|
212
|
+
}>, "many">>;
|
|
213
|
+
}, "strip", z.ZodTypeAny, {
|
|
214
|
+
id: string;
|
|
215
|
+
title: string;
|
|
216
|
+
text?: string | undefined;
|
|
217
|
+
fields?: {
|
|
218
|
+
value: string;
|
|
219
|
+
type: "number" | "boolean" | "text" | "url";
|
|
220
|
+
key: string;
|
|
221
|
+
unit?: string | undefined;
|
|
222
|
+
}[] | undefined;
|
|
223
|
+
}, {
|
|
224
|
+
id: string;
|
|
225
|
+
title: string;
|
|
226
|
+
text?: string | undefined;
|
|
227
|
+
fields?: {
|
|
228
|
+
value: string;
|
|
229
|
+
type: "number" | "boolean" | "text" | "url";
|
|
230
|
+
key: string;
|
|
231
|
+
unit?: string | undefined;
|
|
232
|
+
}[] | undefined;
|
|
233
|
+
}>, "many">>;
|
|
177
234
|
}, "strip", z.ZodTypeAny, {
|
|
178
235
|
id: string;
|
|
179
236
|
title: string;
|
|
@@ -209,24 +266,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
209
266
|
features?: string[] | undefined;
|
|
210
267
|
featured?: boolean | undefined;
|
|
211
268
|
isPromoted?: boolean | undefined;
|
|
212
|
-
originalPrice?: number | undefined;
|
|
213
|
-
mainImage?: string | undefined;
|
|
214
|
-
currentBid?: number | undefined;
|
|
215
|
-
availableQuantity?: number | undefined;
|
|
216
|
-
categorySlug?: string | undefined;
|
|
217
|
-
sellerAvatar?: string | undefined;
|
|
218
|
-
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
219
|
-
listingType?: "fixed" | "auction" | "standard" | "pre-order" | undefined;
|
|
220
269
|
isAuction?: boolean | undefined;
|
|
221
270
|
isPreOrder?: boolean | undefined;
|
|
222
|
-
inStock?: boolean | undefined;
|
|
223
|
-
stockCount?: number | undefined;
|
|
224
|
-
reviewCount?: number | undefined;
|
|
225
|
-
attributes?: Record<string, string> | undefined;
|
|
226
|
-
publishedAt?: string | undefined;
|
|
227
|
-
stockQuantity?: number | undefined;
|
|
228
271
|
subcategory?: string | undefined;
|
|
229
272
|
brand?: string | undefined;
|
|
273
|
+
stockQuantity?: number | undefined;
|
|
274
|
+
availableQuantity?: number | undefined;
|
|
275
|
+
mainImage?: string | undefined;
|
|
230
276
|
specifications?: {
|
|
231
277
|
value: string;
|
|
232
278
|
name: string;
|
|
@@ -234,17 +280,14 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
234
280
|
}[] | undefined;
|
|
235
281
|
shippingInfo?: string | undefined;
|
|
236
282
|
returnPolicy?: string | undefined;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
quantity: number;
|
|
243
|
-
discountPercent: number;
|
|
244
|
-
}[] | undefined;
|
|
283
|
+
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
284
|
+
insurance?: boolean | undefined;
|
|
285
|
+
insuranceCost?: number | undefined;
|
|
286
|
+
shippingPaidBy?: "seller" | "buyer" | undefined;
|
|
287
|
+
auctionEndDate?: string | Date | undefined;
|
|
245
288
|
startingBid?: number | undefined;
|
|
289
|
+
currentBid?: number | undefined;
|
|
246
290
|
bidCount?: number | undefined;
|
|
247
|
-
auctionEndDate?: string | Date | undefined;
|
|
248
291
|
reservePrice?: number | undefined;
|
|
249
292
|
buyNowPrice?: number | undefined;
|
|
250
293
|
minBidIncrement?: number | undefined;
|
|
@@ -258,12 +301,43 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
258
301
|
preOrderCurrentCount?: number | undefined;
|
|
259
302
|
preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
|
|
260
303
|
preOrderCancellable?: boolean | undefined;
|
|
304
|
+
pickupAddressId?: string | undefined;
|
|
261
305
|
viewCount?: number | undefined;
|
|
262
306
|
avgRating?: number | undefined;
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
307
|
+
reviewCount?: number | undefined;
|
|
308
|
+
bulkDiscounts?: {
|
|
309
|
+
quantity: number;
|
|
310
|
+
discountPercent: number;
|
|
311
|
+
}[] | undefined;
|
|
312
|
+
ingredients?: string[] | undefined;
|
|
313
|
+
howToUse?: string[] | undefined;
|
|
314
|
+
allowOffers?: boolean | undefined;
|
|
315
|
+
minOfferPercent?: number | undefined;
|
|
316
|
+
customFields?: {
|
|
317
|
+
value: string;
|
|
318
|
+
type: "number" | "boolean" | "text" | "url";
|
|
319
|
+
key: string;
|
|
320
|
+
unit?: string | undefined;
|
|
321
|
+
}[] | undefined;
|
|
322
|
+
customSections?: {
|
|
323
|
+
id: string;
|
|
324
|
+
title: string;
|
|
325
|
+
text?: string | undefined;
|
|
326
|
+
fields?: {
|
|
327
|
+
value: string;
|
|
328
|
+
type: "number" | "boolean" | "text" | "url";
|
|
329
|
+
key: string;
|
|
330
|
+
unit?: string | undefined;
|
|
331
|
+
}[] | undefined;
|
|
332
|
+
}[] | undefined;
|
|
333
|
+
originalPrice?: number | undefined;
|
|
334
|
+
categorySlug?: string | undefined;
|
|
335
|
+
sellerAvatar?: string | undefined;
|
|
336
|
+
listingType?: "fixed" | "auction" | "standard" | "pre-order" | undefined;
|
|
337
|
+
inStock?: boolean | undefined;
|
|
338
|
+
stockCount?: number | undefined;
|
|
339
|
+
attributes?: Record<string, string> | undefined;
|
|
340
|
+
publishedAt?: string | undefined;
|
|
267
341
|
}, {
|
|
268
342
|
id: string;
|
|
269
343
|
title: string;
|
|
@@ -299,24 +373,13 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
299
373
|
features?: string[] | undefined;
|
|
300
374
|
featured?: boolean | undefined;
|
|
301
375
|
isPromoted?: boolean | undefined;
|
|
302
|
-
originalPrice?: number | undefined;
|
|
303
|
-
mainImage?: string | undefined;
|
|
304
|
-
currentBid?: number | undefined;
|
|
305
|
-
availableQuantity?: number | undefined;
|
|
306
|
-
categorySlug?: string | undefined;
|
|
307
|
-
sellerAvatar?: string | undefined;
|
|
308
|
-
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
309
|
-
listingType?: "fixed" | "auction" | "standard" | "pre-order" | undefined;
|
|
310
376
|
isAuction?: boolean | undefined;
|
|
311
377
|
isPreOrder?: boolean | undefined;
|
|
312
|
-
inStock?: boolean | undefined;
|
|
313
|
-
stockCount?: number | undefined;
|
|
314
|
-
reviewCount?: number | undefined;
|
|
315
|
-
attributes?: Record<string, string> | undefined;
|
|
316
|
-
publishedAt?: string | undefined;
|
|
317
|
-
stockQuantity?: number | undefined;
|
|
318
378
|
subcategory?: string | undefined;
|
|
319
379
|
brand?: string | undefined;
|
|
380
|
+
stockQuantity?: number | undefined;
|
|
381
|
+
availableQuantity?: number | undefined;
|
|
382
|
+
mainImage?: string | undefined;
|
|
320
383
|
specifications?: {
|
|
321
384
|
value: string;
|
|
322
385
|
name: string;
|
|
@@ -324,17 +387,14 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
324
387
|
}[] | undefined;
|
|
325
388
|
shippingInfo?: string | undefined;
|
|
326
389
|
returnPolicy?: string | undefined;
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
quantity: number;
|
|
333
|
-
discountPercent: number;
|
|
334
|
-
}[] | undefined;
|
|
390
|
+
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
391
|
+
insurance?: boolean | undefined;
|
|
392
|
+
insuranceCost?: number | undefined;
|
|
393
|
+
shippingPaidBy?: "seller" | "buyer" | undefined;
|
|
394
|
+
auctionEndDate?: string | Date | undefined;
|
|
335
395
|
startingBid?: number | undefined;
|
|
396
|
+
currentBid?: number | undefined;
|
|
336
397
|
bidCount?: number | undefined;
|
|
337
|
-
auctionEndDate?: string | Date | undefined;
|
|
338
398
|
reservePrice?: number | undefined;
|
|
339
399
|
buyNowPrice?: number | undefined;
|
|
340
400
|
minBidIncrement?: number | undefined;
|
|
@@ -348,12 +408,43 @@ export declare const productItemSchema: z.ZodObject<{
|
|
|
348
408
|
preOrderCurrentCount?: number | undefined;
|
|
349
409
|
preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
|
|
350
410
|
preOrderCancellable?: boolean | undefined;
|
|
411
|
+
pickupAddressId?: string | undefined;
|
|
351
412
|
viewCount?: number | undefined;
|
|
352
413
|
avgRating?: number | undefined;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
414
|
+
reviewCount?: number | undefined;
|
|
415
|
+
bulkDiscounts?: {
|
|
416
|
+
quantity: number;
|
|
417
|
+
discountPercent: number;
|
|
418
|
+
}[] | undefined;
|
|
419
|
+
ingredients?: string[] | undefined;
|
|
420
|
+
howToUse?: string[] | undefined;
|
|
421
|
+
allowOffers?: boolean | undefined;
|
|
422
|
+
minOfferPercent?: number | undefined;
|
|
423
|
+
customFields?: {
|
|
424
|
+
value: string;
|
|
425
|
+
type: "number" | "boolean" | "text" | "url";
|
|
426
|
+
key: string;
|
|
427
|
+
unit?: string | undefined;
|
|
428
|
+
}[] | undefined;
|
|
429
|
+
customSections?: {
|
|
430
|
+
id: string;
|
|
431
|
+
title: string;
|
|
432
|
+
text?: string | undefined;
|
|
433
|
+
fields?: {
|
|
434
|
+
value: string;
|
|
435
|
+
type: "number" | "boolean" | "text" | "url";
|
|
436
|
+
key: string;
|
|
437
|
+
unit?: string | undefined;
|
|
438
|
+
}[] | undefined;
|
|
439
|
+
}[] | undefined;
|
|
440
|
+
originalPrice?: number | undefined;
|
|
441
|
+
categorySlug?: string | undefined;
|
|
442
|
+
sellerAvatar?: string | undefined;
|
|
443
|
+
listingType?: "fixed" | "auction" | "standard" | "pre-order" | undefined;
|
|
444
|
+
inStock?: boolean | undefined;
|
|
445
|
+
stockCount?: number | undefined;
|
|
446
|
+
attributes?: Record<string, string> | undefined;
|
|
447
|
+
publishedAt?: string | undefined;
|
|
357
448
|
}>;
|
|
358
449
|
/** Base Zod schema for list-query parameters. */
|
|
359
450
|
export declare const productListParamsSchema: z.ZodObject<{
|
|
@@ -381,8 +472,8 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
381
472
|
perPage?: number | undefined;
|
|
382
473
|
storeId?: string | undefined;
|
|
383
474
|
featured?: boolean | undefined;
|
|
384
|
-
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
385
475
|
isAuction?: boolean | undefined;
|
|
476
|
+
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
386
477
|
inStock?: boolean | undefined;
|
|
387
478
|
}, {
|
|
388
479
|
sort?: string | undefined;
|
|
@@ -395,7 +486,7 @@ export declare const productListParamsSchema: z.ZodObject<{
|
|
|
395
486
|
perPage?: number | undefined;
|
|
396
487
|
storeId?: string | undefined;
|
|
397
488
|
featured?: boolean | undefined;
|
|
398
|
-
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
399
489
|
isAuction?: boolean | undefined;
|
|
490
|
+
condition?: "new" | "used" | "refurbished" | "broken" | "like_new" | "good" | "fair" | "poor" | undefined;
|
|
400
491
|
inStock?: boolean | undefined;
|
|
401
492
|
}>;
|