@mohasinac/appkit 4.2.0 → 4.3.1

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 (258) hide show
  1. package/dist/_internal/client/features/layout/navActive.d.ts +17 -0
  2. package/dist/_internal/client/features/layout/navActive.js +33 -0
  3. package/dist/_internal/server/features/auth/actions.d.ts +0 -1
  4. package/dist/_internal/server/features/auth/actions.js +0 -1
  5. package/dist/_internal/server/features/bundles/data.d.ts +22 -4
  6. package/dist/_internal/server/features/bundles/data.js +41 -4
  7. package/dist/_internal/server/features/bundles/index.d.ts +1 -1
  8. package/dist/_internal/server/features/bundles/index.js +1 -1
  9. package/dist/_internal/server/features/checkout/actions.js +7 -78
  10. package/dist/_internal/server/features/checkout/prize-bundle-gates.d.ts +5 -16
  11. package/dist/_internal/server/features/checkout/prize-bundle-gates.js +5 -30
  12. package/dist/_internal/server/features/orders/adapters.js +1 -14
  13. package/dist/_internal/server/features/products/actions.js +8 -3
  14. package/dist/_internal/server/features/products/service.d.ts +15 -0
  15. package/dist/_internal/server/features/products/service.js +41 -0
  16. package/dist/_internal/server/functions/firestore.d.ts +3 -1
  17. package/dist/_internal/server/functions/firestore.js +17 -1
  18. package/dist/_internal/server/functions/scheduled.d.ts +2 -5
  19. package/dist/_internal/server/functions/scheduled.js +6 -30
  20. package/dist/_internal/server/jobs/core/bundleStockSync.js +25 -7
  21. package/dist/_internal/server/jobs/core/countersReconcile.js +7 -1
  22. package/dist/_internal/server/jobs/core/index.d.ts +4 -4
  23. package/dist/_internal/server/jobs/core/index.js +4 -4
  24. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.d.ts +19 -0
  25. package/dist/_internal/server/jobs/core/onPrizeDrawPaymentConfirmed.js +30 -0
  26. package/dist/_internal/server/jobs/core/paymentWindowTimeout.d.ts +2 -2
  27. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +2 -2
  28. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.d.ts +20 -0
  29. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +113 -0
  30. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.d.ts +14 -0
  31. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +74 -0
  32. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.d.ts +22 -0
  33. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +68 -0
  34. package/dist/_internal/server/jobs/handlers/index.d.ts +3 -4
  35. package/dist/_internal/server/jobs/handlers/index.js +8 -6
  36. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.d.ts +3 -0
  37. package/dist/_internal/server/jobs/handlers/onPrizeDrawPaymentConfirmed.js +11 -0
  38. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.d.ts +2 -0
  39. package/dist/_internal/server/jobs/handlers/prizeDrawExpiryReveal.js +2 -0
  40. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.d.ts +3 -0
  41. package/dist/_internal/server/jobs/handlers/prizeDrawSoldOutReveal.js +4 -0
  42. package/dist/_internal/shared/actions/action-registry.js +40 -10
  43. package/dist/_internal/shared/checkout/rules/_defaults.js +1 -2
  44. package/dist/_internal/shared/checkout/rules/_limits.d.ts +3 -0
  45. package/dist/_internal/shared/checkout/rules/_limits.js +3 -0
  46. package/dist/_internal/shared/checkout/rules/_registry.d.ts +2 -1
  47. package/dist/_internal/shared/checkout/rules/_registry.js +2 -2
  48. package/dist/_internal/shared/checkout/rules/classified.rule.js +0 -1
  49. package/dist/_internal/shared/checkout/rules/index.d.ts +1 -1
  50. package/dist/_internal/shared/checkout/rules/index.js +1 -1
  51. package/dist/_internal/shared/checkout/rules/prize-draw.rule.js +20 -1
  52. package/dist/_internal/shared/checkout/rules/types.d.ts +17 -7
  53. package/dist/_internal/shared/features/categories/bundle-copy.d.ts +2 -0
  54. package/dist/_internal/shared/features/categories/bundle-copy.js +2 -0
  55. package/dist/_internal/shared/features/categories/bundle-pricing.d.ts +23 -0
  56. package/dist/_internal/shared/features/categories/bundle-pricing.js +25 -0
  57. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -1
  58. package/dist/constants/api-endpoints.d.ts +6 -9
  59. package/dist/constants/api-endpoints.js +5 -5
  60. package/dist/constants/field-names.d.ts +17 -1
  61. package/dist/constants/field-names.js +17 -1
  62. package/dist/features/account/components/UserSidebar.js +21 -9
  63. package/dist/features/account/hooks/useNotifications.js +5 -0
  64. package/dist/features/admin/actions/admin-actions.js +6 -0
  65. package/dist/features/admin/actions/notification-actions.js +1 -2
  66. package/dist/features/admin/components/AdminBlogEditorView.js +1 -1
  67. package/dist/features/admin/components/AdminGuideHubView.js +15 -1
  68. package/dist/features/admin/components/AdminPaymentsGuideView.d.ts +2 -0
  69. package/dist/features/admin/components/AdminPaymentsGuideView.js +36 -0
  70. package/dist/features/admin/components/AdminPrizeDrawsView.js +3 -3
  71. package/dist/features/admin/components/AdminSidebar.js +18 -9
  72. package/dist/features/admin/components/AdminSiteSettingsView.js +33 -6
  73. package/dist/features/admin/components/AdminTesterChecklistView.js +74 -22
  74. package/dist/features/admin/components/AdminWhatsAppGuideView.d.ts +2 -0
  75. package/dist/features/admin/components/AdminWhatsAppGuideView.js +35 -0
  76. package/dist/features/admin/hooks/useAdminListing.d.ts +4 -0
  77. package/dist/features/admin/hooks/useAdminListing.js +4 -2
  78. package/dist/features/admin/schemas/firestore.d.ts +13 -5
  79. package/dist/features/admin/schemas/firestore.js +1 -2
  80. package/dist/features/auctions/actions/bid-actions.js +3 -1
  81. package/dist/features/auctions/components/AuctionBidsTable.d.ts +6 -1
  82. package/dist/features/auctions/components/AuctionBidsTable.js +8 -4
  83. package/dist/features/auctions/components/AuctionDetailPageView.js +10 -13
  84. package/dist/features/auctions/components/CollapsibleBidHistory.d.ts +23 -2
  85. package/dist/features/auctions/components/CollapsibleBidHistory.js +22 -3
  86. package/dist/features/auctions/components/LiveBidPrice.d.ts +19 -0
  87. package/dist/features/auctions/components/LiveBidPrice.js +17 -0
  88. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +1 -1
  89. package/dist/features/auctions/components/PlaceBidFormClient.js +16 -1
  90. package/dist/features/auctions/hooks/useBids.d.ts +33 -0
  91. package/dist/features/auctions/hooks/useBids.js +32 -0
  92. package/dist/features/auctions/hooks/useLiveAuctionBid.d.ts +20 -0
  93. package/dist/features/auctions/hooks/useLiveAuctionBid.js +23 -0
  94. package/dist/features/auth/checkout-value-otp.d.ts +7 -10
  95. package/dist/features/auth/checkout-value-otp.js +14 -10
  96. package/dist/features/auth/server.d.ts +0 -1
  97. package/dist/features/auth/server.js +0 -1
  98. package/dist/features/blog/api/[slug]/route.d.ts +5 -0
  99. package/dist/features/blog/api/[slug]/route.js +12 -3
  100. package/dist/features/blog/components/BlogPostView.d.ts +2 -0
  101. package/dist/features/blog/components/BlogPostView.js +5 -4
  102. package/dist/features/blog/hooks/useBlog.d.ts +2 -0
  103. package/dist/features/blog/hooks/useBlog.js +2 -0
  104. package/dist/features/blog/repository/blog.repository.d.ts +7 -0
  105. package/dist/features/blog/repository/blog.repository.js +44 -0
  106. package/dist/features/blog/schemas/firestore.d.ts +3 -0
  107. package/dist/features/blog/schemas/firestore.js +1 -0
  108. package/dist/features/blog/types/index.d.ts +2 -0
  109. package/dist/features/cart/components/index.d.ts +0 -2
  110. package/dist/features/cart/components/index.js +0 -1
  111. package/dist/features/cart/hooks/useCartCount.js +22 -5
  112. package/dist/features/cart/hooks/useGuestCart.js +11 -1
  113. package/dist/features/cart/utils/guest-cart.d.ts +4 -0
  114. package/dist/features/cart/utils/guest-cart.js +10 -0
  115. package/dist/features/cart/utils/pending-ops.d.ts +17 -0
  116. package/dist/features/cart/utils/pending-ops.js +46 -5
  117. package/dist/features/categories/components/BrandDetailPageView.js +6 -2
  118. package/dist/features/categories/components/BundleDetailView.js +4 -2
  119. package/dist/features/categories/components/CategoryDetailPageView.js +10 -2
  120. package/dist/features/categories/schemas/firestore.d.ts +7 -0
  121. package/dist/features/checkout/actions/index.d.ts +0 -1
  122. package/dist/features/checkout/actions/index.js +0 -1
  123. package/dist/features/checkout/schemas/firestore.d.ts +2 -2
  124. package/dist/features/events/actions/event-actions.d.ts +2 -0
  125. package/dist/features/events/actions/event-actions.js +5 -0
  126. package/dist/features/events/components/EventCard.js +2 -3
  127. package/dist/features/events/components/RelatedEventsCarousel.d.ts +15 -0
  128. package/dist/features/events/components/RelatedEventsCarousel.js +13 -0
  129. package/dist/features/events/components/index.d.ts +2 -0
  130. package/dist/features/events/components/index.js +1 -0
  131. package/dist/features/events/repository/events.repository.d.ts +6 -0
  132. package/dist/features/events/repository/events.repository.js +21 -0
  133. package/dist/features/history/repository/user-history.repository.d.ts +1 -1
  134. package/dist/features/history/utils/guest-history.d.ts +1 -1
  135. package/dist/features/homepage/components/FeaturedBundlesSection.js +4 -2
  136. package/dist/features/homepage/components/FeaturedProductsSection.js +2 -2
  137. package/dist/features/homepage/components/SectionCarousel.js +1 -1
  138. package/dist/features/homepage/components/WelcomeSection.js +1 -1
  139. package/dist/features/layout/AppLayoutShell.js +44 -10
  140. package/dist/features/layout/NavbarLayout.js +1 -1
  141. package/dist/features/layout/TitleBarLayout.js +2 -2
  142. package/dist/features/media/MediaVideo.d.ts +7 -0
  143. package/dist/features/media/MediaVideo.js +36 -5
  144. package/dist/features/orders/components/OrdersList.js +1 -5
  145. package/dist/features/orders/schemas/firestore.d.ts +2 -8
  146. package/dist/features/orders/schemas/index.d.ts +3 -32
  147. package/dist/features/orders/schemas/index.js +1 -4
  148. package/dist/features/orders/types/index.d.ts +6 -9
  149. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +1 -1
  150. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +2 -1
  151. package/dist/features/products/actions/product-actions.js +13 -10
  152. package/dist/features/products/components/MarketplaceBundleCard.d.ts +1 -1
  153. package/dist/features/products/components/MarketplaceBundleCard.js +7 -4
  154. package/dist/features/products/components/PrizeDrawDetailPageView.js +1 -1
  155. package/dist/features/products/components/PrizeDrawItemsEditor.js +2 -2
  156. package/dist/features/products/components/PrizeDrawWinnerMappingView.d.ts +14 -0
  157. package/dist/features/products/components/PrizeDrawWinnerMappingView.js +37 -0
  158. package/dist/features/products/components/PrizeRevealModal.d.ts +10 -23
  159. package/dist/features/products/components/PrizeRevealModal.js +18 -147
  160. package/dist/features/products/components/ProductDetailPageView.js +72 -30
  161. package/dist/features/products/components/ProductForm.js +7 -14
  162. package/dist/features/products/components/ProductGalleryClient.d.ts +10 -1
  163. package/dist/features/products/components/ProductGalleryClient.js +29 -10
  164. package/dist/features/products/components/ProductsIndexListing.js +2 -1
  165. package/dist/features/products/components/RelatedProductsCarousel.js +2 -2
  166. package/dist/features/products/components/ShowGroupSection.js +2 -2
  167. package/dist/features/products/components/SublistingCarouselSection.js +1 -1
  168. package/dist/features/products/components/index.d.ts +4 -2
  169. package/dist/features/products/components/index.js +1 -0
  170. package/dist/features/products/constants/action-defs.d.ts +1 -3
  171. package/dist/features/products/constants/action-defs.js +0 -5
  172. package/dist/features/products/repository/products.repository.d.ts +6 -0
  173. package/dist/features/products/repository/products.repository.js +20 -1
  174. package/dist/features/products/schemas/firestore.d.ts +28 -3
  175. package/dist/features/products/schemas/index.d.ts +11 -3
  176. package/dist/features/products/schemas/index.js +10 -2
  177. package/dist/features/products/types/index.d.ts +2 -1
  178. package/dist/features/reviews/actions/review-actions.js +1 -0
  179. package/dist/features/reviews/components/ReviewDetailPageView.js +8 -1
  180. package/dist/features/reviews/components/ReviewDetailShell.js +2 -1
  181. package/dist/features/reviews/components/ReviewsList.js +2 -1
  182. package/dist/features/reviews/schemas/firestore.d.ts +3 -0
  183. package/dist/features/reviews/schemas/index.d.ts +6 -0
  184. package/dist/features/reviews/schemas/index.js +2 -0
  185. package/dist/features/reviews/types/index.d.ts +3 -0
  186. package/dist/features/scams/actions/scam-actions.d.ts +3 -0
  187. package/dist/features/scams/actions/scam-actions.js +3 -2
  188. package/dist/features/scams/components/ScamProfileView.d.ts +2 -1
  189. package/dist/features/scams/components/ScamProfileView.js +2 -2
  190. package/dist/features/scams/repository/scammer.repository.d.ts +6 -0
  191. package/dist/features/scams/repository/scammer.repository.js +24 -0
  192. package/dist/features/seller/actions/seller-actions.js +8 -0
  193. package/dist/features/seller/components/SellerPrizeDrawsView.js +4 -0
  194. package/dist/features/seller/components/SellerSidebar.js +20 -9
  195. package/dist/features/stores/components/StoreDetailLayoutView.js +6 -6
  196. package/dist/features/stores/components/StoreGuideHubView.js +7 -1
  197. package/dist/features/stores/components/StoreNavTabs.d.ts +10 -1
  198. package/dist/features/stores/components/StoreNavTabs.js +22 -9
  199. package/dist/features/stores/components/StoreWhatsAppGuideView.d.ts +3 -0
  200. package/dist/features/stores/components/StoreWhatsAppGuideView.js +12 -0
  201. package/dist/features/stores/components/index.d.ts +2 -0
  202. package/dist/features/stores/components/index.js +1 -0
  203. package/dist/features/tester/actions/index.d.ts +1 -0
  204. package/dist/features/tester/actions/index.js +1 -0
  205. package/dist/features/tester/actions/leaderboard-actions.d.ts +3 -0
  206. package/dist/features/tester/actions/leaderboard-actions.js +5 -0
  207. package/dist/features/tester/components/AdminTesterFeedbackIssuesView.js +26 -4
  208. package/dist/features/tester/components/AdminTesterFeedbackListView.js +13 -0
  209. package/dist/features/tester/components/BugHunterLeaderboardView.d.ts +23 -0
  210. package/dist/features/tester/components/BugHunterLeaderboardView.js +18 -0
  211. package/dist/features/tester/components/index.d.ts +2 -0
  212. package/dist/features/tester/components/index.js +1 -0
  213. package/dist/features/tester/repository/tester-checklist-item.repository.d.ts +12 -1
  214. package/dist/features/tester/repository/tester-checklist-item.repository.js +80 -0
  215. package/dist/features/tester/repository/tester-checklist-response.repository.d.ts +4 -0
  216. package/dist/features/tester/repository/tester-checklist-response.repository.js +4 -0
  217. package/dist/features/tester/schemas/firestore.d.ts +22 -1
  218. package/dist/features/tester/schemas/firestore.js +8 -0
  219. package/dist/features/tester/seed-data/categories-tester-seed-data.js +42 -7
  220. package/dist/features/tester/seed-data/events-tester-seed-data.js +23 -0
  221. package/dist/features/tester/seed-data/orders-tester-seed-data.js +69 -31
  222. package/dist/features/tester/seed-data/products-tester-seed-data.js +154 -9
  223. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +252 -15
  224. package/dist/features/wishlist/hooks/useGuestWishlist.js +11 -1
  225. package/dist/features/wishlist/hooks/useWishlistCount.js +16 -2
  226. package/dist/features/wishlist/utils/guest-wishlist.d.ts +5 -0
  227. package/dist/features/wishlist/utils/guest-wishlist.js +11 -0
  228. package/dist/index.d.ts +23 -22
  229. package/dist/index.js +31 -58
  230. package/dist/jobs.d.ts +1 -1
  231. package/dist/jobs.js +2 -2
  232. package/dist/next/routing/route-map.d.ts +9 -2
  233. package/dist/next/routing/route-map.js +4 -1
  234. package/dist/schemas/registry.d.ts +6 -26
  235. package/dist/seed/bids-seed-data.js +14 -2
  236. package/dist/seed/blog-posts-seed-data.js +2 -1
  237. package/dist/seed/categories-seed-data.js +11 -2
  238. package/dist/seed/events-seed-data.js +11 -11
  239. package/dist/seed/faq-seed-data.js +5 -1
  240. package/dist/seed/products-auctions-seed-data.js +2 -2
  241. package/dist/seed/products-preorders-seed-data.d.ts +2 -1
  242. package/dist/seed/products-prize-draws-seed-data.js +56 -10
  243. package/dist/seed/products-standard-seed-data.js +80 -3
  244. package/dist/seed/scammers-seed-data.js +75 -2
  245. package/dist/seed/site-settings-seed-data.js +5 -2
  246. package/dist/server-entry.d.ts +0 -1
  247. package/dist/server-entry.js +0 -1
  248. package/dist/server.d.ts +4 -20
  249. package/dist/server.js +10 -57
  250. package/dist/styles.css +1 -277
  251. package/dist/tailwind-utilities.css +1 -1
  252. package/dist/ui/components/BaseListingCard.js +1 -1
  253. package/dist/ui/components/ImageLightbox.d.ts +9 -0
  254. package/dist/ui/components/ImageLightbox.js +6 -3
  255. package/dist/ui/components/ListingToolbar.d.ts +18 -1
  256. package/dist/ui/components/ListingToolbar.js +5 -2
  257. package/dist/ui/components/StickyToolbar.js +13 -8
  258. package/package.json +1 -1
@@ -8,13 +8,11 @@ import { Button, ConfirmDeleteModal, Div, IconButton, Input, Li, Nav, Row, Span,
8
8
  import { BottomSheet } from "../../layout/BottomSheet";
9
9
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
10
10
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
11
+ import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
11
12
  const __O = {
12
13
  hidden: "overflow-hidden",
13
14
  yAuto: "overflow-y-auto",
14
15
  };
15
- function isNavItemActive(item, activeHref) {
16
- return activeHref === item.href || activeHref.startsWith(item.href + "/");
17
- }
18
16
  function NavLink({ item, isActive, onClick }) {
19
17
  const [showConfirm, setShowConfirm] = useState(false);
20
18
  const handleClick = (e) => {
@@ -35,13 +33,27 @@ function NavLink({ item, isActive, onClick }) {
35
33
  }, onClose: () => setShowConfirm(false) }))] }));
36
34
  }
37
35
  function DrawerContent({ groups, items, activeHref, onItemClick, }) {
38
- // Accordion — only one group open at a time.
36
+ // Accordion — only one group open at a time. Initial pick: the group
37
+ // containing the active path, else the first group with defaultOpen.
39
38
  const [openGroup, setOpenGroup] = useState(() => {
40
39
  if (!groups)
41
40
  return null;
42
- const match = groups.find((g) => g.defaultOpen === true || g.items.some((i) => activeHref === i.href || activeHref.startsWith(i.href + "/")));
43
- return match?.title ?? null;
41
+ const active = findActiveNavGroup(groups, activeHref);
42
+ if (active)
43
+ return active.title;
44
+ return groups.find((g) => g.defaultOpen === true)?.title ?? null;
44
45
  });
46
+ // Re-sync whenever the route changes — the sidebar stays mounted across
47
+ // client-side navigation, so the lazy initializer above only fires once.
48
+ useEffect(() => {
49
+ if (!groups)
50
+ return;
51
+ const active = findActiveNavGroup(groups, activeHref);
52
+ if (active)
53
+ setOpenGroup(active.title);
54
+ // eslint-disable-next-line react-hooks/exhaustive-deps
55
+ }, [activeHref]);
56
+ const activeItem = findActiveNavItem(groups ? groups.flatMap((g) => g.items) : items, activeHref);
45
57
  const { query, setQuery, isSearching, filteredGroups } = useSidebarSearch(groups ?? []);
46
58
  const toggle = useCallback((title) => {
47
59
  if (isSearching)
@@ -50,16 +62,16 @@ function DrawerContent({ groups, items, activeHref, onItemClick, }) {
50
62
  }, [isSearching]);
51
63
  if (!groups || groups.length === 0) {
52
64
  return (_jsx(Nav, { "aria-label": "User navigation", padding: "y-sm", children: _jsx(Ul, { paddingX: "x-sm", spacing: "2xs", children: items.map((item) => {
53
- const isActive = activeHref === item.href || activeHref.startsWith(item.href + "/");
65
+ const isActive = activeItem?.href === item.href;
54
66
  return (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isActive, onClick: onItemClick }) }, item.href));
55
67
  }) }) }));
56
68
  }
57
69
  return (_jsxs(Nav, { "aria-label": "User navigation", padding: "y-xs", children: [_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search navigation\u2026", "aria-label": "Search navigation" }) }), isSearching && filteredGroups.length === 0 && (_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsxs(Span, { size: "xs", color: "muted", children: ["No matches for \u201C", query, "\u201D."] }) })), filteredGroups.map((group) => {
58
70
  const isOpen = isSearching || openGroup === group.title;
59
- const hasActive = group.items.some((i) => activeHref === i.href || activeHref.startsWith(i.href + "/"));
71
+ const hasActive = !!findActiveNavItem(group.items, activeHref);
60
72
  return (_jsxs(Div, { className: "mb-0.5", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => toggle(group.title), paddingX: "md", paddingY: "sm", weight: "semibold", rounded: "none", className: `w-full text-[0.6875rem] uppercase tracking-widest transition-colors ${hasActive && !isOpen
61
73
  ? "text-primary-600 dark:text-primary-400"
62
- : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isNavItemActive(item, activeHref), onClick: onItemClick }) }, item.href))) }))] }, group.title));
74
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
63
75
  })] }));
64
76
  }
65
77
  function DrawerPanel({ title, onClose, children, }) {
@@ -9,6 +9,11 @@ export function useNotifications(limit = 10, options) {
9
9
  queryKey: ["notifications", "list", String(limit)],
10
10
  queryFn: () => apiClient.get(`${notificationsEndpoint}?limit=${limit}`),
11
11
  staleTime: 30000,
12
+ // Notifications originate server-side (order/bid/moderation events) with
13
+ // no client-driven local-first path to short-circuit, unlike cart/
14
+ // wishlist. Poll on the same ~30s cadence as useSyncManager so the bell
15
+ // badge picks up new notifications without requiring a window refocus.
16
+ refetchInterval: 30000,
12
17
  });
13
18
  const { mutate: markRead } = useMutation({
14
19
  mutationFn: (id) => apiClient.patch(readEndpoint(id), {}),
@@ -14,6 +14,7 @@ import { productRepository } from "../../products/repository/products.repository
14
14
  import { NotFoundError, ValidationError } from "../../../errors";
15
15
  import { finalizeStagedMediaUrl, finalizeStagedMediaField, finalizeStagedMediaArray, } from "../../media/finalize";
16
16
  import { getProviders } from "../../../contracts";
17
+ import { assertPrizeDrawNotLocked, assertPrizeDrawWonItemsImmutable, } from "../../../_internal/server/features/products/service";
17
18
  const ERR_UID_REQUIRED = "uid is required";
18
19
  export async function revokeSession(adminId, sessionId) {
19
20
  if (!sessionId?.trim()) {
@@ -153,6 +154,10 @@ export async function adminUpdateProduct(adminId, id, input) {
153
154
  if (!existing) {
154
155
  throw new NotFoundError("Product not found");
155
156
  }
157
+ if (input.status && input.status !== "published" && existing.status === "published") {
158
+ assertPrizeDrawNotLocked(existing, "unpublished or archived");
159
+ }
160
+ assertPrizeDrawWonItemsImmutable(existing, input.prizeDrawItems);
156
161
  const finalized = { ...input };
157
162
  if (typeof finalized.mainImage === "string" && finalized.mainImage) {
158
163
  finalized.mainImage = await finalizeStagedMediaUrl(finalized.mainImage);
@@ -194,6 +199,7 @@ export async function adminDeleteProduct(adminId, id) {
194
199
  if (!existing) {
195
200
  throw new NotFoundError("Product not found");
196
201
  }
202
+ assertPrizeDrawNotLocked(existing, "deleted");
197
203
  await productRepository.delete(id);
198
204
  serverLogger.info("adminDeleteProduct", {
199
205
  adminId,
@@ -113,9 +113,8 @@ export async function sendNotification(input) {
113
113
  offer_expired: "offers", offer_counter_accepted: "offers",
114
114
  refund_initiated: "orderUpdates",
115
115
  product_available: "system",
116
- prize_reveal_ready: "orderUpdates",
116
+ prize_won: "orderUpdates",
117
117
  prize_reveal_expired: "orderUpdates",
118
- prize_reveal_reminder: "orderUpdates",
119
118
  };
120
119
  const typeKey = typeToPrefsKey[type];
121
120
  // If the user has explicitly disabled this notification type, skip all external channels.
@@ -114,7 +114,7 @@ export function AdminBlogEditorView({ postId, onSaved, onDeleted, embedded, ...r
114
114
  slug: draft.slug || toSlug(draft.title),
115
115
  excerpt: draft.excerpt,
116
116
  content: draft.content,
117
- coverImage: draft.coverImage || undefined,
117
+ coverImage: draft.coverImage ? { type: "image", url: draft.coverImage } : undefined,
118
118
  youtubeId: draft.youtubeId || undefined,
119
119
  category: draft.category,
120
120
  tags: draft.tags,
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, } from "lucide-react";
2
+ import { Users, Package, Store, ShoppingBag, FileText, Settings, Shield, BarChart2, AlertTriangle, BookOpen, MessageSquare, CreditCard, } from "lucide-react";
3
3
  import { Alert, Div, Heading, Row, Section, Stack, Text, TextLink } from "../../../ui";
4
4
  import { ROUTES } from "../../../next/routing/route-map";
5
5
  const CLS_WARN_PANEL = "rounded-2xl border border-amber-200 bg-warning-surface dark:border-amber-800 p-[var(--appkit-space-6)]";
@@ -69,6 +69,20 @@ const GUIDE_CARDS = [
69
69
  href: String(ROUTES.ADMIN.GUIDE_TRUST),
70
70
  permission: "admin:moderation:read",
71
71
  },
72
+ {
73
+ Icon: MessageSquare,
74
+ title: "WhatsApp Integration",
75
+ description: "Connect Meta's WhatsApp Business Cloud API — Business Manager setup, phone registration, access tokens, and message templates.",
76
+ href: String(ROUTES.ADMIN.GUIDE_WHATSAPP),
77
+ permission: "admin:site:read",
78
+ },
79
+ {
80
+ Icon: CreditCard,
81
+ title: "Payments (Razorpay)",
82
+ description: "Enable and configure Razorpay — API keys, webhooks, and going live from test mode.",
83
+ href: String(ROUTES.ADMIN.GUIDE_PAYMENTS),
84
+ permission: "admin:site:read",
85
+ },
72
86
  ];
73
87
  export function AdminGuideHubView({ permissions = [], isFullAdmin = false }) {
74
88
  const hasPermission = (perm) => isFullAdmin || permissions.includes(perm);
@@ -0,0 +1,2 @@
1
+ import React from "react";
2
+ export declare function AdminPaymentsGuideView(): React.JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { CreditCard, KeyRound, Webhook, Settings, Power, Rocket, Code2 } from "lucide-react";
3
+ import { Code, Div, Heading, Li, Row, Section, Span, Stack, Text, Ul } from "../../../ui";
4
+ import { GC } from "../../_guide-cls";
5
+ export function AdminPaymentsGuideView() {
6
+ return (_jsxs(Stack, { className: "max-w-3xl mx-auto", padding: "b-2xl", gap: "xl", children: [_jsxs(Section, { children: [_jsxs(Row, { className: "mb-2", align: "center", gap: "3", children: [_jsx(Row, { className: "flex-shrink-0 w-10 h-10 [background:linear-gradient(135deg,var(--appkit-color-primary-700)_0%,var(--appkit-color-cobalt)_100%)]", align: "center", justify: "center", rounded: "xl", children: _jsx(CreditCard, { className: "w-5 h-5 text-white" }) }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)] tracking-widest", size: "sm", weight: "semibold", transform: "uppercase", children: "Admin Guide" })] }), _jsx(Heading, { level: 1, className: "text-[var(--appkit-color-text)] mb-2", mdSize: "3xl", size: "2xl", weight: "bold", children: "Payments (Razorpay)" }), _jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", children: "Razorpay is disabled by default on this platform \u2014 manual UPI/bank transfer and Cash on Delivery are the default payment methods. Enable Razorpay only once you have real (or test-mode) API keys." })] }), [
7
+ {
8
+ Icon: CreditCard, title: "Create a Razorpay Account",
9
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: ["Sign up at ", _jsx(Span, { weight: "bold", children: "razorpay.com" }), "."] }), _jsxs(Li, { children: ["For development, use ", _jsx(Span, { weight: "bold", children: "Test Mode" }), " keys \u2014 no real money moves and no KYC is required to start integrating."] })] })),
10
+ },
11
+ {
12
+ Icon: KeyRound, title: "Get Your API Keys",
13
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "Dashboard \u2192 Settings \u2192 API Keys \u2192 Generate Key." }), _jsxs(Li, { children: ["This gives you a ", _jsx(Span, { weight: "bold", children: "Key ID" }), " (public, safe to expose to the client) and a ", _jsx(Span, { weight: "bold", children: "Key Secret" }), " (private, server-only)."] })] })),
14
+ },
15
+ {
16
+ Icon: Webhook, title: "Configure a Webhook",
17
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsx(Li, { children: "Dashboard \u2192 Settings \u2192 Webhooks \u2192 Add New Webhook." }), _jsxs(Li, { children: ["Point it at ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "https://<your-domain>/api/payment/webhook" }), "."] }), _jsxs(Li, { children: ["Copy the ", _jsx(Span, { weight: "bold", children: "Webhook Secret" }), " shown after creation \u2014 it's used to verify that webhook calls actually came from Razorpay (HMAC signature check)."] })] })),
18
+ },
19
+ {
20
+ Icon: Settings, title: "Enter Credentials in the Admin UI",
21
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Integrations" }), ": ", _jsx(Span, { weight: "bold", children: "Client ID" }), " \u2192 your Razorpay Key ID, ", _jsx(Span, { weight: "bold", children: "Client Secret" }), " \u2192 your Razorpay Key Secret."] }), _jsx(Li, { children: "Webhook secret is configured the same way, under the same credentials block." })] })),
22
+ },
23
+ {
24
+ Icon: Power, title: "Enable the Payment Method",
25
+ content: (_jsxs(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: [_jsx(Span, { weight: "bold", children: "Site Settings \u2192 Shipping tab \u2192 Payment methods \u2192 \"Razorpay (online card/UPI) enabled\"" }), ". This is off by default \u2014 manual payment stays the platform default even after Razorpay is configured, so you can test Razorpay in isolation before flipping it on for real buyers."] })),
26
+ },
27
+ {
28
+ Icon: Rocket, title: "Go Live",
29
+ content: (_jsx(Text, { className: "text-[var(--appkit-color-text-muted)]", size: "sm", children: "Switch from Test Mode to Live Mode keys in the Razorpay dashboard once you're ready for real transactions (requires KYC/business verification on Razorpay's side). Update the same two credential fields in Site Settings with the live keys." })),
30
+ },
31
+ {
32
+ Icon: Code2, title: "How It's Used in This Codebase",
33
+ content: (_jsxs(Ul, { className: GC.listMuted, children: [_jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/create-order" }), " computes the exact amount server-side from the buyer's live cart (never trusts a client-supplied amount) and creates a Razorpay order."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/verify" }), " verifies the payment signature, decrements stock, and places the order(s)."] }), _jsxs(Li, { children: [_jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "POST /api/payment/webhook" }), " is a fast, bounded fallback signal handler \u2014 signature-verified, no heavy work."] }), _jsxs(Li, { children: ["None of these routes need code changes to go live \u2014 only the credentials + the ", _jsx(Code, { size: "xs", padding: "xs", rounded: "default", surface: "subtle", children: "razorpayEnabled" }), " toggle."] })] })),
34
+ },
35
+ ].map(({ Icon, title, content }) => (_jsxs(Section, { overflow: "hidden", className: "border border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface)]", rounded: "2xl", children: [_jsxs(Row, { className: "border-b border-[var(--appkit-color-border)] bg-[var(--appkit-color-surface-2,var(--appkit-color-border))]/20", padding: "inlineLg", align: "center", gap: "3", children: [_jsx(Icon, { className: "w-5 h-5 text-[var(--appkit-color-primary)]" }), _jsx(Heading, { level: 2, size: "base", weight: "semibold", children: title })] }), _jsx(Div, { paddingY: "y-md-lg", padding: "x-lg", children: content })] }, title)))] }));
36
+ }
@@ -3,8 +3,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { SIEVE_OP, Stack, sieveFilter } from "@mohasinac/appkit";
4
4
  import { sortBy } from "@mohasinac/appkit";
5
5
  import React from "react";
6
- import { Pencil } from "lucide-react";
7
- import { Badge, Button, FilterChipGroup, ListingLayout, Span, Text, TextLink } from "../../../ui";
6
+ import { Pencil, ListChecks } from "lucide-react";
7
+ import { Badge, Button, FilterChipGroup, ListingLayout, Row, Span, Text, TextLink } from "../../../ui";
8
8
  import { ADMIN_ENDPOINTS } from "../../../constants/api-endpoints";
9
9
  import { ADMIN_BULK_ACTIONS, ROW_ACTION_META } from "../../products/constants/action-defs";
10
10
  import { ADMIN_PRODUCT_STATUS_TABS } from "../constants/filter-tabs";
@@ -101,7 +101,7 @@ export function AdminPrizeDrawsView({ children, ...props }) {
101
101
  destructive: ROW_ACTION_META[id].destructive,
102
102
  onClick: () => selection.clearSelection(),
103
103
  })),
104
- renderRowActions: (row) => (_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) })),
104
+ renderRowActions: (row) => (_jsxs(Row, { gap: "xs", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_EDIT(row.id)), "aria-label": "Edit", children: _jsx(Pencil, { className: "w-4 h-4" }) }) }), _jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsx(TextLink, { href: String(ROUTES.ADMIN.PRIZE_DRAWS_ENTRIES(row.id)), "aria-label": "View entries", children: _jsx(ListChecks, { className: "w-4 h-4" }) }) })] })),
105
105
  renderFilterPanel: ({ pendingFilters, setPendingFilters }) => (_jsx(FilterChipGroup, { label: "Status", tabs: ADMIN_PRODUCT_STATUS_TABS, value: pendingFilters.status ?? "", onChange: (id) => setPendingFilters((p) => ({ ...p, status: id })) })),
106
106
  };
107
107
  return _jsx(DataListingView, { config: config });
@@ -7,25 +7,34 @@ import { Button, Div, IconButton, Input, Li, Nav, Row, Span, Stack, Ul } from ".
7
7
  import { BottomSheet } from "../../layout/BottomSheet";
8
8
  import { SidebarCollapseToggle } from "../../../_internal/client/features/layout/SidebarCollapseToggle";
9
9
  import { useSidebarSearch } from "../../../_internal/client/features/layout/useSidebarSearch";
10
+ import { findActiveNavGroup, findActiveNavItem } from "../../../_internal/client/features/layout/navActive";
10
11
  const __O = {
11
12
  hidden: "overflow-hidden",
12
13
  yAuto: "overflow-y-auto",
13
14
  };
14
- function isNavItemActive(item, activePath) {
15
- return activePath === item.href || activePath.startsWith(item.href + "/");
16
- }
17
15
  function NavLink({ item, isActive, onClick }) {
18
16
  return (_jsxs(Link, { href: item.href, onClick: onClick, className: `flex items-center justify-end gap-[var(--appkit-space-2-5)] rounded-lg px-[var(--appkit-space-3)] py-[var(--appkit-space-2)] text-[0.8125rem] font-medium leading-tight transition-colors ${isActive
19
17
  ? "bg-[var(--appkit-color-surface)] bg-[var(--appkit-color-surface-elevated)] text-[var(--appkit-color-text)]"
20
18
  : "text-[var(--appkit-color-text-muted)] hover:bg-zinc-50 hover:bg-[var(--appkit-color-surface-elevated)]/60 hover:text-zinc-800 hover:text-[var(--appkit-color-text-muted)]"}`, children: [item.icon && _jsx(Span, { size: "base", className: "shrink-0 opacity-60", children: item.icon }), _jsx(Span, { className: "truncate", children: item.label })] }));
21
19
  }
22
20
  function GroupsContent({ groups, activePath, onItemClick, }) {
23
- // Accordion — only one group open at a time. Initial pick: the first group
24
- // whose defaultOpen is true, or that contains the active path.
21
+ // Accordion — only one group open at a time. Initial pick: the group
22
+ // containing the active path, else the first group with defaultOpen.
25
23
  const [openGroup, setOpenGroup] = useState(() => {
26
- const match = groups.find((g) => g.defaultOpen ?? g.items.some((i) => activePath === i.href || activePath.startsWith(i.href + "/")));
27
- return match?.title ?? null;
24
+ const active = findActiveNavGroup(groups, activePath);
25
+ if (active)
26
+ return active.title;
27
+ return groups.find((g) => g.defaultOpen)?.title ?? null;
28
28
  });
29
+ // Re-sync whenever the route changes — the sidebar stays mounted across
30
+ // client-side navigation, so the lazy initializer above only fires once.
31
+ useEffect(() => {
32
+ const active = findActiveNavGroup(groups, activePath);
33
+ if (active)
34
+ setOpenGroup(active.title);
35
+ // eslint-disable-next-line react-hooks/exhaustive-deps
36
+ }, [activePath]);
37
+ const activeItem = findActiveNavItem(groups.flatMap((g) => g.items), activePath);
29
38
  const { query, setQuery, isSearching, filteredGroups } = useSidebarSearch(groups);
30
39
  const toggle = useCallback((title) => {
31
40
  if (isSearching)
@@ -34,10 +43,10 @@ function GroupsContent({ groups, activePath, onItemClick, }) {
34
43
  }, [isSearching]);
35
44
  return (_jsxs(Nav, { "aria-label": "Admin navigation", padding: "y-xs", children: [_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsx(Input, { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Search navigation\u2026", "aria-label": "Search navigation" }) }), isSearching && filteredGroups.length === 0 && (_jsx(Div, { padding: "x-md", paddingY: "y-sm", children: _jsxs(Span, { size: "xs", color: "muted", children: ["No matches for \u201C", query, "\u201D."] }) })), filteredGroups.map((group) => {
36
45
  const isOpen = isSearching || openGroup === group.title;
37
- const hasActive = group.items.some((i) => activePath === i.href || activePath.startsWith(i.href + "/"));
46
+ const hasActive = !!findActiveNavItem(group.items, activePath);
38
47
  return (_jsxs(Div, { className: "mb-0.5", children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => toggle(group.title), paddingX: "md", paddingY: "sm", weight: "semibold", rounded: "none", className: `w-full text-[0.6875rem] uppercase tracking-widest transition-colors ${hasActive && !isOpen
39
48
  ? "text-[var(--appkit-color-text-muted)]"
40
- : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: isNavItemActive(item, activePath), onClick: onItemClick }) }, item.href))) }))] }, group.title));
49
+ : "text-[var(--appkit-color-text-faint)] hover:text-[var(--appkit-color-text-muted)]"}`, children: _jsxs(Row, { align: "center", justify: "between", className: "w-full", children: [_jsx(Span, { children: group.title }), _jsx("svg", { className: `w-3 h-3 transition-transform duration-150 ${isOpen ? "rotate-180" : ""}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2.5, d: "M19 9l-7 7-7-7" }) })] }) }), isOpen && (_jsx(Ul, { paddingX: "x-sm", paddingY: "y-bottom-xs", spacing: "2xs", children: group.items.map((item) => (_jsx(Li, { children: _jsx(NavLink, { item: item, isActive: activeItem?.href === item.href, onClick: onItemClick }) }, item.href))) }))] }, group.title));
41
50
  })] }));
42
51
  }
43
52
  function DrawerPanel({ title, onClose, children, }) {
@@ -102,8 +102,15 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
102
102
  const [watermarkType, setWatermarkType] = React.useState("text");
103
103
  const [watermarkText, setWatermarkText] = React.useState("letitrip.in");
104
104
  const [watermarkImageUrl, setWatermarkImageUrl] = React.useState("");
105
- const [watermarkSize, setWatermarkSize] = React.useState(30);
106
- const [watermarkOpacity, setWatermarkOpacity] = React.useState(20);
105
+ const [watermarkSize, setWatermarkSize] = React.useState(10);
106
+ const [watermarkOpacity, setWatermarkOpacity] = React.useState(10);
107
+ // 4 corners + center are picked from the Select; "custom" is only ever set
108
+ // by the offset toggle below, never a 6th Select option (keeps the picker
109
+ // at 5 options — see PaginatedSelect rule for >5).
110
+ const [watermarkPosition, setWatermarkPosition] = React.useState("center");
111
+ const [watermarkUseCustomOffset, setWatermarkUseCustomOffset] = React.useState(false);
112
+ const [watermarkOffsetX, setWatermarkOffsetX] = React.useState(0);
113
+ const [watermarkOffsetY, setWatermarkOffsetY] = React.useState(0);
107
114
  const watermarkPreviewRef = React.useRef(null);
108
115
  React.useEffect(() => {
109
116
  const el = watermarkPreviewRef.current;
@@ -300,8 +307,13 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
300
307
  setWatermarkType(s.watermark?.type ?? "text");
301
308
  setWatermarkText(s.watermark?.text ?? "letitrip.in");
302
309
  setWatermarkImageUrl(s.watermark?.imageUrl ?? "");
303
- setWatermarkSize(s.watermark?.size ?? 30);
304
- setWatermarkOpacity(s.watermark?.opacity ?? 20);
310
+ setWatermarkSize(s.watermark?.size ?? 10);
311
+ setWatermarkOpacity(s.watermark?.opacity ?? 10);
312
+ const loadedPosition = s.watermark?.position ?? "center";
313
+ setWatermarkUseCustomOffset(loadedPosition === "custom");
314
+ setWatermarkPosition(loadedPosition === "custom" ? "center" : loadedPosition);
315
+ setWatermarkOffsetX(s.watermark?.offsetX ?? 0);
316
+ setWatermarkOffsetY(s.watermark?.offsetY ?? 0);
305
317
  setPlatformFeePercent(s.commissions?.platformFeePercent ?? 5);
306
318
  setGstPercent(s.commissions?.gstPercent ?? 18);
307
319
  setMinimumTransactionFee(s.commissions?.minimumTransactionFee ?? 0);
@@ -485,7 +497,16 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
485
497
  seo: { defaultTitle: seoTitle, defaultDescription: seoDescription, defaultOgImage: seoOgImage, noIndex: seoNoIndex, canonicalBaseUrl: canonicalUrl },
486
498
  contact: { email: supportEmail, phone: supportPhone, address: supportAddress, supportHours, whatsappNumber: whatsapp },
487
499
  socialLinks: { instagram, twitter, facebook, youtube, linkedin, pinterest },
488
- watermark: { type: watermarkType, text: watermarkText, imageUrl: watermarkImageUrl, size: watermarkSize, opacity: watermarkOpacity },
500
+ watermark: {
501
+ type: watermarkType,
502
+ text: watermarkText,
503
+ imageUrl: watermarkImageUrl,
504
+ size: watermarkSize,
505
+ opacity: watermarkOpacity,
506
+ position: watermarkUseCustomOffset ? "custom" : watermarkPosition,
507
+ offsetX: watermarkOffsetX,
508
+ offsetY: watermarkOffsetY,
509
+ },
489
510
  commissions: { platformFeePercent, gstPercent, minimumTransactionFee, gatewayFeePercent, payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee, whatsappNotifyFeeEnabled, whatsappNotifyFee, giftWrapFeeEnabled, giftWrapFee, shipmentProtectionFeeEnabled, shipmentProtectionFeePercent, shipmentProtectionFeeMin, codDepositPercent, sellerShippingFixed, platformShippingPercent, platformShippingFixedMin },
490
511
  laborRate: { hourlyRate: laborHourlyRate, maxHoursPerDay: laborMaxHoursPerDay },
491
512
  emi: {
@@ -621,7 +642,13 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
621
642
  } }) })] })] })] }) }), _jsx(TabsContent, { value: "themes", children: _jsx(Form, { onSubmit: (e) => {
622
643
  e.preventDefault();
623
644
  saveAllMutation.mutate();
624
- }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }) }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS), disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx(Input, { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 cursor-pointer", bare: true, disabled: !announcementEnabled }), " "] })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] })] }) }), _jsx(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "A seller must also opt in via their own Payout Settings for EMI to appear at checkout on their items. See the \"How EMI Works\" public page for buyer-facing copy." }), _jsx(Toggle, { checked: emiEnabled, onChange: setEmiEnabled, label: "Enable EMI platform-wide" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Minimum order value (\u20B9)", helperText: "A seller's cart subtotal must exceed this for EMI to appear as an option.", value: String(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Tenure options (months, comma-separated)", helperText: "e.g. 2,3,4,5,6", value: emiTenureOptionsText, onChange: (e) => setEmiTenureOptionsText(e.target.value) }), _jsx(Input, { label: "Token / down-payment (%)", helperText: "Collected upfront at checkout.", value: String(emiTokenPercent), onChange: (e) => setEmiTokenPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 }), _jsx(Input, { label: "Billing day (1\u201310)", helperText: "Day of month each installment is due.", value: String(emiBillingDay), onChange: (e) => setEmiBillingDay(parseFloat(e.target.value) || 1), type: "number", min: 1, max: 10 }), _jsx(Input, { label: "Surcharge (% of principal per month)", helperText: "The 'excess EMI fee' the buyer pays for spreading payment.", value: String(emiSurchargePercentPerMonth), onChange: (e) => setEmiSurchargePercentPerMonth(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Seller's share of surcharge (%)", helperText: "The rest of the surcharge goes to the platform.", value: String(emiSurchargeSellerSharePercent), onChange: (e) => setEmiSurchargeSellerSharePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 })] })] }) }), _jsx(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Required before enabling GST-inclusive checkout (P-8). Also set a GST rate + HSN code on each taxable product for the tax breakdown to appear." }), _jsx(Toggle, { checked: gstEnabled, onChange: setGstEnabled, label: "Enable GST on checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "GSTIN", helperText: "15-character GST Identification Number.", value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Registered address" }), _jsx(Textarea, { value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 })] })] }) }), _jsx(TabsContent, { value: "featureflags", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." })] }) }), _jsx(TabsContent, { value: "limits", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }) }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] })] }) }), _jsx(TabsContent, { value: "legal", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
645
+ }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsx(ThemeManagerView, { value: themeRegistry, onChange: setThemeRegistry, previewOrigin: "/" }) }) }), _jsx(TabsContent, { value: "announcement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Toggle, { label: "Show announcement bar", checked: announcementEnabled, onChange: setAnnouncementEnabled }), _jsx(Input, { label: "Announcement text", value: announcementText, onChange: (e) => setAnnouncementText(e.target.value), placeholder: "\uD83C\uDF89 Free shipping on orders \u20B9999+", disabled: !announcementEnabled }), _jsx(Input, { label: "Link URL (optional)", value: announcementLink, onChange: (e) => setAnnouncementLink(e.target.value), placeholder: String(ROUTES.PUBLIC.PRODUCTS), disabled: !announcementEnabled }), _jsxs(Stack, { gap: "none", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "mb-1", children: "Background color" }), _jsx(Input, { type: "color", value: announcementBg || "#1d4ed8", onChange: (e) => setAnnouncementBg(e.target.value), className: "h-10 w-32 cursor-pointer", bare: true, disabled: !announcementEnabled }), " "] })] }) }), _jsx(TabsContent, { value: "seo", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Default meta title", value: seoTitle, onChange: (e) => setSeoTitle(e.target.value), placeholder: "LetItRip \u2014 Buy, Sell & Auction Collectibles in India", maxLength: 60, helperText: "Max 60 chars. Use {page} token for dynamic insertion." }), _jsx(Input, { label: "Default meta description", value: seoDescription, onChange: (e) => setSeoDescription(e.target.value), placeholder: "India's largest collectibles marketplace\u2026", maxLength: 160, helperText: "Max 160 chars." }), _jsx(ImageUpload, { label: "Default OG image", currentImage: seoOgImage, onUpload: (file) => upload(file, "store"), onChange: setSeoOgImage }), _jsx(Input, { label: "Canonical base URL", value: canonicalUrl, onChange: (e) => setCanonicalUrl(e.target.value), placeholder: "https://letitrip.in" }), _jsx(Toggle, { label: "Robots noindex (disables search indexing \u2014 use carefully)", checked: seoNoIndex, onChange: setSeoNoIndex })] }) }), _jsx(TabsContent, { value: "contact", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Support email", value: supportEmail, onChange: (e) => setSupportEmail(e.target.value), type: "email", placeholder: "support@letitrip.in" }), _jsx(Input, { label: "Support phone", value: supportPhone, onChange: (e) => setSupportPhone(e.target.value), placeholder: "+91 XXXXX XXXXX" })] }), _jsx(Input, { label: "Physical address", value: supportAddress, onChange: (e) => setSupportAddress(e.target.value), placeholder: "Mumbai, Maharashtra, India" }), _jsx(Input, { label: "Support hours", value: supportHours, onChange: (e) => setSupportHours(e.target.value), placeholder: "Mon\u2013Fri, 10 AM \u2013 6 PM IST" }), _jsx(Text, { className: "pt-[0.5rem]", color: "muted", size: "sm", weight: "medium", children: "Social links" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Instagram URL", value: instagram, onChange: (e) => setInstagram(e.target.value), placeholder: "https://instagram.com/letitrip" }), _jsx(Input, { label: "Twitter / X URL", value: twitter, onChange: (e) => setTwitter(e.target.value), placeholder: "https://twitter.com/letitrip" }), _jsx(Input, { label: "Facebook URL", value: facebook, onChange: (e) => setFacebook(e.target.value), placeholder: "https://facebook.com/letitrip" }), _jsx(Input, { label: "YouTube URL", value: youtube, onChange: (e) => setYoutube(e.target.value), placeholder: "https://youtube.com/@letitrip" }), _jsx(Input, { label: "WhatsApp number", value: whatsapp, onChange: (e) => setWhatsapp(e.target.value), placeholder: "+91XXXXXXXXXX" }), _jsx(Input, { label: "LinkedIn URL", value: linkedin, onChange: (e) => setLinkedin(e.target.value), placeholder: "https://linkedin.com/company/letitrip" }), _jsx(Input, { label: "Pinterest URL", value: pinterest, onChange: (e) => setPinterest(e.target.value), placeholder: "https://pinterest.com/letitrip" })] })] }) }), _jsx(TabsContent, { value: "watermark", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Select, { label: "Watermark type", options: [{ label: "Text", value: "text" }, { label: "Image", value: "image" }], value: watermarkType, onValueChange: (v) => setWatermarkType(v) }), watermarkType === "text" ? (_jsx(Input, { label: "Watermark text", value: watermarkText, onChange: (e) => setWatermarkText(e.target.value), placeholder: "letitrip.in" })) : (_jsx(ImageUpload, { label: "Watermark image", currentImage: watermarkImageUrl, onUpload: (file) => upload(file, "store"), onChange: setWatermarkImageUrl })), _jsx(Slider, { label: `Size — ${watermarkSize}% of image width`, value: watermarkSize, onChange: setWatermarkSize, min: 5, max: 100, step: 5 }), _jsx(Slider, { label: `Opacity — ${watermarkOpacity}%`, value: watermarkOpacity, onChange: setWatermarkOpacity, min: 5, max: 100, step: 5 }), _jsx(Select, { label: "Position", options: [
646
+ { label: "Center", value: "center" },
647
+ { label: "Top left", value: "top-left" },
648
+ { label: "Top right", value: "top-right" },
649
+ { label: "Bottom left", value: "bottom-left" },
650
+ { label: "Bottom right", value: "bottom-right" },
651
+ ], value: watermarkPosition, onValueChange: (v) => setWatermarkPosition(v), disabled: watermarkUseCustomOffset }), _jsx(Toggle, { checked: watermarkUseCustomOffset, onChange: setWatermarkUseCustomOffset, label: "Use a custom X/Y offset instead" }), watermarkUseCustomOffset && (_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "X offset (%) \u2014 + right / \u2212 left of center", value: String(watermarkOffsetX), onChange: (e) => setWatermarkOffsetX(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 }), _jsx(Input, { label: "Y offset (%) \u2014 + down / \u2212 up from center", value: String(watermarkOffsetY), onChange: (e) => setWatermarkOffsetY(Math.max(-45, Math.min(45, parseInt(e.target.value) || 0))), type: "number", min: -45, max: 45 })] })), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Preview (text watermark only)" }), _jsx(Row, { surface: "default", justify: "end", align: "end", className: `relative h-32 ${__O.hidden}`, rounded: "default", children: _jsx(Span, { ref: watermarkPreviewRef, weight: "medium", color: "faint", padding: "1", className: "select-none", children: watermarkText }) })] })] }) }), _jsx(TabsContent, { value: "fees", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Platform fee \u2014 our cut (%)", helperText: "% charged on order value. Buyer pays this.", value: String(platformFeePercent), onChange: (e) => setPlatformFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "GST on platform fee (%)", helperText: "Applied to our fee only (not full order). Usually 18%.", value: String(gstPercent), onChange: (e) => setGstPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Razorpay gateway cost (%)", helperText: "Gateway's own fee \u2014 absorbed by platform, not passed through.", value: String(gatewayFeePercent), onChange: (e) => setGatewayFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 10, step: 0.01 }), _jsx(Input, { label: "Minimum transaction fee (\u20B9)", helperText: "Per-transaction floor. Total charge will never be below base + this.", value: String(minimumTransactionFee), onChange: (e) => setMinimumTransactionFee(parseFloat(e.target.value) || 0), type: "number", min: 0, step: 0.01 }), _jsx(Input, { label: "Seller payout hold (days)", value: String(payoutHoldDays), onChange: (e) => setPayoutHoldDays(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Minimum payout amount (\u20B9)", value: String(minPayoutAmount), onChange: (e) => setMinPayoutAmount(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Auction listing fee (\u20B9)", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", value: String(promotedSlotFee), onChange: (e) => setPromotedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "COD deposit (%)", helperText: "% of order collected upfront on COD orders; remainder paid on delivery.", value: String(codDepositPercent), onChange: (e) => setCodDepositPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 })] }), _jsx(Toggle, { checked: whatsappNotifyFeeEnabled, onChange: setWhatsappNotifyFeeEnabled, label: "Offer the WhatsApp order-updates addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "WhatsApp addon fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in. Requires WhatsApp credentials + at least one approved template \u2014 see the WhatsApp tab.", value: String(whatsappNotifyFee), onChange: (e) => setWhatsappNotifyFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !whatsappNotifyFeeEnabled }) }), _jsx(Toggle, { checked: giftWrapFeeEnabled, onChange: setGiftWrapFeeEnabled, label: "Offer the gift-wrap addon at checkout" }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Gift wrap fee (\u20B9)", helperText: "Flat fee charged to the buyer when they opt in.", value: String(giftWrapFee), onChange: (e) => setGiftWrapFee(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !giftWrapFeeEnabled }) }), _jsx(Toggle, { checked: shipmentProtectionFeeEnabled, onChange: setShipmentProtectionFeeEnabled, label: "Offer the shipment-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Shipment protection (%)", helperText: "% of order subtotal, charged when the buyer opts in.", value: String(shipmentProtectionFeePercent), onChange: (e) => setShipmentProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !shipmentProtectionFeeEnabled }), _jsx(Input, { label: "Shipment protection minimum (\u20B9)", helperText: "Floor for the shipment-protection fee.", value: String(shipmentProtectionFeeMin), onChange: (e) => setShipmentProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !shipmentProtectionFeeEnabled })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Used to compute each procurement shipment's labor cost (hours spent \u00D7 hourly rate) and its estimated processing time (hours spent \u00F7 max hours/day)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Hourly rate (\u20B9)", helperText: "Your effective hourly wage for sorting/categorizing/listing a shipment.", value: String(laborHourlyRate), onChange: (e) => setLaborHourlyRate(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Max hours per day", helperText: "Used only to project how many days a shipment's tracked hours will take.", value: String(laborMaxHoursPerDay), onChange: (e) => setLaborMaxHoursPerDay(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 24 })] })] }) }), _jsx(TabsContent, { value: "emi", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "A seller must also opt in via their own Payout Settings for EMI to appear at checkout on their items. See the \"How EMI Works\" public page for buyer-facing copy." }), _jsx(Toggle, { checked: emiEnabled, onChange: setEmiEnabled, label: "Enable EMI platform-wide" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Minimum order value (\u20B9)", helperText: "A seller's cart subtotal must exceed this for EMI to appear as an option.", value: String(emiMinOrderValue), onChange: (e) => setEmiMinOrderValue(Math.round((parseFloat(e.target.value) || 0) * 100) / 100), type: "number", min: 0 }), _jsx(Input, { label: "Tenure options (months, comma-separated)", helperText: "e.g. 2,3,4,5,6", value: emiTenureOptionsText, onChange: (e) => setEmiTenureOptionsText(e.target.value) }), _jsx(Input, { label: "Token / down-payment (%)", helperText: "Collected upfront at checkout.", value: String(emiTokenPercent), onChange: (e) => setEmiTokenPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 }), _jsx(Input, { label: "Billing day (1\u201310)", helperText: "Day of month each installment is due.", value: String(emiBillingDay), onChange: (e) => setEmiBillingDay(parseFloat(e.target.value) || 1), type: "number", min: 1, max: 10 }), _jsx(Input, { label: "Surcharge (% of principal per month)", helperText: "The 'excess EMI fee' the buyer pays for spreading payment.", value: String(emiSurchargePercentPerMonth), onChange: (e) => setEmiSurchargePercentPerMonth(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Seller's share of surcharge (%)", helperText: "The rest of the surcharge goes to the platform.", value: String(emiSurchargeSellerSharePercent), onChange: (e) => setEmiSurchargeSellerSharePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100 })] })] }) }), _jsx(TabsContent, { value: "gst", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Required before enabling GST-inclusive checkout (P-8). Also set a GST rate + HSN code on each taxable product for the tax breakdown to appear." }), _jsx(Toggle, { checked: gstEnabled, onChange: setGstEnabled, label: "Enable GST on checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "GSTIN", helperText: "15-character GST Identification Number.", value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Registered address" }), _jsx(Textarea, { value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 })] })] }) }), _jsx(TabsContent, { value: "featureflags", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "Site-wide platform feature toggles. Disabling a flag hides that feature's nav entries and pages \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _jsx(Grid, { cols: 2, gap: "md", children: FEATURE_FLAG_META.map((flag) => (_jsxs(Stack, { gap: "xs", children: [_jsx(Toggle, { label: `${flag.icon} ${flag.labelKey}`, checked: featureFlags[flag.key] ?? false, onChange: (v) => setFeatureFlags((prev) => ({ ...prev, [flag.key]: v })) }), _jsx(Text, { size: "xs", color: "muted", children: flag.descKey })] }, flag.key))) }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Listing types" }), _jsx(Text, { size: "xs", color: "muted", children: "Disabled types are hidden from listings and reject create/add-to-cart." }), _jsx(Grid, { cols: 2, gap: "sm", children: LISTING_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: listingTypeFlags[key] ?? true, onChange: (v) => setListingTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Category types" }), _jsx(Grid, { cols: 2, gap: "sm", children: CATEGORY_TYPE_KEYS.map((key) => (_jsx(Toggle, { label: key, checked: categoryTypeFlags[key] ?? true, onChange: (v) => setCategoryTypeFlags((prev) => ({ ...prev, [key]: v })) }, key))) })] })] }) }), _jsx(TabsContent, { value: "integrations", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Keys are masked in transit and stored encrypted. Click Reveal to view." }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Razorpay" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Razorpay Key ID", value: razorpayKeyId, onChange: setRazorpayKeyId, placeholder: "rzp_live_\u2026" }), _jsx(MaskedInput, { label: "Razorpay Secret", value: razorpaySecret, onChange: setRazorpaySecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "SMTP / Email" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "SMTP host", value: smtpHost, onChange: (e) => setSmtpHost(e.target.value), placeholder: "smtp.sendgrid.net" }), _jsx(Input, { label: "SMTP port", value: smtpPort, onChange: (e) => setSmtpPort(e.target.value), type: "number", placeholder: "587" }), _jsx(Input, { label: "SMTP user", value: smtpUser, onChange: (e) => setSmtpUser(e.target.value), placeholder: "apikey" }), _jsx(MaskedInput, { label: "SMTP password", value: smtpPassword, onChange: setSmtpPassword, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsx(Input, { label: "From address", value: smtpFrom, onChange: (e) => setSmtpFrom(e.target.value), placeholder: "noreply@letitrip.in", type: "email" })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Analytics & Tracking" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Google Analytics ID", value: gaMeasurementId, onChange: (e) => setGaMeasurementId(e.target.value), placeholder: "G-XXXXXXXXXX" }), _jsx(Input, { label: "Facebook Pixel ID", value: fbPixelId, onChange: (e) => setFbPixelId(e.target.value), placeholder: "XXXXXXXXXXXXXXXX" }), _jsx(Input, { label: "GTM Container ID", value: gtmContainerId, onChange: (e) => setGtmContainerId(e.target.value), placeholder: "GTM-XXXXXXX" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Meta (Instagram & Facebook Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Used by the Social Feed section to fetch Instagram and Facebook posts via Meta Graph API v19." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Page Access Token", value: metaPageAccessToken, onChange: setMetaPageAccessToken, placeholder: "EAAxxxxxxx\u2026" }), _jsx(Input, { label: "Page ID (or handle)", value: metaPageId, onChange: (e) => setMetaPageId(e.target.value), placeholder: "letitrip" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "TikTok for Developers (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials + long-lived access token from TikTok for Developers. Used to list your account's public videos." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client Key", value: tiktokClientKey, onChange: setTiktokClientKey, placeholder: "aw\u2026" }), _jsx(MaskedInput, { label: "Client Secret", value: tiktokClientSecret, onChange: setTiktokClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" }), _jsx(MaskedInput, { label: "Access Token (long-lived)", value: tiktokAccessToken, onChange: setTiktokAccessToken, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] }), _jsxs(Stack, { gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "DeviantArt OAuth2 (Social Feed)" }), _jsx(Text, { size: "xs", color: "muted", children: "Client credentials for DeviantArt gallery fetching (client-credentials OAuth2 flow \u2014 no user login required)." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(MaskedInput, { label: "Client ID", value: deviantartClientId, onChange: setDeviantartClientId, placeholder: "1234" }), _jsx(MaskedInput, { label: "Client Secret", value: deviantartClientSecret, onChange: setDeviantartClientSecret, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] })] })] }) }), _jsx(TabsContent, { value: "shipping", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "Payment methods" }), _jsx(Toggle, { label: "Razorpay (online card/UPI) enabled \u2014 disabled by default, manual payment is the default", checked: razorpayEnabled, onChange: setRazorpayEnabled }), _jsx(Toggle, { label: "Manual UPI/bank transfer enabled", checked: upiManualEnabled, onChange: setUpiManualEnabled }), _jsx(Toggle, { label: "Cash on delivery (COD) enabled", checked: codEnabled, onChange: setCodEnabled }), _jsx(Input, { label: "Free shipping threshold (\u20B9)", value: String(freeShippingThreshold), onChange: (e) => setFreeShippingThreshold(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Orders above this amount get free shipping." }), _jsx(Select, { label: "Default carrier", options: CARRIER_OPTIONS, value: defaultCarrier, onValueChange: setDefaultCarrier }), _jsx(Input, { label: "Max delivery radius (km, 0 = no limit)", value: String(maxDeliveryRadius), onChange: (e) => setMaxDeliveryRadius(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "High-value checkout OTP threshold (\u20B9)", helperText: "Carts at or above this subtotal require an extra OTP verification step before payment (0 = disabled).", value: String(otpCheckoutThreshold), onChange: (e) => setOtpCheckoutThreshold(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Text, { size: "sm", weight: "medium", color: "muted", className: "pt-[var(--appkit-space-2)]", children: "Seller shipping charge defaults" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Seller shipping fixed (\u20B9)", helperText: "Flat shipping charge collected on the seller's behalf.", value: String(sellerShippingFixed), onChange: (e) => setSellerShippingFixed(parseFloat(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Platform shipping (%)", helperText: "Platform's own shipping markup, as a % of order value.", value: String(platformShippingPercent), onChange: (e) => setPlatformShippingPercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "Platform shipping minimum (\u20B9)", helperText: "Floor for the platform shipping markup.", value: String(platformShippingFixedMin), onChange: (e) => setPlatformShippingFixedMin(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "auction", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Input, { label: "Minimum bid increment (\u20B9)", value: String(minBidIncrement), onChange: (e) => setMinBidIncrement(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Global default \u2014 individual auctions may override." }), _jsx(Input, { label: "Auto-extend window (minutes before end)", value: String(autoExtendWindow), onChange: (e) => setAutoExtendWindow(parseInt(e.target.value) || 0), type: "number", min: 0, helperText: "Extend auction end time if a bid arrives within this window." }), _jsx(Input, { label: "Settlement grace period (hours)", value: String(settlementGrace), onChange: (e) => setSettlementGrace(parseInt(e.target.value) || 0), type: "number", min: 1, helperText: "Time winner has to pay before the auction is re-listed." })] }) }), _jsx(TabsContent, { value: "limits", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Max products per store", value: String(maxProductsPerStore), onChange: (e) => setMaxProductsPerStore(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max images per product", value: String(maxImagesPerProduct), onChange: (e) => setMaxImagesPerProduct(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max video size (MB)", value: String(maxVideoSizeMb), onChange: (e) => setMaxVideoSizeMb(parseInt(e.target.value) || 0), type: "number", min: 1 }), _jsx(Input, { label: "Max custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Max custom sections per product", value: String(maxCustomSections), onChange: (e) => setMaxCustomSections(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Order cancellation window (hours)", value: String(orderCancelWindow), onChange: (e) => setOrderCancelWindow(parseInt(e.target.value) || 0), type: "number", min: 0 })] }) }) }), _jsx(TabsContent, { value: "whatsapp", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [_jsx(Text, { size: "xs", color: "muted", children: "Platform-level WhatsApp Business Cloud API credentials. Used for automated purchase announcements to admin numbers when orders are placed. Store owners configure their own credentials in Store \u2192 WhatsApp." }), _jsx(Input, { label: "Phone Number ID", value: waPhoneNumberId, onChange: (e) => setWaPhoneNumberId(e.target.value), placeholder: "987654321098765", helperText: "From Meta for Developers \u2192 App \u2192 WhatsApp \u2192 API Setup" }), _jsx(MaskedInput, { label: "Cloud API System User Token", value: waCloudApiToken, onChange: setWaCloudApiToken, placeholder: "EAAxxxxxxxx\u2026", helperText: "Long-lived system user access token with WhatsApp Business permissions" }), _jsx(Input, { label: "Admin Notify Numbers", value: waAdminNotifyNumbers, onChange: (e) => setWaAdminNotifyNumbers(e.target.value), placeholder: "919876543210,918765432109", helperText: "Comma-separated, digits-only, include country code. These receive a WhatsApp message when any order is placed." }), _jsx(Text, { size: "xs", color: "muted", children: "Approved Meta message templates for the buyer-facing WhatsApp order-updates addon (Fees tab). Meta rejects business-initiated free-text WhatsApp messages outside a 24h customer-service window \u2014 a real template name here is required for that notification type to actually deliver. Leave blank to skip WhatsApp for that type (in-app + email still fire)." }), _jsx(Input, { label: "Template language code", value: waTemplateLanguage, onChange: (e) => setWaTemplateLanguage(e.target.value), placeholder: "en", helperText: "BCP-47 code the templates below were approved in, e.g. en or en_US." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Order placed template name", value: waTemplateOrderPlaced, onChange: (e) => setWaTemplateOrderPlaced(e.target.value), placeholder: "order_placed_update" }), _jsx(Input, { label: "Order confirmed template name", value: waTemplateOrderConfirmed, onChange: (e) => setWaTemplateOrderConfirmed(e.target.value), placeholder: "order_confirmed_update" }), _jsx(Input, { label: "Order shipped template name", value: waTemplateOrderShipped, onChange: (e) => setWaTemplateOrderShipped(e.target.value), placeholder: "order_shipped_update" }), _jsx(Input, { label: "Order delivered template name", value: waTemplateOrderDelivered, onChange: (e) => setWaTemplateOrderDelivered(e.target.value), placeholder: "order_delivered_update" }), _jsx(Input, { label: "Order cancelled template name", value: waTemplateOrderCancelled, onChange: (e) => setWaTemplateOrderCancelled(e.target.value), placeholder: "order_cancelled_update" }), _jsx(Input, { label: "Refund initiated template name", value: waTemplateRefundInitiated, onChange: (e) => setWaTemplateRefundInitiated(e.target.value), placeholder: "refund_initiated_update" })] })] }) }), _jsx(TabsContent, { value: "notifications", children: _jsxs(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "lg", children: [_jsx(Text, { size: "xs", color: "muted", children: "In-app notifications are always on. Enable external channels below to let the platform fan out to email, WhatsApp, or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "xs", surface: "muted", rounded: "lg", border: "default", padding: "md", children: [_jsxs(Row, { justify: "between", gap: "sm", children: [_jsx(Text, { size: "sm", weight: "medium", color: "muted", children: "In-app (notification bell)" }), _jsx(Span, { color: "success", surface: "success-surface", size: "xs", weight: "semibold", rounded: "full", padding: "pill-xs", children: "Always on" })] }), _jsx(Text, { size: "xs", color: "muted", children: "Displayed in the notification bell and inbox. Cannot be disabled." })] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "Email notifications", checked: notifEmailEnabled, onChange: setNotifEmailEnabled }), notifEmailEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send email", options: PRIORITY_OPTIONS, value: notifEmailMinPriority, onValueChange: setNotifEmailMinPriority }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Resend API (for transactional email)" }), _jsx(MaskedInput, { label: "Resend API Key", value: resendApiKey, onChange: setResendApiKey, placeholder: "re_live_\u2026", helperText: "Get your key at resend.com \u2014 used for all transactional notifications." }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "From email", value: notifFromEmail, onChange: (e) => setNotifFromEmail(e.target.value), placeholder: "noreply@letitrip.in", type: "email" }), _jsx(Input, { label: "From name", value: notifFromName, onChange: (e) => setNotifFromName(e.target.value), placeholder: "LetItRip" })] }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifEmailTypes, onChange: (values) => setNotifEmailTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "WhatsApp notifications", checked: notifWhatsappEnabled, onChange: setNotifWhatsappEnabled }), notifWhatsappEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send WhatsApp", options: PRIORITY_OPTIONS, value: notifWhatsappMinPriority, onValueChange: setNotifWhatsappMinPriority }), _jsx(Toggle, { label: "Enable WhatsApp OTP (for login and verification)", checked: notifWhatsappOtpEnabled, onChange: setNotifWhatsappOtpEnabled }), _jsx(Text, { size: "xs", color: "muted", children: "WhatsApp credentials are configured in the WhatsApp tab (\u246C). OTP messages use the same phone number." }), _jsx(Text, { size: "xs", weight: "medium", color: "muted", className: "pt-[0.25rem]", children: "Notification types (leave empty to send all types)" }), _jsx(PaginatedSelect, { multiple: true, options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Toggle, { label: "SMS notifications", checked: notifSmsEnabled, onChange: setNotifSmsEnabled }), notifSmsEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Select, { label: "Minimum priority to send SMS", options: PRIORITY_OPTIONS, value: notifSmsMinPriority, onValueChange: setNotifSmsMinPriority }), _jsx(Text, { size: "xs", color: "muted", children: "SMS gateway credentials (e.g. Twilio, MSG91) can be configured in the Integrations tab once an SMS provider is connected." })] }))] })] }) }), _jsx(TabsContent, { value: "legal", children: _jsx(Form, { onSubmit: (e) => { e.preventDefault(); saveAllMutation.mutate(); }, className: "pt-[var(--appkit-space-4)]", spacing: "md", children: [
625
652
  ["Terms of Service", termsHtml, setTermsHtml],
626
653
  ["Privacy Policy", privacyHtml, setPrivacyHtml],
627
654
  ["Refund Policy", refundHtml, setRefundHtml],