@burdenoff/microfe-billing 2026.614.1 → 2026.623.2

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 (135) hide show
  1. package/dist/billing/BillingAdminRoutes.js +7 -6
  2. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  3. package/dist/billing/BillingRoot.js +1 -1
  4. package/dist/billing/BillingRoot.js.map +1 -1
  5. package/dist/billing/modules/addons/components/FloatingCartDrawer.js +22 -22
  6. package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
  7. package/dist/billing/modules/addons/pages/AddonCreatePage.js +31 -31
  8. package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
  9. package/dist/billing/modules/addons/pages/AddonDetailPage.js +39 -39
  10. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonEditPage.js +29 -29
  12. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonsListPage.js +91 -91
  14. package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
  15. package/dist/billing/modules/analytics/pages/AnalyticsPage.js +40 -40
  16. package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
  17. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +115 -115
  18. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  19. package/dist/billing/modules/billing/pages/InvoicesListPage.js +75 -72
  20. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  21. package/dist/billing/modules/billing/pages/RefundsPage.js +126 -126
  22. package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/pages/UserRefundsPage.js +45 -45
  24. package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
  25. package/dist/billing/modules/checkout/components/BillingAccountSelector.js +25 -25
  26. package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
  27. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +31 -35
  28. package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
  29. package/dist/billing/modules/checkout/components/CreditsAmountInput.js +9 -9
  30. package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
  31. package/dist/billing/modules/checkout/components/OrderSummary.js +31 -31
  32. package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
  33. package/dist/billing/modules/checkout/components/PromoCodeInput.js +8 -8
  34. package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
  35. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
  36. package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
  37. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +9 -9
  38. package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
  39. package/dist/billing/modules/checkout/components/StripePaymentForm.js +21 -21
  40. package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
  41. package/dist/billing/modules/checkout/components/SubscriptionSelector.js +11 -11
  42. package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
  43. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +31 -31
  44. package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
  45. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +23 -23
  46. package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
  47. package/dist/billing/modules/checkout/pages/CheckoutPage.js +91 -91
  48. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  49. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +62 -62
  50. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  51. package/dist/billing/modules/coupons/pages/CouponsListPage.js +72 -60
  52. package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
  53. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +66 -66
  54. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  55. package/dist/billing/modules/coupons/pages/EditCouponPage.js +46 -46
  56. package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
  57. package/dist/billing/modules/credits/pages/AdminCreditsPage.js +19 -19
  58. package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
  59. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +37 -37
  60. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  61. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +90 -90
  62. package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
  63. package/dist/billing/modules/credits/pages/CreditsPage.js +41 -41
  64. package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
  65. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +31 -31
  66. package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
  67. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +35 -35
  68. package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
  69. package/dist/billing/modules/dashboard/components/BillingAccountCard.js +24 -24
  70. package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
  71. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +99 -99
  72. package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
  73. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +23 -23
  74. package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
  75. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +22 -22
  76. package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
  77. package/dist/billing/modules/dashboard/pages/OverviewPage.js +58 -58
  78. package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
  79. package/dist/billing/modules/discounts/pages/DiscountsListPage.js +126 -126
  80. package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
  81. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +83 -79
  82. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  83. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +16 -16
  84. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  85. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +40 -40
  86. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  87. package/dist/billing/modules/plans/pages/PlanCreatePage.js +35 -35
  88. package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
  89. package/dist/billing/modules/plans/pages/PlanDetailPage.js +57 -57
  90. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  91. package/dist/billing/modules/plans/pages/PlanEditPage.js +35 -35
  92. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  93. package/dist/billing/modules/plans/pages/PlansBrowsePage.js +57 -57
  94. package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
  95. package/dist/billing/modules/plans/pages/PlansListPage.js +52 -52
  96. package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
  97. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +40 -39
  98. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  99. package/dist/billing/modules/promotions/pages/PromotionsListPage.js +22 -19
  100. package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
  101. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +34 -34
  102. package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
  103. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +47 -47
  104. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  105. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +39 -39
  106. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  107. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +52 -52
  108. package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
  109. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +23 -23
  110. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  111. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -246
  112. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  113. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +88 -85
  114. package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
  115. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +51 -51
  116. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  117. package/dist/billing/modules/usage/pages/UsagePage.js +206 -196
  118. package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
  119. package/dist/billing/shared/components/ServerError.js +4 -4
  120. package/dist/billing/shared/components/ServerError.js.map +1 -1
  121. package/dist/billing/shared/components/StatCard.js +11 -33
  122. package/dist/billing/shared/components/StatCard.js.map +1 -1
  123. package/dist/billing/shared/ui/Card.js +15 -0
  124. package/dist/billing/shared/ui/Card.js.map +1 -0
  125. package/dist/billing/shared/ui/MetricCard.js +44 -0
  126. package/dist/billing/shared/ui/MetricCard.js.map +1 -0
  127. package/dist/billing/shared/ui/ProgressBar.js +30 -0
  128. package/dist/billing/shared/ui/ProgressBar.js.map +1 -0
  129. package/dist/billing/shared/ui/SectionHeader.js +2 -0
  130. package/dist/billing/shared/ui/StatusPill.js +28 -0
  131. package/dist/billing/shared/ui/StatusPill.js.map +1 -0
  132. package/dist/billing/shared/ui/index.js +5 -0
  133. package/dist/billing/shared/utils/tokens.js +13 -30
  134. package/dist/billing/shared/utils/tokens.js.map +1 -1
  135. package/package.json +1 -1
@@ -42,7 +42,8 @@ var f = [
42
42
  children: [/* @__PURE__ */ u("button", {
43
43
  type: "button",
44
44
  onClick: () => p("/promotions"),
45
- className: "p-2 rounded-lg hover:bg-muted text-muted-foreground",
45
+ "aria-label": "Back to promotions",
46
+ className: "p-2 rounded-button hover:bg-bg-sunken text-text-muted hover:text-text-primary transition-colors",
46
47
  children: /* @__PURE__ */ u(o, { className: "size-5" })
47
48
  }), /* @__PURE__ */ u(t, {
48
49
  title: "Create Promotion",
@@ -78,15 +79,15 @@ var f = [
78
79
  className: "space-y-5",
79
80
  children: [
80
81
  /* @__PURE__ */ d("div", {
81
- className: "bg-card border border-border rounded-xl p-5 space-y-4",
82
+ className: "bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]",
82
83
  children: [
83
84
  /* @__PURE__ */ u("h3", {
84
- className: "font-semibold text-foreground",
85
+ className: "font-semibold text-text-primary",
85
86
  children: "Promotion Details"
86
87
  }),
87
88
  /* @__PURE__ */ d("div", { children: [
88
89
  /* @__PURE__ */ u("label", {
89
- className: "block text-sm font-medium text-foreground mb-1",
90
+ className: "block text-sm font-medium text-text-primary mb-1",
90
91
  children: "Name *"
91
92
  }),
92
93
  /* @__PURE__ */ u("input", {
@@ -94,35 +95,35 @@ var f = [
94
95
  value: x.name,
95
96
  onChange: (e) => C("name", e.target.value),
96
97
  placeholder: "e.g. June Launch Offer",
97
- className: `w-full px-3 py-2 rounded-lg border ${v.name ? "border-destructive" : "border-border"} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`
98
+ className: `w-full px-3 py-2 rounded-input border ${v.name ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
98
99
  }),
99
100
  v.name && /* @__PURE__ */ u("p", {
100
- className: "text-xs text-destructive mt-1",
101
+ className: "text-xs text-status-error-text mt-1",
101
102
  children: v.name
102
103
  })
103
104
  ] }),
104
105
  /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("label", {
105
- className: "block text-sm font-medium text-foreground mb-1",
106
+ className: "block text-sm font-medium text-text-primary mb-1",
106
107
  children: "Description"
107
108
  }), /* @__PURE__ */ u("textarea", {
108
109
  value: x.description,
109
110
  onChange: (e) => C("description", e.target.value),
110
111
  placeholder: "Subscribe in June and get 2 extra months free!",
111
112
  rows: 2,
112
- className: "w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
113
+ className: "w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
113
114
  })] })
114
115
  ]
115
116
  }),
116
117
  /* @__PURE__ */ d("div", {
117
- className: "bg-card border border-border rounded-xl p-5 space-y-4",
118
+ className: "bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]",
118
119
  children: [
119
120
  /* @__PURE__ */ u("h3", {
120
- className: "font-semibold text-foreground",
121
+ className: "font-semibold text-text-primary",
121
122
  children: "Bonus Configuration"
122
123
  }),
123
124
  /* @__PURE__ */ d("div", { children: [
124
125
  /* @__PURE__ */ u("label", {
125
- className: "block text-sm font-medium text-foreground mb-1",
126
+ className: "block text-sm font-medium text-text-primary mb-1",
126
127
  children: "Bonus Months *"
127
128
  }),
128
129
  /* @__PURE__ */ u("input", {
@@ -131,19 +132,19 @@ var f = [
131
132
  min: 1,
132
133
  max: 12,
133
134
  onChange: (e) => C("bonusMonths", parseInt(e.target.value) || 1),
134
- className: `w-32 px-3 py-2 rounded-lg border ${v.bonusMonths ? "border-destructive" : "border-border"} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`
135
+ className: `w-32 px-3 py-2 rounded-input border ${v.bonusMonths ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
135
136
  }),
136
137
  v.bonusMonths && /* @__PURE__ */ u("p", {
137
- className: "text-xs text-destructive mt-1",
138
+ className: "text-xs text-status-error-text mt-1",
138
139
  children: v.bonusMonths
139
140
  }),
140
141
  /* @__PURE__ */ d("div", {
141
- className: "mt-2 flex items-start gap-2 p-3 bg-primary/5 rounded-lg text-sm text-muted-foreground",
142
- children: [/* @__PURE__ */ u(c, { className: "size-4 shrink-0 mt-0.5 text-primary" }), /* @__PURE__ */ d("span", { children: [
142
+ className: "mt-2 flex items-start gap-2 p-3 bg-[var(--color-accent-soft)] rounded-lg text-sm text-text-muted",
143
+ children: [/* @__PURE__ */ u(c, { className: "size-4 shrink-0 mt-0.5 text-text-link" }), /* @__PURE__ */ d("span", { children: [
143
144
  "Users who subscribe during this window get",
144
145
  " ",
145
146
  /* @__PURE__ */ d("strong", {
146
- className: "text-foreground",
147
+ className: "text-text-primary",
147
148
  children: [
148
149
  x.bonusMonths,
149
150
  " free renewal",
@@ -157,7 +158,7 @@ var f = [
157
158
  ] }),
158
159
  /* @__PURE__ */ d("div", { children: [
159
160
  /* @__PURE__ */ u("label", {
160
- className: "block text-sm font-medium text-foreground mb-1",
161
+ className: "block text-sm font-medium text-text-primary mb-1",
161
162
  children: "Max Redemptions"
162
163
  }),
163
164
  /* @__PURE__ */ u("input", {
@@ -166,42 +167,42 @@ var f = [
166
167
  min: 1,
167
168
  onChange: (e) => C("maxRedemptions", e.target.value),
168
169
  placeholder: "Unlimited",
169
- className: "w-48 px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20"
170
+ className: "w-48 px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
170
171
  }),
171
172
  /* @__PURE__ */ u("p", {
172
- className: "text-xs text-muted-foreground mt-1",
173
+ className: "text-xs text-text-muted mt-1",
173
174
  children: "Leave empty for no cap"
174
175
  })
175
176
  ] })
176
177
  ]
177
178
  }),
178
179
  /* @__PURE__ */ d("div", {
179
- className: "bg-card border border-border rounded-xl p-5 space-y-4",
180
+ className: "bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]",
180
181
  children: [
181
182
  /* @__PURE__ */ u("h3", {
182
- className: "font-semibold text-foreground",
183
+ className: "font-semibold text-text-primary",
183
184
  children: "Active Window"
184
185
  }),
185
186
  /* @__PURE__ */ d("div", {
186
187
  className: "grid grid-cols-2 gap-4",
187
188
  children: [/* @__PURE__ */ d("div", { children: [
188
189
  /* @__PURE__ */ u("label", {
189
- className: "block text-sm font-medium text-foreground mb-1",
190
+ className: "block text-sm font-medium text-text-primary mb-1",
190
191
  children: "Start Date *"
191
192
  }),
192
193
  /* @__PURE__ */ u("input", {
193
194
  type: "date",
194
195
  value: x.startsAt,
195
196
  onChange: (e) => C("startsAt", e.target.value),
196
- className: `w-full px-3 py-2 rounded-lg border ${v.startsAt ? "border-destructive" : "border-border"} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`
197
+ className: `w-full px-3 py-2 rounded-input border ${v.startsAt ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
197
198
  }),
198
199
  v.startsAt && /* @__PURE__ */ u("p", {
199
- className: "text-xs text-destructive mt-1",
200
+ className: "text-xs text-status-error-text mt-1",
200
201
  children: v.startsAt
201
202
  })
202
203
  ] }), /* @__PURE__ */ d("div", { children: [
203
204
  /* @__PURE__ */ u("label", {
204
- className: "block text-sm font-medium text-foreground mb-1",
205
+ className: "block text-sm font-medium text-text-primary mb-1",
205
206
  children: "End Date *"
206
207
  }),
207
208
  /* @__PURE__ */ u("input", {
@@ -209,30 +210,30 @@ var f = [
209
210
  value: x.expiresAt,
210
211
  onChange: (e) => C("expiresAt", e.target.value),
211
212
  min: x.startsAt,
212
- className: `w-full px-3 py-2 rounded-lg border ${v.expiresAt ? "border-destructive" : "border-border"} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`
213
+ className: `w-full px-3 py-2 rounded-input border ${v.expiresAt ? "border-destructive" : "border-border-subtle"} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`
213
214
  }),
214
215
  v.expiresAt && /* @__PURE__ */ u("p", {
215
- className: "text-xs text-destructive mt-1",
216
+ className: "text-xs text-status-error-text mt-1",
216
217
  children: v.expiresAt
217
218
  })
218
219
  ] })]
219
220
  }),
220
221
  /* @__PURE__ */ u("p", {
221
- className: "text-xs text-muted-foreground",
222
+ className: "text-xs text-text-muted",
222
223
  children: "Only purchases made between these dates will receive the bonus months"
223
224
  })
224
225
  ]
225
226
  }),
226
227
  /* @__PURE__ */ d("div", {
227
- className: "bg-card border border-border rounded-xl p-5 space-y-4",
228
+ className: "bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]",
228
229
  children: [
229
230
  /* @__PURE__ */ u("h3", {
230
- className: "font-semibold text-foreground",
231
+ className: "font-semibold text-text-primary",
231
232
  children: "Scope"
232
233
  }),
233
234
  /* @__PURE__ */ d("div", { children: [
234
235
  /* @__PURE__ */ u("label", {
235
- className: "block text-sm font-medium text-foreground mb-2",
236
+ className: "block text-sm font-medium text-text-primary mb-2",
236
237
  children: "Applicable Products"
237
238
  }),
238
239
  /* @__PURE__ */ u("div", {
@@ -240,18 +241,18 @@ var f = [
240
241
  children: f.map((e) => /* @__PURE__ */ u("button", {
241
242
  type: "button",
242
243
  onClick: () => w(e),
243
- className: `px-3 py-1.5 rounded-lg border text-sm transition-colors ${x.applicableProductIds.includes(e) ? "border-primary bg-primary/10 text-primary font-medium" : "border-border hover:border-primary/50"}`,
244
+ className: `px-3 py-1.5 rounded-lg border text-sm transition-colors ${x.applicableProductIds.includes(e) ? "border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium" : "border-border-subtle hover:border-action-primary-bg/50"}`,
244
245
  children: e
245
246
  }, e))
246
247
  }),
247
248
  /* @__PURE__ */ u("p", {
248
- className: "text-xs text-muted-foreground mt-1",
249
+ className: "text-xs text-text-muted mt-1",
249
250
  children: x.applicableProductIds.length === 0 ? "No selection = applies to all products" : `Applies to: ${x.applicableProductIds.join(", ")}`
250
251
  })
251
252
  ] }),
252
253
  /* @__PURE__ */ d("div", { children: [
253
254
  /* @__PURE__ */ u("label", {
254
- className: "block text-sm font-medium text-foreground mb-1",
255
+ className: "block text-sm font-medium text-text-primary mb-1",
255
256
  children: "Plan IDs (optional)"
256
257
  }),
257
258
  /* @__PURE__ */ u("input", {
@@ -259,10 +260,10 @@ var f = [
259
260
  value: x.applicablePlanIds,
260
261
  onChange: (e) => C("applicablePlanIds", e.target.value),
261
262
  placeholder: "plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly",
262
- className: "w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
263
+ className: "w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]"
263
264
  }),
264
265
  /* @__PURE__ */ u("p", {
265
- className: "text-xs text-muted-foreground mt-1",
266
+ className: "text-xs text-text-muted mt-1",
266
267
  children: "Comma-separated. Leave empty to apply to all plans"
267
268
  })
268
269
  ] })
@@ -273,13 +274,13 @@ var f = [
273
274
  children: [/* @__PURE__ */ u("button", {
274
275
  type: "button",
275
276
  onClick: () => p("/promotions"),
276
- className: "px-5 py-2 rounded-lg border border-border text-foreground hover:bg-muted text-sm",
277
+ className: "px-5 py-2 rounded-button border border-border-subtle text-text-primary hover:bg-bg-sunken text-sm font-medium transition-colors",
277
278
  children: "Cancel"
278
279
  }), /* @__PURE__ */ d("button", {
279
280
  type: "submit",
280
281
  disabled: g,
281
- className: "px-5 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 text-sm disabled:opacity-50",
282
- children: [g ? /* @__PURE__ */ u("div", { className: "size-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ u(l, { className: "size-4" }), "Create Promotion"]
282
+ className: "px-5 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50",
283
+ children: [g ? /* @__PURE__ */ u("div", { className: "size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin" }) : /* @__PURE__ */ u(l, { className: "size-4" }), "Create Promotion"]
283
284
  })]
284
285
  })
285
286
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation({ refetchQueries: ['ListPromotions'] });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds.split(',').flatMap((s) => {\n const trimmed = s.trim();\n return trimmed ? [trimmed] : [];\n });\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-lg border ${errors.name ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.name && <p className=\"text-xs text-destructive mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Bonus Months *</label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-lg border ${errors.bonusMonths ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-destructive mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-primary/5 rounded-lg text-sm text-muted-foreground\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-primary\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-foreground\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">Start Date *</label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-lg border ${errors.startsAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-lg border ${errors.expiresAt ? 'border-destructive' : 'border-border'} bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-destructive mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-card border border-border rounded-xl p-5 space-y-4\">\n <h3 className=\"font-semibold text-foreground\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-primary bg-primary/10 text-primary font-medium'\n : 'border-border hover:border-primary/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-foreground mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-lg border border-border bg-background text-foreground placeholder:text-muted-foreground text-sm focus:outline-none focus:ring-2 focus:ring-primary/20\"\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-lg border border-border text-foreground hover:bg-muted text-sm\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2 text-sm disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-primary-foreground border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CreatePromotionPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/CreatePromotionPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { ArrowLeft, Save, Gift, Info } from 'lucide-react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { useCreatePromotionMutation } from '../../../../generated/global-operations';\n\nconst KNOWN_PRODUCTS = ['vibecontrols', 'workspaces', 'botlit', 'fluidgrids', 'healthybowl'];\n\ninterface FormData {\n name: string;\n description: string;\n startsAt: string;\n expiresAt: string;\n bonusMonths: number;\n maxRedemptions: string;\n applicableProductIds: string[];\n applicablePlanIds: string;\n}\n\nexport const CreatePromotionPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [createPromotion] = useCreatePromotionMutation({ refetchQueries: ['ListPromotions'] });\n const [isSubmitting, setIsSubmitting] = useState(false);\n const [errors, setErrors] = useState<Partial<Record<keyof FormData, string>>>({});\n\n const today = new Date().toISOString().split('T')[0];\n const [form, setForm] = useState<FormData>({\n name: '',\n description: '',\n startsAt: today,\n expiresAt: '',\n bonusMonths: 1,\n maxRedemptions: '',\n applicableProductIds: [],\n applicablePlanIds: '',\n });\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to create promotions.\" />;\n }\n\n const set = <K extends keyof FormData>(k: K, v: FormData[K]) =>\n setForm((prev) => ({ ...prev, [k]: v }));\n\n const toggleProduct = (pid: string) => {\n set(\n 'applicableProductIds',\n form.applicableProductIds.includes(pid)\n ? form.applicableProductIds.filter((p) => p !== pid)\n : [...form.applicableProductIds, pid]\n );\n };\n\n const validate = () => {\n const e: typeof errors = {};\n if (!form.name.trim()) e.name = 'Name is required';\n if (!form.startsAt) e.startsAt = 'Start date is required';\n if (!form.expiresAt) e.expiresAt = 'End date is required';\n if (form.expiresAt && form.expiresAt <= form.startsAt)\n e.expiresAt = 'End date must be after start date';\n if (form.bonusMonths < 1) e.bonusMonths = 'Must be at least 1';\n setErrors(e);\n return Object.keys(e).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n if (!validate()) return;\n setIsSubmitting(true);\n try {\n const planIds = form.applicablePlanIds.split(',').flatMap((s) => {\n const trimmed = s.trim();\n return trimmed ? [trimmed] : [];\n });\n\n await createPromotion({\n variables: {\n input: {\n name: form.name.trim(),\n description: form.description.trim() || undefined,\n startsAt: new Date(form.startsAt).toISOString(),\n expiresAt: new Date(form.expiresAt).toISOString(),\n bonusMonths: form.bonusMonths,\n maxRedemptions: form.maxRedemptions ? parseInt(form.maxRedemptions) : undefined,\n applicableProductIds: form.applicableProductIds,\n applicablePlanIds: planIds,\n },\n },\n });\n navigateTo('/promotions');\n } catch (err) {\n setErrors({ name: err instanceof Error ? err.message : 'Failed to create promotion' });\n } finally {\n setIsSubmitting(false);\n }\n };\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center gap-4\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n aria-label=\"Back to promotions\"\n className=\"p-2 rounded-button hover:bg-bg-sunken text-text-muted hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-5\" />\n </button>\n <PageHeader\n title=\"Create Promotion\"\n description=\"Offer bonus free renewal months to subscribers who sign up within the promotion window\"\n icon={<Gift className=\"size-6\" />}\n />\n </div>\n\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Basic info */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Promotion Details</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Name *</label>\n <input\n type=\"text\"\n value={form.name}\n onChange={(e) => set('name', e.target.value)}\n placeholder=\"e.g. June Launch Offer\"\n className={`w-full px-3 py-2 rounded-input border ${errors.name ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.name && <p className=\"text-xs text-status-error-text mt-1\">{errors.name}</p>}\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">Description</label>\n <textarea\n value={form.description}\n onChange={(e) => set('description', e.target.value)}\n placeholder=\"Subscribe in June and get 2 extra months free!\"\n rows={2}\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n </div>\n </div>\n\n {/* Bonus config */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Bonus Configuration</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Bonus Months *\n </label>\n <input\n type=\"number\"\n value={form.bonusMonths}\n min={1}\n max={12}\n onChange={(e) => set('bonusMonths', parseInt(e.target.value) || 1)}\n className={`w-32 px-3 py-2 rounded-input border ${errors.bonusMonths ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.bonusMonths && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.bonusMonths}</p>\n )}\n <div className=\"mt-2 flex items-start gap-2 p-3 bg-[var(--color-accent-soft)] rounded-lg text-sm text-text-muted\">\n <Info className=\"size-4 shrink-0 mt-0.5 text-text-link\" />\n <span>\n Users who subscribe during this window get{' '}\n <strong className=\"text-text-primary\">\n {form.bonusMonths} free renewal{form.bonusMonths !== 1 ? 's' : ''}\n </strong>{' '}\n after their first paid period — their quota resets each month as normal.\n </span>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Max Redemptions\n </label>\n <input\n type=\"number\"\n value={form.maxRedemptions}\n min={1}\n onChange={(e) => set('maxRedemptions', e.target.value)}\n placeholder=\"Unlimited\"\n className=\"w-48 px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">Leave empty for no cap</p>\n </div>\n </div>\n\n {/* Date window */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Active Window</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Start Date *\n </label>\n <input\n type=\"date\"\n value={form.startsAt}\n onChange={(e) => set('startsAt', e.target.value)}\n className={`w-full px-3 py-2 rounded-input border ${errors.startsAt ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.startsAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.startsAt}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">End Date *</label>\n <input\n type=\"date\"\n value={form.expiresAt}\n onChange={(e) => set('expiresAt', e.target.value)}\n min={form.startsAt}\n className={`w-full px-3 py-2 rounded-input border ${errors.expiresAt ? 'border-destructive' : 'border-border-subtle'} bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]`}\n />\n {errors.expiresAt && (\n <p className=\"text-xs text-status-error-text mt-1\">{errors.expiresAt}</p>\n )}\n </div>\n </div>\n <p className=\"text-xs text-text-muted\">\n Only purchases made between these dates will receive the bonus months\n </p>\n </div>\n\n {/* Scope */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card p-5 space-y-4 shadow-[var(--shadow-elevation-1)]\">\n <h3 className=\"font-semibold text-text-primary\">Scope</h3>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">\n Applicable Products\n </label>\n <div className=\"flex flex-wrap gap-2\">\n {KNOWN_PRODUCTS.map((pid) => (\n <button\n key={pid}\n type=\"button\"\n onClick={() => toggleProduct(pid)}\n className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${\n form.applicableProductIds.includes(pid)\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link font-medium'\n : 'border-border-subtle hover:border-action-primary-bg/50'\n }`}\n >\n {pid}\n </button>\n ))}\n </div>\n <p className=\"text-xs text-text-muted mt-1\">\n {form.applicableProductIds.length === 0\n ? 'No selection = applies to all products'\n : `Applies to: ${form.applicableProductIds.join(', ')}`}\n </p>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n Plan IDs (optional)\n </label>\n <input\n type=\"text\"\n value={form.applicablePlanIds}\n onChange={(e) => set('applicablePlanIds', e.target.value)}\n placeholder=\"plan-vibecontrols-pro-monthly, plan-vibecontrols-pro-yearly\"\n className=\"w-full px-3 py-2 rounded-input border border-border-subtle bg-bg-surface text-text-primary placeholder:text-text-muted text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n Comma-separated. Leave empty to apply to all plans\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex gap-3 justify-end\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/promotions')}\n className=\"px-5 py-2 rounded-button border border-border-subtle text-text-primary hover:bg-bg-sunken text-sm font-medium transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"px-5 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover flex items-center gap-2 text-sm font-medium transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n >\n {isSubmitting ? (\n <div className=\"size-4 border-2 border-action-primary-text border-t-transparent rounded-full animate-spin\" />\n ) : (\n <Save className=\"size-4\" />\n )}\n Create Promotion\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -23,16 +23,16 @@ var _ = () => {
23
23
  }
24
24
  }, A = /* @__PURE__ */ new Date(), j = (e) => e.isActive ? new Date(e.expiresAt) < A ? {
25
25
  text: "Expired",
26
- cls: "bg-destructive/10 text-destructive"
26
+ cls: "bg-status-error-bg-subtle text-status-error-text"
27
27
  } : new Date(e.startsAt) > A ? {
28
28
  text: "Upcoming",
29
- cls: "bg-primary/10 text-primary"
29
+ cls: "bg-[var(--color-accent-soft)] text-text-link"
30
30
  } : {
31
31
  text: "Live",
32
- cls: "bg-green-500/10 text-green-600"
32
+ cls: "bg-status-success-bg-subtle text-status-success-text"
33
33
  } : {
34
34
  text: "Inactive",
35
- cls: "bg-muted text-muted-foreground"
35
+ cls: "bg-bg-sunken text-text-muted"
36
36
  };
37
37
  return /* @__PURE__ */ g("div", {
38
38
  className: "max-w-5xl mx-auto p-4 sm:p-6 space-y-6",
@@ -45,40 +45,43 @@ var _ = () => {
45
45
  icon: /* @__PURE__ */ h(l, { className: "size-6" })
46
46
  }), /* @__PURE__ */ g("button", {
47
47
  onClick: () => _("/promotions/create"),
48
- className: "flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 text-sm font-medium shrink-0",
48
+ className: "flex items-center gap-2 px-4 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover text-sm font-medium shrink-0 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
49
49
  children: [/* @__PURE__ */ h(u, { className: "size-4" }), "New Promotion"]
50
50
  })]
51
51
  }),
52
52
  /* @__PURE__ */ h("div", {
53
53
  className: "flex items-center gap-3",
54
54
  children: /* @__PURE__ */ g("label", {
55
- className: "flex items-center gap-2 text-sm text-muted-foreground cursor-pointer select-none",
55
+ className: "flex items-center gap-2 text-sm text-text-muted cursor-pointer select-none",
56
56
  children: [/* @__PURE__ */ h("input", {
57
57
  type: "checkbox",
58
58
  checked: y,
59
59
  onChange: (e) => b(e.target.checked),
60
- className: "rounded border-border"
60
+ className: "rounded border-border-subtle"
61
61
  }), "Show inactive / expired"]
62
62
  })
63
63
  }),
64
64
  w ? /* @__PURE__ */ h("div", {
65
- className: "text-center py-16 text-muted-foreground",
65
+ className: "text-center py-16 text-text-muted",
66
66
  children: "Loading promotions…"
67
67
  }) : O.length === 0 ? /* @__PURE__ */ g("div", {
68
- className: "text-center py-16 border border-dashed border-border rounded-xl",
68
+ className: "flex flex-col items-center justify-center text-center py-16 border border-dashed border-border-subtle rounded-card bg-bg-surface",
69
69
  children: [
70
- /* @__PURE__ */ h(l, { className: "size-10 mx-auto mb-3 text-muted-foreground/50" }),
70
+ /* @__PURE__ */ h("div", {
71
+ className: "flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] mb-4",
72
+ children: /* @__PURE__ */ h(l, { className: "size-6 text-text-link" })
73
+ }),
71
74
  /* @__PURE__ */ h("p", {
72
- className: "text-muted-foreground font-medium",
75
+ className: "text-base font-semibold text-text-primary",
73
76
  children: "No promotions yet"
74
77
  }),
75
78
  /* @__PURE__ */ h("p", {
76
- className: "text-sm text-muted-foreground mt-1",
79
+ className: "text-sm text-text-muted mt-1",
77
80
  children: "Create one to offer bonus months to early subscribers"
78
81
  }),
79
82
  /* @__PURE__ */ h("button", {
80
83
  onClick: () => _("/promotions/create"),
81
- className: "mt-4 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm",
84
+ className: "mt-4 px-4 py-2 rounded-button bg-action-primary-bg text-action-primary-text text-sm font-medium transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
82
85
  children: "Create Promotion"
83
86
  })
84
87
  ]
@@ -87,14 +90,14 @@ var _ = () => {
87
90
  children: O.map((e) => {
88
91
  let { text: t, cls: n } = j(e), r = x === e.id;
89
92
  return /* @__PURE__ */ g("div", {
90
- className: "bg-card border border-border rounded-xl p-5 flex flex-col sm:flex-row sm:items-center gap-4",
93
+ className: "bg-bg-surface border border-border-subtle rounded-card p-5 flex flex-col sm:flex-row sm:items-center gap-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)]",
91
94
  children: [/* @__PURE__ */ g("div", {
92
95
  className: "flex-1 min-w-0",
93
96
  children: [
94
97
  /* @__PURE__ */ g("div", {
95
98
  className: "flex items-center gap-2 mb-1",
96
99
  children: [/* @__PURE__ */ h("span", {
97
- className: "font-semibold text-foreground truncate",
100
+ className: "font-semibold text-text-primary truncate",
98
101
  children: e.name
99
102
  }), /* @__PURE__ */ h("span", {
100
103
  className: `text-xs font-medium px-2 py-0.5 rounded-full ${n}`,
@@ -102,18 +105,18 @@ var _ = () => {
102
105
  })]
103
106
  }),
104
107
  e.description && /* @__PURE__ */ h("p", {
105
- className: "text-sm text-muted-foreground mb-2 line-clamp-1",
108
+ className: "text-sm text-text-muted mb-2 line-clamp-1",
106
109
  children: e.description
107
110
  }),
108
111
  /* @__PURE__ */ g("div", {
109
- className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground",
112
+ className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-muted",
110
113
  children: [
111
114
  /* @__PURE__ */ g("span", {
112
115
  className: "flex items-center gap-1",
113
116
  children: [
114
117
  /* @__PURE__ */ h(l, { className: "size-3" }),
115
118
  /* @__PURE__ */ h("strong", {
116
- className: "text-foreground",
119
+ className: "text-text-primary",
117
120
  children: e.bonusMonths
118
121
  }),
119
122
  " bonus month",
@@ -152,7 +155,7 @@ var _ = () => {
152
155
  onClick: () => k(e.id, e.isActive),
153
156
  disabled: r,
154
157
  title: e.isActive ? "Deactivate" : "Reactivate",
155
- className: `p-2 rounded-lg transition-colors ${e.isActive ? "text-green-600 hover:bg-green-50" : "text-muted-foreground hover:bg-muted"} disabled:opacity-50`,
158
+ className: `p-2 rounded-button transition-colors ${e.isActive ? "text-status-success-text hover:bg-status-success-bg-subtle" : "text-text-muted hover:bg-bg-sunken"} disabled:opacity-50`,
156
159
  children: e.isActive ? /* @__PURE__ */ h(p, { className: "size-5" }) : /* @__PURE__ */ h(f, { className: "size-5" })
157
160
  })
158
161
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"PromotionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/PromotionsListPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { Plus, Tag, Calendar, Users, ToggleLeft, ToggleRight, Gift } from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListPromotionsQuery,\n useDeactivatePromotionMutation,\n useUpdatePromotionMutation,\n} from '../../../../generated/global-operations';\n\nexport const PromotionsListPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [showInactive, setShowInactive] = useState(false);\n const [actionId, setActionId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useListPromotionsQuery({\n variables: { includeInactive: showInactive },\n });\n\n const [deactivatePromotion] = useDeactivatePromotionMutation();\n const [updatePromotion] = useUpdatePromotionMutation();\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to manage promotions.\" />;\n }\n\n const promotions = data?.listPromotions ?? [];\n\n const handleToggle = async (id: string, currentlyActive: boolean) => {\n setActionId(id);\n try {\n if (currentlyActive) {\n await deactivatePromotion({ variables: { id } });\n } else {\n await updatePromotion({ variables: { id, input: { isActive: true } } });\n }\n await refetch();\n } finally {\n setActionId(null);\n }\n };\n\n const now = new Date();\n const statusLabel = (p: { isActive: boolean; startsAt: string; expiresAt: string }) => {\n if (!p.isActive) return { text: 'Inactive', cls: 'bg-muted text-muted-foreground' };\n if (new Date(p.expiresAt) < now)\n return { text: 'Expired', cls: 'bg-destructive/10 text-destructive' };\n if (new Date(p.startsAt) > now) return { text: 'Upcoming', cls: 'bg-primary/10 text-primary' };\n return { text: 'Live', cls: 'bg-green-500/10 text-green-600' };\n };\n\n return (\n <div className=\"max-w-5xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center justify-between gap-4\">\n <PageHeader\n title=\"Promotions\"\n description=\"Time-bounded offers that grant bonus free renewal months to subscribers\"\n icon={<Gift className=\"size-6\" />}\n />\n <button\n onClick={() => navigateTo('/promotions/create')}\n className=\"flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 text-sm font-medium shrink-0\"\n >\n <Plus className=\"size-4\" />\n New Promotion\n </button>\n </div>\n\n {/* Filter */}\n <div className=\"flex items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-muted-foreground cursor-pointer select-none\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"rounded border-border\"\n />\n Show inactive / expired\n </label>\n </div>\n\n {loading ? (\n <div className=\"text-center py-16 text-muted-foreground\">Loading promotions…</div>\n ) : promotions.length === 0 ? (\n <div className=\"text-center py-16 border border-dashed border-border rounded-xl\">\n <Gift className=\"size-10 mx-auto mb-3 text-muted-foreground/50\" />\n <p className=\"text-muted-foreground font-medium\">No promotions yet</p>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Create one to offer bonus months to early subscribers\n </p>\n <button\n onClick={() => navigateTo('/promotions/create')}\n className=\"mt-4 px-4 py-2 rounded-lg bg-primary text-primary-foreground text-sm\"\n >\n Create Promotion\n </button>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {promotions.map((p: (typeof promotions)[number]) => {\n const { text, cls } = statusLabel(p);\n const busy = actionId === p.id;\n return (\n <div\n key={p.id}\n className=\"bg-card border border-border rounded-xl p-5 flex flex-col sm:flex-row sm:items-center gap-4\"\n >\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <span className=\"font-semibold text-foreground truncate\">{p.name}</span>\n <span className={`text-xs font-medium px-2 py-0.5 rounded-full ${cls}`}>\n {text}\n </span>\n </div>\n {p.description && (\n <p className=\"text-sm text-muted-foreground mb-2 line-clamp-1\">\n {p.description}\n </p>\n )}\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground\">\n <span className=\"flex items-center gap-1\">\n <Gift className=\"size-3\" />\n <strong className=\"text-foreground\">{p.bonusMonths}</strong> bonus month\n {p.bonusMonths !== 1 ? 's' : ''}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"size-3\" />\n {new Date(p.startsAt).toLocaleDateString()} –{' '}\n {new Date(p.expiresAt).toLocaleDateString()}\n </span>\n <span className=\"flex items-center gap-1\">\n <Users className=\"size-3\" />\n {p.currentRedemptions}\n {p.maxRedemptions ? ` / ${p.maxRedemptions}` : ''} redemptions\n </span>\n {p.applicableProductIds.length > 0 && (\n <span className=\"flex items-center gap-1\">\n <Tag className=\"size-3\" />\n {p.applicableProductIds.join(', ')}\n </span>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n <button\n onClick={() => handleToggle(p.id, p.isActive)}\n disabled={busy}\n title={p.isActive ? 'Deactivate' : 'Reactivate'}\n className={`p-2 rounded-lg transition-colors ${\n p.isActive\n ? 'text-green-600 hover:bg-green-50'\n : 'text-muted-foreground hover:bg-muted'\n } disabled:opacity-50`}\n >\n {p.isActive ? (\n <ToggleRight className=\"size-5\" />\n ) : (\n <ToggleLeft className=\"size-5\" />\n )}\n </button>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"PromotionsListPage.js","names":[],"sources":["../../../../../src/billing/modules/promotions/pages/PromotionsListPage.tsx"],"sourcesContent":["import { type FC, useState } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { Plus, Tag, Calendar, Users, ToggleLeft, ToggleRight, Gift } from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListPromotionsQuery,\n useDeactivatePromotionMutation,\n useUpdatePromotionMutation,\n} from '../../../../generated/global-operations';\n\nexport const PromotionsListPage: FC = () => {\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n const [showInactive, setShowInactive] = useState(false);\n const [actionId, setActionId] = useState<string | null>(null);\n\n const { data, loading, refetch } = useListPromotionsQuery({\n variables: { includeInactive: showInactive },\n });\n\n const [deactivatePromotion] = useDeactivatePromotionMutation();\n const [updatePromotion] = useUpdatePromotionMutation();\n\n if (!permissions.canManagePlans) {\n return <AccessDenied message=\"You don't have permission to manage promotions.\" />;\n }\n\n const promotions = data?.listPromotions ?? [];\n\n const handleToggle = async (id: string, currentlyActive: boolean) => {\n setActionId(id);\n try {\n if (currentlyActive) {\n await deactivatePromotion({ variables: { id } });\n } else {\n await updatePromotion({ variables: { id, input: { isActive: true } } });\n }\n await refetch();\n } finally {\n setActionId(null);\n }\n };\n\n const now = new Date();\n const statusLabel = (p: { isActive: boolean; startsAt: string; expiresAt: string }) => {\n if (!p.isActive) return { text: 'Inactive', cls: 'bg-bg-sunken text-text-muted' };\n if (new Date(p.expiresAt) < now)\n return { text: 'Expired', cls: 'bg-status-error-bg-subtle text-status-error-text' };\n if (new Date(p.startsAt) > now)\n return { text: 'Upcoming', cls: 'bg-[var(--color-accent-soft)] text-text-link' };\n return { text: 'Live', cls: 'bg-status-success-bg-subtle text-status-success-text' };\n };\n\n return (\n <div className=\"max-w-5xl mx-auto p-4 sm:p-6 space-y-6\">\n <div className=\"flex items-center justify-between gap-4\">\n <PageHeader\n title=\"Promotions\"\n description=\"Time-bounded offers that grant bonus free renewal months to subscribers\"\n icon={<Gift className=\"size-6\" />}\n />\n <button\n onClick={() => navigateTo('/promotions/create')}\n className=\"flex items-center gap-2 px-4 py-2 rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover text-sm font-medium shrink-0 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <Plus className=\"size-4\" />\n New Promotion\n </button>\n </div>\n\n {/* Filter */}\n <div className=\"flex items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-text-muted cursor-pointer select-none\">\n <input\n type=\"checkbox\"\n checked={showInactive}\n onChange={(e) => setShowInactive(e.target.checked)}\n className=\"rounded border-border-subtle\"\n />\n Show inactive / expired\n </label>\n </div>\n\n {loading ? (\n <div className=\"text-center py-16 text-text-muted\">Loading promotions…</div>\n ) : promotions.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center text-center py-16 border border-dashed border-border-subtle rounded-card bg-bg-surface\">\n <div className=\"flex size-12 items-center justify-center rounded-full bg-[var(--color-accent-soft)] mb-4\">\n <Gift className=\"size-6 text-text-link\" />\n </div>\n <p className=\"text-base font-semibold text-text-primary\">No promotions yet</p>\n <p className=\"text-sm text-text-muted mt-1\">\n Create one to offer bonus months to early subscribers\n </p>\n <button\n onClick={() => navigateTo('/promotions/create')}\n className=\"mt-4 px-4 py-2 rounded-button bg-action-primary-bg text-action-primary-text text-sm font-medium transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n Create Promotion\n </button>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {promotions.map((p: (typeof promotions)[number]) => {\n const { text, cls } = statusLabel(p);\n const busy = actionId === p.id;\n return (\n <div\n key={p.id}\n className=\"bg-bg-surface border border-border-subtle rounded-card p-5 flex flex-col sm:flex-row sm:items-center gap-4 shadow-[var(--shadow-elevation-1)] transition-all duration-200 hover:border-border-strong hover:shadow-[var(--shadow-elevation-2)]\"\n >\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 mb-1\">\n <span className=\"font-semibold text-text-primary truncate\">{p.name}</span>\n <span className={`text-xs font-medium px-2 py-0.5 rounded-full ${cls}`}>\n {text}\n </span>\n </div>\n {p.description && (\n <p className=\"text-sm text-text-muted mb-2 line-clamp-1\">{p.description}</p>\n )}\n <div className=\"flex flex-wrap gap-x-4 gap-y-1 text-xs text-text-muted\">\n <span className=\"flex items-center gap-1\">\n <Gift className=\"size-3\" />\n <strong className=\"text-text-primary\">{p.bonusMonths}</strong> bonus month\n {p.bonusMonths !== 1 ? 's' : ''}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"size-3\" />\n {new Date(p.startsAt).toLocaleDateString()} –{' '}\n {new Date(p.expiresAt).toLocaleDateString()}\n </span>\n <span className=\"flex items-center gap-1\">\n <Users className=\"size-3\" />\n {p.currentRedemptions}\n {p.maxRedemptions ? ` / ${p.maxRedemptions}` : ''} redemptions\n </span>\n {p.applicableProductIds.length > 0 && (\n <span className=\"flex items-center gap-1\">\n <Tag className=\"size-3\" />\n {p.applicableProductIds.join(', ')}\n </span>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n <button\n onClick={() => handleToggle(p.id, p.isActive)}\n disabled={busy}\n title={p.isActive ? 'Deactivate' : 'Reactivate'}\n className={`p-2 rounded-button transition-colors ${\n p.isActive\n ? 'text-status-success-text hover:bg-status-success-bg-subtle'\n : 'text-text-muted hover:bg-bg-sunken'\n } disabled:opacity-50`}\n >\n {p.isActive ? (\n <ToggleRight className=\"size-5\" />\n ) : (\n <ToggleLeft className=\"size-5\" />\n )}\n </button>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}