@mohasinac/appkit 4.63.3 → 4.63.5

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 (179) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
  3. package/dist/_internal/server/features/checkout/actions.js +22 -5
  4. package/dist/_internal/server/features/orders/adapters.js +3 -1
  5. package/dist/_internal/server/features/promotions/adapters.d.ts +25 -0
  6. package/dist/_internal/server/features/promotions/adapters.js +43 -0
  7. package/dist/_internal/server/features/promotions/data.d.ts +26 -0
  8. package/dist/_internal/server/features/promotions/data.js +45 -0
  9. package/dist/_internal/server/features/promotions/index.d.ts +2 -1
  10. package/dist/_internal/server/features/promotions/index.js +2 -1
  11. package/dist/_internal/server/features/site-settings/adapters.js +20 -2
  12. package/dist/_internal/server/features/stores/adapters.d.ts +1 -1
  13. package/dist/_internal/server/features/stores/adapters.js +1 -0
  14. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
  15. package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
  16. package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
  17. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  18. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  19. package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
  20. package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
  21. package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
  22. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  23. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  24. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +45 -14
  25. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  26. package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
  27. package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
  28. package/dist/_internal/server/notifications/channel-health.js +2 -2
  29. package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
  30. package/dist/_internal/server/notifications/send-guard.js +4 -2
  31. package/dist/_internal/shared/actions/action-registry.js +28 -0
  32. package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
  33. package/dist/_internal/shared/checkout/order-math.js +1 -1
  34. package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
  35. package/dist/_internal/shared/features/checkout/config.js +2 -0
  36. package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
  37. package/dist/_internal/shared/features/messaging/config.js +4 -1
  38. package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
  39. package/dist/_internal/shared/features/site-settings/fees.js +2 -0
  40. package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
  41. package/dist/_internal/shared/fees/calculator.d.ts +8 -0
  42. package/dist/_internal/shared/fees/calculator.js +7 -0
  43. package/dist/cli/index.js +2 -0
  44. package/dist/client.d.ts +2 -2
  45. package/dist/client.js +1 -1
  46. package/dist/constants/api-endpoints.d.ts +15 -0
  47. package/dist/constants/api-endpoints.js +8 -0
  48. package/dist/constants/field-names.d.ts +4 -0
  49. package/dist/constants/field-names.js +4 -0
  50. package/dist/constants/notification-types.d.ts +1 -1
  51. package/dist/constants/notification-types.js +1 -1
  52. package/dist/core/integration-keys.d.ts +5 -0
  53. package/dist/core/integration-keys.js +10 -0
  54. package/dist/features/about/components/FeesView.js +6 -0
  55. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  56. package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
  57. package/dist/features/account/components/UserSidebar.js +1 -1
  58. package/dist/features/account/types/index.d.ts +2 -0
  59. package/dist/features/admin/actions/notification-actions.d.ts +17 -2
  60. package/dist/features/admin/actions/notification-actions.js +154 -9
  61. package/dist/features/admin/components/AdminBlogEditorView.js +10 -2
  62. package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
  63. package/dist/features/admin/components/AdminOrdersView.js +2 -0
  64. package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
  65. package/dist/features/admin/components/AdminStoreEditorView.js +1 -0
  66. package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
  67. package/dist/features/admin/constants/site-settings-tabs.js +12 -6
  68. package/dist/features/admin/repository/site-settings.repository.js +1 -1
  69. package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
  70. package/dist/features/admin/schemas/admin-user-form.js +44 -3
  71. package/dist/features/admin/schemas/firestore.d.ts +73 -0
  72. package/dist/features/admin/schemas/firestore.js +8 -3
  73. package/dist/features/auth/checkout-value-otp.d.ts +1 -1
  74. package/dist/features/auth/checkout-value-otp.js +1 -1
  75. package/dist/features/auth/permissions/constants.d.ts +2 -2
  76. package/dist/features/auth/permissions/constants.js +2 -1
  77. package/dist/features/blog/schemas/blog-form.d.ts +3 -0
  78. package/dist/features/blog/schemas/blog-form.js +14 -0
  79. package/dist/features/blog/schemas/firestore.d.ts +17 -0
  80. package/dist/features/blog/schemas/firestore.js +1 -0
  81. package/dist/features/blog/schemas/index.d.ts +6 -0
  82. package/dist/features/blog/schemas/index.js +3 -0
  83. package/dist/features/blog/types/index.d.ts +2 -0
  84. package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
  85. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  86. package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
  87. package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
  88. package/dist/features/cart/repository/cart.repository.js +2 -1
  89. package/dist/features/cart/schemas/firestore.d.ts +2 -1
  90. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
  91. package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
  92. package/dist/features/events/components/AdminEventEditorView.js +64 -9
  93. package/dist/features/events/components/EventOfferCard.js +3 -18
  94. package/dist/features/events/components/EventRaffleWinnerView.d.ts +6 -0
  95. package/dist/features/events/components/EventRaffleWinnerView.js +11 -1
  96. package/dist/features/events/schemas/event-form.d.ts +10 -0
  97. package/dist/features/events/schemas/event-form.js +19 -0
  98. package/dist/features/events/schemas/firestore.d.ts +68 -0
  99. package/dist/features/events/schemas/firestore.js +25 -0
  100. package/dist/features/events/schemas/index.d.ts +7 -0
  101. package/dist/features/events/schemas/index.js +5 -0
  102. package/dist/features/events/types/index.d.ts +4 -0
  103. package/dist/features/homepage/lib/section-renderer.js +1 -1
  104. package/dist/features/orders/actions/refund-actions.js +1 -0
  105. package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
  106. package/dist/features/orders/components/OrderAddonBadges.js +4 -3
  107. package/dist/features/orders/repository/orders.repository.d.ts +8 -0
  108. package/dist/features/orders/repository/orders.repository.js +2 -0
  109. package/dist/features/orders/schemas/firestore.d.ts +4 -0
  110. package/dist/features/orders/types/index.d.ts +5 -2
  111. package/dist/features/promotions/components/AttachedCouponsSection.d.ts +30 -0
  112. package/dist/features/promotions/components/AttachedCouponsSection.js +99 -0
  113. package/dist/features/promotions/components/CouponCodeCopyRow.d.ts +19 -0
  114. package/dist/features/promotions/components/CouponCodeCopyRow.js +39 -0
  115. package/dist/features/promotions/components/index.d.ts +4 -0
  116. package/dist/features/promotions/components/index.js +2 -0
  117. package/dist/features/promotions/constants/attached-coupons.d.ts +55 -0
  118. package/dist/features/promotions/constants/attached-coupons.js +24 -0
  119. package/dist/features/promotions/repository/coupons.repository.d.ts +20 -0
  120. package/dist/features/promotions/repository/coupons.repository.js +39 -0
  121. package/dist/features/promotions/schemas/attached-coupons.d.ts +39 -0
  122. package/dist/features/promotions/schemas/attached-coupons.js +66 -0
  123. package/dist/features/search/action-index/settings-entries.js +9 -0
  124. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
  125. package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
  126. package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
  127. package/dist/features/store-extensions/schemas/firestore.js +10 -0
  128. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +1 -0
  129. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  130. package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
  131. package/dist/features/stores/repository/store.repository.d.ts +23 -1
  132. package/dist/features/stores/repository/store.repository.js +29 -1
  133. package/dist/features/stores/schemas/firestore.d.ts +16 -6
  134. package/dist/features/stores/schemas/firestore.js +3 -6
  135. package/dist/features/stores/schemas/index.d.ts +86 -0
  136. package/dist/features/stores/schemas/index.js +14 -0
  137. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
  138. package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
  139. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
  140. package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
  141. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
  142. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
  143. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
  144. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
  145. package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
  146. package/dist/features/whatsapp-bot/components/index.js +2 -0
  147. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
  148. package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
  149. package/dist/features/whatsapp-bot/index.d.ts +3 -0
  150. package/dist/features/whatsapp-bot/index.js +3 -0
  151. package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
  152. package/dist/features/whatsapp-bot/manifest.js +13 -0
  153. package/dist/features/whatsapp-bot/messages/en.json +7 -0
  154. package/dist/features/whatsapp-bot/server.d.ts +7 -0
  155. package/dist/features/whatsapp-bot/server.js +7 -0
  156. package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
  157. package/dist/features/whatsapp-bot/types/index.js +1 -0
  158. package/dist/index.d.ts +23 -6
  159. package/dist/index.js +38 -12
  160. package/dist/next/routing/route-map.d.ts +3 -0
  161. package/dist/next/routing/route-map.js +1 -0
  162. package/dist/providers/db-firebase/base.repository.d.ts +1 -1
  163. package/dist/providers/db-firebase/base.repository.js +1 -1
  164. package/dist/repositories/index.d.ts +1 -1
  165. package/dist/repositories/index.js +1 -1
  166. package/dist/schemas/registry.d.ts +121 -0
  167. package/dist/schemas/registry.js +2 -1
  168. package/dist/security/pii-encrypt.js +1 -1
  169. package/dist/server-entry.d.ts +1 -0
  170. package/dist/server-entry.js +2 -0
  171. package/dist/server.d.ts +11 -2
  172. package/dist/server.js +26 -2
  173. package/dist/styles.css +1 -1
  174. package/dist/tailwind-utilities.css +1 -1
  175. package/dist/ui/components/NavigationLoader.d.ts +14 -1
  176. package/dist/ui/components/NavigationLoader.js +79 -30
  177. package/package.json +1 -1
  178. package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
  179. package/dist/features/orders/utils/order-announcement-message.js +0 -16
@@ -201,7 +201,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
201
201
  // ⑦ Fees — all read/written under commissions key
202
202
  const [laborHourlyRate, setLaborHourlyRate] = React.useState(200);
203
203
  const [laborMaxHoursPerDay, setLaborMaxHoursPerDay] = React.useState(6);
204
- // ⑮ EMI — site-wide installment settings
204
+ // ⑯ EMI — site-wide installment settings
205
205
  const [emiEnabled, setEmiEnabled] = React.useState(false);
206
206
  const [emiMinOrderValue, setEmiMinOrderValue] = React.useState(10000);
207
207
  const [emiTenureOptionsText, setEmiTenureOptionsText] = React.useState("2,3,4,5,6");
@@ -209,7 +209,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
209
209
  const [emiBillingDay, setEmiBillingDay] = React.useState(5);
210
210
  const [emiSurchargePercentPerMonth, setEmiSurchargePercentPerMonth] = React.useState(2);
211
211
  const [emiSurchargeSellerSharePercent, setEmiSurchargeSellerSharePercent] = React.useState(50);
212
- // ⑯ GST — Indian tax compliance
212
+ // ⑰ GST — Indian tax compliance
213
213
  const [gstEnabled, setGstEnabled] = React.useState(false);
214
214
  const [gstin, setGstin] = React.useState("");
215
215
  const [gstLegalName, setGstLegalName] = React.useState("");
@@ -243,6 +243,8 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
243
243
  const [preOrderListingFee, setPreOrderListingFee] = React.useState(0);
244
244
  const [featuredSlotFee, setFeaturedSlotFee] = React.useState(999);
245
245
  const [promotedSlotFee, setPromotedSlotFee] = React.useState(499);
246
+ const [whatsappNotifyFeeEnabled, setWhatsappNotifyFeeEnabled] = React.useState(false);
247
+ const [whatsappNotifyFee, setWhatsappNotifyFee] = React.useState(10);
246
248
  const [giftWrapFeeEnabled, setGiftWrapFeeEnabled] = React.useState(false);
247
249
  const [giftWrapFee, setGiftWrapFee] = React.useState(49);
248
250
  const [shipmentProtectionFeeEnabled, setShipmentProtectionFeeEnabled] = React.useState(false);
@@ -316,6 +318,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
316
318
  const [categoryTypeFlags, setCategoryTypeFlags] = React.useState({});
317
319
  // Per-channel notification-type allowlists — empty array means "all types".
318
320
  const [notifEmailTypes, setNotifEmailTypes] = React.useState([]);
321
+ const [notifWhatsappTypes, setNotifWhatsappTypes] = React.useState([]);
319
322
  // ⑩ Auction
320
323
  const [bidIncrementTiers, setBidIncrementTiers] = React.useState(DEFAULT_AUCTION_BID_INCREMENT_TIERS);
321
324
  const [autoExtendWindow, setAutoExtendWindow] = React.useState(5);
@@ -346,6 +349,17 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
346
349
  */
347
350
  const [cancellationHtml, setCancellationHtml] = React.useState("");
348
351
  const [grievanceHtml, setGrievanceHtml] = React.useState("");
352
+ // ⑬ WhatsApp Business Cloud API (platform-level)
353
+ const [waPhoneNumberId, setWaPhoneNumberId] = React.useState("");
354
+ const [waCloudApiToken, setWaCloudApiToken] = React.useState("");
355
+ const [waAdminNotifyNumbers, setWaAdminNotifyNumbers] = React.useState("");
356
+ const [waTemplateLanguage, setWaTemplateLanguage] = React.useState("en");
357
+ const [waTemplateOrderPlaced, setWaTemplateOrderPlaced] = React.useState("");
358
+ const [waTemplateOrderConfirmed, setWaTemplateOrderConfirmed] = React.useState("");
359
+ const [waTemplateOrderShipped, setWaTemplateOrderShipped] = React.useState("");
360
+ const [waTemplateOrderDelivered, setWaTemplateOrderDelivered] = React.useState("");
361
+ const [waTemplateOrderCancelled, setWaTemplateOrderCancelled] = React.useState("");
362
+ const [waTemplateRefundInitiated, setWaTemplateRefundInitiated] = React.useState("");
349
363
  // ⓪ About
350
364
  const [aboutTitle, setAboutTitle] = React.useState("");
351
365
  const [aboutSubtitle, setAboutSubtitle] = React.useState("");
@@ -364,9 +378,12 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
364
378
  const [aboutCtaTitle, setAboutCtaTitle] = React.useState("");
365
379
  const [aboutCtaSell, setAboutCtaSell] = React.useState("");
366
380
  const [aboutCtaShop, setAboutCtaShop] = React.useState("");
367
- // ⑬ Notification channels
381
+ // ⑭ Notification channels
368
382
  const [notifEmailEnabled, setNotifEmailEnabled] = React.useState(false);
369
383
  const [notifEmailMinPriority, setNotifEmailMinPriority] = React.useState("normal");
384
+ const [notifWhatsappEnabled, setNotifWhatsappEnabled] = React.useState(false);
385
+ const [notifWhatsappMinPriority, setNotifWhatsappMinPriority] = React.useState("high");
386
+ const [notifWhatsappOtpEnabled, setNotifWhatsappOtpEnabled] = React.useState(false);
370
387
  const [notifSmsEnabled, setNotifSmsEnabled] = React.useState(false);
371
388
  const [notifSmsMinPriority, setNotifSmsMinPriority] = React.useState("high");
372
389
  // Resend API key + sender identity (used by the email channel)
@@ -384,7 +401,9 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
384
401
  * off customer email must never also blind the operator.
385
402
  */
386
403
  const [msgEmailEnabled, setMsgEmailEnabled] = React.useState(false);
404
+ const [msgWhatsappEnabled, setMsgWhatsappEnabled] = React.useState(false);
387
405
  const [msgCeilingEmail, setMsgCeilingEmail] = React.useState(80);
406
+ const [msgCeilingWhatsapp, setMsgCeilingWhatsapp] = React.useState(200);
388
407
  // Snapshot of the masked placeholder strings the server returned for every
389
408
  // credentials.* field, captured once per load. A combined single-save has
390
409
  // to send the FULL credentials object on every save (not just the group the
@@ -472,6 +491,8 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
472
491
  setPreOrderListingFee(s.commissions?.preOrderListingFee ?? 0);
473
492
  setFeaturedSlotFee(s.commissions?.featuredSlotFee ?? 999);
474
493
  setPromotedSlotFee(s.commissions?.promotedSlotFee ?? 499);
494
+ setWhatsappNotifyFeeEnabled(s.commissions?.whatsappNotifyFeeEnabled ?? false);
495
+ setWhatsappNotifyFee(s.commissions?.whatsappNotifyFee ?? 10);
475
496
  setGiftWrapFeeEnabled(s.commissions?.giftWrapFeeEnabled ?? false);
476
497
  setGiftWrapFee(s.commissions?.giftWrapFee ?? 49);
477
498
  setShipmentProtectionFeeEnabled(s.commissions?.shipmentProtectionFeeEnabled ?? false);
@@ -537,6 +558,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
537
558
  setListingTypeFlags(s.listings?.listingTypes ?? {});
538
559
  setCategoryTypeFlags(s.listings?.categoryTypes ?? {});
539
560
  setNotifEmailTypes(s.notificationChannels?.email?.types ?? []);
561
+ setNotifWhatsappTypes(s.notificationChannels?.whatsapp?.types ?? []);
540
562
  setBidIncrementTiers(s.auctionConfig?.bidIncrementTiers ?? DEFAULT_AUCTION_BID_INCREMENT_TIERS);
541
563
  setAutoExtendWindow(s.auctionConfig?.autoExtendWindowMinutes ?? 5);
542
564
  setSettlementGrace(s.auctionConfig?.settlementGracePeriodHours ?? 24);
@@ -569,8 +591,21 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
569
591
  setAboutCtaTitle(s.aboutContent?.ctaTitle ?? "");
570
592
  setAboutCtaSell(s.aboutContent?.ctaSell ?? "");
571
593
  setAboutCtaShop(s.aboutContent?.ctaShop ?? "");
594
+ setWaPhoneNumberId(s.credentialsMasked?.whatsappPhoneNumberId ?? "");
595
+ setWaCloudApiToken(s.credentialsMasked?.whatsappCloudApiToken ?? "");
596
+ setWaAdminNotifyNumbers(s.credentialsMasked?.whatsappAdminNotifyNumbers ?? "");
597
+ setWaTemplateLanguage(s.credentialsMasked?.whatsappTemplateLanguage ?? "en");
598
+ setWaTemplateOrderPlaced(s.credentialsMasked?.whatsappTemplateOrderPlaced ?? "");
599
+ setWaTemplateOrderConfirmed(s.credentialsMasked?.whatsappTemplateOrderConfirmed ?? "");
600
+ setWaTemplateOrderShipped(s.credentialsMasked?.whatsappTemplateOrderShipped ?? "");
601
+ setWaTemplateOrderDelivered(s.credentialsMasked?.whatsappTemplateOrderDelivered ?? "");
602
+ setWaTemplateOrderCancelled(s.credentialsMasked?.whatsappTemplateOrderCancelled ?? "");
603
+ setWaTemplateRefundInitiated(s.credentialsMasked?.whatsappTemplateRefundInitiated ?? "");
572
604
  setNotifEmailEnabled(s.notificationChannels?.email?.enabled ?? false);
573
605
  setNotifEmailMinPriority(s.notificationChannels?.email?.minPriority ?? "normal");
606
+ setNotifWhatsappEnabled(s.notificationChannels?.whatsapp?.enabled ?? false);
607
+ setNotifWhatsappMinPriority(s.notificationChannels?.whatsapp?.minPriority ?? "high");
608
+ setNotifWhatsappOtpEnabled(s.notificationChannels?.whatsapp?.otpEnabled ?? false);
574
609
  setNotifSmsEnabled(s.notificationChannels?.sms?.enabled ?? false);
575
610
  setNotifSmsMinPriority(s.notificationChannels?.sms?.minPriority ?? "high");
576
611
  setResendApiKey(s.credentialsMasked?.resendApiKey ?? "");
@@ -582,7 +617,9 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
582
617
  // settings document written before this feature existed reads as quiet
583
618
  // rather than as "on and unmetered".
584
619
  setMsgEmailEnabled(s.messaging?.emailEnabled ?? false);
620
+ setMsgWhatsappEnabled(s.messaging?.whatsappEnabled ?? false);
585
621
  setMsgCeilingEmail(s.messaging?.dailyCeiling?.email ?? 80);
622
+ setMsgCeilingWhatsapp(s.messaging?.dailyCeiling?.whatsapp ?? 200);
586
623
  originalMaskedRef.current = {
587
624
  razorpayKeyId: s.credentialsMasked?.razorpayKeyId ?? "",
588
625
  razorpayKeySecret: s.credentialsMasked?.razorpayKeySecret ?? "",
@@ -602,6 +639,16 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
602
639
  tiktokAccessToken: s.credentialsMasked?.tiktokAccessToken ?? "",
603
640
  deviantartClientId: s.credentialsMasked?.deviantartClientId ?? "",
604
641
  deviantartClientSecret: s.credentialsMasked?.deviantartClientSecret ?? "",
642
+ whatsappPhoneNumberId: s.credentialsMasked?.whatsappPhoneNumberId ?? "",
643
+ whatsappCloudApiToken: s.credentialsMasked?.whatsappCloudApiToken ?? "",
644
+ whatsappAdminNotifyNumbers: s.credentialsMasked?.whatsappAdminNotifyNumbers ?? "",
645
+ whatsappTemplateLanguage: s.credentialsMasked?.whatsappTemplateLanguage ?? "en",
646
+ whatsappTemplateOrderPlaced: s.credentialsMasked?.whatsappTemplateOrderPlaced ?? "",
647
+ whatsappTemplateOrderConfirmed: s.credentialsMasked?.whatsappTemplateOrderConfirmed ?? "",
648
+ whatsappTemplateOrderShipped: s.credentialsMasked?.whatsappTemplateOrderShipped ?? "",
649
+ whatsappTemplateOrderDelivered: s.credentialsMasked?.whatsappTemplateOrderDelivered ?? "",
650
+ whatsappTemplateOrderCancelled: s.credentialsMasked?.whatsappTemplateOrderCancelled ?? "",
651
+ whatsappTemplateRefundInitiated: s.credentialsMasked?.whatsappTemplateRefundInitiated ?? "",
605
652
  resendApiKey: s.credentialsMasked?.resendApiKey ?? "",
606
653
  };
607
654
  }, [data]);
@@ -663,7 +710,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
663
710
  offsetX: watermarkOffsetX,
664
711
  offsetY: watermarkOffsetY,
665
712
  },
666
- commissions: { platformFeePercent, gstPercent, minimumTransactionFee, platformFeeMax, gatewayFeePercent, payoutHoldDays, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee, giftWrapFeeEnabled, giftWrapFee, shipmentProtectionFeeEnabled, shipmentProtectionFeePercent, shipmentProtectionFeeMin, saleProtectionFeeEnabled, saleProtectionFeePercent, saleProtectionFeeMin, codHandlingFeePercent, codHandlingFeeCap, codDepositPercent, sellerShippingFixed, platformShippingPercent, platformShippingFixedMin },
713
+ commissions: { platformFeePercent, gstPercent, minimumTransactionFee, platformFeeMax, gatewayFeePercent, payoutHoldDays, whatsappNotifyFee, whatsappNotifyFeeEnabled, minPayoutAmount, auctionListingFee, preOrderListingFee, featuredSlotFee, promotedSlotFee, giftWrapFeeEnabled, giftWrapFee, shipmentProtectionFeeEnabled, shipmentProtectionFeePercent, shipmentProtectionFeeMin, saleProtectionFeeEnabled, saleProtectionFeePercent, saleProtectionFeeMin, codHandlingFeePercent, codHandlingFeeCap, codDepositPercent, sellerShippingFixed, platformShippingPercent, platformShippingFixedMin },
667
714
  laborRate: { hourlyRate: laborHourlyRate, maxHoursPerDay: laborMaxHoursPerDay },
668
715
  emi: {
669
716
  enabled: emiEnabled,
@@ -706,6 +753,16 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
706
753
  tiktokAccessToken: maskedOrReal("tiktokAccessToken", tiktokAccessToken),
707
754
  deviantartClientId: maskedOrReal("deviantartClientId", deviantartClientId),
708
755
  deviantartClientSecret: maskedOrReal("deviantartClientSecret", deviantartClientSecret),
756
+ whatsappPhoneNumberId: maskedOrReal("whatsappPhoneNumberId", waPhoneNumberId),
757
+ whatsappCloudApiToken: maskedOrReal("whatsappCloudApiToken", waCloudApiToken),
758
+ whatsappAdminNotifyNumbers: maskedOrReal("whatsappAdminNotifyNumbers", waAdminNotifyNumbers),
759
+ whatsappTemplateLanguage: maskedOrReal("whatsappTemplateLanguage", waTemplateLanguage),
760
+ whatsappTemplateOrderPlaced: maskedOrReal("whatsappTemplateOrderPlaced", waTemplateOrderPlaced),
761
+ whatsappTemplateOrderConfirmed: maskedOrReal("whatsappTemplateOrderConfirmed", waTemplateOrderConfirmed),
762
+ whatsappTemplateOrderShipped: maskedOrReal("whatsappTemplateOrderShipped", waTemplateOrderShipped),
763
+ whatsappTemplateOrderDelivered: maskedOrReal("whatsappTemplateOrderDelivered", waTemplateOrderDelivered),
764
+ whatsappTemplateOrderCancelled: maskedOrReal("whatsappTemplateOrderCancelled", waTemplateOrderCancelled),
765
+ whatsappTemplateRefundInitiated: maskedOrReal("whatsappTemplateRefundInitiated", waTemplateRefundInitiated),
709
766
  resendApiKey: maskedOrReal("resendApiKey", resendApiKey),
710
767
  },
711
768
  emailSettings: {
@@ -722,8 +779,10 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
722
779
  },
723
780
  messaging: {
724
781
  emailEnabled: msgEmailEnabled,
782
+ whatsappEnabled: msgWhatsappEnabled,
725
783
  dailyCeiling: {
726
784
  email: msgCeilingEmail,
785
+ whatsapp: msgCeilingWhatsapp,
727
786
  // No SMS sender exists; the ceiling is carried so one cannot ship
728
787
  // uncapped if somebody adds a provider later.
729
788
  sms: 50,
@@ -743,6 +802,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
743
802
  notificationChannels: {
744
803
  inApp: { enabled: true, readOnly: true },
745
804
  email: { enabled: notifEmailEnabled, minPriority: notifEmailMinPriority, types: notifEmailTypes },
805
+ whatsapp: { enabled: notifWhatsappEnabled, minPriority: notifWhatsappMinPriority, otpEnabled: notifWhatsappOtpEnabled, types: notifWhatsappTypes },
746
806
  sms: { enabled: notifSmsEnabled, minPriority: notifSmsMinPriority },
747
807
  },
748
808
  };
@@ -851,7 +911,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
851
911
  { label: "Top right", value: "top-right" },
852
912
  { label: "Bottom left", value: "bottom-left" },
853
913
  { label: "Bottom right", value: "bottom-right" },
854
- ], 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(SettingsTabForm, { id: "fees", label: "Fees", 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: "PhonePe 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: "Maximum platform fee (\u20B9)", helperText: "Ceiling on the buyer's platform commission, charged once per checkout on every payment method. GST is calculated on the capped amount.", value: String(platformFeeMax), onChange: (e) => setPlatformFeeMax(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)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it.", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it.", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it. /admin/deals sets isPromoted for free today.", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it. /admin/deals sets isPromoted for free today.", 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: 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(Toggle, { checked: saleProtectionFeeEnabled, onChange: setSaleProtectionFeeEnabled, label: "Offer the sale-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Sale protection (%)", helperText: "% of order subtotal, charged when the buyer opts in. Covers the item against not-as-described and counterfeit claims.", value: String(saleProtectionFeePercent), onChange: (e) => setSaleProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !saleProtectionFeeEnabled }), _jsx(Input, { label: "Sale protection minimum (\u20B9)", helperText: "Floor for the sale-protection fee.", value: String(saleProtectionFeeMin), onChange: (e) => setSaleProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !saleProtectionFeeEnabled })] }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "COD handling fee (%)", helperText: "% of order subtotal, charged on Cash-on-Delivery orders only.", value: String(codHandlingFeePercent), onChange: (e) => setCodHandlingFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "COD handling fee cap (\u20B9)", helperText: "Ceiling for the COD handling fee, however large the order.", value: String(codHandlingFeeCap), onChange: (e) => setCodHandlingFeeCap(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(SettingsTabForm, { id: "procurement", label: "Procurement", 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(SettingsTabForm, { id: "emi", label: "Emi", 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(SettingsTabForm, { id: "gst", label: "Gst", 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.", error: gstProblems.gstin, value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", error: gstProblems.legalName, value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { label: "Registered address", value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 }), gstProblems.address ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: gstProblems.address })) : null] })] }) }), _jsx(TabsContent, { value: "listings", children: _jsxs(SettingsTabForm, { id: "listings", label: "Listings", children: [_jsx(Text, { size: "xs", color: "muted", children: "Which listing and category types the marketplace offers. A disabled type is hidden from every browse surface and rejected on create and add-to-cart \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _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, {
914
+ ], 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(SettingsTabForm, { id: "fees", label: "Fees", 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: "PhonePe 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: "Maximum platform fee (\u20B9)", helperText: "Ceiling on the buyer's platform commission, charged once per checkout on every payment method. GST is calculated on the capped amount.", value: String(platformFeeMax), onChange: (e) => setPlatformFeeMax(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)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it.", value: String(auctionListingFee), onChange: (e) => setAuctionListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Pre-order listing fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it.", value: String(preOrderListingFee), onChange: (e) => setPreOrderListingFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Featured slot fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it. /admin/deals sets isPromoted for free today.", value: String(featuredSlotFee), onChange: (e) => setFeaturedSlotFee(parseInt(e.target.value) || 0), type: "number", min: 0 }), _jsx(Input, { label: "Promoted slot fee (\u20B9)", helperText: "Not charged yet \u2014 no billing path exists for seller listing fees. Setting a value records the intended price; nothing collects it. /admin/deals sets isPromoted for free today.", 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(Toggle, { checked: saleProtectionFeeEnabled, onChange: setSaleProtectionFeeEnabled, label: "Offer the sale-protection addon at checkout" }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Sale protection (%)", helperText: "% of order subtotal, charged when the buyer opts in. Covers the item against not-as-described and counterfeit claims.", value: String(saleProtectionFeePercent), onChange: (e) => setSaleProtectionFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1, disabled: !saleProtectionFeeEnabled }), _jsx(Input, { label: "Sale protection minimum (\u20B9)", helperText: "Floor for the sale-protection fee.", value: String(saleProtectionFeeMin), onChange: (e) => setSaleProtectionFeeMin(parseFloat(e.target.value) || 0), type: "number", min: 0, disabled: !saleProtectionFeeEnabled })] }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "COD handling fee (%)", helperText: "% of order subtotal, charged on Cash-on-Delivery orders only.", value: String(codHandlingFeePercent), onChange: (e) => setCodHandlingFeePercent(parseFloat(e.target.value) || 0), type: "number", min: 0, max: 100, step: 0.1 }), _jsx(Input, { label: "COD handling fee cap (\u20B9)", helperText: "Ceiling for the COD handling fee, however large the order.", value: String(codHandlingFeeCap), onChange: (e) => setCodHandlingFeeCap(parseFloat(e.target.value) || 0), type: "number", min: 0 })] })] }) }), _jsx(TabsContent, { value: "procurement", children: _jsxs(SettingsTabForm, { id: "procurement", label: "Procurement", 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(SettingsTabForm, { id: "emi", label: "Emi", 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(SettingsTabForm, { id: "gst", label: "Gst", 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.", error: gstProblems.gstin, value: gstin, onChange: (e) => setGstin(e.target.value), placeholder: "29AAAAA0000A1Z5", maxLength: 15 }), _jsx(Input, { label: "Legal name", helperText: "Registered business name for invoices.", error: gstProblems.legalName, value: gstLegalName, onChange: (e) => setGstLegalName(e.target.value), placeholder: "LetItRip Collectibles Pvt Ltd" })] }), _jsxs(Stack, { gap: "xs", children: [_jsx(Textarea, { label: "Registered address", value: gstAddress, onChange: (e) => setGstAddress(e.target.value), placeholder: "Registered business address for GST invoices\u2026", rows: 3 }), gstProblems.address ? (_jsx(Text, { size: "sm", variant: "error", role: "alert", children: gstProblems.address })) : null] })] }) }), _jsx(TabsContent, { value: "listings", children: _jsxs(SettingsTabForm, { id: "listings", label: "Listings", children: [_jsx(Text, { size: "xs", color: "muted", children: "Which listing and category types the marketplace offers. A disabled type is hidden from every browse surface and rejected on create and add-to-cart \u2014 existing data is untouched, it just becomes unreachable until re-enabled." }), _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, {
855
915
  // The plugin's own display label, not the raw key — the
856
916
  // literal version rendered "pre-order" and
857
917
  // "digital-code" at an admin.
@@ -870,7 +930,7 @@ export function AdminSiteSettingsView({ labels = {}, ...rest }) {
870
930
  ...bidIncrementTiers.slice(0, -1),
871
931
  { upTo: 100, increment: 10 },
872
932
  bidIncrementTiers[bidIncrementTiers.length - 1] ?? { upTo: null, increment: 1000 },
873
- ]), children: "+ Add tier" })] }), _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(SettingsTabForm, { id: "limits", label: "Limits", 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 custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(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: "notifications", children: _jsxs(SettingsTabForm, { id: "notifications", label: "Notifications", 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 or SMS. Users can further restrict which types they receive." }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Outbound messaging budget" }), _jsxs(Text, { size: "xs", color: "muted", children: ["The master switches. These suppress ", _jsx(Span, { weight: "semibold", children: "user-facing" }), " messages only \u2014 the daily status digest always reaches staff, so turning customer email off never leaves the site unwatched. Transactional messages (checkout verification codes, order receipts) also keep sending, because suppressing those breaks checkout rather than quietening it."] }), _jsx(Toggle, { label: "Send user-facing email", checked: msgEmailEnabled, onChange: setMsgEmailEnabled }), _jsx(Grid, { cols: 2, gap: "md", children: _jsx(Input, { label: "Daily email ceiling", type: "number", value: String(msgCeilingEmail), onChange: (e) => setMsgCeilingEmail(Number(e.target.value) || 0), helperText: "Resend's free tier is 100/day. 80 leaves headroom for password-reset mail and in-flight sends." }) }), _jsx(Text, { size: "xs", color: "muted", children: "Some notification types never email regardless of these settings \u2014 losing an auction, being outbid, marketing, review activity and ticket updates are shown in the notification bell only. That is deliberate: those were the highest-volume sends and the ones most likely to exhaust the day's allowance. A support reply can still be emailed one-off from the ticket screen." })] }), _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(Div, { id: "setting-email-notifications", 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, { ariaLabel: "Notification types", 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(Div, { id: "setting-daily-digest", children: _jsx(Toggle, { label: "Daily status digest", checked: digestEnabled, onChange: setDigestEnabled }) }), digestEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Text, { size: "xs", color: "muted", children: "Sent every morning at 10:00 IST with the previous 24 hours of order activity. Its arrival doubles as the platform health check \u2014 if it stops landing, something needs a look." }), _jsx(Textarea, { label: "Recipients (To)", value: digestRecipients, onChange: (e) => setDigestRecipients(e.target.value), placeholder: "mohasin@letitrip.in", rows: 3, helperText: "One address per line. Each address is billed as a separate send against the daily allowance \u2014 keep this list short." })] }))] }), _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(SettingsTabForm, { id: "legal", label: "Legal", children: [
933
+ ]), children: "+ Add tier" })] }), _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(SettingsTabForm, { id: "limits", label: "Limits", 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 custom fields per product", value: String(maxCustomFields), onChange: (e) => setMaxCustomFields(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(SettingsTabForm, { id: "whatsapp", label: "Whatsapp", 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(SettingsTabForm, { id: "notifications", label: "Notifications", 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: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Text, { size: "sm", weight: "semibold", children: "Outbound messaging budget" }), _jsxs(Text, { size: "xs", color: "muted", children: ["The master switches. These suppress ", _jsx(Span, { weight: "semibold", children: "user-facing" }), " messages only \u2014 the daily status digest always reaches staff, so turning customer email off never leaves the site unwatched. Transactional messages (checkout verification codes, order receipts) also keep sending, because suppressing those breaks checkout rather than quietening it."] }), _jsx(Toggle, { label: "Send user-facing email", checked: msgEmailEnabled, onChange: setMsgEmailEnabled }), _jsx(Toggle, { label: "Send user-facing WhatsApp", checked: msgWhatsappEnabled, onChange: setMsgWhatsappEnabled }), _jsxs(Grid, { cols: 2, gap: "md", children: [_jsx(Input, { label: "Daily email ceiling", type: "number", value: String(msgCeilingEmail), onChange: (e) => setMsgCeilingEmail(Number(e.target.value) || 0), helperText: "Resend's free tier is 100/day. 80 leaves headroom for password-reset mail and in-flight sends." }), _jsx(Input, { label: "Daily WhatsApp ceiling", type: "number", value: String(msgCeilingWhatsapp), onChange: (e) => setMsgCeilingWhatsapp(Number(e.target.value) || 0), helperText: "A sanity bound. Meta bills per conversation, not per message." })] }), _jsx(Text, { size: "xs", color: "muted", children: "Some notification types never email regardless of these settings \u2014 losing an auction, being outbid, marketing, review activity and ticket updates are shown in the notification bell only. That is deliberate: those were the highest-volume sends and the ones most likely to exhaust the day's allowance. A support reply can still be emailed one-off from the ticket screen." })] }), _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(Div, { id: "setting-email-notifications", 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, { ariaLabel: "Notification types", 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(Div, { id: "setting-daily-digest", children: _jsx(Toggle, { label: "Daily status digest", checked: digestEnabled, onChange: setDigestEnabled }) }), digestEnabled && (_jsxs(Stack, { gap: "md", className: NOTIF_CHANNEL_INDENT, children: [_jsx(Text, { size: "xs", color: "muted", children: "Sent every morning at 10:00 IST with the previous 24 hours of order activity. Its arrival doubles as the platform health check \u2014 if it stops landing, something needs a look." }), _jsx(Textarea, { label: "Recipients (To)", value: digestRecipients, onChange: (e) => setDigestRecipients(e.target.value), placeholder: "mohasin@letitrip.in", rows: 3, helperText: "One address per line. Each address is billed as a separate send against the daily allowance \u2014 keep this list short." })] }))] }), _jsxs(Stack, { gap: "md", rounded: "lg", border: "default", padding: "md", children: [_jsx(Div, { id: "setting-whatsapp-notifications", 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(PaginatedSelect, { multiple: true, ariaLabel: "Notification types to send over WhatsApp", options: NOTIF_TYPE_OPTIONS, value: notifWhatsappTypes, onChange: (values) => setNotifWhatsappTypes(values), placeholder: "All notification types" }), _jsx(Text, { size: "xs", color: "muted", children: "Leave empty to send all 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(SettingsTabForm, { id: "legal", label: "Legal", children: [
874
934
  ["Terms of Service", termsHtml, setTermsHtml],
875
935
  ["Privacy Policy", privacyHtml, setPrivacyHtml],
876
936
  ["Refund Policy", refundHtml, setRefundHtml],
@@ -78,6 +78,7 @@ const CAPABILITY_META = {
78
78
  lower_commission_rate: { group: CAP_GROUP.PLATFORM, label: "Lower Commission Rate" },
79
79
  early_access_features: { group: CAP_GROUP.PLATFORM, label: "Early Access Features" },
80
80
  advanced_analytics: { group: CAP_GROUP.PLATFORM, label: "Advanced Analytics" },
81
+ whatsapp_catalog_sync: { group: CAP_GROUP.PLATFORM, label: "WhatsApp Catalog Sync" },
81
82
  };
82
83
  const CAPABILITY_GROUP_ORDER = [
83
84
  CAP_GROUP.LISTINGS,
@@ -76,33 +76,38 @@ export declare const SITE_SETTINGS_TABS: readonly [{
76
76
  readonly label: "⑫ Legal";
77
77
  readonly description: "Terms, privacy policy, refund policy and the GST registration details.";
78
78
  readonly keywords: ["terms", "privacy", "policy", "gstin", "refund policy", "compliance"];
79
+ }, {
80
+ readonly id: "whatsapp";
81
+ readonly label: "⑬ WhatsApp";
82
+ readonly description: "WhatsApp Business credentials and which messages go out over it.";
83
+ readonly keywords: ["whatsapp", "meta", "business api", "order updates"];
79
84
  }, {
80
85
  readonly id: "notifications";
81
- readonly label: "⑬ Notifications";
82
- readonly description: "Which channels are on — email and SMS — and the daily digest.";
86
+ readonly label: "⑭ Notifications";
87
+ readonly description: "Which channels are on — email, WhatsApp, SMS — and the daily digest.";
83
88
  readonly keywords: ["email", "sms", "alerts", "digest", "notify"];
84
89
  }, {
85
90
  readonly id: "procurement";
86
- readonly label: "⑭ Procurement";
91
+ readonly label: "⑮ Procurement";
87
92
  readonly description: "Labour rate and the defaults used to price inbound shipments.";
88
93
  readonly keywords: ["labour rate", "labor", "import", "landed cost", "supplier"];
89
94
  }, {
90
95
  readonly id: "emi";
91
- readonly label: "⑮ EMI";
96
+ readonly label: "⑯ EMI";
92
97
  readonly description: "Instalment plans: eligibility threshold, tenures and the surcharge split.";
93
98
  readonly keywords: ["instalments", "installments", "emi", "pay monthly", "finance"];
94
99
  }, {
95
100
  readonly id: "gst";
96
- readonly label: "⑯ GST";
101
+ readonly label: "⑰ GST";
97
102
  readonly description: "GST rates, HSN defaults and the registered business details.";
98
103
  readonly keywords: ["tax", "gst", "hsn", "vat", "invoice tax"];
99
104
  }, {
100
105
  readonly id: "listings";
101
- readonly label: "⑰ Listings";
106
+ readonly label: "⑱ Listings";
102
107
  readonly description: "Which listing types sellers may create, and their defaults.";
103
108
  readonly keywords: ["listing types", "auctions on", "enable", "categories"];
104
109
  }];
105
- export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "emi" | "legal" | "appearance" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "procurement")[];
110
+ export declare const SITE_SETTINGS_TAB_IDS: ("about" | "shipping" | "notifications" | "contact" | "auction" | "seo" | "listings" | "whatsapp" | "emi" | "legal" | "appearance" | "gst" | "watermark" | "integrations" | "limits" | "announcement" | "branding" | "themes" | "fees" | "procurement")[];
106
111
  /**
107
112
  * The union `?tab=` is validated against.
108
113
  *
@@ -108,33 +108,39 @@ export const SITE_SETTINGS_TABS = [
108
108
  description: "Terms, privacy policy, refund policy and the GST registration details.",
109
109
  keywords: ["terms", "privacy", "policy", "gstin", "refund policy", "compliance"],
110
110
  },
111
+ {
112
+ id: "whatsapp",
113
+ label: "⑬ WhatsApp",
114
+ description: "WhatsApp Business credentials and which messages go out over it.",
115
+ keywords: ["whatsapp", "meta", "business api", "order updates"],
116
+ },
111
117
  {
112
118
  id: "notifications",
113
- label: "⑬ Notifications",
114
- description: "Which channels are on — email and SMS — and the daily digest.",
119
+ label: "⑭ Notifications",
120
+ description: "Which channels are on — email, WhatsApp, SMS — and the daily digest.",
115
121
  keywords: ["email", "sms", "alerts", "digest", "notify"],
116
122
  },
117
123
  {
118
124
  id: "procurement",
119
- label: "⑭ Procurement",
125
+ label: "⑮ Procurement",
120
126
  description: "Labour rate and the defaults used to price inbound shipments.",
121
127
  keywords: ["labour rate", "labor", "import", "landed cost", "supplier"],
122
128
  },
123
129
  {
124
130
  id: "emi",
125
- label: "⑮ EMI",
131
+ label: "⑯ EMI",
126
132
  description: "Instalment plans: eligibility threshold, tenures and the surcharge split.",
127
133
  keywords: ["instalments", "installments", "emi", "pay monthly", "finance"],
128
134
  },
129
135
  {
130
136
  id: "gst",
131
- label: "⑯ GST",
137
+ label: "⑰ GST",
132
138
  description: "GST rates, HSN defaults and the registered business details.",
133
139
  keywords: ["tax", "gst", "hsn", "vat", "invoice tax"],
134
140
  },
135
141
  {
136
142
  id: "listings",
137
- label: "⑰ Listings",
143
+ label: "⑱ Listings",
138
144
  description: "Which listing types sellers may create, and their defaults.",
139
145
  keywords: ["listing types", "auctions on", "enable", "categories"],
140
146
  },
@@ -137,7 +137,7 @@ export class SiteSettingsRepository extends BaseRepository {
137
137
  encrypted[key] = encryptSecret(value.trim());
138
138
  }
139
139
  else if (value !== undefined && typeof value !== "string") {
140
- // Non-string credential fields (e.g. a nested Record<string,string>)
140
+ // Non-string credential fields (e.g. whatsappTemplates: Record<string,string>)
141
141
  // are stored as-is — they are not secret values to encrypt.
142
142
  encrypted[key] = value;
143
143
  }
@@ -65,6 +65,67 @@ export declare function toPublicProfilePayload(v: {
65
65
  linkedin?: string;
66
66
  }): Record<string, JsonValue> | undefined;
67
67
  export type AdminUserUpdateValues = z.infer<typeof adminUserUpdateSchema>;
68
+ /**
69
+ * A store's WhatsApp Business connection.
70
+ *
71
+ * `accessToken` is optional because the form shows a masked placeholder and
72
+ * only sends the field when the seller types a NEW value — so "absent" means
73
+ * "unchanged", not "clear it". The `.min(20)` therefore applies only to a real
74
+ * replacement, and is there because a truncated token fails silently at sync
75
+ * time rather than on save.
76
+ */
77
+ export declare const whatsappSettingsSchema: z.ZodObject<{
78
+ catalogSyncEnabled: z.ZodBoolean;
79
+ phoneNumber: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
80
+ wabaId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
81
+ catalogId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
82
+ accessToken: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
83
+ }, "strip", z.ZodTypeAny, {
84
+ catalogSyncEnabled: boolean;
85
+ phoneNumber?: string | undefined;
86
+ accessToken?: string | undefined;
87
+ wabaId?: string | undefined;
88
+ catalogId?: string | undefined;
89
+ }, {
90
+ catalogSyncEnabled: boolean;
91
+ phoneNumber?: string | undefined;
92
+ accessToken?: string | undefined;
93
+ wabaId?: string | undefined;
94
+ catalogId?: string | undefined;
95
+ }>;
96
+ export type WhatsappSettingsValues = z.infer<typeof whatsappSettingsSchema>;
97
+ /**
98
+ * The CONNECTION form's fields — credentials only.
99
+ *
100
+ * `catalogSyncEnabled` is deliberately absent. Its toggle lives in a different
101
+ * card and owns its own write (`syncToggleMutation`), because the credentials
102
+ * form used to send it unconditionally from two cards away: a seller flipped
103
+ * the switch, saw it move, and nothing persisted it. A field in this schema is
104
+ * a field this form's Save button owns.
105
+ *
106
+ * 🛑 `.omit()` rather than a second literal, so the two cannot drift — it
107
+ * carries the SAME field instances into the new shape, which is what keeps the
108
+ * `annotate()` WeakMap entries alive. (A wrapper such as `.superRefine()` is
109
+ * transparent for the same reason; a field-level re-wrap is not.)
110
+ */
111
+ export declare const whatsappConnectionSchema: z.ZodObject<Omit<{
112
+ catalogSyncEnabled: z.ZodBoolean;
113
+ phoneNumber: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
114
+ wabaId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
115
+ catalogId: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
116
+ accessToken: z.ZodUnion<[z.ZodOptional<z.ZodString>, z.ZodLiteral<"">]>;
117
+ }, "catalogSyncEnabled">, "strip", z.ZodTypeAny, {
118
+ phoneNumber?: string | undefined;
119
+ accessToken?: string | undefined;
120
+ wabaId?: string | undefined;
121
+ catalogId?: string | undefined;
122
+ }, {
123
+ phoneNumber?: string | undefined;
124
+ accessToken?: string | undefined;
125
+ wabaId?: string | undefined;
126
+ catalogId?: string | undefined;
127
+ }>;
128
+ export type WhatsappConnectionValues = z.infer<typeof whatsappConnectionSchema>;
68
129
  /**
69
130
  * A buyer's lottery entry.
70
131
  *
@@ -8,20 +8,24 @@
8
8
  * false, and saving any unrelated field silently stripped a real
9
9
  * tester's access.
10
10
  *
11
+ * `SellerWhatsAppSettingsView` writes `accessToken` — a live Meta
12
+ * credential — plus the WABA and catalog ids the sync runs against. A
13
+ * malformed id there fails at sync time, hours later, with no obvious
14
+ * cause.
11
15
  *
12
16
  * `LotteryPullForm` is a buyer-facing entry: a transaction id, a payment
13
17
  * time and an item number, all parsed with a bare `parseInt` and a `new
14
18
  * Date(...)` that yields `Invalid Date` on anything unparseable.
15
19
  *
16
20
  * EXPORTS:
17
- * adminUserUpdateSchema, lotteryPullSchema,
21
+ * adminUserUpdateSchema, whatsappSettingsSchema, lotteryPullSchema,
18
22
  * and their inferred value types
19
23
  *
20
- * @tag domain:admin,lottery
24
+ * @tag domain:admin,whatsapp,lottery
21
25
  * @tag layer:schema
22
26
  * @tag pattern:none
23
27
  * @tag access:isomorphic
24
- * @tag consumers:AdminUserEditorView,LotteryPullForm
28
+ * @tag consumers:AdminUserEditorView,SellerWhatsAppSettingsView,LotteryPullForm
25
29
  * @tag sideEffects:none
26
30
  */
27
31
  import { z } from "zod";
@@ -127,6 +131,43 @@ export function toPublicProfilePayload(v) {
127
131
  publicProfile.socialLinks = socialLinks;
128
132
  return Object.keys(publicProfile).length > 0 ? publicProfile : undefined;
129
133
  }
134
+ /**
135
+ * A store's WhatsApp Business connection.
136
+ *
137
+ * `accessToken` is optional because the form shows a masked placeholder and
138
+ * only sends the field when the seller types a NEW value — so "absent" means
139
+ * "unchanged", not "clear it". The `.min(20)` therefore applies only to a real
140
+ * replacement, and is there because a truncated token fails silently at sync
141
+ * time rather than on save.
142
+ */
143
+ export const whatsappSettingsSchema = z.object({
144
+ catalogSyncEnabled: annotate(z.boolean(), {
145
+ section: "sync", sectionLabel: "Catalog sync", sectionRequired: true, quick: true, order: 1, row: "quarter",
146
+ }),
147
+ phoneNumber: annotate(z.string().trim().regex(PHONE_RE, "Enter the 10-digit WhatsApp Business number.").optional().or(z.literal("")), { section: "connection", sectionLabel: "Connection", order: 1, row: "pair" }),
148
+ // Meta ids are numeric strings; a pasted URL or a stray space is the usual
149
+ // mistake and produces a sync that never runs.
150
+ wabaId: annotate(z.string().trim().regex(/^\d{5,32}$/, "A WABA ID is a numeric id, not a URL.").optional().or(z.literal("")), { section: "connection", order: 2, row: "pair" }),
151
+ catalogId: annotate(z.string().trim().regex(/^\d{5,32}$/, "A catalog ID is a numeric id, not a URL.").optional().or(z.literal("")), { section: "connection", order: 3, row: "pair" }),
152
+ accessToken: annotate(z.string().trim().min(20, "That access token looks truncated.").optional().or(z.literal("")), { section: "connection", order: 4, row: "full", help: "Leave blank to keep the current token." }),
153
+ });
154
+ /**
155
+ * The CONNECTION form's fields — credentials only.
156
+ *
157
+ * `catalogSyncEnabled` is deliberately absent. Its toggle lives in a different
158
+ * card and owns its own write (`syncToggleMutation`), because the credentials
159
+ * form used to send it unconditionally from two cards away: a seller flipped
160
+ * the switch, saw it move, and nothing persisted it. A field in this schema is
161
+ * a field this form's Save button owns.
162
+ *
163
+ * 🛑 `.omit()` rather than a second literal, so the two cannot drift — it
164
+ * carries the SAME field instances into the new shape, which is what keeps the
165
+ * `annotate()` WeakMap entries alive. (A wrapper such as `.superRefine()` is
166
+ * transparent for the same reason; a field-level re-wrap is not.)
167
+ */
168
+ export const whatsappConnectionSchema = whatsappSettingsSchema.omit({
169
+ catalogSyncEnabled: true,
170
+ });
130
171
  /**
131
172
  * A buyer's lottery entry.
132
173
  *