@mohasinac/appkit 2.4.3 → 2.4.4
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 +3 -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/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 +4 -0
- package/dist/features/search/schemas/index.d.ts +2 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -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
|
@@ -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
|
}>;
|
|
@@ -131,6 +131,30 @@ export const productItemSchema = z.object({
|
|
|
131
131
|
pickupAddressId: z.string().optional(),
|
|
132
132
|
insurance: z.boolean().optional(),
|
|
133
133
|
insuranceCost: z.number().optional(),
|
|
134
|
+
// Custom fields & sections
|
|
135
|
+
customFields: z
|
|
136
|
+
.array(z.object({
|
|
137
|
+
key: z.string(),
|
|
138
|
+
type: z.enum(["text", "number", "boolean", "url"]),
|
|
139
|
+
value: z.string(),
|
|
140
|
+
unit: z.string().optional(),
|
|
141
|
+
}))
|
|
142
|
+
.optional(),
|
|
143
|
+
customSections: z
|
|
144
|
+
.array(z.object({
|
|
145
|
+
id: z.string(),
|
|
146
|
+
title: z.string(),
|
|
147
|
+
text: z.string().optional(),
|
|
148
|
+
fields: z
|
|
149
|
+
.array(z.object({
|
|
150
|
+
key: z.string(),
|
|
151
|
+
type: z.enum(["text", "number", "boolean", "url"]),
|
|
152
|
+
value: z.string(),
|
|
153
|
+
unit: z.string().optional(),
|
|
154
|
+
}))
|
|
155
|
+
.optional(),
|
|
156
|
+
}))
|
|
157
|
+
.optional(),
|
|
134
158
|
});
|
|
135
159
|
/** Base Zod schema for list-query parameters. */
|
|
136
160
|
export const productListParamsSchema = z.object({
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-listing Categories — Firestore schema
|
|
3
|
+
*
|
|
4
|
+
* A sub-listing category is a named bucket that groups independent listings of the
|
|
5
|
+
* same real-world collectible across conditions, grades, prices, and sellers.
|
|
6
|
+
* Each listing stays a full product/auction/pre-order — the category is just the
|
|
7
|
+
* thread linking them (e.g. "Base Set Charizard 108/120").
|
|
8
|
+
*/
|
|
9
|
+
export declare const SUBLISTING_CATEGORIES_COLLECTION: "sublistingCategories";
|
|
10
|
+
export declare const SUBLISTING_CATEGORY_PREFIX: "sublisting-";
|
|
11
|
+
export interface SublistingCategoryDocument {
|
|
12
|
+
id: string;
|
|
13
|
+
slug: string;
|
|
14
|
+
name: string;
|
|
15
|
+
/** Card set code or grade label, e.g. "108/120", "PSA 10" */
|
|
16
|
+
itemCode?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
/** /media/<slug> proxy URL */
|
|
19
|
+
coverImage?: string;
|
|
20
|
+
/** Denormalised count — updated on member add/remove */
|
|
21
|
+
productCount: number;
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
24
|
+
createdBy: string;
|
|
25
|
+
}
|
|
26
|
+
export type SublistingCategoryCreateInput = Omit<SublistingCategoryDocument, "id" | "productCount" | "createdAt" | "updatedAt">;
|
|
27
|
+
export type SublistingCategoryUpdateInput = Partial<Pick<SublistingCategoryDocument, "name" | "itemCode" | "description" | "coverImage">>;
|
|
28
|
+
export declare const SUBLISTING_CATEGORY_SIEVE_FIELDS: {
|
|
29
|
+
readonly name: {
|
|
30
|
+
readonly canFilter: true;
|
|
31
|
+
readonly canSort: true;
|
|
32
|
+
};
|
|
33
|
+
readonly slug: {
|
|
34
|
+
readonly canFilter: true;
|
|
35
|
+
readonly canSort: false;
|
|
36
|
+
};
|
|
37
|
+
readonly productCount: {
|
|
38
|
+
readonly canFilter: false;
|
|
39
|
+
readonly canSort: true;
|
|
40
|
+
};
|
|
41
|
+
readonly createdAt: {
|
|
42
|
+
readonly canFilter: false;
|
|
43
|
+
readonly canSort: true;
|
|
44
|
+
};
|
|
45
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-listing Categories — Firestore schema
|
|
3
|
+
*
|
|
4
|
+
* A sub-listing category is a named bucket that groups independent listings of the
|
|
5
|
+
* same real-world collectible across conditions, grades, prices, and sellers.
|
|
6
|
+
* Each listing stays a full product/auction/pre-order — the category is just the
|
|
7
|
+
* thread linking them (e.g. "Base Set Charizard 108/120").
|
|
8
|
+
*/
|
|
9
|
+
export const SUBLISTING_CATEGORIES_COLLECTION = "sublistingCategories";
|
|
10
|
+
export const SUBLISTING_CATEGORY_PREFIX = "sublisting-";
|
|
11
|
+
export const SUBLISTING_CATEGORY_SIEVE_FIELDS = {
|
|
12
|
+
name: { canFilter: true, canSort: true },
|
|
13
|
+
slug: { canFilter: true, canSort: false },
|
|
14
|
+
productCount: { canFilter: false, canSort: true },
|
|
15
|
+
createdAt: { canFilter: false, canSort: true },
|
|
16
|
+
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { MediaField } from "../../media/types/index";
|
|
2
|
+
import type { CustomField, CustomSection } from "../schemas/firestore";
|
|
3
|
+
export type { CustomField, CustomSection } from "../schemas/firestore";
|
|
2
4
|
export type ProductStatus = "draft" | "published" | "archived" | "sold" | "out_of_stock" | "discontinued";
|
|
3
5
|
export type ProductCondition = "new" | "like_new" | "good" | "fair" | "poor" | "used" | "refurbished" | "broken";
|
|
4
6
|
export type ListingType = "fixed" | "standard" | "auction" | "pre-order";
|
|
@@ -103,6 +105,8 @@ export interface ProductItem {
|
|
|
103
105
|
pickupAddressId?: string;
|
|
104
106
|
insurance?: boolean;
|
|
105
107
|
insuranceCost?: number;
|
|
108
|
+
customFields?: CustomField[];
|
|
109
|
+
customSections?: CustomSection[];
|
|
106
110
|
}
|
|
107
111
|
export interface ProductListResponse {
|
|
108
112
|
items: ProductItem[];
|
|
@@ -29,8 +29,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
|
|
|
29
29
|
currency?: string | undefined;
|
|
30
30
|
featured?: boolean | undefined;
|
|
31
31
|
isPromoted?: boolean | undefined;
|
|
32
|
-
mainImage?: string | undefined;
|
|
33
32
|
isAuction?: boolean | undefined;
|
|
33
|
+
mainImage?: string | undefined;
|
|
34
34
|
}, {
|
|
35
35
|
id: string;
|
|
36
36
|
title: string;
|
|
@@ -40,8 +40,8 @@ export declare const searchProductItemSchema: z.ZodObject<{
|
|
|
40
40
|
currency?: string | undefined;
|
|
41
41
|
featured?: boolean | undefined;
|
|
42
42
|
isPromoted?: boolean | undefined;
|
|
43
|
-
mainImage?: string | undefined;
|
|
44
43
|
isAuction?: boolean | undefined;
|
|
44
|
+
mainImage?: string | undefined;
|
|
45
45
|
}>;
|
|
46
46
|
/**
|
|
47
47
|
* Form/query schema for a search request.
|