@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.
Files changed (43) hide show
  1. package/dist/billing/BillingUserRoutes.js +3 -3
  2. package/dist/billing/BillingUserRoutes.js.map +1 -1
  3. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +34 -34
  4. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  5. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js +94 -94
  6. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  7. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js +162 -187
  8. package/dist/billing/modules/affiliate/pages/AffiliateDashboardPage.js.map +1 -1
  9. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +146 -153
  10. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  11. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +10 -10
  12. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  13. package/dist/billing/modules/billing/pages/TransactionsPage.js +62 -62
  14. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  15. package/dist/billing/modules/coupons/pages/CouponDetailPage.js +164 -179
  16. package/dist/billing/modules/coupons/pages/CouponDetailPage.js.map +1 -1
  17. package/dist/billing/modules/coupons/pages/CreateCouponPage.js +8 -8
  18. package/dist/billing/modules/coupons/pages/CreateCouponPage.js.map +1 -1
  19. package/dist/billing/modules/earnings/pages/EarningsPage.js +243 -305
  20. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  21. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js +73 -78
  22. package/dist/billing/modules/earnings/pages/PayoutAdminPage.js.map +1 -1
  23. package/dist/billing/modules/earnings/pages/PayoutsPage.js +173 -179
  24. package/dist/billing/modules/earnings/pages/PayoutsPage.js.map +1 -1
  25. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js +4 -4
  26. package/dist/billing/modules/plans/pages/PlanAssignFreePage.js.map +1 -1
  27. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js +4 -4
  28. package/dist/billing/modules/promotions/pages/CreatePromotionPage.js.map +1 -1
  29. package/dist/billing/modules/settings/pages/SettingsPage.js +181 -173
  30. package/dist/billing/modules/settings/pages/SettingsPage.js.map +1 -1
  31. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js +111 -99
  32. package/dist/billing/modules/settings/pages/TeamPermissionsPage.js.map +1 -1
  33. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js +68 -83
  34. package/dist/billing/modules/subscriptions/components/ManageSeatsModal.js.map +1 -1
  35. package/dist/billing/shared/components/AccessDenied.js +4 -4
  36. package/dist/billing/shared/components/AccessDenied.js.map +1 -1
  37. package/dist/billing/shared/components/ActorIdentity.js +8 -8
  38. package/dist/billing/shared/components/ActorIdentity.js.map +1 -1
  39. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js +11 -11
  40. package/dist/billing/shared/components/CurrencyPriceOverridesEditor.js.map +1 -1
  41. package/dist/billing/shared/ui/MetricCard.js +1 -1
  42. package/dist/billing/shared/ui/MetricCard.js.map +1 -1
  43. 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 d = ({ subscription: d, onClose: f, onConfirm: p, isLoading: m = !1 }) => {
8
- let { t: h } = u(), g = (e, t) => {
9
- let n = h(e);
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
- }, _ = d.plan, v = _?.minSeats ?? 1, y = _?.maxSeats ?? 999, b = _?.pricePerSeat ?? 0, x = _?.currency ?? "USD", S = d.seatCount ?? v, [C, w] = n(S), [T, E] = n(null);
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
- w(S);
14
- }, [S]);
15
- let D = C - S, O = C * b, k = S * b, A = d.paymentGateway === "razorpay";
16
- function j() {
17
- w((e) => Math.max(v, e - 1)), E(null);
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 M() {
20
- w((e) => Math.min(y, e + 1)), E(null);
20
+ function L() {
21
+ k((e) => Math.min(w, e + 1)), j(null);
21
22
  }
22
- function N(e) {
23
+ function R(e) {
23
24
  let t = parseInt(e, 10);
24
- isNaN(t) || (t < v ? (E(`Minimum ${v} seat${v === 1 ? "" : "s"} required for this plan.`), w(t)) : t > y ? (E(`Maximum ${y} seats allowed.`), w(t)) : (E(null), w(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 P() {
27
- if (!(C < v || C > y)) {
28
- if (C === S) {
29
- f();
27
+ async function z() {
28
+ if (!(O < C || O > w)) {
29
+ if (O === D) {
30
+ _();
30
31
  return;
31
32
  }
32
- E(null);
33
+ j(null);
33
34
  try {
34
- await p(C), f();
35
+ await v(O), _();
35
36
  } catch (e) {
36
- E(e instanceof Error ? e.message : "Failed to update seats.");
37
+ j(e instanceof Error ? e.message : "Failed to update seats.");
37
38
  }
38
39
  }
39
40
  }
40
- return /* @__PURE__ */ c("div", {
41
- className: "fixed inset-0 bg-overlay-scrim flex items-center justify-center z-50 p-4",
42
- children: /* @__PURE__ */ l("div", {
43
- className: "bg-bg-surface border border-border-subtle rounded-card shadow-[var(--shadow-elevation-4)] w-full max-w-md",
44
- role: "dialog",
45
- "aria-modal": "true",
46
- "aria-labelledby": "manage-seats-title",
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: "flex items-center justify-between p-6 border-b border-border-subtle",
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: S
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: j,
86
- disabled: C <= v || m,
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: C,
94
- min: v,
95
- max: y,
96
- onChange: (e) => N(e.target.value),
97
- disabled: m,
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: M,
103
- disabled: C >= y || m,
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: v === y ? `(${v} fixed)` : `(min ${v}${y < 999 ? `, max ${y}` : ""})`
98
+ children: C === w ? `(${C} fixed)` : `(min ${C}${w < 999 ? `, max ${w}` : ""})`
111
99
  })
112
100
  ]
113
101
  })]
114
102
  }),
115
- b > 0 && /* @__PURE__ */ l("div", {
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(b, x), " / month"] })]
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(k, x), " / month"] })]
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: D > 0 ? "text-status-error-text" : D < 0 ? "text-status-success-text" : "",
130
- children: [e(O, x), " / month"]
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
- D !== 0 && /* @__PURE__ */ c("div", {
121
+ M !== 0 && /* @__PURE__ */ c("div", {
134
122
  className: "text-xs text-text-secondary",
135
- children: D > 0 ? `+${D} seat${D === 1 ? "" : "s"} — +${e(D * b, x)}/month` : `${D} seat${Math.abs(D) === 1 ? "" : "s"} — ${e(D * b, x)}/month`
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
- A && D > 0 && /* @__PURE__ */ l("div", {
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
- T && /* @__PURE__ */ l("div", {
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: T })]
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("div", {
150
- className: "flex items-center justify-end gap-3 p-6 border-t border-border-subtle",
151
- children: [/* @__PURE__ */ c("button", {
152
- type: "button",
153
- onClick: f,
154
- disabled: m,
155
- 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",
156
- children: "Cancel"
157
- }), /* @__PURE__ */ l("button", {
158
- type: "button",
159
- onClick: P,
160
- disabled: m || C === S || C < v || C > y,
161
- 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",
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 { d as ManageSeatsModal };
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-muted flex items-center justify-center",
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-muted-foreground",
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-foreground",
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-muted-foreground max-w-sm",
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-muted flex items-center justify-center\">\n <svg\n className=\"size-6 text-muted-foreground\"\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-foreground\">\n {tr('billing.accessDenied.title', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground 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;eAA0C;KAAmB,CAAA;IACtE;;EACF,CAAA"}
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-muted/40 px-2 py-1" : p ? "inline-flex items-center gap-2" : "flex items-center gap-3",
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-foreground" : "truncate text-sm font-medium text-foreground",
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-muted-foreground",
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-muted-foreground hover:text-foreground",
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-card px-2 py-1",
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-muted-foreground",
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-muted-foreground transition hover:text-foreground",
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-muted/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-primary/10 text-[10px] font-semibold text-primary'\n : 'flex size-9 items-center justify-center rounded-full bg-primary/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-foreground'\n : 'truncate text-sm font-medium text-foreground'\n }\n >\n {profile.displayName}\n </p>\n {!isChip ? (\n <p className=\"truncate text-xs text-muted-foreground\">\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-muted-foreground hover:text-foreground\">\n ID\n </summary>\n <div className=\"mt-1 flex items-center gap-2 rounded-md border border-border bg-card px-2 py-1\">\n <span className=\"font-mono text-[11px] text-muted-foreground\">{profile.actorId}</span>\n <button\n type=\"button\"\n onClick={onCopy}\n className=\"text-muted-foreground transition hover:text-foreground\"\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,2FACA,IACE,mCACA;YANV;GASE,kBAAC,OAAD;IACE,WACE,IACI,8GACA;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,wCACA;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;gBAA+C,EAAQ;MAAe,CAAA,EACtF,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"}
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-card overflow-hidden",
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-muted/30 flex items-center justify-between hover:bg-muted/50 transition-colors",
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-foreground",
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-muted-foreground",
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-muted-foreground mb-4",
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-foreground",
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-muted-foreground",
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-muted-foreground font-normal truncate",
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-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground ${o ? "flex-1 min-w-0" : "w-full"} ${a?.amount === void 0 ? "" : "border-primary/50"}`
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-background text-foreground focus:outline-none focus:ring-1 focus:ring-primary placeholder:text-muted-foreground disabled:opacity-40"
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,4BACA,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"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.623.3",
3
+ "version": "2026.624.2",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [