@mohasinac/appkit 4.26.0 → 4.28.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 (277) 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/bundles/data.d.ts +19 -0
  5. package/dist/_internal/server/features/bundles/data.js +32 -0
  6. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  7. package/dist/_internal/server/features/bundles/index.js +1 -1
  8. package/dist/_internal/server/features/checkout/actions.js +6 -4
  9. package/dist/_internal/server/features/classified/metadata.js +3 -1
  10. package/dist/_internal/server/features/digital-code/metadata.js +3 -1
  11. package/dist/_internal/server/features/live/metadata.js +3 -1
  12. package/dist/_internal/server/features/refunds/actions.js +3 -2
  13. package/dist/_internal/server/features/reviews/actions.js +3 -2
  14. package/dist/_internal/server/features/seo/sitemap.js +189 -40
  15. package/dist/_internal/server/jobs/core/listingProcessor.d.ts +0 -1
  16. package/dist/_internal/server/jobs/core/listingProcessor.js +19 -1
  17. package/dist/_internal/shared/actions/action-registry.js +14 -0
  18. package/dist/_internal/shared/checkout/order-math.d.ts +25 -3
  19. package/dist/_internal/shared/features/checkout/config.d.ts +3 -3
  20. package/dist/_internal/shared/features/checkout/config.js +6 -3
  21. package/dist/client.d.ts +32 -2
  22. package/dist/client.js +37 -2
  23. package/dist/constants/api-endpoints.d.ts +21 -3
  24. package/dist/constants/api-endpoints.js +14 -1
  25. package/dist/constants/field-names.d.ts +2 -2
  26. package/dist/constants/field-names.js +2 -2
  27. package/dist/core/newsletter.repository.js +14 -1
  28. package/dist/features/about/components/FAQPageView.js +1 -1
  29. package/dist/features/about/components/FeesView.js +1 -1
  30. package/dist/features/account/components/AddressForm.d.ts +2 -0
  31. package/dist/features/account/components/AddressForm.js +5 -1
  32. package/dist/features/account/components/UserBidsView.js +1 -1
  33. package/dist/features/account/components/UserSidebar.js +1 -1
  34. package/dist/features/account/components/UserSupportView.js +24 -2
  35. package/dist/features/account/hooks/useAddresses.d.ts +29 -1
  36. package/dist/features/account/hooks/useAddresses.js +4 -1
  37. package/dist/features/account/hooks/useProfile.d.ts +1 -1
  38. package/dist/features/account/hooks/useProfile.js +1 -1
  39. package/dist/features/addresses/repository/addresses.repository.d.ts +13 -0
  40. package/dist/features/addresses/repository/addresses.repository.js +19 -0
  41. package/dist/features/admin/actions/chat-actions.js +5 -1
  42. package/dist/features/admin/components/AdminAdsView.js +1 -1
  43. package/dist/features/admin/components/AdminBidsView.js +6 -1
  44. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +9 -1
  45. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +42 -10
  46. package/dist/features/admin/components/AdminCarouselView.js +1 -1
  47. package/dist/features/admin/components/AdminFeatureEditorView.js +1 -1
  48. package/dist/features/admin/components/AdminGroupedListingsView.js +14 -2
  49. package/dist/features/admin/components/AdminSidebar.js +1 -1
  50. package/dist/features/admin/components/AdminViewCards.js +2 -2
  51. package/dist/features/admin/components/DataTable.js +2 -2
  52. package/dist/features/admin/schemas/firestore.js +9 -3
  53. package/dist/features/auctions/actions/bid-actions.js +45 -4
  54. package/dist/features/auctions/components/AuctionBottomActions.d.ts +21 -6
  55. package/dist/features/auctions/components/AuctionBottomActions.js +42 -11
  56. package/dist/features/auctions/components/AuctionDetailPageView.d.ts +8 -3
  57. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  58. package/dist/features/auctions/components/MarketplaceAuctionCard.js +4 -5
  59. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +27 -2
  60. package/dist/features/auctions/components/PlaceBidFormClient.js +42 -20
  61. package/dist/features/auth/components/SocialAuthButtons.js +1 -1
  62. package/dist/features/auth/hooks/useAuth.d.ts +23 -1
  63. package/dist/features/auth/hooks/useAuth.js +27 -1
  64. package/dist/features/auth/permissions/constants.d.ts +1 -1
  65. package/dist/features/auth/permissions/constants.js +3 -0
  66. package/dist/features/auth/repository/session.repository.d.ts +16 -1
  67. package/dist/features/auth/repository/session.repository.js +23 -0
  68. package/dist/features/auth/repository/user.repository.js +13 -2
  69. package/dist/features/blog/components/BlogListView.js +2 -2
  70. package/dist/features/cart/components/CartPriceBreakdown.d.ts +11 -1
  71. package/dist/features/cart/components/CartPriceBreakdown.js +3 -3
  72. package/dist/features/cart/components/StoreAddonsPicker.d.ts +9 -0
  73. package/dist/features/cart/components/StoreAddonsPicker.js +15 -5
  74. package/dist/features/cart/components/index.d.ts +1 -1
  75. package/dist/features/cart/components/index.js +1 -1
  76. package/dist/features/cart/hooks/useGuestCartMerge.d.ts +8 -1
  77. package/dist/features/cart/hooks/useGuestCartMerge.js +2 -1
  78. package/dist/features/cart/utils/line-total.d.ts +25 -0
  79. package/dist/features/cart/utils/line-total.js +26 -0
  80. package/dist/features/catalogue/repository/catalogue.repository.d.ts +12 -0
  81. package/dist/features/catalogue/repository/catalogue.repository.js +15 -0
  82. package/dist/features/categories/components/CategoryTree.js +2 -2
  83. package/dist/features/contact/components/ContactForm.js +1 -1
  84. package/dist/features/copilot/hooks/useCopilotFeedback.d.ts +1 -1
  85. package/dist/features/copilot/hooks/useCopilotFeedback.js +1 -1
  86. package/dist/features/events/components/AdminEventEditorView.js +16 -9
  87. package/dist/features/events/components/AdminEventEntriesView.js +7 -13
  88. package/dist/features/events/components/EventRafflesSection.js +14 -2
  89. package/dist/features/events/components/EventStatusBadge.js +1 -1
  90. package/dist/features/events/components/event-entry-detail-fields.d.ts +24 -0
  91. package/dist/features/events/components/event-entry-detail-fields.js +13 -0
  92. package/dist/features/faq/components/ContactCTA.js +1 -1
  93. package/dist/features/faq/components/FAQHelpfulButtons.js +2 -2
  94. package/dist/features/faq/repository/faqs.repository.d.ts +16 -1
  95. package/dist/features/faq/repository/faqs.repository.js +64 -54
  96. package/dist/features/faq/schemas/firestore.d.ts +3 -2
  97. package/dist/features/faq/schemas/firestore.js +2 -2
  98. package/dist/features/faq/schemas/index.d.ts +3 -3
  99. package/dist/features/faq/schemas/index.js +1 -1
  100. package/dist/features/filters/FilterFacetSection.js +2 -2
  101. package/dist/features/forms/Toggle.js +1 -1
  102. package/dist/features/grouped/components/GroupedListingEditorView.d.ts +18 -0
  103. package/dist/features/grouped/components/GroupedListingEditorView.js +192 -0
  104. package/dist/features/homepage/components/AdvertisementBanner.js +2 -2
  105. package/dist/features/homepage/components/BrandsSection.js +1 -1
  106. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  107. package/dist/features/homepage/components/HomepageCustomerReviewsSection.js +1 -1
  108. package/dist/features/homepage/components/HomepageSectionBoundary.d.ts +39 -0
  109. package/dist/features/homepage/components/HomepageSectionBoundary.js +49 -0
  110. package/dist/features/homepage/components/ShopByCategorySection.js +1 -1
  111. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  112. package/dist/features/homepage/lib/section-renderer.js +2 -1
  113. package/dist/features/layout/AppLayoutShell.js +5 -5
  114. package/dist/features/layout/BottomActions.js +5 -5
  115. package/dist/features/layout/BottomSheet.js +1 -1
  116. package/dist/features/layout/MainNavbar.js +1 -1
  117. package/dist/features/layout/NavbarLayout.js +1 -1
  118. package/dist/features/layout/TitleBarLayout.js +3 -3
  119. package/dist/features/layout/hooks/useFormBottomActions.d.ts +11 -1
  120. package/dist/features/layout/hooks/useFormBottomActions.js +16 -2
  121. package/dist/features/lottery/schemas/config-write.d.ts +141 -0
  122. package/dist/features/lottery/schemas/config-write.js +139 -0
  123. package/dist/features/media/MediaPickerModal.js +3 -1
  124. package/dist/features/media/MediaVideo.js +4 -2
  125. package/dist/features/media/components/MediaSlider.js +1 -1
  126. package/dist/features/orders/components/OrdersList.js +2 -2
  127. package/dist/features/payments/repository/payout.repository.d.ts +11 -1
  128. package/dist/features/payments/repository/payout.repository.js +14 -6
  129. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -2
  130. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  131. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  132. package/dist/features/products/columns/productTableColumns.js +3 -3
  133. package/dist/features/products/components/CompareOverlay.js +1 -1
  134. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  135. package/dist/features/products/components/PrizeDrawEntryActions.js +2 -2
  136. package/dist/features/products/components/PrizeDrawsSection.js +5 -1
  137. package/dist/features/products/components/ProductDetailActions.js +11 -7
  138. package/dist/features/products/components/ProductDetailPageView.js +3 -3
  139. package/dist/features/products/components/ProductFeatureBadges.js +2 -2
  140. package/dist/features/products/components/ProductFeaturesSelector.js +1 -1
  141. package/dist/features/products/components/ProductFilters.js +15 -8
  142. package/dist/features/products/components/ProductGrid.js +8 -17
  143. package/dist/features/products/components/ProductTabs.js +7 -4
  144. package/dist/features/products/components/ProductTabsShell.js +2 -7
  145. package/dist/features/products/constants/action-defs.d.ts +21 -4
  146. package/dist/features/products/constants/action-defs.js +7 -3
  147. package/dist/features/products/hooks/useRelatedProducts.js +2 -1
  148. package/dist/features/products/repository/products.repository.d.ts +1 -1
  149. package/dist/features/products/repository/products.repository.js +27 -13
  150. package/dist/features/products/schemas/firestore.d.ts +3 -2
  151. package/dist/features/products/schemas/firestore.js +1 -1
  152. package/dist/features/reviews/actions/review-actions.d.ts +8 -1
  153. package/dist/features/reviews/actions/review-actions.js +14 -3
  154. package/dist/features/reviews/components/ReviewDetailShell.js +10 -4
  155. package/dist/features/reviews/components/ReviewModal.js +1 -1
  156. package/dist/features/reviews/repository/reviews.repository.d.ts +25 -1
  157. package/dist/features/reviews/repository/reviews.repository.js +53 -13
  158. package/dist/features/reviews/schemas/firestore.d.ts +6 -0
  159. package/dist/features/scams/components/ScamRegistryView.js +2 -2
  160. package/dist/features/search/components/Search.js +3 -3
  161. package/dist/features/search/components/SearchResultsSection.js +1 -1
  162. package/dist/features/seller/components/SellerAddressesView.js +3 -1
  163. package/dist/features/seller/components/SellerBidsView.js +6 -1
  164. package/dist/features/seller/components/SellerSidebar.js +1 -1
  165. package/dist/features/shell/SectionForm.d.ts +21 -1
  166. package/dist/features/shell/SectionForm.js +15 -3
  167. package/dist/features/shell/build-sections.js +10 -1
  168. package/dist/features/shell/field-ui-meta.d.ts +21 -0
  169. package/dist/features/shipments/components/AdminShipmentLotItemsView.js +23 -6
  170. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  171. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +21 -4
  172. package/dist/features/store-extensions/repository/store-extensions.repositories.js +24 -4
  173. package/dist/features/store-extensions/schemas/shipping-config-form.js +13 -0
  174. package/dist/features/store-extensions/schemas/store-category-form.js +1 -1
  175. package/dist/features/stores/actions/store-query-actions.js +25 -1
  176. package/dist/features/stores/components/InteractiveStoreCard.js +1 -1
  177. package/dist/features/stores/components/StoreHeader.js +1 -1
  178. package/dist/features/stores/components/StoreNavTabs.js +20 -8
  179. package/dist/features/support/repository/support.repository.d.ts +12 -0
  180. package/dist/features/support/repository/support.repository.js +15 -0
  181. package/dist/features/support/schemas/ticket-create-form.d.ts +33 -0
  182. package/dist/features/support/schemas/ticket-create-form.js +82 -0
  183. package/dist/features/tester/seed-data/products-tester-seed-data.js +108 -2
  184. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -13
  185. package/dist/features/wishlist/components/WishlistHeartButton.d.ts +55 -0
  186. package/dist/features/wishlist/components/WishlistHeartButton.js +52 -0
  187. package/dist/features/wishlist/components/index.d.ts +2 -1
  188. package/dist/features/wishlist/components/index.js +1 -1
  189. package/dist/index.d.ts +10 -3
  190. package/dist/index.js +19 -4
  191. package/dist/monitoring/error-tracking.js +39 -5
  192. package/dist/monitoring/server-logger.js +70 -32
  193. package/dist/next/components/ErrorView.js +1 -1
  194. package/dist/next/routing/route-map.d.ts +39 -7
  195. package/dist/next/routing/route-map.js +34 -4
  196. package/dist/providers/db-firebase/base.repository.d.ts +30 -0
  197. package/dist/providers/db-firebase/base.repository.js +56 -9
  198. package/dist/react/ErrorBoundary.js +9 -1
  199. package/dist/react/contexts/SessionContext.js +6 -2
  200. package/dist/react/hooks/useAuthGate.d.ts +22 -0
  201. package/dist/react/hooks/useAuthGate.js +86 -7
  202. package/dist/schemas/registry.d.ts +3 -3
  203. package/dist/security/index.d.ts +1 -1
  204. package/dist/security/index.js +1 -1
  205. package/dist/security/pii-encrypt.d.ts +13 -5
  206. package/dist/security/pii-encrypt.js +132 -6
  207. package/dist/security/pii-schemas.d.ts +20 -4
  208. package/dist/security/pii-schemas.js +23 -5
  209. package/dist/seed/_helpers/product-search-txt.d.ts +18 -0
  210. package/dist/seed/_helpers/product-search-txt.js +33 -0
  211. package/dist/seed/faq-seed-data.d.ts +16 -0
  212. package/dist/seed/faq-seed-data.js +22 -1
  213. package/dist/seed/products-art-seed-data.js +49 -7
  214. package/dist/seed/products-auctions-seed-data.js +12 -4
  215. package/dist/seed/products-classifieds-seed-data.d.ts +5 -0
  216. package/dist/seed/products-classifieds-seed-data.js +65 -9
  217. package/dist/seed/products-digital-codes-seed-data.d.ts +5 -0
  218. package/dist/seed/products-digital-codes-seed-data.js +65 -9
  219. package/dist/seed/products-live-items-seed-data.d.ts +5 -0
  220. package/dist/seed/products-live-items-seed-data.js +33 -5
  221. package/dist/seed/products-preorders-seed-data.d.ts +1 -1
  222. package/dist/seed/products-preorders-seed-data.js +6 -2
  223. package/dist/seed/products-prize-draws-seed-data.d.ts +5 -0
  224. package/dist/seed/products-prize-draws-seed-data.js +57 -8
  225. package/dist/seed/products-standard-seed-data.js +6 -2
  226. package/dist/seed/products-stickers-seed-data.js +49 -7
  227. package/dist/seed/site-settings-seed-data.js +5 -3
  228. package/dist/seo/json-ld.d.ts +44 -2
  229. package/dist/seo/json-ld.js +57 -11
  230. package/dist/server.d.ts +1 -1
  231. package/dist/server.js +1 -1
  232. package/dist/styles.css +368 -90
  233. package/dist/tailwind-utilities.css +1 -1
  234. package/dist/tokens/index.d.ts +1 -1
  235. package/dist/tokens/index.js +1 -1
  236. package/dist/tokens/themes/default-dark.js +6 -0
  237. package/dist/tokens/themes/default-light.js +5 -0
  238. package/dist/tokens/tokens.css +84 -0
  239. package/dist/ui/DataTable.js +1 -1
  240. package/dist/ui/DataTable.style.css +3 -3
  241. package/dist/ui/components/BulkActionBar.style.css +1 -3
  242. package/dist/ui/components/Button.js +15 -6
  243. package/dist/ui/components/Dropdown.style.css +1 -4
  244. package/dist/ui/components/FilterChipGroup.js +1 -1
  245. package/dist/ui/components/FilterDrawer.style.css +1 -1
  246. package/dist/ui/components/Icon.d.ts +53 -0
  247. package/dist/ui/components/Icon.js +41 -0
  248. package/dist/ui/components/IconButton.d.ts +13 -0
  249. package/dist/ui/components/IconButton.js +13 -0
  250. package/dist/ui/components/IconButton.style.css +18 -10
  251. package/dist/ui/components/ListingLayout.style.css +2 -10
  252. package/dist/ui/components/ListingToolbar.js +6 -6
  253. package/dist/ui/components/Modal.js +1 -1
  254. package/dist/ui/components/PaginatedSelect.style.css +3 -3
  255. package/dist/ui/components/Pagination.style.css +1 -6
  256. package/dist/ui/components/RichTextEditor.js +1 -1
  257. package/dist/ui/components/RowActionMenu.style.css +1 -1
  258. package/dist/ui/components/SideDrawer.style.css +1 -1
  259. package/dist/ui/components/Slider.js +1 -1
  260. package/dist/ui/components/TabBarShell.d.ts +78 -0
  261. package/dist/ui/components/TabBarShell.js +95 -0
  262. package/dist/ui/components/Tabs.js +12 -8
  263. package/dist/ui/components/Tabs.style.css +249 -41
  264. package/dist/ui/components/ViewToggle.style.css +1 -5
  265. package/dist/ui/components/tabs-overflow.d.ts +17 -0
  266. package/dist/ui/components/tabs-overflow.js +142 -0
  267. package/dist/ui/icons/icon-registry.d.ts +38 -0
  268. package/dist/ui/icons/icon-registry.js +37 -0
  269. package/dist/ui/index.d.ts +6 -0
  270. package/dist/ui/index.js +3 -0
  271. package/dist/utils/index.d.ts +1 -1
  272. package/dist/utils/index.js +1 -1
  273. package/dist/utils/media-url.d.ts +27 -4
  274. package/dist/utils/media-url.js +37 -6
  275. package/dist/utils/search-txt.d.ts +71 -0
  276. package/dist/utils/search-txt.js +118 -0
  277. package/package.json +1 -1
@@ -18,7 +18,26 @@ function getLogsDir() {
18
18
  }
19
19
  const MAX_LOG_FILE_SIZE = 10 * 1024 * 1024;
20
20
  const MAX_LOG_FILES = 10;
21
- const isFileLoggingEnabled = !process.env.VERCEL;
21
+ /**
22
+ * File logging is OPT-IN, not "anywhere that isn't Vercel".
23
+ *
24
+ * This was `!process.env.VERCEL`, which is `true` inside Cloud Functions — where
25
+ * `VERCEL` is simply unset and the filesystem is read-only. Every log line in
26
+ * every Function therefore ran mkdir/stat/appendFile, failed, and emitted a
27
+ * SECOND line carrying the EROFS stack: every Function log doubled, plus four
28
+ * wasted syscalls per line, against a log buffer that drops at ~4 KB/s
29
+ * (CLAUDE.md Rule #6).
30
+ *
31
+ * Serverless runtimes have no durable disk and their stdout is already
32
+ * collected, so the only place local files help is a developer machine.
33
+ * `LOG_TO_FILE=1` turns it on there; nothing else does.
34
+ */
35
+ const isServerlessRuntime = Boolean(process.env.VERCEL ||
36
+ process.env.FUNCTION_TARGET || // Cloud Functions (2nd gen)
37
+ process.env.K_SERVICE || // Cloud Run / Functions
38
+ process.env.FUNCTIONS_EMULATOR ||
39
+ process.env.AWS_LAMBDA_FUNCTION_NAME);
40
+ const isFileLoggingEnabled = process.env.LOG_TO_FILE === "1" && !isServerlessRuntime;
22
41
  function serializeError(error) {
23
42
  const serialized = {
24
43
  name: error.name,
@@ -136,46 +155,65 @@ async function writeLog(entry) {
136
155
  console.error("Failed to write log:", error);
137
156
  }
138
157
  }
139
- export const serverLogger = {
140
- debug(message, data) {
141
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
142
- console.debug(`[DEBUG] ${message}`, sanitized);
143
- },
144
- info(message, data) {
145
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
146
- const entry = {
147
- level: "info",
158
+ /**
159
+ * Cloud Logging severity names. GCP parses a JSON stdout line and promotes
160
+ * `severity` into the real log level; a plain `[INFO] msg` string arrives as
161
+ * unparsed `DEFAULT`-severity text, which is what every appkit server log has
162
+ * been doing — invisible to any severity filter or alert.
163
+ */
164
+ const GCP_SEVERITY = {
165
+ debug: "DEBUG",
166
+ info: "INFO",
167
+ warn: "WARNING",
168
+ error: "ERROR",
169
+ };
170
+ /**
171
+ * Emit one line. Structured JSON where a collector is reading stdout
172
+ * (Vercel/Cloud Functions/Cloud Run), human-readable locally.
173
+ */
174
+ function emit(level, message, sanitized) {
175
+ const consoleFn = level === "error" ? console.error : level === "warn" ? console.warn : level === "debug" ? console.debug : console.info;
176
+ if (isServerlessRuntime) {
177
+ consoleFn(JSON.stringify({
178
+ severity: GCP_SEVERITY[level],
148
179
  message,
149
180
  timestamp: new Date().toISOString(),
150
- data: sanitized,
151
- };
152
- console.info(`[INFO] ${message}`, sanitized);
153
- if (isFileLoggingEnabled)
154
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
155
- },
156
- warn(message, data) {
157
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
181
+ ...(sanitized !== undefined ? { data: sanitized } : {}),
182
+ }));
183
+ return;
184
+ }
185
+ consoleFn(`[${level.toUpperCase()}] ${message}`, sanitized);
186
+ }
187
+ function record(level, message, data) {
188
+ const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
189
+ emit(level, message, sanitized);
190
+ if (isFileLoggingEnabled) {
158
191
  const entry = {
159
- level: "warn",
192
+ level,
160
193
  message,
161
194
  timestamp: new Date().toISOString(),
162
195
  data: sanitized,
163
196
  };
164
- console.warn(`[WARN] ${message}`, sanitized);
165
- if (isFileLoggingEnabled)
166
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
197
+ writeLog(entry).catch((err) => {
198
+ // stderr, not the logger — a logger that logs its own failure through
199
+ // itself recurses. normalizeError is pure classification, so it is safe
200
+ // to call here.
201
+ process.stderr.write(`log write failed: ${normalizeError(err).message}\n`);
202
+ });
203
+ }
204
+ }
205
+ export const serverLogger = {
206
+ debug(message, data) {
207
+ record("debug", message, data);
208
+ },
209
+ info(message, data) {
210
+ record("info", message, data);
211
+ },
212
+ warn(message, data) {
213
+ record("warn", message, data);
167
214
  },
168
215
  error(message, data) {
169
- const sanitized = data ? redactPii(normalizeLogData(data)) : undefined;
170
- const entry = {
171
- level: "error",
172
- message,
173
- timestamp: new Date().toISOString(),
174
- data: sanitized,
175
- };
176
- console.error(`[ERROR] ${message}`, sanitized);
177
- if (isFileLoggingEnabled)
178
- writeLog(entry).catch((err) => { process.stderr.write(String(err) + "\n"); });
216
+ record("error", message, data);
179
217
  },
180
218
  };
181
219
  /** Pull the commonly needed headers from a NextRequest in one call. */
@@ -28,5 +28,5 @@ export function ErrorView({ error, reset, heading = "Something went wrong", desc
28
28
  metadata: { digest: error.digest ?? null },
29
29
  });
30
30
  }, [error]);
31
- return (_jsxs(Stack, { justify: "center", role: "alert", className: "min-h-[50vh] text-center", padding: "x-xl", align: "center", children: [_jsx(Heading, { level: 2, className: "mb-2", children: heading }), _jsx(Text, { variant: "secondary", className: "mb-6", children: description }), process.env.NODE_ENV === "development" && error.digest && (_jsxs(Text, { variant: "muted", size: "xs", className: "mb-4", children: ["digest: ", error.digest] })), _jsx(Button, { type: "button", variant: "primary", onClick: reset, children: retryLabel })] }));
31
+ return (_jsxs(Stack, { justify: "center", role: "alert", className: "min-h-[50vh] text-center", padding: "x-xl", align: "center", children: [_jsx(Heading, { level: 2, className: "mb-2", children: heading }), _jsx(Text, { variant: "secondary", className: "mb-6", children: description }), error.digest && (_jsxs(Text, { variant: "muted", size: "xs", className: "mb-4", children: ["Reference: ", error.digest] })), _jsx(Button, { type: "button", variant: "primary", onClick: reset, children: retryLabel })] }));
32
32
  }
@@ -143,8 +143,6 @@ export declare const DEFAULT_ROUTE_MAP: {
143
143
  */
144
144
  readonly ADDRESSES_NEW: "/user/addresses/new";
145
145
  readonly ADDRESSES_EDIT: (id: string) => string;
146
- /** @deprecated Use ADDRESSES_NEW. Kept so existing links keep resolving. */
147
- readonly ADDRESSES_ADD: "/user/addresses/new";
148
146
  readonly ORDER_DETAIL: (id: string) => string;
149
147
  readonly ORDER_CANCEL: (id: string) => string;
150
148
  readonly ORDER_TRACK: (id: string) => string;
@@ -161,6 +159,7 @@ export declare const DEFAULT_ROUTE_MAP: {
161
159
  readonly OFFERS: "/user/offers";
162
160
  readonly REVIEWS: "/user/reviews";
163
161
  readonly BIDS: "/user/bids";
162
+ readonly BID_DETAIL: (id: string) => string;
164
163
  readonly RETURNS: "/user/returns";
165
164
  readonly SUPPORT: "/user/support";
166
165
  readonly SUPPORT_NEW: "/user/support/new";
@@ -198,10 +197,13 @@ export declare const DEFAULT_ROUTE_MAP: {
198
197
  readonly SHIPPING: "/store/shipping";
199
198
  readonly PAYOUT_SETTINGS: "/store/payout-settings";
200
199
  readonly ADDRESSES: "/store/addresses";
200
+ readonly ADDRESSES_NEW: "/store/addresses/new";
201
+ readonly ADDRESSES_EDIT: (id: string) => string;
201
202
  readonly OFFERS: "/store/offers";
202
203
  readonly WHATSAPP: "/store/whatsapp";
203
204
  readonly REVIEWS: "/store/reviews";
204
205
  readonly BIDS: "/store/bids";
206
+ readonly BID_DETAIL: (id: string) => string;
205
207
  readonly SUBLISTING_CATEGORIES: "/store/sublisting-categories";
206
208
  readonly SUBLISTING_CATEGORIES_NEW: "/store/sublisting-categories/new";
207
209
  readonly SUBLISTING_CATEGORIES_EDIT: (id: string) => string;
@@ -216,7 +218,6 @@ export declare const DEFAULT_ROUTE_MAP: {
216
218
  readonly PRIZE_DRAWS_NEW: "/store/prize-draws/new";
217
219
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
218
220
  readonly PRIZE_DRAW_ENTRIES: (id: string) => string;
219
- readonly PRODUCT_CODES: (id: string) => string;
220
221
  readonly CLASSIFIED: "/store/classified";
221
222
  readonly CLASSIFIED_NEW: "/store/classified/new";
222
223
  readonly CLASSIFIED_EDIT: (id: string) => string;
@@ -270,14 +271,19 @@ export declare const DEFAULT_ROUTE_MAP: {
270
271
  readonly CATALOGUE_APPROVALS: "/admin/catalogue-approvals";
271
272
  readonly USERS: "/admin/users";
272
273
  readonly USER_DETAIL: (id: string) => string;
274
+ readonly USER_EDIT: (id: string) => string;
273
275
  readonly SITE: "/admin/site";
274
276
  readonly CAROUSEL: "/admin/carousel";
275
277
  readonly CAROUSEL_NEW: "/admin/carousel/new";
276
278
  readonly CAROUSEL_EDIT: (id: string) => string;
277
279
  readonly CAROUSELS: "/admin/carousels";
278
280
  readonly CAROUSEL_DETAIL: (id: string) => string;
281
+ readonly CAROUSELS_NEW: "/admin/carousels/new";
282
+ readonly CAROUSELS_EDIT: (id: string) => string;
279
283
  readonly SECTIONS: "/admin/sections";
280
284
  readonly NAVIGATION: "/admin/navigation";
285
+ readonly NAVIGATION_NEW: "/admin/navigation/new";
286
+ readonly NAVIGATION_EDIT: (id: string) => string;
281
287
  readonly CATEGORIES: "/admin/categories";
282
288
  readonly CATEGORIES_NEW: "/admin/categories/new";
283
289
  readonly CATEGORIES_EDIT: (id: string) => string;
@@ -288,6 +294,8 @@ export declare const DEFAULT_ROUTE_MAP: {
288
294
  readonly FAQS_NEW: "/admin/faqs/new";
289
295
  readonly FAQS_EDIT: (id: string) => string;
290
296
  readonly TESTER_CHECKLIST: "/admin/tester-checklist";
297
+ readonly TESTER_CHECKLIST_NEW: "/admin/tester-checklist/new";
298
+ readonly TESTER_CHECKLIST_EDIT: (id: string) => string;
291
299
  readonly TESTER_FEEDBACK: "/admin/tester-feedback";
292
300
  readonly REVIEWS: "/admin/reviews";
293
301
  readonly COUPONS: "/admin/coupons";
@@ -303,11 +311,14 @@ export declare const DEFAULT_ROUTE_MAP: {
303
311
  readonly ART: "/admin/art";
304
312
  readonly STICKERS: "/admin/stickers";
305
313
  readonly GROUPED_LISTINGS: "/admin/grouped-listings";
314
+ readonly GROUPED_LISTINGS_NEW: "/admin/grouped-listings/new";
315
+ readonly GROUPED_LISTINGS_EDIT: (id: string) => string;
306
316
  readonly DEALS: "/admin/deals";
307
317
  readonly FEATURED: "/admin/featured";
308
318
  readonly ORDERS: "/admin/orders";
309
319
  readonly ORDER_DETAIL: (id: string) => string;
310
320
  readonly BIDS: "/admin/bids";
321
+ readonly BID_DETAIL: (id: string) => string;
311
322
  readonly OFFERS: "/admin/offers";
312
323
  readonly BLOG: "/admin/blog";
313
324
  readonly BLOG_NEW: "/admin/blog/new";
@@ -331,6 +342,7 @@ export declare const DEFAULT_ROUTE_MAP: {
331
342
  readonly CONTACT: "/admin/contact";
332
343
  readonly SESSIONS: "/admin/sessions";
333
344
  readonly ALL_EVENT_ENTRIES: "/admin/event-entries";
345
+ readonly EVENT_ENTRY_DETAIL: (id: string) => string;
334
346
  readonly NOTIFICATIONS: "/admin/notifications";
335
347
  readonly CARTS: "/admin/carts";
336
348
  readonly WISHLISTS: "/admin/wishlists";
@@ -353,8 +365,11 @@ export declare const DEFAULT_ROUTE_MAP: {
353
365
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
354
366
  readonly PRIZE_DRAWS_ENTRIES: (id: string) => string;
355
367
  readonly LOTTERIES: "/admin/lotteries";
368
+ readonly LOTTERY_CONFIG: (id: string) => string;
356
369
  readonly LOTTERY_ENTRIES: (id: string) => string;
357
370
  readonly TEAM: "/admin/team";
371
+ readonly TEAM_NEW: "/admin/team/new";
372
+ readonly TEAM_EDIT: (id: string) => string;
358
373
  readonly SUPPORT_TICKETS: "/admin/support-tickets";
359
374
  readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
360
375
  readonly SCAMMERS: "/admin/scammers";
@@ -531,8 +546,6 @@ export declare const ROUTES: {
531
546
  */
532
547
  readonly ADDRESSES_NEW: "/user/addresses/new";
533
548
  readonly ADDRESSES_EDIT: (id: string) => string;
534
- /** @deprecated Use ADDRESSES_NEW. Kept so existing links keep resolving. */
535
- readonly ADDRESSES_ADD: "/user/addresses/new";
536
549
  readonly ORDER_DETAIL: (id: string) => string;
537
550
  readonly ORDER_CANCEL: (id: string) => string;
538
551
  readonly ORDER_TRACK: (id: string) => string;
@@ -549,6 +562,7 @@ export declare const ROUTES: {
549
562
  readonly OFFERS: "/user/offers";
550
563
  readonly REVIEWS: "/user/reviews";
551
564
  readonly BIDS: "/user/bids";
565
+ readonly BID_DETAIL: (id: string) => string;
552
566
  readonly RETURNS: "/user/returns";
553
567
  readonly SUPPORT: "/user/support";
554
568
  readonly SUPPORT_NEW: "/user/support/new";
@@ -586,10 +600,13 @@ export declare const ROUTES: {
586
600
  readonly SHIPPING: "/store/shipping";
587
601
  readonly PAYOUT_SETTINGS: "/store/payout-settings";
588
602
  readonly ADDRESSES: "/store/addresses";
603
+ readonly ADDRESSES_NEW: "/store/addresses/new";
604
+ readonly ADDRESSES_EDIT: (id: string) => string;
589
605
  readonly OFFERS: "/store/offers";
590
606
  readonly WHATSAPP: "/store/whatsapp";
591
607
  readonly REVIEWS: "/store/reviews";
592
608
  readonly BIDS: "/store/bids";
609
+ readonly BID_DETAIL: (id: string) => string;
593
610
  readonly SUBLISTING_CATEGORIES: "/store/sublisting-categories";
594
611
  readonly SUBLISTING_CATEGORIES_NEW: "/store/sublisting-categories/new";
595
612
  readonly SUBLISTING_CATEGORIES_EDIT: (id: string) => string;
@@ -604,7 +621,6 @@ export declare const ROUTES: {
604
621
  readonly PRIZE_DRAWS_NEW: "/store/prize-draws/new";
605
622
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
606
623
  readonly PRIZE_DRAW_ENTRIES: (id: string) => string;
607
- readonly PRODUCT_CODES: (id: string) => string;
608
624
  readonly CLASSIFIED: "/store/classified";
609
625
  readonly CLASSIFIED_NEW: "/store/classified/new";
610
626
  readonly CLASSIFIED_EDIT: (id: string) => string;
@@ -658,14 +674,19 @@ export declare const ROUTES: {
658
674
  readonly CATALOGUE_APPROVALS: "/admin/catalogue-approvals";
659
675
  readonly USERS: "/admin/users";
660
676
  readonly USER_DETAIL: (id: string) => string;
677
+ readonly USER_EDIT: (id: string) => string;
661
678
  readonly SITE: "/admin/site";
662
679
  readonly CAROUSEL: "/admin/carousel";
663
680
  readonly CAROUSEL_NEW: "/admin/carousel/new";
664
681
  readonly CAROUSEL_EDIT: (id: string) => string;
665
682
  readonly CAROUSELS: "/admin/carousels";
666
683
  readonly CAROUSEL_DETAIL: (id: string) => string;
684
+ readonly CAROUSELS_NEW: "/admin/carousels/new";
685
+ readonly CAROUSELS_EDIT: (id: string) => string;
667
686
  readonly SECTIONS: "/admin/sections";
668
687
  readonly NAVIGATION: "/admin/navigation";
688
+ readonly NAVIGATION_NEW: "/admin/navigation/new";
689
+ readonly NAVIGATION_EDIT: (id: string) => string;
669
690
  readonly CATEGORIES: "/admin/categories";
670
691
  readonly CATEGORIES_NEW: "/admin/categories/new";
671
692
  readonly CATEGORIES_EDIT: (id: string) => string;
@@ -676,6 +697,8 @@ export declare const ROUTES: {
676
697
  readonly FAQS_NEW: "/admin/faqs/new";
677
698
  readonly FAQS_EDIT: (id: string) => string;
678
699
  readonly TESTER_CHECKLIST: "/admin/tester-checklist";
700
+ readonly TESTER_CHECKLIST_NEW: "/admin/tester-checklist/new";
701
+ readonly TESTER_CHECKLIST_EDIT: (id: string) => string;
679
702
  readonly TESTER_FEEDBACK: "/admin/tester-feedback";
680
703
  readonly REVIEWS: "/admin/reviews";
681
704
  readonly COUPONS: "/admin/coupons";
@@ -691,11 +714,14 @@ export declare const ROUTES: {
691
714
  readonly ART: "/admin/art";
692
715
  readonly STICKERS: "/admin/stickers";
693
716
  readonly GROUPED_LISTINGS: "/admin/grouped-listings";
717
+ readonly GROUPED_LISTINGS_NEW: "/admin/grouped-listings/new";
718
+ readonly GROUPED_LISTINGS_EDIT: (id: string) => string;
694
719
  readonly DEALS: "/admin/deals";
695
720
  readonly FEATURED: "/admin/featured";
696
721
  readonly ORDERS: "/admin/orders";
697
722
  readonly ORDER_DETAIL: (id: string) => string;
698
723
  readonly BIDS: "/admin/bids";
724
+ readonly BID_DETAIL: (id: string) => string;
699
725
  readonly OFFERS: "/admin/offers";
700
726
  readonly BLOG: "/admin/blog";
701
727
  readonly BLOG_NEW: "/admin/blog/new";
@@ -719,6 +745,7 @@ export declare const ROUTES: {
719
745
  readonly CONTACT: "/admin/contact";
720
746
  readonly SESSIONS: "/admin/sessions";
721
747
  readonly ALL_EVENT_ENTRIES: "/admin/event-entries";
748
+ readonly EVENT_ENTRY_DETAIL: (id: string) => string;
722
749
  readonly NOTIFICATIONS: "/admin/notifications";
723
750
  readonly CARTS: "/admin/carts";
724
751
  readonly WISHLISTS: "/admin/wishlists";
@@ -741,8 +768,11 @@ export declare const ROUTES: {
741
768
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
742
769
  readonly PRIZE_DRAWS_ENTRIES: (id: string) => string;
743
770
  readonly LOTTERIES: "/admin/lotteries";
771
+ readonly LOTTERY_CONFIG: (id: string) => string;
744
772
  readonly LOTTERY_ENTRIES: (id: string) => string;
745
773
  readonly TEAM: "/admin/team";
774
+ readonly TEAM_NEW: "/admin/team/new";
775
+ readonly TEAM_EDIT: (id: string) => string;
746
776
  readonly SUPPORT_TICKETS: "/admin/support-tickets";
747
777
  readonly SUPPORT_TICKET_BY_ID: (id: string) => string;
748
778
  readonly SCAMMERS: "/admin/scammers";
@@ -810,10 +840,13 @@ export declare const SELLER_ROUTES: {
810
840
  readonly SHIPPING: "/store/shipping";
811
841
  readonly PAYOUT_SETTINGS: "/store/payout-settings";
812
842
  readonly ADDRESSES: "/store/addresses";
843
+ readonly ADDRESSES_NEW: "/store/addresses/new";
844
+ readonly ADDRESSES_EDIT: (id: string) => string;
813
845
  readonly OFFERS: "/store/offers";
814
846
  readonly WHATSAPP: "/store/whatsapp";
815
847
  readonly REVIEWS: "/store/reviews";
816
848
  readonly BIDS: "/store/bids";
849
+ readonly BID_DETAIL: (id: string) => string;
817
850
  readonly SUBLISTING_CATEGORIES: "/store/sublisting-categories";
818
851
  readonly SUBLISTING_CATEGORIES_NEW: "/store/sublisting-categories/new";
819
852
  readonly SUBLISTING_CATEGORIES_EDIT: (id: string) => string;
@@ -828,7 +861,6 @@ export declare const SELLER_ROUTES: {
828
861
  readonly PRIZE_DRAWS_NEW: "/store/prize-draws/new";
829
862
  readonly PRIZE_DRAWS_EDIT: (id: string) => string;
830
863
  readonly PRIZE_DRAW_ENTRIES: (id: string) => string;
831
- readonly PRODUCT_CODES: (id: string) => string;
832
864
  readonly CLASSIFIED: "/store/classified";
833
865
  readonly CLASSIFIED_NEW: "/store/classified/new";
834
866
  readonly CLASSIFIED_EDIT: (id: string) => string;
@@ -147,8 +147,6 @@ export const DEFAULT_ROUTE_MAP = {
147
147
  */
148
148
  ADDRESSES_NEW: "/user/addresses/new",
149
149
  ADDRESSES_EDIT: (id) => `/user/addresses/${id}/edit`,
150
- /** @deprecated Use ADDRESSES_NEW. Kept so existing links keep resolving. */
151
- ADDRESSES_ADD: "/user/addresses/new",
152
150
  ORDER_DETAIL: (id) => `/user/orders/view/${id}`,
153
151
  ORDER_CANCEL: (id) => `/user/orders/${id}/cancel`,
154
152
  ORDER_TRACK: (id) => `/user/orders/${id}/track`,
@@ -165,6 +163,7 @@ export const DEFAULT_ROUTE_MAP = {
165
163
  OFFERS: "/user/offers",
166
164
  REVIEWS: "/user/reviews",
167
165
  BIDS: "/user/bids",
166
+ BID_DETAIL: (id) => `/user/bids/${id}/view`,
168
167
  RETURNS: "/user/returns",
169
168
  SUPPORT: "/user/support",
170
169
  SUPPORT_NEW: "/user/support/new",
@@ -202,10 +201,13 @@ export const DEFAULT_ROUTE_MAP = {
202
201
  SHIPPING: "/store/shipping",
203
202
  PAYOUT_SETTINGS: "/store/payout-settings",
204
203
  ADDRESSES: "/store/addresses",
204
+ ADDRESSES_NEW: "/store/addresses/new",
205
+ ADDRESSES_EDIT: (id) => `/store/addresses/${id}/edit`,
205
206
  OFFERS: "/store/offers",
206
207
  WHATSAPP: "/store/whatsapp",
207
208
  REVIEWS: "/store/reviews",
208
209
  BIDS: "/store/bids",
210
+ BID_DETAIL: (id) => `/store/bids/${id}/view`,
209
211
  SUBLISTING_CATEGORIES: "/store/sublisting-categories",
210
212
  SUBLISTING_CATEGORIES_NEW: "/store/sublisting-categories/new",
211
213
  SUBLISTING_CATEGORIES_EDIT: (id) => `/store/sublisting-categories/${id}/edit`,
@@ -221,7 +223,6 @@ export const DEFAULT_ROUTE_MAP = {
221
223
  PRIZE_DRAWS_NEW: "/store/prize-draws/new",
222
224
  PRIZE_DRAWS_EDIT: (id) => `/store/prize-draws/${id}/edit`,
223
225
  PRIZE_DRAW_ENTRIES: (id) => `/store/prize-draws/${id}/entries`,
224
- PRODUCT_CODES: (id) => `/store/products/${id}/codes`,
225
226
  // SB-UNI-R — classified / digital-code / live
226
227
  CLASSIFIED: "/store/classified",
227
228
  CLASSIFIED_NEW: "/store/classified/new",
@@ -279,14 +280,24 @@ export const DEFAULT_ROUTE_MAP = {
279
280
  CATALOGUE_APPROVALS: "/admin/catalogue-approvals",
280
281
  USERS: "/admin/users",
281
282
  USER_DETAIL: (id) => `/admin/users/${id}`,
283
+ USER_EDIT: (id) => `/admin/users/${id}/edit`,
282
284
  SITE: "/admin/site",
283
285
  CAROUSEL: "/admin/carousel",
284
286
  CAROUSEL_NEW: "/admin/carousel/new",
285
287
  CAROUSEL_EDIT: (id) => `/admin/carousel/${id}/edit`,
286
288
  CAROUSELS: "/admin/carousels",
287
289
  CAROUSEL_DETAIL: (id) => `/admin/carousels/${id}`,
290
+ /*
291
+ * 🛑 PLURAL = the named carousel GROUP. Singular `CAROUSEL_NEW` /
292
+ * `CAROUSEL_EDIT` above are `/admin/carousel/*` and mean a SLIDE.
293
+ * Two different entities, one letter apart — do not merge them.
294
+ */
295
+ CAROUSELS_NEW: "/admin/carousels/new",
296
+ CAROUSELS_EDIT: (id) => `/admin/carousels/${id}/edit`,
288
297
  SECTIONS: "/admin/sections",
289
298
  NAVIGATION: "/admin/navigation",
299
+ NAVIGATION_NEW: "/admin/navigation/new",
300
+ NAVIGATION_EDIT: (id) => `/admin/navigation/${id}/edit`,
290
301
  CATEGORIES: "/admin/categories",
291
302
  CATEGORIES_NEW: "/admin/categories/new",
292
303
  CATEGORIES_EDIT: (id) => `/admin/categories/${id}/edit`,
@@ -297,6 +308,8 @@ export const DEFAULT_ROUTE_MAP = {
297
308
  FAQS_NEW: "/admin/faqs/new",
298
309
  FAQS_EDIT: (id) => `/admin/faqs/${id}/edit`,
299
310
  TESTER_CHECKLIST: "/admin/tester-checklist",
311
+ TESTER_CHECKLIST_NEW: "/admin/tester-checklist/new",
312
+ TESTER_CHECKLIST_EDIT: (id) => `/admin/tester-checklist/${id}/edit`,
300
313
  TESTER_FEEDBACK: "/admin/tester-feedback",
301
314
  REVIEWS: "/admin/reviews",
302
315
  COUPONS: "/admin/coupons",
@@ -313,11 +326,14 @@ export const DEFAULT_ROUTE_MAP = {
313
326
  ART: "/admin/art",
314
327
  STICKERS: "/admin/stickers",
315
328
  GROUPED_LISTINGS: "/admin/grouped-listings",
329
+ GROUPED_LISTINGS_NEW: "/admin/grouped-listings/new",
330
+ GROUPED_LISTINGS_EDIT: (id) => `/admin/grouped-listings/${id}/edit`,
316
331
  DEALS: "/admin/deals",
317
332
  FEATURED: "/admin/featured",
318
333
  ORDERS: "/admin/orders",
319
334
  ORDER_DETAIL: (id) => `/admin/orders/${id}/view`,
320
335
  BIDS: "/admin/bids",
336
+ BID_DETAIL: (id) => `/admin/bids/${id}/view`,
321
337
  OFFERS: "/admin/offers",
322
338
  BLOG: "/admin/blog",
323
339
  BLOG_NEW: "/admin/blog/new",
@@ -341,6 +357,7 @@ export const DEFAULT_ROUTE_MAP = {
341
357
  CONTACT: "/admin/contact",
342
358
  SESSIONS: "/admin/sessions",
343
359
  ALL_EVENT_ENTRIES: "/admin/event-entries",
360
+ EVENT_ENTRY_DETAIL: (id) => `/admin/event-entries/${id}/view`,
344
361
  NOTIFICATIONS: "/admin/notifications",
345
362
  CARTS: "/admin/carts",
346
363
  WISHLISTS: "/admin/wishlists",
@@ -365,8 +382,18 @@ export const DEFAULT_ROUTE_MAP = {
365
382
  PRIZE_DRAWS_EDIT: (id) => `/admin/prize-draws/${id}/edit`,
366
383
  PRIZE_DRAWS_ENTRIES: (id) => `/admin/prize-draws/${id}/entries`,
367
384
  LOTTERIES: "/admin/lotteries",
385
+ /*
386
+ * Slot configuration only. A lottery EVENT (title, dates, status, media)
387
+ * is created through the ordinary event editor — `LotteryAdminEditView`
388
+ * has no fields for any of those, so treating it as the whole create
389
+ * flow would produce a lottery with no dates, and the draw window is
390
+ * measured from them.
391
+ */
392
+ LOTTERY_CONFIG: (id) => `/admin/lotteries/${id}/edit`,
368
393
  LOTTERY_ENTRIES: (id) => `/admin/lotteries/${id}/entries`,
369
394
  TEAM: "/admin/team",
395
+ TEAM_NEW: "/admin/team/new",
396
+ TEAM_EDIT: (id) => `/admin/team/${id}/edit`,
370
397
  SUPPORT_TICKETS: "/admin/support-tickets",
371
398
  SUPPORT_TICKET_BY_ID: (id) => `/admin/support-tickets/${id}`,
372
399
  SCAMMERS: "/admin/scammers",
@@ -479,7 +506,10 @@ export const PROTECTED_ROUTES = [
479
506
  ROUTES.USER.SETTINGS,
480
507
  ROUTES.USER.ORDERS,
481
508
  ROUTES.USER.ADDRESSES,
482
- ROUTES.USER.ADDRESSES_ADD,
509
+ // Was ADDRESSES_ADD, a deprecated alias resolving to the same path. Swapped
510
+ // rather than dropped: deleting the alias without this would have removed
511
+ // /user/addresses/new from the protected set entirely.
512
+ ROUTES.USER.ADDRESSES_NEW,
483
513
  ROUTES.USER.NOTIFICATIONS,
484
514
  ROUTES.USER.CART,
485
515
  ];
@@ -12,6 +12,36 @@ export declare abstract class BaseRepository<T extends DocumentData> {
12
12
  findBy(field: string, value: unknown): Promise<T[]>;
13
13
  findOneBy(field: string, value: unknown): Promise<T | null>;
14
14
  findAll(limit?: number): Promise<T[]>;
15
+ /**
16
+ * PII fields for this collection, encrypted on every write path below.
17
+ *
18
+ * Declare it in the subclass instead of overriding `create`/`update` by hand.
19
+ * There were no encryption hooks here at all, so encryption was opt-in per
20
+ * method per repository — which meant `createWithId`, `update`, and the whole
21
+ * `*InTx` / `*InBatch` family were silent bypasses in any repo that had only
22
+ * overridden `create`. `sessions.deviceInfo.ip` sat in plaintext for exactly
23
+ * that reason.
24
+ */
25
+ protected piiFields: readonly string[];
26
+ /** Blind-index map (plaintext field → index field) for equality lookups. */
27
+ protected piiIndexMap: Record<string, string>;
28
+ /**
29
+ * Build this document's `searchTxt` tokens. Return `null` to leave the field
30
+ * alone (the default), so a collection opts in by overriding this.
31
+ *
32
+ * MUST NOT include PII: `searchTxt` stores readable fragments of the source
33
+ * text, so indexing an encrypted field would undo the encryption.
34
+ */
35
+ protected buildSearchTxtFor(_data: Record<string, unknown>): string[] | null;
36
+ /**
37
+ * The one place writes are transformed. Every write path routes through here
38
+ * so a new method cannot silently skip encryption.
39
+ *
40
+ * `encryptPiiFields` is idempotent (it skips anything already carrying the
41
+ * `enc:v1:` prefix), so a subclass that still encrypts by hand before calling
42
+ * super is harmless rather than double-encrypted.
43
+ */
44
+ protected applyWriteHooks<D extends object>(data: D): D;
15
45
  create(data: Partial<T> | FirestoreDocument): Promise<T>;
16
46
  createWithId(id: string, data: Partial<T>): Promise<T>;
17
47
  update(id: string, data: Partial<T>): Promise<T>;
@@ -4,8 +4,22 @@ import { serverLogger } from "../../monitoring";
4
4
  import { applySieveToFirestore, countSieveMatches, } from "./sieve";
5
5
  import { deserializeTimestamps, prepareForFirestore } from "./helpers";
6
6
  import { getAdminDb } from "./admin";
7
+ import { encryptPiiFields, piiIndicesFor } from "../../security/pii-encrypt";
7
8
  export class BaseRepository {
8
9
  constructor(collectionName) {
10
+ /**
11
+ * PII fields for this collection, encrypted on every write path below.
12
+ *
13
+ * Declare it in the subclass instead of overriding `create`/`update` by hand.
14
+ * There were no encryption hooks here at all, so encryption was opt-in per
15
+ * method per repository — which meant `createWithId`, `update`, and the whole
16
+ * `*InTx` / `*InBatch` family were silent bypasses in any repo that had only
17
+ * overridden `create`. `sessions.deviceInfo.ip` sat in plaintext for exactly
18
+ * that reason.
19
+ */
20
+ this.piiFields = [];
21
+ /** Blind-index map (plaintext field → index field) for equality lookups. */
22
+ this.piiIndexMap = {};
9
23
  this.collection = collectionName;
10
24
  }
11
25
  get db() {
@@ -82,10 +96,43 @@ export class BaseRepository {
82
96
  throw new DatabaseError("Failed to fetch all documents", error);
83
97
  }
84
98
  }
99
+ /**
100
+ * Build this document's `searchTxt` tokens. Return `null` to leave the field
101
+ * alone (the default), so a collection opts in by overriding this.
102
+ *
103
+ * MUST NOT include PII: `searchTxt` stores readable fragments of the source
104
+ * text, so indexing an encrypted field would undo the encryption.
105
+ */
106
+ buildSearchTxtFor(_data) {
107
+ return null;
108
+ }
109
+ /**
110
+ * The one place writes are transformed. Every write path routes through here
111
+ * so a new method cannot silently skip encryption.
112
+ *
113
+ * `encryptPiiFields` is idempotent (it skips anything already carrying the
114
+ * `enc:v1:` prefix), so a subclass that still encrypts by hand before calling
115
+ * super is harmless rather than double-encrypted.
116
+ */
117
+ applyWriteHooks(data) {
118
+ let out = data;
119
+ if (this.piiFields.length > 0) {
120
+ out = {
121
+ ...encryptPiiFields(out, [...this.piiFields]),
122
+ // Indices are derived from the PLAINTEXT source and must not carry it
123
+ // along — see piiIndicesFor's docstring for the bug that caused.
124
+ ...piiIndicesFor(data, this.piiIndexMap),
125
+ };
126
+ }
127
+ const searchTxt = this.buildSearchTxtFor(out);
128
+ if (searchTxt)
129
+ out.searchTxt = searchTxt;
130
+ return out;
131
+ }
85
132
  async create(data) {
86
133
  try {
87
134
  const cleanData = prepareForFirestore({
88
- ...data,
135
+ ...this.applyWriteHooks(data),
89
136
  createdAt: new Date(),
90
137
  updatedAt: new Date(),
91
138
  });
@@ -102,7 +149,7 @@ export class BaseRepository {
102
149
  try {
103
150
  const now = new Date();
104
151
  const cleanData = prepareForFirestore({
105
- ...data,
152
+ ...this.applyWriteHooks(data),
106
153
  createdAt: now,
107
154
  updatedAt: now,
108
155
  });
@@ -125,7 +172,7 @@ export class BaseRepository {
125
172
  async update(id, data) {
126
173
  try {
127
174
  const cleanData = prepareForFirestore({
128
- ...data,
175
+ ...this.applyWriteHooks(data),
129
176
  updatedAt: new Date(),
130
177
  });
131
178
  await this.getCollection().doc(id).update(cleanData);
@@ -240,7 +287,7 @@ export class BaseRepository {
240
287
  const docRef = this.getCollection().doc();
241
288
  const now = new Date();
242
289
  tx.set(docRef, prepareForFirestore({
243
- ...data,
290
+ ...this.applyWriteHooks(data),
244
291
  createdAt: now,
245
292
  updatedAt: now,
246
293
  }));
@@ -250,7 +297,7 @@ export class BaseRepository {
250
297
  const docRef = this.getCollection().doc(id);
251
298
  const now = new Date();
252
299
  tx.set(docRef, prepareForFirestore({
253
- ...data,
300
+ ...this.applyWriteHooks(data),
254
301
  createdAt: now,
255
302
  updatedAt: now,
256
303
  }));
@@ -258,7 +305,7 @@ export class BaseRepository {
258
305
  }
259
306
  updateInTx(tx, id, data) {
260
307
  const docRef = this.getCollection().doc(id);
261
- tx.update(docRef, prepareForFirestore({ ...data, updatedAt: new Date() }));
308
+ tx.update(docRef, prepareForFirestore({ ...this.applyWriteHooks(data), updatedAt: new Date() }));
262
309
  }
263
310
  deleteInTx(tx, id) {
264
311
  const docRef = this.getCollection().doc(id);
@@ -268,7 +315,7 @@ export class BaseRepository {
268
315
  const docRef = this.getCollection().doc();
269
316
  const now = new Date();
270
317
  batch.set(docRef, prepareForFirestore({
271
- ...data,
318
+ ...this.applyWriteHooks(data),
272
319
  createdAt: now,
273
320
  updatedAt: now,
274
321
  }));
@@ -278,14 +325,14 @@ export class BaseRepository {
278
325
  const docRef = this.getCollection().doc(id);
279
326
  const now = new Date();
280
327
  batch.set(docRef, prepareForFirestore({
281
- ...data,
328
+ ...this.applyWriteHooks(data),
282
329
  createdAt: now,
283
330
  updatedAt: now,
284
331
  }));
285
332
  }
286
333
  updateInBatch(batch, id, data) {
287
334
  const docRef = this.getCollection().doc(id);
288
- batch.update(docRef, prepareForFirestore({ ...data, updatedAt: new Date() }));
335
+ batch.update(docRef, prepareForFirestore({ ...this.applyWriteHooks(data), updatedAt: new Date() }));
289
336
  }
290
337
  deleteInBatch(batch, id) {
291
338
  const docRef = this.getCollection().doc(id);