@mohasinac/appkit 4.63.4 → 4.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/server/features/categories/adapters.d.ts +1 -1
  3. package/dist/_internal/server/features/categories/adapters.js +13 -0
  4. package/dist/_internal/server/features/checkout/actions.d.ts +2 -0
  5. package/dist/_internal/server/features/checkout/actions.js +22 -5
  6. package/dist/_internal/server/features/events/like-actions.d.ts +25 -0
  7. package/dist/_internal/server/features/events/like-actions.js +67 -0
  8. package/dist/_internal/server/features/events/likes.d.ts +65 -0
  9. package/dist/_internal/server/features/events/likes.js +146 -0
  10. package/dist/_internal/server/features/orders/adapters.js +3 -1
  11. package/dist/_internal/server/features/seo/index.d.ts +3 -0
  12. package/dist/_internal/server/features/seo/index.js +2 -0
  13. package/dist/_internal/server/features/seo/product-detail-types.d.ts +1 -0
  14. package/dist/_internal/server/features/seo/product-detail-types.js +26 -0
  15. package/dist/_internal/server/features/seo/robots.js +20 -1
  16. package/dist/_internal/server/features/seo/sitemap.js +125 -27
  17. package/dist/_internal/server/features/seo/static-params.d.ts +107 -0
  18. package/dist/_internal/server/features/seo/static-params.js +228 -0
  19. package/dist/_internal/server/features/seo/taxonomy-links.d.ts +44 -0
  20. package/dist/_internal/server/features/seo/taxonomy-links.js +85 -0
  21. package/dist/_internal/server/features/site-settings/adapters.js +20 -2
  22. package/dist/_internal/server/features/stores/adapters.d.ts +2 -2
  23. package/dist/_internal/server/features/stores/adapters.js +88 -1
  24. package/dist/_internal/server/functions/scheduled.d.ts +16 -1
  25. package/dist/_internal/server/functions/scheduled.js +23 -1
  26. package/dist/_internal/server/jobs/core/analyticsAlertEvaluate.js +5 -2
  27. package/dist/_internal/server/jobs/core/jobRunners.js +5 -0
  28. package/dist/_internal/server/jobs/core/onOrderCreate.js +104 -10
  29. package/dist/_internal/server/jobs/core/onOrderStatusChange.d.ts +2 -0
  30. package/dist/_internal/server/jobs/core/onOrderStatusChange.js +1 -0
  31. package/dist/_internal/server/jobs/core/onScamReportCreate.js +1 -0
  32. package/dist/_internal/server/jobs/core/onScamReportRejected.js +1 -0
  33. package/dist/_internal/server/jobs/core/onScamReportVerified.js +1 -0
  34. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +1 -0
  35. package/dist/_internal/server/jobs/core/pendingOrderTimeout.js +1 -0
  36. package/dist/_internal/server/jobs/core/sellerStandingRollup.d.ts +63 -0
  37. package/dist/_internal/server/jobs/core/sellerStandingRollup.js +412 -0
  38. package/dist/_internal/server/jobs/core/whatsappNotify.d.ts +35 -0
  39. package/dist/_internal/server/jobs/core/whatsappNotify.js +140 -0
  40. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  41. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  42. package/dist/_internal/server/jobs/handlers/sellerStandingRollup.d.ts +12 -0
  43. package/dist/_internal/server/jobs/handlers/sellerStandingRollup.js +14 -0
  44. package/dist/_internal/server/notifications/channel-health.d.ts +8 -6
  45. package/dist/_internal/server/notifications/channel-health.js +2 -2
  46. package/dist/_internal/server/notifications/send-guard.d.ts +1 -1
  47. package/dist/_internal/server/notifications/send-guard.js +4 -2
  48. package/dist/_internal/shared/actions/action-registry.js +89 -0
  49. package/dist/_internal/shared/checkout/order-math.d.ts +1 -1
  50. package/dist/_internal/shared/checkout/order-math.js +1 -1
  51. package/dist/_internal/shared/features/checkout/config.d.ts +2 -0
  52. package/dist/_internal/shared/features/checkout/config.js +2 -0
  53. package/dist/_internal/shared/features/messaging/config.d.ts +5 -7
  54. package/dist/_internal/shared/features/messaging/config.js +4 -1
  55. package/dist/_internal/shared/features/seller-standing/config.d.ts +130 -0
  56. package/dist/_internal/shared/features/seller-standing/config.js +153 -0
  57. package/dist/_internal/shared/features/site-settings/fees.d.ts +2 -0
  58. package/dist/_internal/shared/features/site-settings/fees.js +2 -0
  59. package/dist/_internal/shared/features/site-settings/types.d.ts +27 -2
  60. package/dist/_internal/shared/fees/calculator.d.ts +8 -0
  61. package/dist/_internal/shared/fees/calculator.js +7 -0
  62. package/dist/cli/index.js +2 -0
  63. package/dist/client.d.ts +2 -2
  64. package/dist/client.js +1 -1
  65. package/dist/constants/api-endpoints.d.ts +39 -0
  66. package/dist/constants/api-endpoints.js +16 -0
  67. package/dist/constants/notification-types.d.ts +1 -1
  68. package/dist/constants/notification-types.js +1 -1
  69. package/dist/core/integration-keys.d.ts +5 -0
  70. package/dist/core/integration-keys.js +10 -0
  71. package/dist/features/about/components/FeesView.js +6 -0
  72. package/dist/features/about/components/HelpPageView.js +9 -1
  73. package/dist/features/account/components/BecomeSellerView.d.ts +15 -3
  74. package/dist/features/account/components/BecomeSellerView.js +2 -2
  75. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  76. package/dist/features/account/components/NotificationPreferencesPanel.js +4 -3
  77. package/dist/features/account/types/index.d.ts +2 -0
  78. package/dist/features/admin/actions/notification-actions.d.ts +17 -2
  79. package/dist/features/admin/actions/notification-actions.js +154 -9
  80. package/dist/features/admin/components/AdminOrderEditorView.d.ts +1 -1
  81. package/dist/features/admin/components/AdminOrdersView.js +2 -0
  82. package/dist/features/admin/components/AdminSiteSettingsView.js +66 -6
  83. package/dist/features/admin/components/AdminStoreEditorView.d.ts +12 -1
  84. package/dist/features/admin/components/AdminStoreEditorView.js +31 -2
  85. package/dist/features/admin/components/AdminStoresGuideView.js +1 -1
  86. package/dist/features/admin/components/AdminStoresView.js +59 -2
  87. package/dist/features/admin/components/AdminTrustGuideView.js +10 -0
  88. package/dist/features/admin/constants/site-settings-tabs.d.ts +12 -7
  89. package/dist/features/admin/constants/site-settings-tabs.js +12 -6
  90. package/dist/features/admin/repository/site-settings.repository.js +1 -1
  91. package/dist/features/admin/schemas/admin-editor-forms.d.ts +10 -0
  92. package/dist/features/admin/schemas/admin-editor-forms.js +34 -0
  93. package/dist/features/admin/schemas/admin-user-form.d.ts +61 -0
  94. package/dist/features/admin/schemas/admin-user-form.js +44 -3
  95. package/dist/features/admin/schemas/firestore.d.ts +73 -0
  96. package/dist/features/admin/schemas/firestore.js +8 -3
  97. package/dist/features/auth/checkout-value-otp.d.ts +1 -1
  98. package/dist/features/auth/checkout-value-otp.js +1 -1
  99. package/dist/features/auth/permissions/constants.d.ts +2 -2
  100. package/dist/features/auth/permissions/constants.js +2 -1
  101. package/dist/features/blog/components/BlogIndexPageView.js +2 -2
  102. package/dist/features/blog/components/BlogListView.js +2 -1
  103. package/dist/features/blog/components/BlogPostView.js +2 -1
  104. package/dist/features/blog/constants/category-badge.d.ts +33 -0
  105. package/dist/features/blog/constants/category-badge.js +40 -0
  106. package/dist/features/cart/components/CartPriceBreakdown.d.ts +2 -0
  107. package/dist/features/cart/components/CartPriceBreakdown.js +1 -1
  108. package/dist/features/cart/components/StoreAddonsPicker.d.ts +5 -2
  109. package/dist/features/cart/components/StoreAddonsPicker.js +5 -4
  110. package/dist/features/cart/repository/cart.repository.js +2 -1
  111. package/dist/features/cart/schemas/firestore.d.ts +2 -1
  112. package/dist/features/categories/components/TaxonomyLinkIndex.d.ts +39 -0
  113. package/dist/features/categories/components/TaxonomyLinkIndex.js +11 -0
  114. package/dist/features/categories/schemas/firestore.d.ts +25 -0
  115. package/dist/features/categories/utils/load-category-options.d.ts +33 -0
  116. package/dist/features/categories/utils/load-category-options.js +50 -0
  117. package/dist/features/checkout/actions/checkout-value-otp-actions.d.ts +5 -10
  118. package/dist/features/checkout/actions/checkout-value-otp-actions.js +88 -2
  119. package/dist/features/events/components/EventCard.js +4 -12
  120. package/dist/features/events/components/EventInsights.d.ts +27 -0
  121. package/dist/features/events/components/EventInsights.js +10 -0
  122. package/dist/features/events/components/EventLikeButton.d.ts +21 -0
  123. package/dist/features/events/components/EventLikeButton.js +59 -0
  124. package/dist/features/events/components/EventLikersRail.d.ts +23 -0
  125. package/dist/features/events/components/EventLikersRail.js +54 -0
  126. package/dist/features/events/components/EventTypeBadge.d.ts +26 -0
  127. package/dist/features/events/components/EventTypeBadge.js +7 -0
  128. package/dist/features/events/components/EventsListPageView.js +2 -2
  129. package/dist/features/events/components/SpinWheel.d.ts +41 -0
  130. package/dist/features/events/components/SpinWheel.js +128 -0
  131. package/dist/features/events/components/SpinWheelView.js +36 -15
  132. package/dist/features/events/constants/event-type-meta.d.ts +54 -0
  133. package/dist/features/events/constants/event-type-meta.js +109 -0
  134. package/dist/features/events/repository/event-likes.repository.d.ts +57 -0
  135. package/dist/features/events/repository/event-likes.repository.js +151 -0
  136. package/dist/features/events/schemas/firestore.d.ts +111 -0
  137. package/dist/features/events/schemas/likes.d.ts +81 -0
  138. package/dist/features/events/schemas/likes.js +59 -0
  139. package/dist/features/events/types/index.d.ts +7 -0
  140. package/dist/features/events/utils/carousel-slide.d.ts +42 -0
  141. package/dist/features/events/utils/carousel-slide.js +174 -0
  142. package/dist/features/events/utils/event-charts.d.ts +65 -0
  143. package/dist/features/events/utils/event-charts.js +172 -0
  144. package/dist/features/faq/constants/help-guides.js +9 -0
  145. package/dist/features/faq/schemas/firestore.js +1 -0
  146. package/dist/features/faq/types/index.d.ts +19 -0
  147. package/dist/features/homepage/components/BrandsSection.js +25 -1
  148. package/dist/features/homepage/components/HeroCarousel.js +47 -10
  149. package/dist/features/homepage/lib/section-renderer.js +1 -1
  150. package/dist/features/homepage/types/index.d.ts +7 -0
  151. package/dist/features/orders/actions/refund-actions.js +1 -0
  152. package/dist/features/orders/components/OrderAddonBadges.d.ts +3 -1
  153. package/dist/features/orders/components/OrderAddonBadges.js +4 -3
  154. package/dist/features/orders/repository/orders.repository.d.ts +8 -0
  155. package/dist/features/orders/repository/orders.repository.js +2 -0
  156. package/dist/features/orders/schemas/firestore.d.ts +4 -0
  157. package/dist/features/orders/types/index.d.ts +5 -2
  158. package/dist/features/search/action-index/settings-entries.js +9 -0
  159. package/dist/features/store-extensions/repository/store-extensions.repositories.d.ts +6 -1
  160. package/dist/features/store-extensions/repository/store-extensions.repositories.js +10 -1
  161. package/dist/features/store-extensions/schemas/firestore.d.ts +17 -1
  162. package/dist/features/store-extensions/schemas/firestore.js +10 -0
  163. package/dist/features/stores/components/InteractiveStoreCard.js +2 -1
  164. package/dist/features/stores/components/SellerStandingPanel.d.ts +31 -0
  165. package/dist/features/stores/components/SellerStandingPanel.js +25 -0
  166. package/dist/features/stores/components/SellerTierBadge.d.ts +57 -0
  167. package/dist/features/stores/components/SellerTierBadge.js +19 -0
  168. package/dist/features/stores/components/StoreCapabilitiesGuideView.js +33 -2
  169. package/dist/features/stores/components/StoreCreateForm.d.ts +46 -0
  170. package/dist/features/stores/components/StoreCreateForm.js +107 -0
  171. package/dist/features/stores/components/StoreGuideHubView.js +18 -5
  172. package/dist/features/stores/components/StoreHeader.js +2 -1
  173. package/dist/features/stores/components/StoreSettingsGuideView.js +6 -2
  174. package/dist/features/stores/repository/store.repository.d.ts +23 -1
  175. package/dist/features/stores/repository/store.repository.js +52 -1
  176. package/dist/features/stores/schemas/firestore.d.ts +76 -7
  177. package/dist/features/stores/schemas/firestore.js +21 -6
  178. package/dist/features/stores/schemas/index.d.ts +88 -2
  179. package/dist/features/stores/schemas/index.js +14 -0
  180. package/dist/features/stores/schemas/store-create.d.ts +47 -0
  181. package/dist/features/stores/schemas/store-create.js +86 -0
  182. package/dist/features/stores/types/index.d.ts +42 -0
  183. package/dist/features/whatsapp-bot/api/send-status/route.d.ts +12 -0
  184. package/dist/features/whatsapp-bot/api/send-status/route.js +38 -0
  185. package/dist/features/whatsapp-bot/api/webhook/route.d.ts +12 -0
  186. package/dist/features/whatsapp-bot/api/webhook/route.js +43 -0
  187. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.d.ts +57 -0
  188. package/dist/features/whatsapp-bot/components/SellerWhatsAppSettingsView.js +76 -0
  189. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.d.ts +9 -0
  190. package/dist/features/whatsapp-bot/components/WhatsAppChatButton.js +7 -0
  191. package/dist/features/whatsapp-bot/components/index.d.ts +3 -0
  192. package/dist/features/whatsapp-bot/components/index.js +2 -0
  193. package/dist/features/whatsapp-bot/helpers/whatsapp.d.ts +52 -0
  194. package/dist/features/whatsapp-bot/helpers/whatsapp.js +288 -0
  195. package/dist/features/whatsapp-bot/index.d.ts +3 -0
  196. package/dist/features/whatsapp-bot/index.js +3 -0
  197. package/dist/features/whatsapp-bot/manifest.d.ts +2 -0
  198. package/dist/features/whatsapp-bot/manifest.js +13 -0
  199. package/dist/features/whatsapp-bot/messages/en.json +7 -0
  200. package/dist/features/whatsapp-bot/server.d.ts +7 -0
  201. package/dist/features/whatsapp-bot/server.js +7 -0
  202. package/dist/features/whatsapp-bot/types/index.d.ts +126 -0
  203. package/dist/features/whatsapp-bot/types/index.js +1 -0
  204. package/dist/index.d.ts +23 -6
  205. package/dist/index.js +38 -12
  206. package/dist/next/routing/route-map.d.ts +6 -1
  207. package/dist/next/routing/route-map.js +14 -0
  208. package/dist/providers/db-firebase/base.repository.d.ts +1 -1
  209. package/dist/providers/db-firebase/base.repository.js +1 -1
  210. package/dist/repositories/index.d.ts +1 -1
  211. package/dist/repositories/index.js +1 -1
  212. package/dist/schemas/registry.d.ts +115 -0
  213. package/dist/schemas/registry.js +2 -1
  214. package/dist/security/pii-encrypt.js +1 -1
  215. package/dist/server.d.ts +15 -2
  216. package/dist/server.js +32 -2
  217. package/dist/styles.css +36 -1
  218. package/dist/tailwind-utilities.css +1 -1
  219. package/dist/ui/components/HorizontalScroller.js +15 -2
  220. package/dist/ui/components/NavigationLoader.d.ts +14 -1
  221. package/dist/ui/components/NavigationLoader.js +79 -30
  222. package/dist/ui/components/PageHeroHeading.d.ts +40 -0
  223. package/dist/ui/components/PageHeroHeading.js +15 -0
  224. package/dist/ui/components/Typography.d.ts +3 -3
  225. package/dist/ui/components/Typography.js +59 -31
  226. package/dist/ui/components/Typography.style.css +35 -0
  227. package/dist/ui/index.d.ts +2 -0
  228. package/dist/ui/index.js +1 -0
  229. package/package.json +1 -1
  230. package/scripts/seed-cli.mjs +4 -2
  231. package/dist/features/orders/utils/order-announcement-message.d.ts +0 -18
  232. package/dist/features/orders/utils/order-announcement-message.js +0 -16
@@ -2,13 +2,15 @@ import { normalizeError } from "../../../errors/normalize.js";
2
2
  import { notificationRepository } from "../repository/notification.repository.js";
3
3
  import { siteSettingsRepository } from "../repository/site-settings.repository.js";
4
4
  import { userRepository } from "../../auth/repository/user.repository.js";
5
+ import { storeRepository } from "../../stores/repository/store.repository.js";
6
+ import { safeRead } from "../../../errors/safe-read.js";
5
7
  import { createResendProvider } from "../../../providers/email-resend/provider.js";
6
8
  import { isChannelHealthy, withChannelRetry } from "../../../_internal/server/notifications/channel-health.js";
9
+ import { enqueueJob } from "../../jobs/actions/enqueue-job.js";
7
10
  import { serverLogger } from "../../../monitoring/index.js";
8
11
  import { resolveNotificationActionUrl, TYPE_AUDIENCE, } from "../../../_internal/shared/features/notifications/action-url.js";
9
12
  import { isEmailEligible } from "../../../_internal/shared/features/notifications/email-eligibility.js";
10
13
  import { renderNotificationEmail } from "../../email/notification-templates.js";
11
- import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore.js";
12
14
  /**
13
15
  * A notification's `actionUrl` is a site-relative path — correct for the
14
16
  * in-app bell, useless in an email, where a bare `/user/orders/x` is not a
@@ -31,6 +33,85 @@ function absoluteActionUrl(path) {
31
33
  const base = process.env.NEXT_PUBLIC_APP_URL?.replace(/\/+$/, "");
32
34
  return base ? `${base}${path}` : path;
33
35
  }
36
+ import { DEFAULT_NOTIFICATION_CHANNELS, meetsMinPriority, } from "../schemas/firestore.js";
37
+ /**
38
+ * Notification types whose WhatsApp send is gated behind the SELLER's
39
+ * standing WhatsApp-notifications subscription — see `storeId` on
40
+ * `SendNotificationInput`.
41
+ *
42
+ * 🛑 This replaced a per-ORDER ₹10 addon the buyer paid at checkout
43
+ * (`OrderDocument.whatsappNotifyAddon`). The axis moved deliberately: the
44
+ * platform owns one WhatsApp number, so the cost is a standing monthly one
45
+ * borne by the seller who benefits from it, not a line item a buyer opts into
46
+ * per order. A buyer should not have to purchase their own delivery updates.
47
+ *
48
+ * Every other type (bid_won, offers, scam reports, support tickets, admin
49
+ * alerts, …) keeps the original two gates — admin channel enabled, user has
50
+ * not opted out — unchanged.
51
+ */
52
+ /**
53
+ * Does this store subscribe to WhatsApp order notifications?
54
+ *
55
+ * The subscription is a monthly add-on priced by the admin
56
+ * (`commissions.whatsappNotifyMonthlyFee`); this reads only whether it is
57
+ * currently active. Billing lives with the other recurring charges, not here.
58
+ *
59
+ * 🛑 Fails CLOSED. A store we cannot read, or one that has not subscribed,
60
+ * gets no WhatsApp send — the buyer still receives the in-app notification and
61
+ * the email, which are ungated. Failing open would message customers on behalf
62
+ * of a seller who is not paying for it, and bill the platform for the template.
63
+ */
64
+ async function storeWhatsAppSubscribed(storeId) {
65
+ if (!storeId)
66
+ return false;
67
+ const store = await safeRead(() => storeRepository.findBySlug(storeId), {
68
+ route: "sendNotification",
69
+ key: `whatsapp-subscription:${storeId}`,
70
+ fallback: null,
71
+ });
72
+ return store?.whatsappNotifySubscribed === true;
73
+ }
74
+ const ORDER_GATED_TYPES = new Set([
75
+ "order_placed",
76
+ "order_confirmed",
77
+ "order_shipped",
78
+ "order_delivered",
79
+ "order_cancelled",
80
+ "refund_initiated",
81
+ ]);
82
+ /**
83
+ * Notification type → the flat `SiteSettingsCredentials` field holding its
84
+ * approved Meta template name.
85
+ *
86
+ * 🛑 **`Partial` here is CORRECT and is not the `typeToPrefsKey` mistake.**
87
+ *
88
+ * The distinction is what a missing key MEANS. A missing `typeToPrefsKey`
89
+ * entry silently ignored a user's opt-out — a real setting that read as
90
+ * honoured and was not. A missing entry here means "no Meta-approved template
91
+ * exists for this type", which is a fact about the Meta account, not a bug:
92
+ * six of the 28 types have one, and the rest legitimately fall back to
93
+ * free-form (which Meta accepts inside the 24-hour customer-service window
94
+ * and rejects outside it — the runner already logs that rejection).
95
+ *
96
+ * Getting six approved templates is a Meta Business review process, not a
97
+ * code change, so listing all 28 here with empty values would be a
98
+ * `Record<…>` that lies about coverage.
99
+ *
100
+ * The six that ARE approved are the order lifecycle, which is also the set
101
+ * gated behind the buyer's ₹10 addon above — the two lists agree on purpose.
102
+ */
103
+ const WHATSAPP_TEMPLATE_FIELD = {
104
+ order_placed: "whatsappTemplateOrderPlaced",
105
+ order_confirmed: "whatsappTemplateOrderConfirmed",
106
+ order_shipped: "whatsappTemplateOrderShipped",
107
+ order_delivered: "whatsappTemplateOrderDelivered",
108
+ order_cancelled: "whatsappTemplateOrderCancelled",
109
+ refund_initiated: "whatsappTemplateRefundInitiated",
110
+ };
111
+ function resolveWhatsAppTemplateName(type, creds) {
112
+ const field = WHATSAPP_TEMPLATE_FIELD[type];
113
+ return (field ? creds[field] : undefined)?.trim() ?? "";
114
+ }
34
115
  export async function markNotificationRead(id) {
35
116
  await notificationRepository.markAsRead(id);
36
117
  }
@@ -53,15 +134,15 @@ export async function getUnreadNotificationCount(userId) {
53
134
  /**
54
135
  * Create an in-app notification and fan out to enabled external channels.
55
136
  *
56
- * In-app is always written. Email is sent only when:
137
+ * In-app is always written. Email / WhatsApp are sent only when:
57
138
  * 1. The channel is enabled in siteSettings.notificationChannels
58
139
  * 2. Notification priority meets the channel's minPriority threshold
59
140
  * 3. The optional `types` filter includes this notification type (or is absent)
60
- * 4. The contact field (userEmail) is supplied by the caller
141
+ * 4. The contact field (userEmail / userPhone) is supplied by the caller
61
142
  * 5. The required credentials are set in siteSettings.credentials
62
143
  */
63
144
  export async function sendNotification(input) {
64
- const { userEmail, emailHtml, audience, eligibilityOverride, ...notifInput } = input;
145
+ const { userEmail, userPhone, emailHtml, audience, eligibilityOverride, ...notifInput } = input;
65
146
  /*
66
147
  * Fill `actionUrl` when the caller did not.
67
148
  *
@@ -103,12 +184,12 @@ export async function sendNotification(input) {
103
184
  * suppresses the emailed/messaged COPY, not the notification.
104
185
  *
105
186
  * One map gates both channels deliberately. If a type is ever genuinely
106
- * worth a channel of its own, split the map then rather than
187
+ * WhatsApp-worthy but not email-worthy, split the map then rather than
107
188
  * loosening this early return.
108
189
  */
109
190
  if (!isEmailEligible(notifInput.type) &&
110
191
  eligibilityOverride !== "staff_requested") {
111
- return { notification, email: "skipped" };
192
+ return { notification, email: "skipped", whatsapp: "skipped" };
112
193
  }
113
194
  // Load channel config + credentials (one Firestore read).
114
195
  let settings;
@@ -118,7 +199,7 @@ export async function sendNotification(input) {
118
199
  catch (err) {
119
200
  void normalizeError(err);
120
201
  serverLogger.warn("sendNotification: could not load siteSettings — skipping external channels", { err });
121
- return { notification, email: "skipped" };
202
+ return { notification, email: "skipped", whatsapp: "skipped" };
122
203
  }
123
204
  const channels = settings.notificationChannels ?? DEFAULT_NOTIFICATION_CHANNELS;
124
205
  const creds = settings.credentials ?? {};
@@ -150,6 +231,7 @@ export async function sendNotification(input) {
150
231
  ? userRepository.revealUser(fetchedUserDoc)
151
232
  : null;
152
233
  const resolvedEmail = userEmail ?? fetchedUserPlain?.email ?? undefined;
234
+ const resolvedPhone = userPhone ?? fetchedUserPlain?.phoneNumber ?? undefined;
153
235
  // Map NotificationType to a NotificationTypePrefs key.
154
236
  /*
155
237
  * 🛑 COMPLETE and compile-checked: `Record<NotificationType, …>`, not
@@ -212,7 +294,7 @@ export async function sendNotification(input) {
212
294
  const typeKey = typeToPrefsKey[type];
213
295
  // If the user has explicitly disabled this notification type, skip all external channels.
214
296
  if (typeKey && userTypePrefs[typeKey] === false) {
215
- return { notification, email: "skipped" };
297
+ return { notification, email: "skipped", whatsapp: "skipped" };
216
298
  }
217
299
  // Email channel — admin enabled AND user hasn't opted out
218
300
  let emailStatus = "skipped";
@@ -259,10 +341,73 @@ export async function sendNotification(input) {
259
341
  }
260
342
  }
261
343
  }
344
+ // WhatsApp channel — admin enabled AND user hasn't opted out AND (for
345
+ // order-lifecycle types) the buyer paid the per-order WhatsApp addon.
346
+ let whatsappStatus = "skipped";
347
+ if (channels.whatsapp.enabled &&
348
+ userChannelPrefs.whatsapp !== false &&
349
+ resolvedPhone &&
350
+ meetsMinPriority(priority, channels.whatsapp.minPriority) &&
351
+ (!channels.whatsapp.types?.length || channels.whatsapp.types.includes(type)) &&
352
+ (!ORDER_GATED_TYPES.has(type) || (await storeWhatsAppSubscribed(input.storeId)))) {
353
+ /*
354
+ * ENV FIRST, site settings second.
355
+ *
356
+ * The platform owns one WhatsApp number, so its credentials are
357
+ * deployment configuration rather than admin-editable content — they
358
+ * belong beside the other secrets in the env, not in a Firestore document
359
+ * an admin screen can rewrite. Rotating a leaked Meta token should be a
360
+ * redeploy, not a form submission.
361
+ *
362
+ * 🛑 `WHATSAPP_PHONE_NUMBER_ID` is the Cloud API's numeric id, NOT the
363
+ * dialable number: sends go to `/{phone-number-id}/messages`, and a `+1…`
364
+ * string there is a 404 that looks like a routing bug.
365
+ *
366
+ * The site-settings fallback is kept so an install that configured these
367
+ * through the admin screen keeps working — this changes precedence, not
368
+ * behaviour, and nothing has to be migrated.
369
+ */
370
+ const phoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID?.trim() ||
371
+ (creds.whatsappPhoneNumberId?.trim() ?? "");
372
+ const accessToken = process.env.WHATSAPP_MESSAGES_TOKEN?.trim() ||
373
+ (creds.whatsappCloudApiToken?.trim() ?? "");
374
+ if (phoneNumberId && accessToken) {
375
+ try {
376
+ const { jobId } = await enqueueJob({
377
+ jobType: "whatsappNotify",
378
+ payload: {
379
+ toPhone: resolvedPhone,
380
+ title,
381
+ message,
382
+ type,
383
+ templateName: resolveWhatsAppTemplateName(type, creds),
384
+ templateLanguage: creds.whatsappTemplateLanguage ?? "en",
385
+ notificationId: notification.id,
386
+ phoneNumberId,
387
+ accessToken,
388
+ },
389
+ requestedBy: input.userId,
390
+ });
391
+ whatsappStatus = "queued";
392
+ await notificationRepository
393
+ .update(notification.id, { whatsappStatus: "queued", whatsappJobId: jobId })
394
+ .catch((err) => {
395
+ void normalizeError(err);
396
+ serverLogger.warn("sendNotification: failed to write whatsappJobId onto notification doc", { notificationId: notification.id });
397
+ });
398
+ }
399
+ catch (err) {
400
+ void normalizeError(err);
401
+ serverLogger.error("sendNotification: WhatsApp job enqueue failed", { userId: input.userId, err });
402
+ whatsappStatus = "failed";
403
+ }
404
+ }
405
+ }
262
406
  serverLogger.info("sendNotification: dispatched", {
263
407
  userId: input.userId,
264
408
  type,
265
409
  email: emailStatus,
410
+ whatsapp: whatsappStatus,
266
411
  });
267
- return { notification, email: emailStatus };
412
+ return { notification, email: emailStatus, whatsapp: whatsappStatus };
268
413
  }
@@ -37,7 +37,7 @@ export interface AdminOrderEditorViewProps {
37
37
  paymentReviewOutcome?: string;
38
38
  /**
39
39
  * Paid add-ons + applied coupon for this order. Operational, not decorative:
40
- * `giftWrapAddon` tells the packer to wrap the parcel, and the
40
+ * `whatsappNotifyAddon` is who a status change should message, and the
41
41
  * coupon is what an admin needs when reconciling a discounted order.
42
42
  */
43
43
  addons?: OrderAddonBadgesOrder;
@@ -255,6 +255,8 @@ export function AdminOrdersView({ children, ...props }) {
255
255
  // Straight off the order document — the admin list route returns
256
256
  // raw docs, so these need no serializer entry (cf. Root Cause #38).
257
257
  addons: {
258
+ whatsappNotifyAddon: Boolean(item.whatsappNotifyAddon),
259
+ whatsappNotifyFee: Number(item.whatsappNotifyFee ?? 0) || undefined,
258
260
  giftWrapAddon: Boolean(item.giftWrapAddon),
259
261
  giftWrapFee: Number(item.giftWrapFee ?? 0) || undefined,
260
262
  giftWrapMessage: toStringValue(item.giftWrapMessage) || undefined,
@@ -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],
@@ -7,6 +7,17 @@ export interface AdminStoreEditorViewProps {
7
7
  currentStatus?: string;
8
8
  currentIsVerified?: boolean;
9
9
  currentIsFeatured?: boolean;
10
+ /**
11
+ * 🛑 Both REQUIRED for the controls to show the stored value, and both are
12
+ * the Root Cause #38 trap: `AdminStoresView` seeds this editor from a LIST
13
+ * row, so a field the list serializer omits arrives `undefined`, the control
14
+ * defaults to off, and the save handler sends that wrong default back —
15
+ * silently un-trusting a trusted store while the admin was editing
16
+ * something else. The list route emits `trustedSeller` already; `sellerTier`
17
+ * had to be added to it in the same change.
18
+ */
19
+ currentSellerTier?: "seller" | "trusted";
20
+ currentTrustedSeller?: boolean;
10
21
  currentCapabilities?: string[];
11
22
  /**
12
23
  * 🛑 Required for the note to DISPLAY. Without it `adminNotes` was hardcoded
@@ -19,4 +30,4 @@ export interface AdminStoreEditorViewProps {
19
30
  */
20
31
  currentAdminNotes?: string;
21
32
  }
22
- export declare function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentCapabilities, currentAdminNotes, }: AdminStoreEditorViewProps): React.JSX.Element;
33
+ export declare function AdminStoreEditorView({ open, onClose, storeId, storeName, currentStatus, currentIsVerified, currentIsFeatured, currentSellerTier, currentTrustedSeller, currentCapabilities, currentAdminNotes, }: AdminStoreEditorViewProps): React.JSX.Element;