@mohasinac/appkit 3.2.20 → 3.3.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 (190) hide show
  1. package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
  2. package/dist/_internal/server/features/checkout/actions.js +239 -190
  3. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  4. package/dist/_internal/server/functions/scheduled.js +9 -1
  5. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +8 -11
  6. package/dist/_internal/server/jobs/core/emiInstallmentReminder.d.ts +8 -0
  7. package/dist/_internal/server/jobs/core/emiInstallmentReminder.js +78 -0
  8. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.d.ts +2 -2
  9. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +15 -10
  10. package/dist/_internal/server/jobs/handlers/emiInstallmentReminder.d.ts +2 -0
  11. package/dist/_internal/server/jobs/handlers/emiInstallmentReminder.js +2 -0
  12. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  13. package/dist/_internal/server/jobs/handlers/index.js +2 -0
  14. package/dist/_internal/server/providers/index.d.ts +4 -4
  15. package/dist/_internal/server/providers/index.js +4 -4
  16. package/dist/_internal/server/providers/payment/razorpay-mock.d.ts +3 -2
  17. package/dist/_internal/server/providers/payment/razorpay-mock.js +3 -1
  18. package/dist/_internal/server/providers/resolve.d.ts +10 -13
  19. package/dist/_internal/server/providers/resolve.js +9 -15
  20. package/dist/_internal/shared/actions/action-registry.js +28 -0
  21. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -0
  22. package/dist/_internal/shared/checkout/rules/_registry.js +4 -0
  23. package/dist/_internal/shared/checkout/rules/art.rule.d.ts +9 -0
  24. package/dist/_internal/shared/checkout/rules/art.rule.js +5 -0
  25. package/dist/_internal/shared/checkout/rules/auction.rule.js +1 -0
  26. package/dist/_internal/shared/checkout/rules/classified.rule.js +1 -0
  27. package/dist/_internal/shared/checkout/rules/index.d.ts +2 -0
  28. package/dist/_internal/shared/checkout/rules/index.js +2 -0
  29. package/dist/_internal/shared/checkout/rules/stickers.rule.d.ts +9 -0
  30. package/dist/_internal/shared/checkout/rules/stickers.rule.js +5 -0
  31. package/dist/_internal/shared/checkout/rules/types.d.ts +6 -0
  32. package/dist/_internal/shared/features/checkout/config.d.ts +16 -1
  33. package/dist/_internal/shared/features/checkout/config.js +16 -1
  34. package/dist/_internal/shared/features/emi/schedule.d.ts +63 -0
  35. package/dist/_internal/shared/features/emi/schedule.js +62 -0
  36. package/dist/_internal/shared/fees/calculator.d.ts +53 -0
  37. package/dist/_internal/shared/fees/calculator.js +40 -0
  38. package/dist/_internal/shared/listing-types/_registry.d.ts +22 -66
  39. package/dist/_internal/shared/listing-types/_registry.js +19 -2
  40. package/dist/_internal/shared/listing-types/art/config.d.ts +15 -0
  41. package/dist/_internal/shared/listing-types/art/config.js +14 -0
  42. package/dist/_internal/shared/listing-types/art/ctas.d.ts +1 -0
  43. package/dist/_internal/shared/listing-types/art/ctas.js +3 -0
  44. package/dist/_internal/shared/listing-types/art/og.d.ts +1 -0
  45. package/dist/_internal/shared/listing-types/art/og.js +1 -0
  46. package/dist/_internal/shared/listing-types/art/schema.d.ts +2 -0
  47. package/dist/_internal/shared/listing-types/art/schema.js +3 -0
  48. package/dist/_internal/shared/listing-types/art/seed-factory.d.ts +1 -0
  49. package/dist/_internal/shared/listing-types/art/seed-factory.js +1 -0
  50. package/dist/_internal/shared/listing-types/auction/config.d.ts +8 -0
  51. package/dist/_internal/shared/listing-types/auction/config.js +6 -0
  52. package/dist/_internal/shared/listing-types/capabilities.js +17 -0
  53. package/dist/_internal/shared/listing-types/classified/config.d.ts +8 -0
  54. package/dist/_internal/shared/listing-types/classified/config.js +6 -0
  55. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +8 -0
  56. package/dist/_internal/shared/listing-types/digital-code/config.js +6 -0
  57. package/dist/_internal/shared/listing-types/live/config.d.ts +8 -0
  58. package/dist/_internal/shared/listing-types/live/config.js +6 -0
  59. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +8 -0
  60. package/dist/_internal/shared/listing-types/pre-order/config.js +6 -0
  61. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +8 -0
  62. package/dist/_internal/shared/listing-types/prize-draw/config.js +6 -0
  63. package/dist/_internal/shared/listing-types/standard/config.d.ts +8 -0
  64. package/dist/_internal/shared/listing-types/standard/config.js +6 -0
  65. package/dist/_internal/shared/listing-types/stickers/config.d.ts +15 -0
  66. package/dist/_internal/shared/listing-types/stickers/config.js +14 -0
  67. package/dist/_internal/shared/listing-types/stickers/ctas.d.ts +1 -0
  68. package/dist/_internal/shared/listing-types/stickers/ctas.js +3 -0
  69. package/dist/_internal/shared/listing-types/stickers/og.d.ts +1 -0
  70. package/dist/_internal/shared/listing-types/stickers/og.js +1 -0
  71. package/dist/_internal/shared/listing-types/stickers/schema.d.ts +2 -0
  72. package/dist/_internal/shared/listing-types/stickers/schema.js +3 -0
  73. package/dist/_internal/shared/listing-types/stickers/seed-factory.d.ts +1 -0
  74. package/dist/_internal/shared/listing-types/stickers/seed-factory.js +1 -0
  75. package/dist/client.d.ts +7 -2
  76. package/dist/client.js +4 -2
  77. package/dist/constants/field-names.d.ts +2 -6
  78. package/dist/constants/field-names.js +2 -6
  79. package/dist/contracts/index.d.ts +4 -2
  80. package/dist/contracts/index.js +6 -0
  81. package/dist/contracts/payment.d.ts +14 -9
  82. package/dist/contracts/payment.js +11 -1
  83. package/dist/contracts/shipping.d.ts +14 -9
  84. package/dist/contracts/shipping.js +11 -1
  85. package/dist/features/about/components/PublicProfileView.js +3 -6
  86. package/dist/features/admin/components/AdminAddressBookView.d.ts +11 -0
  87. package/dist/features/admin/components/AdminAddressBookView.js +30 -0
  88. package/dist/features/admin/components/AdminArtView.d.ts +3 -0
  89. package/dist/features/admin/components/AdminArtView.js +45 -0
  90. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  91. package/dist/features/admin/components/AdminSiteSettingsView.js +14 -9
  92. package/dist/features/admin/components/AdminStickersView.d.ts +3 -0
  93. package/dist/features/admin/components/AdminStickersView.js +45 -0
  94. package/dist/features/admin/components/index.d.ts +6 -0
  95. package/dist/features/admin/components/index.js +4 -0
  96. package/dist/features/admin/schemas/firestore.d.ts +19 -11
  97. package/dist/features/admin/schemas/firestore.js +12 -2
  98. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +3 -2
  99. package/dist/features/auth/hooks/useAuth.js +35 -9
  100. package/dist/features/auth/schemas/firestore.d.ts +2 -6
  101. package/dist/features/cart/schemas/firestore.d.ts +3 -2
  102. package/dist/features/consultation/schemas/index.d.ts +2 -2
  103. package/dist/features/loyalty/schemas/index.d.ts +6 -6
  104. package/dist/features/orders/repository/orders.repository.d.ts +20 -2
  105. package/dist/features/orders/repository/orders.repository.js +34 -5
  106. package/dist/features/orders/schemas/firestore.d.ts +33 -7
  107. package/dist/features/orders/schemas/firestore.js +1 -1
  108. package/dist/features/orders/schemas/index.d.ts +176 -31
  109. package/dist/features/orders/schemas/index.js +20 -6
  110. package/dist/features/orders/types/index.d.ts +3 -3
  111. package/dist/features/payments/repository/payout.repository.js +2 -2
  112. package/dist/features/payments/schemas/firestore.d.ts +3 -3
  113. package/dist/features/payments/schemas/firestore.js +2 -2
  114. package/dist/features/payments/schemas/index.d.ts +5 -5
  115. package/dist/features/payments/schemas/index.js +2 -2
  116. package/dist/features/products/api/[id]/route.js +4 -1
  117. package/dist/features/products/api/route.js +4 -1
  118. package/dist/features/products/components/CompareOverlay.js +7 -9
  119. package/dist/features/products/components/ProductDetailPageView.d.ts +3 -1
  120. package/dist/features/products/components/ProductDetailPageView.js +2 -2
  121. package/dist/features/products/components/ProductForm.js +18 -7
  122. package/dist/features/products/components/ProductGrid.js +7 -12
  123. package/dist/features/products/components/ShowGroupSection.js +3 -5
  124. package/dist/features/products/components/SublistingCarouselSection.js +3 -6
  125. package/dist/features/products/constants/listing-tabs.d.ts +32 -0
  126. package/dist/features/products/constants/listing-tabs.js +8 -0
  127. package/dist/features/products/index.d.ts +1 -1
  128. package/dist/features/products/index.js +3 -1
  129. package/dist/features/products/schemas/firestore.d.ts +20 -1
  130. package/dist/features/products/schemas/firestore.js +2 -0
  131. package/dist/features/products/schemas/index.d.ts +48 -0
  132. package/dist/features/products/schemas/index.js +9 -0
  133. package/dist/features/products/schemas/product-features.d.ts +1 -1
  134. package/dist/features/products/types/index.d.ts +5 -3
  135. package/dist/features/products/utils/listing-type.d.ts +6 -0
  136. package/dist/features/products/utils/listing-type.js +3 -0
  137. package/dist/features/reviews/api/[id]/route.js +5 -4
  138. package/dist/features/seller/actions/seller-actions.d.ts +29 -14
  139. package/dist/features/seller/actions/seller-actions.js +101 -27
  140. package/dist/features/seller/components/SellerArtView.d.ts +7 -0
  141. package/dist/features/seller/components/SellerArtView.js +114 -0
  142. package/dist/features/seller/components/SellerOrdersView.js +38 -1
  143. package/dist/features/seller/components/SellerProductShell.d.ts +5 -1
  144. package/dist/features/seller/components/SellerProductShell.js +69 -59
  145. package/dist/features/seller/components/SellerShippingView.js +6 -25
  146. package/dist/features/seller/components/SellerStickersView.d.ts +7 -0
  147. package/dist/features/seller/components/SellerStickersView.js +114 -0
  148. package/dist/features/seller/components/index.d.ts +4 -0
  149. package/dist/features/seller/components/index.js +2 -0
  150. package/dist/features/seller/messages/en.json +1 -1
  151. package/dist/features/seller/schemas/index.d.ts +7 -7
  152. package/dist/features/seller/schemas/index.js +1 -1
  153. package/dist/features/seller/types/index.d.ts +1 -1
  154. package/dist/features/stores/components/StoreDetailLayoutView.js +18 -2
  155. package/dist/features/stores/schemas/firestore.d.ts +7 -1
  156. package/dist/features/stores/schemas/index.d.ts +19 -16
  157. package/dist/features/stores/schemas/index.js +1 -1
  158. package/dist/index.d.ts +21 -36
  159. package/dist/index.js +27 -39
  160. package/dist/next/routing/route-map.d.ts +22 -0
  161. package/dist/next/routing/route-map.js +10 -0
  162. package/dist/providers/payment-manual/index.d.ts +40 -0
  163. package/dist/providers/payment-manual/index.js +91 -0
  164. package/dist/providers/payment-razorpay/index.d.ts +4 -2
  165. package/dist/providers/payment-razorpay/index.js +4 -1
  166. package/dist/providers/shipping-manual/index.d.ts +22 -0
  167. package/dist/providers/shipping-manual/index.js +47 -0
  168. package/dist/providers.d.ts +2 -2
  169. package/dist/providers.js +5 -3
  170. package/dist/schemas/registry.d.ts +129 -70
  171. package/dist/schemas/registry.js +0 -4
  172. package/dist/security/pii-encrypt.js +0 -7
  173. package/dist/security/pii-redact.js +0 -1
  174. package/dist/seed/conversations-seed-data.js +2 -2
  175. package/dist/seed/faq-seed-data.js +7 -7
  176. package/dist/seed/grouped-listings-seed-data.d.ts +2 -0
  177. package/dist/seed/index.d.ts +2 -0
  178. package/dist/seed/index.js +2 -0
  179. package/dist/seed/manifest.js +20 -16
  180. package/dist/seed/payouts-seed-data.js +5 -5
  181. package/dist/seed/products-art-seed-data.d.ts +11 -0
  182. package/dist/seed/products-art-seed-data.js +125 -0
  183. package/dist/seed/products-preorders-seed-data.d.ts +2 -0
  184. package/dist/seed/products-stickers-seed-data.d.ts +11 -0
  185. package/dist/seed/products-stickers-seed-data.js +125 -0
  186. package/dist/seed/site-settings-seed-data.js +13 -4
  187. package/dist/seed/stores-seed-data.js +11 -11
  188. package/dist/server.d.ts +7 -15
  189. package/dist/server.js +17 -42
  190. package/package.json +1 -1
@@ -12,8 +12,23 @@ import { useMediaUpload } from "../../media";
12
12
  import { resolveDate } from "../../../utils/date.formatter";
13
13
  import { normalizeRichTextHtml } from "../../../utils/string.formatter";
14
14
  import { useListingTypeFlags } from "../../../react/hooks/useListingTypeFlags";
15
- import { isAuctionListing, isPreOrderListing, isPrizeDrawListing, } from "../utils/listing-type";
15
+ import { isAuctionListing, isPreOrderListing, isPrizeDrawListing, normalizeListingType, } from "../utils/listing-type";
16
16
  import { PrizeDrawItemsEditor } from "./PrizeDrawItemsEditor";
17
+ // ListingType uses the canonical SB1-G spellings ("standard", "pre-order");
18
+ // ProductFeatureProductType predates it with legacy spellings for 2 values.
19
+ // Replaces the old 2-case ternary that silently mapped every other type
20
+ // (classified/digital-code/live/prize-draw/art/stickers) to "product".
21
+ const LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE = {
22
+ standard: "product",
23
+ auction: "auction",
24
+ "pre-order": "preorder",
25
+ "prize-draw": "prize-draw",
26
+ classified: "classified",
27
+ "digital-code": "digital-code",
28
+ live: "live",
29
+ art: "art",
30
+ stickers: "stickers",
31
+ };
17
32
  export const PRODUCT_STATUS_OPTIONS = [
18
33
  { value: "draft", label: "Draft" },
19
34
  { value: "published", label: "Published" },
@@ -122,7 +137,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
122
137
  .split(",")
123
138
  .map((tag) => tag.trim())
124
139
  .filter(Boolean),
125
- }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
140
+ }), disabled: isReadonly, placeholder: t("formTagsPlaceholder") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(Checkbox, { label: t("formFeatured"), checked: !!product.featured, onChange: (e) => update({ featured: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formIsPromoted"), checked: !!product.isPromoted, onChange: (e) => update({ isPromoted: e.target.checked }), disabled: isReadonly }), _jsx(Checkbox, { label: t("formAllowShipBeforeEmiComplete"), checked: !!product.allowShipBeforeEmiComplete, onChange: (e) => update({ allowShipBeforeEmiComplete: e.target.checked }), disabled: isReadonly })] }), _jsx(Heading, { level: 4, className: "mt-4", children: t("sectionConditionShipping") }), _jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "condition", label: t("formCondition"), type: "select", value: product.condition || "new", onChange: (value) => update({
126
141
  condition: value,
127
142
  }), disabled: isReadonly, options: [
128
143
  { value: "new", label: t("formConditionNew") },
@@ -199,11 +214,7 @@ export function ProductForm({ product, onChange, isReadonly = false, renderDescr
199
214
  value: product.groupId,
200
215
  onChange: (id) => update({ groupId: id || undefined }),
201
216
  disabled: isReadonly,
202
- }), !isAuctionListing(product) && renderGroupSettings?.(product), _jsx(ProductFeaturesSelector, { value: product.features ?? [], onChange: (features) => update({ features }), productType: (isAuctionListing(product)
203
- ? "auction"
204
- : isPreOrderListing(product)
205
- ? "preorder"
206
- : "product"), storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Custom Sections" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Assign a storage location for warehouse or shelf organisation. Used by the Print & Label Center." }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: "physicalLocation.zone", label: "Zone", type: "text", placeholder: "e.g. A", value: product.physicalLocation?.zone ?? "", onChange: (v) => update({
217
+ }), !isAuctionListing(product) && renderGroupSettings?.(product), _jsx(ProductFeaturesSelector, { value: product.features ?? [], onChange: (features) => update({ features }), productType: LISTING_TYPE_TO_FEATURE_PRODUCT_TYPE[normalizeListingType(product)], storeId: product.storeId, disabled: isReadonly }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Custom Sections" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Add up to 3 custom tabs to your product page \u2014 e.g. \"Box Contents\", \"Compatibility\", \"Grading Details\"." }), _jsx(CustomSectionsEditor, { sections: product.customSections ?? [], onChange: (sections) => update({ customSections: sections }) })] }), _jsx(FormField, { name: "shippingInfo", label: t("formShipping"), type: "textarea", value: product.shippingInfo || "", onChange: (value) => update({ shippingInfo: value }), disabled: isReadonly, placeholder: "Shipping information..." }), _jsx(FormField, { name: "returnPolicy", label: t("formReturnPolicy"), type: "textarea", value: product.returnPolicy || "", onChange: (value) => update({ returnPolicy: value }), disabled: isReadonly, placeholder: "Return policy details..." }), _jsxs(Div, { children: [_jsx(Heading, { level: 3, className: "mb-1", color: "muted", size: "sm", weight: "semibold", children: "Physical Inventory" }), _jsx(Text, { className: "mb-3", color: "muted", size: "xs", children: "Assign a storage location for warehouse or shelf organisation. Used by the Print & Label Center." }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { name: "physicalLocation.zone", label: "Zone", type: "text", placeholder: "e.g. A", value: product.physicalLocation?.zone ?? "", onChange: (v) => update({
207
218
  physicalLocation: {
208
219
  zone: v,
209
220
  shelf: product.physicalLocation?.shelf ?? "",
@@ -11,15 +11,14 @@ import { useLongPress } from "../../../react/hooks/useLongPress";
11
11
  import { FeatureBadgeList } from "./FeatureBadge";
12
12
  import { useProductFeatures } from "./ProductFeaturesContext";
13
13
  import { PRODUCT_FEATURE_CARD_MAX_VISIBLE } from "../constants/product-features.constants";
14
- import { isAuctionListing, isPreOrderListing } from "../utils/listing-type";
14
+ import { normalizeListingType } from "../utils/listing-type";
15
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
15
16
  const __P = {
16
17
  p3: "p-3",
17
18
  };
18
19
  const __O = {
19
20
  hidden: "overflow-hidden",
20
21
  };
21
- const CLS_BADGE_AUCTION = "bg-warning-surface text-white";
22
- const CLS_BADGE_PREORDER = "bg-violet-600 text-white";
23
22
  const CLS_BADGE_NEW = "rounded-full bg-error-surface px-2 py-0.5 text-[10px] text-white shadow-sm";
24
23
  const CLS_BADGE_SALE = "rounded-full bg-success-surface px-2 py-0.5 text-[10px] font-bold text-white shadow-sm";
25
24
  const CLS_BADGE_TRENDING = "rounded-full bg-indigo-600 px-2 py-0.5 text-[10px] font-bold text-white shadow-sm";
@@ -38,14 +37,10 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
38
37
  ? Math.round(((product.originalPrice - product.price) / product.originalPrice) *
39
38
  100)
40
39
  : null;
41
- // SB1-G canonical accessors that handle the legacy boolean fallback.
42
- const isAuction = isAuctionListing(product);
43
- const isPreOrder = isPreOrderListing(product);
44
- const typeBadge = isAuction
45
- ? { label: "Auction", cls: CLS_BADGE_AUCTION }
46
- : isPreOrder
47
- ? { label: "Pre-Order", cls: CLS_BADGE_PREORDER }
48
- : null;
40
+ const listingPlugin = pluginFor(normalizeListingType(product));
41
+ const typeBadge = listingPlugin.badge
42
+ ? { label: listingPlugin.badge.label, cls: listingPlugin.badge.className }
43
+ : null;
49
44
  const longPress = useLongPress(() => onSelect?.(product.id));
50
45
  const featuresList = useProductFeatures();
51
46
  const handleCardClick = selectionMode
@@ -97,7 +92,7 @@ export function ProductCard({ product, href, onClick, onAddToWishlist, isWishlis
97
92
  return seller ? (_jsxs(Text, { className: "mt-0.5 text-[11px]", color: "faint", children: ["by ", seller] })) : null;
98
93
  })(), product.rating !== undefined && (_jsxs(Row, { className: "mt-1", gap: "xs", children: [_jsx(Span, { className: CLS_STAR, children: "\u2605" }), _jsxs(Span, { className: "text-[11px]", color: "muted", children: [product.rating.toFixed(1), product.reviewCount ? ` (${product.reviewCount})` : ""] })] })), _jsxs(Div, { className: "mt-auto", padding: "t-xs", children: [_jsxs(Row, { align: "baseline", gap: "sm", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary dark:text-primary-400", children: formatCurrency(product.price, getDefaultCurrency()) }), product.originalPrice && product.originalPrice > product.price && (_jsx(Span, { size: "xs", className: "line-through", color: "faint", children: formatCurrency(product.originalPrice, getDefaultCurrency()) }))] }), (() => {
99
94
  const stock = product.stockCount ?? product.stockQuantity ?? product.availableQuantity;
100
- if (stock === undefined || isAuction)
95
+ if (stock === undefined || !listingPlugin.showsStockQuantity)
101
96
  return null;
102
97
  if (stock <= 0) {
103
98
  return (_jsx(Text, { className: CLS_DISCOUNT_TEXT, children: "Out of stock" }));
@@ -2,20 +2,18 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useEffect, useState } from "react";
4
4
  import Link from "next/link";
5
- import { ROUTES } from "../../../next";
6
5
  import { Div, Row, Span, Table, Thead, Tbody, Tr, Th, Td, Text, Modal, SideDrawer, Button } from "../../../ui";
7
6
  import { MediaImage } from "../../media/MediaImage";
8
7
  import { formatCurrency } from "../../../utils/number.formatter";
9
- import { isPreOrderListing } from "../utils/listing-type";
8
+ import { normalizeListingType } from "../utils/listing-type";
9
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
10
10
  const __O = {
11
11
  hidden: "overflow-hidden",
12
12
  xAuto: "overflow-x-auto",
13
13
  };
14
14
  function memberHref(m) {
15
15
  const slug = m.slug ?? m.id;
16
- if (isPreOrderListing(m))
17
- return String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(slug));
18
- return String(ROUTES.PUBLIC.PRODUCT_DETAIL(slug));
16
+ return pluginFor(normalizeListingType(m)).detailRoute(slug);
19
17
  }
20
18
  function MemberThumb({ member, isCurrent }) {
21
19
  const image = member.images?.[0] ?? "";
@@ -5,18 +5,15 @@ import Link from "next/link";
5
5
  import { ROUTES } from "../../../next";
6
6
  import { Div, Row, Span, Text } from "../../../ui";
7
7
  import { formatCurrency } from "../../../utils/number.formatter";
8
- import { isAuctionListing, isPreOrderListing } from "../utils/listing-type";
8
+ import { normalizeListingType } from "../utils/listing-type";
9
+ import { pluginFor } from "../../../_internal/shared/listing-types/_registry";
9
10
  const __O = {
10
11
  hidden: "overflow-hidden",
11
12
  xAuto: "overflow-x-auto",
12
13
  };
13
14
  function getHref(listing) {
14
15
  const slug = listing.slug ?? listing.id;
15
- if (isAuctionListing(listing))
16
- return String(ROUTES.PUBLIC.AUCTION_DETAIL(slug));
17
- if (isPreOrderListing(listing))
18
- return String(ROUTES.PUBLIC.PRE_ORDER_DETAIL(slug));
19
- return String(ROUTES.PUBLIC.PRODUCT_DETAIL(slug));
16
+ return pluginFor(normalizeListingType(listing)).detailRoute(slug);
20
17
  }
21
18
  function ListingThumb({ listing, isCurrent, }) {
22
19
  const image = listing.images?.[0] ?? listing.mainImage ?? "";
@@ -52,6 +52,14 @@ export declare const CATEGORY_PAGE_TABS: readonly [{
52
52
  readonly id: "live";
53
53
  readonly label: "Live Items";
54
54
  readonly listingType: "live";
55
+ }, {
56
+ readonly id: "art";
57
+ readonly label: "Art";
58
+ readonly listingType: "art";
59
+ }, {
60
+ readonly id: "stickers";
61
+ readonly label: "Stickers";
62
+ readonly listingType: "stickers";
55
63
  }, {
56
64
  readonly id: "stores";
57
65
  readonly label: "Stores";
@@ -91,6 +99,14 @@ export declare const STORE_PAGE_TABS: readonly [{
91
99
  readonly id: "live";
92
100
  readonly label: "Live Items";
93
101
  readonly listingType: "live";
102
+ }, {
103
+ readonly id: "art";
104
+ readonly label: "Art";
105
+ readonly listingType: "art";
106
+ }, {
107
+ readonly id: "stickers";
108
+ readonly label: "Stickers";
109
+ readonly listingType: "stickers";
94
110
  }];
95
111
  export type StoreTabId = (typeof STORE_PAGE_TABS)[number]["id"];
96
112
  /** Tabs shown on the seller-dashboard listings view + admin products list. */
@@ -125,6 +141,14 @@ export declare const SELLER_LISTING_TABS: readonly [{
125
141
  readonly id: "live";
126
142
  readonly label: "Live Items";
127
143
  readonly listingType: "live";
144
+ }, {
145
+ readonly id: "art";
146
+ readonly label: "Art";
147
+ readonly listingType: "art";
148
+ }, {
149
+ readonly id: "stickers";
150
+ readonly label: "Stickers";
151
+ readonly listingType: "stickers";
128
152
  }];
129
153
  export type SellerListingTabId = (typeof SELLER_LISTING_TABS)[number]["id"];
130
154
  /** Tabs shown on `/search` results. */
@@ -163,5 +187,13 @@ export declare const SEARCH_RESULT_TABS: readonly [{
163
187
  readonly id: "live";
164
188
  readonly label: "Live Items";
165
189
  readonly listingType: "live";
190
+ }, {
191
+ readonly id: "art";
192
+ readonly label: "Art";
193
+ readonly listingType: "art";
194
+ }, {
195
+ readonly id: "stickers";
196
+ readonly label: "Stickers";
197
+ readonly listingType: "stickers";
166
198
  }];
167
199
  export type SearchTabId = (typeof SEARCH_RESULT_TABS)[number]["id"];
@@ -18,6 +18,8 @@ export const CATEGORY_PAGE_TABS = [
18
18
  { id: "classifieds", label: "Classifieds", listingType: "classified" },
19
19
  { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
20
20
  { id: "live", label: "Live Items", listingType: "live" },
21
+ { id: "art", label: "Art", listingType: "art" },
22
+ { id: "stickers", label: "Stickers", listingType: "stickers" },
21
23
  { id: "stores", label: "Stores", entity: "stores" },
22
24
  ];
23
25
  /** Tabs shown on the public `/stores/[slug]` nav bar. */
@@ -30,6 +32,8 @@ export const STORE_PAGE_TABS = [
30
32
  { id: "classifieds", label: "Classifieds", listingType: "classified" },
31
33
  { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
32
34
  { id: "live", label: "Live Items", listingType: "live" },
35
+ { id: "art", label: "Art", listingType: "art" },
36
+ { id: "stickers", label: "Stickers", listingType: "stickers" },
33
37
  ];
34
38
  /** Tabs shown on the seller-dashboard listings view + admin products list. */
35
39
  export const SELLER_LISTING_TABS = [
@@ -41,6 +45,8 @@ export const SELLER_LISTING_TABS = [
41
45
  { id: "classifieds", label: "Classifieds", listingType: "classified" },
42
46
  { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
43
47
  { id: "live", label: "Live Items", listingType: "live" },
48
+ { id: "art", label: "Art", listingType: "art" },
49
+ { id: "stickers", label: "Stickers", listingType: "stickers" },
44
50
  ];
45
51
  /** Tabs shown on `/search` results. */
46
52
  export const SEARCH_RESULT_TABS = [
@@ -53,4 +59,6 @@ export const SEARCH_RESULT_TABS = [
53
59
  { id: "classifieds", label: "Classifieds", listingType: "classified" },
54
60
  { id: "digital-codes", label: "Digital Codes", listingType: "digital-code" },
55
61
  { id: "live", label: "Live Items", listingType: "live" },
62
+ { id: "art", label: "Art", listingType: "art" },
63
+ { id: "stickers", label: "Stickers", listingType: "stickers" },
56
64
  ];
@@ -9,5 +9,5 @@ export * from "./components";
9
9
  export * from "./schemas";
10
10
  export * from "./columns";
11
11
  export { manifest } from "./manifest";
12
- export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, } from "./utils/listing-type";
12
+ export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing, isClassifiedListing, isDigitalCodeListing, isLiveListing, isArtListing, isStickersListing, } from "./utils/listing-type";
13
13
  export { sanitizeProductForPublic, sanitizeProductsForPublic, } from "./utils/sanitize";
@@ -11,5 +11,7 @@ export * from "./columns";
11
11
  export { manifest } from "./manifest";
12
12
  export { normalizeListingType, isAuctionListing, isPreOrderListing, isStandardListing, isPrizeDrawListing,
13
13
  // SB-UNI-F 2026-05-13 — Phase 2 predicates.
14
- isClassifiedListing, isDigitalCodeListing, isLiveListing, } from "./utils/listing-type";
14
+ isClassifiedListing, isDigitalCodeListing, isLiveListing,
15
+ // Art/stickers session.
16
+ isArtListing, isStickersListing, } from "./utils/listing-type";
15
17
  export { sanitizeProductForPublic, sanitizeProductsForPublic, } from "./utils/sanitize";
@@ -105,6 +105,21 @@ export interface ProductLiveItemMeta {
105
105
  /** CITES permit number (Appendix I/II species). */
106
106
  cites?: string;
107
107
  }
108
+ /**
109
+ * Print-specific metadata shared by the "art" and "stickers" listing types
110
+ * (both are printed-only physical goods). Every field optional so partial
111
+ * seller input still saves.
112
+ */
113
+ export interface ProductPrintMeta {
114
+ /** e.g. "A4", "12x18 in", "5x5 cm sheet". */
115
+ size?: string;
116
+ /** e.g. "Matte photo paper", "Vinyl", "Canvas". */
117
+ material?: string;
118
+ /** e.g. "Glossy", "Matte", "Holographic". */
119
+ finish?: string;
120
+ /** Limited-edition print run size; omitted for open editions. */
121
+ editionSize?: number;
122
+ }
108
123
  export interface ProductDocument extends BaseDocument {
109
124
  title: string;
110
125
  description: string;
@@ -178,6 +193,10 @@ export interface ProductDocument extends BaseDocument {
178
193
  isPromoted?: boolean;
179
194
  isOnSale?: boolean;
180
195
  isSold?: boolean;
196
+ /** When true, an EMI order for this product ships as soon as it's confirmed instead of waiting for every installment to be paid. Default false. */
197
+ allowShipBeforeEmiComplete?: boolean;
198
+ /** Print-specific metadata for "art" / "stickers" listings — printed-only physical goods. Optional on every listing type; only populated for art/stickers. */
199
+ printMeta?: ProductPrintMeta;
181
200
  promotionEndDate?: Date;
182
201
  pickupAddressId?: string;
183
202
  viewCount?: number;
@@ -313,7 +332,7 @@ export declare const PRODUCT_COLLECTION: "products";
313
332
  export declare const PRODUCT_INDEXED_FIELDS: readonly ["storeId", "status", "category", "featured", "listingType", "isPromoted", "isOnSale", "isSold", "searchTokens", "createdAt", "barcodeId"];
314
333
  export declare const DEFAULT_PRODUCT_DATA: Partial<ProductDocument>;
315
334
  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", "listingType", "auctionEndDate", "startingBid", "currentBid", "bidCount", "reservePrice", "buyNowPrice", "minBidIncrement", "autoExtendable", "auctionExtensionMinutes", "auctionShippingPaidBy", "buyItNowPriceInPaise", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderCurrentCount", "preOrderProductionStatus", "preOrderCancellable", "condition", "insurance", "insuranceCost", "shippingPaidBy", "isPromoted", "isOnSale", "isSold", "slug", "seoTitle", "seoDescription", "seoKeywords", "viewCount", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "createdAt"];
316
- 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", "buyItNowPriceInPaise", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "barcodeId"];
335
+ 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", "buyItNowPriceInPaise", "bidsHaveStarted", "grading", "card", "classified", "digitalCode", "liveItem", "catalogProductId", "listingType", "preOrderDeliveryDate", "preOrderDepositPercent", "preOrderDepositAmount", "preOrderMaxQuantity", "preOrderProductionStatus", "preOrderCancellable", "isOnSale", "isSold", "allowShipBeforeEmiComplete", "printMeta", "seoTitle", "seoDescription", "seoKeywords", "customFields", "customSections", "sublistingCategoryId", "groupId", "isGroupParent", "groupParentSlug", "groupChildSlugs", "groupTitle", "barcodeId"];
317
336
  export type ProductCreateInput = Omit<ProductDocument, "id" | "createdAt" | "updatedAt" | "availableQuantity" | "bidCount" | "currentBid" | "auctionOriginalEndDate">;
318
337
  export type ProductUpdateInput = Partial<Pick<ProductDocument, (typeof PRODUCT_UPDATABLE_FIELDS)[number]>>;
319
338
  export type ProductAdminUpdateInput = Partial<Omit<ProductDocument, "id" | "createdAt">>;
@@ -163,6 +163,8 @@ export const PRODUCT_UPDATABLE_FIELDS = [
163
163
  "preOrderCancellable",
164
164
  "isOnSale",
165
165
  "isSold",
166
+ "allowShipBeforeEmiComplete",
167
+ "printMeta",
166
168
  "seoTitle",
167
169
  "seoDescription",
168
170
  "seoKeywords",
@@ -29,6 +29,23 @@ export declare const productSeoSchema: z.ZodObject<{
29
29
  description?: string | undefined;
30
30
  keywords?: string[] | undefined;
31
31
  }>;
32
+ /** Print-specific metadata for "art" / "stickers" listings. */
33
+ export declare const printMetaSchema: z.ZodObject<{
34
+ size: z.ZodOptional<z.ZodString>;
35
+ material: z.ZodOptional<z.ZodString>;
36
+ finish: z.ZodOptional<z.ZodString>;
37
+ editionSize: z.ZodOptional<z.ZodNumber>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ size?: string | undefined;
40
+ material?: string | undefined;
41
+ finish?: string | undefined;
42
+ editionSize?: number | undefined;
43
+ }, {
44
+ size?: string | undefined;
45
+ material?: string | undefined;
46
+ finish?: string | undefined;
47
+ editionSize?: number | undefined;
48
+ }>;
32
49
  /**
33
50
  * Base Zod schema for a product item.
34
51
  * Apps can extend this to add their own fields:
@@ -86,6 +103,23 @@ export declare const productItemSchema: z.ZodObject<{
86
103
  }>>;
87
104
  featured: z.ZodOptional<z.ZodBoolean>;
88
105
  isPromoted: z.ZodOptional<z.ZodBoolean>;
106
+ allowShipBeforeEmiComplete: z.ZodOptional<z.ZodBoolean>;
107
+ printMeta: z.ZodOptional<z.ZodObject<{
108
+ size: z.ZodOptional<z.ZodString>;
109
+ material: z.ZodOptional<z.ZodString>;
110
+ finish: z.ZodOptional<z.ZodString>;
111
+ editionSize: z.ZodOptional<z.ZodNumber>;
112
+ }, "strip", z.ZodTypeAny, {
113
+ size?: string | undefined;
114
+ material?: string | undefined;
115
+ finish?: string | undefined;
116
+ editionSize?: number | undefined;
117
+ }, {
118
+ size?: string | undefined;
119
+ material?: string | undefined;
120
+ finish?: string | undefined;
121
+ editionSize?: number | undefined;
122
+ }>>;
89
123
  currentBid: z.ZodOptional<z.ZodNumber>;
90
124
  availableQuantity: z.ZodOptional<z.ZodNumber>;
91
125
  category: z.ZodOptional<z.ZodString>;
@@ -337,6 +371,13 @@ export declare const productItemSchema: z.ZodObject<{
337
371
  preOrderCurrentCount?: number | undefined;
338
372
  preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
339
373
  preOrderCancellable?: boolean | undefined;
374
+ allowShipBeforeEmiComplete?: boolean | undefined;
375
+ printMeta?: {
376
+ size?: string | undefined;
377
+ material?: string | undefined;
378
+ finish?: string | undefined;
379
+ editionSize?: number | undefined;
380
+ } | undefined;
340
381
  pickupAddressId?: string | undefined;
341
382
  viewCount?: number | undefined;
342
383
  avgRating?: number | undefined;
@@ -466,6 +507,13 @@ export declare const productItemSchema: z.ZodObject<{
466
507
  preOrderCurrentCount?: number | undefined;
467
508
  preOrderProductionStatus?: "upcoming" | "in_production" | "ready_to_ship" | undefined;
468
509
  preOrderCancellable?: boolean | undefined;
510
+ allowShipBeforeEmiComplete?: boolean | undefined;
511
+ printMeta?: {
512
+ size?: string | undefined;
513
+ material?: string | undefined;
514
+ finish?: string | undefined;
515
+ editionSize?: number | undefined;
516
+ } | undefined;
469
517
  pickupAddressId?: string | undefined;
470
518
  viewCount?: number | undefined;
471
519
  avgRating?: number | undefined;
@@ -13,6 +13,13 @@ export const productSeoSchema = z.object({
13
13
  description: z.string().optional(),
14
14
  keywords: z.array(z.string()).optional(),
15
15
  });
16
+ /** Print-specific metadata for "art" / "stickers" listings. */
17
+ export const printMetaSchema = z.object({
18
+ size: z.string().max(80).optional(),
19
+ material: z.string().max(80).optional(),
20
+ finish: z.string().max(80).optional(),
21
+ editionSize: z.number().int().positive().optional(),
22
+ });
16
23
  // --- Base item schema ---------------------------------------------------------
17
24
  /**
18
25
  * Base Zod schema for a product item.
@@ -43,6 +50,8 @@ export const productItemSchema = z.object({
43
50
  .optional(),
44
51
  featured: z.boolean().optional(),
45
52
  isPromoted: z.boolean().optional(),
53
+ allowShipBeforeEmiComplete: z.boolean().optional(),
54
+ printMeta: printMetaSchema.optional(),
46
55
  currentBid: z.number().optional(),
47
56
  availableQuantity: z.number().optional(),
48
57
  category: z.string().optional(),
@@ -20,7 +20,7 @@ export declare const MAX_STORE_CUSTOM_FEATURES = 20;
20
20
  export declare const MAX_FEATURES_PER_PRODUCT = 10;
21
21
  export type ProductFeatureScope = "platform" | "store";
22
22
  export type ProductFeatureCategory = "shipping" | "seller" | "condition" | "platform" | "auction" | "preorder" | "custom";
23
- export type ProductFeatureProductType = "product" | "auction" | "preorder" | "prize-draw" | "classified" | "digital-code" | "live" | "all";
23
+ export type ProductFeatureProductType = "product" | "auction" | "preorder" | "prize-draw" | "classified" | "digital-code" | "live" | "art" | "stickers" | "all";
24
24
  /**
25
25
  * Either an appkit icon-set name key (e.g. "truck") or a raw SVG path-d string.
26
26
  * Path strings must start with "M " — see isFeatureIconPath().
@@ -1,6 +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
+ import type { CustomField, CustomSection, ProductPrintMeta } from "../schemas/firestore";
3
+ export type { CustomField, CustomSection, ProductPrintMeta } from "../schemas/firestore";
4
4
  export type ProductStatus = "draft" | "published" | "in_review" | "archived";
5
5
  export type ProductCondition = "new" | "like_new" | "good" | "fair" | "poor" | "used" | "refurbished" | "broken" | "graded";
6
6
  /**
@@ -10,7 +10,7 @@ export type ProductCondition = "new" | "like_new" | "good" | "fair" | "poor" | "
10
10
  * accepts legacy alias tokens (`preorder` → `pre-order`, `product` → `standard`)
11
11
  * but stored values are always one of these five.
12
12
  */
13
- export type ListingType = "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live";
13
+ export type ListingType = "standard" | "auction" | "pre-order" | "prize-draw" | "classified" | "digital-code" | "live" | "art" | "stickers";
14
14
  export interface ProductImage {
15
15
  url: string;
16
16
  thumbnailUrl?: string;
@@ -39,6 +39,8 @@ export interface ProductItem {
39
39
  };
40
40
  featured?: boolean;
41
41
  isPromoted?: boolean;
42
+ allowShipBeforeEmiComplete?: boolean;
43
+ printMeta?: ProductPrintMeta;
42
44
  currentBid?: number;
43
45
  availableQuantity?: number;
44
46
  /** @deprecated Use categorySlugs[0] */
@@ -33,3 +33,9 @@ export declare const isDigitalCodeListing: (input?: {
33
33
  export declare const isLiveListing: (input?: {
34
34
  listingType?: ListingType;
35
35
  }) => boolean;
36
+ export declare const isArtListing: (input?: {
37
+ listingType?: ListingType;
38
+ }) => boolean;
39
+ export declare const isStickersListing: (input?: {
40
+ listingType?: ListingType;
41
+ }) => boolean;
@@ -19,6 +19,9 @@ export const isPrizeDrawListing = (input) => normalizeListingType(input) === "pr
19
19
  export const isClassifiedListing = (input) => normalizeListingType(input) === "classified";
20
20
  export const isDigitalCodeListing = (input) => normalizeListingType(input) === "digital-code";
21
21
  export const isLiveListing = (input) => normalizeListingType(input) === "live";
22
+ // Art/stickers session — printed-only physical-goods listing types.
23
+ export const isArtListing = (input) => normalizeListingType(input) === "art";
24
+ export const isStickersListing = (input) => normalizeListingType(input) === "stickers";
22
25
  // SB-UNI-D — isBundleListing removed. Bundles are now a categoryType
23
26
  // discriminator on CategoryDocument; consumers should query
24
27
  // `categoriesRepository.findBySlugAndType(slug, "bundle")` instead.
@@ -13,6 +13,7 @@ import { z } from "zod";
13
13
  import { getProviders } from "../../../../contracts";
14
14
  import { createRouteHandler } from "../../../../next";
15
15
  import { normalizeError } from "../../../../errors/normalize";
16
+ import { isAdminUser, isModeratorUser } from "../../../auth/role-predicates";
16
17
  const ERR_REVIEW_NOT_FOUND = "Review not found";
17
18
  // ---- GET /api/reviews/[id] ---------------------------------------------------
18
19
  export async function GET(_request, context) {
@@ -70,8 +71,8 @@ export const reviewItemPATCH = createRouteHandler({
70
71
  if (!review)
71
72
  return NextResponse.json({ success: false, error: ERR_REVIEW_NOT_FOUND }, { status: 404 });
72
73
  const isOwner = review.userId === user.uid;
73
- const isModerator = user.role === "moderator";
74
- const isAdmin = user.role === "admin";
74
+ const isModerator = isModeratorUser(user);
75
+ const isAdmin = isAdminUser(user);
75
76
  if (!isOwner && !isModerator && !isAdmin) {
76
77
  return NextResponse.json({ success: false, error: "Forbidden" }, { status: 403 });
77
78
  }
@@ -101,8 +102,8 @@ export const reviewItemDELETE = createRouteHandler({
101
102
  if (!review)
102
103
  return NextResponse.json({ success: false, error: ERR_REVIEW_NOT_FOUND }, { status: 404 });
103
104
  const isOwner = review.userId === user.uid;
104
- const isModerator = user.role === "moderator";
105
- const isAdmin = user.role === "admin";
105
+ const isModerator = isModeratorUser(user);
106
+ const isAdmin = isAdminUser(user);
106
107
  if (!isOwner && !isModerator && !isAdmin) {
107
108
  return NextResponse.json({ success: false, error: "Forbidden" }, { status: 403 });
108
109
  }
@@ -5,13 +5,11 @@
5
5
  * explicit params. Called by letitrip thin wrappers that handle auth,
6
6
  * rate-limiting, and user-facing validation.
7
7
  *
8
- * Note: Shiprocket-specific actions (updateSellerShipping,
9
- * verifyShiprocketPickupOtp, shiprocketShipOrder) remain in the letitrip
10
- * thin wrapper because they use the @/lib/shiprocket/client SDK which is
11
- * a permanent letitrip-only dependency.
8
+ * Note: updateSellerShipping remains in the letitrip thin wrapper
9
+ * (src/actions/seller.actions.ts) alongside the other seller server actions.
12
10
  */
13
11
  import type { JsonValue } from "../../../schemas/types";
14
- import type { SellerPayoutDetails } from "../../auth/schemas/firestore";
12
+ import type { SellerPayoutDetails, SellerShippingConfig } from "../../auth/schemas/firestore";
15
13
  import type { StoreDocument } from "../../stores/schemas/firestore";
16
14
  import type { ProductDocument } from "../../products/schemas/firestore";
17
15
  import type { OrderDocument } from "../../orders/schemas/firestore";
@@ -86,6 +84,14 @@ export interface CustomShipOrderInput {
86
84
  trackingNumber: string;
87
85
  trackingUrl: string;
88
86
  }
87
+ export interface MarkEmiInstallmentPaidInput {
88
+ /** 1-based installment number, matches EmiInstallment.index. */
89
+ installmentIndex: number;
90
+ /** Buyer-supplied UTR/reference number for this installment's manual payment. */
91
+ transactionId?: string;
92
+ /** Signed-URL media slug for the buyer's payment proof for this installment. */
93
+ proofUrl?: string;
94
+ }
89
95
  export interface SellerListParams {
90
96
  filters?: string;
91
97
  sorts?: string;
@@ -109,15 +115,7 @@ export declare function requestPayout(userId: string, userName: string, userEmai
109
115
  export declare function bulkSellerOrder(userId: string, userRole: string, userDisplayName: string, userEmail: string, orderIds: string[]): Promise<BulkSellerOrderResult>;
110
116
  export declare function createSellerProduct(userId: string, userName: string, userEmail: string, input: Record<string, JsonValue>): Promise<void>;
111
117
  export declare function getSellerStore(userId: string): Promise<StoreDocument | null>;
112
- export declare function getSellerShipping(userId: string): Promise<{
113
- method: import("../../auth").SellerShippingMethod;
114
- customShippingPrice?: number;
115
- customCarrierName?: string;
116
- shiprocketEmail?: string;
117
- shiprocketTokenExpiry?: Date;
118
- pickupAddress?: import("../../auth").SellerPickupAddress;
119
- isConfigured: boolean;
120
- } | null>;
118
+ export declare function getSellerShipping(userId: string): Promise<SellerShippingConfig | null>;
121
119
  export declare function getSellerPayoutSettings(userId: string): Promise<SellerPayoutDetails | {
122
120
  method: string;
123
121
  isConfigured: boolean;
@@ -166,8 +164,25 @@ export declare function listSellerMyProducts(userId: string, params?: SellerList
166
164
  }>;
167
165
  export declare function sellerUpdateProduct(userId: string, userRole: string, productId: string, input: Record<string, JsonValue>): Promise<ProductDocument>;
168
166
  export declare function sellerDeleteProduct(userId: string, userRole: string, productId: string): Promise<void>;
167
+ /**
168
+ * EMI orders stay unshipped until every installment is paid, UNLESS every
169
+ * product in the order has its `allowShipBeforeEmiComplete` flag set. This
170
+ * is the single choke point for the "ship" transition, so it can't be
171
+ * bypassed by a different route/action reaching `orderRepository.update`
172
+ * directly with `status: "shipped"`.
173
+ */
174
+ export declare function assertEmiShippable(order: OrderDocument): Promise<void>;
169
175
  export declare function customShipOrder(userId: string, userRole: string, orderId: string, input: CustomShipOrderInput): Promise<{
170
176
  orderId: string;
171
177
  method: string;
172
178
  }>;
179
+ /**
180
+ * Records collection of one EMI installment. Seller/admin marks the
181
+ * installment paid after verifying the buyer's manual transfer (UTR +
182
+ * proof). Recomputes `emiRemainingBalance` from the still-pending
183
+ * installments and flips `emiComplete` once none remain — that flag is the
184
+ * single gate `assertEmiShippable` reads, so this is the only place an
185
+ * EMI order can become shippable.
186
+ */
187
+ export declare function markEmiInstallmentPaid(userId: string, userRole: string, orderId: string, input: MarkEmiInstallmentPaidInput): Promise<OrderDocument>;
173
188
  export {};