@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.
Files changed (45) hide show
  1. package/dist/constants/api-endpoints.d.ts +6 -0
  2. package/dist/constants/api-endpoints.js +2 -0
  3. package/dist/features/admin/components/AdminSublistingCategoriesView.d.ts +1 -0
  4. package/dist/features/admin/components/AdminSublistingCategoriesView.js +56 -0
  5. package/dist/features/admin/components/AdminSublistingCategoryEditorView.d.ts +7 -0
  6. package/dist/features/admin/components/AdminSublistingCategoryEditorView.js +83 -0
  7. package/dist/features/admin/components/index.d.ts +3 -0
  8. package/dist/features/admin/components/index.js +2 -0
  9. package/dist/features/auctions/components/AuctionDetailPageView.js +9 -1
  10. package/dist/features/auctions/schemas/index.d.ts +2 -2
  11. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +9 -1
  12. package/dist/features/products/components/CustomFieldsEditor.d.ts +8 -0
  13. package/dist/features/products/components/CustomFieldsEditor.js +33 -0
  14. package/dist/features/products/components/CustomSectionTabContent.d.ts +4 -0
  15. package/dist/features/products/components/CustomSectionTabContent.js +13 -0
  16. package/dist/features/products/components/CustomSectionsEditor.d.ts +6 -0
  17. package/dist/features/products/components/CustomSectionsEditor.js +27 -0
  18. package/dist/features/products/components/ProductDetailPageView.js +9 -1
  19. package/dist/features/products/components/ProductForm.js +3 -2
  20. package/dist/features/products/components/ProductTabsShell.d.ts +8 -1
  21. package/dist/features/products/components/ProductTabsShell.js +19 -9
  22. package/dist/features/products/components/index.d.ts +6 -1
  23. package/dist/features/products/components/index.js +3 -0
  24. package/dist/features/products/repository/sublisting-categories.repository.d.ts +16 -0
  25. package/dist/features/products/repository/sublisting-categories.repository.js +126 -0
  26. package/dist/features/products/schemas/firestore.d.ts +20 -2
  27. package/dist/features/products/schemas/firestore.js +8 -0
  28. package/dist/features/products/schemas/index.d.ts +147 -56
  29. package/dist/features/products/schemas/index.js +24 -0
  30. package/dist/features/products/schemas/sublisting-categories.d.ts +45 -0
  31. package/dist/features/products/schemas/sublisting-categories.js +16 -0
  32. package/dist/features/products/types/index.d.ts +4 -0
  33. package/dist/features/search/schemas/index.d.ts +2 -2
  34. package/dist/index.d.ts +4 -2
  35. package/dist/index.js +4 -1
  36. package/dist/next/routing/route-map.d.ts +6 -0
  37. package/dist/next/routing/route-map.js +3 -0
  38. package/dist/repositories/index.d.ts +2 -0
  39. package/dist/repositories/index.js +1 -0
  40. package/dist/seed/sublisting-categories-seed-data.d.ts +5 -5
  41. package/dist/seed/sublisting-categories-seed-data.js +81 -237
  42. package/dist/styles.css +1 -0
  43. package/dist/tailwind-input.css +4 -0
  44. package/dist/tailwind-utilities.css +1 -0
  45. 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
- ingredients?: string[] | undefined;
238
- howToUse?: string[] | undefined;
239
- allowOffers?: boolean | undefined;
240
- minOfferPercent?: number | undefined;
241
- bulkDiscounts?: {
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
- shippingPaidBy?: "seller" | "buyer" | undefined;
264
- pickupAddressId?: string | undefined;
265
- insurance?: boolean | undefined;
266
- insuranceCost?: number | undefined;
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
- ingredients?: string[] | undefined;
328
- howToUse?: string[] | undefined;
329
- allowOffers?: boolean | undefined;
330
- minOfferPercent?: number | undefined;
331
- bulkDiscounts?: {
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
- shippingPaidBy?: "seller" | "buyer" | undefined;
354
- pickupAddressId?: string | undefined;
355
- insurance?: boolean | undefined;
356
- insuranceCost?: number | undefined;
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.