@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.
- package/dist/billing/BillingAdminRoutes.js +7 -6
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingRoot.js +1 -1
- package/dist/billing/BillingRoot.js.map +1 -1
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js +22 -22
- package/dist/billing/modules/addons/components/FloatingCartDrawer.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonCreatePage.js +31 -31
- package/dist/billing/modules/addons/pages/AddonCreatePage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonDetailPage.js +39 -39
- package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonEditPage.js +29 -29
- package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsListPage.js +91 -91
- package/dist/billing/modules/addons/pages/AddonsListPage.js.map +1 -1
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js +40 -40
- package/dist/billing/modules/analytics/pages/AnalyticsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +115 -115
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +75 -72
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +126 -126
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +45 -45
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js +25 -25
- package/dist/billing/modules/checkout/components/BillingAccountSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js +31 -35
- package/dist/billing/modules/checkout/components/CompleteBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js +9 -9
- package/dist/billing/modules/checkout/components/CreditsAmountInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js +31 -31
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/components/PromoCodeInput.js +8 -8
- package/dist/billing/modules/checkout/components/PromoCodeInput.js.map +1 -1
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js +5 -5
- package/dist/billing/modules/checkout/components/RazorpayPaymentButton.js.map +1 -1
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js +9 -9
- package/dist/billing/modules/checkout/components/SimpleAutoPayToggle.js.map +1 -1
- package/dist/billing/modules/checkout/components/StripePaymentForm.js +21 -21
- package/dist/billing/modules/checkout/components/StripePaymentForm.js.map +1 -1
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js +11 -11
- package/dist/billing/modules/checkout/components/SubscriptionSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js +31 -31
- package/dist/billing/modules/checkout/components/UnifiedPaymentMethodSelector.js.map +1 -1
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js +23 -23
- package/dist/billing/modules/checkout/components/WorkspaceQuotaAssignmentModal.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +91 -91
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +62 -62
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponsListPage.js +72 -60
- package/dist/billing/modules/coupons/pages/CouponsListPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +66 -66
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/EditCouponPage.js +46 -46
- package/dist/billing/modules/coupons/pages/EditCouponPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js +19 -19
- package/dist/billing/modules/credits/pages/AdminCreditsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +37 -37
- package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js +90 -90
- package/dist/billing/modules/credits/pages/CreditWithdrawalPage.js.map +1 -1
- package/dist/billing/modules/credits/pages/CreditsPage.js +41 -41
- package/dist/billing/modules/credits/pages/CreditsPage.js.map +1 -1
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js +31 -31
- package/dist/billing/modules/dashboard/components/ActiveSubscriptionCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js +35 -35
- package/dist/billing/modules/dashboard/components/AddPaymentMethodModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js +24 -24
- package/dist/billing/modules/dashboard/components/BillingAccountCard.js.map +1 -1
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js +99 -99
- package/dist/billing/modules/dashboard/components/CreateBillingAccountModal.js.map +1 -1
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js +23 -23
- package/dist/billing/modules/dashboard/components/PaymentMethodsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js +22 -22
- package/dist/billing/modules/dashboard/components/RecommendedAddonsSection.js.map +1 -1
- package/dist/billing/modules/dashboard/pages/OverviewPage.js +58 -58
- package/dist/billing/modules/dashboard/pages/OverviewPage.js.map +1 -1
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js +126 -126
- package/dist/billing/modules/discounts/pages/DiscountsListPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +83 -79
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +16 -16
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +40 -40
- package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanDetailPage.js +57 -57
- package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +35 -35
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +57 -57
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansListPage.js +52 -52
- package/dist/billing/modules/plans/pages/PlansListPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +40 -39
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +22 -19
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js +34 -34
- package/dist/billing/modules/quota/pages/QuotaTemplateCreatePage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +47 -47
- package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +39 -39
- package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js +52 -52
- package/dist/billing/modules/quota/pages/QuotaTemplatesListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +23 -23
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +259 -246
- package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js +88 -85
- package/dist/billing/modules/subscriptions/pages/SubscriptionsListPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +51 -51
- package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
- package/dist/billing/modules/usage/pages/UsagePage.js +206 -196
- package/dist/billing/modules/usage/pages/UsagePage.js.map +1 -1
- package/dist/billing/shared/components/ServerError.js +4 -4
- package/dist/billing/shared/components/ServerError.js.map +1 -1
- package/dist/billing/shared/components/StatCard.js +11 -33
- package/dist/billing/shared/components/StatCard.js.map +1 -1
- package/dist/billing/shared/ui/Card.js +15 -0
- package/dist/billing/shared/ui/Card.js.map +1 -0
- package/dist/billing/shared/ui/MetricCard.js +44 -0
- package/dist/billing/shared/ui/MetricCard.js.map +1 -0
- package/dist/billing/shared/ui/ProgressBar.js +30 -0
- package/dist/billing/shared/ui/ProgressBar.js.map +1 -0
- package/dist/billing/shared/ui/SectionHeader.js +2 -0
- package/dist/billing/shared/ui/StatusPill.js +28 -0
- package/dist/billing/shared/ui/StatusPill.js.map +1 -0
- package/dist/billing/shared/ui/index.js +5 -0
- package/dist/billing/shared/utils/tokens.js +13 -30
- package/dist/billing/shared/utils/tokens.js.map +1 -1
- package/package.json +1 -1
|
@@ -73,7 +73,7 @@ var O = () => {
|
|
|
73
73
|
}), /* @__PURE__ */ E("button", {
|
|
74
74
|
type: "button",
|
|
75
75
|
onClick: () => k("/coupons/create"),
|
|
76
|
-
className: "
|
|
76
|
+
className: "inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors duration-200 hover:bg-action-primary-bgHover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
77
77
|
children: [/* @__PURE__ */ T(v, { className: "size-4" }), "Create Coupon"]
|
|
78
78
|
})]
|
|
79
79
|
}),
|
|
@@ -81,81 +81,93 @@ var O = () => {
|
|
|
81
81
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4",
|
|
82
82
|
children: [
|
|
83
83
|
/* @__PURE__ */ E("div", {
|
|
84
|
-
className: "
|
|
84
|
+
className: "rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]",
|
|
85
85
|
children: [
|
|
86
86
|
/* @__PURE__ */ E("div", {
|
|
87
87
|
className: "flex items-center justify-between mb-2",
|
|
88
88
|
children: [/* @__PURE__ */ T("span", {
|
|
89
|
-
className: "text-sm text-
|
|
89
|
+
className: "text-sm text-text-secondary",
|
|
90
90
|
children: "Total Coupons"
|
|
91
|
-
}), /* @__PURE__ */ T(
|
|
91
|
+
}), /* @__PURE__ */ T("span", {
|
|
92
|
+
className: "flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]",
|
|
93
|
+
children: /* @__PURE__ */ T(S, { className: "size-4 text-accent-blue" })
|
|
94
|
+
})]
|
|
92
95
|
}),
|
|
93
96
|
/* @__PURE__ */ T("p", {
|
|
94
|
-
className: "text-2xl font-
|
|
97
|
+
className: "text-2xl font-semibold text-text-primary tabular-nums",
|
|
95
98
|
children: Z.totalCoupons
|
|
96
99
|
}),
|
|
97
100
|
/* @__PURE__ */ E("p", {
|
|
98
|
-
className: "text-xs text-muted
|
|
101
|
+
className: "text-xs text-text-muted mt-1",
|
|
99
102
|
children: [Z.activeCoupons, " active"]
|
|
100
103
|
})
|
|
101
104
|
]
|
|
102
105
|
}),
|
|
103
106
|
/* @__PURE__ */ E("div", {
|
|
104
|
-
className: "
|
|
107
|
+
className: "rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]",
|
|
105
108
|
children: [
|
|
106
109
|
/* @__PURE__ */ E("div", {
|
|
107
110
|
className: "flex items-center justify-between mb-2",
|
|
108
111
|
children: [/* @__PURE__ */ T("span", {
|
|
109
|
-
className: "text-sm text-
|
|
112
|
+
className: "text-sm text-text-secondary",
|
|
110
113
|
children: "Total Uses"
|
|
111
|
-
}), /* @__PURE__ */ T(
|
|
114
|
+
}), /* @__PURE__ */ T("span", {
|
|
115
|
+
className: "flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle",
|
|
116
|
+
children: /* @__PURE__ */ T(w, { className: "size-4 text-status-success-text" })
|
|
117
|
+
})]
|
|
112
118
|
}),
|
|
113
119
|
/* @__PURE__ */ T("p", {
|
|
114
|
-
className: "text-2xl font-
|
|
120
|
+
className: "text-2xl font-semibold text-text-primary tabular-nums",
|
|
115
121
|
children: Z.totalUses
|
|
116
122
|
}),
|
|
117
123
|
/* @__PURE__ */ T("p", {
|
|
118
|
-
className: "text-xs text-muted
|
|
124
|
+
className: "text-xs text-text-muted mt-1",
|
|
119
125
|
children: "All time"
|
|
120
126
|
})
|
|
121
127
|
]
|
|
122
128
|
}),
|
|
123
129
|
/* @__PURE__ */ E("div", {
|
|
124
|
-
className: "
|
|
130
|
+
className: "rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]",
|
|
125
131
|
children: [
|
|
126
132
|
/* @__PURE__ */ E("div", {
|
|
127
133
|
className: "flex items-center justify-between mb-2",
|
|
128
134
|
children: [/* @__PURE__ */ T("span", {
|
|
129
|
-
className: "text-sm text-
|
|
135
|
+
className: "text-sm text-text-secondary",
|
|
130
136
|
children: "Total Discount"
|
|
131
|
-
}), /* @__PURE__ */ T(
|
|
137
|
+
}), /* @__PURE__ */ T("span", {
|
|
138
|
+
className: "flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]",
|
|
139
|
+
children: /* @__PURE__ */ T(p, { className: "size-4 text-accent-purple" })
|
|
140
|
+
})]
|
|
132
141
|
}),
|
|
133
142
|
/* @__PURE__ */ T("p", {
|
|
134
|
-
className: "text-2xl font-
|
|
143
|
+
className: "text-2xl font-semibold text-text-primary tabular-nums",
|
|
135
144
|
children: Q(Z.totalDiscountValue)
|
|
136
145
|
}),
|
|
137
146
|
/* @__PURE__ */ T("p", {
|
|
138
|
-
className: "text-xs text-muted
|
|
147
|
+
className: "text-xs text-text-muted mt-1",
|
|
139
148
|
children: "Fixed amount only"
|
|
140
149
|
})
|
|
141
150
|
]
|
|
142
151
|
}),
|
|
143
152
|
/* @__PURE__ */ E("div", {
|
|
144
|
-
className: "
|
|
153
|
+
className: "rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]",
|
|
145
154
|
children: [
|
|
146
155
|
/* @__PURE__ */ E("div", {
|
|
147
156
|
className: "flex items-center justify-between mb-2",
|
|
148
157
|
children: [/* @__PURE__ */ T("span", {
|
|
149
|
-
className: "text-sm text-
|
|
158
|
+
className: "text-sm text-text-secondary",
|
|
150
159
|
children: "Active Rate"
|
|
151
|
-
}), /* @__PURE__ */ T(
|
|
160
|
+
}), /* @__PURE__ */ T("span", {
|
|
161
|
+
className: "flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle",
|
|
162
|
+
children: /* @__PURE__ */ T(d, { className: "size-4 text-status-success-text" })
|
|
163
|
+
})]
|
|
152
164
|
}),
|
|
153
165
|
/* @__PURE__ */ E("p", {
|
|
154
|
-
className: "text-2xl font-
|
|
166
|
+
className: "text-2xl font-semibold text-text-primary tabular-nums",
|
|
155
167
|
children: [Z.totalCoupons > 0 ? Math.round(Z.activeCoupons / Z.totalCoupons * 100) : 0, "%"]
|
|
156
168
|
}),
|
|
157
169
|
/* @__PURE__ */ T("p", {
|
|
158
|
-
className: "text-xs text-muted
|
|
170
|
+
className: "text-xs text-text-muted mt-1",
|
|
159
171
|
children: "Of all coupons"
|
|
160
172
|
})
|
|
161
173
|
]
|
|
@@ -193,20 +205,20 @@ var O = () => {
|
|
|
193
205
|
children: [/* @__PURE__ */ T("button", {
|
|
194
206
|
type: "button",
|
|
195
207
|
onClick: () => z(null),
|
|
196
|
-
className: "rounded-
|
|
208
|
+
className: "rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
197
209
|
children: "Cancel"
|
|
198
210
|
}), /* @__PURE__ */ T("button", {
|
|
199
211
|
type: "button",
|
|
200
212
|
onClick: () => fe(R.id),
|
|
201
213
|
disabled: Y,
|
|
202
|
-
className: "rounded-
|
|
214
|
+
className: "rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50",
|
|
203
215
|
children: Y ? "Deactivating…" : "Deactivate"
|
|
204
216
|
})]
|
|
205
217
|
})]
|
|
206
218
|
})
|
|
207
219
|
}),
|
|
208
220
|
/* @__PURE__ */ T("div", {
|
|
209
|
-
className: "bg-
|
|
221
|
+
className: "bg-bg-surface border border-border-subtle rounded-lg p-4",
|
|
210
222
|
children: /* @__PURE__ */ E("div", {
|
|
211
223
|
className: "grid grid-cols-1 md:grid-cols-4 gap-4",
|
|
212
224
|
children: [
|
|
@@ -214,12 +226,12 @@ var O = () => {
|
|
|
214
226
|
className: "md:col-span-2",
|
|
215
227
|
children: /* @__PURE__ */ E("div", {
|
|
216
228
|
className: "relative",
|
|
217
|
-
children: [/* @__PURE__ */ T(x, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted
|
|
229
|
+
children: [/* @__PURE__ */ T(x, { className: "absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted" }), /* @__PURE__ */ T("input", {
|
|
218
230
|
type: "text",
|
|
219
231
|
placeholder: "Search coupons...",
|
|
220
232
|
value: A,
|
|
221
233
|
onChange: (e) => ae(e.target.value),
|
|
222
|
-
className: "w-full pl-10 pr-4 py-2 rounded-
|
|
234
|
+
className: "w-full pl-10 pr-4 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)]"
|
|
223
235
|
})]
|
|
224
236
|
})
|
|
225
237
|
}),
|
|
@@ -228,7 +240,7 @@ var O = () => {
|
|
|
228
240
|
onChange: (e) => {
|
|
229
241
|
M(e.target.value), L(0);
|
|
230
242
|
},
|
|
231
|
-
className: "px-4 py-2 rounded-
|
|
243
|
+
className: "px-4 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)]",
|
|
232
244
|
children: [
|
|
233
245
|
/* @__PURE__ */ T("option", {
|
|
234
246
|
value: "ALL",
|
|
@@ -253,7 +265,7 @@ var O = () => {
|
|
|
253
265
|
onChange: (e) => {
|
|
254
266
|
P(e.target.value), L(0);
|
|
255
267
|
},
|
|
256
|
-
className: "px-4 py-2 rounded-
|
|
268
|
+
className: "px-4 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)]",
|
|
257
269
|
children: [
|
|
258
270
|
/* @__PURE__ */ T("option", {
|
|
259
271
|
value: "ALL",
|
|
@@ -277,63 +289,63 @@ var O = () => {
|
|
|
277
289
|
})
|
|
278
290
|
}),
|
|
279
291
|
/* @__PURE__ */ T("div", {
|
|
280
|
-
className: "bg-
|
|
292
|
+
className: "bg-bg-surface border border-border-subtle rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]",
|
|
281
293
|
children: /* @__PURE__ */ T("div", {
|
|
282
294
|
className: "overflow-x-auto",
|
|
283
295
|
children: /* @__PURE__ */ E("table", {
|
|
284
296
|
className: "w-full",
|
|
285
297
|
children: [/* @__PURE__ */ T("thead", {
|
|
286
|
-
className: "bg-
|
|
298
|
+
className: "sticky top-0 z-10 bg-bg-sunken border-b border-border-subtle",
|
|
287
299
|
children: /* @__PURE__ */ E("tr", { children: [
|
|
288
300
|
/* @__PURE__ */ T("th", {
|
|
289
|
-
className: "text-left
|
|
301
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
290
302
|
children: "Code"
|
|
291
303
|
}),
|
|
292
304
|
/* @__PURE__ */ T("th", {
|
|
293
|
-
className: "text-left
|
|
305
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
294
306
|
children: "Type"
|
|
295
307
|
}),
|
|
296
308
|
/* @__PURE__ */ T("th", {
|
|
297
|
-
className: "text-left
|
|
309
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
298
310
|
children: "Value"
|
|
299
311
|
}),
|
|
300
312
|
/* @__PURE__ */ T("th", {
|
|
301
|
-
className: "text-left
|
|
313
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
302
314
|
children: "Status"
|
|
303
315
|
}),
|
|
304
316
|
/* @__PURE__ */ T("th", {
|
|
305
|
-
className: "text-left
|
|
317
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
306
318
|
children: "Usage"
|
|
307
319
|
}),
|
|
308
320
|
/* @__PURE__ */ T("th", {
|
|
309
|
-
className: "text-left
|
|
321
|
+
className: "text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
310
322
|
children: "Valid Until"
|
|
311
323
|
}),
|
|
312
324
|
/* @__PURE__ */ T("th", {
|
|
313
|
-
className: "text-right
|
|
325
|
+
className: "text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted",
|
|
314
326
|
children: "Actions"
|
|
315
327
|
})
|
|
316
328
|
] })
|
|
317
329
|
}), /* @__PURE__ */ T("tbody", {
|
|
318
|
-
className: "divide-y divide-border",
|
|
330
|
+
className: "divide-y divide-border-subtle",
|
|
319
331
|
children: G ? /* @__PURE__ */ T("tr", { children: /* @__PURE__ */ T("td", {
|
|
320
332
|
colSpan: 7,
|
|
321
|
-
className: "p-8 text-center text-muted
|
|
333
|
+
className: "p-8 text-center text-text-muted",
|
|
322
334
|
children: "Loading coupons…"
|
|
323
335
|
}) }) : X.length === 0 ? /* @__PURE__ */ T("tr", { children: /* @__PURE__ */ T("td", {
|
|
324
336
|
colSpan: 7,
|
|
325
|
-
className: "p-8 text-center text-muted
|
|
337
|
+
className: "p-8 text-center text-text-muted",
|
|
326
338
|
children: "No coupons found"
|
|
327
339
|
}) }) : X.map((e) => /* @__PURE__ */ E("tr", {
|
|
328
|
-
className: "hover:bg-
|
|
340
|
+
className: "hover:bg-bg-sunken/50",
|
|
329
341
|
children: [
|
|
330
342
|
/* @__PURE__ */ T("td", {
|
|
331
343
|
className: "p-4",
|
|
332
344
|
children: /* @__PURE__ */ E("div", { children: [/* @__PURE__ */ T("p", {
|
|
333
|
-
className: "font-medium text-
|
|
345
|
+
className: "font-medium text-text-primary font-mono",
|
|
334
346
|
children: e.code
|
|
335
347
|
}), /* @__PURE__ */ T("p", {
|
|
336
|
-
className: "text-sm text-muted
|
|
348
|
+
className: "text-sm text-text-muted",
|
|
337
349
|
children: e.name
|
|
338
350
|
})] })
|
|
339
351
|
}),
|
|
@@ -342,7 +354,7 @@ var O = () => {
|
|
|
342
354
|
children: /* @__PURE__ */ E("div", {
|
|
343
355
|
className: "flex items-center gap-2",
|
|
344
356
|
children: [de(e.type), /* @__PURE__ */ T("span", {
|
|
345
|
-
className: "text-sm text-
|
|
357
|
+
className: "text-sm text-text-primary capitalize",
|
|
346
358
|
children: e.type.replace("_", " ").toLowerCase()
|
|
347
359
|
})]
|
|
348
360
|
})
|
|
@@ -350,7 +362,7 @@ var O = () => {
|
|
|
350
362
|
/* @__PURE__ */ T("td", {
|
|
351
363
|
className: "p-4",
|
|
352
364
|
children: /* @__PURE__ */ T("span", {
|
|
353
|
-
className: "text-sm font-medium text-
|
|
365
|
+
className: "text-sm font-medium text-text-primary",
|
|
354
366
|
children: e.type === "PERCENTAGE" ? `${e.value}%` : Q(e.value)
|
|
355
367
|
})
|
|
356
368
|
}),
|
|
@@ -364,18 +376,18 @@ var O = () => {
|
|
|
364
376
|
/* @__PURE__ */ E("td", {
|
|
365
377
|
className: "p-4",
|
|
366
378
|
children: [/* @__PURE__ */ E("div", {
|
|
367
|
-
className: "text-sm text-
|
|
379
|
+
className: "text-sm text-text-primary",
|
|
368
380
|
children: [/* @__PURE__ */ T("span", {
|
|
369
381
|
className: "font-medium",
|
|
370
382
|
children: e.currentUses
|
|
371
383
|
}), e.maxTotalUses && /* @__PURE__ */ E("span", {
|
|
372
|
-
className: "text-muted
|
|
384
|
+
className: "text-text-muted",
|
|
373
385
|
children: [" / ", e.maxTotalUses]
|
|
374
386
|
})]
|
|
375
387
|
}), e.maxTotalUses && /* @__PURE__ */ T("div", {
|
|
376
|
-
className: "w-full bg-
|
|
388
|
+
className: "w-full bg-bg-sunken rounded-full h-1.5 mt-1",
|
|
377
389
|
children: /* @__PURE__ */ T("div", {
|
|
378
|
-
className: "bg-primary h-1.5 rounded-full",
|
|
390
|
+
className: "bg-action-primary-bg h-1.5 rounded-full",
|
|
379
391
|
style: { width: `${Math.min(e.currentUses / e.maxTotalUses * 100, 100)}%` }
|
|
380
392
|
})
|
|
381
393
|
})]
|
|
@@ -383,7 +395,7 @@ var O = () => {
|
|
|
383
395
|
/* @__PURE__ */ T("td", {
|
|
384
396
|
className: "p-4",
|
|
385
397
|
children: /* @__PURE__ */ T("span", {
|
|
386
|
-
className: "text-sm text-muted
|
|
398
|
+
className: "text-sm text-text-muted",
|
|
387
399
|
children: e.validUntil ? $(e.validUntil) : "No expiry"
|
|
388
400
|
})
|
|
389
401
|
}),
|
|
@@ -395,14 +407,14 @@ var O = () => {
|
|
|
395
407
|
/* @__PURE__ */ T("button", {
|
|
396
408
|
type: "button",
|
|
397
409
|
onClick: () => k(`/coupons/${e.id}`),
|
|
398
|
-
className: "p-2 rounded-lg hover:bg-
|
|
410
|
+
className: "p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary",
|
|
399
411
|
title: "View details",
|
|
400
412
|
children: /* @__PURE__ */ T(h, { className: "size-4" })
|
|
401
413
|
}),
|
|
402
414
|
/* @__PURE__ */ T("button", {
|
|
403
415
|
type: "button",
|
|
404
416
|
onClick: () => k(`/coupons/${e.id}/edit`),
|
|
405
|
-
className: "p-2 rounded-lg hover:bg-
|
|
417
|
+
className: "p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary",
|
|
406
418
|
title: "Edit coupon",
|
|
407
419
|
children: /* @__PURE__ */ T(m, { className: "size-4" })
|
|
408
420
|
}),
|
|
@@ -410,20 +422,20 @@ var O = () => {
|
|
|
410
422
|
type: "button",
|
|
411
423
|
onClick: () => z(e),
|
|
412
424
|
disabled: Y || B === e.id,
|
|
413
|
-
className: "p-2 rounded-lg hover:bg-
|
|
425
|
+
className: "p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed",
|
|
414
426
|
title: "Deactivate coupon",
|
|
415
427
|
children: /* @__PURE__ */ T(b, { className: "size-4" })
|
|
416
428
|
}) : e.status === "INACTIVE" ? /* @__PURE__ */ T("button", {
|
|
417
429
|
type: "button",
|
|
418
430
|
onClick: () => pe(e.id, s.Active),
|
|
419
431
|
disabled: B === e.id,
|
|
420
|
-
className: "p-2 rounded-lg hover:bg-status-success-bg-subtle text-muted
|
|
432
|
+
className: "p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed",
|
|
421
433
|
title: "Activate coupon",
|
|
422
434
|
children: /* @__PURE__ */ T(y, { className: "size-4" })
|
|
423
435
|
}) : /* @__PURE__ */ T("button", {
|
|
424
436
|
type: "button",
|
|
425
437
|
disabled: !0,
|
|
426
|
-
className: "p-2 rounded-lg text-muted
|
|
438
|
+
className: "p-2 rounded-lg text-text-muted/40 cursor-not-allowed",
|
|
427
439
|
title: `Cannot change status (${e.status.toLowerCase()})`,
|
|
428
440
|
children: /* @__PURE__ */ T(C, { className: "size-4" })
|
|
429
441
|
})
|
|
@@ -439,7 +451,7 @@ var O = () => {
|
|
|
439
451
|
J > 20 && /* @__PURE__ */ E("div", {
|
|
440
452
|
className: "flex items-center justify-between px-2",
|
|
441
453
|
children: [/* @__PURE__ */ E("p", {
|
|
442
|
-
className: "text-sm text-muted
|
|
454
|
+
className: "text-sm text-text-muted",
|
|
443
455
|
children: [
|
|
444
456
|
"Showing ",
|
|
445
457
|
I * 20 + 1,
|
|
@@ -457,11 +469,11 @@ var O = () => {
|
|
|
457
469
|
type: "button",
|
|
458
470
|
onClick: () => L((e) => Math.max(0, e - 1)),
|
|
459
471
|
disabled: I === 0,
|
|
460
|
-
className: "p-2 rounded-lg border border-border hover:bg-
|
|
472
|
+
className: "p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed",
|
|
461
473
|
children: /* @__PURE__ */ T(f, { className: "size-4" })
|
|
462
474
|
}),
|
|
463
475
|
/* @__PURE__ */ E("span", {
|
|
464
|
-
className: "text-sm text-
|
|
476
|
+
className: "text-sm text-text-primary font-medium",
|
|
465
477
|
children: [
|
|
466
478
|
I + 1,
|
|
467
479
|
" / ",
|
|
@@ -472,7 +484,7 @@ var O = () => {
|
|
|
472
484
|
type: "button",
|
|
473
485
|
onClick: () => L((e) => e + 1),
|
|
474
486
|
disabled: !oe,
|
|
475
|
-
className: "p-2 rounded-lg border border-border hover:bg-
|
|
487
|
+
className: "p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed",
|
|
476
488
|
children: /* @__PURE__ */ T(te, { className: "size-4" })
|
|
477
489
|
})
|
|
478
490
|
]
|
|
@@ -483,7 +495,7 @@ var O = () => {
|
|
|
483
495
|
children: /* @__PURE__ */ E("button", {
|
|
484
496
|
type: "button",
|
|
485
497
|
onClick: me,
|
|
486
|
-
className: "px-4 py-2 rounded-lg border border-border text-
|
|
498
|
+
className: "px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2",
|
|
487
499
|
children: [/* @__PURE__ */ T(ne, { className: "size-4" }), "Export Coupons"]
|
|
488
500
|
})
|
|
489
501
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CouponsListPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CouponsListPage.tsx"],"sourcesContent":["/**\n * Coupons List Page\n *\n * Admin interface for managing coupon codes:\n * - View all coupons\n * - Filter by status, type, scope\n * - Create new coupons\n * - Edit/deactivate existing coupons\n * - View usage analytics\n */\n\nimport { type FC, useState, useMemo, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport {\n Plus,\n Search,\n Tag,\n TrendingUp,\n CheckCircle,\n AlertCircle,\n Percent,\n DollarSign,\n Package,\n Eye,\n Edit,\n Trash2,\n Download,\n PowerOff,\n Power,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"px-4 py-2 rounded-lg bg-primary text-primary-foreground hover:bg-primary/90 flex items-center gap-2\"\n >\n <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Coupons</span>\n <Tag className=\"size-4 text-accent-blue\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalCoupons}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Uses</span>\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">{stats.totalUses}</p>\n <p className=\"text-xs text-muted-foreground mt-1\">All time</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Total Discount</span>\n <DollarSign className=\"size-4 text-accent-purple\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-muted-foreground\">Active Rate</span>\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </div>\n <p className=\"text-2xl font-bold text-foreground\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-muted-foreground mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-lg border border-border bg-background px-3 py-2 text-sm font-medium text-foreground transition-colors hover:bg-muted\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-lg bg-destructive px-3 py-2 text-sm font-medium text-primary-foreground transition-colors hover:bg-destructive/90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-card border border-border rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted-foreground\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-10 pr-4 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 </div>\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"px-4 py-2 rounded-lg border border-border bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary/20\"\n >\n <option value=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-card border border-border rounded-lg overflow-hidden\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"bg-muted/50 border-b border-border\">\n <tr>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Code</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Type</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Value</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Status</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Usage</th>\n <th className=\"text-left p-4 text-sm font-medium text-foreground\">Valid Until</th>\n <th className=\"text-right p-4 text-sm font-medium text-foreground\">Actions</th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border\">\n {loading ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n Loading coupons…\n </td>\n </tr>\n ) : filteredCoupons.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-muted-foreground\">\n No coupons found\n </td>\n </tr>\n ) : (\n filteredCoupons.map((coupon: Coupon) => (\n <tr key={coupon.id} className=\"hover:bg-muted/50\">\n <td className=\"p-4\">\n <div>\n <p className=\"font-medium text-foreground font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-muted-foreground\">{coupon.name}</p>\n </div>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-foreground capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm font-medium text-foreground\">\n {coupon.type === 'PERCENTAGE'\n ? `${coupon.value}%`\n : formatCurrency(coupon.value)}\n </span>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"text-sm text-foreground\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-muted-foreground\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-muted rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-primary h-1.5 rounded-full\"\n style={{\n width: `${Math.min(\n (coupon.currentUses / coupon.maxTotalUses) * 100,\n 100\n )}%`,\n }}\n />\n </div>\n )}\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm text-muted-foreground\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-muted text-muted-foreground hover:text-foreground\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-destructive/10 text-muted-foreground hover:text-destructive disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-muted-foreground hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-muted-foreground/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-muted-foreground\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-foreground font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border hover:bg-muted disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border text-foreground hover:bg-muted flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;AAmEA,IAAa,UAA4B;CACvC,IAAM,EAAE,SAAM,GAAS,EACjB,MAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAEnC,IAAa,GAAoB,EACjC,KAAc,GAAuB,EAKrC,CAAC,GAAa,MAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAA6B,MAAM,EACrE,CAAC,GAAY,KAAiB,EAA2B,MAAM,EAC/D,CAAC,KAAe,EAA4B,MAAM,EAClD,CAAC,GAAM,KAAW,EAAS,EAAE,EAC7B,CAAC,GAAoB,KAAyB,EAAwB,KAAK,EAC3E,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAC3D,CAAC,GAAU,KAAe,EAGtB,KAAK,EAGT,EAAE,SAAM,YAAS,eAAY,EAAoB,EACrD,WAAW;EACT,OAAO;EACP,QAAQ,IAAO;EACf,QAAQ;GACN,QAAQ,MAAiB,QAA4C,KAAA,IAAnC;GAClC,MAAM,MAAe,QAAwC,KAAA,IAA/B;GAC9B,OAAO,MAAgB,QAA0C,KAAA,IAAjC;GAChC,QAAQ,KAAe,KAAA;GACxB;EACF,EACF,CAAC,EAEI,IAAU,QACR,GAAM,aAAa,WAAW,EAAE,EACtC,CAAC,GAAM,aAAa,QAAQ,CAC7B,EACK,IAAQ,GAAM,aAAa,SAAS,GACpC,KAAU,GAAM,aAAa,WAAW,IACxC,KAAa,KAAK,KAAK,IAAQ,GAAU,EAGzC,CAAC,IAAkB,EAAE,SAAS,OAAkB,GAA6B,EAC7E,CAAC,MAAgB,GAAyB,EAG1C,IAAkB;AAGxB,UAAgB;AACd,IAAQ,EAAE;IACT;EAAC;EAAa;EAAc;EAAY;EAAY,CAAC;CAGxD,IAAM,IAAQ,SAOL;EAAE,cAAc;EAAO,eANR,EAAQ,QAAQ,MAAc,EAAE,WAAW,SAAS,CAAC;EAM9B,WAL3B,EAAQ,QAAQ,GAAa,MAAc,IAAM,EAAE,aAAa,EAAE;EAK5B,oBAJ7B,EAAQ,QAAQ,GAAa,MAClD,EAAE,SAAS,iBAAuB,IAAM,EAAE,QAAQ,EAAE,cACjD,GACN,EAAE;EACuE,GAC3E,CAAC,GAAS,EAAM,CAAC;AAGpB,KAAI,CAAC,GAAY,mBACf,QAAO,kBAAC,GAAD,EAAc,SAAQ,gDAAiD,CAAA;CAIhF,IAAM,KAAkB,MACf,IAAI,KAAK,aAAa,SAAS;EACpC,OAAO;EACP,UAAU;EACX,CAAC,CAAC,OAAO,EAAO,EAIb,KAAc,MACX,IAAI,KAAK,EAAW,CAAC,mBAAmB,SAAS;EACtD,MAAM;EACN,OAAO;EACP,KAAK;EACN,CAAC,EAIE,MAAkB,MAAmB;AACzC,UAAQ,GAAR;GACE,KAAK,SACH,QAAO;GACT,KAAK,WACH,QAAO;GACT,KAAK,UACH,QAAO;GACT,QACE,QAAO;;IAKP,MAAe,MAAiB;AACpC,UAAQ,GAAR;GACE,KAAK,aACH,QAAO,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;GACvC,KAAK,eACH,QAAO,kBAAC,GAAD,EAAY,WAAU,UAAW,CAAA;GAC1C,KAAK,gBACH,QAAO,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;GACvC,QACE,QAAO,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA;;;AAIvC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KACE,OAAO,GAAG,yBAAyB,UAAU;KAC7C,aAAY;KACZ,MAAM,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA;KAChC,CAAA,EAEF,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAW,kBAAkB;KAC5C,WAAU;eAHZ,CAKE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,EAAA,gBAEpB;OACL;;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAoB,CAAA,EACpE,kBAAC,GAAD,EAAK,WAAU,2BAA4B,CAAA,CACvC;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAsC,EAAM;QAAiB,CAAA;OAC1E,kBAAC,KAAD;QAAG,WAAU;kBAAb,CAAmD,EAAM,eAAc,UAAW;;OAC9E;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAiB,CAAA,EACjE,kBAAC,GAAD,EAAY,WAAU,mCAAoC,CAAA,CACtD;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAsC,EAAM;QAAc,CAAA;OACvE,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAAY,CAAA;OAC1D;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAqB,CAAA,EACrE,kBAAC,GAAD,EAAY,WAAU,6BAA8B,CAAA,CAChD;;OACN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAe,EAAM,mBAAmB;QACvC,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAAqB,CAAA;OACnE;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,QAAD;SAAM,WAAU;mBAAgC;SAAkB,CAAA,EAClE,kBAAC,GAAD,EAAa,WAAU,mCAAoC,CAAA,CACvD;;OACN,kBAAC,KAAD;QAAG,WAAU;kBAAb,CACG,EAAM,eAAe,IAClB,KAAK,MAAO,EAAM,gBAAgB,EAAM,eAAgB,IAAI,GAC5D,GAAE,IAEJ;;OACJ,kBAAC,KAAD;QAAG,WAAU;kBAAqC;QAAkB,CAAA;OAChE;;KACF;;GAEL,KACC,kBAAC,OAAD;IACE,WACE,EAAS,SAAS,YACd,mIACA;IAEN,MAAK;IACL,aAAU;cAPZ,CASG,EAAS,SAAS,YACjB,kBAAC,GAAD,EAAa,WAAU,0BAA2B,CAAA,GAElD,kBAAC,GAAD,EAAa,WAAU,0BAA2B,CAAA,EAEpD,kBAAC,KAAD;KAAG,WAAU;eAAuB,EAAS;KAAY,CAAA,CACrD;;GAGP,KACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAAkD;OAC9B,kBAAC,QAAD;QAAM,WAAU;kBAAa,EAAmB;QAAY,CAAA;;OAC5E;SACJ,kBAAC,KAAD;MAAG,WAAU;gBAAsC;MAE/C,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,EAAsB,KAAK;OAC1C,WAAU;iBACX;OAEQ,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAiB,EAAmB,GAAG;OACtD,UAAU;OACV,WAAU;iBAET,IAAe,kBAAkB;OAC3B,CAAA,CACL;QACF;;IACF,CAAA;GAIR,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,yEAA0E,CAAA,EAC5F,kBAAC,SAAD;SACE,MAAK;SACL,aAAY;SACZ,OAAO;SACP,WAAW,MAAM,GAAe,EAAE,OAAO,MAAM;SAC/C,WAAU;SACV,CAAA,CACE;;OACF,CAAA;MAGN,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM;AAEf,QADA,EAAgB,EAAE,OAAO,MAA4B,EACrD,EAAQ,EAAE;;OAEZ,WAAU;iBANZ;QAQE,kBAAC,UAAD;SAAQ,OAAM;mBAAM;SAAmB,CAAA;QACvC,kBAAC,UAAD;SAAQ,OAAM;mBAAS;SAAe,CAAA;QACtC,kBAAC,UAAD;SAAQ,OAAM;mBAAW;SAAiB,CAAA;QAC1C,kBAAC,UAAD;SAAQ,OAAM;mBAAU;SAAgB,CAAA;QACjC;;MAGT,kBAAC,UAAD;OACE,OAAO;OACP,WAAW,MAAM;AAEf,QADA,EAAc,EAAE,OAAO,MAA0B,EACjD,EAAQ,EAAE;;OAEZ,WAAU;iBANZ;QAQE,kBAAC,UAAD;SAAQ,OAAM;mBAAM;SAAkB,CAAA;QACtC,kBAAC,UAAD;SAAQ,OAAM;mBAAa;SAAmB,CAAA;QAC9C,kBAAC,UAAD;SAAQ,OAAM;mBAAe;SAAqB,CAAA;QAClD,kBAAC,UAAD;SAAQ,OAAM;mBAAgB;SAAsB,CAAA;QAC7C;;MACL;;IACF,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OAAO,WAAU;iBACf,kBAAC,MAAD,EAAA,UAAA;QACE,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAS,CAAA;QAC3E,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAS,CAAA;QAC3E,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAU,CAAA;QAC5E,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAW,CAAA;QAC7E,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAU,CAAA;QAC5E,kBAAC,MAAD;SAAI,WAAU;mBAAoD;SAAgB,CAAA;QAClF,kBAAC,MAAD;SAAI,WAAU;mBAAqD;SAAY,CAAA;QAC5E,EAAA,CAAA;OACC,CAAA,EACR,kBAAC,SAAD;OAAO,WAAU;iBACd,IACC,kBAAC,MAAD,EAAA,UACE,kBAAC,MAAD;QAAI,SAAS;QAAG,WAAU;kBAAwC;QAE7D,CAAA,EACF,CAAA,GACH,EAAgB,WAAW,IAC7B,kBAAC,MAAD,EAAA,UACE,kBAAC,MAAD;QAAI,SAAS;QAAG,WAAU;kBAAwC;QAE7D,CAAA,EACF,CAAA,GAEL,EAAgB,KAAK,MACnB,kBAAC,MAAD;QAAoB,WAAU;kBAA9B;SACE,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAyC,EAAO;WAAS,CAAA,EACtE,kBAAC,KAAD;WAAG,WAAU;qBAAiC,EAAO;WAAS,CAAA,CAC1D,EAAA,CAAA;UACH,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACG,GAAY,EAAO,KAAK,EACzB,kBAAC,QAAD;YAAM,WAAU;sBACb,EAAO,KAAK,QAAQ,KAAK,IAAI,CAAC,aAAa;YACvC,CAAA,CACH;;UACH,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAO,SAAS,eACb,GAAG,EAAO,MAAM,KAChB,EAAe,EAAO,MAAM;WAC3B,CAAA;UACJ,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,QAAD;WACE,WAAW,0DAA0D,GACnE,EAAO,OACR;qBAEA,EAAO;WACH,CAAA;UACJ,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBAAd,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBAAe,EAAO;YAAmB,CAAA,EACxD,EAAO,gBACN,kBAAC,QAAD;YAAM,WAAU;sBAAhB,CAAwC,OAAI,EAAO,aAAoB;cAErE;cACL,EAAO,gBACN,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,OAAD;YACE,WAAU;YACV,OAAO,EACL,OAAO,GAAG,KAAK,IACZ,EAAO,cAAc,EAAO,eAAgB,KAC7C,IACD,CAAC,IACH;YACD,CAAA;WACE,CAAA,CAEL;;SACL,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAO,aAAa,EAAW,EAAO,WAAW,GAAG;WAChD,CAAA;UACJ,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAW,YAAY,EAAO,KAAK;aAClD,WAAU;aACV,OAAM;uBAEN,kBAAC,GAAD,EAAK,WAAU,UAAW,CAAA;aACnB,CAAA;YACT,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAW,YAAY,EAAO,GAAG,OAAO;aACvD,WAAU;aACV,OAAM;uBAEN,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA;aACpB,CAAA;YACR,EAAO,WAAW,WACjB,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,EAAsB,EAAO;aAC5C,UAAU,KAAgB,MAAe,EAAO;aAChD,WAAU;aACV,OAAM;uBAEN,kBAAC,GAAD,EAAU,WAAU,UAAW,CAAA;aACxB,CAAA,GACP,EAAO,WAAW,aACpB,kBAAC,UAAD;aACE,MAAK;aACL,eAAe,GAAmB,EAAO,IAAI,EAAa,OAAO;aACjE,UAAU,MAAe,EAAO;aAChC,WAAU;aACV,OAAM;uBAEN,kBAAC,GAAD,EAAO,WAAU,UAAW,CAAA;aACrB,CAAA,GAET,kBAAC,UAAD;aACE,MAAK;aACL,UAAA;aACA,WAAU;aACV,OAAO,yBAAyB,EAAO,OAAO,aAAa,CAAC;uBAE5D,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;aACtB,CAAA;YAEP;;UACH,CAAA;SACF;UA3GI,EAAO,GA2GX,CACL;OAEE,CAAA,CACF;;KACJ,CAAA;IACF,CAAA;GAGL,IAAQ,MACP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAb;MAA6C;MAClC,IAAO,KAAY;MAAE;MAAE,KAAK,KAAK,IAAO,KAAK,IAAW,EAAM;MAAC;MAAK;MAAO;MAAI;MAEtF;QACJ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,KAAK,IAAI,GAAG,IAAI,EAAE,CAAC;OACjD,UAAU,MAAS;OACnB,WAAU;iBAEV,kBAAC,GAAD,EAAa,WAAU,UAAW,CAAA;OAC3B,CAAA;MACT,kBAAC,QAAD;OAAM,WAAU;iBAAhB;QACG,IAAO;QAAE;QAAI;QACT;;MACP,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,GAAS,MAAM,IAAI,EAAE;OACpC,UAAU,CAAC;OACX,WAAU;iBAEV,kBAAC,IAAD,EAAc,WAAU,UAAW,CAAA;OAC5B,CAAA;MACL;OACF;;GAIR,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAKE,kBAAC,IAAD,EAAU,WAAU,UAAW,CAAA,EAAA,iBAExB;;IACL,CAAA;GACF;;CAGR,eAAe,GAAiB,GAAkB;AAChD,MAAI;AAIF,GAHA,MAAM,GAAiB,EAAE,WAAW,EAAE,IAAI,GAAU,EAAE,CAAC,EACvD,MAAM,GAAS,EACf,EAAsB,KAAK,EAC3B,EAAY;IAAE,MAAM;IAAW,SAAS;IAAoC,CAAC;WACtE,GAAO;AAEd,GADA,QAAQ,MAAM,gCAAgC,EAAM,EACpD,EAAY;IACV,MAAM;IACN,SAAS,gCAAgC,aAAiB,QAAQ,EAAM,UAAU;IACnF,CAAC;;;CAIN,eAAe,GAAmB,GAAkB,GAAyB;AAC3E,IAAc,EAAS;AACvB,MAAI;AAGF,GAFA,MAAM,GAAa,EAAE,WAAW;IAAE,IAAI;IAAU,OAAO,EAAE,QAAQ,GAAW;IAAE,EAAE,CAAC,EACjF,MAAM,GAAS,EACf,EAAY;IACV,MAAM;IACN,SAAS,UAAU,MAAc,EAAa,SAAS,cAAc,cAAc;IACpF,CAAC;WACK,GAAO;AAEd,GADA,QAAQ,MAAM,mCAAmC,EAAM,EACvD,EAAY;IACV,MAAM;IACN,SAAS,4BAA4B,aAAiB,QAAQ,EAAM,UAAU;IAC/E,CAAC;YACM;AACR,KAAc,KAAK;;;CAIvB,SAAS,KAAe;EACtB,IAAM,IAA0B,EAAgB,KAAK,OAAoB;GACvE,MAAM,EAAO;GACb,MAAM,EAAO;GACb,aAAa,EAAO,eAAe;GACnC,QAAQ,EAAO;GACf,MAAM,EAAO;GACb,OAAO,EAAO;GACd,aAAa,EAAO;GACpB,cAAc,EAAO,gBAAgB;GACrC,YAAY,EAAO,cAAc;GAClC,EAAE;AAEH,MAAI,EAAK,WAAW,GAAG;AACrB,KAAY;IACV,MAAM;IACN,SAAS;IACV,CAAC;AACF;;EAGF,IAAM,IAAY,OAAO,KAAK,EAAK,GAAG,CAAC,KAAK,IAAI,EAC1C,IAAU,EACb,KAAK,MACJ,OAAO,OAAO,EAAI,CACf,KAAK,MAAU,IAAI,OAAO,EAAM,CAAC,WAAW,MAAK,OAAK,CAAC,GAAG,CAC1D,KAAK,IAAI,CACb,CACA,KAAK,KAAK,EAEP,IAAO,IAAI,KAAK,CAAC,GAAG,EAAU,IAAI,IAAU,EAAE,EAAE,MAAM,0BAA0B,CAAC,EACjF,IAAc,IAAI,gBAAgB,EAAK,EACvC,IAAO,SAAS,cAAc,IAAI;AAMxC,EALA,EAAK,OAAO,GACZ,EAAK,WAAW,uBAChB,EAAK,OAAO,EACZ,IAAI,gBAAgB,EAAY,EAEhC,EAAY;GACV,MAAM;GACN,SAAS,YAAY,EAAK,OAAO,SAAS,EAAK,WAAW,IAAI,KAAK,IAAI;GACxE,CAAC"}
|
|
1
|
+
{"version":3,"file":"CouponsListPage.js","names":[],"sources":["../../../../../src/billing/modules/coupons/pages/CouponsListPage.tsx"],"sourcesContent":["/**\n * Coupons List Page\n *\n * Admin interface for managing coupon codes:\n * - View all coupons\n * - Filter by status, type, scope\n * - Create new coupons\n * - Edit/deactivate existing coupons\n * - View usage analytics\n */\n\nimport { type FC, useState, useMemo, useEffect } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport {\n Plus,\n Search,\n Tag,\n TrendingUp,\n CheckCircle,\n AlertCircle,\n Percent,\n DollarSign,\n Package,\n Eye,\n Edit,\n Trash2,\n Download,\n PowerOff,\n Power,\n ChevronLeft,\n ChevronRight,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport {\n useListCouponsQuery,\n useDeactivateCouponMutation,\n useUpdateCouponMutation,\n} from '../../../../generated/global-operations';\nimport { CouponStatus } from '../../../../generated/global-types';\nimport type {\n CouponScope as GQLCouponScope,\n CouponStatus as GQLCouponStatus,\n CouponType as GQLCouponType,\n Coupon as GQLCoupon,\n} from '../../../../generated/global-types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ntype Coupon = GQLCoupon;\n\ntype CouponStatusFilter = 'ALL' | GQLCouponStatus;\ntype CouponTypeFilter = 'ALL' | GQLCouponType;\ntype CouponScopeFilter = 'ALL' | GQLCouponScope;\n\ninterface CouponExportRow {\n code: string;\n name: string;\n description: string;\n status: string;\n type: string;\n value: number;\n currentUses: number;\n maxTotalUses: number | '';\n validUntil: string;\n}\n\nexport const CouponsListPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const navigateTo = useBillingNavigate();\n const permissions = useBillingPermissions();\n\n const PAGE_SIZE = 20;\n\n // State\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState<CouponStatusFilter>('ALL');\n const [typeFilter, setTypeFilter] = useState<CouponTypeFilter>('ALL');\n const [scopeFilter] = useState<CouponScopeFilter>('ALL');\n const [page, setPage] = useState(0);\n const [couponToDeactivate, setCouponToDeactivate] = useState<Coupon | null>(null);\n const [togglingId, setTogglingId] = useState<string | null>(null);\n const [feedback, setFeedback] = useState<{\n tone: 'success' | 'error';\n message: string;\n } | null>(null);\n\n // Fetch coupons from backend (server-side pagination)\n const { data, loading, refetch } = useListCouponsQuery({\n variables: {\n limit: PAGE_SIZE,\n offset: page * PAGE_SIZE,\n filter: {\n status: statusFilter !== 'ALL' ? (statusFilter as GQLCouponStatus) : undefined,\n type: typeFilter !== 'ALL' ? (typeFilter as GQLCouponType) : undefined,\n scope: scopeFilter !== 'ALL' ? (scopeFilter as GQLCouponScope) : undefined,\n search: searchQuery || undefined,\n },\n },\n });\n\n const coupons = useMemo<Coupon[]>(\n () => data?.listCoupons?.coupons ?? [],\n [data?.listCoupons?.coupons]\n );\n const total = data?.listCoupons?.total ?? 0;\n const hasMore = data?.listCoupons?.hasMore ?? false;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n // Mutations\n const [deactivateCoupon, { loading: deactivating }] = useDeactivateCouponMutation();\n const [updateCoupon] = useUpdateCouponMutation();\n\n // With server-side filtering, filteredCoupons === coupons\n const filteredCoupons = coupons;\n\n // Reset to first page when filters change\n useEffect(() => {\n setPage(0);\n }, [searchQuery, statusFilter, typeFilter, scopeFilter]);\n\n // Statistics from current page (best effort without a separate aggregation query)\n const stats = useMemo(() => {\n const activeCoupons = coupons.filter((c: Coupon) => c.status === 'ACTIVE').length;\n const totalUses = coupons.reduce((sum: number, c: Coupon) => sum + c.currentUses, 0);\n const totalDiscountValue = coupons.reduce((sum: number, c: Coupon) => {\n if (c.type === 'FIXED_AMOUNT') return sum + c.value * c.currentUses;\n return sum;\n }, 0);\n return { totalCoupons: total, activeCoupons, totalUses, totalDiscountValue };\n }, [coupons, total]);\n\n // Check permissions\n if (!permissions.canManageDiscounts) {\n return <AccessDenied message=\"You don't have permission to manage coupons.\" />;\n }\n\n // Format currency\n const formatCurrency = (amount: number) => {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency: 'USD',\n }).format(amount);\n };\n\n // Format date\n const formatDate = (dateString: string) => {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n });\n };\n\n // Get status color\n const getStatusColor = (status: string) => {\n switch (status) {\n case 'ACTIVE':\n return 'text-status-success-text bg-status-success-bg-subtle';\n case 'INACTIVE':\n return 'text-text-secondary bg-bg-canvas';\n case 'EXPIRED':\n return 'text-status-error-text bg-status-error-bg-subtle';\n default:\n return 'text-text-secondary bg-bg-canvas';\n }\n };\n\n // Get type icon\n const getTypeIcon = (type: string) => {\n switch (type) {\n case 'PERCENTAGE':\n return <Percent className=\"size-4\" />;\n case 'FIXED_AMOUNT':\n return <DollarSign className=\"size-4\" />;\n case 'FREE_SHIPPING':\n return <Package className=\"size-4\" />;\n default:\n return <Tag className=\"size-4\" />;\n }\n };\n\n return (\n <div className=\"max-w-7xl mx-auto p-4 sm:p-6 space-y-6\">\n {/* Header */}\n <div className=\"flex items-center justify-between\">\n <PageHeader\n title={tr('billing.coupons.title', 'Coupons')}\n description=\"Manage discount codes and promotional offers\"\n icon={<Tag className=\"size-6\" />}\n />\n\n <button\n type=\"button\"\n onClick={() => navigateTo('/coupons/create')}\n className=\"inline-flex items-center gap-2 rounded-button bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text 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 <Plus className=\"size-4\" />\n Create Coupon\n </button>\n </div>\n\n {/* Statistics Cards */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4\">\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Coupons</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <Tag className=\"size-4 text-accent-blue\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">{stats.activeCoupons} active</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Uses</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <TrendingUp className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">{stats.totalUses}</p>\n <p className=\"text-xs text-text-muted mt-1\">All time</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Total Discount</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-[var(--color-accent-soft)]\">\n <DollarSign className=\"size-4 text-accent-purple\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {formatCurrency(stats.totalDiscountValue)}\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Fixed amount only</p>\n </div>\n\n <div className=\"rounded-card border border-border-subtle bg-bg-surface p-5 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm text-text-secondary\">Active Rate</span>\n <span className=\"flex size-9 items-center justify-center rounded-lg bg-status-success-bg-subtle\">\n <CheckCircle className=\"size-4 text-status-success-text\" />\n </span>\n </div>\n <p className=\"text-2xl font-semibold text-text-primary tabular-nums\">\n {stats.totalCoupons > 0\n ? Math.round((stats.activeCoupons / stats.totalCoupons) * 100)\n : 0}\n %\n </p>\n <p className=\"text-xs text-text-muted mt-1\">Of all coupons</p>\n </div>\n </div>\n\n {feedback && (\n <div\n className={\n feedback.tone === 'success'\n ? 'flex items-start gap-3 rounded-lg border border-status-success-border bg-status-success-bg-subtle p-4 text-status-success-text'\n : 'flex items-start gap-3 rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4 text-status-error-text'\n }\n role=\"status\"\n aria-live=\"polite\"\n >\n {feedback.tone === 'success' ? (\n <CheckCircle className=\"mt-0.5 size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n )}\n <p className=\"text-sm font-medium\">{feedback.message}</p>\n </div>\n )}\n\n {couponToDeactivate && (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg-subtle p-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"font-medium text-status-error-text\">\n Deactivate coupon <span className=\"font-mono\">{couponToDeactivate.code}</span>?\n </p>\n <p className=\"mt-1 text-sm text-status-error-text\">\n This will prevent new checkouts from applying this coupon.\n </p>\n </div>\n <div className=\"flex gap-2 self-end sm:self-auto\">\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(null)}\n className=\"rounded-button border border-border-subtle bg-bg-surface px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={() => handleDeactivate(couponToDeactivate.id)}\n disabled={deactivating}\n className=\"rounded-button bg-action-danger-bg px-3 py-2 text-sm font-medium text-action-danger-text transition-colors hover:bg-action-danger-bgHover disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {deactivating ? 'Deactivating…' : 'Deactivate'}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg p-4\">\n <div className=\"grid grid-cols-1 md:grid-cols-4 gap-4\">\n {/* Search */}\n <div className=\"md:col-span-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search coupons...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-10 pr-4 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 </div>\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) => {\n setStatusFilter(e.target.value as CouponStatusFilter);\n setPage(0);\n }}\n className=\"px-4 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 <option value=\"ALL\">All Status</option>\n <option value=\"ACTIVE\">Active</option>\n <option value=\"INACTIVE\">Inactive</option>\n <option value=\"EXPIRED\">Expired</option>\n </select>\n\n {/* Type Filter */}\n <select\n value={typeFilter}\n onChange={(e) => {\n setTypeFilter(e.target.value as CouponTypeFilter);\n setPage(0);\n }}\n className=\"px-4 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 <option value=\"ALL\">All Types</option>\n <option value=\"PERCENTAGE\">Percentage</option>\n <option value=\"FIXED_AMOUNT\">Fixed Amount</option>\n <option value=\"FREE_SHIPPING\">Free Shipping</option>\n </select>\n </div>\n </div>\n\n {/* Coupons Table */}\n <div className=\"bg-bg-surface border border-border-subtle rounded-card overflow-hidden shadow-[var(--shadow-elevation-1)]\">\n <div className=\"overflow-x-auto\">\n <table className=\"w-full\">\n <thead className=\"sticky top-0 z-10 bg-bg-sunken border-b border-border-subtle\">\n <tr>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Code\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Type\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Value\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Status\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Usage\n </th>\n <th className=\"text-left px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Valid Until\n </th>\n <th className=\"text-right px-4 py-3 text-xs font-medium uppercase tracking-wide text-text-muted\">\n Actions\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-border-subtle\">\n {loading ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-text-muted\">\n Loading coupons…\n </td>\n </tr>\n ) : filteredCoupons.length === 0 ? (\n <tr>\n <td colSpan={7} className=\"p-8 text-center text-text-muted\">\n No coupons found\n </td>\n </tr>\n ) : (\n filteredCoupons.map((coupon: Coupon) => (\n <tr key={coupon.id} className=\"hover:bg-bg-sunken/50\">\n <td className=\"p-4\">\n <div>\n <p className=\"font-medium text-text-primary font-mono\">{coupon.code}</p>\n <p className=\"text-sm text-text-muted\">{coupon.name}</p>\n </div>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center gap-2\">\n {getTypeIcon(coupon.type)}\n <span className=\"text-sm text-text-primary capitalize\">\n {coupon.type.replace('_', ' ').toLowerCase()}\n </span>\n </div>\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm font-medium text-text-primary\">\n {coupon.type === 'PERCENTAGE'\n ? `${coupon.value}%`\n : formatCurrency(coupon.value)}\n </span>\n </td>\n <td className=\"p-4\">\n <span\n className={`inline-flex px-2 py-1 rounded-full text-xs font-medium ${getStatusColor(\n coupon.status\n )}`}\n >\n {coupon.status}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"text-sm text-text-primary\">\n <span className=\"font-medium\">{coupon.currentUses}</span>\n {coupon.maxTotalUses && (\n <span className=\"text-text-muted\"> / {coupon.maxTotalUses}</span>\n )}\n </div>\n {coupon.maxTotalUses && (\n <div className=\"w-full bg-bg-sunken rounded-full h-1.5 mt-1\">\n <div\n className=\"bg-action-primary-bg h-1.5 rounded-full\"\n style={{\n width: `${Math.min(\n (coupon.currentUses / coupon.maxTotalUses) * 100,\n 100\n )}%`,\n }}\n />\n </div>\n )}\n </td>\n <td className=\"p-4\">\n <span className=\"text-sm text-text-muted\">\n {coupon.validUntil ? formatDate(coupon.validUntil) : 'No expiry'}\n </span>\n </td>\n <td className=\"p-4\">\n <div className=\"flex items-center justify-end gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"View details\"\n >\n <Eye className=\"size-4\" />\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo(`/coupons/${coupon.id}/edit`)}\n className=\"p-2 rounded-lg hover:bg-bg-sunken text-text-muted hover:text-text-primary\"\n title=\"Edit coupon\"\n >\n <Edit className=\"size-4\" />\n </button>\n {coupon.status === 'ACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => setCouponToDeactivate(coupon)}\n disabled={deactivating || togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-error-bg-subtle text-text-muted hover:text-status-error-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Deactivate coupon\"\n >\n <PowerOff className=\"size-4\" />\n </button>\n ) : coupon.status === 'INACTIVE' ? (\n <button\n type=\"button\"\n onClick={() => handleToggleStatus(coupon.id, CouponStatus.Active)}\n disabled={togglingId === coupon.id}\n className=\"p-2 rounded-lg hover:bg-status-success-bg-subtle text-text-muted hover:text-status-success-text disabled:opacity-50 disabled:cursor-not-allowed\"\n title=\"Activate coupon\"\n >\n <Power className=\"size-4\" />\n </button>\n ) : (\n <button\n type=\"button\"\n disabled\n className=\"p-2 rounded-lg text-text-muted/40 cursor-not-allowed\"\n title={`Cannot change status (${coupon.status.toLowerCase()})`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n </div>\n\n {/* Pagination */}\n {total > PAGE_SIZE && (\n <div className=\"flex items-center justify-between px-2\">\n <p className=\"text-sm text-text-muted\">\n Showing {page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, total)} of {total}{' '}\n coupons\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronLeft className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-primary font-medium\">\n {page + 1} / {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => p + 1)}\n disabled={!hasMore}\n className=\"p-2 rounded-lg border border-border-subtle hover:bg-bg-sunken disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Export Button */}\n <div className=\"flex justify-end\">\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"px-4 py-2 rounded-lg border border-border-subtle text-text-primary hover:bg-bg-sunken flex items-center gap-2\"\n >\n <Download className=\"size-4\" />\n Export Coupons\n </button>\n </div>\n </div>\n );\n\n async function handleDeactivate(couponId: string) {\n try {\n await deactivateCoupon({ variables: { id: couponId } });\n await refetch();\n setCouponToDeactivate(null);\n setFeedback({ tone: 'success', message: 'Coupon deactivated successfully.' });\n } catch (error) {\n console.error('Failed to deactivate coupon:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to deactivate coupon: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n }\n }\n\n async function handleToggleStatus(couponId: string, newStatus: CouponStatus) {\n setTogglingId(couponId);\n try {\n await updateCoupon({ variables: { id: couponId, input: { status: newStatus } } });\n await refetch();\n setFeedback({\n tone: 'success',\n message: `Coupon ${newStatus === CouponStatus.Active ? 'activated' : 'deactivated'} successfully.`,\n });\n } catch (error) {\n console.error('Failed to update coupon status:', error);\n setFeedback({\n tone: 'error',\n message: `Failed to update status: ${error instanceof Error ? error.message : 'Unknown error'}`,\n });\n } finally {\n setTogglingId(null);\n }\n }\n\n function handleExport() {\n const rows: CouponExportRow[] = filteredCoupons.map((coupon: Coupon) => ({\n code: coupon.code,\n name: coupon.name,\n description: coupon.description ?? '',\n status: coupon.status,\n type: coupon.type,\n value: coupon.value,\n currentUses: coupon.currentUses,\n maxTotalUses: coupon.maxTotalUses ?? '',\n validUntil: coupon.validUntil ?? '',\n }));\n\n if (rows.length === 0) {\n setFeedback({\n tone: 'error',\n message: 'There are no coupons to export for the current filters.',\n });\n return;\n }\n\n const csvHeader = Object.keys(rows[0]).join(',');\n const csvBody = rows\n .map((row: CouponExportRow) =>\n Object.values(row)\n .map((value) => `\"${String(value).replaceAll('\"', '\"\"')}\"`)\n .join(',')\n )\n .join('\\n');\n\n const blob = new Blob([`${csvHeader}\\n${csvBody}`], { type: 'text/csv;charset=utf-8' });\n const downloadUrl = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = downloadUrl;\n link.download = 'billing-coupons.csv';\n link.click();\n URL.revokeObjectURL(downloadUrl);\n\n setFeedback({\n tone: 'success',\n message: `Exported ${rows.length} coupon${rows.length === 1 ? '' : 's'} to CSV.`,\n });\n }\n};\n"],"mappings":"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"}
|