@burdenoff/microfe-billing 2026.623.3 → 2026.624.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/BillingUserRoutes.js +3 -3
- package/dist/billing/BillingUserRoutes.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
- package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
- package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +162 -187
- package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +146 -153
- package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
- package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
- package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js +164 -179
- package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js +8 -8
- package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/EarningsPage.js +243 -305
- package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +73 -78
- package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
- package/dist/billing/modules/earnings/pages/PayoutsPage.js +173 -179
- package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
- package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
- package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/SettingsPage.js +181 -173
- package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +111 -99
- package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +68 -83
- package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
- package/dist/billing/shared/components/AccessDenied.js +4 -4
- package/dist/billing/shared/components/AccessDenied.js.map +1 -1
- package/dist/billing/shared/components/ActorIdentity.js +8 -8
- package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
- package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
- package/dist/billing/shared/ui/MetricCard.js +1 -1
- package/dist/billing/shared/ui/MetricCard.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,73 +3,61 @@ import { useEffect as t, useState as n } from "react";
|
|
|
3
3
|
import { AlertCircle as r, Loader2 as i, Minus as a, Plus as o, Users as s } from "lucide-react";
|
|
4
4
|
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
5
|
import { useI18n as u } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
+
import { ResponsiveDialog as d, ResponsiveDialogContent as f, ResponsiveDialogFooter as p, ResponsiveDialogHeader as m, ResponsiveDialogTitle as h } from "@burdenoff/fe-libs/ui";
|
|
6
7
|
//#region src/billing/modules/subscriptions/components/ManageSeatsModal.tsx
|
|
7
|
-
var
|
|
8
|
-
let { t:
|
|
9
|
-
let n =
|
|
8
|
+
var g = ({ subscription: g, onClose: _, onConfirm: v, isLoading: y = !1 }) => {
|
|
9
|
+
let { t: b } = u(), x = (e, t) => {
|
|
10
|
+
let n = b(e);
|
|
10
11
|
return n === e ? t : n;
|
|
11
|
-
},
|
|
12
|
+
}, S = g.plan, C = S?.minSeats ?? 1, w = S?.maxSeats ?? 999, T = S?.pricePerSeat ?? 0, E = S?.currency ?? "USD", D = g.seatCount ?? C, [O, k] = n(D), [A, j] = n(null);
|
|
12
13
|
t(() => {
|
|
13
|
-
|
|
14
|
-
}, [
|
|
15
|
-
let
|
|
16
|
-
function
|
|
17
|
-
|
|
14
|
+
k(D);
|
|
15
|
+
}, [D]);
|
|
16
|
+
let M = O - D, N = O * T, P = D * T, F = g.paymentGateway === "razorpay";
|
|
17
|
+
function I() {
|
|
18
|
+
k((e) => Math.max(C, e - 1)), j(null);
|
|
18
19
|
}
|
|
19
|
-
function
|
|
20
|
-
|
|
20
|
+
function L() {
|
|
21
|
+
k((e) => Math.min(w, e + 1)), j(null);
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
+
function R(e) {
|
|
23
24
|
let t = parseInt(e, 10);
|
|
24
|
-
isNaN(t) || (t <
|
|
25
|
+
isNaN(t) || (t < C ? (j(`Minimum ${C} seat${C === 1 ? "" : "s"} required for this plan.`), k(t)) : t > w ? (j(`Maximum ${w} seats allowed.`), k(t)) : (j(null), k(t)));
|
|
25
26
|
}
|
|
26
|
-
async function
|
|
27
|
-
if (!(
|
|
28
|
-
if (
|
|
29
|
-
|
|
27
|
+
async function z() {
|
|
28
|
+
if (!(O < C || O > w)) {
|
|
29
|
+
if (O === D) {
|
|
30
|
+
_();
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
j(null);
|
|
33
34
|
try {
|
|
34
|
-
await
|
|
35
|
+
await v(O), _();
|
|
35
36
|
} catch (e) {
|
|
36
|
-
|
|
37
|
+
j(e instanceof Error ? e.message : "Failed to update seats.");
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
return /* @__PURE__ */ c(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
return /* @__PURE__ */ c(d, {
|
|
42
|
+
open: !0,
|
|
43
|
+
onOpenChange: (e) => {
|
|
44
|
+
!e && !y && _();
|
|
45
|
+
},
|
|
46
|
+
children: /* @__PURE__ */ l(f, {
|
|
47
|
+
className: "sm:max-w-md",
|
|
47
48
|
children: [
|
|
49
|
+
/* @__PURE__ */ c(m, { children: /* @__PURE__ */ l(h, {
|
|
50
|
+
className: "flex items-center gap-2",
|
|
51
|
+
children: [/* @__PURE__ */ c(s, { className: "size-5 text-text-link" }), x("billing.subscriptions.manageSeats", "Manage Seats")]
|
|
52
|
+
}) }),
|
|
48
53
|
/* @__PURE__ */ l("div", {
|
|
49
|
-
className: "
|
|
50
|
-
children: [/* @__PURE__ */ l("div", {
|
|
51
|
-
className: "flex items-center gap-2",
|
|
52
|
-
children: [/* @__PURE__ */ c(s, { className: "size-5 text-text-link" }), /* @__PURE__ */ c("h2", {
|
|
53
|
-
id: "manage-seats-title",
|
|
54
|
-
className: "text-lg font-semibold text-text-primary",
|
|
55
|
-
children: g("billing.subscriptions.manageSeats", "Manage Seats")
|
|
56
|
-
})]
|
|
57
|
-
}), /* @__PURE__ */ c("button", {
|
|
58
|
-
type: "button",
|
|
59
|
-
onClick: f,
|
|
60
|
-
className: "text-text-secondary hover:text-text-primary transition-colors",
|
|
61
|
-
"aria-label": "Close manage seats dialog",
|
|
62
|
-
children: "✕"
|
|
63
|
-
})]
|
|
64
|
-
}),
|
|
65
|
-
/* @__PURE__ */ l("div", {
|
|
66
|
-
className: "p-6 space-y-6",
|
|
54
|
+
className: "space-y-6",
|
|
67
55
|
children: [
|
|
68
56
|
/* @__PURE__ */ l("div", {
|
|
69
57
|
className: "flex items-center justify-between text-sm text-text-secondary",
|
|
70
58
|
children: [/* @__PURE__ */ c("span", { children: "Current seats" }), /* @__PURE__ */ c("span", {
|
|
71
59
|
className: "font-medium text-text-primary",
|
|
72
|
-
children:
|
|
60
|
+
children: D
|
|
73
61
|
})]
|
|
74
62
|
}),
|
|
75
63
|
/* @__PURE__ */ l("div", {
|
|
@@ -82,91 +70,88 @@ var d = ({ subscription: d, onClose: f, onConfirm: p, isLoading: m = !1 }) => {
|
|
|
82
70
|
children: [
|
|
83
71
|
/* @__PURE__ */ c("button", {
|
|
84
72
|
type: "button",
|
|
85
|
-
onClick:
|
|
86
|
-
disabled:
|
|
73
|
+
onClick: I,
|
|
74
|
+
disabled: O <= C || y,
|
|
87
75
|
className: "size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
88
76
|
"aria-label": "Decrease seat count",
|
|
89
77
|
children: /* @__PURE__ */ c(a, { className: "size-4" })
|
|
90
78
|
}),
|
|
91
79
|
/* @__PURE__ */ c("input", {
|
|
92
80
|
type: "number",
|
|
93
|
-
value:
|
|
94
|
-
min:
|
|
95
|
-
max:
|
|
96
|
-
onChange: (e) =>
|
|
97
|
-
disabled:
|
|
81
|
+
value: O,
|
|
82
|
+
min: C,
|
|
83
|
+
max: w,
|
|
84
|
+
onChange: (e) => R(e.target.value),
|
|
85
|
+
disabled: y,
|
|
98
86
|
className: "w-20 h-9 text-center border border-border-subtle rounded-button bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50"
|
|
99
87
|
}),
|
|
100
88
|
/* @__PURE__ */ c("button", {
|
|
101
89
|
type: "button",
|
|
102
|
-
onClick:
|
|
103
|
-
disabled:
|
|
90
|
+
onClick: L,
|
|
91
|
+
disabled: O >= w || y,
|
|
104
92
|
className: "size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors",
|
|
105
93
|
"aria-label": "Increase seat count",
|
|
106
94
|
children: /* @__PURE__ */ c(o, { className: "size-4" })
|
|
107
95
|
}),
|
|
108
96
|
/* @__PURE__ */ c("span", {
|
|
109
97
|
className: "text-sm text-text-secondary",
|
|
110
|
-
children:
|
|
98
|
+
children: C === w ? `(${C} fixed)` : `(min ${C}${w < 999 ? `, max ${w}` : ""})`
|
|
111
99
|
})
|
|
112
100
|
]
|
|
113
101
|
})]
|
|
114
102
|
}),
|
|
115
|
-
|
|
103
|
+
T > 0 && /* @__PURE__ */ l("div", {
|
|
116
104
|
className: "rounded-lg bg-bg-sunken border border-border-subtle p-4 space-y-2 text-sm",
|
|
117
105
|
children: [
|
|
118
106
|
/* @__PURE__ */ l("div", {
|
|
119
107
|
className: "flex justify-between text-text-secondary",
|
|
120
|
-
children: [/* @__PURE__ */ c("span", { children: "Per seat" }), /* @__PURE__ */ l("span", { children: [e(
|
|
108
|
+
children: [/* @__PURE__ */ c("span", { children: "Per seat" }), /* @__PURE__ */ l("span", { children: [e(T, E), " / month"] })]
|
|
121
109
|
}),
|
|
122
110
|
/* @__PURE__ */ l("div", {
|
|
123
111
|
className: "flex justify-between text-text-secondary",
|
|
124
|
-
children: [/* @__PURE__ */ c("span", { children: "Current total" }), /* @__PURE__ */ l("span", { children: [e(
|
|
112
|
+
children: [/* @__PURE__ */ c("span", { children: "Current total" }), /* @__PURE__ */ l("span", { children: [e(P, E), " / month"] })]
|
|
125
113
|
}),
|
|
126
114
|
/* @__PURE__ */ l("div", {
|
|
127
115
|
className: "border-t border-border-subtle pt-2 flex justify-between font-medium text-text-primary",
|
|
128
116
|
children: [/* @__PURE__ */ c("span", { children: "New total" }), /* @__PURE__ */ l("span", {
|
|
129
|
-
className:
|
|
130
|
-
children: [e(
|
|
117
|
+
className: M > 0 ? "text-status-error-text" : M < 0 ? "text-status-success-text" : "",
|
|
118
|
+
children: [e(N, E), " / month"]
|
|
131
119
|
})]
|
|
132
120
|
}),
|
|
133
|
-
|
|
121
|
+
M !== 0 && /* @__PURE__ */ c("div", {
|
|
134
122
|
className: "text-xs text-text-secondary",
|
|
135
|
-
children:
|
|
123
|
+
children: M > 0 ? `+${M} seat${M === 1 ? "" : "s"} — +${e(M * T, E)}/month` : `${M} seat${Math.abs(M) === 1 ? "" : "s"} — ${e(M * T, E)}/month`
|
|
136
124
|
})
|
|
137
125
|
]
|
|
138
126
|
}),
|
|
139
|
-
|
|
127
|
+
F && M > 0 && /* @__PURE__ */ l("div", {
|
|
140
128
|
className: "flex gap-2 p-3 rounded-button bg-status-warning-bg-subtle border border-status-warning-border text-sm text-status-warning-text",
|
|
141
129
|
children: [/* @__PURE__ */ c(r, { className: "size-4 mt-0.5 shrink-0" }), /* @__PURE__ */ c("span", { children: "Seat additions apply from the next billing cycle. A separate prorated charge will be raised for the remaining days in the current cycle." })]
|
|
142
130
|
}),
|
|
143
|
-
|
|
131
|
+
A && /* @__PURE__ */ l("div", {
|
|
144
132
|
className: "flex gap-2 p-3 rounded-button bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text",
|
|
145
|
-
children: [/* @__PURE__ */ c(r, { className: "size-4 mt-0.5 shrink-0" }), /* @__PURE__ */ c("span", { children:
|
|
133
|
+
children: [/* @__PURE__ */ c(r, { className: "size-4 mt-0.5 shrink-0" }), /* @__PURE__ */ c("span", { children: A })]
|
|
146
134
|
})
|
|
147
135
|
]
|
|
148
136
|
}),
|
|
149
|
-
/* @__PURE__ */ l("
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
children: [m && /* @__PURE__ */ c(i, { className: "size-4 animate-spin" }), C === S ? "No change" : D > 0 ? `Add ${D} seat${D === 1 ? "" : "s"}` : `Remove ${Math.abs(D)} seat${Math.abs(D) === 1 ? "" : "s"}`]
|
|
163
|
-
})]
|
|
164
|
-
})
|
|
137
|
+
/* @__PURE__ */ l(p, { children: [/* @__PURE__ */ c("button", {
|
|
138
|
+
type: "button",
|
|
139
|
+
onClick: _,
|
|
140
|
+
disabled: y,
|
|
141
|
+
className: "px-4 py-2 text-sm rounded-button border border-border-subtle hover:bg-action-ghost-bgHover text-text-primary transition-colors disabled:opacity-50",
|
|
142
|
+
children: "Cancel"
|
|
143
|
+
}), /* @__PURE__ */ l("button", {
|
|
144
|
+
type: "button",
|
|
145
|
+
onClick: z,
|
|
146
|
+
disabled: y || O === D || O < C || O > w,
|
|
147
|
+
className: "px-4 py-2 text-sm rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2",
|
|
148
|
+
children: [y && /* @__PURE__ */ c(i, { className: "size-4 animate-spin" }), O === D ? "No change" : M > 0 ? `Add ${M} seat${M === 1 ? "" : "s"}` : `Remove ${Math.abs(M)} seat${Math.abs(M) === 1 ? "" : "s"}`]
|
|
149
|
+
})] })
|
|
165
150
|
]
|
|
166
151
|
})
|
|
167
152
|
});
|
|
168
153
|
};
|
|
169
154
|
//#endregion
|
|
170
|
-
export {
|
|
155
|
+
export { g as ManageSeatsModal };
|
|
171
156
|
|
|
172
157
|
//# sourceMappingURL=ManageSeatsModal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ManageSeatsModal.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/components/ManageSeatsModal.tsx"],"sourcesContent":["/**\n * ManageSeatsModal\n * Lets users update the seat count on a PER_SEAT subscription.\n * Shows current seats, min/max bounds, and a proration notice for Razorpay.\n */\n\nimport { type FC, useState, useEffect } from 'react';\nimport { Minus, Plus, Users, AlertCircle, Loader2 } from 'lucide-react';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport type { BillingSubscription } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface ManageSeatsModalProps {\n subscription: BillingSubscription;\n onClose: () => void;\n onConfirm: (seatCount: number) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const ManageSeatsModal: FC<ManageSeatsModalProps> = ({\n subscription,\n onClose,\n onConfirm,\n isLoading = false,\n}) => {\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 plan = subscription.plan;\n const minSeats = plan?.minSeats ?? 1;\n const maxSeats = plan?.maxSeats ?? 999;\n const pricePerSeat = plan?.pricePerSeat ?? 0;\n const currency = plan?.currency ?? 'USD';\n const currentSeats = subscription.seatCount ?? minSeats;\n\n const [newSeatCount, setNewSeatCount] = useState(currentSeats);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n setNewSeatCount(currentSeats);\n }, [currentSeats]);\n\n const seatDelta = newSeatCount - currentSeats;\n const newMonthlyTotal = newSeatCount * pricePerSeat;\n const currentMonthlyTotal = currentSeats * pricePerSeat;\n const isRazorpay = subscription.paymentGateway === 'razorpay';\n\n function decrement() {\n setNewSeatCount((n) => Math.max(minSeats, n - 1));\n setError(null);\n }\n\n function increment() {\n setNewSeatCount((n) => Math.min(maxSeats, n + 1));\n setError(null);\n }\n\n function handleInputChange(value: string) {\n const parsed = parseInt(value, 10);\n if (isNaN(parsed)) return;\n if (parsed < minSeats) {\n setError(`Minimum ${minSeats} seat${minSeats !== 1 ? 's' : ''} required for this plan.`);\n setNewSeatCount(parsed);\n } else if (parsed > maxSeats) {\n setError(`Maximum ${maxSeats} seats allowed.`);\n setNewSeatCount(parsed);\n } else {\n setError(null);\n setNewSeatCount(parsed);\n }\n }\n\n async function handleConfirm() {\n if (newSeatCount < minSeats || newSeatCount > maxSeats) return;\n if (newSeatCount === currentSeats) {\n onClose();\n return;\n }\n setError(null);\n try {\n await onConfirm(newSeatCount);\n onClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update seats.');\n }\n }\n\n return (\n <div className=\"fixed inset-0 bg-overlay-scrim flex items-center justify-center z-50 p-4\">\n <div\n className=\"bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-md\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"manage-seats-title\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between p-6 border-b border-border-subtle\">\n <div className=\"flex items-center gap-2\">\n <Users className=\"size-5 text-text-link\" />\n <h2 id=\"manage-seats-title\" className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.subscriptions.manageSeats', 'Manage Seats')}\n </h2>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"text-text-secondary hover:text-text-primary transition-colors\"\n aria-label=\"Close manage seats dialog\"\n >\n ✕\n </button>\n </div>\n\n {/* Body */}\n <div className=\"p-6 space-y-6\">\n {/* Current vs new */}\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>Current seats</span>\n <span className=\"font-medium text-text-primary\">{currentSeats}</span>\n </div>\n\n {/* Seat selector */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium text-text-primary\">New seat count</label>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={decrement}\n disabled={newSeatCount <= minSeats || isLoading}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n aria-label=\"Decrease seat count\"\n >\n <Minus className=\"size-4\" />\n </button>\n <input\n type=\"number\"\n value={newSeatCount}\n min={minSeats}\n max={maxSeats}\n onChange={(e) => handleInputChange(e.target.value)}\n disabled={isLoading}\n className=\"w-20 h-9 text-center border border-border-subtle rounded-button bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={increment}\n disabled={newSeatCount >= maxSeats || isLoading}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n aria-label=\"Increase seat count\"\n >\n <Plus className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-secondary\">\n {minSeats === maxSeats\n ? `(${minSeats} fixed)`\n : `(min ${minSeats}${maxSeats < 999 ? `, max ${maxSeats}` : ''})`}\n </span>\n </div>\n </div>\n\n {/* Pricing breakdown */}\n {pricePerSeat > 0 && (\n <div className=\"rounded-lg bg-bg-sunken border border-border-subtle p-4 space-y-2 text-sm\">\n <div className=\"flex justify-between text-text-secondary\">\n <span>Per seat</span>\n <span>{formatCurrency(pricePerSeat, currency)} / month</span>\n </div>\n <div className=\"flex justify-between text-text-secondary\">\n <span>Current total</span>\n <span>{formatCurrency(currentMonthlyTotal, currency)} / month</span>\n </div>\n <div className=\"border-t border-border-subtle pt-2 flex justify-between font-medium text-text-primary\">\n <span>New total</span>\n <span\n className={\n seatDelta > 0\n ? 'text-status-error-text'\n : seatDelta < 0\n ? 'text-status-success-text'\n : ''\n }\n >\n {formatCurrency(newMonthlyTotal, currency)} / month\n </span>\n </div>\n {seatDelta !== 0 && (\n <div className=\"text-xs text-text-secondary\">\n {seatDelta > 0\n ? `+${seatDelta} seat${seatDelta !== 1 ? 's' : ''} — +${formatCurrency(seatDelta * pricePerSeat, currency)}/month`\n : `${seatDelta} seat${Math.abs(seatDelta) !== 1 ? 's' : ''} — ${formatCurrency(seatDelta * pricePerSeat, currency)}/month`}\n </div>\n )}\n </div>\n )}\n\n {/* Razorpay proration notice */}\n {isRazorpay && seatDelta > 0 && (\n <div className=\"flex gap-2 p-3 rounded-button bg-status-warning-bg-subtle border border-status-warning-border text-sm text-status-warning-text\">\n <AlertCircle className=\"size-4 mt-0.5 shrink-0\" />\n <span>\n Seat additions apply from the next billing cycle. A separate prorated charge will be\n raised for the remaining days in the current cycle.\n </span>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div className=\"flex gap-2 p-3 rounded-button bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 mt-0.5 shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-end gap-3 p-6 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm rounded-button border border-border-subtle hover:bg-action-ghost-bgHover text-text-primary transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleConfirm}\n disabled={\n isLoading ||\n newSeatCount === currentSeats ||\n newSeatCount < minSeats ||\n newSeatCount > maxSeats\n }\n className=\"px-4 py-2 text-sm rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2\"\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {newSeatCount === currentSeats\n ? 'No change'\n : seatDelta > 0\n ? `Add ${seatDelta} seat${seatDelta !== 1 ? 's' : ''}`\n : `Remove ${Math.abs(seatDelta)} seat${Math.abs(seatDelta) !== 1 ? 's' : ''}`}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ManageSeatsModal.js","names":[],"sources":["../../../../../src/billing/modules/subscriptions/components/ManageSeatsModal.tsx"],"sourcesContent":["/**\n * ManageSeatsModal\n * Lets users update the seat count on a PER_SEAT subscription.\n * Shows current seats, min/max bounds, and a proration notice for Razorpay.\n */\n\nimport { type FC, useState, useEffect } from 'react';\nimport { Minus, Plus, Users, AlertCircle, Loader2 } from 'lucide-react';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport type { BillingSubscription } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n} from '@burdenoff/fe-libs/ui';\n\ninterface ManageSeatsModalProps {\n subscription: BillingSubscription;\n onClose: () => void;\n onConfirm: (seatCount: number) => Promise<void>;\n isLoading?: boolean;\n}\n\nexport const ManageSeatsModal: FC<ManageSeatsModalProps> = ({\n subscription,\n onClose,\n onConfirm,\n isLoading = false,\n}) => {\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 plan = subscription.plan;\n const minSeats = plan?.minSeats ?? 1;\n const maxSeats = plan?.maxSeats ?? 999;\n const pricePerSeat = plan?.pricePerSeat ?? 0;\n const currency = plan?.currency ?? 'USD';\n const currentSeats = subscription.seatCount ?? minSeats;\n\n const [newSeatCount, setNewSeatCount] = useState(currentSeats);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n setNewSeatCount(currentSeats);\n }, [currentSeats]);\n\n const seatDelta = newSeatCount - currentSeats;\n const newMonthlyTotal = newSeatCount * pricePerSeat;\n const currentMonthlyTotal = currentSeats * pricePerSeat;\n const isRazorpay = subscription.paymentGateway === 'razorpay';\n\n function decrement() {\n setNewSeatCount((n) => Math.max(minSeats, n - 1));\n setError(null);\n }\n\n function increment() {\n setNewSeatCount((n) => Math.min(maxSeats, n + 1));\n setError(null);\n }\n\n function handleInputChange(value: string) {\n const parsed = parseInt(value, 10);\n if (isNaN(parsed)) return;\n if (parsed < minSeats) {\n setError(`Minimum ${minSeats} seat${minSeats !== 1 ? 's' : ''} required for this plan.`);\n setNewSeatCount(parsed);\n } else if (parsed > maxSeats) {\n setError(`Maximum ${maxSeats} seats allowed.`);\n setNewSeatCount(parsed);\n } else {\n setError(null);\n setNewSeatCount(parsed);\n }\n }\n\n async function handleConfirm() {\n if (newSeatCount < minSeats || newSeatCount > maxSeats) return;\n if (newSeatCount === currentSeats) {\n onClose();\n return;\n }\n setError(null);\n try {\n await onConfirm(newSeatCount);\n onClose();\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to update seats.');\n }\n }\n\n return (\n <ResponsiveDialog\n open\n onOpenChange={(open) => {\n if (!open && !isLoading) onClose();\n }}\n >\n <ResponsiveDialogContent className=\"sm:max-w-md\">\n {/* Header */}\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle className=\"flex items-center gap-2\">\n <Users className=\"size-5 text-text-link\" />\n {tr('billing.subscriptions.manageSeats', 'Manage Seats')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n {/* Body */}\n <div className=\"space-y-6\">\n {/* Current vs new */}\n <div className=\"flex items-center justify-between text-sm text-text-secondary\">\n <span>Current seats</span>\n <span className=\"font-medium text-text-primary\">{currentSeats}</span>\n </div>\n\n {/* Seat selector */}\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium text-text-primary\">New seat count</label>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={decrement}\n disabled={newSeatCount <= minSeats || isLoading}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n aria-label=\"Decrease seat count\"\n >\n <Minus className=\"size-4\" />\n </button>\n <input\n type=\"number\"\n value={newSeatCount}\n min={minSeats}\n max={maxSeats}\n onChange={(e) => handleInputChange(e.target.value)}\n disabled={isLoading}\n className=\"w-20 h-9 text-center border border-border-subtle rounded-button bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] disabled:opacity-50\"\n />\n <button\n type=\"button\"\n onClick={increment}\n disabled={newSeatCount >= maxSeats || isLoading}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors\"\n aria-label=\"Increase seat count\"\n >\n <Plus className=\"size-4\" />\n </button>\n <span className=\"text-sm text-text-secondary\">\n {minSeats === maxSeats\n ? `(${minSeats} fixed)`\n : `(min ${minSeats}${maxSeats < 999 ? `, max ${maxSeats}` : ''})`}\n </span>\n </div>\n </div>\n\n {/* Pricing breakdown */}\n {pricePerSeat > 0 && (\n <div className=\"rounded-lg bg-bg-sunken border border-border-subtle p-4 space-y-2 text-sm\">\n <div className=\"flex justify-between text-text-secondary\">\n <span>Per seat</span>\n <span>{formatCurrency(pricePerSeat, currency)} / month</span>\n </div>\n <div className=\"flex justify-between text-text-secondary\">\n <span>Current total</span>\n <span>{formatCurrency(currentMonthlyTotal, currency)} / month</span>\n </div>\n <div className=\"border-t border-border-subtle pt-2 flex justify-between font-medium text-text-primary\">\n <span>New total</span>\n <span\n className={\n seatDelta > 0\n ? 'text-status-error-text'\n : seatDelta < 0\n ? 'text-status-success-text'\n : ''\n }\n >\n {formatCurrency(newMonthlyTotal, currency)} / month\n </span>\n </div>\n {seatDelta !== 0 && (\n <div className=\"text-xs text-text-secondary\">\n {seatDelta > 0\n ? `+${seatDelta} seat${seatDelta !== 1 ? 's' : ''} — +${formatCurrency(seatDelta * pricePerSeat, currency)}/month`\n : `${seatDelta} seat${Math.abs(seatDelta) !== 1 ? 's' : ''} — ${formatCurrency(seatDelta * pricePerSeat, currency)}/month`}\n </div>\n )}\n </div>\n )}\n\n {/* Razorpay proration notice */}\n {isRazorpay && seatDelta > 0 && (\n <div className=\"flex gap-2 p-3 rounded-button bg-status-warning-bg-subtle border border-status-warning-border text-sm text-status-warning-text\">\n <AlertCircle className=\"size-4 mt-0.5 shrink-0\" />\n <span>\n Seat additions apply from the next billing cycle. A separate prorated charge will be\n raised for the remaining days in the current cycle.\n </span>\n </div>\n )}\n\n {/* Error */}\n {error && (\n <div className=\"flex gap-2 p-3 rounded-button bg-status-error-bg-subtle border border-status-error-border text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 mt-0.5 shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n </div>\n\n {/* Footer */}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={isLoading}\n className=\"px-4 py-2 text-sm rounded-button border border-border-subtle hover:bg-action-ghost-bgHover text-text-primary transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleConfirm}\n disabled={\n isLoading ||\n newSeatCount === currentSeats ||\n newSeatCount < minSeats ||\n newSeatCount > maxSeats\n }\n className=\"px-4 py-2 text-sm rounded-button bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2\"\n >\n {isLoading && <Loader2 className=\"size-4 animate-spin\" />}\n {newSeatCount === currentSeats\n ? 'No change'\n : seatDelta > 0\n ? `Add ${seatDelta} seat${seatDelta !== 1 ? 's' : ''}`\n : `Remove ${Math.abs(seatDelta)} seat${Math.abs(seatDelta) !== 1 ? 's' : ''}`}\n </button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n};\n"],"mappings":"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"}
|
|
@@ -12,9 +12,9 @@ var r = ({ message: r }) => {
|
|
|
12
12
|
className: "text-center space-y-2",
|
|
13
13
|
children: [
|
|
14
14
|
/* @__PURE__ */ e("div", {
|
|
15
|
-
className: "size-12 mx-auto rounded-full bg-
|
|
15
|
+
className: "size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center",
|
|
16
16
|
children: /* @__PURE__ */ e("svg", {
|
|
17
|
-
className: "size-6 text-
|
|
17
|
+
className: "size-6 text-text-secondary",
|
|
18
18
|
fill: "none",
|
|
19
19
|
viewBox: "0 0 24 24",
|
|
20
20
|
stroke: "currentColor",
|
|
@@ -27,11 +27,11 @@ var r = ({ message: r }) => {
|
|
|
27
27
|
})
|
|
28
28
|
}),
|
|
29
29
|
/* @__PURE__ */ e("h2", {
|
|
30
|
-
className: "text-lg font-semibold text-
|
|
30
|
+
className: "text-lg font-semibold text-text-primary",
|
|
31
31
|
children: a("billing.accessDenied.title", "Access Denied")
|
|
32
32
|
}),
|
|
33
33
|
/* @__PURE__ */ e("p", {
|
|
34
|
-
className: "text-sm text-
|
|
34
|
+
className: "text-sm text-text-secondary max-w-sm",
|
|
35
35
|
children: o
|
|
36
36
|
})
|
|
37
37
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../../src/billing/shared/components/AccessDenied.tsx"],"sourcesContent":["/**\n * Shared Access Denied Component\n * Displays when user lacks permission to view a page\n */\n\nimport type { FC } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({ message }) => {\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\n const displayMessage =\n message ??\n tr('billing.accessDenied.defaultMessage', \"You don't have permission to access this page.\");\n\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-
|
|
1
|
+
{"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../../src/billing/shared/components/AccessDenied.tsx"],"sourcesContent":["/**\n * Shared Access Denied Component\n * Displays when user lacks permission to view a page\n */\n\nimport type { FC } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({ message }) => {\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\n const displayMessage =\n message ??\n tr('billing.accessDenied.defaultMessage', \"You don't have permission to access this page.\");\n\n return (\n <div className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <div className=\"size-12 mx-auto rounded-full bg-bg-sunken flex items-center justify-center\">\n <svg\n className=\"size-6 text-text-secondary\"\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=\"M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z\"\n />\n </svg>\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">{displayMessage}</p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAYA,IAAa,KAAuC,EAAE,iBAAc;CAClE,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAa,EAAE,EAAI;AACzB,SAAO,MAAe,IAAM,IAAW;IAGnC,IACJ,KACA,EAAG,uCAAuC,iDAAiD;AAE7F,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MACE,WAAU;MACV,MAAK;MACL,SAAQ;MACR,QAAO;gBAEP,kBAAC,QAAD;OACE,eAAc;OACd,gBAAe;OACf,aAAa;OACb,GAAE;OACF,CAAA;MACE,CAAA;KACF,CAAA;IACN,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,gBAAgB;KAC/C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAwC;KAAmB,CAAA;IACpE;;EACF,CAAA"}
|
|
@@ -15,10 +15,10 @@ var s = ({ profile: s, variant: c = "cell" }) => {
|
|
|
15
15
|
});
|
|
16
16
|
}, f = c === "chip", p = c === "inline";
|
|
17
17
|
return /* @__PURE__ */ a("div", {
|
|
18
|
-
className: f ? "inline-flex items-center gap-2 rounded-full border border-border bg-
|
|
18
|
+
className: f ? "inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1" : p ? "inline-flex items-center gap-2" : "flex items-center gap-3",
|
|
19
19
|
children: [
|
|
20
20
|
/* @__PURE__ */ i("div", {
|
|
21
|
-
className: f ? "flex size-6 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold text-primary" : "flex size-9 items-center justify-center rounded-full bg-primary/10 text-xs font-semibold text-primary",
|
|
21
|
+
className: f ? "flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary" : "flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary",
|
|
22
22
|
"aria-hidden": "true",
|
|
23
23
|
children: s.avatarUrl ? /* @__PURE__ */ i("img", {
|
|
24
24
|
src: s.avatarUrl,
|
|
@@ -32,27 +32,27 @@ var s = ({ profile: s, variant: c = "cell" }) => {
|
|
|
32
32
|
/* @__PURE__ */ a("div", {
|
|
33
33
|
className: "min-w-0 flex-1",
|
|
34
34
|
children: [/* @__PURE__ */ i("p", {
|
|
35
|
-
className: f ? "text-xs font-medium text-
|
|
35
|
+
className: f ? "text-xs font-medium text-text-primary" : "truncate text-sm font-medium text-text-primary",
|
|
36
36
|
children: s.displayName
|
|
37
37
|
}), f ? null : /* @__PURE__ */ i("p", {
|
|
38
|
-
className: "truncate text-xs text-
|
|
38
|
+
className: "truncate text-xs text-text-secondary",
|
|
39
39
|
children: s.email ?? s.secondaryLabel
|
|
40
40
|
})]
|
|
41
41
|
}),
|
|
42
42
|
!f && !p ? /* @__PURE__ */ a("details", {
|
|
43
43
|
className: "group",
|
|
44
44
|
children: [/* @__PURE__ */ i("summary", {
|
|
45
|
-
className: "cursor-pointer text-[10px] uppercase tracking-wider text-
|
|
45
|
+
className: "cursor-pointer text-[10px] uppercase tracking-wider text-text-secondary hover:text-text-primary",
|
|
46
46
|
children: "ID"
|
|
47
47
|
}), /* @__PURE__ */ a("div", {
|
|
48
|
-
className: "mt-1 flex items-center gap-2 rounded-md border border-border bg-
|
|
48
|
+
className: "mt-1 flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2 py-1",
|
|
49
49
|
children: [/* @__PURE__ */ i("span", {
|
|
50
|
-
className: "font-mono text-[11px] text-
|
|
50
|
+
className: "font-mono text-[11px] text-text-secondary",
|
|
51
51
|
children: s.actorId
|
|
52
52
|
}), /* @__PURE__ */ i("button", {
|
|
53
53
|
type: "button",
|
|
54
54
|
onClick: d,
|
|
55
|
-
className: "text-
|
|
55
|
+
className: "text-text-secondary transition hover:text-text-primary",
|
|
56
56
|
"aria-label": "Copy actor ID",
|
|
57
57
|
children: i(l ? t : n, { className: "size-3.5" })
|
|
58
58
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActorIdentity.js","names":[],"sources":["../../../../src/billing/shared/components/ActorIdentity.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { Check, Copy, User2 } from 'lucide-react';\nimport type { ActorProfile } from '../hooks/useActorProfiles';\n\ninterface ActorIdentityProps {\n profile: ActorProfile;\n variant?: 'cell' | 'inline' | 'chip';\n}\n\nfunction initials(displayName: string): string {\n const cleaned = displayName.trim();\n if (!cleaned) return '?';\n const parts = cleaned.split(/\\s+/).filter(Boolean);\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n}\n\nexport const ActorIdentity: FC<ActorIdentityProps> = ({ profile, variant = 'cell' }) => {\n const [copied, setCopied] = useState(false);\n\n const onCopy = () => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return;\n void navigator.clipboard.writeText(profile.actorId).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n const isChip = variant === 'chip';\n const isInline = variant === 'inline';\n\n return (\n <div\n className={\n isChip\n ? 'inline-flex items-center gap-2 rounded-full border border-border bg-
|
|
1
|
+
{"version":3,"file":"ActorIdentity.js","names":[],"sources":["../../../../src/billing/shared/components/ActorIdentity.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\nimport { Check, Copy, User2 } from 'lucide-react';\nimport type { ActorProfile } from '../hooks/useActorProfiles';\n\ninterface ActorIdentityProps {\n profile: ActorProfile;\n variant?: 'cell' | 'inline' | 'chip';\n}\n\nfunction initials(displayName: string): string {\n const cleaned = displayName.trim();\n if (!cleaned) return '?';\n const parts = cleaned.split(/\\s+/).filter(Boolean);\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase();\n}\n\nexport const ActorIdentity: FC<ActorIdentityProps> = ({ profile, variant = 'cell' }) => {\n const [copied, setCopied] = useState(false);\n\n const onCopy = () => {\n if (typeof navigator === 'undefined' || !navigator.clipboard) return;\n void navigator.clipboard.writeText(profile.actorId).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n });\n };\n\n const isChip = variant === 'chip';\n const isInline = variant === 'inline';\n\n return (\n <div\n className={\n isChip\n ? 'inline-flex items-center gap-2 rounded-full border border-border-subtle bg-bg-sunken/40 px-2 py-1'\n : isInline\n ? 'inline-flex items-center gap-2'\n : 'flex items-center gap-3'\n }\n >\n <div\n className={\n isChip\n ? 'flex size-6 items-center justify-center rounded-full bg-action-primary-bg/10 text-[10px] font-semibold text-primary'\n : 'flex size-9 items-center justify-center rounded-full bg-action-primary-bg/10 text-xs font-semibold text-primary'\n }\n aria-hidden=\"true\"\n >\n {profile.avatarUrl ? (\n <img src={profile.avatarUrl} alt=\"\" className=\"size-full rounded-full object-cover\" />\n ) : profile.resolved ? (\n initials(profile.displayName)\n ) : (\n <User2 className=\"size-4\" aria-hidden=\"true\" />\n )}\n </div>\n <div className=\"min-w-0 flex-1\">\n <p\n className={\n isChip\n ? 'text-xs font-medium text-text-primary'\n : 'truncate text-sm font-medium text-text-primary'\n }\n >\n {profile.displayName}\n </p>\n {!isChip ? (\n <p className=\"truncate text-xs text-text-secondary\">\n {profile.email ?? profile.secondaryLabel}\n </p>\n ) : null}\n </div>\n {!isChip && !isInline ? (\n <details className=\"group\">\n <summary className=\"cursor-pointer text-[10px] uppercase tracking-wider text-text-secondary hover:text-text-primary\">\n ID\n </summary>\n <div className=\"mt-1 flex items-center gap-2 rounded-md border border-border-subtle bg-bg-surface px-2 py-1\">\n <span className=\"font-mono text-[11px] text-text-secondary\">{profile.actorId}</span>\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"text-text-secondary transition hover:text-text-primary\"\n aria-label=\"Copy actor ID\"\n >\n {copied ? <Check className=\"size-3.5\" /> : <Copy className=\"size-3.5\" />}\n </button>\n </div>\n </details>\n ) : null}\n </div>\n );\n};\n"],"mappings":";;;;AASA,SAAS,EAAS,GAA6B;CAC7C,IAAM,IAAU,EAAY,MAAM;AAClC,KAAI,CAAC,EAAS,QAAO;CACrB,IAAM,IAAQ,EAAQ,MAAM,MAAM,CAAC,OAAO,QAAQ;AAElD,QADI,EAAM,WAAW,IAAU,EAAM,GAAG,OAAO,EAAE,CAAC,aAAa,IACvD,EAAM,GAAG,OAAO,EAAE,GAAG,EAAM,EAAM,SAAS,GAAG,OAAO,EAAE,EAAE,aAAa;;AAG/E,IAAa,KAAyC,EAAE,YAAS,aAAU,aAAa;CACtF,IAAM,CAAC,GAAQ,KAAa,EAAS,GAAM,EAErC,UAAe;AACf,SAAO,YAAc,OAAe,CAAC,UAAU,aAC9C,UAAU,UAAU,UAAU,EAAQ,QAAQ,CAAC,WAAW;AAE7D,GADA,EAAU,GAAK,EACf,iBAAiB,EAAU,GAAM,EAAE,KAAK;IACxC;IAGE,IAAS,MAAY,QACrB,IAAW,MAAY;AAE7B,QACE,kBAAC,OAAD;EACE,WACE,IACI,sGACA,IACE,mCACA;YANV;GASE,kBAAC,OAAD;IACE,WACE,IACI,wHACA;IAEN,eAAY;cAEX,EAAQ,YACP,kBAAC,OAAD;KAAK,KAAK,EAAQ;KAAW,KAAI;KAAG,WAAU;KAAwC,CAAA,GACpF,EAAQ,WACV,EAAS,EAAQ,YAAY,GAE7B,kBAAC,GAAD;KAAO,WAAU;KAAS,eAAY;KAAS,CAAA;IAE7C,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KACE,WACE,IACI,0CACA;eAGL,EAAQ;KACP,CAAA,EACF,IAIE,OAHF,kBAAC,KAAD;KAAG,WAAU;eACV,EAAQ,SAAS,EAAQ;KACxB,CAAA,CAEF;;GACL,CAAC,KAAU,CAAC,IACX,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,WAAD;KAAS,WAAU;eAAkG;KAE3G,CAAA,EACV,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MAAM,WAAU;gBAA6C,EAAQ;MAAe,CAAA,EACpF,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,cAAW;gBAED,EAAT,IAAU,IAAiC,GAAlC,EAAO,WAAU,YAAa,CAAgC;MACjE,CAAA,CACL;OACE;QACR;GACA"}
|
|
@@ -134,22 +134,22 @@ var r = [
|
|
|
134
134
|
}]);
|
|
135
135
|
}, f = i.length;
|
|
136
136
|
return /* @__PURE__ */ n("div", {
|
|
137
|
-
className: "border border-border rounded-lg bg-
|
|
137
|
+
className: "border border-border-subtle rounded-lg bg-bg-surface overflow-hidden",
|
|
138
138
|
children: [/* @__PURE__ */ n("button", {
|
|
139
139
|
type: "button",
|
|
140
140
|
onClick: () => c((e) => !e),
|
|
141
|
-
className: "w-full p-4 border-b border-border bg-
|
|
141
|
+
className: "w-full p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between hover:bg-bg-sunken/50 transition-colors",
|
|
142
142
|
children: [/* @__PURE__ */ n("div", {
|
|
143
143
|
className: "flex items-center gap-2",
|
|
144
144
|
children: [/* @__PURE__ */ t("h2", {
|
|
145
|
-
className: "font-semibold text-
|
|
145
|
+
className: "font-semibold text-text-primary",
|
|
146
146
|
children: "Currency Price Overrides"
|
|
147
147
|
}), f > 0 && /* @__PURE__ */ n("span", {
|
|
148
|
-
className: "px-2 py-0.5 text-xs font-medium bg-primary/10 text-primary rounded-full",
|
|
148
|
+
className: "px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded-full",
|
|
149
149
|
children: [f, " set"]
|
|
150
150
|
})]
|
|
151
151
|
}), /* @__PURE__ */ n("div", {
|
|
152
|
-
className: "flex items-center gap-2 text-sm text-
|
|
152
|
+
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
153
153
|
children: [/* @__PURE__ */ n("span", { children: [s ? "Hide" : "Show", " (leave blank to use auto-calculated exchange rates)"] }), /* @__PURE__ */ t("svg", {
|
|
154
154
|
className: `w-4 h-4 transition-transform ${s ? "rotate-180" : ""}`,
|
|
155
155
|
fill: "none",
|
|
@@ -167,7 +167,7 @@ var r = [
|
|
|
167
167
|
className: "p-4",
|
|
168
168
|
children: [
|
|
169
169
|
/* @__PURE__ */ t("p", {
|
|
170
|
-
className: "text-xs text-
|
|
170
|
+
className: "text-xs text-text-secondary mb-4",
|
|
171
171
|
children: "Set a specific price for any currency. Leave a field empty to auto-calculate from the USD base price using live exchange rates."
|
|
172
172
|
}),
|
|
173
173
|
/* @__PURE__ */ t("div", {
|
|
@@ -177,15 +177,15 @@ var r = [
|
|
|
177
177
|
return /* @__PURE__ */ n("div", {
|
|
178
178
|
className: "space-y-1",
|
|
179
179
|
children: [/* @__PURE__ */ n("label", {
|
|
180
|
-
className: "flex items-center gap-1 text-xs font-medium text-
|
|
180
|
+
className: "flex items-center gap-1 text-xs font-medium text-text-primary",
|
|
181
181
|
children: [
|
|
182
182
|
/* @__PURE__ */ t("span", {
|
|
183
|
-
className: "text-
|
|
183
|
+
className: "text-text-secondary",
|
|
184
184
|
children: i
|
|
185
185
|
}),
|
|
186
186
|
/* @__PURE__ */ t("span", { children: e }),
|
|
187
187
|
/* @__PURE__ */ n("span", {
|
|
188
|
-
className: "text-
|
|
188
|
+
className: "text-text-secondary font-normal truncate",
|
|
189
189
|
children: [
|
|
190
190
|
"(",
|
|
191
191
|
r,
|
|
@@ -202,7 +202,7 @@ var r = [
|
|
|
202
202
|
value: a?.amount ?? "",
|
|
203
203
|
onChange: (t) => u(e, t.target.value),
|
|
204
204
|
placeholder: "auto",
|
|
205
|
-
className: `px-2 py-1.5 text-sm border border-border rounded-md bg-
|
|
205
|
+
className: `px-2 py-1.5 text-sm border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-text-secondary ${o ? "flex-1 min-w-0" : "w-full"} ${a?.amount === void 0 ? "" : "border-primary/50"}`
|
|
206
206
|
}), o && /* @__PURE__ */ t("input", {
|
|
207
207
|
type: "number",
|
|
208
208
|
step: "0.01",
|
|
@@ -212,7 +212,7 @@ var r = [
|
|
|
212
212
|
placeholder: "/seat",
|
|
213
213
|
disabled: !a,
|
|
214
214
|
title: "Per-seat amount (requires flat amount to be set first)",
|
|
215
|
-
className: "flex-1 min-w-0 px-2 py-1.5 text-sm border border-border rounded-md bg-
|
|
215
|
+
className: "flex-1 min-w-0 px-2 py-1.5 text-sm border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-text-secondary disabled:opacity-40"
|
|
216
216
|
})]
|
|
217
217
|
})]
|
|
218
218
|
}, e);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CurrencyPriceOverridesEditor.js","names":[],"sources":["../../../../src/billing/shared/components/CurrencyPriceOverridesEditor.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\n\nimport type { CurrencyPriceOverrideInput } from '../types';\n\ninterface CurrencyPriceOverridesEditorProps {\n overrides: CurrencyPriceOverrideInput[];\n onChange: (overrides: CurrencyPriceOverrideInput[]) => void;\n showPerSeatAmount?: boolean;\n}\n\nconst PRICING_CURRENCIES: { code: string; name: string; symbol: string }[] = [\n { code: 'EUR', name: 'Euro', symbol: '€' },\n { code: 'GBP', name: 'British Pound', symbol: '£' },\n { code: 'INR', name: 'Indian Rupee', symbol: '₹' },\n { code: 'AUD', name: 'Australian Dollar', symbol: 'A$' },\n { code: 'CAD', name: 'Canadian Dollar', symbol: 'C$' },\n { code: 'SGD', name: 'Singapore Dollar', symbol: 'S$' },\n { code: 'JPY', name: 'Japanese Yen', symbol: '¥' },\n { code: 'MXN', name: 'Mexican Peso', symbol: 'MX$' },\n { code: 'BRL', name: 'Brazilian Real', symbol: 'R$' },\n { code: 'CHF', name: 'Swiss Franc', symbol: 'Fr' },\n { code: 'CNY', name: 'Chinese Yuan', symbol: '¥' },\n { code: 'HKD', name: 'Hong Kong Dollar', symbol: 'HK$' },\n { code: 'NZD', name: 'New Zealand Dollar', symbol: 'NZ$' },\n { code: 'SEK', name: 'Swedish Krona', symbol: 'kr' },\n { code: 'KRW', name: 'South Korean Won', symbol: '₩' },\n { code: 'NOK', name: 'Norwegian Krone', symbol: 'kr' },\n { code: 'DKK', name: 'Danish Krone', symbol: 'kr' },\n { code: 'PLN', name: 'Polish Złoty', symbol: 'zł' },\n { code: 'THB', name: 'Thai Baht', symbol: '฿' },\n { code: 'MYR', name: 'Malaysian Ringgit', symbol: 'RM' },\n];\n\nexport const CurrencyPriceOverridesEditor: FC<CurrencyPriceOverridesEditorProps> = ({\n overrides,\n onChange,\n showPerSeatAmount = false,\n}) => {\n const [isExpanded, setIsExpanded] = useState(false);\n\n const getOverride = (currency: string): CurrencyPriceOverrideInput | undefined =>\n overrides.find((o) => o.currency === currency);\n\n const handleAmountChange = (currency: string, value: string) => {\n const trimmed = value.trim();\n const existing = overrides.filter((o) => o.currency !== currency);\n\n if (trimmed === '') {\n onChange(existing);\n return;\n }\n\n const amount = parseFloat(trimmed);\n if (isNaN(amount) || amount < 0) return;\n\n const currentOverride = getOverride(currency);\n onChange([\n ...existing,\n {\n currency,\n amount,\n ...(currentOverride?.perSeatAmount !== undefined\n ? { perSeatAmount: currentOverride.perSeatAmount }\n : {}),\n },\n ]);\n };\n\n const handlePerSeatChange = (currency: string, value: string) => {\n const trimmed = value.trim();\n const existing = overrides.filter((o) => o.currency !== currency);\n const currentOverride = getOverride(currency);\n\n if (!currentOverride) return;\n\n if (trimmed === '') {\n onChange([...existing, { currency, amount: currentOverride.amount }]);\n return;\n }\n\n const perSeatAmount = parseFloat(trimmed);\n if (isNaN(perSeatAmount) || perSeatAmount < 0) return;\n\n onChange([...existing, { ...currentOverride, perSeatAmount }]);\n };\n\n const overrideCount = overrides.length;\n\n return (\n <div className=\"border border-border rounded-lg bg-card overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setIsExpanded((v) => !v)}\n className=\"w-full p-4 border-b border-border bg-muted/30 flex items-center justify-between hover:bg-muted/50 transition-colors\"\n >\n <div className=\"flex items-center gap-2\">\n <h2 className=\"font-semibold text-foreground\">Currency Price Overrides</h2>\n {overrideCount > 0 && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-primary/10 text-primary rounded-full\">\n {overrideCount} set\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <span>\n {isExpanded ? 'Hide' : 'Show'} (leave blank to use auto-calculated exchange rates)\n </span>\n <svg\n className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </div>\n </button>\n\n {isExpanded && (\n <div className=\"p-4\">\n <p className=\"text-xs text-muted-foreground mb-4\">\n Set a specific price for any currency. Leave a field empty to auto-calculate from the\n USD base price using live exchange rates.\n </p>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3\">\n {PRICING_CURRENCIES.map(({ code, name, symbol }) => {\n const override = getOverride(code);\n return (\n <div key={code} className=\"space-y-1\">\n <label className=\"flex items-center gap-1 text-xs font-medium text-foreground\">\n <span className=\"text-muted-foreground\">{symbol}</span>\n <span>{code}</span>\n <span className=\"text-muted-foreground font-normal truncate\">({name})</span>\n </label>\n <div className={showPerSeatAmount ? 'flex gap-1' : ''}>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={override?.amount ?? ''}\n onChange={(e) => handleAmountChange(code, e.target.value)}\n placeholder=\"auto\"\n className={`px-2 py-1.5 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground ${\n showPerSeatAmount ? 'flex-1 min-w-0' : 'w-full'\n } ${override?.amount !== undefined ? 'border-primary/50' : ''}`}\n />\n {showPerSeatAmount && (\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={override?.perSeatAmount ?? ''}\n onChange={(e) => handlePerSeatChange(code, e.target.value)}\n placeholder=\"/seat\"\n disabled={!override}\n title=\"Per-seat amount (requires flat amount to be set first)\"\n className=\"flex-1 min-w-0 px-2 py-1.5 text-sm border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground disabled:opacity-40\"\n />\n )}\n </div>\n </div>\n );\n })}\n </div>\n {overrideCount > 0 && (\n <button\n type=\"button\"\n onClick={() => onChange([])}\n className=\"mt-4 text-xs text-status-error-text hover:underline\"\n >\n Clear all overrides\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CurrencyPriceOverridesEditor.js","names":[],"sources":["../../../../src/billing/shared/components/CurrencyPriceOverridesEditor.tsx"],"sourcesContent":["import { useState, type FC } from 'react';\n\nimport type { CurrencyPriceOverrideInput } from '../types';\n\ninterface CurrencyPriceOverridesEditorProps {\n overrides: CurrencyPriceOverrideInput[];\n onChange: (overrides: CurrencyPriceOverrideInput[]) => void;\n showPerSeatAmount?: boolean;\n}\n\nconst PRICING_CURRENCIES: { code: string; name: string; symbol: string }[] = [\n { code: 'EUR', name: 'Euro', symbol: '€' },\n { code: 'GBP', name: 'British Pound', symbol: '£' },\n { code: 'INR', name: 'Indian Rupee', symbol: '₹' },\n { code: 'AUD', name: 'Australian Dollar', symbol: 'A$' },\n { code: 'CAD', name: 'Canadian Dollar', symbol: 'C$' },\n { code: 'SGD', name: 'Singapore Dollar', symbol: 'S$' },\n { code: 'JPY', name: 'Japanese Yen', symbol: '¥' },\n { code: 'MXN', name: 'Mexican Peso', symbol: 'MX$' },\n { code: 'BRL', name: 'Brazilian Real', symbol: 'R$' },\n { code: 'CHF', name: 'Swiss Franc', symbol: 'Fr' },\n { code: 'CNY', name: 'Chinese Yuan', symbol: '¥' },\n { code: 'HKD', name: 'Hong Kong Dollar', symbol: 'HK$' },\n { code: 'NZD', name: 'New Zealand Dollar', symbol: 'NZ$' },\n { code: 'SEK', name: 'Swedish Krona', symbol: 'kr' },\n { code: 'KRW', name: 'South Korean Won', symbol: '₩' },\n { code: 'NOK', name: 'Norwegian Krone', symbol: 'kr' },\n { code: 'DKK', name: 'Danish Krone', symbol: 'kr' },\n { code: 'PLN', name: 'Polish Złoty', symbol: 'zł' },\n { code: 'THB', name: 'Thai Baht', symbol: '฿' },\n { code: 'MYR', name: 'Malaysian Ringgit', symbol: 'RM' },\n];\n\nexport const CurrencyPriceOverridesEditor: FC<CurrencyPriceOverridesEditorProps> = ({\n overrides,\n onChange,\n showPerSeatAmount = false,\n}) => {\n const [isExpanded, setIsExpanded] = useState(false);\n\n const getOverride = (currency: string): CurrencyPriceOverrideInput | undefined =>\n overrides.find((o) => o.currency === currency);\n\n const handleAmountChange = (currency: string, value: string) => {\n const trimmed = value.trim();\n const existing = overrides.filter((o) => o.currency !== currency);\n\n if (trimmed === '') {\n onChange(existing);\n return;\n }\n\n const amount = parseFloat(trimmed);\n if (isNaN(amount) || amount < 0) return;\n\n const currentOverride = getOverride(currency);\n onChange([\n ...existing,\n {\n currency,\n amount,\n ...(currentOverride?.perSeatAmount !== undefined\n ? { perSeatAmount: currentOverride.perSeatAmount }\n : {}),\n },\n ]);\n };\n\n const handlePerSeatChange = (currency: string, value: string) => {\n const trimmed = value.trim();\n const existing = overrides.filter((o) => o.currency !== currency);\n const currentOverride = getOverride(currency);\n\n if (!currentOverride) return;\n\n if (trimmed === '') {\n onChange([...existing, { currency, amount: currentOverride.amount }]);\n return;\n }\n\n const perSeatAmount = parseFloat(trimmed);\n if (isNaN(perSeatAmount) || perSeatAmount < 0) return;\n\n onChange([...existing, { ...currentOverride, perSeatAmount }]);\n };\n\n const overrideCount = overrides.length;\n\n return (\n <div className=\"border border-border-subtle rounded-lg bg-bg-surface overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setIsExpanded((v) => !v)}\n className=\"w-full p-4 border-b border-border-subtle bg-bg-sunken/30 flex items-center justify-between hover:bg-bg-sunken/50 transition-colors\"\n >\n <div className=\"flex items-center gap-2\">\n <h2 className=\"font-semibold text-text-primary\">Currency Price Overrides</h2>\n {overrideCount > 0 && (\n <span className=\"px-2 py-0.5 text-xs font-medium bg-action-primary-bg/10 text-primary rounded-full\">\n {overrideCount} set\n </span>\n )}\n </div>\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span>\n {isExpanded ? 'Hide' : 'Show'} (leave blank to use auto-calculated exchange rates)\n </span>\n <svg\n className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`}\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M19 9l-7 7-7-7\" />\n </svg>\n </div>\n </button>\n\n {isExpanded && (\n <div className=\"p-4\">\n <p className=\"text-xs text-text-secondary mb-4\">\n Set a specific price for any currency. Leave a field empty to auto-calculate from the\n USD base price using live exchange rates.\n </p>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-3\">\n {PRICING_CURRENCIES.map(({ code, name, symbol }) => {\n const override = getOverride(code);\n return (\n <div key={code} className=\"space-y-1\">\n <label className=\"flex items-center gap-1 text-xs font-medium text-text-primary\">\n <span className=\"text-text-secondary\">{symbol}</span>\n <span>{code}</span>\n <span className=\"text-text-secondary font-normal truncate\">({name})</span>\n </label>\n <div className={showPerSeatAmount ? 'flex gap-1' : ''}>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={override?.amount ?? ''}\n onChange={(e) => handleAmountChange(code, e.target.value)}\n placeholder=\"auto\"\n className={`px-2 py-1.5 text-sm border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-text-secondary ${\n showPerSeatAmount ? 'flex-1 min-w-0' : 'w-full'\n } ${override?.amount !== undefined ? 'border-primary/50' : ''}`}\n />\n {showPerSeatAmount && (\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0\"\n value={override?.perSeatAmount ?? ''}\n onChange={(e) => handlePerSeatChange(code, e.target.value)}\n placeholder=\"/seat\"\n disabled={!override}\n title=\"Per-seat amount (requires flat amount to be set first)\"\n className=\"flex-1 min-w-0 px-2 py-1.5 text-sm border border-border-subtle rounded-md bg-bg-surface text-text-primary focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-text-secondary disabled:opacity-40\"\n />\n )}\n </div>\n </div>\n );\n })}\n </div>\n {overrideCount > 0 && (\n <button\n type=\"button\"\n onClick={() => onChange([])}\n className=\"mt-4 text-xs text-status-error-text hover:underline\"\n >\n Clear all overrides\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -18,7 +18,7 @@ function i({ label: i, value: a, icon: o, trend: s, onClick: c, isActive: l = !1
|
|
|
18
18
|
children: a
|
|
19
19
|
}),
|
|
20
20
|
s && /* @__PURE__ */ n("p", {
|
|
21
|
-
className: r("mt-1 text-xs font-medium", s.isPositive ? "text-status-success-text" : "text-status-error-text"),
|
|
21
|
+
className: r("mt-1 text-xs font-medium tabular-nums", s.isPositive ? "text-status-success-text" : "text-status-error-text"),
|
|
22
22
|
children: [
|
|
23
23
|
s.isPositive ? "+" : "",
|
|
24
24
|
s.value,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricCard.js","names":[],"sources":["../../../../src/billing/shared/ui/MetricCard.tsx"],"sourcesContent":["/**\n * Boff UI — MetricCard primitive\n *\n * Stat/metric card following the Boff UI recipe: small muted label, large\n * tabular-nums value, optional trend, and an optional icon in an accent-soft\n * tile. One question per card. Can render as a button when `onClick` is\n * provided (used by the usage dashboard's filterable stats).\n *\n * Visual-only primitive.\n */\n\nimport type { ReactNode } from 'react';\nimport { cn } from '@burdenoff/fe-libs/shared/utils';\n\ninterface MetricCardTrend {\n value: number;\n isPositive: boolean;\n}\n\ninterface MetricCardProps {\n label: ReactNode;\n value: ReactNode;\n icon?: ReactNode;\n trend?: MetricCardTrend;\n /** When provided, renders an interactive button (with selected ring when active). */\n onClick?: () => void;\n isActive?: boolean;\n className?: string;\n /** Passthrough for data-tour and other data-* hooks. */\n [key: `data-${string}`]: string | undefined;\n}\n\nexport function MetricCard({\n label,\n value,\n icon,\n trend,\n onClick,\n isActive = false,\n className,\n ...rest\n}: MetricCardProps) {\n const base = cn(\n 'rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 p-5 text-left',\n onClick &&\n 'w-full transition-all duration-200 ease-[var(--motion-easing-out)] hover:border-border-strong hover:shadow-elevation-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n isActive && 'ring-1 ring-[var(--color-focus-ring)] border-border-strong',\n className\n );\n\n const inner = (\n <>\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm text-text-secondary\">{label}</p>\n {icon && (\n <span className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {icon}\n </span>\n )}\n </div>\n <p className=\"mt-2 text-2xl font-semibold tabular-nums text-text-primary\">{value}</p>\n {trend && (\n <p\n className={cn(\n 'mt-1 text-xs font-medium',\n trend.isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {trend.isPositive ? '+' : ''}\n {trend.value}%\n </p>\n )}\n </>\n );\n\n if (onClick) {\n return (\n <button type=\"button\" onClick={onClick} className={base} {...rest}>\n {inner}\n </button>\n );\n }\n\n return (\n <div className={base} {...rest}>\n {inner}\n </div>\n );\n}\n"],"mappings":";;;AAgCA,SAAgB,EAAW,EACzB,UACA,UACA,SACA,UACA,YACA,cAAW,IACX,cACA,GAAG,KACe;CAClB,IAAM,IAAO,EACX,2FACA,KACE,uNACF,KAAY,8DACZ,EACD,EAEK,IACJ,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAU,CAAA,EACrD,KACC,kBAAC,QAAD;IAAM,WAAU;cACb;IACI,CAAA,CAEL;;EACN,kBAAC,KAAD;GAAG,WAAU;aAA8D;GAAU,CAAA;EACpF,KACC,kBAAC,KAAD;GACE,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"MetricCard.js","names":[],"sources":["../../../../src/billing/shared/ui/MetricCard.tsx"],"sourcesContent":["/**\n * Boff UI — MetricCard primitive\n *\n * Stat/metric card following the Boff UI recipe: small muted label, large\n * tabular-nums value, optional trend, and an optional icon in an accent-soft\n * tile. One question per card. Can render as a button when `onClick` is\n * provided (used by the usage dashboard's filterable stats).\n *\n * Visual-only primitive.\n */\n\nimport type { ReactNode } from 'react';\nimport { cn } from '@burdenoff/fe-libs/shared/utils';\n\ninterface MetricCardTrend {\n value: number;\n isPositive: boolean;\n}\n\ninterface MetricCardProps {\n label: ReactNode;\n value: ReactNode;\n icon?: ReactNode;\n trend?: MetricCardTrend;\n /** When provided, renders an interactive button (with selected ring when active). */\n onClick?: () => void;\n isActive?: boolean;\n className?: string;\n /** Passthrough for data-tour and other data-* hooks. */\n [key: `data-${string}`]: string | undefined;\n}\n\nexport function MetricCard({\n label,\n value,\n icon,\n trend,\n onClick,\n isActive = false,\n className,\n ...rest\n}: MetricCardProps) {\n const base = cn(\n 'rounded-card border border-border-subtle bg-bg-surface shadow-elevation-1 p-5 text-left',\n onClick &&\n 'w-full transition-all duration-200 ease-[var(--motion-easing-out)] hover:border-border-strong hover:shadow-elevation-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]',\n isActive && 'ring-1 ring-[var(--color-focus-ring)] border-border-strong',\n className\n );\n\n const inner = (\n <>\n <div className=\"flex items-start justify-between gap-3\">\n <p className=\"text-sm text-text-secondary\">{label}</p>\n {icon && (\n <span className=\"flex size-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n {icon}\n </span>\n )}\n </div>\n <p className=\"mt-2 text-2xl font-semibold tabular-nums text-text-primary\">{value}</p>\n {trend && (\n <p\n className={cn(\n 'mt-1 text-xs font-medium tabular-nums',\n trend.isPositive ? 'text-status-success-text' : 'text-status-error-text'\n )}\n >\n {trend.isPositive ? '+' : ''}\n {trend.value}%\n </p>\n )}\n </>\n );\n\n if (onClick) {\n return (\n <button type=\"button\" onClick={onClick} className={base} {...rest}>\n {inner}\n </button>\n );\n }\n\n return (\n <div className={base} {...rest}>\n {inner}\n </div>\n );\n}\n"],"mappings":";;;AAgCA,SAAgB,EAAW,EACzB,UACA,UACA,SACA,UACA,YACA,cAAW,IACX,cACA,GAAG,KACe;CAClB,IAAM,IAAO,EACX,2FACA,KACE,uNACF,KAAY,8DACZ,EACD,EAEK,IACJ,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAU,CAAA,EACrD,KACC,kBAAC,QAAD;IAAM,WAAU;cACb;IACI,CAAA,CAEL;;EACN,kBAAC,KAAD;GAAG,WAAU;aAA8D;GAAU,CAAA;EACpF,KACC,kBAAC,KAAD;GACE,WAAW,EACT,yCACA,EAAM,aAAa,6BAA6B,yBACjD;aAJH;IAMG,EAAM,aAAa,MAAM;IACzB,EAAM;IAAM;IACX;;EAEL,EAAA,CAAA;AAWL,QARI,IAEA,kBAAC,UAAD;EAAQ,MAAK;EAAkB;EAAS,WAAW;EAAM,GAAI;YAC1D;EACM,CAAA,GAKX,kBAAC,OAAD;EAAK,WAAW;EAAM,GAAI;YACvB;EACG,CAAA"}
|