@mohasinac/appkit 4.1.1 → 4.2.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 (225) hide show
  1. package/dist/_internal/client/features/maintenance/index.d.ts +2 -0
  2. package/dist/_internal/client/features/maintenance/index.js +1 -0
  3. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.d.ts +9 -0
  4. package/dist/_internal/client/features/maintenance/views/CloudLogsListView.js +55 -0
  5. package/dist/_internal/server/features/auctions/index.d.ts +1 -1
  6. package/dist/_internal/server/features/auctions/index.js +1 -1
  7. package/dist/_internal/server/features/auctions/service.js +2 -2
  8. package/dist/_internal/server/features/checkout/actions.d.ts +15 -0
  9. package/dist/_internal/server/features/checkout/actions.js +245 -182
  10. package/dist/_internal/server/features/maintenance/cloud-logs-data.d.ts +50 -0
  11. package/dist/_internal/server/features/maintenance/cloud-logs-data.js +152 -0
  12. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  13. package/dist/_internal/server/functions/scheduled.js +9 -1
  14. package/dist/_internal/server/jobs/core/jobRunners.js +10 -0
  15. package/dist/_internal/server/jobs/core/newsletterExport.d.ts +15 -0
  16. package/dist/_internal/server/jobs/core/newsletterExport.js +43 -0
  17. package/dist/_internal/server/jobs/core/onOrderCreate.d.ts +4 -12
  18. package/dist/_internal/server/jobs/core/onOrderCreate.js +3 -3
  19. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  20. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  21. package/dist/_internal/server/jobs/core/onProductWrite.js +14 -2
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/revenueRollup.d.ts +13 -0
  25. package/dist/_internal/server/jobs/core/revenueRollup.js +22 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  27. package/dist/_internal/server/jobs/core/whatsappNotify.js +82 -0
  28. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  29. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  30. package/dist/_internal/server/jobs/handlers/revenueRollup.d.ts +2 -0
  31. package/dist/_internal/server/jobs/handlers/revenueRollup.js +2 -0
  32. package/dist/_internal/shared/features/auctions/config.d.ts +1 -1
  33. package/dist/_internal/shared/features/auctions/config.js +1 -1
  34. package/dist/_internal/shared/features/auctions/schema.js +1 -1
  35. package/dist/_internal/shared/features/cart/schema.js +2 -2
  36. package/dist/_internal/shared/features/checkout/config.d.ts +7 -0
  37. package/dist/_internal/shared/features/checkout/config.js +7 -0
  38. package/dist/_internal/shared/features/events/schema.js +1 -1
  39. package/dist/_internal/shared/features/orders/schema.js +1 -1
  40. package/dist/_internal/shared/features/products/schema.js +3 -3
  41. package/dist/_internal/shared/features/promotions/schema.js +3 -3
  42. package/dist/_internal/shared/fees/calculator.d.ts +32 -7
  43. package/dist/_internal/shared/fees/calculator.js +25 -0
  44. package/dist/client.d.ts +4 -4
  45. package/dist/client.js +4 -2
  46. package/dist/constants/api-endpoints.d.ts +9 -6
  47. package/dist/constants/api-endpoints.js +3 -2
  48. package/dist/contracts/client-payment-gateway.d.ts +1 -2
  49. package/dist/features/about/components/AboutView.d.ts +6 -16
  50. package/dist/features/about/components/AboutView.js +3 -3
  51. package/dist/features/about/components/FeesView.js +18 -0
  52. package/dist/features/about/index.d.ts +2 -1
  53. package/dist/features/about/schemas/firestore.d.ts +47 -0
  54. package/dist/features/about/schemas/firestore.js +6 -0
  55. package/dist/features/about/schemas/index.d.ts +210 -0
  56. package/dist/features/about/schemas/index.js +45 -0
  57. package/dist/features/account/hooks/useProfile.js +3 -0
  58. package/dist/features/admin/actions/notification-actions.d.ts +4 -1
  59. package/dist/features/admin/actions/notification-actions.js +56 -10
  60. package/dist/features/admin/components/AdminAdsView.js +1 -1
  61. package/dist/features/admin/components/AdminCarouselGroupEditorView.d.ts +6 -0
  62. package/dist/features/admin/components/AdminCarouselGroupEditorView.js +50 -0
  63. package/dist/features/admin/components/AdminCartsView.js +16 -3
  64. package/dist/features/admin/components/AdminCouponEditorView.js +2 -2
  65. package/dist/features/admin/components/AdminCouponsView.js +14 -4
  66. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -42
  67. package/dist/features/admin/components/AdminMediaView.js +1 -1
  68. package/dist/features/admin/components/AdminNewsletterView.js +42 -11
  69. package/dist/features/admin/components/AdminNotificationsView.js +31 -19
  70. package/dist/features/admin/components/AdminOrdersView.js +7 -0
  71. package/dist/features/admin/components/AdminPayoutsView.js +18 -3
  72. package/dist/features/admin/components/AdminProductsView.js +17 -2
  73. package/dist/features/admin/components/AdminReviewsView.js +16 -2
  74. package/dist/features/admin/components/AdminScammersView.js +7 -0
  75. package/dist/features/admin/components/AdminSectionsView.js +4 -0
  76. package/dist/features/admin/components/AdminSiteConfigGuideView.js +1 -1
  77. package/dist/features/admin/components/AdminSiteSettingsView.js +127 -8
  78. package/dist/features/admin/components/AdminStoreEditorView.d.ts +2 -1
  79. package/dist/features/admin/components/AdminStoreEditorView.js +4 -4
  80. package/dist/features/admin/components/AdminStoresView.js +1 -1
  81. package/dist/features/admin/components/AdminSupportTicketDetailView.js +23 -5
  82. package/dist/features/admin/components/AdminSupportTicketsView.js +20 -2
  83. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +4 -1
  84. package/dist/features/admin/components/AdminTesterChecklistView.js +8 -0
  85. package/dist/features/admin/components/index.d.ts +2 -0
  86. package/dist/features/admin/components/index.js +1 -0
  87. package/dist/features/admin/constants/filter-tabs.d.ts +55 -41
  88. package/dist/features/admin/constants/filter-tabs.js +55 -27
  89. package/dist/features/admin/repository/analytics-rollup.repository.d.ts +25 -0
  90. package/dist/features/admin/repository/analytics-rollup.repository.js +31 -0
  91. package/dist/features/admin/repository/site-settings.repository.js +8 -1
  92. package/dist/features/admin/schemas/firestore.d.ts +46 -0
  93. package/dist/features/admin/schemas/firestore.js +62 -24
  94. package/dist/features/auctions/actions/bid-actions.js +1 -1
  95. package/dist/features/auctions/components/AuctionDetailPageView.js +2 -2
  96. package/dist/features/auctions/components/AuctionsListView.js +8 -1
  97. package/dist/features/auth/components/LoginForm.js +2 -2
  98. package/dist/features/auth/components/RegisterForm.js +2 -2
  99. package/dist/features/auth/permissions/constants.d.ts +16 -0
  100. package/dist/features/auth/permissions/constants.js +53 -0
  101. package/dist/features/auth/schemas/firestore.js +1 -0
  102. package/dist/features/blog/components/BlogFilters.js +1 -1
  103. package/dist/features/cart/schemas/firestore.d.ts +1 -1
  104. package/dist/features/categories/schemas/firestore.d.ts +1 -1
  105. package/dist/features/consultation/components/ConsultationForm.js +1 -1
  106. package/dist/features/events/components/EventFilters.js +2 -0
  107. package/dist/features/homepage/components/WelcomeSection.js +2 -2
  108. package/dist/features/layout/BackToTop.d.ts +9 -5
  109. package/dist/features/layout/BackToTop.js +17 -21
  110. package/dist/features/layout/TitleBarLayout.d.ts +2 -2
  111. package/dist/features/layout/TitleBarLayout.js +4 -4
  112. package/dist/features/media/AvatarUpload.js +7 -3
  113. package/dist/features/media/MediaVideo.js +1 -1
  114. package/dist/features/media/finalize.d.ts +9 -13
  115. package/dist/features/media/finalize.js +23 -74
  116. package/dist/features/media/server.d.ts +1 -1
  117. package/dist/features/media/server.js +1 -1
  118. package/dist/features/orders/actions/refund-actions.js +1 -0
  119. package/dist/features/orders/hooks/useOrders.js +16 -4
  120. package/dist/features/orders/schemas/firestore.d.ts +19 -5
  121. package/dist/features/payments/schemas/firestore.d.ts +2 -2
  122. package/dist/features/pre-orders/components/PreOrdersListView.js +7 -0
  123. package/dist/features/products/components/ArtStickersListView.js +5 -0
  124. package/dist/features/products/components/ProductDetailPageView.d.ts +7 -1
  125. package/dist/features/products/components/ProductForm.js +14 -1
  126. package/dist/features/products/components/ProductsIndexPageView.js +7 -0
  127. package/dist/features/products/constants/action-defs.d.ts +29 -3
  128. package/dist/features/products/constants/action-defs.js +77 -1
  129. package/dist/features/products/schemas/firestore.d.ts +1 -1
  130. package/dist/features/products/schemas/product-templates.d.ts +1 -1
  131. package/dist/features/products/types/index.d.ts +4 -0
  132. package/dist/features/scams/schemas/index.js +2 -2
  133. package/dist/features/search/components/Search.js +3 -3
  134. package/dist/features/seller/components/SellerAuctionsView.js +27 -2
  135. package/dist/features/seller/components/SellerOrdersView.js +2 -2
  136. package/dist/features/seller/components/SellerPayoutsView.js +24 -10
  137. package/dist/features/seller/components/SellerPreOrdersView.js +12 -4
  138. package/dist/features/seller/components/SellerPrizeDrawsView.js +23 -5
  139. package/dist/features/seller/components/SellerProductShell.d.ts +2 -0
  140. package/dist/features/seller/components/SellerProductShell.js +1 -1
  141. package/dist/features/seller/components/SellerProductsView.js +8 -6
  142. package/dist/features/seller/components/SellerReviewsView.js +27 -6
  143. package/dist/features/seller/components/index.d.ts +0 -2
  144. package/dist/features/seller/components/index.js +0 -1
  145. package/dist/features/stores/components/StoreListingsGuideView.js +2 -1
  146. package/dist/features/stores/components/StoreProductsView.js +1 -1
  147. package/dist/features/stores/schemas/firestore.d.ts +11 -0
  148. package/dist/features/stores/schemas/firestore.js +5 -0
  149. package/dist/features/stores/schemas/index.d.ts +2 -2
  150. package/dist/features/tester/actions/checklist-item-actions.d.ts +6 -0
  151. package/dist/features/tester/actions/checklist-item-actions.js +2 -0
  152. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +13 -2
  153. package/dist/features/tester/components/AdminTesterFeedbackListView.js +2 -1
  154. package/dist/features/tester/components/AdminTesterFeedbackReportView.js +9 -5
  155. package/dist/features/tester/components/TesterHubView.js +34 -21
  156. package/dist/features/tester/repository/tester-checklist-item.repository.js +1 -0
  157. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +18 -4
  158. package/dist/features/tester/repository/tester-checklist-response.repository.js +43 -8
  159. package/dist/features/tester/schemas/firestore.d.ts +8 -3
  160. package/dist/features/tester/schemas/firestore.js +6 -0
  161. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +21 -9
  162. package/dist/features/tester/utils/phases.d.ts +31 -0
  163. package/dist/features/tester/utils/phases.js +46 -0
  164. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +1 -1
  165. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +10 -1
  166. package/dist/features/whatsapp-bot/helpers/whatsapp.js +47 -0
  167. package/dist/features/whatsapp-bot/types/index.d.ts +20 -1
  168. package/dist/features/wishlist/types/index.d.ts +3 -0
  169. package/dist/index.d.ts +15 -6
  170. package/dist/index.js +19 -9
  171. package/dist/next/components/NotFoundView.js +2 -1
  172. package/dist/next/routing/route-map.d.ts +6 -12
  173. package/dist/next/routing/route-map.js +3 -4
  174. package/dist/providers/db-firebase/admin.d.ts +1 -0
  175. package/dist/providers/db-firebase/admin.js +1 -1
  176. package/dist/repositories/index.d.ts +2 -0
  177. package/dist/repositories/index.js +1 -0
  178. package/dist/schemas/registry.d.ts +2 -2
  179. package/dist/seed/addresses-seed-data.js +18 -18
  180. package/dist/seed/bids-seed-data.js +4 -4
  181. package/dist/seed/blog-posts-seed-data.js +82 -82
  182. package/dist/seed/carousel-slides-seed-data.js +14 -10
  183. package/dist/seed/carousels-seed-data.js +3 -3
  184. package/dist/seed/cart-seed-data.js +92 -92
  185. package/dist/seed/claimed-coupons-seed-data.js +10 -10
  186. package/dist/seed/conversations-seed-data.js +75 -75
  187. package/dist/seed/coupon-usage-seed-data.js +12 -12
  188. package/dist/seed/coupons-seed-data.js +41 -41
  189. package/dist/seed/events-seed-data.js +105 -105
  190. package/dist/seed/grouped-listings-seed-data.d.ts +2 -137
  191. package/dist/seed/grouped-listings-seed-data.js +79 -143
  192. package/dist/seed/history-seed-data.js +38 -38
  193. package/dist/seed/homepage-sections-seed-data.js +9 -9
  194. package/dist/seed/notifications-seed-data.js +56 -56
  195. package/dist/seed/offers-seed-data.js +91 -91
  196. package/dist/seed/orders-seed-data.js +121 -106
  197. package/dist/seed/payouts-seed-data.js +42 -42
  198. package/dist/seed/products-auctions-seed-data.js +10 -2
  199. package/dist/seed/products-standard-seed-data.js +40 -8
  200. package/dist/seed/reviews-seed-data.js +42 -23
  201. package/dist/seed/scammers-seed-data.js +22 -22
  202. package/dist/seed/sessions-seed-data.js +7 -7
  203. package/dist/seed/shipments-seed-data.js +10 -10
  204. package/dist/seed/site-settings-seed-data.js +89 -0
  205. package/dist/seed/store-addresses-seed-data.js +2 -2
  206. package/dist/seed/store-extensions-seed-data.js +93 -93
  207. package/dist/seed/stores-seed-data.js +2 -0
  208. package/dist/seed/support-tickets-seed-data.js +47 -47
  209. package/dist/seed/users-seed-data.js +55 -89
  210. package/dist/seed/wishlists-seed-data.js +22 -22
  211. package/dist/server.d.ts +4 -3
  212. package/dist/server.js +7 -7
  213. package/dist/styles.css +1 -1
  214. package/dist/tailwind-utilities.css +1 -1
  215. package/dist/ui/components/PageLoader.js +8 -3
  216. package/dist/ui/components/Select.d.ts +8 -1
  217. package/dist/ui/components/Select.js +2 -2
  218. package/dist/ui/components/SiteMark.d.ts +23 -0
  219. package/dist/ui/components/SiteMark.js +76 -0
  220. package/dist/ui/index.d.ts +2 -0
  221. package/dist/ui/index.js +1 -0
  222. package/dist/validation/schemas.d.ts +0 -2
  223. package/dist/validation/schemas.js +0 -2
  224. package/package.json +1 -1
  225. package/scripts/seed-cli.mjs +17 -15
@@ -189,6 +189,13 @@ export const DEFAULT_SITE_SETTINGS_DATA = {
189
189
  codDepositPercent: 10,
190
190
  codHandlingFeeMin: 200,
191
191
  codHandlingFeePercent: 10,
192
+ whatsappNotifyFeeEnabled: false,
193
+ whatsappNotifyFee: 10,
194
+ giftWrapFeeEnabled: false,
195
+ giftWrapFee: 49,
196
+ shipmentProtectionFeeEnabled: false,
197
+ shipmentProtectionFeePercent: 2,
198
+ shipmentProtectionFeeMin: 30,
192
199
  sellerShippingFixed: 0,
193
200
  platformShippingPercent: 10,
194
201
  platformShippingFixedMin: 0,
@@ -284,61 +291,92 @@ export const DEFAULT_TRUST_BAR_ITEMS = [
284
291
  { icon: "??", label: "24/7 Support", visible: true },
285
292
  { icon: "?", label: "Authentic Sellers", visible: true },
286
293
  ];
294
+ // NOTE: labelKey/descKey hold plain display text (not i18n message keys) —
295
+ // this feature-flag list has no matching next-intl namespace, so the admin
296
+ // UI reads these two fields directly rather than through getTranslations().
287
297
  export const FEATURE_FLAG_META = [
288
298
  {
289
299
  key: "chats",
290
- labelKey: "chats",
291
- descKey: "chatsDesc",
292
- icon: "??",
300
+ labelKey: "In-app chats",
301
+ descKey: "Buyer/seller direct messaging.",
302
+ icon: "\u{1F4AC}",
293
303
  category: "platform",
294
304
  },
295
305
  {
296
306
  key: "smsVerification",
297
- labelKey: "smsVerification",
298
- descKey: "smsVerificationDesc",
299
- icon: "??",
307
+ labelKey: "SMS verification",
308
+ descKey: "Phone-number OTP verification during signup/checkout.",
309
+ icon: "\u{1F4F1}",
300
310
  category: "platform",
301
311
  },
302
312
  {
303
313
  key: "translations",
304
- labelKey: "translations",
305
- descKey: "translationsDesc",
306
- icon: "??",
314
+ labelKey: "Translations",
315
+ descKey: "Locale switcher and translated UI strings.",
316
+ icon: "\u{1F310}",
307
317
  category: "platform",
308
318
  },
309
319
  {
310
320
  key: "auctions",
311
- labelKey: "auctions",
312
- descKey: "auctionsDesc",
313
- icon: "??",
321
+ labelKey: "Auctions",
322
+ descKey: "Auction listings and bidding.",
323
+ icon: "\u{1F528}",
314
324
  category: "platform",
315
325
  },
316
326
  {
317
327
  key: "reviews",
318
- labelKey: "reviews",
319
- descKey: "reviewsDesc",
320
- icon: "?",
328
+ labelKey: "Reviews",
329
+ descKey: "Product and store reviews.",
330
+ icon: "",
321
331
  category: "platform",
322
332
  },
323
333
  {
324
334
  key: "notifications",
325
- labelKey: "notifications",
326
- descKey: "notificationsDesc",
327
- icon: "??",
335
+ labelKey: "Notifications",
336
+ descKey: "In-app notification bell and inbox.",
337
+ icon: "\u{1F514}",
328
338
  category: "platform",
329
339
  },
330
340
  {
331
341
  key: "sellerRegistration",
332
- labelKey: "sellerRegistration",
333
- descKey: "sellerRegistrationDesc",
334
- icon: "??",
342
+ labelKey: "Seller registration",
343
+ descKey: "\"Become a seller\" signup flow.",
344
+ icon: "\u{1F3EA}",
335
345
  category: "platform",
336
346
  },
337
347
  {
338
348
  key: "preOrders",
339
- labelKey: "preOrders",
340
- descKey: "preOrdersDesc",
341
- icon: "??",
349
+ labelKey: "Pre-orders",
350
+ descKey: "Pre-order listings and deposit checkout.",
351
+ icon: "\u{1F4E6}",
352
+ category: "platform",
353
+ },
354
+ {
355
+ key: "wishlists",
356
+ labelKey: "Wishlists",
357
+ descKey: "Save-for-later wishlist feature.",
358
+ icon: "❤️",
359
+ category: "platform",
360
+ },
361
+ {
362
+ key: "events",
363
+ labelKey: "Events",
364
+ descKey: "Sales, raffles, spin-wheel, and lottery events.",
365
+ icon: "\u{1F389}",
366
+ category: "platform",
367
+ },
368
+ {
369
+ key: "blog",
370
+ labelKey: "Blog",
371
+ descKey: "Blog posts section.",
372
+ icon: "\u{1F4DD}",
373
+ category: "platform",
374
+ },
375
+ {
376
+ key: "coupons",
377
+ labelKey: "Coupons",
378
+ descKey: "Coupon codes and discounts.",
379
+ icon: "\u{1F3AB}",
342
380
  category: "platform",
343
381
  },
344
382
  ];
@@ -47,7 +47,7 @@ export async function placeBid(userId, userEmail, input) {
47
47
  const baseBid = (product.currentBid ?? 0) > 0
48
48
  ? product.currentBid
49
49
  : (product.startingBid ?? product.price);
50
- const minIncrement = product.minBidIncrement ?? 100;
50
+ const minIncrement = product.minBidIncrement ?? 1;
51
51
  // Proxy-bid semantics (eBay style): the buyer's `bidAmount` is treated as
52
52
  // their **maximum** they're willing to pay; the visible price only steps up
53
53
  // in `minIncrement` units as needed to stay ahead of the previous high bid.
@@ -125,9 +125,9 @@ export async function AuctionDetailPageView({ id, initialAuction, onPlaceBid, on
125
125
  price: typeof p.price === "number" ? p.price : undefined,
126
126
  storeId: typeof p.storeId === "string" ? p.storeId : undefined,
127
127
  storeName: typeof p.storeName === "string" ? p.storeName : undefined,
128
- } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { size: "xl", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
128
+ } }), _jsxs(Container, { size: "xl", padding: "y-lg", children: [_jsxs(Row, { className: "mb-4", align: "center", justify: "between", gap: "sm", wrap: true, children: [_jsxs(Nav, { "aria-label": "Breadcrumb", layout: "flex-wrap", gap: "2xs", textSize: "xs", color: "muted", children: [_jsx(Link, { href: String(ROUTES.HOME), className: CLS_BREADCRUMB_LINK, children: "Home" }), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.AUCTIONS), className: CLS_BREADCRUMB_LINK, children: "Auctions" }), category && (_jsxs(_Fragment, { children: [_jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Link, { href: String(ROUTES.PUBLIC.CATEGORY_DETAIL(category)), className: CLS_BREADCRUMB_LINK, children: categoryName || category })] })), _jsx(Span, { "aria-hidden": true, children: "/" }), _jsx(Span, { className: "truncate max-w-[200px]", color: "muted", children: title })] }), _jsx(ShareButton, { title: title })] }), _jsx(AuctionDetailView, { renderGallery: () => (_jsx(ProductGalleryClient, { images: images, productName: title })), renderInfo: () => renderAuctionInfoPanel({ title, currentBid, currency, bidCount, isEnded, endDate, buyNowPrice, featured, freeShipping, condition, category, categoryName, brand, brandSlug, productFeatures, features, descriptionHtml, safeSeller, storeHref }), renderBidForm: () => onPlaceBid ? (_jsx(Stack, { id: "auction-bid-form", gap: "3", children: _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { justify: "between", align: "baseline", children: [_jsx(Span, { size: "xl", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " ", bidCount === 1 ? "bid" : "bids", !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] }) })) : (
129
129
  /* Read-only bid panel — shown when no bid action is wired (preview/demo) */
130
- _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), " \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && !bidsHaveStarted && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Stack, { className: `lg:hidden ${__P.p4}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: `${__P.p4} lg:hidden`, border: "subtle", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
130
+ _jsxs(Stack, { className: `${__P.p5}`, border: "subtle", gap: "md", rounded: "xl", surface: "muted", children: [_jsx(Stack, { gap: "xs", children: _jsxs(Text, { size: "xs", color: "muted", children: ["Starting bid: ", formatCurrency(startingBid, currency), !isEnded && _jsxs(_Fragment, { children: [" \u00B7 min increment ", formatCurrency(minBidIncrement, currency)] })] }) }), _jsxs(Stack, { gap: "sm", children: [_jsx(Input, { type: "number", placeholder: `At least ${formatCurrency(currentBid + minBidIncrement, currency)}`, min: currentBid + minBidIncrement, "aria-label": "Your bid amount", disabled: isEnded }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", disabled: isEnded, children: isEnded ? "Auction Ended" : "Place Bid" }), buyNowPrice !== null && !isEnded && !bidsHaveStarted && (_jsxs(Button, { variant: "secondary", size: "md", className: "w-full", disabled: true, children: ["Buy Now \u2014 ", formatCurrency(buyNowPrice, currency)] }))] }), tags.length > 0 && (_jsx(Div, { border: "default", className: "border-t", padding: "t-md", children: _jsx(Row, { wrap: true, gap: "xs", children: tags.map((tag) => (_jsx(Span, { size: "xs", rounded: "full", padding: "pill-sm-tall", surface: "subtle", color: "muted", children: tag }, tag))) }) }))] })), renderMobileBidForm: () => !isEnded && onPlaceBid ? (_jsxs(Stack, { className: `lg:hidden ${__P.p4}`, border: "subtle", gap: "3", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(PlaceBidModalButton, { productId: String(product.id), currentBid: currentBid, startingBid: startingBid, minBidIncrement: minBidIncrement, currency: currency, isEnded: isEnded, buyNowPrice: buyNowPrice, bidsHaveStarted: bidsHaveStarted, bidCount: bidCount, tags: tags, onPlaceBid: onPlaceBid, onBuyNow: onBuyNow, triggerClassName: "w-full" })] })) : !isEnded ? (_jsxs(Div, { className: `${__P.p4} lg:hidden`, border: "subtle", rounded: "xl", surface: "muted", children: [_jsxs(Row, { align: "center", gap: "sm", className: "mb-3", children: [_jsx(Span, { size: "base", weight: "bold", className: "text-primary-600 dark:text-primary-400", children: formatCurrency(currentBid, currency) }), _jsxs(Span, { size: "xs", color: "muted", children: [bidCount, " bids"] })] }), _jsx(Button, { variant: "primary", size: "md", className: "w-full", children: "Place Bid" })] })) : null, renderSublistingSection: sublistingCategoryId
131
131
  ? () => (_jsx(SublistingCarouselSection, { sublistingCategoryId: sublistingCategoryId, currentListingId: String(product.id) }))
132
132
  : undefined, renderTabs: () => (_jsx(ProductTabsShell, { descriptionContent: descriptionHtml ? (_jsx(RichText, { html: descriptionHtml, proseClass: "prose prose-sm sm:prose max-w-none dark:prose-invert", className: "text-[var(--appkit-color-text-muted)]" })) : undefined, specsContent: specs.length > 0 ? (_jsx(Dl, { divide: "subtle", rounded: "xl", border: "subtle", className: "overflow-hidden", children: specs.map((s, i) => (_jsxs(Row, { gap: "md", oddEven: "zebra", surface: "default", padding: "inline", children: [_jsx(Dt, { className: "w-36 flex-shrink-0", color: "primary", weight: "medium", children: s.name }), _jsxs(Dd, { className: "flex-1", color: "muted", children: [s.value, s.unit ? ` ${s.unit}` : ""] })] }, i))) })) : undefined, customTabs: customSections.map((s) => ({
133
133
  id: s.id,
@@ -28,7 +28,14 @@ function buildAuctionFilters(params) {
28
28
  else if (values.length > 1)
29
29
  parts.push(sieveFilter("storeId", SIEVE_OP.EQ, values.join("|")));
30
30
  }
31
- const dateFrom = sp(params, "dateFrom");
31
+ // Mirror AuctionsIndexListing's client-side default (Root Cause pattern —
32
+ // SSR initialData is seeded into React Query with staleTime:Infinity, so if
33
+ // this SSR filter doesn't already exclude ended auctions, the client never
34
+ // refetches and ended auctions leak into the default "Show ended" off view).
35
+ const showEnded = sp(params, "showEnded") === "true";
36
+ const dateFrom = showEnded
37
+ ? sp(params, "dateFrom")
38
+ : sp(params, "dateFrom") || new Date().toISOString();
32
39
  const dateTo = sp(params, "dateTo");
33
40
  if (dateFrom)
34
41
  parts.push(sieveFilter("auctionEndDate", SIEVE_OP.GTE, dateFrom));
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useState } from "react";
5
- import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
5
+ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } from "../../../ui";
6
6
  import { Form } from "../../../ui/components/Form";
7
7
  import { FieldInput } from "../../../ui/forms/FieldInput";
8
8
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
@@ -16,7 +16,7 @@ export function LoginForm({ onSubmit, onGoogleLogin, isLoading = false, error, l
16
16
  password: "",
17
17
  rememberMe: false,
18
18
  });
19
- return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
19
+ return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Sign In" }), (labels.subtitle || renderCreateAccountLink) && (_jsxs(Text, { className: "mt-2", color: "muted", size: "sm", children: [labels.subtitle ?? "Or", " ", renderCreateAccountLink?.()] }))] }), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "current-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), _jsxs(Row, { justify: "between", children: [_jsx(FieldCheckbox, { name: "rememberMe", label: labels.rememberMe ?? "Remember me", checked: values.rememberMe ?? false, onChange: (c) => setValues({ ...values, rememberMe: c }) }), renderForgotPasswordLink?.()] }), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
20
20
  clearErrors();
21
21
  const parsed = loginSchema.safeParse(values);
22
22
  if (!parsed.success)
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { normalizeError } from "../../../errors/normalize";
4
4
  import { useState } from "react";
5
5
  import { z } from "zod";
6
- import { Alert, Button, Div, Heading, Row, Stack, Text, useToast } from "../../../ui";
6
+ import { Alert, Button, Div, Heading, Row, SiteMark, Stack, Text, useToast } from "../../../ui";
7
7
  import { Form } from "../../../ui/components/Form";
8
8
  import { FieldInput } from "../../../ui/forms/FieldInput";
9
9
  import { FieldCheckbox } from "../../../ui/forms/FieldCheckbox";
@@ -28,7 +28,7 @@ export function RegisterForm({ onSubmit, isLoading = false, error, success, labe
28
28
  displayName: "",
29
29
  acceptTerms: false,
30
30
  });
31
- return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
31
+ return (_jsx(Row, { className: `min-h-[60vh] ${className}`, align: "center", justify: "center", padding: "x-md", children: _jsxs(Stack, { className: "max-w-md w-full", gap: "lg", children: [_jsxs(Div, { className: "text-center", children: [_jsx(Row, { justify: "center", className: "mb-3", children: _jsx(SiteMark, { size: "lg" }) }), _jsx(Heading, { level: 1, className: "font-extrabold", size: "3xl", children: labels.title ?? "Create Account" }), (labels.subtitle || renderLoginLink) && (_jsxs(Text, { className: "mt-2 text-[var(--appkit-color-text-muted)]", size: "sm", children: [labels.subtitle ?? "Already have an account?", " ", renderLoginLink?.()] }))] }), success && (_jsx(Alert, { variant: "success", compact: true, children: success })), error && (_jsx(Alert, { variant: "error", compact: true, children: error })), _jsx(Form, { noValidate: true, onSubmit: (e) => e.preventDefault(), spacing: "md", children: ({ setFieldError, clearErrors }) => (_jsxs(_Fragment, { children: [_jsx(FieldInput, { name: "displayName", label: labels.displayNameLabel ?? "Full name", type: "text", autoComplete: "name", required: true, placeholder: labels.displayNamePlaceholder ?? "Your name", value: values.displayName, onChange: (v) => setValues({ ...values, displayName: v }) }), _jsx(FieldInput, { name: "email", label: labels.emailLabel ?? "Email address", type: "email", autoComplete: "username", required: true, placeholder: labels.emailPlaceholder ?? "you@example.com", value: values.email, onChange: (v) => setValues({ ...values, email: v }) }), _jsxs(Div, { children: [_jsx(FieldInput, { name: "password", label: labels.passwordLabel ?? "Password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.passwordPlaceholder ?? "••••••••", value: values.password, onChange: (v) => setValues({ ...values, password: v }) }), renderPasswordStrength?.(values.password)] }), _jsx(FieldInput, { name: "confirmPassword", label: labels.confirmPasswordLabel ?? "Confirm password", type: "password", autoComplete: "new-password", required: true, placeholder: labels.confirmPasswordPlaceholder ?? "••••••••", value: values.confirmPassword, onChange: (v) => setValues({ ...values, confirmPassword: v }) }), _jsx(FieldCheckbox, { name: "acceptTerms", label: `${labels.acceptTermsLabel ?? "I accept the terms"}`, checked: values.acceptTerms, onChange: (c) => setValues({ ...values, acceptTerms: c }) }), renderTermsLink && (_jsx(Text, { size: "xs", variant: "secondary", children: renderTermsLink() })), _jsx(Button, { type: "submit", isLoading: isLoading, disabled: isLoading, className: "w-full", onClick: async () => {
32
32
  clearErrors();
33
33
  const parsed = registerClientSchema.safeParse(values);
34
34
  if (!parsed.success)
@@ -61,3 +61,19 @@ export declare function hasAllPermissions(permissions: Permission[], required: P
61
61
  * Check whether a store's capabilities include a specific capability.
62
62
  */
63
63
  export declare function hasCapability(capabilities: StoreCapability[], required: StoreCapability): boolean;
64
+ /**
65
+ * Groups permission prefixes into human-labeled domains for display —
66
+ * used by the employee-permission editor and the read-only permissions
67
+ * catalog page. `prefix` may hold multiple `|`-separated prefixes when a
68
+ * domain spans more than one resource namespace.
69
+ */
70
+ export declare const PERMISSION_DOMAINS: {
71
+ label: string;
72
+ prefix: string;
73
+ }[];
74
+ /** True when `perm` starts with any of the `|`-separated prefixes in `prefix`. */
75
+ export declare function matchesDomain(perm: string, prefix: string): boolean;
76
+ /** Every distinct permission (across all PERMISSION_GROUPS presets) belonging to a domain. */
77
+ export declare function getPermissionsForDomain(prefix: string): Permission[];
78
+ /** "admin:orders:mark-shipped" -> "Mark Shipped" */
79
+ export declare function formatPermLabel(perm: string): string;
@@ -342,3 +342,56 @@ export function hasAllPermissions(permissions, required) {
342
342
  export function hasCapability(capabilities, required) {
343
343
  return capabilities.includes(required);
344
344
  }
345
+ // ============================================================================
346
+ // PERMISSION DOMAINS — display grouping (S-ADMIN-7, 2026-08-19)
347
+ // ============================================================================
348
+ /**
349
+ * Groups permission prefixes into human-labeled domains for display —
350
+ * used by the employee-permission editor and the read-only permissions
351
+ * catalog page. `prefix` may hold multiple `|`-separated prefixes when a
352
+ * domain spans more than one resource namespace.
353
+ */
354
+ export const PERMISSION_DOMAINS = [
355
+ { label: "Dashboard", prefix: "admin:dashboard:" },
356
+ { label: "Users & Bans", prefix: "admin:users:|admin:user-bans:" },
357
+ { label: "Products", prefix: "admin:products:" },
358
+ { label: "Orders & Returns", prefix: "admin:orders:|admin:returns:" },
359
+ { label: "Stores", prefix: "admin:stores:|admin:store-addresses:" },
360
+ { label: "Finance", prefix: "admin:analytics:|admin:payouts:" },
361
+ { label: "Catalog", prefix: "admin:categories:|admin:brands:|admin:coupons:|admin:deals:|admin:featured:" },
362
+ {
363
+ label: "Content",
364
+ prefix: "admin:reviews:|admin:blog:|admin:bids:|admin:media:",
365
+ },
366
+ {
367
+ label: "Site / CMS",
368
+ prefix: "admin:site:|admin:navigation:|admin:sections:|admin:carousel:|admin:ads:|admin:faqs:|admin:newsletter:|admin:contact:",
369
+ },
370
+ { label: "Events", prefix: "admin:events:|admin:event-entries:" },
371
+ { label: "Support & Safety", prefix: "admin:support-tickets:|admin:scammers:" },
372
+ { label: "System", prefix: "admin:sessions:|admin:notifications:|admin:carts:|admin:wishlists:|admin:feature-flags:|admin:copilot:|admin:team:" },
373
+ ];
374
+ /** True when `perm` starts with any of the `|`-separated prefixes in `prefix`. */
375
+ export function matchesDomain(perm, prefix) {
376
+ return prefix.split("|").some((p) => perm.startsWith(p));
377
+ }
378
+ /** Every distinct permission (across all PERMISSION_GROUPS presets) belonging to a domain. */
379
+ export function getPermissionsForDomain(prefix) {
380
+ const allPerms = Object.values(PERMISSION_GROUPS).flat();
381
+ const seen = new Set();
382
+ const result = [];
383
+ for (const p of allPerms) {
384
+ if (matchesDomain(p, prefix) && !seen.has(p)) {
385
+ seen.add(p);
386
+ result.push(p);
387
+ }
388
+ }
389
+ return result;
390
+ }
391
+ /** "admin:orders:mark-shipped" -> "Mark Shipped" */
392
+ export function formatPermLabel(perm) {
393
+ const parts = perm.split(":");
394
+ return parts[parts.length - 1]
395
+ .replace(/-/g, " ")
396
+ .replace(/\b\w/g, (c) => c.toUpperCase());
397
+ }
@@ -30,6 +30,7 @@ export const DEFAULT_USER_DATA = {
30
30
  emailVerified: false,
31
31
  phoneVerified: false,
32
32
  disabled: false,
33
+ isTester: false,
33
34
  photoURL: null,
34
35
  displayName: null,
35
36
  publicProfile: {
@@ -67,5 +67,5 @@ export function BlogFilters({ table, variant = "admin" }) {
67
67
  const selectedCategory = table.get("category")
68
68
  ? table.get("category").split("|").filter(Boolean)
69
69
  : [];
70
- return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : "") })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true }), _jsx(RangeFilter, { title: t("votesRange"), minValue: table.get("minVotes"), maxValue: table.get("maxVotes"), onMinChange: (v) => table.set("minVotes", v), onMaxChange: (v) => table.set("maxVotes", v), minBound: 0, maxBound: 100000, step: 10, minPlaceholder: t("minVotes"), maxPlaceholder: t("maxVotes"), defaultCollapsed: true })] }));
70
+ return (_jsxs(Div, { children: [variant !== "public" && (_jsx(FilterFacetSection, { title: t("status"), options: statusOptions, selected: selectedStatus, onChange: (vals) => table.set("status", vals.join("|")), searchable: false, defaultCollapsed: false })), _jsx(FilterFacetSection, { title: t("category"), options: categoryOptions, selected: selectedCategory, onChange: (vals) => table.set("category", vals.join("|")), searchable: false, defaultCollapsed: variant !== "public" }), variant !== "public" && (_jsx(SwitchFilter, { title: t("isFeatured"), label: t("showFeaturedOnly"), checked: table.get("isFeatured") === "true", onChange: (v) => table.set("isFeatured", v ? "true" : "") })), _jsx(RangeFilter, { title: t("dateRange"), type: "date", minValue: table.get("dateFrom"), maxValue: table.get("dateTo"), onMinChange: (v) => table.set("dateFrom", v), onMaxChange: (v) => table.set("dateTo", v), minPlaceholder: t("minDate"), maxPlaceholder: t("maxDate"), defaultCollapsed: true })] }));
71
71
  }
@@ -50,7 +50,7 @@ export interface CartItemDocument {
50
50
  * ShippingPicker writes this when the buyer selects a provider.
51
51
  */
52
52
  chosenShippingProviderId?: string;
53
- /** Fee in paise for the chosen provider, snapshotted at cart time. */
53
+ /** Fee in decimal rupees for the chosen provider, snapshotted at cart time. */
54
54
  chosenShippingFee?: number;
55
55
  addedAt: Date;
56
56
  updatedAt: Date;
@@ -135,7 +135,7 @@ export interface CategoryDocument extends BaseDocument {
135
135
  * Does NOT update `partOfBundleIds`; used as a discovery surface only.
136
136
  */
137
137
  bundleKind?: "special" | "brand";
138
- /** Discounted bundle price in paise. */
138
+ /** Discounted bundle price in decimal rupees. */
139
139
  bundlePrice?: number;
140
140
  /** Rule resolving the bundle's member products — static list or live query. */
141
141
  bundleQueryRule?: BundleQueryRule;
@@ -43,7 +43,7 @@ export function ConsultationForm({ onSubmit, isPending, concerns = [], }) {
43
43
  }
44
44
  return (_jsxs(Form, { onSubmit: handleSubmit, spacing: "md", children: [_jsx(FieldInput, { name: "name", type: "text", placeholder: "Full name", value: form.name, onChange: (v) => handleValueChange("name", v), required: true }), _jsx(FieldInput, { name: "email", type: "email", placeholder: "Email address", value: form.email, onChange: (v) => handleValueChange("email", v), required: true }), _jsx(FieldInput, { name: "phone", type: "tel", placeholder: "Phone number", value: form.phone, onChange: (v) => handleValueChange("phone", v), required: true }), concerns.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", children: concerns.map((c) => (_jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => toggleConcern(c), className: `rounded-full border px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] text-[length:var(--appkit-text-xs)] transition ${form.concern.includes(c)
45
45
  ? "border-primary bg-primary text-white"
46
- : "border-neutral-300 text-neutral-700 hover:border-primary"}`, children: c }, c))) })), _jsxs(Row, { gap: "3", children: [_jsx(FieldInput, { name: "preferredDate", type: "date", value: form.preferredDate, onChange: (v) => handleValueChange("preferredDate", v), required: true, className: "flex-1" }), _jsx(Select, { value: form.preferredTime, onValueChange: (value) => handleValueChange("preferredTime", value), options: TIME_SLOTS.map((t) => ({ value: t, label: t })), className: "flex-1" })] }), _jsx(Select, { value: form.mode, onValueChange: (value) => handleValueChange("mode", value), options: [
46
+ : "border-neutral-300 text-neutral-700 hover:border-primary"}`, children: c }, c))) })), _jsxs(Row, { gap: "3", children: [_jsx(FieldInput, { name: "preferredDate", type: "date", value: form.preferredDate, onChange: (v) => handleValueChange("preferredDate", v), required: true, className: "flex-1" }), _jsx(Select, { value: form.preferredTime, onValueChange: (value) => handleValueChange("preferredTime", value), options: TIME_SLOTS.map((t) => ({ value: t, label: t })), wrapperClassName: "flex-1" })] }), _jsx(Select, { value: form.mode, onValueChange: (value) => handleValueChange("mode", value), options: [
47
47
  { value: "remote", label: "Remote (Video Call)" },
48
48
  { value: "in-person", label: "In-Person" },
49
49
  ] }), _jsx(FieldTextarea, { name: "message", placeholder: "Any additional notes (optional)", value: form.message, onChange: (v) => handleValueChange("message", v), rows: 3 }), _jsx(Button, { type: "submit", isLoading: isPending, disabled: isPending, variant: "primary", className: "w-full", children: isPending ? "Booking..." : "Book Consultation" })] }));
@@ -56,6 +56,8 @@ export function EventFilters({ table, variant = "admin" }) {
56
56
  { value: "poll", label: t("eventTypePoll") },
57
57
  { value: "survey", label: t("eventTypeSurvey") },
58
58
  { value: "feedback", label: t("eventTypeFeedback") },
59
+ { value: "raffle", label: t("eventTypeRaffle") },
60
+ { value: "spin_wheel", label: t("eventTypeSpinWheel") },
59
61
  ];
60
62
  const adminStatusOptions = [
61
63
  { value: "draft", label: t("eventStatusDraft") },
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { THEMED_TEXT_SECONDARY, FLEX_CENTER } from "../../../_internal/shared/styles/themed";
4
- import { Button, Div, Grid, Heading, Row, Section, SiteLogo, Span, Text, TextLink } from "../../../ui";
4
+ import { Button, Div, Grid, Heading, Row, Section, SiteLogo, SiteMark, Span, Stack, Text, TextLink } from "../../../ui";
5
5
  // --- Component ---------------------------------------------------------------
6
6
  export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, ctaLabel = "Shop now", ctaHref, onCtaClick, secondaryCtaLabel = "Browse products", secondaryCtaHref, onSecondaryCtaClick, trustChips = [], isLoading = false, brandLogoText = "", className = "", }) {
7
7
  const themed = { textSecondary: THEMED_TEXT_SECONDARY };
@@ -9,5 +9,5 @@ export function WelcomeSection({ title, subtitle, pillLabel, showCTA = true, cta
9
9
  if (isLoading) {
10
10
  return (_jsx(Section, { className: `relative overflow-hidden md:py-[6rem] ${className}`, paddingY: "y-4xl", paddingX: "x-md", children: _jsxs(Div, { className: "animate-pulse max-w-4xl mx-auto text-left", children: [_jsx(Div, { className: "h-6 w-52 mb-6", surface: "subtle", rounded: "full" }), _jsx(Div, { className: "h-20 mb-4 max-w-2xl", surface: "subtle", rounded: "lg" }), _jsx(Div, { className: "h-6 mb-8 max-w-lg", surface: "subtle", rounded: "lg" }), _jsxs(Row, { justify: "start", gap: "md", children: [_jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" }), _jsx(Div, { className: "h-12 w-36", surface: "subtle", rounded: "xl" })] })] }) }));
11
11
  }
12
- return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsx(Row, { centered: true, className: `absolute inset-0 ${flex.center} px-[2.5rem]`, children: _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "hero" }) })] }) })] }) })] }));
12
+ return (_jsxs(Section, { className: `relative overflow-hidden md:py-[7rem] ${className}`, paddingY: "y-5xl", paddingX: "x-md", children: [_jsx(Div, { className: "pointer-events-none absolute -top-32 -left-32 w-[28rem] h-[28rem] bg-primary/10 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "pointer-events-none absolute -bottom-40 -right-40 w-[36rem] h-[36rem] bg-secondary/10 dark:bg-secondary/15 blur-3xl", rounded: "full", "aria-hidden": "true" }), _jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", children: _jsxs(Grid, { align: "center", gap: "2xl", className: "grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2", children: [_jsxs(Div, { className: "text-left", children: [pillLabel && (_jsx(Div, { children: _jsxs(Span, { layout: "inline-flex", gap: "md", size: "xs", weight: "medium", className: "border border-primary-[500]/30 bg-primary-500/10 px-[1.25rem] py-[0.375rem] tracking-[0.2em] text-primary-700 dark:text-primary-400 backdrop-blur-sm", rounded: "full", transform: "uppercase", children: [_jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" }), pillLabel, _jsx(Span, { className: "w-1.5 h-1.5 bg-primary-500 inline-block", rounded: "full", "aria-hidden": "true" })] }) })), _jsx(Heading, { level: 1, variant: "none", gradient: "brand-tri", className: "mt-4 font-display leading-[1.1] tracking-tight break-words", size: "5xl", mdSize: "6xl", lgSize: "7xl", xlSize: "7xl", children: title }), subtitle && (_jsx(Text, { className: `mt-4 ${themed.textSecondary} max-w-xl leading-relaxed`, size: "xl", children: subtitle })), showCTA && (_jsxs(Row, { wrap: true, gap: "md", className: "mt-8", justify: "start", children: [ctaHref ? (_jsx(TextLink, { rounded: "xl", href: ctaHref, className: "inline-flex items-[center] justify-[center] px-[2rem] py-[0.875rem] !bg-primary hover:!bg-primary-600 text-white dark:!bg-primary dark:hover:!bg-primary-600 dark:text-white btn-glow transition-all hover:scale-[1.02]", size: "base", weight: "bold", children: ctaLabel })) : (_jsx(Button, { variant: "primary", size: "lg", onClick: onCtaClick, children: ctaLabel })), secondaryCtaHref ? (_jsx(TextLink, { rounded: "xl", href: secondaryCtaHref, className: "inline-flex items-[center] justify-[center] border-2 border-cobalt/40 dark:border-cobalt-400/40 px-[2rem] py-[0.875rem] text-cobalt-700 dark:text-cobalt-300 hover:bg-cobalt-50 dark:hover:bg-cobalt-900/20 transition-all hover:scale-[1.02]", size: "base", weight: "semibold", children: secondaryCtaLabel })) : (_jsx(Button, { variant: "outline", size: "lg", onClick: onSecondaryCtaClick, children: secondaryCtaLabel }))] })), trustChips.length > 0 && (_jsx(Row, { wrap: true, gap: "sm", className: "mt-6", justify: "start", children: trustChips.map((chip) => (_jsxs(Span, { layout: "inline-flex", gap: "xs", size: "xs", weight: "medium", className: "border border-zinc-[200] dark:border-slate-[700] px-[0.875rem] py-[0.375rem]", rounded: "full", surface: "subtle", color: "muted", children: [chip.emoji, " ", chip.label] }, chip.key))) }))] }), _jsx(Div, { className: "hidden lg:block", children: _jsxs(Div, { className: `relative overflow-hidden aspect-[4/3] bg-[image:var(--appkit-gradient-section-mesh)]`, shadow: "2xl", border: "default", rounded: "3xl", children: [_jsx(Div, { className: "absolute inset-0 bg-[image:var(--appkit-gradient-glass)]" }), _jsxs(Stack, { centered: true, gap: "sm", className: `absolute inset-0 ${flex.center} px-[2.5rem]`, children: [_jsx(SiteMark, { title: brandLogoText || "LetItRip.in", size: "hero" }), _jsx(SiteLogo, { title: brandLogoText || "LetItRip.in", size: "hero" })] })] }) })] }) })] }));
13
13
  }
@@ -7,11 +7,15 @@ export interface BackToTopProps {
7
7
  /**
8
8
  * BackToTop — floating scroll-to-top button.
9
9
  * Appears after the page has scrolled past `threshold` pixels. A small
10
- * dismiss control lets the user hide it for the rest of the browser
11
- * session (sessionStorage) on narrow viewports it can sit close enough
12
- * to the bottom nav / other floating controls to feel like clutter, so it
13
- * needs the same "get out of my way" escape hatch as StickyToolbar.
14
- * Re-appears on the next page load.
10
+ * dismiss control hides it for the current page view only — dismissal is
11
+ * plain in-memory component state, reset on every route change (via
12
+ * `usePathname()`) so navigating anywhere brings it back. Deliberately NOT
13
+ * persisted to storage: an earlier version wrote the dismissal to
14
+ * `sessionStorage`, and because this component is mounted once inside the
15
+ * persistent `AppLayoutShell` (it never remounts on client-side navigation),
16
+ * that meant one click hid the button for the rest of the browser tab
17
+ * session with no way to bring it back — reported as "the button is gone
18
+ * forever." See CLAUDE.md Recurrent Root Cause Patterns.
15
19
  */
16
20
  export declare function BackToTop({ threshold, ariaLabel, className, }: BackToTopProps): import("react").JSX.Element | null;
17
21
  /** SkipToMain — visually hidden link that becomes visible on focus for keyboard users. */
@@ -1,46 +1,42 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
4
5
  import { useEffect, useState } from "react";
5
6
  import { ChevronUp, X } from "lucide-react";
6
7
  import { Button, Div } from "../../ui";
7
- import { normalizeError } from "../../errors/normalize";
8
- const DISMISSED_KEY = "appkit:back-to-top-dismissed";
9
8
  /**
10
9
  * BackToTop — floating scroll-to-top button.
11
10
  * Appears after the page has scrolled past `threshold` pixels. A small
12
- * dismiss control lets the user hide it for the rest of the browser
13
- * session (sessionStorage) on narrow viewports it can sit close enough
14
- * to the bottom nav / other floating controls to feel like clutter, so it
15
- * needs the same "get out of my way" escape hatch as StickyToolbar.
16
- * Re-appears on the next page load.
11
+ * dismiss control hides it for the current page view only — dismissal is
12
+ * plain in-memory component state, reset on every route change (via
13
+ * `usePathname()`) so navigating anywhere brings it back. Deliberately NOT
14
+ * persisted to storage: an earlier version wrote the dismissal to
15
+ * `sessionStorage`, and because this component is mounted once inside the
16
+ * persistent `AppLayoutShell` (it never remounts on client-side navigation),
17
+ * that meant one click hid the button for the rest of the browser tab
18
+ * session with no way to bring it back — reported as "the button is gone
19
+ * forever." See CLAUDE.md Recurrent Root Cause Patterns.
17
20
  */
18
21
  export function BackToTop({ threshold = 400, ariaLabel = "Back to top", className = "", }) {
22
+ const pathname = usePathname();
19
23
  const [visible, setVisible] = useState(false);
20
24
  const [dismissed, setDismissed] = useState(false);
21
25
  useEffect(() => {
22
- try {
23
- setDismissed(window.sessionStorage.getItem(DISMISSED_KEY) === "1");
24
- }
25
- catch (_err) {
26
- void normalizeError(_err);
27
- // sessionStorage unavailable — never dismissed for this render.
28
- }
29
26
  const handleScroll = () => setVisible(window.scrollY > threshold);
30
27
  window.addEventListener("scroll", handleScroll, { passive: true });
31
28
  return () => window.removeEventListener("scroll", handleScroll);
32
29
  }, [threshold]);
30
+ // Reset dismissal on every route change — the component itself never
31
+ // unmounts (it lives inside the persistent app shell), so without this
32
+ // a single dismiss would otherwise hide the button for the whole session.
33
+ useEffect(() => {
34
+ setDismissed(false);
35
+ }, [pathname]);
33
36
  if (!visible || dismissed)
34
37
  return null;
35
38
  const handleDismiss = () => {
36
39
  setDismissed(true);
37
- try {
38
- window.sessionStorage.setItem(DISMISSED_KEY, "1");
39
- }
40
- catch (_err) {
41
- void normalizeError(_err);
42
- // sessionStorage unavailable — dismissal still applies for this render.
43
- }
44
40
  };
45
41
  return (_jsxs(Div, { layout: "flex", align: "center", gap: "1", className: `fixed bottom-[calc(var(--keyboard-inset-height,0px)+var(--bottom-nav-height,4rem))] right-4 z-[var(--appkit-z-bottom-nav)] ${className}`, children: [_jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": ariaLabel, onClick: () => window.scrollTo({ top: 0, behavior: "smooth" }), className: "flex items-center justify-center w-10 h-10 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-muted)] hover:bg-[var(--appkit-color-surface)] dark:hover:bg-slate-700 transition-colors", children: _jsx(ChevronUp, { className: "w-5 h-5", "aria-hidden": "true" }) }), _jsx(Button, { type: "button", variant: "outline", size: "sm", "aria-label": "Hide back-to-top button", onClick: handleDismiss, className: "flex items-center justify-center w-6 h-6 rounded-full bg-[var(--appkit-color-surface)] border border-[var(--appkit-color-border)] shadow-md text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text)] transition-colors", children: _jsx(X, { className: "w-3 h-3", "aria-hidden": "true" }) })] }));
46
42
  }
@@ -61,9 +61,9 @@ export interface TitleBarLayoutProps {
61
61
  * TitleBarLayout — generic top sticky title-bar shell.
62
62
  *
63
63
  * Layout:
64
- * TB1 (h-14, all screens): logo | navSlot? | [secondary actions lg+] | search | deals | theme | hamburger
64
+ * TB1 (h-14, all screens): wordmark (+ mark on mobile) | centred mark (md+) | [secondary actions lg+] | search | deals | theme | hamburger
65
65
  * TB2 (h-10, below lg only): wishlist | cart | profile — mirrors what TB1 hides below lg
66
66
  *
67
67
  * Receives all domain data as props — zero domain imports.
68
68
  */
69
- export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
69
+ export declare function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount, cartHref, cartCount, profileHref, unreadNotificationCount, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot: _navSlot, promoStripText, isDark, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle, id, className, }: TitleBarLayoutProps): React.JSX.Element;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
- import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, Span } from "../../ui";
3
+ import { Avatar, BlockHeader, Button, Div, Row, Section, SiteLogo, SiteMark, Span } from "../../ui";
4
4
  /** Shared icon-button class for all action buttons in the title bar. */
5
5
  const iconBtn = "flex items-center justify-center w-9 h-9 rounded-lg text-[var(--appkit-color-text-muted)] hover:bg-primary-50 hover:text-primary-700 hover:bg-[var(--appkit-color-surface-elevated)] dark:hover:text-secondary-400 transition-colors";
6
6
  /** Badge counter class for wishlist/cart counts. */
@@ -9,12 +9,12 @@ const countBadge = "absolute -top-0.5 -right-0.5 flex items-center justify-cente
9
9
  * TitleBarLayout — generic top sticky title-bar shell.
10
10
  *
11
11
  * Layout:
12
- * TB1 (h-14, all screens): logo | navSlot? | [secondary actions lg+] | search | deals | theme | hamburger
12
+ * TB1 (h-14, all screens): wordmark (+ mark on mobile) | centred mark (md+) | [secondary actions lg+] | search | deals | theme | hamburger
13
13
  * TB2 (h-10, below lg only): wishlist | cart | profile — mirrors what TB1 hides below lg
14
14
  *
15
15
  * Receives all domain data as props — zero domain imports.
16
16
  */
17
- export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
17
+ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, searchOpen: _searchOpen, brandName, brandShortName: _brandShortName, siteLogoUrl, logoHref, promotionsHref, compareHref, wishlistHref, wishlistCount = 0, cartHref, cartCount = 0, profileHref, unreadNotificationCount = 0, notificationsHref, loginHref, registerHref, user, notificationSlot, devSlot, navSlot: _navSlot, promoStripText, isDark = false, onToggleTheme, onTourStart, hasDashboardNav, onToggleDashboardNav, hideSidebarToggle = false, id = "titlebar", className = "", }) {
18
18
  // ── Element builders ────────────────────────────────────────────────────────
19
19
  const promotionsEl = promotionsHref ? (_jsxs(Link, { href: promotionsHref, "aria-label": "Today's deals", className: "flex items-center gap-[var(--appkit-space-1)] px-[var(--appkit-space-3)] py-[var(--appkit-space-1)] rounded-full text-[length:var(--appkit-text-xs)] font-bold bg-primary-100 text-primary-700 dark:bg-secondary-900/40 dark:text-secondary-400 hover:bg-primary-200 dark:hover:bg-secondary-900/60 transition-colors border border-primary-200/60 dark:border-secondary-700/40", children: [_jsx("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M21.41 11.58l-9-9C12.05 2.22 11.55 2 11 2H4c-1.1 0-2 .9-2 2v7c0 .55.22 1.05.59 1.42l9 9c.36.37.86.58 1.41.58.55 0 1.05-.21 1.41-.59l7-7c.37-.36.59-.86.59-1.41 0-.55-.23-1.06-.59-1.42zM5.5 7C4.67 7 4 6.33 4 5.5S4.67 4 5.5 4 7 4.67 7 5.5 6.33 7 5.5 7z" }) }), _jsx(Span, { className: "hidden sm:inline", children: "Today's Deals" })] })) : null;
20
20
  const themeBtn = onToggleTheme ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: onToggleTheme, className: iconBtn, children: isDark ? (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 3v1m0 16v1m8.66-9h-1M4.34 12h-1m15.07-6.07-.71.71M6.34 17.66l-.71.71m12.73 0-.71-.71M6.34 6.34l-.71-.71M12 5a7 7 0 1 0 0 14A7 7 0 0 0 12 5z" }) })) : (_jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 12.79A9 9 0 1 1 11.21 3a7 7 0 1 0 9.79 9.79z" }) })) })) : null;
@@ -33,7 +33,7 @@ export function TitleBarLayout({ onToggleSidebar, sidebarOpen, onSearchToggle, s
33
33
  const authButtonsEl = !user && (loginHref || registerHref) ? (_jsxs(Row, { gap: "xs", className: "hidden lg:flex", children: [loginHref && (_jsx(Link, { href: loginHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-medium text-[var(--appkit-color-text-muted)] hover:text-primary-700 dark:hover:text-secondary-400 transition-colors rounded-lg hover:bg-primary-50 hover:bg-[var(--appkit-color-surface-elevated)]", children: "Sign in" })), registerHref && (_jsx(Link, { href: registerHref, className: "px-[var(--appkit-space-3)] py-[var(--appkit-space-1-5)] text-[length:var(--appkit-text-sm)] font-semibold rounded-lg bg-primary text-white hover:bg-primary/90 transition-colors btn-glow shadow-sm", children: "Register" }))] })) : null;
34
34
  const hasTb2 = !!(notificationsEl || wishlistEl || cartEl || profileEl);
35
35
  // ── Render ───────────────────────────────────────────────────────────────────
36
- return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { title: brandName, size: "md" }) }) }), siteLogoUrl ? (_jsx(Row, { className: "absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteLogo, { src: siteLogoUrl, title: brandName, size: "md" }) }) })) : (navSlot && _jsx(Div, { className: "hidden md:flex ml-auto", children: navSlot })), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
36
+ return (_jsxs(BlockHeader, { id: id, className: `sticky top-0 z-50 bg-[var(--appkit-color-bg)]/95 backdrop-blur-md border-b border-[var(--appkit-color-border-subtle)] shadow-sm ${className}`, children: [promoStripText && (_jsx(Section, { color: "inverse", tone: "accent-banner", className: "text-[length:var(--appkit-text-xs)] text-center font-medium", padding: "y-2xs", children: promoStripText })), _jsxs(Div, { paddingX: "x-page", className: "container mx-auto max-w-[1920px]", children: [_jsxs(Row, { justify: "between", gap: "none", className: "relative h-14", children: [_jsx(Row, { gap: "3", children: _jsxs(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center gap-[var(--appkit-space-2)] transition-opacity hover:opacity-80", children: [_jsx(Div, { className: "flex md:hidden", children: _jsx(SiteMark, { title: brandName, size: "sm" }) }), _jsx(SiteLogo, { title: brandName, size: "md" })] }) }), _jsx(Row, { className: "hidden md:flex absolute inset-y-0 left-1/2 -translate-x-1/2", align: "center", children: _jsx(Link, { href: logoHref, "aria-label": brandName, className: "flex items-center transition-opacity hover:opacity-80", children: _jsx(SiteMark, { src: siteLogoUrl, title: brandName, size: "lg" }) }) }), _jsxs(Row, { gap: "xs", children: [devSlot, compareEl, notificationSlot, notificationsEl && _jsx(Div, { className: "hidden lg:flex", children: notificationsEl }), wishlistEl && _jsx(Div, { className: "hidden lg:flex", children: wishlistEl }), cartEl && _jsx(Div, { className: "hidden lg:flex", children: cartEl }), user
37
37
  ? profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })
38
38
  : authButtonsEl ?? (profileEl && _jsx(Div, { className: "hidden lg:flex", children: profileEl })), searchBtn, promotionsEl, tourBtn, themeBtn, hamburgerBtn] })] }), hasTb2 && (_jsxs(Row, { border: "subtle", as: "nav", "aria-label": "Account actions", justify: "end", gap: "xs", className: "flex lg:hidden h-10 border-t px-[var(--appkit-space-1)]", children: [notificationsEl, wishlistEl, cartEl, profileEl] }))] })] }));
39
39
  }