@mohasinac/appkit 4.71.2 → 4.72.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 (43) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/constants/api-endpoints.d.ts +3 -0
  3. package/dist/constants/api-endpoints.js +6 -0
  4. package/dist/features/classified/components/ClassifiedDetailPageView.js +2 -2
  5. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +2 -2
  6. package/dist/features/events/components/EntryVoteButtons.d.ts +19 -0
  7. package/dist/features/events/components/EntryVoteButtons.js +65 -0
  8. package/dist/features/events/repository/entry-votes.repository.d.ts +45 -0
  9. package/dist/features/events/repository/entry-votes.repository.js +154 -0
  10. package/dist/features/events/schemas/entry-votes.d.ts +134 -0
  11. package/dist/features/events/schemas/entry-votes.js +114 -0
  12. package/dist/features/events/schemas/index.d.ts +29 -7
  13. package/dist/features/events/schemas/index.js +29 -2
  14. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +1 -1
  15. package/dist/features/live/components/LiveItemDetailPageView.js +2 -2
  16. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +3 -3
  17. package/dist/features/products/components/ClearanceDetailPageView.js +2 -2
  18. package/dist/features/products/components/index.d.ts +0 -2
  19. package/dist/features/products/components/index.js +0 -1
  20. package/dist/features/products/schemas/index.d.ts +6 -7
  21. package/dist/features/products/schemas/index.js +20 -2
  22. package/dist/features/promotions/schemas/firestore.d.ts +17 -1
  23. package/dist/features/promotions/schemas/firestore.js +22 -0
  24. package/dist/features/promotions/schemas/index.d.ts +45 -21
  25. package/dist/features/promotions/schemas/index.js +37 -8
  26. package/dist/features/promotions/types/index.d.ts +1 -1
  27. package/dist/features/reviews/schemas/index.d.ts +3 -0
  28. package/dist/features/reviews/schemas/index.js +25 -0
  29. package/dist/index.d.ts +0 -2
  30. package/dist/index.js +0 -3
  31. package/dist/next/routing/portals.d.ts +22 -0
  32. package/dist/next/routing/portals.js +43 -0
  33. package/dist/providers/db-firebase/admin.d.ts +2 -0
  34. package/dist/providers/db-firebase/admin.js +25 -2
  35. package/dist/schemas/registry.d.ts +11 -8
  36. package/dist/styles.css +1 -1
  37. package/dist/tailwind-utilities.css +1 -1
  38. package/dist/ui/components/ListingDetailShell.d.ts +68 -0
  39. package/dist/ui/components/ListingDetailShell.js +32 -0
  40. package/package.json +533 -525
  41. package/scripts/seed-cli.mjs +32 -2
  42. package/dist/features/products/components/PreOrderDetailView.d.ts +0 -18
  43. package/dist/features/products/components/PreOrderDetailView.js +0 -12
@@ -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,
@@ -1245,7 +1245,7 @@ export declare const SCHEMAS: {
1245
1245
  couponSnapshot: import("zod").ZodObject<{
1246
1246
  name: import("zod").ZodString;
1247
1247
  description: import("zod").ZodOptional<import("zod").ZodString>;
1248
- type: import("zod").ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
1248
+ type: import("zod").ZodString;
1249
1249
  scope: import("zod").ZodEnum<["admin", "seller"]>;
1250
1250
  storeId: import("zod").ZodOptional<import("zod").ZodString>;
1251
1251
  discount: import("zod").ZodObject<{
@@ -1284,7 +1284,7 @@ export declare const SCHEMAS: {
1284
1284
  excludeCategories?: string[] | undefined;
1285
1285
  }>;
1286
1286
  }, "strip", import("zod").ZodTypeAny, {
1287
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1287
+ type: string;
1288
1288
  name: string;
1289
1289
  scope: "seller" | "admin";
1290
1290
  discount: {
@@ -1303,7 +1303,7 @@ export declare const SCHEMAS: {
1303
1303
  description?: string | undefined;
1304
1304
  storeId?: string | undefined;
1305
1305
  }, {
1306
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1306
+ type: string;
1307
1307
  name: string;
1308
1308
  scope: "seller" | "admin";
1309
1309
  discount: {
@@ -1366,7 +1366,7 @@ export declare const SCHEMAS: {
1366
1366
  }, import("zod").ZodTypeAny, "passthrough">;
1367
1367
  couponId: string;
1368
1368
  couponSnapshot: {
1369
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1369
+ type: string;
1370
1370
  name: string;
1371
1371
  scope: "seller" | "admin";
1372
1372
  discount: {
@@ -1406,7 +1406,7 @@ export declare const SCHEMAS: {
1406
1406
  }, import("zod").ZodTypeAny, "passthrough">;
1407
1407
  couponId: string;
1408
1408
  couponSnapshot: {
1409
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1409
+ type: string;
1410
1410
  name: string;
1411
1411
  scope: "seller" | "admin";
1412
1412
  discount: {
@@ -1452,7 +1452,7 @@ export declare const SCHEMAS: {
1452
1452
  code: import("zod").ZodString;
1453
1453
  name: import("zod").ZodString;
1454
1454
  description: import("zod").ZodString;
1455
- type: import("zod").ZodEnum<["percentage", "fixed", "free_shipping", "buy_x_get_y"]>;
1455
+ type: import("zod").ZodString;
1456
1456
  scope: import("zod").ZodEnum<["admin", "seller"]>;
1457
1457
  storeId: import("zod").ZodOptional<import("zod").ZodString>;
1458
1458
  applicableToCollectorDrops: import("zod").ZodOptional<import("zod").ZodBoolean>;
@@ -1583,7 +1583,7 @@ export declare const SCHEMAS: {
1583
1583
  }, "strip", import("zod").ZodTypeAny, {
1584
1584
  code: string;
1585
1585
  id: string;
1586
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1586
+ type: string;
1587
1587
  description: string;
1588
1588
  createdAt: string | Date | import("zod").objectOutputType<{
1589
1589
  toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
@@ -1642,7 +1642,7 @@ export declare const SCHEMAS: {
1642
1642
  }, {
1643
1643
  code: string;
1644
1644
  id: string;
1645
- type: "fixed" | "percentage" | "free_shipping" | "buy_x_get_y";
1645
+ type: string;
1646
1646
  description: string;
1647
1647
  createdAt: string | Date | import("zod").objectInputType<{
1648
1648
  toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
@@ -2929,6 +2929,7 @@ export declare const SCHEMAS: {
2929
2929
  productId: import("zod").ZodString;
2930
2930
  productTitle: import("zod").ZodString;
2931
2931
  listingType: import("zod").ZodOptional<import("zod").ZodString>;
2932
+ reviewType: import("zod").ZodOptional<import("zod").ZodString>;
2932
2933
  storeId: import("zod").ZodOptional<import("zod").ZodString>;
2933
2934
  storeName: import("zod").ZodOptional<import("zod").ZodString>;
2934
2935
  userId: import("zod").ZodString;
@@ -3038,6 +3039,7 @@ export declare const SCHEMAS: {
3038
3039
  sellerRepliedAt?: string | Date | import("zod").objectOutputType<{
3039
3040
  toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
3040
3041
  }, import("zod").ZodTypeAny, "passthrough"> | undefined;
3042
+ reviewType?: string | undefined;
3041
3043
  }, {
3042
3044
  title: string;
3043
3045
  id: string;
@@ -3086,6 +3088,7 @@ export declare const SCHEMAS: {
3086
3088
  sellerRepliedAt?: string | Date | import("zod").objectInputType<{
3087
3089
  toDate: import("zod").ZodFunction<import("zod").ZodTuple<[], import("zod").ZodUnknown>, import("zod").ZodDate>;
3088
3090
  }, import("zod").ZodTypeAny, "passthrough"> | undefined;
3091
+ reviewType?: string | undefined;
3089
3092
  }>;
3090
3093
  readonly scammerProfiles: import("zod").ZodObject<{
3091
3094
  createdAt: import("zod").ZodUnion<[import("zod").ZodDate, import("zod").ZodString, import("zod").ZodObject<{