@infuro/cms-core 1.0.33 → 1.0.35

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 (164) hide show
  1. package/README.md +22 -6
  2. package/dist/OrderTrackPage-W3J6WIG2.js +426 -0
  3. package/dist/OrderTrackPage-Z6FCJQYY.cjs +428 -0
  4. package/dist/admin.cjs +34072 -20487
  5. package/dist/admin.d.cts +191 -9
  6. package/dist/admin.d.ts +191 -9
  7. package/dist/admin.js +33913 -20423
  8. package/dist/api.cjs +123 -16508
  9. package/dist/api.d.cts +633 -3
  10. package/dist/api.d.ts +633 -3
  11. package/dist/api.js +14 -16519
  12. package/dist/auth.cjs +196 -955
  13. package/dist/auth.d.cts +201 -42
  14. package/dist/auth.d.ts +201 -42
  15. package/dist/auth.js +4 -888
  16. package/dist/chunk-2XE4X74C.js +29602 -0
  17. package/dist/chunk-3DZRIZOD.cjs +52 -0
  18. package/dist/chunk-3KPNTQBV.cjs +620 -0
  19. package/dist/chunk-46GKADYE.cjs +84 -0
  20. package/dist/chunk-4TRPKBKO.js +589 -0
  21. package/dist/chunk-5ELSW2UP.js +150 -0
  22. package/dist/chunk-6KPQWRFW.js +171 -0
  23. package/dist/chunk-7NMT3GBR.cjs +29 -0
  24. package/dist/chunk-7YIBMQDR.cjs +86 -0
  25. package/dist/chunk-BGJAV67S.cjs +29710 -0
  26. package/dist/chunk-CNTSLED2.cjs +559 -0
  27. package/dist/chunk-CRFV5WJK.js +16 -0
  28. package/dist/chunk-D5FBNKQM.cjs +104 -0
  29. package/dist/chunk-EF2JBPDM.js +114 -0
  30. package/dist/chunk-EQPITOP6.js +543 -0
  31. package/dist/chunk-GO7PPYNU.js +76 -0
  32. package/dist/chunk-HF3L2GIT.cjs +194 -0
  33. package/dist/chunk-J7XHLOLO.js +830 -0
  34. package/dist/chunk-JBRR5UYL.js +87 -0
  35. package/dist/chunk-JC6DLWTE.js +21 -0
  36. package/dist/chunk-JHF4YYRC.cjs +188 -0
  37. package/dist/chunk-JXF23MPG.js +25 -0
  38. package/dist/chunk-MQBT33IV.js +48 -0
  39. package/dist/chunk-N44BJZ4C.cjs +123 -0
  40. package/dist/chunk-OOTLNH63.cjs +18 -0
  41. package/dist/chunk-PAMVINEX.cjs +577 -0
  42. package/dist/chunk-PNOKNSG2.cjs +222 -0
  43. package/dist/chunk-QM733NUK.js +186 -0
  44. package/dist/chunk-R73BKDRO.cjs +90 -0
  45. package/dist/chunk-SHUYVCID.js +4 -0
  46. package/dist/chunk-SYBOCAWB.js +97 -0
  47. package/dist/chunk-TDV6EJHV.js +192 -0
  48. package/dist/chunk-TFUOHI37.cjs +839 -0
  49. package/dist/chunk-TIAEXJLI.cjs +182 -0
  50. package/dist/chunk-UPLVMVRX.cjs +23 -0
  51. package/dist/chunk-USNT2KNT.cjs +6 -0
  52. package/dist/chunk-WEMDMVHQ.js +216 -0
  53. package/dist/chunk-XDSNZUW7.js +564 -0
  54. package/dist/chunk-XQ3QUHWR.cjs +83 -0
  55. package/dist/chunk-XW5ATPRW.cjs +155 -0
  56. package/dist/chunk-ZQ4X6OAU.js +82 -0
  57. package/dist/chunk-ZSVCP6N3.js +83 -0
  58. package/dist/cli.cjs +142 -84
  59. package/dist/cli.js +101 -27
  60. package/dist/email-FKR4FXFI.js +5 -0
  61. package/dist/email-KVKVPNCL.cjs +58 -0
  62. package/dist/email-queue-CGZBVVAS.js +2 -0
  63. package/dist/email-queue-TPZWLTIV.cjs +23 -0
  64. package/dist/erp-config-enabled-CUMQ6N2V.cjs +11 -0
  65. package/dist/erp-config-enabled-T37X7T34.js +2 -0
  66. package/dist/erp-order-invoice-3YISSOWW.js +3 -0
  67. package/dist/erp-order-invoice-ZR5F2KTA.cjs +12 -0
  68. package/dist/event-order-defaults-6E543227.js +2 -0
  69. package/dist/event-order-defaults-DUGDVGJI.cjs +39 -0
  70. package/dist/event-order-notifications-5GSAOYVS.cjs +27 -0
  71. package/dist/event-order-notifications-IIIF23D5.js +10 -0
  72. package/dist/hooks.cjs +259 -132
  73. package/dist/hooks.d.cts +42 -1
  74. package/dist/hooks.d.ts +42 -1
  75. package/dist/hooks.js +234 -91
  76. package/dist/index.cjs +3884 -21070
  77. package/dist/index.d.cts +2255 -749
  78. package/dist/index.d.ts +2255 -749
  79. package/dist/index.js +1925 -19891
  80. package/dist/interpolate-WAKPN56R.cjs +11 -0
  81. package/dist/interpolate-WDUWDF3I.js +2 -0
  82. package/dist/layout-36UFTHC5.cjs +11 -0
  83. package/dist/layout-AOBN26XG.js +2 -0
  84. package/dist/migrations/1778660000004-CreateCustomerAndCustomerContacts.ts +99 -0
  85. package/dist/migrations/1778660000005-AddCustomerIdToVendorCustomers.ts +39 -0
  86. package/dist/migrations/1778660000006-UpdateVendorCustomersContactNullable.ts +36 -0
  87. package/dist/migrations/1778660000007-VendorOwnerCustomerContactsPermission.ts +39 -0
  88. package/dist/migrations/1779300000000-VendorOwnerContactsPermission.ts +39 -0
  89. package/dist/migrations/1779400000000-AddCustomerContactIdToOrders.ts +33 -0
  90. package/dist/migrations/1779400000000-ConvertStatusToEnum.ts +142 -0
  91. package/dist/migrations/1779400000000-SeedDefaultCurrencies.ts +33 -0
  92. package/dist/migrations/1779500000000-VendorRolesAndPermissions.ts +102 -0
  93. package/dist/migrations/1779600000000-VendorRegistrationProfile.ts +52 -0
  94. package/dist/migrations/1779700000000-CollectionIsCatalog.ts +22 -0
  95. package/dist/migrations/1779710000000-CollectionPlatformCatalog.ts +60 -0
  96. package/dist/migrations/1779720000000-ProductCategoryPlatformCatalog.ts +157 -0
  97. package/dist/migrations/1779800000000-EventsAndEventProducts.ts +173 -0
  98. package/dist/migrations/1779810000000-BackfillVendorRoleEventsPermissions.ts +38 -0
  99. package/dist/migrations/1779820000000-FixProductsCategoryForeignKey.ts +70 -0
  100. package/dist/migrations/1779830000000-ProductTitleColumn.ts +32 -0
  101. package/dist/migrations/1779840000000-EventAboutAndVisibilityFields.ts +34 -0
  102. package/dist/migrations/1779850000000-EventCityCountrySplit.ts +43 -0
  103. package/dist/migrations/1779860000000-CreateCombos.ts +140 -0
  104. package/dist/migrations/1780100000000-OrderAttendeesEventId.ts +55 -0
  105. package/dist/migrations/1780110000000-EventContactFormId.ts +49 -0
  106. package/dist/migrations/1780130000000-ProductVariants.ts +54 -0
  107. package/dist/migrations/1780140000000-EventManagementEnhancements.ts +44 -0
  108. package/dist/migrations/1780160000000-EventRegistrationGstProductContactForm.ts +79 -0
  109. package/dist/migrations/1780170000000-ComboCurrencyPrices.ts +20 -0
  110. package/dist/migrations/1781110000000-VendorOwnerCombosPermission.ts +38 -0
  111. package/dist/migrations/1781120000000-RefundPoliciesAndRequests.ts +225 -0
  112. package/dist/migrations/1781130000000-CreateAttendees.ts +44 -0
  113. package/dist/migrations/1781140000000-EventOrderNotifications.ts +29 -0
  114. package/dist/migrations/1781150000000-EventNamedLists.ts +28 -0
  115. package/dist/migrations/1781160000000-RefundPolicyEventTiers.ts +27 -0
  116. package/dist/migrations/1781170000000-EventRefundPolicy.ts +42 -0
  117. package/dist/migrations/1781180000000-RemoveCartItemsUniqueConstraint.ts +15 -0
  118. package/dist/migrations/1781190000000-RenameProductTaxesToProductConfig.ts +84 -0
  119. package/dist/migrations/1781200000000-VendorOwnerProductConfigPermissions.ts +51 -0
  120. package/dist/migrations/1781210000000-VendorRoleProductConfigPermissions.ts +46 -0
  121. package/dist/migrations/helpers/vendor-role-seed.ts +243 -0
  122. package/dist/paid-order-erp-3USEG6WL.js +3 -0
  123. package/dist/paid-order-erp-FSQ3GSXE.cjs +12 -0
  124. package/dist/pg-boss-service-4XF2TQ2E.js +2 -0
  125. package/dist/pg-boss-service-L6MTF4EQ.cjs +15 -0
  126. package/dist/razorpay-KLO2KNCB.js +49 -0
  127. package/dist/razorpay-U23LGY72.cjs +56 -0
  128. package/dist/rbac-debug-db-D7ME7QUY.cjs +35 -0
  129. package/dist/rbac-debug-db-MTCTVNNM.js +33 -0
  130. package/dist/sms-defaults-2NL24H57.js +2 -0
  131. package/dist/sms-defaults-KCOPKQBA.cjs +19 -0
  132. package/dist/stripe-4FD27QVJ.js +51 -0
  133. package/dist/stripe-VU6OEG2D.cjs +57 -0
  134. package/dist/theme.cjs +31 -59
  135. package/dist/theme.d.cts +78 -3
  136. package/dist/theme.d.ts +78 -3
  137. package/dist/theme.js +23 -20
  138. package/dist/vendor-invite-status-77O7ZQ3U.cjs +19 -0
  139. package/dist/vendor-invite-status-PWJ3DE76.js +2 -0
  140. package/dist/whatsapp-queue-3DXA4MXU.js +2 -0
  141. package/dist/whatsapp-queue-KG65MXD5.cjs +15 -0
  142. package/package.json +147 -141
  143. package/dist/admin.cjs.map +0 -1
  144. package/dist/admin.js.map +0 -1
  145. package/dist/api.cjs.map +0 -1
  146. package/dist/api.js.map +0 -1
  147. package/dist/auth.cjs.map +0 -1
  148. package/dist/auth.js.map +0 -1
  149. package/dist/cli.cjs.map +0 -1
  150. package/dist/cli.js.map +0 -1
  151. package/dist/config-DJ5CmQvS.d.cts +0 -8
  152. package/dist/config-DJ5CmQvS.d.ts +0 -8
  153. package/dist/hooks.cjs.map +0 -1
  154. package/dist/hooks.js.map +0 -1
  155. package/dist/index-DWd5Gjc4.d.ts +0 -582
  156. package/dist/index-rZTnpK7y.d.cts +0 -582
  157. package/dist/index.cjs.map +0 -1
  158. package/dist/index.js.map +0 -1
  159. package/dist/theme.cjs.map +0 -1
  160. package/dist/theme.js.map +0 -1
  161. package/dist/types-DCVaXlPV.d.cts +0 -78
  162. package/dist/types-DCVaXlPV.d.ts +0 -78
  163. package/dist/vendor-scope-DTYhJk_I.d.cts +0 -129
  164. package/dist/vendor-scope-DTYhJk_I.d.ts +0 -129
package/README.md CHANGED
@@ -694,17 +694,33 @@ The CSS variables are injected by the admin layout at runtime. Your website's ow
694
694
  | `NEXTAUTH_URL` | Yes | App base URL |
695
695
  | `STORAGE_TYPE` | No | `s3` or `local` (default: local) |
696
696
  | `AWS_BUCKET_NAME` | If S3 | S3 bucket name |
697
- | `AWS_REGION` | If S3/SES | AWS region |
698
- | `AWS_ACCESS_KEY_ID` | If S3/SES | AWS access key |
699
- | `AWS_SECRET_ACCESS_KEY` | If S3/SES | AWS secret key |
697
+ | `AWS_REGION` | If S3 | Default AWS region |
698
+ | `AWS_SES_REGION` | If `SMTP_TYPE=AWS` | SES region for email (required when `AWS_REGION` has no SMTP, e.g. use `ap-south-1` if `AWS_REGION=ap-south-2`) |
699
+ | `AWS_ACCESS_KEY_ID` | If S3 / SES API | IAM access key (not SES SMTP username) |
700
+ | `AWS_SECRET_ACCESS_KEY` | If S3 / SES API | IAM secret key (not SES SMTP password) |
700
701
  | `SMTP_TYPE` | No | `SMTP`, `AWS`, or `GMAIL` |
701
- | `SMTP_FROM` | If email | Sender email |
702
+ | `SMTP_FROM` | If email | Sender email (must be verified in SES) |
702
703
  | `SMTP_TO` | If email | Default recipient |
703
- | `SMTP_USER` | If SMTP | SMTP username |
704
- | `SMTP_PASSWORD` | If SMTP | SMTP password |
704
+ | `SMTP_USER` | If SMTP / `SMTP_TYPE=AWS` | SMTP username; for AWS use **SES SMTP credentials** from the SES console |
705
+ | `SMTP_PASSWORD` | If SMTP / `SMTP_TYPE=AWS` | SMTP password; for AWS use the SES SMTP password (not the IAM secret) |
706
+ | `SMTP_HOST` | No | Override SMTP host (optional for `AWS`; default is SES SMTP endpoint) |
707
+ | `SMTP_PORT` | No | Default `587` for AWS SES SMTP |
705
708
  | `GOOGLE_ANALYTICS_PRIVATE_KEY` | If analytics | GA service account key |
706
709
  | `GOOGLE_ANALYTICS_CLIENT_EMAIL` | If analytics | GA service account email |
707
710
  | `GOOGLE_ANALYTICS_VIEW_ID` | If analytics | GA property/view ID |
711
+ | `WHATSAPP_ACCESS_TOKEN` | If WhatsApp notifications | Meta Cloud API permanent/system user token |
712
+ | `WHATSAPP_PHONE_NUMBER_ID` | If WhatsApp notifications | WhatsApp Business phone number ID from Meta developer console |
713
+ | `WHATSAPP_API_VERSION` | No | Graph API version (default `v21.0`) |
714
+
715
+ ### Event order notifications
716
+
717
+ When an order is created (storefront checkout or admin **Place order**) and it includes products linked to an event via **event_products**, the core can notify the customer on enabled channels:
718
+
719
+ 1. **Platform admin** — Plugins → **Email** and **WhatsApp**: configure global templates (`GET/PUT /api/message-templates/event-order`). WhatsApp requires a [Meta-approved template](https://developers.facebook.com/docs/whatsapp/business-management-api/message-templates) name matching `externalTemplateRef` (default `event_order_placed`).
720
+ 2. **Vendor** — Event edit → **Order notifications**: enable notifications, pick **Email** / **WhatsApp**, optionally override templates. API: `GET/PUT /api/events/:id/notifications`, test via `POST /api/events/:id/notifications/test`.
721
+ 3. **Consumer app** — Register `whatsappPlugin()` and `emailPlugin()` in `getCms()`, plus optional `queuePlugin` for async sends. Call `dispatchEventOrderNotifications(orderId, deps)` is wired automatically from storefront checkout and admin order create.
722
+
723
+ Template variables: `{{eventName}}`, `{{orderNumber}}`, `{{customerName}}`, `{{customerEmail}}`, `{{customerPhone}}`, `{{orderTotal}}`, `{{currency}}`, `{{venue}}`, `{{startDate}}`, `{{productNames}}`.
708
724
 
709
725
  ## Development
710
726
 
@@ -0,0 +1,426 @@
1
+ import { __name } from './chunk-SHUYVCID.js';
2
+
3
+ // src/admin/pages/OrderTrackPage.ts
4
+ function renderOrderTrackingPage(order) {
5
+ const contact = order.contact;
6
+ const items = order.items ?? [];
7
+ const currency = String(order.currency ?? "INR");
8
+ function formatMoney(amount) {
9
+ return new Intl.NumberFormat("en-IN", {
10
+ style: "currency",
11
+ currency,
12
+ minimumFractionDigits: 2
13
+ }).format(Number(amount));
14
+ }
15
+ __name(formatMoney, "formatMoney");
16
+ function formatDate(dateStr) {
17
+ return new Date(dateStr).toLocaleDateString("en-IN", {
18
+ year: "numeric",
19
+ month: "long",
20
+ day: "numeric"
21
+ });
22
+ }
23
+ __name(formatDate, "formatDate");
24
+ const statusConfig = {
25
+ pending: {
26
+ label: "Order Placed",
27
+ color: "#f59e0b",
28
+ step: 1
29
+ },
30
+ confirmed: {
31
+ label: "Confirmed",
32
+ color: "#3b82f6",
33
+ step: 2
34
+ },
35
+ processing: {
36
+ label: "Processing",
37
+ color: "#8b5cf6",
38
+ step: 3
39
+ },
40
+ completed: {
41
+ label: "Completed",
42
+ color: "#10b981",
43
+ step: 4
44
+ },
45
+ cancelled: {
46
+ label: "Cancelled",
47
+ color: "#ef4444",
48
+ step: 0
49
+ }
50
+ };
51
+ const status = String(order.status ?? "pending");
52
+ const sc = statusConfig[status] ?? {
53
+ label: status,
54
+ color: "#6b7280",
55
+ step: 1
56
+ };
57
+ const isCancelled = status === "cancelled";
58
+ const steps = [
59
+ {
60
+ label: "Order Placed",
61
+ icon: "\u{1F4CB}"
62
+ },
63
+ {
64
+ label: "Confirmed",
65
+ icon: "\u2705"
66
+ },
67
+ {
68
+ label: "Processing",
69
+ icon: "\u2699\uFE0F"
70
+ },
71
+ {
72
+ label: "Completed",
73
+ icon: "\u{1F389}"
74
+ }
75
+ ];
76
+ const stepsHtml = isCancelled ? `<div class="cancelled-banner">Order Cancelled</div>` : steps.map((s, i) => {
77
+ const stepNum = i + 1;
78
+ const isActive = sc.step === stepNum;
79
+ const isDone = sc.step > stepNum;
80
+ return `
81
+ <div class="step ${isDone ? "done" : ""} ${isActive ? "active" : ""}">
82
+ <div class="step-circle">${isDone ? "\u2713" : s.icon}</div>
83
+ <div class="step-label">${s.label}</div>
84
+ </div>
85
+ ${stepNum < 4 ? `<div class="step-line ${sc.step > stepNum ? "done" : ""}"></div>` : ""}
86
+ `;
87
+ }).join("");
88
+ const itemsHtml = items.map((item) => {
89
+ const name = item.product?.collection?.name ?? item.product?.name ?? `Product #${item.id}`;
90
+ const sku = item.product?.sku ?? "\u2014";
91
+ return `
92
+ <div class="item-row">
93
+ <div class="item-avatar">${name.charAt(0).toUpperCase()}</div>
94
+ <div class="item-info">
95
+ <div class="item-name">${name}</div>
96
+ <div class="item-sku">SKU: ${sku}</div>
97
+ </div>
98
+ <div class="item-qty">\xD7${item.quantity}</div>
99
+ <div class="item-price">${formatMoney(Number(item.unitPrice))}</div>
100
+ </div>
101
+ `;
102
+ }).join("");
103
+ const discountRow = Number(order.discount) > 0 ? `<div class="summary-row discount"><span>Discount</span><span>\u2212${formatMoney(Number(order.discount))}</span></div>` : "";
104
+ const taxRow = Number(order.tax) > 0 ? `<div class="summary-row"><span>Tax</span><span>${formatMoney(Number(order.tax))}</span></div>` : "";
105
+ return `<!DOCTYPE html>
106
+ <html lang="en">
107
+ <head>
108
+ <meta charset="UTF-8">
109
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
110
+ <title>Order ${order.orderNumber} \u2014 Tracking</title>
111
+ <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=DM+Sans:wght@400;500;600&display=swap" rel="stylesheet">
112
+ <style>
113
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
114
+
115
+ :root {
116
+ --accent: ${sc.color};
117
+ --bg: #f8f7f4;
118
+ --card: #ffffff;
119
+ --text: #1a1a2e;
120
+ --muted: #6b7280;
121
+ --border: #e5e7eb;
122
+ --radius: 16px;
123
+ }
124
+
125
+ body {
126
+ font-family: 'DM Sans', sans-serif;
127
+ background: var(--bg);
128
+ color: var(--text);
129
+ min-height: 100vh;
130
+ padding: 24px 16px 48px;
131
+ }
132
+
133
+ .container { max-width: 640px; margin: 0 auto; }
134
+
135
+ /* Header */
136
+ .header {
137
+ text-align: center;
138
+ margin-bottom: 32px;
139
+ animation: fadeDown 0.5s ease both;
140
+ }
141
+ .header-eyebrow {
142
+ font-size: 12px;
143
+ font-weight: 600;
144
+ letter-spacing: 0.15em;
145
+ text-transform: uppercase;
146
+ color: var(--accent);
147
+ margin-bottom: 8px;
148
+ }
149
+ .header h1 {
150
+ font-family: 'Playfair Display', serif;
151
+ font-size: clamp(26px, 6vw, 36px);
152
+ color: var(--text);
153
+ line-height: 1.2;
154
+ }
155
+ .header-meta {
156
+ font-size: 13px;
157
+ color: var(--muted);
158
+ margin-top: 6px;
159
+ }
160
+
161
+ /* Status badge */
162
+ .status-badge {
163
+ display: inline-flex;
164
+ align-items: center;
165
+ gap: 6px;
166
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
167
+ color: var(--accent);
168
+ border: 1.5px solid color-mix(in srgb, var(--accent) 30%, transparent);
169
+ border-radius: 100px;
170
+ padding: 5px 14px;
171
+ font-size: 13px;
172
+ font-weight: 600;
173
+ margin-top: 14px;
174
+ }
175
+ .status-dot {
176
+ width: 7px; height: 7px;
177
+ border-radius: 50%;
178
+ background: var(--accent);
179
+ animation: pulse 2s ease infinite;
180
+ }
181
+
182
+ /* Card */
183
+ .card {
184
+ background: var(--card);
185
+ border-radius: var(--radius);
186
+ border: 1px solid var(--border);
187
+ overflow: hidden;
188
+ margin-bottom: 16px;
189
+ animation: fadeUp 0.5s ease both;
190
+ }
191
+ .card:nth-child(2) { animation-delay: 0.08s; }
192
+ .card:nth-child(3) { animation-delay: 0.16s; }
193
+ .card:nth-child(4) { animation-delay: 0.24s; }
194
+
195
+ .card-header {
196
+ display: flex; align-items: center; gap: 10px;
197
+ padding: 16px 20px;
198
+ border-bottom: 1px solid var(--border);
199
+ background: #fafafa;
200
+ }
201
+ .card-header-icon {
202
+ width: 32px; height: 32px;
203
+ border-radius: 8px;
204
+ background: color-mix(in srgb, var(--accent) 12%, transparent);
205
+ display: flex; align-items: center; justify-content: center;
206
+ font-size: 15px;
207
+ }
208
+ .card-title {
209
+ font-size: 13px;
210
+ font-weight: 600;
211
+ letter-spacing: 0.05em;
212
+ text-transform: uppercase;
213
+ color: var(--muted);
214
+ }
215
+ .card-body { padding: 20px; }
216
+
217
+ /* Progress tracker */
218
+ .progress-track {
219
+ display: flex;
220
+ align-items: flex-start;
221
+ justify-content: space-between;
222
+ padding: 8px 0;
223
+ overflow-x: auto;
224
+ }
225
+ .step {
226
+ display: flex; flex-direction: column; align-items: center;
227
+ gap: 8px; min-width: 60px;
228
+ }
229
+ .step-circle {
230
+ width: 44px; height: 44px;
231
+ border-radius: 50%;
232
+ border: 2px solid var(--border);
233
+ background: white;
234
+ display: flex; align-items: center; justify-content: center;
235
+ font-size: 18px;
236
+ color: var(--muted);
237
+ transition: all 0.3s;
238
+ }
239
+ .step.done .step-circle {
240
+ background: color-mix(in srgb, var(--accent) 15%, white);
241
+ border-color: var(--accent);
242
+ color: var(--accent);
243
+ font-size: 16px;
244
+ font-weight: 700;
245
+ }
246
+ .step.active .step-circle {
247
+ background: var(--accent);
248
+ border-color: var(--accent);
249
+ color: white;
250
+ box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 20%, transparent);
251
+ }
252
+ .step-label {
253
+ font-size: 11px; font-weight: 500; color: var(--muted);
254
+ text-align: center; line-height: 1.3;
255
+ }
256
+ .step.active .step-label, .step.done .step-label { color: var(--accent); font-weight: 600; }
257
+ .step-line {
258
+ flex: 1; height: 2px;
259
+ background: var(--border);
260
+ margin-top: 22px; min-width: 20px;
261
+ transition: background 0.3s;
262
+ }
263
+ .step-line.done { background: var(--accent); }
264
+ .cancelled-banner {
265
+ text-align: center; padding: 16px;
266
+ background: #fef2f2; color: #ef4444;
267
+ border-radius: 10px;
268
+ font-weight: 600; font-size: 15px;
269
+ }
270
+
271
+ /* Info grid */
272
+ .info-grid {
273
+ display: grid;
274
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
275
+ gap: 16px;
276
+ }
277
+ .info-item dt {
278
+ font-size: 11px; font-weight: 600;
279
+ text-transform: uppercase; letter-spacing: 0.08em;
280
+ color: var(--muted); margin-bottom: 4px;
281
+ }
282
+ .info-item dd { font-size: 14px; font-weight: 500; color: var(--text); }
283
+
284
+ /* Customer */
285
+ .customer-row {
286
+ display: flex; align-items: center; gap: 14px;
287
+ }
288
+ .customer-avatar {
289
+ width: 46px; height: 46px; border-radius: 50%;
290
+ background: color-mix(in srgb, var(--accent) 15%, #f3f4f6);
291
+ color: var(--accent);
292
+ display: flex; align-items: center; justify-content: center;
293
+ font-family: 'Playfair Display', serif;
294
+ font-size: 18px; font-weight: 700; flex-shrink: 0;
295
+ }
296
+ .customer-name { font-size: 15px; font-weight: 600; }
297
+ .customer-email { font-size: 13px; color: var(--muted); margin-top: 2px; }
298
+
299
+ /* Items */
300
+ .item-row {
301
+ display: flex; align-items: center; gap: 12px;
302
+ padding: 12px 0;
303
+ border-bottom: 1px solid var(--border);
304
+ }
305
+ .item-row:last-child { border-bottom: none; padding-bottom: 0; }
306
+ .item-row:first-child { padding-top: 0; }
307
+ .item-avatar {
308
+ width: 40px; height: 40px; border-radius: 10px;
309
+ background: linear-gradient(135deg, color-mix(in srgb, var(--accent) 20%, #e5e7eb), color-mix(in srgb, var(--accent) 5%, #f9fafb));
310
+ color: var(--accent);
311
+ display: flex; align-items: center; justify-content: center;
312
+ font-size: 16px; font-weight: 700; flex-shrink: 0;
313
+ }
314
+ .item-info { flex: 1; min-width: 0; }
315
+ .item-name { font-size: 14px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
316
+ .item-sku { font-size: 12px; color: var(--muted); margin-top: 2px; }
317
+ .item-qty { font-size: 13px; color: var(--muted); min-width: 28px; text-align: center; }
318
+ .item-price { font-size: 14px; font-weight: 600; min-width: 80px; text-align: right; }
319
+
320
+ /* Summary */
321
+ .summary-row {
322
+ display: flex; justify-content: space-between;
323
+ font-size: 14px; padding: 7px 0; color: var(--muted);
324
+ }
325
+ .summary-row.discount { color: #10b981; }
326
+ .summary-row.total {
327
+ font-size: 17px; font-weight: 700; color: var(--text);
328
+ border-top: 2px solid var(--border); margin-top: 6px; padding-top: 14px;
329
+ }
330
+
331
+ /* Footer */
332
+ .footer {
333
+ text-align: center; margin-top: 32px;
334
+ font-size: 12px; color: #9ca3af;
335
+ animation: fadeUp 0.5s ease 0.4s both;
336
+ }
337
+
338
+ /* Animations */
339
+ @keyframes fadeDown {
340
+ from { opacity: 0; transform: translateY(-16px); }
341
+ to { opacity: 1; transform: translateY(0); }
342
+ }
343
+ @keyframes fadeUp {
344
+ from { opacity: 0; transform: translateY(16px); }
345
+ to { opacity: 1; transform: translateY(0); }
346
+ }
347
+ @keyframes pulse {
348
+ 0%, 100% { opacity: 1; }
349
+ 50% { opacity: 0.4; }
350
+ }
351
+ </style>
352
+ </head>
353
+ <body>
354
+ <div class="container">
355
+
356
+ <!-- Header -->
357
+ <div class="header">
358
+ <div class="header-eyebrow">Order Tracking</div>
359
+ <h1>${String(order.orderNumber)}</h1>
360
+ <div class="header-meta">Placed on ${formatDate(String(order.createdAt))}</div>
361
+ <div><span class="status-badge"><span class="status-dot"></span>${sc.label}</span></div>
362
+ </div>
363
+
364
+ <!-- Progress -->
365
+ <div class="card">
366
+ <div class="card-header">
367
+ <div class="card-header-icon">\u{1F69A}</div>
368
+ <div class="card-title">Order Progress</div>
369
+ </div>
370
+ <div class="card-body">
371
+ <div class="progress-track">${stepsHtml}</div>
372
+ </div>
373
+ </div>
374
+
375
+ ${contact ? `
376
+ <!-- Customer -->
377
+ <div class="card">
378
+ <div class="card-header">
379
+ <div class="card-header-icon">\u{1F464}</div>
380
+ <div class="card-title">Customer</div>
381
+ </div>
382
+ <div class="card-body">
383
+ <div class="customer-row">
384
+ <div class="customer-avatar">${String(contact.name ?? "?").charAt(0).toUpperCase()}</div>
385
+ <div>
386
+ <div class="customer-name">${String(contact.name ?? "\u2014")}</div>
387
+ <div class="customer-email">${String(contact.email ?? "\u2014")}</div>
388
+ ${contact.phone ? `<div class="customer-email">${String(contact.phone)}</div>` : ""}
389
+ </div>
390
+ </div>
391
+ </div>
392
+ </div>` : ""}
393
+
394
+ <!-- Items -->
395
+ <div class="card">
396
+ <div class="card-header">
397
+ <div class="card-header-icon">\u{1F4E6}</div>
398
+ <div class="card-title">Items (${items.length})</div>
399
+ </div>
400
+ <div class="card-body">
401
+ ${items.length === 0 ? '<p style="color:var(--muted);font-size:14px;">No items</p>' : itemsHtml}
402
+ </div>
403
+ </div>
404
+
405
+ <!-- Summary -->
406
+ <div class="card">
407
+ <div class="card-header">
408
+ <div class="card-header-icon">\u{1F9FE}</div>
409
+ <div class="card-title">Order Summary</div>
410
+ </div>
411
+ <div class="card-body">
412
+ <div class="summary-row"><span>Subtotal</span><span>${formatMoney(Number(order.subtotal))}</span></div>
413
+ ${discountRow}
414
+ ${taxRow}
415
+ <div class="summary-row total"><span>Total</span><span>${formatMoney(Number(order.total))}</span></div>
416
+ </div>
417
+ </div>
418
+
419
+ <div class="footer">Scan the QR code on your receipt to return to this page.</div>
420
+ </div>
421
+ </body>
422
+ </html>`;
423
+ }
424
+ __name(renderOrderTrackingPage, "renderOrderTrackingPage");
425
+
426
+ export { renderOrderTrackingPage };