@mohasinac/appkit 4.27.0 → 4.29.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 (298) hide show
  1. package/dist/_internal/client/features/lottery/LotteryAdminEditView.d.ts +16 -16
  2. package/dist/_internal/client/features/lottery/LotteryAdminEditView.js +29 -6
  3. package/dist/_internal/client/features/lottery/LotteryListView.js +13 -1
  4. package/dist/_internal/server/features/checkout/actions.js +6 -4
  5. package/dist/_internal/server/features/classified/metadata.js +3 -1
  6. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  7. package/dist/_internal/server/features/live/metadata.js +3 -1
  8. package/dist/_internal/server/features/refunds/actions.js +3 -2
  9. package/dist/_internal/server/features/reviews/actions.js +3 -2
  10. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  11. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  12. package/dist/_internal/server/features/stores/adapters.js +5 -0
  13. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  14. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  15. package/dist/_internal/shared/actions/action-registry.js +14 -0
  16. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  17. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  18. package/dist/_internal/shared/features/checkout/config.js +6 -3
  19. package/dist/client.d.ts +32 -2
  20. package/dist/client.js +37 -2
  21. package/dist/constants/api-endpoints.d.ts +21 -3
  22. package/dist/constants/api-endpoints.js +14 -1
  23. package/dist/constants/field-names.d.ts +2 -2
  24. package/dist/constants/field-names.js +2 -2
  25. package/dist/core/newsletter.repository.js +14 -1
  26. package/dist/features/about/components/FAQPageView.js +1 -1
  27. package/dist/features/about/components/FeesView.js +1 -1
  28. package/dist/features/account/components/AddressForm.d.ts +2 -0
  29. package/dist/features/account/components/AddressForm.js +5 -1
  30. package/dist/features/account/components/UserBidsView.js +1 -1
  31. package/dist/features/account/components/UserSidebar.js +1 -1
  32. package/dist/features/account/components/UserSupportView.js +24 -2
  33. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  34. package/dist/features/account/hooks/useAddresses.js +4 -1
  35. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  36. package/dist/features/account/hooks/useProfile.js +1 -1
  37. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  38. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  39. package/dist/features/admin/actions/chat-actions.js +5 -1
  40. package/dist/features/admin/components/AdminAdsView.js +1 -1
  41. package/dist/features/admin/components/AdminBidsView.js +6 -1
  42. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  43. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  44. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  45. package/dist/features/admin/components/AdminContentGuideView.js +1 -1
  46. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  47. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  48. package/dist/features/admin/components/AdminSidebar.js +1 -1
  49. package/dist/features/admin/components/AdminViewCards.js +2 -2
  50. package/dist/features/admin/components/DataTable.js +2 -2
  51. package/dist/features/admin/schemas/firestore.js +9 -3
  52. package/dist/features/auctions/actions/bid-actions.js +45 -4
  53. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  54. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  55. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  56. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  57. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  58. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  59. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  60. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  61. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  62. package/dist/features/auth/hooks/useAuth.js +27 -1
  63. package/dist/features/auth/permissions/constants.d.ts +1 -1
  64. package/dist/features/auth/permissions/constants.js +3 -0
  65. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  66. package/dist/features/auth/repository/session.repository.js +23 -0
  67. package/dist/features/auth/repository/user.repository.js +13 -2
  68. package/dist/features/blog/components/BlogListView.js +2 -2
  69. package/dist/features/blog/repository/blog.repository.d.ts +4 -0
  70. package/dist/features/blog/repository/blog.repository.js +1 -0
  71. package/dist/features/blog/schemas/firestore.d.ts +3 -1
  72. package/dist/features/blog/schemas/firestore.js +1 -0
  73. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  74. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  75. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  76. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  77. package/dist/features/cart/components/index.d.ts +1 -1
  78. package/dist/features/cart/components/index.js +1 -1
  79. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  80. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  81. package/dist/features/cart/utils/line-total.d.ts +25 -0
  82. package/dist/features/cart/utils/line-total.js +26 -0
  83. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  84. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  85. package/dist/features/categories/components/CategoryTree.js +2 -2
  86. package/dist/features/contact/components/ContactForm.js +1 -1
  87. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  88. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  89. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  90. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  91. package/dist/features/events/components/EventRafflesSection.js +14 -2
  92. package/dist/features/events/components/EventStatusBadge.js +1 -1
  93. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  94. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  95. package/dist/features/events/repository/events.repository.js +1 -0
  96. package/dist/features/events/schemas/firestore.d.ts +3 -1
  97. package/dist/features/events/schemas/firestore.js +1 -0
  98. package/dist/features/faq/components/ContactCTA.js +1 -1
  99. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  100. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  101. package/dist/features/faq/repository/faqs.repository.js +64 -54
  102. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  103. package/dist/features/faq/schemas/firestore.js +2 -2
  104. package/dist/features/faq/schemas/index.d.ts +3 -3
  105. package/dist/features/faq/schemas/index.js +1 -1
  106. package/dist/features/filters/FilterFacetSection.js +2 -2
  107. package/dist/features/forms/Toggle.js +1 -1
  108. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  109. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  110. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  111. package/dist/features/homepage/components/BrandsSection.js +1 -1
  112. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  113. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  114. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  115. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  116. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  117. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  118. package/dist/features/homepage/lib/section-renderer.js +2 -1
  119. package/dist/features/layout/AppLayoutShell.js +5 -5
  120. package/dist/features/layout/BottomActions.js +5 -5
  121. package/dist/features/layout/BottomSheet.js +1 -1
  122. package/dist/features/layout/MainNavbar.js +1 -1
  123. package/dist/features/layout/NavbarLayout.js +1 -1
  124. package/dist/features/layout/TitleBarLayout.js +3 -3
  125. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  126. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  127. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  128. package/dist/features/lottery/schemas/config-write.js +139 -0
  129. package/dist/features/media/MediaPickerModal.js +3 -1
  130. package/dist/features/media/MediaVideo.js +4 -2
  131. package/dist/features/media/components/MediaSlider.js +1 -1
  132. package/dist/features/messages/repository/conversations.repository.js +15 -5
  133. package/dist/features/orders/components/OrdersList.js +2 -2
  134. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  135. package/dist/features/payments/repository/payout.repository.js +14 -6
  136. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  137. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  138. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  139. package/dist/features/products/columns/productTableColumns.js +3 -3
  140. package/dist/features/products/components/CompareOverlay.js +1 -1
  141. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  142. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  143. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  144. package/dist/features/products/components/ProductDetailActions.js +11 -7
  145. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  146. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  147. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  148. package/dist/features/products/components/ProductFilters.js +15 -8
  149. package/dist/features/products/components/ProductGrid.js +8 -17
  150. package/dist/features/products/components/ProductTabs.js +7 -4
  151. package/dist/features/products/components/ProductTabsShell.js +2 -7
  152. package/dist/features/products/constants/action-defs.d.ts +21 -4
  153. package/dist/features/products/constants/action-defs.js +7 -3
  154. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  155. package/dist/features/products/repository/products.repository.d.ts +1 -1
  156. package/dist/features/products/repository/products.repository.js +38 -16
  157. package/dist/features/products/schemas/firestore.d.ts +3 -2
  158. package/dist/features/products/schemas/firestore.js +1 -1
  159. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  160. package/dist/features/reviews/actions/review-actions.js +14 -3
  161. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  162. package/dist/features/reviews/components/ReviewModal.js +1 -1
  163. package/dist/features/reviews/repository/reviews.repository.d.ts +29 -1
  164. package/dist/features/reviews/repository/reviews.repository.js +54 -13
  165. package/dist/features/reviews/schemas/firestore.d.ts +9 -1
  166. package/dist/features/reviews/schemas/firestore.js +1 -0
  167. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  168. package/dist/features/search/components/Search.js +3 -3
  169. package/dist/features/search/components/SearchResultsSection.js +1 -1
  170. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  171. package/dist/features/seller/components/SellerBidsView.js +6 -1
  172. package/dist/features/seller/components/SellerSidebar.js +1 -1
  173. package/dist/features/shell/SectionForm.d.ts +21 -1
  174. package/dist/features/shell/SectionForm.js +15 -3
  175. package/dist/features/shell/build-sections.js +10 -1
  176. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  177. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  178. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  179. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  180. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  181. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  182. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  183. package/dist/features/stores/actions/store-query-actions.js +25 -1
  184. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  185. package/dist/features/stores/components/StoreHeader.js +1 -1
  186. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  187. package/dist/features/stores/repository/store.repository.js +1 -0
  188. package/dist/features/stores/schemas/firestore.d.ts +2 -0
  189. package/dist/features/support/repository/support.repository.d.ts +12 -0
  190. package/dist/features/support/repository/support.repository.js +15 -0
  191. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  192. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  193. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +0 -1
  194. package/dist/features/tester/repository/tester-checklist-item.repository.js +6 -15
  195. package/dist/features/tester/schemas/firestore.d.ts +5 -4
  196. package/dist/features/tester/schemas/firestore.js +3 -3
  197. package/dist/features/tester/seed-data/products-tester-seed-data.js +2 -2
  198. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +138 -1
  199. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  200. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  201. package/dist/features/wishlist/components/index.d.ts +2 -1
  202. package/dist/features/wishlist/components/index.js +1 -1
  203. package/dist/index.d.ts +10 -3
  204. package/dist/index.js +19 -4
  205. package/dist/monitoring/error-tracking.js +39 -5
  206. package/dist/monitoring/server-logger.js +70 -32
  207. package/dist/next/components/ErrorView.js +1 -1
  208. package/dist/next/routing/route-map.d.ts +39 -7
  209. package/dist/next/routing/route-map.js +34 -4
  210. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  211. package/dist/providers/db-firebase/base.repository.js +56 -9
  212. package/dist/react/ErrorBoundary.js +9 -1
  213. package/dist/react/contexts/SessionContext.js +6 -2
  214. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  215. package/dist/react/hooks/useAuthGate.js +86 -7
  216. package/dist/schemas/registry.d.ts +3 -3
  217. package/dist/security/index.d.ts +1 -1
  218. package/dist/security/index.js +1 -1
  219. package/dist/security/pii-encrypt.d.ts +13 -5
  220. package/dist/security/pii-encrypt.js +132 -6
  221. package/dist/security/pii-schemas.d.ts +32 -4
  222. package/dist/security/pii-schemas.js +38 -5
  223. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  224. package/dist/seed/_helpers/product-search-txt.js +33 -0
  225. package/dist/seed/_helpers/search-txt-wrappers.d.ts +20 -0
  226. package/dist/seed/_helpers/search-txt-wrappers.js +73 -0
  227. package/dist/seed/blog-posts-seed-data.d.ts +4 -0
  228. package/dist/seed/blog-posts-seed-data.js +7 -1
  229. package/dist/seed/events-seed-data.d.ts +5 -1
  230. package/dist/seed/events-seed-data.js +7 -1
  231. package/dist/seed/faq-seed-data.d.ts +16 -0
  232. package/dist/seed/faq-seed-data.js +22 -1
  233. package/dist/seed/products-art-seed-data.js +49 -7
  234. package/dist/seed/products-auctions-seed-data.js +12 -4
  235. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  236. package/dist/seed/products-classifieds-seed-data.js +65 -9
  237. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  238. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  239. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  240. package/dist/seed/products-live-items-seed-data.js +33 -5
  241. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  242. package/dist/seed/products-preorders-seed-data.js +6 -2
  243. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  244. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  245. package/dist/seed/products-standard-seed-data.js +6 -2
  246. package/dist/seed/products-stickers-seed-data.js +49 -7
  247. package/dist/seed/reviews-seed-data.js +2 -1
  248. package/dist/seed/site-settings-seed-data.js +5 -3
  249. package/dist/seed/stores-seed-data.d.ts +4 -0
  250. package/dist/seed/stores-seed-data.js +7 -1
  251. package/dist/seo/json-ld.d.ts +44 -2
  252. package/dist/seo/json-ld.js +57 -11
  253. package/dist/styles.css +368 -90
  254. package/dist/tailwind-utilities.css +1 -1
  255. package/dist/tokens/index.d.ts +1 -1
  256. package/dist/tokens/index.js +1 -1
  257. package/dist/tokens/themes/default-dark.js +6 -0
  258. package/dist/tokens/themes/default-light.js +5 -0
  259. package/dist/tokens/tokens.css +84 -0
  260. package/dist/ui/DataTable.js +1 -1
  261. package/dist/ui/DataTable.style.css +3 -3
  262. package/dist/ui/components/BulkActionBar.style.css +1 -3
  263. package/dist/ui/components/Button.js +15 -6
  264. package/dist/ui/components/Dropdown.style.css +1 -4
  265. package/dist/ui/components/FilterChipGroup.js +1 -1
  266. package/dist/ui/components/FilterDrawer.style.css +1 -1
  267. package/dist/ui/components/Icon.d.ts +53 -0
  268. package/dist/ui/components/Icon.js +41 -0
  269. package/dist/ui/components/IconButton.d.ts +13 -0
  270. package/dist/ui/components/IconButton.js +13 -0
  271. package/dist/ui/components/IconButton.style.css +18 -10
  272. package/dist/ui/components/ListingLayout.style.css +2 -10
  273. package/dist/ui/components/ListingToolbar.js +6 -6
  274. package/dist/ui/components/Modal.js +1 -1
  275. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  276. package/dist/ui/components/Pagination.style.css +1 -6
  277. package/dist/ui/components/RichTextEditor.js +1 -1
  278. package/dist/ui/components/RowActionMenu.style.css +1 -1
  279. package/dist/ui/components/SideDrawer.style.css +1 -1
  280. package/dist/ui/components/Slider.js +1 -1
  281. package/dist/ui/components/TabBarShell.d.ts +78 -0
  282. package/dist/ui/components/TabBarShell.js +95 -0
  283. package/dist/ui/components/Tabs.js +12 -8
  284. package/dist/ui/components/Tabs.style.css +249 -41
  285. package/dist/ui/components/ViewToggle.style.css +1 -5
  286. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  287. package/dist/ui/components/tabs-overflow.js +142 -0
  288. package/dist/ui/icons/icon-registry.d.ts +38 -0
  289. package/dist/ui/icons/icon-registry.js +37 -0
  290. package/dist/ui/index.d.ts +6 -0
  291. package/dist/ui/index.js +3 -0
  292. package/dist/utils/index.d.ts +1 -1
  293. package/dist/utils/index.js +1 -1
  294. package/dist/utils/media-url.d.ts +27 -4
  295. package/dist/utils/media-url.js +37 -6
  296. package/dist/utils/search-txt.d.ts +71 -0
  297. package/dist/utils/search-txt.js +118 -0
  298. package/package.json +1 -1
@@ -15,6 +15,7 @@ const addressFormSchema = z.object({
15
15
  phone: z.string().min(1, "Phone number is required"),
16
16
  addressLine1: z.string().min(1, "Address is required"),
17
17
  addressLine2: z.string().optional(),
18
+ landmark: z.string().max(100, "Keep the landmark under 100 characters.").optional(),
18
19
  city: z.string().min(1, "City is required"),
19
20
  state: z.string().min(1, "State is required"),
20
21
  postalCode: z.string().min(1, "Postal code is required"),
@@ -27,6 +28,7 @@ const DEFAULT_LABELS = {
27
28
  phone: "Phone Number",
28
29
  addressLine1: "Address Line 1",
29
30
  addressLine2: "Address Line 2",
31
+ landmark: "Landmark",
30
32
  city: "City",
31
33
  state: "State",
32
34
  postalCode: "Postal Code",
@@ -42,6 +44,7 @@ const DEFAULT_PLACEHOLDERS = {
42
44
  phone: "Enter phone number",
43
45
  addressLine1: "Street address, P.O. box",
44
46
  addressLine2: "Apartment, suite, unit, building, floor, etc.",
47
+ landmark: "Opposite the Metro station, next to the temple, etc.",
45
48
  city: "City",
46
49
  state: "State/Province",
47
50
  postalCode: "Postal/ZIP code",
@@ -58,6 +61,7 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
58
61
  phone: initialData?.phone || "",
59
62
  addressLine1: initialData?.addressLine1 || "",
60
63
  addressLine2: initialData?.addressLine2 || "",
64
+ landmark: initialData?.landmark || "",
61
65
  city: initialData?.city || "",
62
66
  state: initialData?.state || "",
63
67
  postalCode: initialData?.postalCode || "",
@@ -114,5 +118,5 @@ export function AddressForm({ initialData, onSubmit, onCancel, isLoading = false
114
118
  submitLabel: effectiveSubmitLabel,
115
119
  cancelLabel: mergedLabels.cancel,
116
120
  isLoading,
117
- }, children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsx(FormErrorSummary, {}), _jsxs(Row, { padding: "t-xs", align: "center", justify: "start", gap: "xs", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, isLoading: isLoading, onClick: () => void submitAddress({ setFieldError, clearErrors }), children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] })) }));
121
+ }, children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FormField, { label: mergedLabels.label, name: "label", type: "text", value: formData.label, onChange: (value) => handleChange("label", value), placeholder: mergedPlaceholders.label, required: true }), _jsx(FormField, { label: mergedLabels.fullName, name: "fullName", type: "text", value: formData.fullName, onChange: (value) => handleChange("fullName", value), placeholder: mergedPlaceholders.fullName, required: true }), _jsx(FormField, { label: mergedLabels.phone, name: "phone", type: "tel", value: formData.phone, onChange: (value) => handleChange("phone", value), placeholder: mergedPlaceholders.phone, required: true }), _jsx(FormField, { label: mergedLabels.addressLine1, name: "addressLine1", type: "text", value: formData.addressLine1, onChange: (value) => handleChange("addressLine1", value), placeholder: mergedPlaceholders.addressLine1, required: true }), _jsx(FormField, { label: mergedLabels.addressLine2, name: "addressLine2", type: "text", value: formData.addressLine2, onChange: (value) => handleChange("addressLine2", value), placeholder: mergedPlaceholders.addressLine2 }), _jsx(FormField, { label: mergedLabels.landmark, name: "landmark", type: "text", value: formData.landmark ?? "", onChange: (value) => handleChange("landmark", value), placeholder: mergedPlaceholders.landmark }), _jsxs(FormGroup, { columns: 3, children: [_jsx(FormField, { label: mergedLabels.city, name: "city", type: "text", value: formData.city, onChange: (value) => handleChange("city", value), placeholder: mergedPlaceholders.city, required: true }), _jsx(FormField, { label: mergedLabels.state, name: "state", type: "text", value: formData.state, onChange: (value) => handleChange("state", value), placeholder: mergedPlaceholders.state, required: true }), _jsx(FormField, { label: mergedLabels.postalCode, name: "postalCode", type: "text", value: formData.postalCode, onChange: (value) => handleChange("postalCode", value), placeholder: mergedPlaceholders.postalCode, required: true })] }), _jsx(FormField, { label: mergedLabels.country, name: "country", type: "text", value: formData.country, onChange: (value) => handleChange("country", value), placeholder: mergedPlaceholders.country, required: true }), _jsx(Checkbox, { checked: formData.isDefault, onChange: (e) => handleChange("isDefault", e.target.checked), label: mergedLabels.setDefault }), _jsx(FormErrorSummary, {}), _jsxs(Row, { padding: "t-xs", align: "center", justify: "start", gap: "xs", children: [_jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: isLoading, children: mergedLabels.cancel }), _jsx(Button, { type: "submit", variant: "primary", disabled: isLoading, isLoading: isLoading, onClick: () => void submitAddress({ setFieldError, clearErrors }), children: isLoading ? mergedLabels.loading : effectiveSubmitLabel })] })] })) }));
118
122
  }
@@ -51,5 +51,5 @@ export function UserBidsView() {
51
51
  },
52
52
  };
53
53
  const badge = detail ? bidStatusBadge(detail.status) : null;
54
- return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: badge ? [{ label: badge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "buyer") : undefined, footer: detail ? (_jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTION_DETAIL(detail.productId)), children: "View auction \u2192" })) : undefined })] }));
54
+ return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: detail?.productTitle || "Bid", badges: badge ? [{ label: badge.label }] : undefined, fields: detail ? buildBidDetailFields(detail, "buyer") : undefined, footer: detail ? (_jsxs(_Fragment, { children: [_jsx(TextLink, { href: String(ROUTES.USER.BID_DETAIL(detail.id)), children: "Open full page \u2192" }), _jsx(TextLink, { href: String(ROUTES.PUBLIC.AUCTION_DETAIL(detail.productId)), children: "View auction \u2192" })] })) : undefined })] }));
55
55
  }
@@ -27,7 +27,7 @@ function NavLink({ item, isActive, onClick }) {
27
27
  };
28
28
  const linkClass = `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
29
29
  ? "bg-primary-50 dark:bg-primary-900/25 text-primary-700 dark:text-primary-300"
30
- : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)] hover:text-zinc-900 dark:hover:text-zinc-100"}`;
30
+ : "text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-900 dark:hover:text-zinc-100"}`;
31
31
  return (_jsxs(_Fragment, { children: [_jsxs(Link, { href: item.href, onClick: handleClick, className: linkClass, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-70", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }), showConfirm && item.confirm && (_jsx(ConfirmDeleteModal, { isOpen: true, title: item.confirm.title ?? "Confirm", message: item.confirm.message, confirmText: "Continue", variant: "warning", onConfirm: () => {
32
32
  setShowConfirm(false);
33
33
  onClick?.();
@@ -7,6 +7,8 @@ import { Button, Div, FormActions, Row, Select, SideDrawer, Span, Stack, Text, U
7
7
  import { FieldInput } from "../../../ui/forms/FieldInput";
8
8
  import { FieldTextarea } from "../../../ui/forms/FieldTextarea";
9
9
  import { apiClient } from "../../../http";
10
+ import { supportTicketCreateSchema } from "../../support/schemas/ticket-create-form";
11
+ import { ValidationError } from "../../../errors/validation-error";
10
12
  import { SUPPORT_ENDPOINTS } from "../../../constants/api-endpoints";
11
13
  const __P = {
12
14
  p3: "p-[var(--appkit-space-3)]",
@@ -33,7 +35,7 @@ const CATEGORY_OPTIONS = [
33
35
  { label: "Appeal a ban (unban request)", value: "unban_request" },
34
36
  { label: "General", value: "general" },
35
37
  ];
36
- const CLS_MSG_USER = "bg-zinc-50 border border-zinc-200 bg-[var(--appkit-color-surface)]/40 border-[var(--appkit-color-border)]";
38
+ const CLS_MSG_USER = "border bg-[var(--appkit-color-surface)]/40 border-[var(--appkit-color-border)]";
37
39
  const CLS_MSG_STAFF = "bg-info-surface border border-info dark:bg-info-surface dark:border-info";
38
40
  const STATUS_BADGE = {
39
41
  open: "bg-info-surface text-info",
@@ -68,12 +70,32 @@ export function UserSupportView(_props) {
68
70
  const invalidate = () => queryClient.invalidateQueries({ queryKey: ["user", "support-tickets"] });
69
71
  const createMutation = useApiMutation({
70
72
  mutationFn: async () => {
71
- await apiClient.post(SUPPORT_ENDPOINTS.TICKETS, {
73
+ /*
74
+ * This drawer validated NOTHING. Its sibling page
75
+ * (`/user/support/new`) hand-rolled a `canSubmit` boolean that required
76
+ * an order id for an `order_issue` ticket — a real rule, since an order
77
+ * complaint with no order is unactionable — and the route did not
78
+ * enforce it either. So the same ticket was acceptable or not purely
79
+ * depending on which surface the user happened to open.
80
+ *
81
+ * One schema now, across both surfaces and the route.
82
+ */
83
+ const parsed = supportTicketCreateSchema.safeParse({
72
84
  subject: newSubject.trim(),
73
85
  category: newCategory,
74
86
  description: newDescription.trim(),
75
87
  orderId: newOrderId.trim() || undefined,
76
88
  });
89
+ if (!parsed.success) {
90
+ /*
91
+ * ValidationError, not a bare Error — it carries the issue LIST, so
92
+ * the failure keeps which field caused it. A plain Error flattens the
93
+ * whole thing to one string, which is what `audit-unvalidated-safeparse`
94
+ * exists to stop. It caught this in my own code.
95
+ */
96
+ throw new ValidationError(parsed.error.issues[0]?.message ?? "Check the ticket details.", parsed.error.issues);
97
+ }
98
+ await apiClient.post(SUPPORT_ENDPOINTS.TICKETS, parsed.data);
77
99
  },
78
100
  onSuccess: () => {
79
101
  showToast("Support ticket created.", "success");
@@ -9,6 +9,18 @@ export interface Address {
9
9
  state: string;
10
10
  postalCode: string;
11
11
  country: string;
12
+ /**
13
+ * A nearby reference point ("opposite the Metro station").
14
+ *
15
+ * Store addresses have always accepted it — `POST /api/store/addresses`
16
+ * declares `landmark: z.string().max(100).optional()` and
17
+ * `SellerAddressesView`'s drawer collects it — while this shape did not.
18
+ * So an address created in that drawer WITH a landmark, then edited through
19
+ * any surface built on `AddressForm`, was saved back with `landmark`
20
+ * absent and lost it. Added here, on the form, and on the `Address` type so
21
+ * the round trip is lossless.
22
+ */
23
+ landmark?: string;
12
24
  isDefault?: boolean;
13
25
  }
14
26
  export interface AddressFormData {
@@ -21,6 +33,8 @@ export interface AddressFormData {
21
33
  state: string;
22
34
  postalCode: string;
23
35
  country: string;
36
+ /** See Address.landmark — the form must carry it or an edit drops it. */
37
+ landmark?: string;
24
38
  isDefault?: boolean;
25
39
  }
26
40
  interface SetDefaultAddressData {
@@ -46,8 +60,22 @@ export declare function useCreateAddress(options?: {
46
60
  onSuccess?: (data: Address) => void;
47
61
  onError?: (error: Error) => void;
48
62
  }): import("@tanstack/react-query").UseMutationResult<Address, Error, AddressFormData, unknown>;
49
- export declare function useUpdateAddress(id: string, options?: {
63
+ /**
64
+ * Update options.
65
+ *
66
+ * `method` exists because the two portals disagree: `/api/user/addresses/[id]`
67
+ * exports PATCH and `/api/store/addresses/[id]` exports PUT, and
68
+ * `SellerAddressesView` hardcodes PUT against the latter. Changing the route's
69
+ * verb would break that working call site, so the hook bends instead.
70
+ *
71
+ * Caught by `audit-client-verb-match` before the store pages were written —
72
+ * this hook would have 405'd the moment they reused it.
73
+ */
74
+ export interface UpdateAddressOptions {
50
75
  byIdEndpoint?: (id: string) => string;
76
+ method?: "PUT" | "PATCH";
77
+ }
78
+ export declare function useUpdateAddress(id: string, options?: UpdateAddressOptions & {
51
79
  onSuccess?: (data: Address) => void;
52
80
  onError?: (error: Error) => void;
53
81
  }): import("@tanstack/react-query").UseMutationResult<Address, Error, AddressFormData, unknown>;
@@ -48,8 +48,11 @@ export function useCreateAddress(options) {
48
48
  export function useUpdateAddress(id, options) {
49
49
  const queryClient = useQueryClient();
50
50
  const byIdEndpoint = options?.byIdEndpoint ?? DEFAULT_ENDPOINTS.byId;
51
+ const method = options?.method ?? "PATCH";
51
52
  return useMutation({
52
- mutationFn: (data) => apiClient.patch(byIdEndpoint(id), data),
53
+ mutationFn: (data) => method === "PUT"
54
+ ? apiClient.put(byIdEndpoint(id), data)
55
+ : apiClient.patch(byIdEndpoint(id), data),
53
56
  onSuccess: (data) => {
54
57
  queryClient.invalidateQueries({ queryKey: ["addresses"] });
55
58
  options?.onSuccess?.(data);
@@ -1,4 +1,4 @@
1
- import { type JsonValue } from "@mohasinac/appkit/client";
1
+ import type { JsonValue } from "../../../schemas/types";
2
2
  export interface UserProfile {
3
3
  uid: string;
4
4
  email: string | null;
@@ -1,4 +1,4 @@
1
- import { useApiMutation } from "@mohasinac/appkit/client";
1
+ import { useApiMutation } from "../../../client/api/useApiMutation";
2
2
  import { useQuery, useQueryClient } from "@tanstack/react-query";
3
3
  import { apiClient } from "../../../http";
4
4
  import { ACCOUNT_ENDPOINTS } from "../../../constants/api-endpoints";
@@ -11,6 +11,19 @@ export declare class AddressesRepository extends BaseRepository<AddressDocument>
11
11
  listByOwnerType(ownerType: AddressOwnerType, limit?: number): Promise<AddressDocument[]>;
12
12
  countByOwner(ownerType: AddressOwnerType, ownerId: string): Promise<number>;
13
13
  createForOwner(ownerType: AddressOwnerType, ownerId: string, input: AddressCreateInput): Promise<AddressDocument>;
14
+ /**
15
+ * Read one address, scoped to its owner.
16
+ *
17
+ * The owner check is `ownerType` AND `ownerId` — the same pair
18
+ * `updateForOwner` and `deleteForOwner` use, because `addresses` is one
19
+ * top-level collection holding both buyer and store addresses discriminated
20
+ * by `ownerType` (SB-UNI-A). Checking only `ownerId` would let a seller read
21
+ * their own personal address through the store endpoint and vice versa.
22
+ *
23
+ * Returns null rather than throwing: the caller turns it into a 404, and a
24
+ * missing address and a foreign one must be indistinguishable to the client.
25
+ */
26
+ getForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<AddressDocument | null>;
14
27
  updateForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string, input: AddressUpdateInput): Promise<AddressDocument>;
15
28
  deleteForOwner(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<void>;
16
29
  setDefault(ownerType: AddressOwnerType, ownerId: string, addressId: string): Promise<AddressDocument>;
@@ -115,6 +115,25 @@ export class AddressesRepository extends BaseRepository {
115
115
  throw new DatabaseError(`Failed to create address for ${ownerType}:${ownerId}`, error);
116
116
  }
117
117
  }
118
+ /**
119
+ * Read one address, scoped to its owner.
120
+ *
121
+ * The owner check is `ownerType` AND `ownerId` — the same pair
122
+ * `updateForOwner` and `deleteForOwner` use, because `addresses` is one
123
+ * top-level collection holding both buyer and store addresses discriminated
124
+ * by `ownerType` (SB-UNI-A). Checking only `ownerId` would let a seller read
125
+ * their own personal address through the store endpoint and vice versa.
126
+ *
127
+ * Returns null rather than throwing: the caller turns it into a 404, and a
128
+ * missing address and a foreign one must be indistinguishable to the client.
129
+ */
130
+ async getForOwner(ownerType, ownerId, addressId) {
131
+ const existing = await this.findById(addressId);
132
+ if (!existing || existing.ownerType !== ownerType || existing.ownerId !== ownerId) {
133
+ return null;
134
+ }
135
+ return existing;
136
+ }
118
137
  async updateForOwner(ownerType, ownerId, addressId, input) {
119
138
  const existing = await this.findById(addressId);
120
139
  if (!existing || existing.ownerType !== ownerType || existing.ownerId !== ownerId) {
@@ -14,6 +14,7 @@ import { getAdminRealtimeDb } from "../../../providers/db-firebase/index";
14
14
  import { AuthorizationError, NotFoundError, ValidationError, } from "../../../errors/index";
15
15
  import { ERROR_MESSAGES } from "../../../errors/messages";
16
16
  import { serverLogger } from "../../../monitoring/index";
17
+ import { normalizeError } from "../../../errors/normalize";
17
18
  export async function getChatRooms(userId, chatEnabled) {
18
19
  if (!chatEnabled)
19
20
  throw new AuthorizationError("Chat is temporarily unavailable.");
@@ -77,7 +78,10 @@ export async function sendChatMessage(userId, chatId, message) {
77
78
  : (room.buyerName ?? "Member");
78
79
  await msgRef.set({ userId, userName, message, timestamp: Date.now() });
79
80
  chatRepository.updateLastMessage(chatId, message).catch((err) => {
80
- serverLogger.warn("Failed to update chat lastMessage", { chatId, err });
81
+ serverLogger.warn("Failed to update chat lastMessage", {
82
+ chatId,
83
+ error: normalizeError(err).message,
84
+ });
81
85
  });
82
86
  serverLogger.debug("sendChatMessage", { userId, chatId });
83
87
  return { messageId: id, timestamp: Date.now() };
@@ -116,7 +116,7 @@ export function AdminAdsView({ endpoint = ADMIN_ENDPOINTS.ADS, labels = {}, crea
116
116
  render: (row) => {
117
117
  const nextStatus = row.status === "active" ? "paused" : "active";
118
118
  const cannotPublish = nextStatus === "active" && row.publishReady === false;
119
- return (_jsxs(Row, { align: "center", gap: "sm", children: [renderEditLink ? renderEditLink(row) : (_jsx(TextLink, { variant: "bare", href: `/admin/ads/${row.id}/edit`, rounded: "md", paddingX: "sm", size: "xs", layout: "inline-flex", align: "center", className: "h-8 border border-neutral-200 border-[var(--appkit-color-border)]", children: "Edit" })), _jsx(Button, { size: "sm", variant: "outline", disabled: statusMutation.isPending || cannotPublish, onClick: () => statusMutation.mutate({ id: row.id, nextStatus }), children: row.status === "active" ? "Pause" : "Publish" })] }));
119
+ return (_jsxs(Row, { align: "center", gap: "sm", children: [renderEditLink ? renderEditLink(row) : (_jsx(TextLink, { variant: "bare", href: `/admin/ads/${row.id}/edit`, rounded: "md", paddingX: "sm", size: "xs", layout: "inline-flex", align: "center", className: "h-8 border border-[var(--appkit-color-border)]", children: "Edit" })), _jsx(Button, { size: "sm", variant: "outline", disabled: statusMutation.isPending || cannotPublish, onClick: () => statusMutation.mutate({ id: row.id, nextStatus }), children: row.status === "active" ? "Pause" : "Publish" })] }));
120
120
  },
121
121
  },
122
122
  ];
@@ -15,6 +15,8 @@ import { toRecordArray, toRelativeDate, toCurrency, toStringValue, } from "../ho
15
15
  import { DataListingView } from "./DataListingView";
16
16
  import { apiClient } from "../../../http";
17
17
  import { RecordStatusTimeline } from "../../status-history/components/RecordStatusTimeline";
18
+ import { TextLink } from "../../../ui";
19
+ import { ROUTES } from "../../../constants/index";
18
20
  export function AdminBidsView({ children, ...props }) {
19
21
  const queryClient = useQueryClient();
20
22
  const { showToast } = useToast();
@@ -99,7 +101,10 @@ export function AdminBidsView({ children, ...props }) {
99
101
  const detailBadge = detail ? bidStatusBadge(detail.status) : null;
100
102
  return (_jsxs(_Fragment, { children: [_jsx(DataListingView, { config: config }), _jsx(RecordDetailModal, { isOpen: detail !== null, onClose: () => setDetail(null), title: toStringValue(detail?.detail?.productTitle, "Bid"), badges: detailBadge ? [{ label: detailBadge.label }] : undefined, fields: detail
101
103
  ? buildBidDetailFields(detail.detail, "admin")
102
- : undefined, extra:
104
+ : undefined, footer: detail ? (
105
+ // The full page — a modal cannot be linked, bookmarked or reloaded
106
+ // into, which is the whole reason the page exists.
107
+ _jsx(TextLink, { href: String(ROUTES.ADMIN.BID_DETAIL(detail.id)), children: "Open full page \u2192" })) : undefined, extra:
103
108
  // A bid moves through more states than any record a buyer owns, and
104
109
  // every one of them happened inside a settlement batch with nothing
105
110
  // recording it until W18. Absent history renders the empty label,
@@ -2,5 +2,13 @@ import React from "react";
2
2
  import type { StackedViewShellProps } from "../../../ui";
3
3
  export interface AdminCarouselGroupEditorViewProps extends Omit<StackedViewShellProps, "sections"> {
4
4
  onCreated?: (id: string) => void;
5
+ /**
6
+ * Present → edit that carousel; absent → create a new one.
7
+ *
8
+ * This component was create-only, which is why `/admin/carousels/[id]/edit`
9
+ * did not exist. Same `featureId?`-style seam `AdminFeatureEditorView` uses.
10
+ */
11
+ carouselId?: string;
12
+ onSaved?: (id: string) => void;
5
13
  }
6
- export declare function AdminCarouselGroupEditorView({ onCreated, ...rest }: AdminCarouselGroupEditorViewProps): React.JSX.Element;
14
+ export declare function AdminCarouselGroupEditorView({ onCreated, carouselId, onSaved, ...rest }: AdminCarouselGroupEditorViewProps): React.JSX.Element;
@@ -3,6 +3,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
3
3
  import React from "react";
4
4
  import { z } from "zod";
5
5
  import { useRouter } from "next/navigation";
6
+ import { useQuery } from "@tanstack/react-query";
6
7
  import { Button, Form, Row, StackedViewShell, useToast } from "../../../ui";
7
8
  import { FieldInput, FieldSelect, FormErrorSummary } from "../../../ui/forms";
8
9
  import { useApiMutation } from "@mohasinac/appkit/client";
@@ -13,28 +14,59 @@ const carouselGroupFormSchema = z.object({
13
14
  name: z.string().min(1, "Name is required").max(200),
14
15
  status: z.enum(["active", "draft"]),
15
16
  });
16
- export function AdminCarouselGroupEditorView({ onCreated, ...rest }) {
17
+ export function AdminCarouselGroupEditorView({ onCreated, carouselId, onSaved, ...rest }) {
17
18
  const { showToast } = useToast();
18
19
  const router = useRouter();
20
+ const isEdit = Boolean(carouselId);
19
21
  const [name, setName] = React.useState("");
20
22
  const [status, setStatus] = React.useState("draft");
23
+ // Seed from the single-item GET, never from a cached list row — a list
24
+ // projection is narrower than the document, and saving would write the
25
+ // missing fields back as their defaults (Root Cause #38).
26
+ const existing = useQuery({
27
+ queryKey: ["admin", "carousel", carouselId],
28
+ queryFn: () => apiClient.get(ADMIN_ENDPOINTS.CAROUSELS_BY_ID(carouselId)),
29
+ enabled: isEdit,
30
+ });
31
+ React.useEffect(() => {
32
+ const doc = existing.data?.data;
33
+ if (!doc)
34
+ return;
35
+ setName(doc.name ?? "");
36
+ setStatus(doc.status === "active" ? "active" : "draft");
37
+ }, [existing.data]);
21
38
  const createMutation = useApiMutation({
22
- mutationFn: () => apiClient.post(ADMIN_ENDPOINTS.CAROUSELS, { name, status }),
39
+ /*
40
+ * PATCH on the edit path, not PUT. `/api/admin/carousels/[id]` exports
41
+ * GET/PATCH/DELETE — copying `AdminFeatureEditorView`'s `apiClient.put`
42
+ * here would have been a 405, which is exactly what
43
+ * `audit-client-verb-match` exists to catch.
44
+ */
45
+ mutationFn: () => isEdit
46
+ ? apiClient.patch(ADMIN_ENDPOINTS.CAROUSELS_BY_ID(carouselId), { name, status })
47
+ : apiClient.post(ADMIN_ENDPOINTS.CAROUSELS, { name, status }),
23
48
  onSuccess: (res) => {
24
- const id = res?.data?.id;
25
- showToast("Carousel created.", "success");
26
- if (id) {
27
- if (onCreated)
28
- onCreated(id);
49
+ const id = res?.data?.id ?? carouselId;
50
+ showToast(isEdit ? "Carousel updated." : "Carousel created.", "success");
51
+ if (!id)
52
+ return;
53
+ if (isEdit) {
54
+ if (onSaved)
55
+ onSaved(id);
29
56
  else
30
57
  router.push(String(ROUTES.ADMIN.CAROUSEL_DETAIL(id)));
58
+ return;
31
59
  }
60
+ if (onCreated)
61
+ onCreated(id);
62
+ else
63
+ router.push(String(ROUTES.ADMIN.CAROUSEL_DETAIL(id)));
32
64
  },
33
65
  onError: (err) => {
34
- showToast(err?.message ?? "Failed to create carousel.", "error");
66
+ showToast(err?.message ?? `Failed to ${isEdit ? "update" : "create"} carousel.`, "error");
35
67
  },
36
68
  });
37
- return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: "New Named Carousel", sections: [
69
+ return (_jsx(StackedViewShell, { portal: "admin", ...rest, title: isEdit ? "Edit Carousel" : "New Named Carousel", sections: [
38
70
  _jsx(Form, { schema: carouselGroupFormSchema, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "name", label: "Carousel name", value: name, onChange: (v) => setName(v), required: true, placeholder: "e.g. Homepage Hero" }), _jsx(FieldSelect, { name: "status", label: "Status", value: status, onChange: (v) => setStatus(v), options: [
39
71
  { value: "draft", label: "Draft" },
40
72
  { value: "active", label: "Active" },
@@ -45,6 +77,6 @@ export function AdminCarouselGroupEditorView({ onCreated, ...rest }) {
45
77
  return;
46
78
  }
47
79
  createMutation.mutate();
48
- }, children: "Create carousel" }) })] })) }, "carousel-group-form"),
80
+ }, children: isEdit ? "Save changes" : "Create carousel" }) })] })) }, "carousel-group-form"),
49
81
  ] }));
50
82
  }
@@ -145,5 +145,5 @@ export function AdminCarouselView({ children, onBulkDelete, ...props }) {
145
145
  ] })),
146
146
  },
147
147
  ];
148
- return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search slide titles", onSearchChange: setSearchInput, onSearchCommit: commitSearch, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), _jsx(DataTable, { rows: displayRows, columns: columns, isLoading: isLoading, emptyLabel: "No carousel slides \u2014 use 'New slide' to add one", rowHrefTemplate: "/admin/carousel/{id}/edit" })] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Status" }), _jsx(Row, { wrap: true, gap: "sm", children: [{ label: "All", value: "" }, { label: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx(Button, { variant: "ghost", type: "button", onClick: () => setPendingFilters((p) => ({ ...p, active: opt.value })), className: `rounded-full px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium border transition-colors min-h-0 h-auto ${(pendingFilters.active || "") === opt.value ? "bg-primary text-white border-primary" : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]"}`, children: opt.label }, opt.label))) })] }) }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Carousel Slide", message: "Delete this carousel slide? This cannot be undone.", onConfirm: () => { deleteMutation.mutate(deleteTargetId); setDeleteTargetId(null); }, onClose: () => setDeleteTargetId(null), isDeleting: deleteMutation.isPending }))] }));
148
+ return (_jsxs(Div, { className: "min-h-screen", children: [_jsx(ListingToolbar, { filterCount: activeFilterCount, onFiltersClick: openFilters, searchValue: searchInput, searchPlaceholder: "Search slide titles", onSearchChange: setSearchInput, onSearchCommit: commitSearch, showTableView: true, view: view, onViewChange: (v) => setView(v), onResetAll: resetAll, hasActiveState: hasActiveState }), totalPages > 1 && (_jsx(StickyToolbar, { offset: "header+pagination", tone: "translucent", border: true, padding: "toolbar", children: _jsx(Row, { justify: "center", children: _jsx(Pagination, { currentPage: currentPage, totalPages: totalPages, onPageChange: (p) => table.setPage(p) }) }) })), selection.selectedCount > 0 && bulkActions.length > 0 && (_jsx(BulkActionBar, { selectedCount: selection.selectedCount, actions: bulkActions, onClearSelection: selection.clearSelection })), _jsxs(Div, { paddingX: "x-sm-md", padding: "y-md", children: [errorMessage && (_jsx(Div, { textSize: "sm", className: "mb-4 border border-error/20", color: "error", surface: "danger-surface", padding: "inline", rounded: "xl", children: errorMessage })), _jsx(DataTable, { rows: displayRows, columns: columns, isLoading: isLoading, emptyLabel: "No carousel slides \u2014 use 'New slide' to add one", rowHrefTemplate: "/admin/carousel/{id}/edit" })] }), _jsx(ListingFilterDrawer, { open: filterOpen, onClose: () => setFilterOpen(false), onApply: applyFilters, onClear: clearFilters, activeCount: activeFilterCount, children: _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { className: "tracking-widest", color: "muted", size: "xs", weight: "semibold", transform: "uppercase", children: "Status" }), _jsx(Row, { wrap: true, gap: "sm", children: [{ label: "All", value: "" }, { label: "Active", value: "true" }, { label: "Inactive", value: "false" }].map((opt) => (_jsx(Button, { variant: "ghost", type: "button", onClick: () => setPendingFilters((p) => ({ ...p, active: opt.value })), className: `rounded-full px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] font-medium border transition-colors min-h-0 h-auto ${(pendingFilters.active || "") === opt.value ? "bg-primary text-white border-primary" : "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-surface-hover"}`, children: opt.label }, opt.label))) })] }) }), deleteTargetId && (_jsx(ConfirmDeleteModal, { isOpen: true, title: "Delete Carousel Slide", message: "Delete this carousel slide? This cannot be undone.", onConfirm: () => { deleteMutation.mutate(deleteTargetId); setDeleteTargetId(null); }, onClose: () => setDeleteTargetId(null), isDeleting: deleteMutation.isPending }))] }));
149
149
  }
@@ -14,7 +14,7 @@ export function AdminContentGuideView() {
14
14
  },
15
15
  {
16
16
  Icon: BookOpen, title: "FAQs",
17
- content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Categories" }), " and where they appear: Shipping (help + footer), Returns (help), Payments (help), Auctions (help), Pre-orders (help). Set ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "showOnHomepage: true" }), " for up to 5 FAQs shown on the public home page."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "searchTokens[]" }), ": Must be manually filled \u2014 drives the FAQ search feature. Include synonyms, misspellings, and related keywords."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "priority + order" }), ": ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "priority" }), " sorts FAQs within their category; ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "order" }), " is the global absolute position. ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "isPinned: true" }), " floats the FAQ above non-pinned items in its category."] })] })),
17
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Categories" }), " and where they appear: Shipping (help + footer), Returns (help), Payments (help), Auctions (help), Pre-orders (help). Set ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "showOnHomepage: true" }), " for up to 5 FAQs shown on the public home page."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Search" }), ": handled automatically \u2014 search terms are derived from the question, answer, category and tags when you save. There is nothing to fill in."] }), _jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "priority + order" }), ": ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "priority" }), " sorts FAQs within their category; ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "order" }), " is the global absolute position. ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "isPinned: true" }), " floats the FAQ above non-pinned items in its category."] })] })),
18
18
  },
19
19
  {
20
20
  Icon: Layout, title: "Carousel Slides",
@@ -19,7 +19,7 @@ const __O = {
19
19
  };
20
20
  const PILL_BASE_CLASS = "inline-flex items-center gap-[var(--appkit-space-1-5)] rounded-full border px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-xs)] font-medium cursor-pointer transition-colors";
21
21
  const PILL_CHECKED_CLASS = "bg-primary text-white border-primary";
22
- const PILL_UNCHECKED_CLASS = "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]";
22
+ const PILL_UNCHECKED_CLASS = "border-[var(--appkit-color-border)] text-[var(--appkit-color-text-muted)] hover:bg-surface-hover";
23
23
  const FIELD_LABEL_CLASS = "text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)]";
24
24
  const DELETE_CONFIRM_TEXT = "Delete this feature? It will fail if any product still references it.";
25
25
  const TOAST = {
@@ -8,12 +8,13 @@ import { normalizeError } from "../../../errors/normalize";
8
8
  import { sortBy } from "@mohasinac/appkit/client";
9
9
  import React, { useState } from "react";
10
10
  import { ListingLayout } from "../../../ui";
11
- import { Button, Row, SideDrawer, Stack, Text, useToast, } from "../../../ui";
11
+ import { Button, Row, SideDrawer, Stack, Text, TextLink, useToast, } from "../../../ui";
12
12
  import { toRecordArray, toRelativeDate, toStringValue, } from "../hooks/useAdminListingData";
13
13
  import { DataListingView } from "./DataListingView";
14
14
  import { ProductInlineSelect } from "../../seller/components/ProductInlineSelect";
15
15
  import { apiClient } from "../../../http";
16
16
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
17
+ import { ROUTES } from "../../../next/routing/route-map";
17
18
  function ReassignProductsDrawer({ state, onClose, onSaved, }) {
18
19
  const [productIds, setProductIds] = useState(state.currentProductIds);
19
20
  const [saving, setSaving] = useState(false);
@@ -75,10 +76,21 @@ export function AdminGroupedListingsView({ children, ...props }) {
75
76
  })),
76
77
  getTotal: (response, mappedRows) => typeof response.total === "number" ? response.total : mappedRows.length,
77
78
  buildFilters: () => "",
79
+ /*
80
+ * Admin had NO create path at all — there was no POST route either,
81
+ * so this could not have been wired before now.
82
+ *
83
+ * `toolbarExtra` rather than `primaryAction`: the latter takes an
84
+ * `onClick(panel)` for opening a create DRAWER, and this create is a
85
+ * page. Same shape `AdminAdsView` uses, and `audit-create-affordance`
86
+ * counts both — which is exactly why it has to, or four views with a
87
+ * perfectly good create link would read as having none.
88
+ */
89
+ toolbarExtra: (_jsx(TextLink, { variant: "bare", href: String(ROUTES.ADMIN.GROUPED_LISTINGS_NEW), rounded: "md", paddingX: "sm", size: "sm", weight: "medium", layout: "inline-flex", align: "center", className: "h-9 bg-[var(--appkit-color-surface)] text-[var(--appkit-color-text)]", children: "New group" })),
78
90
  // Mirrors the row action button's "Reassign products" drawer so the
79
91
  // row itself is clickable, not just the button.
80
92
  onRowClick: (row) => openReassign(row),
81
- renderRowActions: (row) => (_jsx(Button, { size: "sm", variant: "outline", onClick: () => openReassign(row), children: "Reassign products" })),
93
+ renderRowActions: (row) => (_jsxs(Row, { gap: "xs", children: [_jsx(TextLink, { href: String(ROUTES.ADMIN.GROUPED_LISTINGS_EDIT(row.id)), children: "Edit" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => openReassign(row), children: "Reassign products" })] })),
82
94
  }), [refreshKey, openReassign]);
83
95
  if (React.Children.count(children) > 0) {
84
96
  return (_jsx(ListingLayout, { portal: "admin", ...props, children: children }));
@@ -17,7 +17,7 @@ const __O = {
17
17
  function NavLink({ item, isActive, onClick }) {
18
18
  return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
19
19
  ? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"
20
- : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }));
20
+ : "text-[var(--appkit-color-text-muted)] hover:bg-surface-hover hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }));
21
21
  }
22
22
  function GroupsContent({ groups, activePath, onItemClick, }) {
23
23
  // Accordion — only one group open at a time. Initial pick: the group
@@ -52,7 +52,7 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, render
52
52
  };
53
53
  if (view === "list") {
54
54
  return (_jsxs(Row, { gap: "sm", className: [
55
- "px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] cursor-pointer transition-colors hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/50",
55
+ "px-[var(--appkit-space-4)] py-[var(--appkit-space-3)] cursor-pointer transition-colors hover:bg-surface-hover",
56
56
  selected ? "bg-primary-50/40 dark:bg-primary-900/10" : "",
57
57
  ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: _jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }) })), _jsx(RowAvatar, { image: row.image, alt: row.primary, icon: resourceIcon }), _jsxs(Stack, { gap: "none", className: "flex-1 min-w-0", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "truncate", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId }))] }), flags.length > 0 && (_jsx(Row, { gap: "xs", className: "hidden sm:flex shrink-0", children: flags.map(({ key, label, tone }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${STATUS_TONE_CLASSES[tone]}`, rounded: "full", children: label }, key))) })), _jsx(StatusBadge, { status: row.status }), _jsx(Span, { size: "xs", color: "muted", className: "hidden sm:block shrink-0 w-24", align: "end", children: row.updatedAt }), renderRowActions && (_jsx(Div, { "data-no-row-click": true, className: "shrink-0", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] }));
58
58
  }
@@ -60,7 +60,7 @@ function AdminCardItem({ row, view, selected, onToggleSelect, onRowClick, render
60
60
  "border overflow-hidden cursor-pointer transition-all hover:shadow-md hover:-translate-y-0.5",
61
61
  selected
62
62
  ? "border-primary ring-1 ring-primary/20 bg-primary-50/30 dark:bg-primary-900/10"
63
- : "border-zinc-100 border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]",
63
+ : " border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]",
64
64
  ].filter(Boolean).join(" "), onClick: handleClick, role: onRowClick ? "button" : undefined, children: [onToggleSelect && (_jsxs(Row, { gap: "xs", "data-no-row-click": true, paddingY: "t-sm", padding: "x-sm", onClick: (e) => { e.stopPropagation(); onToggleSelect(row.id); }, children: [_jsx(Checkbox, { bare: true, checked: selected, onChange: () => onToggleSelect(row.id), className: "h-4 w-4 rounded border-zinc-300 text-primary accent-primary cursor-pointer", "aria-label": `Select ${row.primary}` }), flags.length > 0 && (_jsx(Row, { gap: "xs", wrap: true, children: flags.map(({ key, label, tone }) => (_jsx(Span, { padding: "pill-2xs", weight: "medium", className: `inline-flex text-[10px] ${STATUS_TONE_CLASSES[tone]}`, rounded: "full", children: label }, key))) }))] })), _jsxs(Stack, { gap: "xs", className: `${__P.p3}.5`, children: [_jsxs(Row, { gap: "sm", align: "center", children: [_jsx(RowAvatar, { image: row.image, alt: row.primary, icon: resourceIcon, size: "12" }), _jsxs(Stack, { gap: "none", className: "min-w-0 flex-1", children: [_jsx(Text, { size: "sm", weight: "semibold", className: "line-clamp-2 leading-snug", color: "primary", children: row.primary }), _jsx(Text, { size: "xs", color: "muted", className: "truncate", children: row.secondary })] })] }), row.barcodeId && (_jsx(Text, { size: "xs", color: "faint", className: "truncate font-mono", children: row.barcodeId })), _jsxs(Row, { justify: "between", gap: "xs", children: [_jsx(StatusBadge, { status: row.status }), _jsx(Span, { color: "muted", className: "text-[11px] shrink-0", children: row.updatedAt })] }), renderRowActions && (_jsx(Row, { "data-no-row-click": true, justify: "end", gap: "xs", className: "border-t border-[var(--appkit-color-border)] -mx-[var(--appkit-space-3)] -mb-[var(--appkit-space-3)] mt-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-2)]", onClick: (e) => e.stopPropagation(), children: renderRowActions(row) }))] })] }));
65
65
  }
66
66
  export function AdminViewCards({ rows, view, isLoading, emptyLabel = "No items found", onRowClick, selectedIdSet, onToggleSelect, renderRowActions, resourceIcon, }) {
@@ -39,7 +39,7 @@ function SelectableRow({ row, columns, isSelected, onToggle, renderRowActions, o
39
39
  : undefined;
40
40
  const handleKeyDown = handleClick
41
41
  ? (event) => {
42
- if (event.key === "Enter" || event.key === " ")
42
+ if (event.key === "Enter" || event.key === "")
43
43
  handleClick();
44
44
  }
45
45
  : undefined;
@@ -54,5 +54,5 @@ export function DataTable({ columns: columnsProp, rows, isLoading, sortKey, sort
54
54
  const allRowsSelected = selectionEnabled && rows.length > 0 && rows.every((r) => selectedIds?.has(r.id));
55
55
  return (_jsxs(Div, { surface: "card", className: `${__O.hidden}`, children: [_jsx(Div, { className: `${__O.xAuto}`, children: _jsxs(Table, { "data-testid": "data-table", size: "sm", children: [_jsx(Thead, { children: _jsxs(Tr, { className: "bg-[var(--appkit-color-bg)] dark:bg-[var(--appkit-color-surface-elevated)]", border: "default", children: [selectionEnabled && (_jsx(Th, { padding: "xs-3", scope: "col", className: "w-10", children: onToggleSelectAll && (_jsx(Checkbox, { bare: true, "data-testid": "select-all-checkbox", "aria-label": allRowsSelected ? "Deselect all" : "Select all", checked: allRowsSelected, onChange: () => onToggleSelectAll(!allRowsSelected), className: "h-4 w-4 rounded border-[var(--appkit-color-border)] accent-zinc-900 dark:accent-zinc-100" })) })), columns.map((col) => (_jsxs(Th, { scope: "col", onClick: col.sortable && onSort ? () => onSort(col.key) : undefined, className: `text-left text-[var(--appkit-color-text)] ${col.sortable && onSort ? "cursor-pointer select-none hover:text-primary" : ""} ${COLUMN_PRIORITY_CLASS[resolveColumnPriority(col)]} ${col.className ?? ""}`, padding: "md", weight: "semibold", children: [col.header, col.sortable && sortKey === col.key && (_jsx(Span, { className: "ml-1", children: sortDir === "asc" ? "↑" : "↓" }))] }, col.key))), renderRowActions && _jsx(Th, { padding: "xs-3", scope: "col", className: "w-12" })] }) }), _jsx(Tbody, { children: isLoading ? (Array.from({ length: 5 }).map((_, i) => (_jsxs(Tr, { border: "default", children: [selectionEnabled && _jsx(Td, { padding: "xs-3", className: "w-10" }), columns.map((col) => (_jsx(Td, { padding: "md", className: COLUMN_PRIORITY_CLASS[resolveColumnPriority(col)], children: _jsx(Div, { className: "h-4 w-full animate-pulse bg-neutral-200", rounded: "default" }) }, col.key)))] }, i)))) : rows.length === 0 ? (_jsx(Tr, { children: _jsx(Td, { colSpan: columns.length + (selectionEnabled ? 1 : 0) + (renderRowActions ? 1 : 0), align: "center", color: "faint", paddingX: "md", paddingY: "md", children: emptyLabel }) })) : (rows.map((row) => (_jsx(SelectableRow, { row: row, columns: columns, isSelected: selectedIds?.has(row.id) ?? false, onToggle: onToggleSelect, renderRowActions: renderRowActions, onRowClick: onRowClick, rowHref: typeof rowHrefTemplate === "function"
56
56
  ? rowHrefTemplate(row)
57
- : rowHrefTemplate?.replace("{id}", encodeURIComponent(row.id)), selectionEnabled: selectionEnabled }, row.id)))) })] }) }), totalPages > 1 && onPageChange && (_jsx(Row, { className: "border-t border-neutral-200", padding: "inline", align: "center", justify: "end", gap: "sm", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (_jsx(Button, { onClick: () => onPageChange(p), variant: p === currentPage ? "primary" : "ghost", size: "sm", className: `h-8 w-8 rounded text-[length:var(--appkit-text-xs)] font-medium transition ${p === currentPage ? "bg-neutral-900 text-white" : "text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] hover:bg-[var(--appkit-color-surface-elevated)]"}`, children: p }, p))) }))] }));
57
+ : rowHrefTemplate?.replace("{id}", encodeURIComponent(row.id)), selectionEnabled: selectionEnabled }, row.id)))) })] }) }), totalPages > 1 && onPageChange && (_jsx(Row, { className: "border-t border-neutral-200", padding: "inline", align: "center", justify: "end", gap: "sm", children: Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (_jsx(Button, { onClick: () => onPageChange(p), variant: p === currentPage ? "primary" : "ghost", size: "sm", className: `h-8 w-8 rounded text-[length:var(--appkit-text-xs)] font-medium transition ${p === currentPage ? "bg-neutral-900 text-white" : "text-[var(--appkit-color-text-muted)] text-[var(--appkit-color-text-muted)] hover:bg-surface-hover"}`, children: p }, p))) }))] }));
58
58
  }
@@ -254,11 +254,17 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
254
254
  codDepositPercent: 10,
255
255
  codHandlingFeeMin: 200,
256
256
  codHandlingFeePercent: 10,
257
- whatsappNotifyFeeEnabled: false,
257
+ // The three buyer add-ons ship ENABLED. They were `false`, which made
258
+ // StoreAddonsPicker return null on both the cart and checkout, so a fresh
259
+ // install had no add-ons at all and no indication why. `undefined` still
260
+ // means disabled everywhere (computeWhatsAppNotifyFee et al. read
261
+ // `if (!rates.XEnabled) return 0`) — only the DEFAULT changed, so an
262
+ // existing siteSettings document keeps whatever it already stored.
263
+ whatsappNotifyFeeEnabled: true,
258
264
  whatsappNotifyFee: 10,
259
- giftWrapFeeEnabled: false,
265
+ giftWrapFeeEnabled: true,
260
266
  giftWrapFee: 49,
261
- shipmentProtectionFeeEnabled: false,
267
+ shipmentProtectionFeeEnabled: true,
262
268
  shipmentProtectionFeePercent: 2,
263
269
  shipmentProtectionFeeMin: 30,
264
270
  sellerShippingFixed: 0,