@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
@@ -8,7 +8,12 @@ export interface NavigationLoaderProps {
8
8
  label?: string;
9
9
  /**
10
10
  * Safety valve: hides the overlay if the route never settles.
11
- * @default 15000 (ms)
11
+ *
12
+ * 10s, matching the sync-function ceiling this app is written against — an
13
+ * RSC payload that has not arrived by then is not arriving, so holding a
14
+ * blocking overlay any longer only hides the page the user still has.
15
+ *
16
+ * @default 10000 (ms)
12
17
  */
13
18
  timeoutMs?: number;
14
19
  /** Additional className on the overlay div. */
@@ -24,6 +29,14 @@ export interface NavigationLoaderProps {
24
29
  * - Start: intercept `history.pushState` + `popstate`
25
30
  * - End: `usePathname()` changing means React has settled
26
31
  *
32
+ * 🛑 It must never start for a navigation whose path+query cannot change,
33
+ * because the end condition would then be unreachable and the overlay would
34
+ * block the whole viewport until `timeoutMs`. An in-page anchor is exactly
35
+ * that: clicking `#section` fires `popstate` (Chrome does, for the App
36
+ * Router's hash entry) while `usePathname()` and `useSearchParams()` both stay
37
+ * put — measured against production, 15s of stranded overlay on the way in AND
38
+ * again on Back. Hence the `live === prev` gate in `onPopState`.
39
+ *
27
40
  * Mount once at the app shell level (e.g. inside LayoutShellClient).
28
41
  */
29
42
  export declare function NavigationLoader({ spinnerVariant, label, timeoutMs, className, }: NavigationLoaderProps): import("react").JSX.Element | null;
@@ -1,9 +1,21 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { normalizeError } from "../../errors/normalize.js";
4
- import { useEffect, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { usePathname, useSearchParams } from "next/navigation.js";
6
6
  import { Spinner } from "./Spinner.js";
7
+ /**
8
+ * The route key this component settles on: path + query, NEVER the hash.
9
+ *
10
+ * It has to match what `usePathname()` + `useSearchParams()` can observe,
11
+ * because those two hooks are the only thing that can hide the overlay. A hash
12
+ * is invisible to both, so including it here would promise a settle signal
13
+ * that can never arrive.
14
+ */
15
+ function windowRouteKey() {
16
+ const url = new URL(window.location.href);
17
+ return `${url.pathname}?${url.searchParams.toString()}`;
18
+ }
7
19
  /**
8
20
  * NavigationLoader
9
21
  *
@@ -14,9 +26,17 @@ import { Spinner } from "./Spinner.js";
14
26
  * - Start: intercept `history.pushState` + `popstate`
15
27
  * - End: `usePathname()` changing means React has settled
16
28
  *
29
+ * 🛑 It must never start for a navigation whose path+query cannot change,
30
+ * because the end condition would then be unreachable and the overlay would
31
+ * block the whole viewport until `timeoutMs`. An in-page anchor is exactly
32
+ * that: clicking `#section` fires `popstate` (Chrome does, for the App
33
+ * Router's hash entry) while `usePathname()` and `useSearchParams()` both stay
34
+ * put — measured against production, 15s of stranded overlay on the way in AND
35
+ * again on Back. Hence the `live === prev` gate in `onPopState`.
36
+ *
17
37
  * Mount once at the app shell level (e.g. inside LayoutShellClient).
18
38
  */
19
- export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading page…", timeoutMs = 15000, className = "", }) {
39
+ export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading page…", timeoutMs = 10000, className = "", }) {
20
40
  const pathname = usePathname();
21
41
  const searchParams = useSearchParams();
22
42
  const [visible, setVisible] = useState(false);
@@ -28,39 +48,55 @@ export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading
28
48
  const pendingUrl = useRef(null);
29
49
  const visibleRef = useRef(false);
30
50
  const activeRef = useRef(false);
51
+ /** Last path+query seen on `window.location`. Null until the first effect. */
52
+ const lastWindowKey = useRef(null);
31
53
  const currentUrl = `${pathname ?? ""}?${searchParams?.toString() ?? ""}`;
32
54
  const prevUrl = useRef(currentUrl);
33
55
  useEffect(() => {
34
56
  visibleRef.current = visible;
35
57
  }, [visible]);
36
- // Hide once the route URL (path + query/hash) has settled in React.
58
+ /** Drop the overlay and every timer behind it. Safe to call when hidden. */
59
+ const hideNow = useCallback(() => {
60
+ activeRef.current = false;
61
+ visibleRef.current = false;
62
+ setVisible(false);
63
+ if (showTimer.current) {
64
+ clearTimeout(showTimer.current);
65
+ showTimer.current = null;
66
+ }
67
+ if (safetyTimer.current) {
68
+ clearTimeout(safetyTimer.current);
69
+ safetyTimer.current = null;
70
+ }
71
+ if (tickTimer.current) {
72
+ clearInterval(tickTimer.current);
73
+ tickTimer.current = null;
74
+ }
75
+ startTime.current = null;
76
+ pendingUrl.current = null;
77
+ }, []);
78
+ // Hide once the route URL (path + query) has settled in React.
37
79
  useEffect(() => {
38
80
  if (currentUrl !== prevUrl.current) {
39
81
  prevUrl.current = currentUrl;
40
- activeRef.current = false;
41
- visibleRef.current = false;
42
- setVisible(false);
43
- if (showTimer.current) {
44
- clearTimeout(showTimer.current);
45
- showTimer.current = null;
46
- }
47
- if (safetyTimer.current)
48
- clearTimeout(safetyTimer.current);
49
- if (tickTimer.current)
50
- clearInterval(tickTimer.current);
51
- startTime.current = null;
52
- pendingUrl.current = null;
82
+ lastWindowKey.current = windowRouteKey();
83
+ hideNow();
53
84
  }
54
- }, [currentUrl]);
85
+ }, [currentUrl, hideNow]);
55
86
  useEffect(() => {
87
+ if (lastWindowKey.current === null)
88
+ lastWindowKey.current = windowRouteKey();
56
89
  function showOverlay(nextKey) {
57
90
  if (activeRef.current || visibleRef.current)
58
91
  return;
59
92
  if (showTimer.current)
60
93
  return;
61
- activeRef.current = true;
94
+ // Checked BEFORE claiming `activeRef`: returning with it already set
95
+ // would leave the flag stuck true, and every later navigation would
96
+ // early-return above — the loader would silently never show again.
62
97
  if (nextKey && pendingUrl.current === nextKey)
63
98
  return;
99
+ activeRef.current = true;
64
100
  pendingUrl.current = nextKey ?? pendingUrl.current;
65
101
  // Defer to next macrotask to avoid updates during router insertion effects.
66
102
  showTimer.current = setTimeout(() => {
@@ -76,15 +112,7 @@ export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading
76
112
  setVisible(true);
77
113
  if (safetyTimer.current)
78
114
  clearTimeout(safetyTimer.current);
79
- safetyTimer.current = setTimeout(() => {
80
- activeRef.current = false;
81
- visibleRef.current = false;
82
- setVisible(false);
83
- if (tickTimer.current)
84
- clearInterval(tickTimer.current);
85
- startTime.current = null;
86
- pendingUrl.current = null;
87
- }, timeoutMs);
115
+ safetyTimer.current = setTimeout(hideNow, timeoutMs);
88
116
  }, 0);
89
117
  }
90
118
  function shouldShowForNextUrl(next) {
@@ -122,15 +150,36 @@ export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading
122
150
  showOverlay();
123
151
  }
124
152
  }
125
- return origPush(...args);
153
+ const result = origPush(...args);
154
+ lastWindowKey.current = windowRouteKey();
155
+ return result;
126
156
  };
127
157
  function onPopState() {
128
- showOverlay();
158
+ // `popstate` fires AFTER the URL has changed, so this is the destination.
159
+ const live = windowRouteKey();
160
+ const prev = lastWindowKey.current;
161
+ lastWindowKey.current = live;
162
+ // Same path+query means an in-page anchor or a second history entry for
163
+ // the route we are already on. React will never re-render, so the hide
164
+ // effect above can never fire — showing the overlay here strands it.
165
+ if (live === prev) {
166
+ hideNow();
167
+ return;
168
+ }
169
+ showOverlay(live);
170
+ }
171
+ // Belt to the braces above: a hash change that reaches us some other way
172
+ // is still, by definition, not a route change.
173
+ function onHashChange() {
174
+ lastWindowKey.current = windowRouteKey();
175
+ hideNow();
129
176
  }
130
177
  window.addEventListener("popstate", onPopState);
178
+ window.addEventListener("hashchange", onHashChange);
131
179
  return () => {
132
180
  history.pushState = origPush;
133
181
  window.removeEventListener("popstate", onPopState);
182
+ window.removeEventListener("hashchange", onHashChange);
134
183
  if (showTimer.current)
135
184
  clearTimeout(showTimer.current);
136
185
  if (safetyTimer.current)
@@ -142,7 +191,7 @@ export function NavigationLoader({ spinnerVariant = "pokeball", label = "Loading
142
191
  startTime.current = null;
143
192
  pendingUrl.current = null;
144
193
  };
145
- }, [timeoutMs]);
194
+ }, [timeoutMs, hideNow]);
146
195
  if (!visible)
147
196
  return null;
148
197
  return (_jsxs("div", { role: "status", "aria-label": label, className: `appkit-nav-loader ${className}`, "data-section": "navigationloader-div-566", children: [_jsx(Spinner, { size: "xl", variant: spinnerVariant, label: label }), _jsxs("span", { className: "appkit-nav-loader__timer", "aria-hidden": "true", children: [elapsed.toFixed(1), "s"] })] }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.63.3",
3
+ "version": "4.63.5",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,18 +0,0 @@
1
- export interface PurchaseAnnouncementInput {
2
- buyerName: string;
3
- firstItemName: string;
4
- /** 0 if only one item in the order */
5
- additionalItemCount: number;
6
- /** Total order amount in decimal rupees */
7
- totalAmount: number;
8
- orderId: string;
9
- }
10
- /**
11
- * Plain-text body for the "New order placed" staff notification row.
12
- *
13
- * Originally built for the WhatsApp purchase announcement. That integration
14
- * was removed 2026-10-20, but the builder survives because its output is now
15
- * the `adminNotifications` body the admin inbox renders and the daily digest
16
- * counts — a durable record rather than a per-admin blast.
17
- */
18
- export declare function buildPurchaseAnnouncementMessage(input: PurchaseAnnouncementInput): string;
@@ -1,16 +0,0 @@
1
- /**
2
- * Plain-text body for the "New order placed" staff notification row.
3
- *
4
- * Originally built for the WhatsApp purchase announcement. That integration
5
- * was removed 2026-10-20, but the builder survives because its output is now
6
- * the `adminNotifications` body the admin inbox renders and the daily digest
7
- * counts — a durable record rather than a per-admin blast.
8
- */
9
- export function buildPurchaseAnnouncementMessage(input) {
10
- const { buyerName, firstItemName, additionalItemCount, totalAmount, orderId } = input;
11
- const extra = additionalItemCount > 0
12
- ? ` + ${additionalItemCount} more item${additionalItemCount > 1 ? "s" : ""}`
13
- : "";
14
- const amount = totalAmount.toLocaleString("en-IN");
15
- return `🛍️ New order! ${buyerName} purchased ${firstItemName}${extra} for ₹${amount}. Order #${orderId}`;
16
- }