@mohasinac/appkit 4.53.1 → 4.54.0-next.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 (220) hide show
  1. package/dist/.build-stamp.json +5 -5
  2. package/dist/_internal/client/theme/ThemeProvider.js +15 -1
  3. package/dist/_internal/server/features/auth/capabilities.js +7 -5
  4. package/dist/_internal/server/features/categories/price-guidance-resolve.d.ts +66 -0
  5. package/dist/_internal/server/features/categories/price-guidance-resolve.js +138 -0
  6. package/dist/_internal/server/features/checkout/supplier-identity.js +28 -6
  7. package/dist/_internal/server/features/price-index/market.d.ts +57 -0
  8. package/dist/_internal/server/features/price-index/market.js +120 -0
  9. package/dist/_internal/server/features/products/list-public.d.ts +12 -0
  10. package/dist/_internal/server/features/products/list-public.js +10 -1
  11. package/dist/_internal/server/features/products/listing-tab-counts.d.ts +25 -1
  12. package/dist/_internal/server/features/products/listing-tab-counts.js +184 -10
  13. package/dist/_internal/server/features/search/index-build/sources.js +43 -2
  14. package/dist/_internal/server/features/stores/tabs.d.ts +7 -2
  15. package/dist/_internal/server/features/stores/tabs.js +5 -3
  16. package/dist/_internal/server/jobs/core/jobRunners.js +0 -10
  17. package/dist/_internal/server/jobs/core/onOrderCreate.js +10 -104
  18. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  19. package/dist/_internal/shared/actions/action-registry.js +128 -0
  20. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +1 -1
  21. package/dist/_internal/shared/features/site-settings/supplier-of-record.d.ts +56 -0
  22. package/dist/_internal/shared/features/site-settings/supplier-of-record.js +57 -0
  23. package/dist/_internal/shared/listing-types/tiers.d.ts +94 -0
  24. package/dist/_internal/shared/listing-types/tiers.js +150 -0
  25. package/dist/_internal/shared/search/match.js +28 -2
  26. package/dist/cli/index.js +0 -2
  27. package/dist/client.d.ts +21 -2
  28. package/dist/client.js +23 -4
  29. package/dist/constants/api-endpoints.d.ts +28 -0
  30. package/dist/constants/api-endpoints.js +14 -0
  31. package/dist/features/account/components/UserOrdersView.js +26 -7
  32. package/dist/features/addresses/utils/postal-autofill.d.ts +39 -0
  33. package/dist/features/addresses/utils/postal-autofill.js +54 -0
  34. package/dist/features/admin/components/AdminStoreEditorView.js +73 -34
  35. package/dist/features/admin/components/OwnerQuickActions.d.ts +29 -0
  36. package/dist/features/admin/components/OwnerQuickActions.js +142 -0
  37. package/dist/features/admin/schemas/admin-editor-forms.d.ts +1 -1
  38. package/dist/features/admin/schemas/admin-editor-forms.js +4 -1
  39. package/dist/features/auctions/components/AuctionDetailPageView.js +8 -7
  40. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -1
  41. package/dist/features/auctions/components/MarketplaceAuctionCard.js +9 -2
  42. package/dist/features/auth/permissions/constants.d.ts +29 -2
  43. package/dist/features/auth/permissions/constants.js +68 -1
  44. package/dist/features/blog/components/BlogFeaturedCard.d.ts +11 -1
  45. package/dist/features/blog/components/BlogFeaturedCard.js +9 -2
  46. package/dist/features/cart/components/CartDrawer.js +3 -2
  47. package/dist/features/cart/components/CartGroupLineRow.js +2 -1
  48. package/dist/features/cart/hooks/useAddToCart.js +10 -5
  49. package/dist/features/cart/utils/pending-ops.d.ts +11 -1
  50. package/dist/features/cart/utils/pending-ops.js +14 -2
  51. package/dist/features/categories/api/[id]/price-guidance/route.js +1 -24
  52. package/dist/features/categories/components/BrandDetailPageView.js +15 -6
  53. package/dist/features/categories/components/BrandDetailTabs.d.ts +7 -1
  54. package/dist/features/categories/components/BrandDetailTabs.js +18 -3
  55. package/dist/features/categories/components/BundleCollage.js +3 -2
  56. package/dist/features/categories/components/BundleItemsPicker.js +3 -4
  57. package/dist/features/categories/components/CategoryDetailPageView.js +19 -8
  58. package/dist/features/categories/components/CategoryDetailTabs.d.ts +7 -1
  59. package/dist/features/categories/components/CategoryDetailTabs.js +29 -7
  60. package/dist/features/categories/components/CategoryGrid.d.ts +11 -1
  61. package/dist/features/categories/components/CategoryGrid.js +9 -2
  62. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +10 -5
  63. package/dist/features/checkout/actions/checkout-value-otp-actions.js +2 -88
  64. package/dist/features/classified/components/ClassifiedDetailPageView.js +10 -1
  65. package/dist/features/digital-codes/components/DigitalCodeDetailPageView.js +10 -1
  66. package/dist/features/events/components/EventCard.d.ts +11 -1
  67. package/dist/features/events/components/EventCard.js +9 -2
  68. package/dist/features/layout/AppLayoutShell.js +2 -1
  69. package/dist/features/layout/BottomActions.d.ts +1 -0
  70. package/dist/features/layout/BottomActions.js +330 -0
  71. package/dist/features/layout/BottomChrome.d.ts +45 -0
  72. package/dist/features/layout/BottomChrome.js +5 -17
  73. package/dist/features/layout/SideActionDock.js +36 -13
  74. package/dist/features/layout/index.d.ts +2 -7
  75. package/dist/features/layout/index.js +7 -7
  76. package/dist/features/layout/useIsDesktop.d.ts +36 -0
  77. package/dist/features/layout/useIsDesktop.js +48 -0
  78. package/dist/features/live/components/LiveItemDetailPageView.js +10 -1
  79. package/dist/features/media/messages/en.json +67 -67
  80. package/dist/features/orders/components/YourPurchaseStrip.d.ts +24 -0
  81. package/dist/features/orders/components/YourPurchaseStrip.js +77 -0
  82. package/dist/features/orders/components/index.d.ts +2 -1
  83. package/dist/features/orders/components/index.js +1 -1
  84. package/dist/features/orders/utils/order-announcement-message.d.ts +18 -0
  85. package/dist/features/orders/utils/order-announcement-message.js +16 -0
  86. package/dist/features/orders/utils/payment-proof-message.d.ts +20 -0
  87. package/dist/features/orders/utils/payment-proof-message.js +17 -0
  88. package/dist/features/parts/schemas/firestore.d.ts +227 -0
  89. package/dist/features/parts/schemas/firestore.js +6 -6
  90. package/dist/features/pre-orders/components/MarketplacePreorderCard.d.ts +11 -1
  91. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +10 -3
  92. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +8 -7
  93. package/dist/features/price-index/components/PriceIndexToolView.d.ts +21 -0
  94. package/dist/features/price-index/components/PriceIndexToolView.js +230 -27
  95. package/dist/features/price-index/lookup-target.d.ts +48 -0
  96. package/dist/features/price-index/lookup-target.js +107 -0
  97. package/dist/features/products/api/[id]/route.js +8 -1
  98. package/dist/features/products/api/route.js +16 -4
  99. package/dist/features/products/components/AvailabilityScopeHint.d.ts +22 -0
  100. package/dist/features/products/components/AvailabilityScopeHint.js +27 -0
  101. package/dist/features/products/components/CompareOverlay.d.ts +2 -1
  102. package/dist/features/products/components/CompareOverlay.js +2 -1
  103. package/dist/features/products/components/GroupSettingsPanel.js +2 -1
  104. package/dist/features/products/components/InteractiveProductCard.d.ts +7 -1
  105. package/dist/features/products/components/InteractiveProductCard.js +16 -4
  106. package/dist/features/products/components/MarketplaceBundleCard.d.ts +11 -1
  107. package/dist/features/products/components/MarketplaceBundleCard.js +9 -2
  108. package/dist/features/products/components/MarketplacePrizeDrawCard.d.ts +11 -1
  109. package/dist/features/products/components/MarketplacePrizeDrawCard.js +9 -2
  110. package/dist/features/products/components/ProductDetailPageView.js +34 -4
  111. package/dist/features/products/components/ProductGrid.d.ts +14 -1
  112. package/dist/features/products/components/ProductGrid.js +22 -5
  113. package/dist/features/products/components/ProductsIndexListing.js +2 -2
  114. package/dist/features/products/components/SublistingCarouselSection.js +2 -1
  115. package/dist/features/products/repository/products.repository.d.ts +1 -0
  116. package/dist/features/products/repository/products.repository.js +5 -1
  117. package/dist/features/products/utils/group-member-image.d.ts +9 -2
  118. package/dist/features/products/utils/group-member-image.js +2 -1
  119. package/dist/features/reviews/api/[id]/route.js +83 -23
  120. package/dist/features/reviews/components/PurchaseReviewAffordance.d.ts +6 -0
  121. package/dist/features/reviews/components/PurchaseReviewAffordance.js +42 -0
  122. package/dist/features/reviews/components/ReviewFormDrawer.d.ts +34 -0
  123. package/dist/features/reviews/components/ReviewFormDrawer.js +127 -0
  124. package/dist/features/reviews/components/index.d.ts +4 -0
  125. package/dist/features/reviews/components/index.js +2 -0
  126. package/dist/features/seller/actions/seller-actions.d.ts +1 -1
  127. package/dist/features/seller/actions/seller-actions.js +49 -3
  128. package/dist/features/seller/components/SellerPayoutSettingsView.js +30 -2
  129. package/dist/features/seller/components/SellerProductShell.d.ts +17 -1
  130. package/dist/features/seller/components/SellerProductsView.js +3 -2
  131. package/dist/features/seller/hooks/useStoreListingEntitlements.d.ts +8 -0
  132. package/dist/features/seller/hooks/useStoreListingEntitlements.js +70 -0
  133. package/dist/features/stores/components/InteractiveStoreCard.d.ts +11 -1
  134. package/dist/features/stores/components/InteractiveStoreCard.js +9 -2
  135. package/dist/features/stores/components/StoreAuctionsListing.js +2 -1
  136. package/dist/features/stores/components/index.d.ts +0 -2
  137. package/dist/features/stores/components/index.js +0 -1
  138. package/dist/features/stores/schemas/index.d.ts +1 -1
  139. package/dist/features/stores/schemas/index.js +10 -1
  140. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +114 -2
  141. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +415 -0
  142. package/dist/index.d.ts +8 -27
  143. package/dist/index.js +14 -35
  144. package/dist/next/routing/route-map.d.ts +2 -0
  145. package/dist/next/routing/route-map.js +9 -0
  146. package/dist/providers/shipping-shiprocket/index.d.ts +98 -0
  147. package/dist/providers/shipping-shiprocket/index.js +353 -0
  148. package/dist/schemas/registry.d.ts +2 -2
  149. package/dist/security/csp.d.ts +16 -1
  150. package/dist/security/csp.js +5 -3
  151. package/dist/seed/_helpers/category-forest.js +27 -0
  152. package/dist/seed/_helpers/category-parts-glossary-map.js +1816 -86
  153. package/dist/seed/_helpers/category-parts.d.ts +1 -1
  154. package/dist/seed/_helpers/category-parts.js +13412 -3020
  155. package/dist/seed/categories-seed-data.js +48 -6
  156. package/dist/seed/homepage-sections-seed-data.js +41 -28
  157. package/dist/seed/model-composition-seed-data.js +6 -8
  158. package/dist/seed/part-glossary/chunk0.d.ts +9 -0
  159. package/dist/seed/part-glossary/chunk0.js +35044 -0
  160. package/dist/seed/part-glossary/chunk1.d.ts +9 -0
  161. package/dist/seed/part-glossary/chunk1.js +39955 -0
  162. package/dist/seed/part-glossary/chunk2.d.ts +9 -0
  163. package/dist/seed/part-glossary/chunk2.js +50335 -0
  164. package/dist/seed/part-glossary/chunk3.d.ts +9 -0
  165. package/dist/seed/part-glossary/chunk3.js +52386 -0
  166. package/dist/seed/part-glossary/chunk4.d.ts +9 -0
  167. package/dist/seed/part-glossary/chunk4.js +50549 -0
  168. package/dist/seed/part-glossary-seed-data.d.ts +1 -1
  169. package/dist/seed/part-glossary-seed-data.js +23 -47483
  170. package/dist/seed/products-beyblade-x-seed-data.js +36 -1
  171. package/dist/server.d.ts +6 -8
  172. package/dist/server.js +16 -20
  173. package/dist/styles.css +516 -358
  174. package/dist/tailwind-utilities.css +1 -1
  175. package/dist/tokens/themes/index.d.ts +3 -1
  176. package/dist/tokens/themes/index.js +11 -1
  177. package/dist/tokens/themes/technical-dark.d.ts +30 -0
  178. package/dist/tokens/themes/technical-dark.js +103 -0
  179. package/dist/tokens/themes/technical-light.d.ts +22 -0
  180. package/dist/tokens/themes/technical-light.js +89 -0
  181. package/dist/tokens/tokens.css +155 -0
  182. package/dist/ui/components/GatedPrice.js +30 -3
  183. package/dist/ui/components/GatedPrice.style.css +22 -19
  184. package/dist/ui/components/QuickCreateModal.style.css +78 -78
  185. package/dist/ui/forms/FormShell.style.css +260 -260
  186. package/package.json +1 -1
  187. package/scripts/seed-cli.mjs +133 -27
  188. package/dist/_internal/server/jobs/core/whatsappCatalogImport.d.ts +0 -27
  189. package/dist/_internal/server/jobs/core/whatsappCatalogImport.js +0 -172
  190. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +0 -35
  191. package/dist/_internal/server/jobs/core/whatsappNotify.js +0 -140
  192. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +0 -2
  193. package/dist/features/admin/components/AdminWhatsAppGuideView.js +0 -35
  194. package/dist/features/orders/components/MarketplaceOrderCard.d.ts +0 -36
  195. package/dist/features/orders/components/MarketplaceOrderCard.js +0 -57
  196. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +0 -3
  197. package/dist/features/stores/components/StoreWhatsAppGuideView.js +0 -12
  198. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +0 -12
  199. package/dist/features/whatsapp-bot/api/send-status/route.js +0 -38
  200. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +0 -12
  201. package/dist/features/whatsapp-bot/api/webhook/route.js +0 -43
  202. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +0 -6
  203. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +0 -270
  204. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +0 -9
  205. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +0 -7
  206. package/dist/features/whatsapp-bot/components/index.d.ts +0 -3
  207. package/dist/features/whatsapp-bot/components/index.js +0 -2
  208. package/dist/features/whatsapp-bot/helpers/payment-proof-message.d.ts +0 -14
  209. package/dist/features/whatsapp-bot/helpers/payment-proof-message.js +0 -17
  210. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +0 -52
  211. package/dist/features/whatsapp-bot/helpers/whatsapp.js +0 -283
  212. package/dist/features/whatsapp-bot/index.d.ts +0 -3
  213. package/dist/features/whatsapp-bot/index.js +0 -3
  214. package/dist/features/whatsapp-bot/manifest.d.ts +0 -2
  215. package/dist/features/whatsapp-bot/manifest.js +0 -13
  216. package/dist/features/whatsapp-bot/messages/en.json +0 -7
  217. package/dist/features/whatsapp-bot/server.d.ts +0 -7
  218. package/dist/features/whatsapp-bot/server.js +0 -7
  219. package/dist/features/whatsapp-bot/types/index.d.ts +0 -126
  220. package/dist/features/whatsapp-bot/types/index.js +0 -1
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ /**
3
+ * Which listing types the signed-in seller's own store may create.
4
+ *
5
+ * ── Why this needs a fetch at all ─────────────────────────────────────────
6
+ *
7
+ * 🛑 `StoreDocument.capabilities` is listed in `PRIVATE_STORE_FIELDS` —
8
+ * "internal feature gating; server-enforced, never client-read" — so it is
9
+ * absent from every public store payload and from the session user. A client
10
+ * component genuinely cannot derive this locally, and the alternative a
11
+ * previous draft reached for (publish the capability array to the browser)
12
+ * would ship `lower_commission_rate`, `priority_support` and fifteen more
13
+ * admin-granted entitlements to anyone who opened devtools. Root Cause #70.
14
+ *
15
+ * So the server sends a PROJECTION: `allowedListingTypes`, already resolved by
16
+ * `listingTypesAllowedFor` on the same capability array the write-path gate
17
+ * uses. Client and server therefore agree by construction rather than by two
18
+ * copies of one rule (Root Cause #30).
19
+ *
20
+ * ── Why the loading state is not "denied" ─────────────────────────────────
21
+ *
22
+ * `isLoading` is returned and callers must respect it. The three-state rule
23
+ * from `useCanSeePrices`: "not known yet" rendering as "no" shows an entitled
24
+ * seller a refusal on every page load. The server refuses authoritatively in
25
+ * `createSellerProduct` / `sellerUpdateProduct`, so an optimistic render while
26
+ * the fetch is in flight costs nothing worse than a late refusal.
27
+ *
28
+ * @tag domain:seller
29
+ * @tag layer:hooks
30
+ * @tag access:client
31
+ */
32
+ import { useMemo } from "react";
33
+ import { useApiQuery } from "../../../client/api/useApiQuery.js";
34
+ import { apiClient } from "../../../http/index.js";
35
+ import { SELLER_ENDPOINTS } from "../../../constants/api-endpoints.js";
36
+ import { BASIC_LISTING_TYPES } from "../../../_internal/shared/listing-types/tiers.js";
37
+ export function useStoreListingEntitlements() {
38
+ const query = useApiQuery({
39
+ queryKey: ["store", "listing-entitlements"],
40
+ queryFn: async () => {
41
+ const res = await apiClient.get(SELLER_ENDPOINTS.PAYOUT_SETTINGS);
42
+ return res?.data ?? {};
43
+ },
44
+ /*
45
+ * The answer changes only when an admin or the seller flips a capability,
46
+ * so a short window is pointless churn — but it must not be `Infinity`,
47
+ * because a seller who has just ticked the box in another tab should not
48
+ * have to hard-reload to see the nav and forms open up.
49
+ */
50
+ staleTime: 60000,
51
+ silentBackgroundFailures: true,
52
+ });
53
+ return useMemo(() => {
54
+ /*
55
+ * 🛑 Falls back to the BASIC types, not to everything and not to nothing.
56
+ *
57
+ * Everything would offer a form that the server then refuses — the exact
58
+ * "filled in a long wizard and got rejected at publish" experience this
59
+ * hook exists to prevent. Nothing would lock a seller out of `standard`
60
+ * on a transient network blip, which is the one type every store has.
61
+ */
62
+ const list = query.data?.allowedListingTypes;
63
+ const allowed = new Set(Array.isArray(list) && list.length > 0 ? list : BASIC_LISTING_TYPES);
64
+ return {
65
+ allowed,
66
+ advancedEnabled: query.data?.advancedListingTypesEnabled === true,
67
+ isLoading: query.isLoading,
68
+ };
69
+ }, [query.data, query.isLoading]);
70
+ }
@@ -12,5 +12,15 @@ export interface InteractiveStoreCardProps {
12
12
  visitStore?: string;
13
13
  };
14
14
  className?: string;
15
+ /**
16
+ * Owner-only pencil / clone / bin, for an admin or whoever owns this record.
17
+ * Pass `<OwnerQuickActions>`; it renders nothing for anyone else.
18
+ *
19
+ * 🛑 Rendered OUTSIDE this card's own `<Link>`/`<TextLink>`, as a sibling.
20
+ * A `<button>` inside an `<a>` is invalid HTML that browsers resolve by
21
+ * closing the anchor early, which breaks the CARD's navigation rather than
22
+ * just the button's — `stopPropagation` stops a click but cannot fix a parse.
23
+ */
24
+ renderOwnerActions?: () => React.ReactNode;
15
25
  }
16
- export declare function InteractiveStoreCard({ store, href, selectable, isSelected, onSelect, labels, className, }: InteractiveStoreCardProps): import("react").JSX.Element;
26
+ export declare function InteractiveStoreCard({ store, href, selectable, isSelected, onSelect, labels, className, renderOwnerActions, }: InteractiveStoreCardProps): import("react").JSX.Element;
@@ -10,13 +10,13 @@ const __O = {
10
10
  hidden: "overflow-hidden",
11
11
  };
12
12
  const CLS_RATING_BADGE = "absolute top-2 right-2 flex items-center gap-[var(--appkit-space-1)] rounded-full bg-black/50 backdrop-blur-sm px-[var(--appkit-space-2)] py-[var(--appkit-space-0-5)] text-[length:var(--appkit-text-xs)] font-semibold text-warning";
13
- export function InteractiveStoreCard({ store, href, selectable, isSelected = false, onSelect, labels = {}, className = "", }) {
13
+ export function InteractiveStoreCard({ store, href, selectable, isSelected = false, onSelect, labels = {}, className = "", renderOwnerActions, }) {
14
14
  const initial = store.storeName[0]?.toUpperCase() ?? "S";
15
15
  const [logoBroken, setLogoBroken] = useState(false);
16
16
  const hasLogo = Boolean(store.storeLogoURL) && !logoBroken;
17
17
  const hasBanner = Boolean(store.storeBannerURL);
18
18
  const longPress = useLongPress(() => onSelect?.(store.id, !isSelected));
19
- return (_jsxs(Div, { className: [
19
+ const card = (_jsxs(Div, { className: [
20
20
  "group relative flex flex-col h-full overflow-hidden rounded-xl border bg-[var(--appkit-color-surface)] shadow-sm transition-shadow hover:shadow-lg",
21
21
  isSelected
22
22
  ? "border-[var(--appkit-color-primary,theme(colors.violet.600))] outline outline-2 outline-[var(--appkit-color-primary,theme(colors.violet.600))]"
@@ -26,4 +26,11 @@ export function InteractiveStoreCard({ store, href, selectable, isSelected = fal
26
26
  e.preventDefault();
27
27
  onSelect(store.id, !isSelected);
28
28
  }, label: isSelected ? "Deselect store" : "Select store", position: "top-2 left-2", className: selectable || isSelected ? "opacity-100" : "opacity-0 pointer-events-none group-hover:opacity-100 group-hover:pointer-events-auto transition-opacity" })), _jsxs(Link, { href: href, className: "flex flex-col flex-1 min-h-0", children: [_jsxs(Div, { surface: "muted", className: `relative aspect-video w-full ${__O.hidden} flex-shrink-0`, children: [hasBanner ? (_jsx(MediaImage, { src: store.storeBannerURL, alt: `${store.storeName} banner`, size: "banner", className: "h-full w-full object-cover transition-transform duration-500 group-hover:scale-105" })) : (_jsx(Row, { className: "absolute inset-0", align: "center", justify: "center", children: _jsx(Span, { className: "opacity-20 select-none", size: "5xl", "aria-hidden": "true", children: "\uD83C\uDFEA" }) })), _jsx(Scrim, { direction: "bottom-up", intensity: "subtle", className: "absolute inset-x-0 bottom-0 h-12" }), store.averageRating != null && store.averageRating > 0 && (_jsxs(Div, { className: CLS_RATING_BADGE, children: ["\u2605 ", store.averageRating.toFixed(1)] }))] }), _jsxs(Stack, { paddingY: "b-md", className: "flex-1", padding: "x-md", children: [_jsx(Row, { className: "-mt-5 mb-2", align: "end", justify: "between", children: _jsx(Div, { className: "flex-shrink-0", children: hasLogo ? (_jsx(Div, { className: "relative h-10 w-10 border-2 border-[var(--appkit-color-border-subtle)] bg-[var(--appkit-color-surface)]", rounded: "lg", shadow: "md", overflow: "hidden", children: _jsx(MediaImage, { src: store.storeLogoURL, alt: store.storeName, size: "thumbnail", onError: () => setLogoBroken(true) }) })) : (_jsx(Row, { textWeight: "bold", textSize: "base", className: "h-10 w-10 border-2 border-white bg-primary/10 dark:bg-primary/20 text-primary", align: "center", justify: "center", rounded: "lg", shadow: "md", children: initial })) }) }), _jsx(Heading, { level: 3, className: `group-hover:text-primary transition-colors`, truncate: true, size: "sm", weight: "bold", color: "primary", children: store.storeName }), store.storeDescription ? (_jsx(RichText, { html: normalizeRichTextHtml(store.storeDescription), proseClass: "prose prose-sm max-w-none dark:prose-invert prose-p:my-0", className: "mt-1 line-clamp-2 text-[length:var(--appkit-text-xs)] text-[var(--appkit-color-text-muted)] flex-1" })) : (_jsx(Div, { className: "flex-1" })), _jsxs(Row, { color: "muted", textSize: "xs", gap: "sm", className: "mt-2.5", wrap: true, children: [store.totalProducts != null && store.totalProducts > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDCE6" }), " ", store.totalProducts, " ", labels.products ?? "products"] })), store.itemsSold != null && store.itemsSold > 0 && (_jsxs(Span, { layout: "flex", gap: "2xs", children: [_jsx(Span, { "aria-hidden": "true", children: "\uD83D\uDECD\uFE0F" }), " ", store.itemsSold, " ", labels.sold ?? "sold"] }))] }), _jsxs(Row, { border: "top-subtle", paddingY: "y-xs-tall", className: "mt-3", align: "center", justify: "between", children: [_jsxs(Span, { size: "xs", weight: "semibold", className: "text-primary group-hover:underline transition-colors", children: [labels.visitStore ?? "Visit store", " \u2192"] }), store.totalReviews != null && store.totalReviews > 0 && (_jsxs(Span, { size: "xs", color: "muted", children: [store.totalReviews, " ", labels.reviews ?? "reviews"] }))] })] })] })] }));
29
+ if (!renderOwnerActions)
30
+ return card;
31
+ /*
32
+ * Sibling of the card, never a child — see the prop's docstring. `z-20`
33
+ * beats this card's own overlays, which all sit at `z-10`.
34
+ */
35
+ return (_jsxs(Div, { className: "relative h-full", children: [card, _jsx(Div, { className: "absolute right-2 bottom-2 z-20", children: renderOwnerActions() })] }));
29
36
  }
@@ -16,6 +16,7 @@ import { AUCTION_PUBLIC_SORT_OPTIONS } from "../../products/constants/sieve.js";
16
16
  import { AvailabilityTabs } from "../../products/components/AvailabilityTabs.js";
17
17
  import { AVAILABILITY_VALUES } from "../../../constants/field-names.js";
18
18
  import { TABLE_KEYS } from "../../../constants/table-keys.js";
19
+ import { productImageUrls } from "../../products/schemas/firestore.js";
19
20
  const STORE_AUCTION_TYPES = ["auction"];
20
21
  const __P = {
21
22
  p3: "p-[var(--appkit-space-3)]",
@@ -75,7 +76,7 @@ export function StoreAuctionsListing({ storeId, initialData }) {
75
76
  description: p.description,
76
77
  price: p.price ?? 0,
77
78
  currency: p.currency || getDefaultCurrency(),
78
- mainImage: p.mainImage ?? p.images?.[0],
79
+ mainImage: p.mainImage || productImageUrls(p.images)[0],
79
80
  images: p.images,
80
81
  listingType: "auction",
81
82
  auctionEndDate: p.auctionEndDate,
@@ -47,5 +47,3 @@ export { StoreCapabilitiesGuideView } from "./StoreCapabilitiesGuideView";
47
47
  export type { StoreCapabilitiesGuideViewProps } from "./StoreCapabilitiesGuideView";
48
48
  export { StoreSettingsGuideView } from "./StoreSettingsGuideView";
49
49
  export type { StoreSettingsGuideViewProps } from "./StoreSettingsGuideView";
50
- export { StoreWhatsAppGuideView } from "./StoreWhatsAppGuideView";
51
- export type { StoreWhatsAppGuideViewProps } from "./StoreWhatsAppGuideView";
@@ -20,4 +20,3 @@ export { StoreOrdersGuideView } from "./StoreOrdersGuideView.js";
20
20
  export { StoreFinanceGuideView } from "./StoreFinanceGuideView.js";
21
21
  export { StoreCapabilitiesGuideView } from "./StoreCapabilitiesGuideView.js";
22
22
  export { StoreSettingsGuideView } from "./StoreSettingsGuideView.js";
23
- export { StoreWhatsAppGuideView } from "./StoreWhatsAppGuideView.js";
@@ -228,7 +228,7 @@ export declare const storeFirestoreSchema: z.ZodObject<{
228
228
  totalReviews: number;
229
229
  averageRating?: number | undefined;
230
230
  }>>;
231
- capabilities: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
231
+ capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<[string, ...string[]]>, "many">>;
232
232
  customCommissionRate: z.ZodOptional<z.ZodNumber>;
233
233
  googleReviews: z.ZodOptional<z.ZodObject<{
234
234
  placeId: z.ZodString;
@@ -1,6 +1,7 @@
1
1
  export * from "./firestore.js";
2
2
  import { z } from "zod";
3
3
  import { StoreStatusValues } from "./firestore.js";
4
+ import { ALL_STORE_CAPABILITIES } from "../../auth/permissions/constants.js";
4
5
  import { auditTimestampsShape, firestoreDateSchema, rupeesSchema } from "../../../schemas/firestore-helpers.js";
5
6
  // ─── Firestore document schema (W2) ───────────────────────────────────────────
6
7
  // Mirrors StoreDocument in ./firestore.ts. Registered into SCHEMAS.firestore.stores.
@@ -70,7 +71,15 @@ export const storeFirestoreSchema = z.object({
70
71
  averageRating: z.number().optional(),
71
72
  })
72
73
  .optional(),
73
- capabilities: z.array(z.string()).optional(),
74
+ /*
75
+ * 🛑 A `z.enum`, not `z.array(z.string())`. Nothing used to check a stored
76
+ * capability against the union, so a typo saved successfully and the gate it
77
+ * was meant to open never opened — with no error anywhere, because an
78
+ * unrecognised capability is indistinguishable from an ungranted one.
79
+ */
80
+ capabilities: z
81
+ .array(z.enum(ALL_STORE_CAPABILITIES))
82
+ .optional(),
74
83
  customCommissionRate: z.number().min(0).max(100).optional(),
75
84
  googleReviews: z
76
85
  .object({
@@ -1,6 +1,6 @@
1
1
  /*
2
2
  * WHY: Authored six-part procedures for content-discovery/authenticity-checker.
3
- * WHAT: 16 cases, keyed by full checklist id.
3
+ * WHAT: 22 cases, keyed by full checklist id.
4
4
  *
5
5
  * 🛑 EVERY CASE IS WRITTEN AS BEFORE → AFTER, because this feature's whole
6
6
  * failure mode is a confident, plausible, wrong answer returned with HTTP 200. A
@@ -44,7 +44,7 @@ export const authored = {
44
44
  startPage: "/tools/authenticity",
45
45
  steps: [
46
46
  "Open /tools/authenticity signed out.",
47
- "Confirm the LOOKUP FORM itself is there and usable — Model name, Code on the box, Brand, Generation, How it was sold, and a 'Check it' button.",
47
+ "Confirm the LOOKUP FORM itself is there and usable — 'Which bey is it?', 'Code on the box', a collapsed 'More details' holding the name-as-printed, brand, generation and how-it-was-sold fields, and a 'Check it' button.",
48
48
  "Confirm there is no sign-in card and no 'Sign in to run a lookup' text anywhere on the page.",
49
49
  "Enter A0813 as the code and submit, still signed out.",
50
50
  "Confirm a real assessment comes back rather than an error or a prompt to sign in.",
@@ -475,4 +475,116 @@ export const authoredTypeahead = {
475
475
  expectedUiState: "The result renders immediately. The network log shows no fetch to the search artifact or any /api/search* endpoint.",
476
476
  endResult: "Read-only; nothing persists.",
477
477
  },
478
+ /* ── The model picker and the per-model code list (2026-10-20) ──────────
479
+ * 🛑 The reported bug these exist for: typing "l drago" returned
480
+ * "Inconclusive — Nothing contradicts it, and nothing confirms it either",
481
+ * while the tool held 33 recorded box codes for Lightning L-Drago, 35 for
482
+ * Meteo and 27 for Destructor and had no way to reach any of them. Every
483
+ * table was keyed by CODE, so naming a bey could never surface them.
484
+ *
485
+ * So each case compares the answer against data the tool DEMONSTRABLY HAS.
486
+ * "Check the picker works" passes against the bug; "picking Lightning
487
+ * L-Drago must list its 33 codes" does not.
488
+ */
489
+ "checklist-content-discovery-authenticity-checker-model-picker-finds-l-drago": {
490
+ roles: ["guest"],
491
+ startPage: "/tools/authenticity",
492
+ steps: [
493
+ "Open /tools/authenticity.",
494
+ "Confirm the FIRST field is 'Which bey is it?' and is a searchable select, not a plain text box.",
495
+ "Open it and type: l drago",
496
+ "Confirm all three L-Drago releases are offered — Lightning L-Drago 100HF, Meteo L-Drago LW105LF and L-Drago Destructor F:S.",
497
+ "Confirm each offered row states how many box codes are on file for it.",
498
+ "Clear the search and type: pegasis",
499
+ "Confirm Storm Pegasus still appears, even though the list spells it Pegasus.",
500
+ "Answer no if 'l drago' offers nothing, or if the field is a free-text input with no list.",
501
+ ],
502
+ inputs: { search: "l drago" },
503
+ expectedBehaviour: "BEFORE: the first field was a free-text 'Model name'. Typing 'l drago' matched four releases, so the engine refused to pick one and returned an inconclusive verdict — correct, and useless, because the three L-Dragos it declined to choose between have 95 recorded box codes between them and the screen never said so. AFTER: the picker resolves exactly one bey, which is what unlocks the per-model code list. It searches aliases as well as labels, because Takara Tomy romanises the line Pegasis and Hasbro prints Pegasus — a label-only filter would find neither from the other.",
504
+ expectedUiState: "A searchable select labelled 'Which bey is it?' listing three L-Drago rows, each annotated with a box-code count. Beys with codes on file are offered before beys without.",
505
+ endResult: "Nothing is written. The picker only reads a bundled list.",
506
+ },
507
+ "checklist-content-discovery-authenticity-checker-picked-model-lists-its-box-codes": {
508
+ roles: ["guest"],
509
+ startPage: "/tools/authenticity",
510
+ steps: [
511
+ "Open /tools/authenticity.",
512
+ "In 'Which bey is it?', search for and pick: Lightning L-Drago 100HF",
513
+ "Leave 'Code on the box' EMPTY.",
514
+ "Press 'Check it'.",
515
+ "Read the verdict line, then look for a panel headed 'Box codes recorded for Lightning L-Drago 100HF'.",
516
+ "Count the codes it lists.",
517
+ "Answer no if the verdict is 'Inconclusive — Nothing contradicts it, and nothing confirms it either', or if no code list appears.",
518
+ ],
519
+ inputs: { modelSlug: "lightning-l-drago-100hf" },
520
+ expectedBehaviour: "BEFORE: naming this bey with no code produced 'Inconclusive — Nothing contradicts it, and nothing confirms it either' and a physical checklist. True, in that nothing had been checked — and it withheld the 33 box codes the tables record for this exact bey, which is the single most useful thing the tool holds and the one thing the user had asked for. AFTER: the verdict is 'I know this one. Now find the code stamped on the box.' — still not a judgement, because nothing has been checked, but with the next step named and the 33 codes listed to check against.",
521
+ expectedUiState: "Verdict badge 'Need a bit more', headline 'I know this one. Now find the code stamped on the box.', a caveat naming the count, and a panel listing 33 codes as badges. A link reading 'Everything we hold on Lightning L-Drago 100HF' points at the model's own category page.",
522
+ expectedData: { recordedCodes: 33 },
523
+ endResult: "Read-only.",
524
+ },
525
+ "checklist-content-discovery-authenticity-checker-picked-model-plus-its-own-code": {
526
+ roles: ["guest"],
527
+ startPage: "/tools/authenticity",
528
+ steps: [
529
+ "Pick 'Lightning L-Drago 100HF' in 'Which bey is it?'.",
530
+ "Enter A0910 in 'Code on the box' — a code recorded against this exact bey.",
531
+ "Press 'Check it' and read the verdict.",
532
+ "Expand 'How I got there' and find the row about the code.",
533
+ "Press 'Start over'. Pick Lightning L-Drago 100HF again, this time enter A0310 — a real code recorded against a DIFFERENT bey.",
534
+ "Read the verdict and expand the working-out again.",
535
+ "Answer no if the second case reads as a mismatch, a discrepancy, or anything implying the item is wrong.",
536
+ ],
537
+ inputs: { modelSlug: "lightning-l-drago-100hf", recordedCode: "A0910", otherBeyCode: "A0310" },
538
+ expectedBehaviour: "A0910 is in this bey's own list, so the check reads as agreement — the strongest thing the tool can say from data, and it still says so is not proof. A0310 is recorded, but on Earth Aquila: that is WEAK SUPPORT, not a mismatch, because a production date covers whatever was boxed that day. The distinction is the whole point. Reporting 'that code belongs to a different bey' as a discrepancy would manufacture suspicion out of how the tables are organised, and the panel highlights the code you entered inside the list so you can see where it sits.",
539
+ expectedUiState: "First run: 'Consistent with an original', and a row reading that the code is recorded on genuine boxes of this exact bey. Second run: still not adverse — a neutral row saying the code is recorded but not against this bey, with the phrase 'weak support rather than a mismatch'.",
540
+ endResult: "Read-only.",
541
+ },
542
+ "checklist-content-discovery-authenticity-checker-missing-code-is-not-an-accusation": {
543
+ roles: ["guest"],
544
+ startPage: "/tools/authenticity",
545
+ steps: [
546
+ "Pick 'Lightning L-Drago 100HF' in 'Which bey is it?'.",
547
+ "Enter Z9999 in 'Code on the box' — well-formed, and in none of the tables.",
548
+ "Press 'Check it'.",
549
+ "Read the verdict, the code panel and the working-out.",
550
+ "Answer no if any sentence implies the item is fake, counterfeit or a reproduction, or if the verdict is more severe than 'Inconclusive'.",
551
+ ],
552
+ inputs: { modelSlug: "lightning-l-drago-100hf", code: "Z9999" },
553
+ expectedBehaviour: "A code absent from a model's list means UNRECORDED, never wrong. These tables are what collectors photographed, not a manufacturing record: 38 of 221 canonical beys have any recorded code at all, new genuine codes turn up regularly, and counterfeiters print real codes anyway. The panel must say so where a user will actually read it, not only inside the collapsed working-out — the reading 'my code is not in the list, so it is fake' is the one this whole feature has to defend against.",
554
+ expectedUiState: "Verdict 'Inconclusive'. A neutral row reading 'Not among the 33 codes recorded for Lightning L-Drago 100HF' whose text says 'unrecorded, not wrong'. The code panel carries a line beginning 'A code missing from this list means unrecorded, not fake.'",
555
+ endResult: "Read-only.",
556
+ },
557
+ "checklist-content-discovery-authenticity-checker-working-out-is-collapsed-by-default": {
558
+ roles: ["guest"],
559
+ startPage: "/tools/authenticity",
560
+ steps: [
561
+ "Pick 'Lightning L-Drago 100HF' and enter A0910. Press 'Check it'.",
562
+ "WITHOUT expanding anything, confirm you can read the verdict badge, the headline, the caveat and the recorded-code list.",
563
+ "Confirm the reasoning is behind a single collapsed control reading 'How I got there — N checks'.",
564
+ "Expand it and confirm every individual check is still there, each with its own confidence chip.",
565
+ "Press 'Start over'. Now enter the code A0813 with no model picked — a code the sources record as a midfake batch.",
566
+ "Confirm the working-out is ALREADY OPEN this time, without being clicked.",
567
+ "Answer no if the reasoning is expanded by default on the clean run, or collapsed on the midfake run.",
568
+ ],
569
+ inputs: { modelSlug: "lightning-l-drago-100hf", cleanCode: "A0910", midfakeCode: "A0813" },
570
+ expectedBehaviour: "BEFORE: every check rendered expanded under a 'What I checked' heading — up to eleven rows of reasoning between the buyer and the answer they asked for. AFTER: it is collapsed, not deleted, because 'why should I believe this' has to stay one tap away for a tool whose entire posture is that it hedges and cites. A CONCERN forces it open: the one case where the reasoning IS the answer is the case where something is wrong, and the user who never expands it must not be the user who never finds out.",
571
+ expectedUiState: "Clean run: verdict block, code panel, a collapsed 'How I got there — N checks', then the physical checks. Midfake run: the same control, already open.",
572
+ endResult: "Read-only.",
573
+ },
574
+ "checklist-content-discovery-authenticity-checker-result-scrolls-into-view": {
575
+ roles: ["guest"],
576
+ startPage: "/tools/authenticity",
577
+ steps: [
578
+ "Resize the browser to a phone viewport (about 390 x 740) or use device emulation.",
579
+ "Pick 'Lightning L-Drago 100HF' and enter A0910.",
580
+ "Scroll so the 'Check it' button is visible, and note what is on screen.",
581
+ "Press 'Check it'.",
582
+ "Without scrolling yourself, confirm the verdict badge and headline are now on screen.",
583
+ "Answer no if the page does not move and the verdict is below the fold.",
584
+ ],
585
+ inputs: { viewport: "390x740", modelSlug: "lightning-l-drago-100hf", code: "A0910" },
586
+ expectedBehaviour: "BEFORE: the form is taller than a phone viewport, so the answer rendered below the fold and pressing 'Check it' looked like it had done nothing — at the one moment this tool must not be ambiguous. AFTER: the result is scrolled into view on every fresh assessment, including the re-run a clarifying question triggers, and anchored at the top of the viewport rather than the middle so the badge and headline land where the eye already is.",
587
+ expectedUiState: "After submitting, the verdict badge and headline are visible without manual scrolling.",
588
+ endResult: "Read-only.",
589
+ },
478
590
  };