@burdenoff/microfe-billing 2026.530.2 → 2026.530.4
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 +110 -71
- package/dist/billing/BillingAdminRoutes.js.map +1 -1
- package/dist/billing/BillingRoot.js +81 -73
- package/dist/billing/BillingRoot.js.map +1 -1
- package/dist/billing/BillingUserRoutes.js +103 -90
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/billing/index.js +2 -1
- package/dist/billing/modules/billing/pages/InvoicesListPage.js +135 -126
- package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/UserRefundsPage.js +417 -0
- package/dist/billing/modules/billing/pages/UserRefundsPage.js.map +1 -0
- package/dist/billing/modules/billing/pages/index.js +1 -0
- package/dist/billing/modules/checkout/hooks/useCheckout.js +163 -161
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +344 -167
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/plans/constants/featuredQuotas.js +37 -0
- package/dist/billing/modules/plans/constants/featuredQuotas.js.map +1 -0
- package/dist/billing/modules/plans/index.js +2 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +205 -0
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -0
- package/dist/billing/modules/plans/pages/PlanCreatePage.js +51 -33
- package/dist/billing/modules/plans/pages/PlanCreatePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanEditPage.js +78 -60
- package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js +246 -126
- package/dist/billing/modules/plans/pages/PlansBrowsePage.js.map +1 -1
- package/dist/billing/modules/plans/pages/index.js +1 -0
- package/dist/billing/modules/promotions/index.js +4 -0
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +289 -0
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -0
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js +168 -0
- package/dist/billing/modules/promotions/pages/PromotionsListPage.js.map +1 -0
- package/dist/billing/modules/promotions/pages/index.js +2 -0
- package/dist/billing/shared/types/graphql.js.map +1 -1
- package/dist/generated/global-operations.js +422 -290
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js +3 -1
- package/dist/generated/global-types.js.map +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ var p = () => {
|
|
|
16
16
|
let { t: p } = f(), m = (e, t) => {
|
|
17
17
|
let n = p(e);
|
|
18
18
|
return n === e ? t : n;
|
|
19
|
-
}, h = a(), g = i(), { quotas: _, isLoading: v } = s({ onlyActive: !0 }), { createPlan: y, isCreating: b } = o(), [x, S] = c(""), [C, w] = c(""), [T, E] = c("USD"), [D, O] = c(r.MONTHLY), [k, A] = c(""), [j, M] = c(
|
|
19
|
+
}, h = a(), g = i(), { quotas: _, isLoading: v } = s({ onlyActive: !0 }), { createPlan: y, isCreating: b } = o(), [x, S] = c(""), [C, w] = c(""), [T, E] = c("USD"), [D, O] = c(r.MONTHLY), [k, A] = c(""), [j, M] = c(!0), [N, P] = c([]), [F, I] = c([]), [L, R] = c(null), [z, B] = c(!1);
|
|
20
20
|
if (!g.canManagePlans) return /* @__PURE__ */ u(e, { message: m("billing.plans.noCreatePermission", "You don't have permission to create plans.") });
|
|
21
21
|
if (v) return /* @__PURE__ */ d("div", {
|
|
22
22
|
className: "space-y-6 p-6",
|
|
@@ -31,16 +31,16 @@ var p = () => {
|
|
|
31
31
|
].map((e) => /* @__PURE__ */ u("div", { className: "h-16 bg-muted animate-pulse rounded" }, e))
|
|
32
32
|
})]
|
|
33
33
|
});
|
|
34
|
-
let
|
|
35
|
-
|
|
34
|
+
let V = () => {
|
|
35
|
+
P([{
|
|
36
36
|
quotaId: "",
|
|
37
37
|
quantity: 1
|
|
38
|
-
}, ...
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
},
|
|
42
|
-
let r = [...
|
|
43
|
-
t === "quotaId" ? r[e].quotaId = n : r[e].quantity = Number(n),
|
|
38
|
+
}, ...N]);
|
|
39
|
+
}, H = (e) => {
|
|
40
|
+
P(N.filter((t, n) => n !== e));
|
|
41
|
+
}, U = (e, t, n) => {
|
|
42
|
+
let r = [...N];
|
|
43
|
+
t === "quotaId" ? r[e].quotaId = n : r[e].quantity = Number(n), P(r);
|
|
44
44
|
};
|
|
45
45
|
return /* @__PURE__ */ d("div", {
|
|
46
46
|
className: "space-y-6 p-6",
|
|
@@ -49,24 +49,24 @@ var p = () => {
|
|
|
49
49
|
description: m("billing.plans.createNewPlanDesc", "Set up a new pricing plan with quotas")
|
|
50
50
|
}), /* @__PURE__ */ d("form", {
|
|
51
51
|
onSubmit: async (e) => {
|
|
52
|
-
if (e.preventDefault(),
|
|
53
|
-
|
|
52
|
+
if (e.preventDefault(), R(null), B(!1), !x.trim()) {
|
|
53
|
+
R("Plan name is required");
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
if (!C || isNaN(Number(C)) || Number(C) < 0) {
|
|
57
|
-
|
|
57
|
+
R("Valid price is required");
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
if (!k.trim()) {
|
|
61
|
-
|
|
61
|
+
R("Product ID is required");
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
|
-
if (
|
|
65
|
-
|
|
64
|
+
if (N.length === 0) {
|
|
65
|
+
R("At least one quota is required");
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
if (
|
|
69
|
-
|
|
68
|
+
if (N.filter((e) => e.quotaId === "").length > 0) {
|
|
69
|
+
R("Please select a quota for all allocations");
|
|
70
70
|
return;
|
|
71
71
|
}
|
|
72
72
|
let t = {
|
|
@@ -75,28 +75,29 @@ var p = () => {
|
|
|
75
75
|
currency: T,
|
|
76
76
|
duration: D,
|
|
77
77
|
productId: k.trim(),
|
|
78
|
-
|
|
78
|
+
isPurchasable: j,
|
|
79
|
+
quotas: N.map((e) => ({
|
|
79
80
|
quotaId: e.quotaId,
|
|
80
81
|
quantity: e.quantity
|
|
81
82
|
})),
|
|
82
|
-
...
|
|
83
|
+
...F.length > 0 ? { currencyPriceOverrides: F } : {}
|
|
83
84
|
};
|
|
84
85
|
try {
|
|
85
|
-
await y(t),
|
|
86
|
+
await y(t), B(!0), setTimeout(() => h("/plans"), 1500);
|
|
86
87
|
} catch (e) {
|
|
87
|
-
|
|
88
|
+
R(e instanceof Error ? e.message : "Failed to create plan");
|
|
88
89
|
}
|
|
89
90
|
},
|
|
90
91
|
className: "space-y-6",
|
|
91
92
|
children: [
|
|
92
|
-
|
|
93
|
+
L && /* @__PURE__ */ u("div", {
|
|
93
94
|
className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
|
|
94
95
|
children: /* @__PURE__ */ u("p", {
|
|
95
96
|
className: "text-sm text-status-error-text",
|
|
96
|
-
children:
|
|
97
|
+
children: L
|
|
97
98
|
})
|
|
98
99
|
}),
|
|
99
|
-
|
|
100
|
+
z && /* @__PURE__ */ u("div", {
|
|
100
101
|
className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
|
|
101
102
|
children: /* @__PURE__ */ u("p", {
|
|
102
103
|
className: "text-sm text-status-success-text",
|
|
@@ -226,7 +227,24 @@ var p = () => {
|
|
|
226
227
|
value: r.YEARLY,
|
|
227
228
|
children: "Yearly"
|
|
228
229
|
})]
|
|
229
|
-
})] })
|
|
230
|
+
})] }),
|
|
231
|
+
/* @__PURE__ */ d("div", {
|
|
232
|
+
className: "flex items-start justify-between gap-4 pt-1",
|
|
233
|
+
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
234
|
+
className: "text-sm font-medium text-foreground",
|
|
235
|
+
children: m("billing.plans.isPurchasableLabel", "Publicly purchasable")
|
|
236
|
+
}), /* @__PURE__ */ u("p", {
|
|
237
|
+
className: "text-xs text-muted-foreground mt-0.5",
|
|
238
|
+
children: j ? m("billing.plans.isPurchasableOn", "Visible on pricing page — users can subscribe directly.") : m("billing.plans.isPurchasableOff", "Internal / custom plan — hidden from pricing page, assign via API only.")
|
|
239
|
+
})] }), /* @__PURE__ */ u("button", {
|
|
240
|
+
type: "button",
|
|
241
|
+
role: "switch",
|
|
242
|
+
"aria-checked": j,
|
|
243
|
+
onClick: () => M((e) => !e),
|
|
244
|
+
className: `relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${j ? "bg-primary" : "bg-muted-foreground/30"}`,
|
|
245
|
+
children: /* @__PURE__ */ u("span", { className: `inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${j ? "translate-x-5" : "translate-x-0"}` })
|
|
246
|
+
})]
|
|
247
|
+
})
|
|
230
248
|
]
|
|
231
249
|
})]
|
|
232
250
|
}), /* @__PURE__ */ d("div", {
|
|
@@ -238,7 +256,7 @@ var p = () => {
|
|
|
238
256
|
children: [m("billing.plans.quotaAllocations", "Quota Allocations"), " *"]
|
|
239
257
|
}), /* @__PURE__ */ u("button", {
|
|
240
258
|
type: "button",
|
|
241
|
-
onClick:
|
|
259
|
+
onClick: V,
|
|
242
260
|
disabled: _.length === 0,
|
|
243
261
|
className: "px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors disabled:opacity-50",
|
|
244
262
|
children: "+ Add Quota"
|
|
@@ -248,10 +266,10 @@ var p = () => {
|
|
|
248
266
|
children: _.length === 0 ? /* @__PURE__ */ u("p", {
|
|
249
267
|
className: "text-sm text-muted-foreground",
|
|
250
268
|
children: m("billing.plans.noQuotasAvailable", "No quotas available. Please create quotas first.")
|
|
251
|
-
}) :
|
|
269
|
+
}) : N.length === 0 ? /* @__PURE__ */ u("p", {
|
|
252
270
|
className: "text-sm text-muted-foreground",
|
|
253
271
|
children: m("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
|
|
254
|
-
}) :
|
|
272
|
+
}) : N.map((e, t) => {
|
|
255
273
|
let n = _.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
|
|
256
274
|
return /* @__PURE__ */ d("div", {
|
|
257
275
|
className: "p-3 border border-border rounded-lg bg-muted/20 space-y-3",
|
|
@@ -259,7 +277,7 @@ var p = () => {
|
|
|
259
277
|
className: "flex items-center gap-3",
|
|
260
278
|
children: [/* @__PURE__ */ d("select", {
|
|
261
279
|
value: e.quotaId,
|
|
262
|
-
onChange: (e) =>
|
|
280
|
+
onChange: (e) => U(t, "quotaId", e.target.value),
|
|
263
281
|
className: `flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${e.quotaId === "" ? "text-muted-foreground" : "text-foreground"}`,
|
|
264
282
|
children: [/* @__PURE__ */ u("option", {
|
|
265
283
|
value: "",
|
|
@@ -271,7 +289,7 @@ var p = () => {
|
|
|
271
289
|
}, e.id))]
|
|
272
290
|
}), /* @__PURE__ */ u("button", {
|
|
273
291
|
type: "button",
|
|
274
|
-
onClick: () =>
|
|
292
|
+
onClick: () => H(t),
|
|
275
293
|
className: "p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors",
|
|
276
294
|
title: "Remove quota",
|
|
277
295
|
children: /* @__PURE__ */ u("svg", {
|
|
@@ -321,7 +339,7 @@ var p = () => {
|
|
|
321
339
|
type: "number",
|
|
322
340
|
min: "1",
|
|
323
341
|
value: e.quantity,
|
|
324
|
-
onChange: (e) =>
|
|
342
|
+
onChange: (e) => U(t, "quantity", e.target.value),
|
|
325
343
|
className: "w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center"
|
|
326
344
|
})]
|
|
327
345
|
}), /* @__PURE__ */ d("div", {
|
|
@@ -341,8 +359,8 @@ var p = () => {
|
|
|
341
359
|
})]
|
|
342
360
|
}),
|
|
343
361
|
/* @__PURE__ */ u(n, {
|
|
344
|
-
overrides:
|
|
345
|
-
onChange:
|
|
362
|
+
overrides: F,
|
|
363
|
+
onChange: I
|
|
346
364
|
}),
|
|
347
365
|
/* @__PURE__ */ d("div", {
|
|
348
366
|
className: "flex items-center gap-3 pt-4",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanCreatePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Create Page\n * Form to create a new plan\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport {\n PlanDuration,\n type CreatePlanInput,\n type CurrencyPriceOverrideInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanCreatePage: 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 const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createPlan, isCreating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [productId, setProductId] = useState('');\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noCreatePermission',\n \"You don't have permission to create plans.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (!productId.trim()) {\n setError('Product ID is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: CreatePlanInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await createPlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/plans'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.plans.createNewPlan', 'Create New Plan')}\n description={tr('billing.plans.createNewPlanDesc', 'Set up a new pricing plan with quotas')}\n />\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.createSuccess', 'Plan created successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('billing.plans.productIdDesc', 'The product this plan belongs to')}\n </p>\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"price\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border rounded-lg bg-muted/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${\n allocation.quotaId === '' ? 'text-muted-foreground' : 'text-foreground'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-muted-foreground\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-foreground\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating || quotas.length === 0}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.plans.creating', 'Creating...')\n : tr('billing.plans.createPlanBtn', 'Create Plan')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanCreatePage.js","names":[],"sources":["../../../../../src/billing/modules/plans/pages/PlanCreatePage.tsx"],"sourcesContent":["/**\n * Plans Module - Plan Create Page\n * Form to create a new plan\n */\n\nimport { useState, type FC, type FormEvent } from 'react';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { usePlanMutations, useQuotas } from '../hooks';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { AccessDenied, CurrencyPriceOverridesEditor, PageHeader } from '../../../shared/components';\nimport {\n PlanDuration,\n type CreatePlanInput,\n type CurrencyPriceOverrideInput,\n} from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface QuotaAllocation {\n quotaId: string;\n quantity: number;\n}\n\nexport const PlanCreatePage: 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 const { quotas, isLoading: isQuotasLoading } = useQuotas({ onlyActive: true });\n const { createPlan, isCreating } = usePlanMutations();\n\n // Form state\n const [name, setName] = useState('');\n const [price, setPrice] = useState('');\n const [currency, setCurrency] = useState('USD');\n const [duration, setDuration] = useState<PlanDuration>(PlanDuration.MONTHLY);\n const [productId, setProductId] = useState('');\n const [isPurchasable, setIsPurchasable] = useState(true);\n const [quotaAllocations, setQuotaAllocations] = useState<QuotaAllocation[]>([]);\n const [currencyPriceOverrides, setCurrencyPriceOverrides] = useState<\n CurrencyPriceOverrideInput[]\n >([]);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState(false);\n\n // Permission check\n if (!permissions.canManagePlans) {\n return (\n <AccessDenied\n message={tr(\n 'billing.plans.noCreatePermission',\n \"You don't have permission to create plans.\"\n )}\n />\n );\n }\n\n // Loading state\n if (isQuotasLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-muted animate-pulse rounded\" />\n <div className=\"max-w-2xl space-y-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-muted animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const handleAddQuota = () => {\n // Add new quota at the top with empty quotaId (placeholder)\n setQuotaAllocations([{ quotaId: '', quantity: 1 }, ...quotaAllocations]);\n };\n\n const handleRemoveQuota = (index: number) => {\n setQuotaAllocations(quotaAllocations.filter((_, i) => i !== index));\n };\n\n const handleQuotaChange = (\n index: number,\n field: 'quotaId' | 'quantity',\n value: string | number\n ) => {\n const updated = [...quotaAllocations];\n if (field === 'quotaId') {\n updated[index].quotaId = value as string;\n } else {\n updated[index].quantity = Number(value);\n }\n setQuotaAllocations(updated);\n };\n\n const handleSubmit = async (e: FormEvent) => {\n e.preventDefault();\n setError(null);\n setSuccess(false);\n\n // Validation\n if (!name.trim()) {\n setError('Plan name is required');\n return;\n }\n if (!price || isNaN(Number(price)) || Number(price) < 0) {\n setError('Valid price is required');\n return;\n }\n if (!productId.trim()) {\n setError('Product ID is required');\n return;\n }\n if (quotaAllocations.length === 0) {\n setError('At least one quota is required');\n return;\n }\n // Check for unselected quotas\n const unselectedQuotas = quotaAllocations.filter((a) => a.quotaId === '');\n if (unselectedQuotas.length > 0) {\n setError('Please select a quota for all allocations');\n return;\n }\n\n const input: CreatePlanInput = {\n name: name.trim(),\n price: Number(price),\n currency,\n duration,\n productId: productId.trim(),\n isPurchasable,\n quotas: quotaAllocations.map((a) => ({\n quotaId: a.quotaId,\n quantity: a.quantity,\n })),\n ...(currencyPriceOverrides.length > 0 ? { currencyPriceOverrides } : {}),\n };\n\n try {\n await createPlan(input);\n setSuccess(true);\n // Navigate back after short delay\n setTimeout(() => navigateTo('/plans'), 1500);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create plan');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.plans.createNewPlan', 'Create New Plan')}\n description={tr('billing.plans.createNewPlanDesc', 'Set up a new pricing plan with quotas')}\n />\n\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Error/Success Messages */}\n {error && (\n <div className=\"p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-error-text\">{error}</p>\n </div>\n )}\n {success && (\n <div className=\"p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg\">\n <p className=\"text-sm text-status-success-text\">\n {tr('billing.plans.createSuccess', 'Plan created successfully! Redirecting...')}\n </p>\n </div>\n )}\n\n {/* Two-column layout on desktop */}\n <div className=\"grid grid-cols-1 lg:grid-cols-2 gap-6\">\n {/* Basic Info */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.basicInfo', 'Basic Information')}\n </h2>\n </div>\n <div className=\"p-4 space-y-4\">\n {/* Name */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.planNameLabel', 'Plan Name')} *\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.planNamePlaceholder', 'e.g., Pro Plan')}\n required\n />\n </div>\n\n {/* Product ID */}\n <div>\n <label\n htmlFor=\"productId\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.productId', 'Product ID')} *\n </label>\n <input\n id=\"productId\"\n type=\"text\"\n value={productId}\n onChange={(e) => setProductId(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder={tr('billing.plans.productIdPlaceholder', 'e.g., prod_abc123')}\n required\n />\n <p className=\"text-xs text-muted-foreground mt-1\">\n {tr('billing.plans.productIdDesc', 'The product this plan belongs to')}\n </p>\n </div>\n\n {/* Price & Currency */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <label htmlFor=\"price\" className=\"block text-sm font-medium text-foreground mb-1\">\n {tr('billing.plans.priceLabel', 'Price')} *\n </label>\n <input\n id=\"price\"\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={price}\n onChange={(e) => setPrice(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n placeholder=\"49.99\"\n required\n />\n </div>\n <div>\n <label\n htmlFor=\"currency\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.currencyLabel', 'Currency')} *\n </label>\n <select\n id=\"currency\"\n value={currency}\n onChange={(e) => setCurrency(e.target.value)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"USD\">USD ($)</option>\n <option value=\"EUR\">EUR</option>\n <option value=\"GBP\">GBP</option>\n <option value=\"INR\">INR</option>\n <option value=\"AUD\">AUD</option>\n <option value=\"CAD\">CAD</option>\n <option value=\"JPY\">JPY</option>\n <option value=\"SGD\">SGD</option>\n </select>\n </div>\n </div>\n\n {/* Duration */}\n <div>\n <label\n htmlFor=\"duration\"\n className=\"block text-sm font-medium text-foreground mb-1\"\n >\n {tr('billing.plans.billingPeriodLabel', 'Billing Period')} *\n </label>\n <select\n id=\"duration\"\n value={duration}\n onChange={(e) => setDuration(e.target.value as PlanDuration)}\n className=\"w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value={PlanDuration.MONTHLY}>Monthly</option>\n <option value={PlanDuration.YEARLY}>Yearly</option>\n </select>\n </div>\n\n {/* Purchasable toggle */}\n <div className=\"flex items-start justify-between gap-4 pt-1\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n {tr('billing.plans.isPurchasableLabel', 'Publicly purchasable')}\n </p>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {isPurchasable\n ? tr(\n 'billing.plans.isPurchasableOn',\n 'Visible on pricing page — users can subscribe directly.'\n )\n : tr(\n 'billing.plans.isPurchasableOff',\n 'Internal / custom plan — hidden from pricing page, assign via API only.'\n )}\n </p>\n </div>\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={isPurchasable}\n onClick={() => setIsPurchasable((v) => !v)}\n className={`relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${\n isPurchasable ? 'bg-primary' : 'bg-muted-foreground/30'\n }`}\n >\n <span\n className={`inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${\n isPurchasable ? 'translate-x-5' : 'translate-x-0'\n }`}\n />\n </button>\n </div>\n </div>\n </div>\n\n {/* Quota Allocations */}\n <div className=\"border border-border rounded-lg bg-card overflow-hidden h-fit\">\n <div className=\"p-4 border-b border-border bg-muted/30 flex items-center justify-between\">\n <h2 className=\"font-semibold text-foreground\">\n {tr('billing.plans.quotaAllocations', 'Quota Allocations')} *\n </h2>\n <button\n type=\"button\"\n onClick={handleAddQuota}\n disabled={quotas.length === 0}\n className=\"px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors disabled:opacity-50\"\n >\n + Add Quota\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {quotas.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAvailable',\n 'No quotas available. Please create quotas first.'\n )}\n </p>\n ) : quotaAllocations.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'billing.plans.noQuotasAllocated',\n 'No quotas allocated. Click \"Add Quota\" to add one.'\n )}\n </p>\n ) : (\n quotaAllocations.map((allocation, index) => {\n const selectedQuota = quotas.find((q) => q.id === allocation.quotaId);\n const limits = selectedQuota?.limits as { type?: string; value?: number } | null;\n const limitValue = limits?.value ?? 0;\n const limitType = limits?.type ?? 'unknown';\n const totalValue = limitValue * allocation.quantity;\n\n return (\n <div\n key={index}\n className=\"p-3 border border-border rounded-lg bg-muted/20 space-y-3\"\n >\n <div className=\"flex items-center gap-3\">\n <select\n value={allocation.quotaId}\n onChange={(e) => handleQuotaChange(index, 'quotaId', e.target.value)}\n className={`flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${\n allocation.quotaId === '' ? 'text-muted-foreground' : 'text-foreground'\n }`}\n >\n <option value=\"\" disabled>\n {tr('billing.plans.selectQuota', 'Select a quota...')}\n </option>\n {quotas.map((quota) => (\n <option key={quota.id} value={quota.id}>\n {quota.name}\n </option>\n ))}\n </select>\n <button\n type=\"button\"\n onClick={() => handleRemoveQuota(index)}\n className=\"p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors\"\n title=\"Remove quota\"\n >\n <svg\n className=\"size-5\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"\n />\n </svg>\n </button>\n </div>\n\n {/* Quota Info - only show when quota is selected */}\n {selectedQuota && (\n <>\n <div className=\"flex items-center justify-between text-sm\">\n <div className=\"text-muted-foreground\">\n <span className=\"capitalize\">Type: {limitType}</span>\n <span className=\"mx-2\">•</span>\n <span>\n Value per unit:{' '}\n <span className=\"font-medium text-foreground\">\n {limitValue.toLocaleString()}\n </span>\n </span>\n </div>\n </div>\n\n {/* Quantity & Total */}\n <div className=\"flex items-center gap-4\">\n <div className=\"flex items-center gap-2\">\n <label className=\"text-sm text-muted-foreground\">\n {tr('billing.plans.quantity', 'Quantity')}:\n </label>\n <input\n type=\"number\"\n min=\"1\"\n value={allocation.quantity}\n onChange={(e) =>\n handleQuotaChange(index, 'quantity', e.target.value)\n }\n className=\"w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center\"\n />\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <span className=\"text-muted-foreground\">=</span>\n <span className=\"font-semibold text-status-success-text\">\n {totalValue.toLocaleString()} total\n </span>\n </div>\n </div>\n </>\n )}\n </div>\n );\n })\n )}\n </div>\n </div>\n </div>\n\n <CurrencyPriceOverridesEditor\n overrides={currencyPriceOverrides}\n onChange={setCurrencyPriceOverrides}\n />\n\n {/* Actions */}\n <div className=\"flex items-center gap-3 pt-4\">\n <button\n type=\"submit\"\n disabled={isCreating || quotas.length === 0}\n className=\"px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50\"\n >\n {isCreating\n ? tr('billing.plans.creating', 'Creating...')\n : tr('billing.plans.createPlanBtn', 'Create Plan')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/plans')}\n className=\"px-6 py-2 text-sm font-medium border border-border text-foreground rounded-md hover:bg-muted transition-colors\"\n >\n Cancel\n </button>\n </div>\n </form>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -21,11 +21,11 @@ var y = () => {
|
|
|
21
21
|
let { t: y } = v(), b = (e, t) => {
|
|
22
22
|
let n = y(e);
|
|
23
23
|
return n === e ? t : n;
|
|
24
|
-
}, { planId: x } = m(), S = c(),
|
|
24
|
+
}, { planId: x } = m(), S = c(), ee = s(), { plan: C, isLoading: w, error: T, refetch: E } = l(x), { quotas: D, isLoading: O } = d({ onlyActive: !0 }), { updatePlan: k, isUpdating: A } = u(), [j, M] = p(""), [N, P] = p(""), [F, I] = p("USD"), [L, R] = p(i.MONTHLY), [z, B] = p(!0), [V, H] = p(!0), [U, W] = p([]), [G, K] = p([]), [q, J] = p(null), [Y, X] = p(!1);
|
|
25
25
|
if (f(() => {
|
|
26
|
-
if (
|
|
26
|
+
if (C && (M(C.name ?? ""), P(String(C.price ?? "")), I(C.currency ?? "USD"), R(C.duration ?? i.MONTHLY), B(C.isActive ?? !0), H(C.isPurchasable ?? !0), C.features && C.features.length > 0)) {
|
|
27
27
|
let e = [], t = /* @__PURE__ */ new Map();
|
|
28
|
-
for (let e of
|
|
28
|
+
for (let e of C.features) if (e.quota?.id) {
|
|
29
29
|
let n = e.quota.id, r = o(e) || 1;
|
|
30
30
|
t.set(n, r);
|
|
31
31
|
}
|
|
@@ -33,10 +33,10 @@ var y = () => {
|
|
|
33
33
|
quotaId: n,
|
|
34
34
|
quantity: r
|
|
35
35
|
});
|
|
36
|
-
|
|
36
|
+
W(e);
|
|
37
37
|
}
|
|
38
|
-
}, [
|
|
39
|
-
if (
|
|
38
|
+
}, [C]), !ee.canManagePlans) return /* @__PURE__ */ g(e, { message: b("billing.plans.noEditPermission", "You don't have permission to edit plans.") });
|
|
39
|
+
if (w || O) return /* @__PURE__ */ _("div", {
|
|
40
40
|
className: "space-y-6 p-6",
|
|
41
41
|
children: [/* @__PURE__ */ g("div", { className: "h-8 w-48 bg-muted animate-pulse rounded" }), /* @__PURE__ */ g("div", {
|
|
42
42
|
className: "max-w-2xl space-y-4",
|
|
@@ -49,16 +49,16 @@ var y = () => {
|
|
|
49
49
|
].map((e) => /* @__PURE__ */ g("div", { className: "h-16 bg-muted animate-pulse rounded" }, e))
|
|
50
50
|
})]
|
|
51
51
|
});
|
|
52
|
-
if (
|
|
52
|
+
if (T && a(T)) return /* @__PURE__ */ g("div", {
|
|
53
53
|
className: "p-6",
|
|
54
54
|
children: /* @__PURE__ */ g(n, {
|
|
55
55
|
title: b("billing.plans.serverUnavailable", "Server Unavailable"),
|
|
56
56
|
message: b("billing.plans.serverUnavailableLoadPlan", "Unable to load plan. The server might be down or experiencing issues."),
|
|
57
|
-
onRetry: () =>
|
|
57
|
+
onRetry: () => E(),
|
|
58
58
|
showRetry: !0
|
|
59
59
|
})
|
|
60
60
|
});
|
|
61
|
-
if (!
|
|
61
|
+
if (!C) return /* @__PURE__ */ g("div", {
|
|
62
62
|
className: "flex items-center justify-center h-full min-h-[400px]",
|
|
63
63
|
children: /* @__PURE__ */ _("div", {
|
|
64
64
|
className: "text-center space-y-4",
|
|
@@ -73,69 +73,70 @@ var y = () => {
|
|
|
73
73
|
})]
|
|
74
74
|
})
|
|
75
75
|
});
|
|
76
|
-
let
|
|
77
|
-
|
|
76
|
+
let Z = () => {
|
|
77
|
+
W([{
|
|
78
78
|
quotaId: "",
|
|
79
79
|
quantity: 1
|
|
80
|
-
}, ...
|
|
81
|
-
},
|
|
82
|
-
U
|
|
83
|
-
},
|
|
84
|
-
let r = [...
|
|
85
|
-
t === "quotaId" ? r[e].quotaId = n : r[e].quantity = Number(n),
|
|
80
|
+
}, ...U]);
|
|
81
|
+
}, Q = (e) => {
|
|
82
|
+
W(U.filter((t, n) => n !== e));
|
|
83
|
+
}, $ = (e, t, n) => {
|
|
84
|
+
let r = [...U];
|
|
85
|
+
t === "quotaId" ? r[e].quotaId = n : r[e].quantity = Number(n), W(r);
|
|
86
86
|
};
|
|
87
87
|
return /* @__PURE__ */ _("div", {
|
|
88
88
|
className: "space-y-6 p-6",
|
|
89
89
|
children: [/* @__PURE__ */ g(t, {
|
|
90
|
-
title: `Edit Plan: ${
|
|
90
|
+
title: `Edit Plan: ${C.name}`,
|
|
91
91
|
description: b("billing.plans.editPlanDesc", "Update the plan details and quota allocations")
|
|
92
92
|
}), /* @__PURE__ */ _("form", {
|
|
93
93
|
onSubmit: async (e) => {
|
|
94
|
-
if (e.preventDefault(),
|
|
95
|
-
|
|
94
|
+
if (e.preventDefault(), J(null), X(!1), !j.trim()) {
|
|
95
|
+
J("Plan name is required");
|
|
96
96
|
return;
|
|
97
97
|
}
|
|
98
|
-
if (!
|
|
99
|
-
|
|
98
|
+
if (!N || isNaN(Number(N)) || Number(N) < 0) {
|
|
99
|
+
J("Valid price is required");
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
-
if (
|
|
103
|
-
|
|
102
|
+
if (U.length === 0) {
|
|
103
|
+
J("At least one quota is required");
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
106
|
+
if (U.filter((e) => e.quotaId === "").length > 0) {
|
|
107
|
+
J("Please select a quota for all allocations");
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
let t = {
|
|
111
111
|
id: x,
|
|
112
|
-
name:
|
|
113
|
-
price: Number(
|
|
114
|
-
currency:
|
|
115
|
-
duration:
|
|
116
|
-
isActive:
|
|
117
|
-
|
|
112
|
+
name: j.trim(),
|
|
113
|
+
price: Number(N),
|
|
114
|
+
currency: F,
|
|
115
|
+
duration: L,
|
|
116
|
+
isActive: z,
|
|
117
|
+
isPurchasable: V,
|
|
118
|
+
quotas: U.map((e) => ({
|
|
118
119
|
quotaId: e.quotaId,
|
|
119
120
|
quantity: e.quantity
|
|
120
121
|
})),
|
|
121
|
-
...
|
|
122
|
+
...G.length > 0 ? { currencyPriceOverrides: G } : {}
|
|
122
123
|
};
|
|
123
124
|
try {
|
|
124
|
-
await
|
|
125
|
+
await k(t), X(!0), setTimeout(() => S(`/plans/${x}`), 1500);
|
|
125
126
|
} catch (e) {
|
|
126
|
-
|
|
127
|
+
J(e instanceof Error ? e.message : "Failed to update plan");
|
|
127
128
|
}
|
|
128
129
|
},
|
|
129
130
|
className: "space-y-6",
|
|
130
131
|
children: [
|
|
131
|
-
|
|
132
|
+
q && /* @__PURE__ */ g("div", {
|
|
132
133
|
className: "p-4 bg-status-error-bg-subtle border border-border-subtle rounded-lg",
|
|
133
134
|
children: /* @__PURE__ */ g("p", {
|
|
134
135
|
className: "text-sm text-status-error-text",
|
|
135
|
-
children:
|
|
136
|
+
children: q
|
|
136
137
|
})
|
|
137
138
|
}),
|
|
138
|
-
|
|
139
|
+
Y && /* @__PURE__ */ g("div", {
|
|
139
140
|
className: "p-4 bg-status-success-bg-subtle border border-border-subtle rounded-lg",
|
|
140
141
|
children: /* @__PURE__ */ g("p", {
|
|
141
142
|
className: "text-sm text-status-success-text",
|
|
@@ -162,8 +163,8 @@ var y = () => {
|
|
|
162
163
|
}), /* @__PURE__ */ g("input", {
|
|
163
164
|
id: "name",
|
|
164
165
|
type: "text",
|
|
165
|
-
value:
|
|
166
|
-
onChange: (e) =>
|
|
166
|
+
value: j,
|
|
167
|
+
onChange: (e) => M(e.target.value),
|
|
167
168
|
className: "w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary",
|
|
168
169
|
placeholder: b("billing.plans.planNamePlaceholder", "e.g., Pro Plan"),
|
|
169
170
|
required: !0
|
|
@@ -179,8 +180,8 @@ var y = () => {
|
|
|
179
180
|
type: "number",
|
|
180
181
|
step: "0.01",
|
|
181
182
|
min: "0",
|
|
182
|
-
value:
|
|
183
|
-
onChange: (e) =>
|
|
183
|
+
value: N,
|
|
184
|
+
onChange: (e) => P(e.target.value),
|
|
184
185
|
className: "w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary",
|
|
185
186
|
placeholder: "49.99",
|
|
186
187
|
required: !0
|
|
@@ -190,8 +191,8 @@ var y = () => {
|
|
|
190
191
|
children: [b("billing.plans.currencyLabel", "Currency"), " *"]
|
|
191
192
|
}), /* @__PURE__ */ _("select", {
|
|
192
193
|
id: "currency",
|
|
193
|
-
value:
|
|
194
|
-
onChange: (e) =>
|
|
194
|
+
value: F,
|
|
195
|
+
onChange: (e) => I(e.target.value),
|
|
195
196
|
className: "w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary",
|
|
196
197
|
children: [
|
|
197
198
|
/* @__PURE__ */ g("option", {
|
|
@@ -235,8 +236,8 @@ var y = () => {
|
|
|
235
236
|
children: [b("billing.plans.billingPeriodLabel", "Billing Period"), " *"]
|
|
236
237
|
}), /* @__PURE__ */ _("select", {
|
|
237
238
|
id: "duration",
|
|
238
|
-
value:
|
|
239
|
-
onChange: (e) =>
|
|
239
|
+
value: L,
|
|
240
|
+
onChange: (e) => R(e.target.value),
|
|
240
241
|
className: "w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary",
|
|
241
242
|
children: [/* @__PURE__ */ g("option", {
|
|
242
243
|
value: i.MONTHLY,
|
|
@@ -251,14 +252,31 @@ var y = () => {
|
|
|
251
252
|
children: [/* @__PURE__ */ g("input", {
|
|
252
253
|
id: "isActive",
|
|
253
254
|
type: "checkbox",
|
|
254
|
-
checked:
|
|
255
|
-
onChange: (e) =>
|
|
255
|
+
checked: z,
|
|
256
|
+
onChange: (e) => B(e.target.checked),
|
|
256
257
|
className: "size-4 rounded border-border text-primary focus:ring-primary"
|
|
257
258
|
}), /* @__PURE__ */ g("label", {
|
|
258
259
|
htmlFor: "isActive",
|
|
259
260
|
className: "text-sm font-medium text-foreground",
|
|
260
261
|
children: b("billing.plans.planIsActive", "Plan is active")
|
|
261
262
|
})]
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ _("div", {
|
|
265
|
+
className: "flex items-start justify-between gap-4 pt-1",
|
|
266
|
+
children: [/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
|
|
267
|
+
className: "text-sm font-medium text-foreground",
|
|
268
|
+
children: b("billing.plans.isPurchasableLabel", "Publicly purchasable")
|
|
269
|
+
}), /* @__PURE__ */ g("p", {
|
|
270
|
+
className: "text-xs text-muted-foreground mt-0.5",
|
|
271
|
+
children: V ? b("billing.plans.isPurchasableOn", "Visible on pricing page — users can subscribe directly.") : b("billing.plans.isPurchasableOff", "Internal / custom plan — hidden from pricing page, assign via API only.")
|
|
272
|
+
})] }), /* @__PURE__ */ g("button", {
|
|
273
|
+
type: "button",
|
|
274
|
+
role: "switch",
|
|
275
|
+
"aria-checked": V,
|
|
276
|
+
onClick: () => H((e) => !e),
|
|
277
|
+
className: `relative inline-flex h-6 w-11 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 ${V ? "bg-primary" : "bg-muted-foreground/30"}`,
|
|
278
|
+
children: /* @__PURE__ */ g("span", { className: `inline-block h-5 w-5 transform rounded-full bg-white shadow transition-transform ${V ? "translate-x-5" : "translate-x-0"}` })
|
|
279
|
+
})]
|
|
262
280
|
})
|
|
263
281
|
]
|
|
264
282
|
})]
|
|
@@ -271,36 +289,36 @@ var y = () => {
|
|
|
271
289
|
children: b("billing.plans.quotaAllocations", "Quota Allocations")
|
|
272
290
|
}), /* @__PURE__ */ g("button", {
|
|
273
291
|
type: "button",
|
|
274
|
-
onClick:
|
|
292
|
+
onClick: Z,
|
|
275
293
|
className: "px-3 py-1 text-sm font-medium text-primary hover:bg-primary/10 rounded-md transition-colors",
|
|
276
294
|
children: "+ Add Quota"
|
|
277
295
|
})]
|
|
278
296
|
}), /* @__PURE__ */ g("div", {
|
|
279
297
|
className: "p-4 space-y-3",
|
|
280
|
-
children:
|
|
298
|
+
children: U.length === 0 ? /* @__PURE__ */ g("p", {
|
|
281
299
|
className: "text-sm text-muted-foreground",
|
|
282
300
|
children: b("billing.plans.noQuotasAllocated", "No quotas allocated. Click \"Add Quota\" to add one.")
|
|
283
|
-
}) :
|
|
284
|
-
let n =
|
|
301
|
+
}) : U.map((e, t) => {
|
|
302
|
+
let n = D.find((t) => t.id === e.quotaId), r = n?.limits, i = r?.value ?? 0, a = r?.type ?? "unknown", o = i * e.quantity;
|
|
285
303
|
return /* @__PURE__ */ _("div", {
|
|
286
304
|
className: "p-3 border border-border rounded-lg bg-muted/20 space-y-3",
|
|
287
305
|
children: [/* @__PURE__ */ _("div", {
|
|
288
306
|
className: "flex items-center gap-3",
|
|
289
307
|
children: [/* @__PURE__ */ _("select", {
|
|
290
308
|
value: e.quotaId,
|
|
291
|
-
onChange: (e) =>
|
|
309
|
+
onChange: (e) => $(t, "quotaId", e.target.value),
|
|
292
310
|
className: `flex-1 px-3 py-2 border border-border rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-primary ${e.quotaId === "" ? "text-muted-foreground" : "text-foreground"}`,
|
|
293
311
|
children: [/* @__PURE__ */ g("option", {
|
|
294
312
|
value: "",
|
|
295
313
|
disabled: !0,
|
|
296
314
|
children: b("billing.plans.selectQuota", "Select a quota...")
|
|
297
|
-
}),
|
|
315
|
+
}), D.map((e) => /* @__PURE__ */ g("option", {
|
|
298
316
|
value: e.id,
|
|
299
317
|
children: e.name
|
|
300
318
|
}, e.id))]
|
|
301
319
|
}), /* @__PURE__ */ g("button", {
|
|
302
320
|
type: "button",
|
|
303
|
-
onClick: () =>
|
|
321
|
+
onClick: () => Q(t),
|
|
304
322
|
className: "p-2 text-status-error-text hover:bg-status-error-bg-subtle rounded-md transition-colors",
|
|
305
323
|
title: "Remove quota",
|
|
306
324
|
children: /* @__PURE__ */ g("svg", {
|
|
@@ -350,7 +368,7 @@ var y = () => {
|
|
|
350
368
|
type: "number",
|
|
351
369
|
min: "1",
|
|
352
370
|
value: e.quantity,
|
|
353
|
-
onChange: (e) =>
|
|
371
|
+
onChange: (e) => $(t, "quantity", e.target.value),
|
|
354
372
|
className: "w-24 px-3 py-1.5 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-primary text-center"
|
|
355
373
|
})]
|
|
356
374
|
}), /* @__PURE__ */ _("div", {
|
|
@@ -370,16 +388,16 @@ var y = () => {
|
|
|
370
388
|
})]
|
|
371
389
|
}),
|
|
372
390
|
/* @__PURE__ */ g(r, {
|
|
373
|
-
overrides:
|
|
374
|
-
onChange:
|
|
391
|
+
overrides: G,
|
|
392
|
+
onChange: K
|
|
375
393
|
}),
|
|
376
394
|
/* @__PURE__ */ _("div", {
|
|
377
395
|
className: "flex items-center gap-3 pt-4",
|
|
378
396
|
children: [/* @__PURE__ */ g("button", {
|
|
379
397
|
type: "submit",
|
|
380
|
-
disabled:
|
|
398
|
+
disabled: A,
|
|
381
399
|
className: "px-6 py-2 text-sm font-medium bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors disabled:opacity-50",
|
|
382
|
-
children:
|
|
400
|
+
children: A ? b("billing.plans.saving", "Saving...") : b("billing.plans.saveChanges", "Save Changes")
|
|
383
401
|
}), /* @__PURE__ */ g("button", {
|
|
384
402
|
type: "button",
|
|
385
403
|
onClick: () => S(`/plans/${x}`),
|