@mohasinac/appkit 4.71.2 โ†’ 4.73.0

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 (47) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/seo/sitemap.d.ts +58 -1
  3. package/dist/_internal/server/features/seo/sitemap.js +89 -18
  4. package/dist/constants/api-endpoints.d.ts +3 -0
  5. package/dist/constants/api-endpoints.js +6 -0
  6. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
  7. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
  8. package/dist/features/events/components/EntryVoteButtons.d.ts +19 -0
  9. package/dist/features/events/components/EntryVoteButtons.js +65 -0
  10. package/dist/features/events/repository/entry-votes.repository.d.ts +45 -0
  11. package/dist/features/events/repository/entry-votes.repository.js +154 -0
  12. package/dist/features/events/schemas/entry-votes.d.ts +134 -0
  13. package/dist/features/events/schemas/entry-votes.js +114 -0
  14. package/dist/features/events/schemas/index.d.ts +29 -7
  15. package/dist/features/events/schemas/index.js +29 -2
  16. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +1 -1
  17. package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
  18. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +3 -3
  19. package/dist/features/products/components/ClearanceDetailPageView.js +2 -2
  20. package/dist/features/products/components/index.d.ts +0 -2
  21. package/dist/features/products/components/index.js +0 -1
  22. package/dist/features/products/schemas/index.d.ts +6 -7
  23. package/dist/features/products/schemas/index.js +20 -2
  24. package/dist/features/promotions/schemas/firestore.d.ts +17 -1
  25. package/dist/features/promotions/schemas/firestore.js +22 -0
  26. package/dist/features/promotions/schemas/index.d.ts +45 -21
  27. package/dist/features/promotions/schemas/index.js +37 -8
  28. package/dist/features/promotions/types/index.d.ts +1 -1
  29. package/dist/features/reviews/schemas/index.d.ts +3 -0
  30. package/dist/features/reviews/schemas/index.js +25 -0
  31. package/dist/index.d.ts +0 -2
  32. package/dist/index.js +0 -3
  33. package/dist/next/routing/portals.d.ts +22 -0
  34. package/dist/next/routing/portals.js +43 -0
  35. package/dist/providers/db-firebase/admin.d.ts +2 -0
  36. package/dist/providers/db-firebase/admin.js +25 -2
  37. package/dist/schemas/registry.d.ts +11 -8
  38. package/dist/server.d.ts +2 -1
  39. package/dist/server.js +1 -0
  40. package/dist/styles.css +1 -1
  41. package/dist/tailwind-utilities.css +1 -1
  42. package/dist/ui/components/ListingDetailShell.d.ts +68 -0
  43. package/dist/ui/components/ListingDetailShell.js +32 -0
  44. package/package.json +533 -525
  45. package/scripts/seed-cli.mjs +32 -2
  46. package/dist/features/products/components/PreOrderDetailView.d.ts +0 -18
  47. package/dist/features/products/components/PreOrderDetailView.js +0 -12
@@ -1,7 +1,6 @@
1
1
  export * from "./firestore.js";
2
2
  import { z } from "zod";
3
3
  import { mediaFieldSchema } from "../../media/types/index.js";
4
- import { ALL_LISTING_TYPES } from "../../../_internal/shared/listing-types/feature-flags.js";
5
4
  import { CLEARANCE_DURATION_DAYS_MIN, CLEARANCE_DURATION_DAYS_MAX, } from "../../../_internal/shared/checkout/rules/_limits.js";
6
5
  // --- Sub-schemas --------------------------------------------------------------
7
6
  export const productImageSchema = z.object({
@@ -54,7 +53,26 @@ export const printMetaSchema = z.object({
54
53
  * Registered in `audit-listing-type-tab-coverage`'s STAYS_DERIVED so nobody
55
54
  * re-expands it into a literal for readability.
56
55
  */
57
- const LISTING_TYPE_ENUM = z.enum(ALL_LISTING_TYPES);
56
+ /*
57
+ * ๐Ÿ›‘ OPEN, not a closed enum. Owner, 2026-11-08: *"nothing in the code should
58
+ * be fixed โ€ฆ the types we have now are the DEFAULT TEMPLATES"*, and listings
59
+ * were named explicitly alongside events, coupons and reviews.
60
+ *
61
+ * `ALL_LISTING_TYPES` stays the shipped template list โ€” it still drives the
62
+ * admin picker, the seed and every default โ€” but a site that wants a listing
63
+ * kind we never imagined adds a config row rather than waiting for a release.
64
+ *
65
+ * The SHAPE is still enforced, because this value becomes a URL segment, a
66
+ * Firestore query value and an index key: lowercase, digits, hyphen/underscore
67
+ * separated, 2-40 characters.
68
+ */
69
+ const SLUG_RE = /^[a-z0-9]+(?:[-_][a-z0-9]+)*$/;
70
+ const LISTING_TYPE_ENUM = z
71
+ .string()
72
+ .trim()
73
+ .min(2)
74
+ .max(40)
75
+ .regex(SLUG_RE, "Use a lowercase slug, e.g. art-and-stickers");
58
76
  export const productItemSchema = z.object({
59
77
  id: z.string(),
60
78
  title: z.string(),
@@ -181,7 +181,23 @@ export declare const COUPON_FIELDS: {
181
181
  readonly SELLER: "seller";
182
182
  };
183
183
  };
184
- export declare const COUPON_TYPE_LABELS: Record<CouponType, string>;
184
+ /**
185
+ * Labels for the SHIPPED coupon types.
186
+ *
187
+ * ๐Ÿ›‘ Since `CouponType` opened (2026-11-08) this is no longer exhaustive โ€” a
188
+ * site-defined type has no entry here and a bare lookup yields `undefined`,
189
+ * which renders as an empty chip. Read it through `couponTypeLabel()`.
190
+ */
191
+ export declare const COUPON_TYPE_LABELS: Record<string, string>;
192
+ /**
193
+ * The label for a coupon type, with a readable fallback.
194
+ *
195
+ * An unknown type is a site-defined one, not an error: it renders its own slug
196
+ * de-slugged ("first_order" -> "First order") rather than blank. A coupon that
197
+ * shows no type at all is worse than one showing a plain name, and it must
198
+ * never throw on a checkout page.
199
+ */
200
+ export declare function couponTypeLabel(type: string): string;
185
201
  export declare const DEFAULT_COUPON_DATA: Partial<CouponDocument>;
186
202
  export declare const COUPONS_PUBLIC_FIELDS: readonly ["id", "code", "name", "description", "type", "discount", "validity.startDate", "validity.endDate", "usage.totalLimit", "usage.perUserLimit"];
187
203
  export declare const COUPONS_UPDATABLE_FIELDS: readonly ["name", "description", "discount", "bxgy", "tiers", "usage", "validity", "restrictions"];
@@ -55,6 +55,13 @@ export const COUPON_FIELDS = {
55
55
  SELLER: "seller",
56
56
  },
57
57
  };
58
+ /**
59
+ * Labels for the SHIPPED coupon types.
60
+ *
61
+ * ๐Ÿ›‘ Since `CouponType` opened (2026-11-08) this is no longer exhaustive โ€” a
62
+ * site-defined type has no entry here and a bare lookup yields `undefined`,
63
+ * which renders as an empty chip. Read it through `couponTypeLabel()`.
64
+ */
58
65
  export const COUPON_TYPE_LABELS = {
59
66
  percentage: "Percentage Discount",
60
67
  fixed: "Fixed Amount Discount",
@@ -64,6 +71,21 @@ export const COUPON_TYPE_LABELS = {
64
71
  // our word for it; "Points back" is what appears on their balance.
65
72
  points_earn: "Points Back",
66
73
  };
74
+ /**
75
+ * The label for a coupon type, with a readable fallback.
76
+ *
77
+ * An unknown type is a site-defined one, not an error: it renders its own slug
78
+ * de-slugged ("first_order" -> "First order") rather than blank. A coupon that
79
+ * shows no type at all is worse than one showing a plain name, and it must
80
+ * never throw on a checkout page.
81
+ */
82
+ export function couponTypeLabel(type) {
83
+ const known = COUPON_TYPE_LABELS[type];
84
+ if (known)
85
+ return known;
86
+ const words = type.replace(/[-_]+/g, " ").trim();
87
+ return words ? words.charAt(0).toUpperCase() + words.slice(1) : type;
88
+ }
67
89
  export const DEFAULT_COUPON_DATA = {
68
90
  usage: { currentUsage: 0 },
69
91
  validity: { isActive: true, startDate: new Date(), endDate: undefined },
@@ -1,6 +1,16 @@
1
1
  export * from "./firestore";
2
2
  import { z } from "zod";
3
- export declare const couponTypeEnumSchema: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
3
+ /**
4
+ * ๐Ÿ›‘ OPEN, not a closed enum โ€” the same change made to events and listings on
5
+ * 2026-11-08. The four below are the shipped DEFAULT TEMPLATES, kept as a named
6
+ * list so they stay discoverable in pickers and seeds.
7
+ *
8
+ * A site wanting a discount kind we do not ship โ€” tiered, bundle, first-order โ€”
9
+ * adds a coupon-type config row and a handler, rather than editing this file to
10
+ * ask permission.
11
+ */
12
+ export declare const DEFAULT_COUPON_TYPE_TEMPLATES: readonly ["percentage", "fixed", "free_shipping", "buy_x_get_y"];
13
+ export declare const couponTypeEnumSchema: z.ZodString;
4
14
  export declare const discountConfigSchema: z.ZodObject<{
5
15
  value: z.ZodNumber;
6
16
  maxDiscount: z.ZodOptional<z.ZodEffects<z.ZodNumber, number, number>>;
@@ -143,7 +153,7 @@ export declare const couponFirestoreSchema: z.ZodObject<{
143
153
  code: z.ZodString;
144
154
  name: z.ZodString;
145
155
  description: z.ZodString;
146
- type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
156
+ type: z.ZodString;
147
157
  scope: z.ZodEnum<["admin", "seller"]>;
148
158
  storeId: z.ZodOptional<z.ZodString>;
149
159
  applicableToCollectorDrops: z.ZodOptional<z.ZodBoolean>;
@@ -274,7 +284,7 @@ export declare const couponFirestoreSchema: z.ZodObject<{
274
284
  }, "strip", z.ZodTypeAny, {
275
285
  code: string;
276
286
  id: string;
277
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
287
+ type: string;
278
288
  description: string;
279
289
  createdAt: string | Date | z.objectOutputType<{
280
290
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
@@ -333,7 +343,7 @@ export declare const couponFirestoreSchema: z.ZodObject<{
333
343
  }, {
334
344
  code: string;
335
345
  id: string;
336
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
346
+ type: string;
337
347
  description: string;
338
348
  createdAt: string | Date | z.objectInputType<{
339
349
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
@@ -414,7 +424,7 @@ export declare const couponCreateBodySchema: z.ZodEffects<z.ZodObject<{
414
424
  code: z.ZodString;
415
425
  name: z.ZodString;
416
426
  description: z.ZodDefault<z.ZodString>;
417
- type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
427
+ type: z.ZodString;
418
428
  tiers: z.ZodOptional<z.ZodArray<z.ZodObject<{
419
429
  minAmount: z.ZodEffects<z.ZodNumber, number, number>;
420
430
  discountValue: z.ZodNumber;
@@ -508,7 +518,7 @@ export declare const couponCreateBodySchema: z.ZodEffects<z.ZodObject<{
508
518
  createdBy: z.ZodDefault<z.ZodOptional<z.ZodString>>;
509
519
  }, "strip", z.ZodTypeAny, {
510
520
  code: string;
511
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
521
+ type: string;
512
522
  description: string;
513
523
  createdBy: string;
514
524
  name: string;
@@ -548,7 +558,7 @@ export declare const couponCreateBodySchema: z.ZodEffects<z.ZodObject<{
548
558
  }[] | undefined;
549
559
  }, {
550
560
  code: string;
551
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
561
+ type: string;
552
562
  name: string;
553
563
  discount: {
554
564
  value: number;
@@ -588,7 +598,7 @@ export declare const couponCreateBodySchema: z.ZodEffects<z.ZodObject<{
588
598
  } | undefined;
589
599
  }>, {
590
600
  code: string;
591
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
601
+ type: string;
592
602
  description: string;
593
603
  createdBy: string;
594
604
  name: string;
@@ -628,7 +638,7 @@ export declare const couponCreateBodySchema: z.ZodEffects<z.ZodObject<{
628
638
  }[] | undefined;
629
639
  }, {
630
640
  code: string;
631
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
641
+ type: string;
632
642
  name: string;
633
643
  discount: {
634
644
  value: number;
@@ -892,7 +902,7 @@ export declare const claimedCouponSourceSchema: z.ZodEnum<["manual", "promo", "s
892
902
  export declare const claimedCouponSnapshotSchema: z.ZodObject<{
893
903
  name: z.ZodString;
894
904
  description: z.ZodOptional<z.ZodString>;
895
- type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
905
+ type: z.ZodString;
896
906
  scope: z.ZodEnum<["admin", "seller"]>;
897
907
  storeId: z.ZodOptional<z.ZodString>;
898
908
  discount: z.ZodObject<{
@@ -931,7 +941,7 @@ export declare const claimedCouponSnapshotSchema: z.ZodObject<{
931
941
  excludeCategories?: string[] | undefined;
932
942
  }>;
933
943
  }, "strip", z.ZodTypeAny, {
934
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
944
+ type: string;
935
945
  name: string;
936
946
  scope: "seller" | "admin";
937
947
  discount: {
@@ -950,7 +960,7 @@ export declare const claimedCouponSnapshotSchema: z.ZodObject<{
950
960
  description?: string | undefined;
951
961
  storeId?: string | undefined;
952
962
  }, {
953
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
963
+ type: string;
954
964
  name: string;
955
965
  scope: "seller" | "admin";
956
966
  discount: {
@@ -978,7 +988,7 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
978
988
  couponSnapshot: z.ZodObject<{
979
989
  name: z.ZodString;
980
990
  description: z.ZodOptional<z.ZodString>;
981
- type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
991
+ type: z.ZodString;
982
992
  scope: z.ZodEnum<["admin", "seller"]>;
983
993
  storeId: z.ZodOptional<z.ZodString>;
984
994
  discount: z.ZodObject<{
@@ -1017,7 +1027,7 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
1017
1027
  excludeCategories?: string[] | undefined;
1018
1028
  }>;
1019
1029
  }, "strip", z.ZodTypeAny, {
1020
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1030
+ type: string;
1021
1031
  name: string;
1022
1032
  scope: "seller" | "admin";
1023
1033
  discount: {
@@ -1036,7 +1046,7 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
1036
1046
  description?: string | undefined;
1037
1047
  storeId?: string | undefined;
1038
1048
  }, {
1039
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1049
+ type: string;
1040
1050
  name: string;
1041
1051
  scope: "seller" | "admin";
1042
1052
  discount: {
@@ -1099,7 +1109,7 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
1099
1109
  }, z.ZodTypeAny, "passthrough">;
1100
1110
  couponId: string;
1101
1111
  couponSnapshot: {
1102
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1112
+ type: string;
1103
1113
  name: string;
1104
1114
  scope: "seller" | "admin";
1105
1115
  discount: {
@@ -1139,7 +1149,7 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
1139
1149
  }, z.ZodTypeAny, "passthrough">;
1140
1150
  couponId: string;
1141
1151
  couponSnapshot: {
1142
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1152
+ type: string;
1143
1153
  name: string;
1144
1154
  scope: "seller" | "admin";
1145
1155
  discount: {
@@ -1169,7 +1179,21 @@ export declare const claimedCouponFirestoreSchema: z.ZodObject<{
1169
1179
  export type CouponFromSchema = z.infer<typeof couponFirestoreSchema>;
1170
1180
  export type CouponUsageFromSchema = z.infer<typeof couponUsageFirestoreSchema>;
1171
1181
  export type ClaimedCouponFromSchema = z.infer<typeof claimedCouponFirestoreSchema>;
1172
- export declare const couponTypeSchema: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
1182
+ /**
1183
+ * ๐Ÿ›‘ The SECOND coupon-type schema, and the 2026-11-08 pass MISSED it.
1184
+ *
1185
+ * `couponTypeEnumSchema` near the top of this file was opened that day;
1186
+ * this one โ€” same four values, different name, 290 lines further down โ€” stayed
1187
+ * closed, so coupons were still half-fixed. A re-measure on 11-09 found it.
1188
+ *
1189
+ * The lesson is the reason the acceptance test is a GREP and not a memory:
1190
+ * opening "the enum" is not the same as opening every enum, and two schemas
1191
+ * for one concept is exactly how a half-finished change hides.
1192
+ *
1193
+ * Both now accept a validated slug; the four values remain
1194
+ * `DEFAULT_COUPON_TYPE_TEMPLATES` above.
1195
+ */
1196
+ export declare const couponTypeSchema: z.ZodString;
1173
1197
  export declare const couponScopeSchema: z.ZodEnum<["admin", "seller"]>;
1174
1198
  /**
1175
1199
  * Base Zod schema for a coupon/promotion.
@@ -1188,7 +1212,7 @@ export declare const couponItemSchema: z.ZodObject<{
1188
1212
  code: z.ZodString;
1189
1213
  name: z.ZodString;
1190
1214
  description: z.ZodString;
1191
- type: z.ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
1215
+ type: z.ZodString;
1192
1216
  scope: z.ZodEnum<["admin", "seller"]>;
1193
1217
  discountValue: z.ZodNumber;
1194
1218
  minOrderAmount: z.ZodOptional<z.ZodNumber>;
@@ -1210,7 +1234,7 @@ export declare const couponItemSchema: z.ZodObject<{
1210
1234
  }, "strip", z.ZodTypeAny, {
1211
1235
  code: string;
1212
1236
  id: string;
1213
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1237
+ type: string;
1214
1238
  description: string;
1215
1239
  createdAt: string;
1216
1240
  updatedAt: string;
@@ -1234,7 +1258,7 @@ export declare const couponItemSchema: z.ZodObject<{
1234
1258
  }, {
1235
1259
  code: string;
1236
1260
  id: string;
1237
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1261
+ type: string;
1238
1262
  description: string;
1239
1263
  createdAt: string;
1240
1264
  updatedAt: string;
@@ -7,12 +7,27 @@ import { auditTimestampsShape, firestoreDateSchema, rupeesSchema } from "../../.
7
7
  // SCHEMAS.firestore.coupons
8
8
  // SCHEMAS.firestore.couponUsage
9
9
  // SCHEMAS.firestore.claimedCoupons
10
- export const couponTypeEnumSchema = z.enum([
10
+ /**
11
+ * ๐Ÿ›‘ OPEN, not a closed enum โ€” the same change made to events and listings on
12
+ * 2026-11-08. The four below are the shipped DEFAULT TEMPLATES, kept as a named
13
+ * list so they stay discoverable in pickers and seeds.
14
+ *
15
+ * A site wanting a discount kind we do not ship โ€” tiered, bundle, first-order โ€”
16
+ * adds a coupon-type config row and a handler, rather than editing this file to
17
+ * ask permission.
18
+ */
19
+ export const DEFAULT_COUPON_TYPE_TEMPLATES = [
11
20
  "percentage",
12
21
  "fixed",
13
22
  "free_shipping",
14
23
  "buy_x_get_y",
15
- ]);
24
+ ];
25
+ export const couponTypeEnumSchema = z
26
+ .string()
27
+ .trim()
28
+ .min(2)
29
+ .max(40)
30
+ .regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/, "Use a lowercase slug, e.g. first_order");
16
31
  export const discountConfigSchema = z.object({
17
32
  value: z.number(),
18
33
  maxDiscount: rupeesSchema.optional(),
@@ -263,12 +278,26 @@ export const claimedCouponFirestoreSchema = z.object({
263
278
  updatedAt: firestoreDateSchema,
264
279
  });
265
280
  // --- Sub-schemas --------------------------------------------------------------
266
- export const couponTypeSchema = z.enum([
267
- "percentage",
268
- "fixed",
269
- "free_shipping",
270
- "buy_x_get_y",
271
- ]);
281
+ /**
282
+ * ๐Ÿ›‘ The SECOND coupon-type schema, and the 2026-11-08 pass MISSED it.
283
+ *
284
+ * `couponTypeEnumSchema` near the top of this file was opened that day;
285
+ * this one โ€” same four values, different name, 290 lines further down โ€” stayed
286
+ * closed, so coupons were still half-fixed. A re-measure on 11-09 found it.
287
+ *
288
+ * The lesson is the reason the acceptance test is a GREP and not a memory:
289
+ * opening "the enum" is not the same as opening every enum, and two schemas
290
+ * for one concept is exactly how a half-finished change hides.
291
+ *
292
+ * Both now accept a validated slug; the four values remain
293
+ * `DEFAULT_COUPON_TYPE_TEMPLATES` above.
294
+ */
295
+ export const couponTypeSchema = z
296
+ .string()
297
+ .trim()
298
+ .min(2)
299
+ .max(40)
300
+ .regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/, "Use a lowercase slug, e.g. first_order");
272
301
  export const couponScopeSchema = z.enum(["admin", "seller"]);
273
302
  // --- Base item schema ---------------------------------------------------------
274
303
  /**
@@ -16,7 +16,7 @@ export type CouponType = "percentage" | "fixed" | "free_shipping" | "buy_x_get_y
16
16
  * `Record<CouponType, string>` โ€” that is the point of declaring the union
17
17
  * first.
18
18
  */
19
- | "points_earn";
19
+ | "points_earn" | (string & {});
20
20
  export type CouponScope = "admin" | "seller";
21
21
  export interface CouponItem {
22
22
  id: string;
@@ -38,6 +38,7 @@ export declare const reviewFirestoreSchema: z.ZodObject<{
38
38
  productId: z.ZodString;
39
39
  productTitle: z.ZodString;
40
40
  listingType: z.ZodOptional<z.ZodString>;
41
+ reviewType: z.ZodOptional<z.ZodString>;
41
42
  storeId: z.ZodOptional<z.ZodString>;
42
43
  storeName: z.ZodOptional<z.ZodString>;
43
44
  userId: z.ZodString;
@@ -147,6 +148,7 @@ export declare const reviewFirestoreSchema: z.ZodObject<{
147
148
  sellerRepliedAt?: string | Date | z.objectOutputType<{
148
149
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
149
150
  }, z.ZodTypeAny, "passthrough"> | undefined;
151
+ reviewType?: string | undefined;
150
152
  }, {
151
153
  title: string;
152
154
  id: string;
@@ -195,6 +197,7 @@ export declare const reviewFirestoreSchema: z.ZodObject<{
195
197
  sellerRepliedAt?: string | Date | z.objectInputType<{
196
198
  toDate: z.ZodFunction<z.ZodTuple<[], z.ZodUnknown>, z.ZodDate>;
197
199
  }, z.ZodTypeAny, "passthrough"> | undefined;
200
+ reviewType?: string | undefined;
198
201
  }>;
199
202
  export type ReviewFromSchema = z.infer<typeof reviewFirestoreSchema>;
200
203
  export declare const reviewStatusSchema: z.ZodEnum<["pending", "approved", "rejected"]>;
@@ -15,6 +15,31 @@ export const reviewFirestoreSchema = z.object({
15
15
  productId: z.string(),
16
16
  productTitle: z.string(),
17
17
  listingType: z.string().optional(),
18
+ /*
19
+ * The KIND of review โ€” the config dimension reviews did not have.
20
+ *
21
+ * Owner, 2026-11-08, naming reviews alongside events, listings and coupons:
22
+ * *"nothing in the code should be fixed โ€ฆ the types we have now are the
23
+ * default templates"*. Reviews had no type at all, so every review was
24
+ * implicitly a product review and a site wanting seller reviews, unboxing
25
+ * reviews or a verified-buyer-only kind had nowhere to put it.
26
+ *
27
+ * An open validated slug, resolved against the review-type config: which
28
+ * rules apply (media allowed, moderation required, who may write one) is a
29
+ * rule set on that row, not a branch in this file.
30
+ *
31
+ * ๐Ÿ›‘ `status` below is deliberately NOT opened. It is the moderation STATE
32
+ * MACHINE โ€” pending/approved/rejected is machinery, not a user choice, and an
33
+ * unknown state would mean a review that no queue can ever clear. Types are
34
+ * config; lifecycle states are code.
35
+ */
36
+ reviewType: z
37
+ .string()
38
+ .trim()
39
+ .min(2)
40
+ .max(40)
41
+ .regex(/^[a-z0-9]+(?:[-_][a-z0-9]+)*$/, "Use a lowercase slug, e.g. seller-review")
42
+ .optional(),
18
43
  storeId: z.string().optional(),
19
44
  storeName: z.string().optional(),
20
45
  userId: z.string(),
package/dist/index.d.ts CHANGED
@@ -2137,7 +2137,6 @@ export { PRODUCT_SORT_VALUES } from "./features/products/columns/index";
2137
2137
  export { PRODUCT_STATUS_OPTIONS } from "./features/products/components/ProductForm";
2138
2138
  export { PRODUCT_UPDATABLE_FIELDS } from "./features/products/schemas/firestore";
2139
2139
  export { PlaceBidForm } from "./features/products/components/PlaceBidForm";
2140
- export { PreOrderDetailView } from "./features/products/components/PreOrderDetailView";
2141
2140
  export type { ClearanceListingViewProps } from "./features/products/components/ClearanceListingView";
2142
2141
  export { ClearanceIndexListing } from "./features/products/components/ClearanceIndexListing";
2143
2142
  export type { ClearanceIndexListingProps } from "./features/products/components/ClearanceIndexListing";
@@ -2234,7 +2233,6 @@ export type { BidHistoryProps } from "./features/products/components/BidHistory"
2234
2233
  export type { InteractiveProductCardProps } from "./features/products/components/InteractiveProductCard";
2235
2234
  export type { ListingType } from "./features/products/types/index";
2236
2235
  export type { PlaceBidFormProps } from "./features/products/components/PlaceBidForm";
2237
- export type { PreOrderDetailViewProps } from "./features/products/components/PreOrderDetailView";
2238
2236
  export type { ProductAdminUpdateInput } from "./features/products/schemas/firestore";
2239
2237
  export type { ProductCondition } from "./features/products/types/index";
2240
2238
  export type { ProductCreateInput } from "./features/products/schemas/firestore";
package/dist/index.js CHANGED
@@ -3971,9 +3971,6 @@ export { PRODUCT_UPDATABLE_FIELDS } from "./features/products/schemas/firestore.
3971
3971
  // [CLIENT-SSR]-Runs in both SSR and browser รขโ‚ฌ" React component or hook that does not depend on browser-only APIs.
3972
3972
  // PlaceBidForm - Component for place bid form.
3973
3973
  export { PlaceBidForm } from "./features/products/components/PlaceBidForm.js";
3974
- // [CLIENT-SSR]-Runs in both SSR and browser รขโ‚ฌ" React component or hook that does not depend on browser-only APIs.
3975
- // PreOrderDetailView - Component for pre order detail view.
3976
- export { PreOrderDetailView } from "./features/products/components/PreOrderDetailView.js";
3977
3974
  // [CLIENT]-Client toolbar+grid for clearance (SB4-F).
3978
3975
  export { ClearanceIndexListing } from "./features/products/components/ClearanceIndexListing.js";
3979
3976
  // [CLIENT]-Marketplace card for clearance (SB4-F).
@@ -91,6 +91,28 @@ export interface PortalDefinition {
91
91
  * point โ€” it is data, editable without touching a mechanism.
92
92
  */
93
93
  export declare const PORTAL_DEFINITIONS: readonly PortalDefinition[];
94
+ /**
95
+ * Hosts that are their OWN DEPLOYMENT, not a portal of this app.
96
+ *
97
+ * ๐Ÿ›‘ A PORTAL and a DEPLOYMENT are not the same thing, and conflating them is
98
+ * how a subdomain ends up serving the wrong site:
99
+ *
100
+ * portal `admin.<domain>` โ†’ THIS app, rewritten to `/admin`
101
+ * deployment `events.<domain>` โ†’ a DIFFERENT app, with its own build
102
+ *
103
+ * A deployment host must never be rewritten to a section here, and must never
104
+ * be 301'd to the canonical host either. If one of these hostnames reaches
105
+ * this app at all, the DNS or the platform mapping is wrong โ€” redirecting it
106
+ * to www would hide that by silently serving the shop instead, which is
107
+ * exactly the failure CLAUDE.md records for `audit.` and `tools.` serving the
108
+ * main homepage as duplicate content.
109
+ *
110
+ * Listed as DATA for the same reason the portals are: adding `live.` or
111
+ * `media.` later is a row, not a code change.
112
+ */
113
+ export declare const DEPLOYMENT_LABELS: readonly string[];
114
+ /** Is this hostname a separate deployment rather than a portal of this app? */
115
+ export declare function isDeploymentHost(hostname: string): boolean;
94
116
  /** Every route section a portal serves. Derived; never hand-maintained. */
95
117
  export declare const PORTAL_SECTIONS: readonly string[];
96
118
  /** True for a dev/preview origin that no public-host rule should police. */
@@ -65,6 +65,42 @@ export const PORTAL_DEFINITIONS = [
65
65
  */
66
66
  { label: "auth", section: "/auth", roles: [], home: "/auth/login" },
67
67
  ];
68
+ /**
69
+ * Hosts that are their OWN DEPLOYMENT, not a portal of this app.
70
+ *
71
+ * ๐Ÿ›‘ A PORTAL and a DEPLOYMENT are not the same thing, and conflating them is
72
+ * how a subdomain ends up serving the wrong site:
73
+ *
74
+ * portal `admin.<domain>` โ†’ THIS app, rewritten to `/admin`
75
+ * deployment `events.<domain>` โ†’ a DIFFERENT app, with its own build
76
+ *
77
+ * A deployment host must never be rewritten to a section here, and must never
78
+ * be 301'd to the canonical host either. If one of these hostnames reaches
79
+ * this app at all, the DNS or the platform mapping is wrong โ€” redirecting it
80
+ * to www would hide that by silently serving the shop instead, which is
81
+ * exactly the failure CLAUDE.md records for `audit.` and `tools.` serving the
82
+ * main homepage as duplicate content.
83
+ *
84
+ * Listed as DATA for the same reason the portals are: adding `live.` or
85
+ * `media.` later is a row, not a code change.
86
+ */
87
+ export const DEPLOYMENT_LABELS = [
88
+ "auth",
89
+ "wiki",
90
+ "tools",
91
+ "audit",
92
+ "events",
93
+ "blogs",
94
+ "live",
95
+ "media",
96
+ "help",
97
+ "chat",
98
+ ];
99
+ /** Is this hostname a separate deployment rather than a portal of this app? */
100
+ export function isDeploymentHost(hostname) {
101
+ const firstLabel = normalizeHostname(hostname).split(".")[0] ?? "";
102
+ return DEPLOYMENT_LABELS.includes(firstLabel);
103
+ }
68
104
  /** Every route section a portal serves. Derived; never hand-maintained. */
69
105
  export const PORTAL_SECTIONS = PORTAL_DEFINITIONS.map((p) => p.section);
70
106
  /**
@@ -313,6 +349,13 @@ export function isOffCanonicalHost(hostname, canonicalHost) {
313
349
  return false;
314
350
  if (portalForHost(host))
315
351
  return false;
352
+ /*
353
+ * A deployment host is someone else's app. Redirecting it to the canonical
354
+ * shop would serve duplicate content under the wrong brand and mask a
355
+ * misrouted DNS record โ€” see `DEPLOYMENT_LABELS`.
356
+ */
357
+ if (isDeploymentHost(host))
358
+ return false;
316
359
  if (isNonPublicHost(host))
317
360
  return false;
318
361
  return true;
@@ -47,6 +47,8 @@ export declare function getAdminAuth(): Auth;
47
47
  * second database โ€” a second database is a split, not a copy.
48
48
  */
49
49
  export type DatabaseTarget = "shop" | "wiki";
50
+ /** Which database this process is actually talking to, for logs and guards. */
51
+ export declare function resolvedDatabaseId(target?: DatabaseTarget): string;
50
52
  export declare function getAdminDb(target?: DatabaseTarget): Firestore;
51
53
  /** The read-only wiki/tools database. Never used by the money path. */
52
54
  export declare function getWikiDb(): Firestore;
@@ -157,11 +157,34 @@ export function getAdminAuth() {
157
157
  set("__mohasinac_firebase_admin_auth__", auth);
158
158
  return auth;
159
159
  }
160
- /** `undefined` means the `(default)` database, which is the shop. */
160
+ /**
161
+ * `undefined` means the `(default)` database.
162
+ *
163
+ * ๐Ÿ›‘ THE SHOP TARGET IS ENV-DRIVEN, and that is the whole point of this table.
164
+ *
165
+ * Owner, 2026-11-08: seeding and local work must not touch the live data โ€”
166
+ * *"make the seeds be in the new db โ€ฆ and use that for local and testing while
167
+ * the live site still works on the original db"*.
168
+ *
169
+ * So production sets nothing and gets `(default)`; a developer or the seed CLI
170
+ * sets `FIREBASE_SHOP_DATABASE_ID=dev-db` in `.env.local` and every read and
171
+ * write in the process follows, because this is the ONE place a Firestore
172
+ * handle is constructed. There is no per-call database argument to forget.
173
+ *
174
+ * ๐Ÿ›‘ `.env.local` is gitignored, so an unset variable means PRODUCTION. That is
175
+ * deliberate โ€” the safe default for a deploy is the real database, and the
176
+ * dangerous direction (a developer silently writing to live) is the one that
177
+ * requires no action. Check this value before any destructive seed command;
178
+ * `seed-cli` prints the resolved database for exactly that reason.
179
+ */
161
180
  const DATABASE_IDS = {
162
- shop: undefined,
181
+ shop: process.env.FIREBASE_SHOP_DATABASE_ID || undefined,
163
182
  wiki: process.env.FIREBASE_WIKI_DATABASE_ID || "wiki-db",
164
183
  };
184
+ /** Which database this process is actually talking to, for logs and guards. */
185
+ export function resolvedDatabaseId(target = "shop") {
186
+ return DATABASE_IDS[target] ?? "(default)";
187
+ }
165
188
  /*
166
189
  * The global cache keys are a typed literal union, so these are spelled out
167
190
  * rather than built with a template โ€” a computed key would not type-check and,