@carlonicora/nextjs-jsonapi 1.23.0 → 1.24.1
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/ApiDataInterface-DPP8s46n.d.mts +21 -0
- package/dist/ApiDataInterface-DPP8s46n.d.ts +21 -0
- package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.mts +20 -0
- package/dist/ApiRequestDataTypeInterface-CUKFDBx2.d.ts +20 -0
- package/dist/{ApiResponseInterface-BKyod24U.d.ts → ApiResponseInterface-CAIAeP5d.d.ts} +1 -1
- package/dist/{ApiResponseInterface-Dqvu09tz.d.mts → ApiResponseInterface-zeewugD7.d.mts} +1 -1
- package/dist/AuthComponent-hxOPs9o8.d.mts +11 -0
- package/dist/AuthComponent-hxOPs9o8.d.ts +11 -0
- package/dist/{BlockNoteEditor-KQTKURH6.js → BlockNoteEditor-UCHRVVVZ.js} +15 -15
- package/dist/{BlockNoteEditor-KQTKURH6.js.map → BlockNoteEditor-UCHRVVVZ.js.map} +1 -1
- package/dist/{BlockNoteEditor-VKN4LZUV.mjs → BlockNoteEditor-ZYZZ6B45.mjs} +5 -5
- package/dist/JsonApiRequest-GR3L56A5.js +24 -0
- package/dist/{JsonApiRequest-54ZBO7WQ.js.map → JsonApiRequest-GR3L56A5.js.map} +1 -1
- package/dist/{JsonApiRequest-XWQWTFEQ.mjs → JsonApiRequest-K5BRU7RE.mjs} +2 -2
- package/dist/billing/index.d.mts +249 -0
- package/dist/billing/index.d.ts +249 -0
- package/dist/billing/index.js +3479 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/index.mjs +3479 -0
- package/dist/billing/index.mjs.map +1 -0
- package/dist/{chunk-KUFWHMMY.mjs → chunk-5U4NJJOF.mjs} +5 -5
- package/dist/{chunk-YF5KBA7H.mjs → chunk-CU4RXSNY.mjs} +168 -3578
- package/dist/chunk-CU4RXSNY.mjs.map +1 -0
- package/dist/{chunk-LSIUPIYQ.js → chunk-EW6QPMN3.js} +3 -3
- package/dist/{chunk-LSIUPIYQ.js.map → chunk-EW6QPMN3.js.map} +1 -1
- package/dist/{chunk-LI6CPNJI.js → chunk-FM6WRAN5.js} +1 -1
- package/dist/{chunk-LI6CPNJI.js.map → chunk-FM6WRAN5.js.map} +1 -1
- package/dist/{chunk-L6XLESU5.mjs → chunk-GR4QPP36.mjs} +2 -2
- package/dist/{chunk-ODNMW2CG.js → chunk-ILKUML3Z.js} +761 -4171
- package/dist/chunk-ILKUML3Z.js.map +1 -0
- package/dist/{chunk-UYY34W7R.js → chunk-NQVPCNRS.js} +26 -26
- package/dist/{chunk-UYY34W7R.js.map → chunk-NQVPCNRS.js.map} +1 -1
- package/dist/{chunk-3VM3WAOV.mjs → chunk-U4MTVHOC.mjs} +1 -1
- package/dist/client/index.d.mts +7 -6
- package/dist/client/index.d.ts +7 -6
- package/dist/client/index.js +5 -5
- package/dist/client/index.mjs +4 -4
- package/dist/components/index.d.mts +8 -250
- package/dist/components/index.d.ts +8 -250
- package/dist/components/index.js +5 -83
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +4 -82
- package/dist/{config--nwiW74Z.d.ts → config-D_91hrI-.d.ts} +1 -1
- package/dist/{config-BKSQmUWU.d.mts → config-h0hNLceh.d.mts} +1 -1
- package/dist/{content.interface-4VICFRA0.d.ts → content.interface-CUIEQ0jk.d.ts} +2 -2
- package/dist/{content.interface-CFc97-Cj.d.mts → content.interface-QcsFR5Ad.d.mts} +2 -2
- package/dist/contexts/index.d.mts +4 -3
- package/dist/contexts/index.d.ts +4 -3
- package/dist/contexts/index.js +5 -5
- package/dist/contexts/index.mjs +4 -4
- package/dist/core/index.d.mts +12 -10
- package/dist/core/index.d.ts +12 -10
- package/dist/core/index.js +3 -3
- package/dist/core/index.mjs +2 -2
- package/dist/index.d.mts +9 -7
- package/dist/index.d.ts +9 -7
- package/dist/index.js +4 -4
- package/dist/index.mjs +3 -3
- package/dist/{notification.interface-CqwaOIgM.d.ts → notification.interface-D7_g5SnS.d.ts} +2 -1
- package/dist/{notification.interface-BGaPiCUM.d.mts → notification.interface-blT8r47t.d.mts} +2 -1
- package/dist/{s3.service-BYs88XEE.d.ts → s3.service-B83hUhqV.d.ts} +4 -3
- package/dist/{s3.service-C0BjOdvn.d.mts → s3.service-DSDfcr0S.d.mts} +4 -3
- package/dist/server/index.d.mts +6 -5
- package/dist/server/index.d.ts +6 -5
- package/dist/server/index.js +15 -15
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +5 -5
- package/dist/{stripe-subscription.interface-B-TM40Io.d.ts → stripe-subscription.interface-CFtupgYh.d.mts} +2 -12
- package/dist/{stripe-subscription.interface-DDxnpj0F.d.mts → stripe-subscription.interface-CNTsrbAx.d.ts} +2 -12
- package/dist/testing/index.d.mts +3 -2
- package/dist/testing/index.d.ts +3 -2
- package/dist/{useSocket-BNj9PrRw.d.mts → useSocket-B5M_a4bD.d.mts} +1 -1
- package/dist/{useSocket-Dwt8cz1x.d.ts → useSocket-Dd03muLJ.d.ts} +1 -1
- package/package.json +36 -31
- package/src/billing/index.ts +8 -0
- package/src/components/index.ts +1 -7
- package/src/components/pages/PageContentContainer.tsx +1 -2
- package/src/shadcnui/ui/resizable.tsx +7 -7
- package/dist/ApiRequestDataTypeInterface-DIEOFn9s.d.mts +0 -40
- package/dist/ApiRequestDataTypeInterface-DIEOFn9s.d.ts +0 -40
- package/dist/JsonApiRequest-54ZBO7WQ.js +0 -24
- package/dist/chunk-ODNMW2CG.js.map +0 -1
- package/dist/chunk-YF5KBA7H.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-VKN4LZUV.mjs.map → BlockNoteEditor-ZYZZ6B45.mjs.map} +0 -0
- /package/dist/{JsonApiRequest-XWQWTFEQ.mjs.map → JsonApiRequest-K5BRU7RE.mjs.map} +0 -0
- /package/dist/{chunk-KUFWHMMY.mjs.map → chunk-5U4NJJOF.mjs.map} +0 -0
- /package/dist/{chunk-L6XLESU5.mjs.map → chunk-GR4QPP36.mjs.map} +0 -0
- /package/dist/{chunk-3VM3WAOV.mjs.map → chunk-U4MTVHOC.mjs.map} +0 -0
|
@@ -0,0 +1,3479 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
Alert,
|
|
4
|
+
AlertDescription,
|
|
5
|
+
AlertDialog,
|
|
6
|
+
AlertDialogAction,
|
|
7
|
+
AlertDialogCancel,
|
|
8
|
+
AlertDialogContent,
|
|
9
|
+
AlertDialogDescription,
|
|
10
|
+
AlertDialogFooter,
|
|
11
|
+
AlertDialogHeader,
|
|
12
|
+
AlertDialogTitle,
|
|
13
|
+
AlertTitle,
|
|
14
|
+
Badge,
|
|
15
|
+
Button,
|
|
16
|
+
Card,
|
|
17
|
+
CardContent,
|
|
18
|
+
CardDescription,
|
|
19
|
+
CardFooter,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
Checkbox,
|
|
23
|
+
CommonEditorButtons,
|
|
24
|
+
Dialog,
|
|
25
|
+
DialogContent,
|
|
26
|
+
DialogDescription,
|
|
27
|
+
DialogHeader,
|
|
28
|
+
DialogTitle,
|
|
29
|
+
DropdownMenu,
|
|
30
|
+
DropdownMenuContent,
|
|
31
|
+
DropdownMenuItem,
|
|
32
|
+
DropdownMenuTrigger,
|
|
33
|
+
Form,
|
|
34
|
+
FormCheckbox,
|
|
35
|
+
FormControl,
|
|
36
|
+
FormInput,
|
|
37
|
+
FormItem,
|
|
38
|
+
FormLabel,
|
|
39
|
+
FormSelect,
|
|
40
|
+
FormTextarea,
|
|
41
|
+
Input,
|
|
42
|
+
Label,
|
|
43
|
+
Skeleton,
|
|
44
|
+
Table,
|
|
45
|
+
TableBody,
|
|
46
|
+
TableCell,
|
|
47
|
+
TableHead,
|
|
48
|
+
TableHeader,
|
|
49
|
+
TableRow,
|
|
50
|
+
Tabs,
|
|
51
|
+
TabsList,
|
|
52
|
+
TabsTrigger,
|
|
53
|
+
useCurrentUserContext
|
|
54
|
+
} from "../chunk-CU4RXSNY.mjs";
|
|
55
|
+
import {
|
|
56
|
+
getRoleId,
|
|
57
|
+
getStripePublishableKey
|
|
58
|
+
} from "../chunk-GR4QPP36.mjs";
|
|
59
|
+
import {
|
|
60
|
+
StripeCustomerService,
|
|
61
|
+
StripeInvoiceService,
|
|
62
|
+
StripePriceService,
|
|
63
|
+
StripeProductService,
|
|
64
|
+
StripeSubscriptionService,
|
|
65
|
+
StripeUsageService,
|
|
66
|
+
cn
|
|
67
|
+
} from "../chunk-5U4NJJOF.mjs";
|
|
68
|
+
import "../chunk-AUXK7QSA.mjs";
|
|
69
|
+
import "../chunk-C7C7VY4F.mjs";
|
|
70
|
+
import "../chunk-U4MTVHOC.mjs";
|
|
71
|
+
import "../chunk-VOXD3ZLY.mjs";
|
|
72
|
+
import {
|
|
73
|
+
__name
|
|
74
|
+
} from "../chunk-PAWJFY3S.mjs";
|
|
75
|
+
|
|
76
|
+
// src/features/billing/components/cards/SubscriptionSummaryCard.tsx
|
|
77
|
+
import { ChevronRight, CreditCard } from "lucide-react";
|
|
78
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
79
|
+
function getStatusBadgeVariant(status) {
|
|
80
|
+
switch (status) {
|
|
81
|
+
case "active" /* ACTIVE */:
|
|
82
|
+
return "default";
|
|
83
|
+
case "trialing" /* TRIALING */:
|
|
84
|
+
return "secondary";
|
|
85
|
+
case "past_due" /* PAST_DUE */:
|
|
86
|
+
case "unpaid" /* UNPAID */:
|
|
87
|
+
case "canceled" /* CANCELED */:
|
|
88
|
+
return "destructive";
|
|
89
|
+
default:
|
|
90
|
+
return "outline";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
__name(getStatusBadgeVariant, "getStatusBadgeVariant");
|
|
94
|
+
function formatDate(date) {
|
|
95
|
+
return new Date(date).toLocaleDateString(void 0, {
|
|
96
|
+
year: "numeric",
|
|
97
|
+
month: "short",
|
|
98
|
+
day: "numeric"
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
__name(formatDate, "formatDate");
|
|
102
|
+
function formatPrice(amount, currency) {
|
|
103
|
+
if (amount === void 0) return "N/A";
|
|
104
|
+
const currencyCode = currency?.toUpperCase() || "USD";
|
|
105
|
+
return new Intl.NumberFormat(void 0, {
|
|
106
|
+
style: "currency",
|
|
107
|
+
currency: currencyCode
|
|
108
|
+
}).format(amount / 100);
|
|
109
|
+
}
|
|
110
|
+
__name(formatPrice, "formatPrice");
|
|
111
|
+
function formatPlanName(subscription) {
|
|
112
|
+
const productName = subscription.price?.product?.name || "";
|
|
113
|
+
const nickname = subscription.price?.nickname || "";
|
|
114
|
+
if (productName && nickname) {
|
|
115
|
+
return `${productName} - ${nickname}`;
|
|
116
|
+
}
|
|
117
|
+
return productName || nickname || "Subscription";
|
|
118
|
+
}
|
|
119
|
+
__name(formatPlanName, "formatPlanName");
|
|
120
|
+
function SubscriptionSummaryCard({
|
|
121
|
+
subscriptions,
|
|
122
|
+
loading,
|
|
123
|
+
error,
|
|
124
|
+
onManageClick
|
|
125
|
+
}) {
|
|
126
|
+
if (loading) {
|
|
127
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
128
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
129
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-sm font-medium", children: "Subscriptions" }),
|
|
130
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" })
|
|
131
|
+
] }),
|
|
132
|
+
/* @__PURE__ */ jsxs(CardContent, { children: [
|
|
133
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-32 mb-2" }),
|
|
134
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24 mb-1" }),
|
|
135
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" })
|
|
136
|
+
] })
|
|
137
|
+
] });
|
|
138
|
+
}
|
|
139
|
+
if (error) {
|
|
140
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
141
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
142
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-sm font-medium", children: "Subscriptions" }),
|
|
143
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) })
|
|
146
|
+
] });
|
|
147
|
+
}
|
|
148
|
+
const activeSubscriptions = subscriptions.filter(
|
|
149
|
+
(sub) => sub.status === "active" /* ACTIVE */ || sub.status === "trialing" /* TRIALING */
|
|
150
|
+
);
|
|
151
|
+
const primarySubscription = activeSubscriptions[0];
|
|
152
|
+
return /* @__PURE__ */ jsxs(Card, { className: "cursor-pointer hover:bg-accent/50 transition-colors", onClick: onManageClick, children: [
|
|
153
|
+
/* @__PURE__ */ jsxs(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
154
|
+
/* @__PURE__ */ jsx(CardTitle, { className: "text-sm font-medium", children: "Subscriptions" }),
|
|
155
|
+
/* @__PURE__ */ jsx(CreditCard, { className: "h-4 w-4 text-muted-foreground" })
|
|
156
|
+
] }),
|
|
157
|
+
/* @__PURE__ */ jsx(CardContent, { children: subscriptions.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
158
|
+
/* @__PURE__ */ jsx("p", { className: "text-xl font-bold text-muted-foreground", children: "No active plan" }),
|
|
159
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Subscribe to get started" }),
|
|
160
|
+
/* @__PURE__ */ jsxs(
|
|
161
|
+
Button,
|
|
162
|
+
{
|
|
163
|
+
variant: "outline",
|
|
164
|
+
size: "sm",
|
|
165
|
+
className: "mt-2",
|
|
166
|
+
onClick: (e) => {
|
|
167
|
+
e.stopPropagation();
|
|
168
|
+
onManageClick();
|
|
169
|
+
},
|
|
170
|
+
children: [
|
|
171
|
+
"View Plans",
|
|
172
|
+
/* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 ml-1" })
|
|
173
|
+
]
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }) : primarySubscription ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
177
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
178
|
+
/* @__PURE__ */ jsx("p", { className: "text-xl font-bold", children: formatPlanName(primarySubscription) }),
|
|
179
|
+
/* @__PURE__ */ jsx(Badge, { variant: primarySubscription.cancelAtPeriodEnd ? "secondary" : getStatusBadgeVariant(primarySubscription.status), children: primarySubscription.cancelAtPeriodEnd ? "Canceling" : primarySubscription.status })
|
|
180
|
+
] }),
|
|
181
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
|
|
182
|
+
formatPrice(primarySubscription.price?.unitAmount, primarySubscription.price?.currency),
|
|
183
|
+
primarySubscription.price?.recurring && /* @__PURE__ */ jsxs("span", { children: [
|
|
184
|
+
"/",
|
|
185
|
+
primarySubscription.price.recurring.interval
|
|
186
|
+
] })
|
|
187
|
+
] }),
|
|
188
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: primarySubscription.cancelAtPeriodEnd ? `Cancels on ${formatDate(primarySubscription.currentPeriodEnd)}` : `Renews on ${formatDate(primarySubscription.currentPeriodEnd)}` }),
|
|
189
|
+
activeSubscriptions.length > 1 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
190
|
+
"+",
|
|
191
|
+
activeSubscriptions.length - 1,
|
|
192
|
+
" more subscription(s)"
|
|
193
|
+
] })
|
|
194
|
+
] }) : null })
|
|
195
|
+
] });
|
|
196
|
+
}
|
|
197
|
+
__name(SubscriptionSummaryCard, "SubscriptionSummaryCard");
|
|
198
|
+
|
|
199
|
+
// src/features/billing/components/cards/PaymentMethodSummaryCard.tsx
|
|
200
|
+
import { Wallet, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
201
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
202
|
+
function getCardBrandIcon(brand) {
|
|
203
|
+
const brandMap = {
|
|
204
|
+
visa: "Visa",
|
|
205
|
+
mastercard: "Mastercard",
|
|
206
|
+
amex: "Amex",
|
|
207
|
+
discover: "Discover",
|
|
208
|
+
diners: "Diners",
|
|
209
|
+
jcb: "JCB",
|
|
210
|
+
unionpay: "UnionPay"
|
|
211
|
+
};
|
|
212
|
+
return brandMap[brand.toLowerCase()] || brand;
|
|
213
|
+
}
|
|
214
|
+
__name(getCardBrandIcon, "getCardBrandIcon");
|
|
215
|
+
function PaymentMethodSummaryCard({
|
|
216
|
+
paymentMethods,
|
|
217
|
+
defaultPaymentMethodId,
|
|
218
|
+
loading,
|
|
219
|
+
error,
|
|
220
|
+
onManageClick
|
|
221
|
+
}) {
|
|
222
|
+
if (loading) {
|
|
223
|
+
return /* @__PURE__ */ jsxs2(Card, { children: [
|
|
224
|
+
/* @__PURE__ */ jsxs2(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
225
|
+
/* @__PURE__ */ jsx2(CardTitle, { className: "text-sm font-medium", children: "Payment Method" }),
|
|
226
|
+
/* @__PURE__ */ jsx2(Wallet, { className: "h-4 w-4 text-muted-foreground" })
|
|
227
|
+
] }),
|
|
228
|
+
/* @__PURE__ */ jsxs2(CardContent, { children: [
|
|
229
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-6 w-32 mb-2" }),
|
|
230
|
+
/* @__PURE__ */ jsx2(Skeleton, { className: "h-4 w-24" })
|
|
231
|
+
] })
|
|
232
|
+
] });
|
|
233
|
+
}
|
|
234
|
+
if (error) {
|
|
235
|
+
return /* @__PURE__ */ jsxs2(Card, { children: [
|
|
236
|
+
/* @__PURE__ */ jsxs2(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
237
|
+
/* @__PURE__ */ jsx2(CardTitle, { className: "text-sm font-medium", children: "Payment Method" }),
|
|
238
|
+
/* @__PURE__ */ jsx2(Wallet, { className: "h-4 w-4 text-muted-foreground" })
|
|
239
|
+
] }),
|
|
240
|
+
/* @__PURE__ */ jsx2(CardContent, { children: /* @__PURE__ */ jsx2("p", { className: "text-sm text-destructive", children: error }) })
|
|
241
|
+
] });
|
|
242
|
+
}
|
|
243
|
+
const defaultMethod = paymentMethods.find((pm) => pm.id === defaultPaymentMethodId) || paymentMethods[0];
|
|
244
|
+
return /* @__PURE__ */ jsxs2(Card, { className: "cursor-pointer hover:bg-accent/50 transition-colors", onClick: onManageClick, children: [
|
|
245
|
+
/* @__PURE__ */ jsxs2(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
246
|
+
/* @__PURE__ */ jsx2(CardTitle, { className: "text-sm font-medium", children: "Payment Method" }),
|
|
247
|
+
/* @__PURE__ */ jsx2(Wallet, { className: "h-4 w-4 text-muted-foreground" })
|
|
248
|
+
] }),
|
|
249
|
+
/* @__PURE__ */ jsx2(CardContent, { children: paymentMethods.length === 0 ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
250
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xl font-bold text-muted-foreground", children: "No payment method" }),
|
|
251
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xs text-muted-foreground", children: "Add a card to enable subscriptions" }),
|
|
252
|
+
/* @__PURE__ */ jsxs2(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: (e) => {
|
|
253
|
+
e.stopPropagation();
|
|
254
|
+
onManageClick();
|
|
255
|
+
}, children: [
|
|
256
|
+
"Add Card",
|
|
257
|
+
/* @__PURE__ */ jsx2(ChevronRight2, { className: "h-4 w-4 ml-1" })
|
|
258
|
+
] })
|
|
259
|
+
] }) : defaultMethod?.card ? /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
260
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-xl font-bold", children: [
|
|
261
|
+
getCardBrandIcon(defaultMethod.card.brand),
|
|
262
|
+
" ****",
|
|
263
|
+
defaultMethod.card.last4
|
|
264
|
+
] }),
|
|
265
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-sm text-muted-foreground", children: [
|
|
266
|
+
"Expires ",
|
|
267
|
+
String(defaultMethod.card.expMonth).padStart(2, "0"),
|
|
268
|
+
"/",
|
|
269
|
+
defaultMethod.card.expYear
|
|
270
|
+
] }),
|
|
271
|
+
paymentMethods.length > 1 && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
|
|
272
|
+
"+",
|
|
273
|
+
paymentMethods.length - 1,
|
|
274
|
+
" more card(s)"
|
|
275
|
+
] })
|
|
276
|
+
] }) : /* @__PURE__ */ jsxs2("div", { className: "space-y-2", children: [
|
|
277
|
+
/* @__PURE__ */ jsx2("p", { className: "text-xl font-bold", children: defaultMethod?.type || "Payment Method" }),
|
|
278
|
+
paymentMethods.length > 1 && /* @__PURE__ */ jsxs2("p", { className: "text-xs text-muted-foreground", children: [
|
|
279
|
+
"+",
|
|
280
|
+
paymentMethods.length - 1,
|
|
281
|
+
" more method(s)"
|
|
282
|
+
] })
|
|
283
|
+
] }) })
|
|
284
|
+
] });
|
|
285
|
+
}
|
|
286
|
+
__name(PaymentMethodSummaryCard, "PaymentMethodSummaryCard");
|
|
287
|
+
|
|
288
|
+
// src/features/billing/components/cards/CustomerInfoCard.tsx
|
|
289
|
+
import { ExternalLink, User } from "lucide-react";
|
|
290
|
+
import { useState } from "react";
|
|
291
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
292
|
+
function formatBalance(balance, currency) {
|
|
293
|
+
if (balance === void 0 || balance === 0) return "$0.00";
|
|
294
|
+
const currencyCode = currency?.toUpperCase() || "USD";
|
|
295
|
+
const displayBalance = -balance;
|
|
296
|
+
return new Intl.NumberFormat(void 0, {
|
|
297
|
+
style: "currency",
|
|
298
|
+
currency: currencyCode
|
|
299
|
+
}).format(displayBalance / 100);
|
|
300
|
+
}
|
|
301
|
+
__name(formatBalance, "formatBalance");
|
|
302
|
+
function CustomerInfoCard({ customer, loading, error }) {
|
|
303
|
+
const [portalLoading, setPortalLoading] = useState(false);
|
|
304
|
+
const handlePortalClick = /* @__PURE__ */ __name(async (e) => {
|
|
305
|
+
e.stopPropagation();
|
|
306
|
+
setPortalLoading(true);
|
|
307
|
+
try {
|
|
308
|
+
const { url } = await StripeCustomerService.createPortalSession();
|
|
309
|
+
window.open(url, "_blank");
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error("[CustomerInfoCard] Failed to create portal session:", err);
|
|
312
|
+
} finally {
|
|
313
|
+
setPortalLoading(false);
|
|
314
|
+
}
|
|
315
|
+
}, "handlePortalClick");
|
|
316
|
+
if (loading) {
|
|
317
|
+
return /* @__PURE__ */ jsxs3(Card, { children: [
|
|
318
|
+
/* @__PURE__ */ jsxs3(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
319
|
+
/* @__PURE__ */ jsx3(CardTitle, { className: "text-sm font-medium", children: "Billing Account" }),
|
|
320
|
+
/* @__PURE__ */ jsx3(User, { className: "h-4 w-4 text-muted-foreground" })
|
|
321
|
+
] }),
|
|
322
|
+
/* @__PURE__ */ jsxs3(CardContent, { children: [
|
|
323
|
+
/* @__PURE__ */ jsx3(Skeleton, { className: "h-6 w-32 mb-2" }),
|
|
324
|
+
/* @__PURE__ */ jsx3(Skeleton, { className: "h-4 w-48 mb-1" }),
|
|
325
|
+
/* @__PURE__ */ jsx3(Skeleton, { className: "h-4 w-24" })
|
|
326
|
+
] })
|
|
327
|
+
] });
|
|
328
|
+
}
|
|
329
|
+
if (error) {
|
|
330
|
+
return /* @__PURE__ */ jsxs3(Card, { children: [
|
|
331
|
+
/* @__PURE__ */ jsxs3(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
332
|
+
/* @__PURE__ */ jsx3(CardTitle, { className: "text-sm font-medium", children: "Billing Account" }),
|
|
333
|
+
/* @__PURE__ */ jsx3(User, { className: "h-4 w-4 text-muted-foreground" })
|
|
334
|
+
] }),
|
|
335
|
+
/* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsx3("p", { className: "text-sm text-destructive", children: error }) })
|
|
336
|
+
] });
|
|
337
|
+
}
|
|
338
|
+
if (!customer) {
|
|
339
|
+
return /* @__PURE__ */ jsxs3(Card, { children: [
|
|
340
|
+
/* @__PURE__ */ jsxs3(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
341
|
+
/* @__PURE__ */ jsx3(CardTitle, { className: "text-sm font-medium", children: "Billing Account" }),
|
|
342
|
+
/* @__PURE__ */ jsx3(User, { className: "h-4 w-4 text-muted-foreground" })
|
|
343
|
+
] }),
|
|
344
|
+
/* @__PURE__ */ jsxs3(CardContent, { children: [
|
|
345
|
+
/* @__PURE__ */ jsx3("p", { className: "text-xl font-bold text-muted-foreground", children: "Not set up" }),
|
|
346
|
+
/* @__PURE__ */ jsx3("p", { className: "text-xs text-muted-foreground", children: "Billing account will be created when you subscribe" })
|
|
347
|
+
] })
|
|
348
|
+
] });
|
|
349
|
+
}
|
|
350
|
+
return /* @__PURE__ */ jsxs3(Card, { children: [
|
|
351
|
+
/* @__PURE__ */ jsxs3(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
352
|
+
/* @__PURE__ */ jsx3(CardTitle, { className: "text-sm font-medium", children: "Billing Account" }),
|
|
353
|
+
/* @__PURE__ */ jsx3(User, { className: "h-4 w-4 text-muted-foreground" })
|
|
354
|
+
] }),
|
|
355
|
+
/* @__PURE__ */ jsx3(CardContent, { children: /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
356
|
+
customer.name && /* @__PURE__ */ jsx3("p", { className: "text-xl font-bold", children: customer.name }),
|
|
357
|
+
customer.email && /* @__PURE__ */ jsx3("p", { className: "text-sm text-muted-foreground", children: customer.email }),
|
|
358
|
+
customer.balance !== void 0 && customer.balance !== 0 && /* @__PURE__ */ jsxs3("p", { className: "text-sm", children: [
|
|
359
|
+
/* @__PURE__ */ jsx3("span", { className: "text-muted-foreground", children: "Credit Balance: " }),
|
|
360
|
+
/* @__PURE__ */ jsx3("span", { className: customer.balance < 0 ? "text-green-600" : "text-destructive", children: formatBalance(customer.balance, customer.currency) })
|
|
361
|
+
] }),
|
|
362
|
+
/* @__PURE__ */ jsxs3(Button, { variant: "outline", size: "sm", className: "mt-2", onClick: handlePortalClick, disabled: portalLoading, children: [
|
|
363
|
+
portalLoading ? "Loading..." : "Manage in Stripe Portal",
|
|
364
|
+
/* @__PURE__ */ jsx3(ExternalLink, { className: "h-4 w-4 ml-1" })
|
|
365
|
+
] })
|
|
366
|
+
] }) })
|
|
367
|
+
] });
|
|
368
|
+
}
|
|
369
|
+
__name(CustomerInfoCard, "CustomerInfoCard");
|
|
370
|
+
|
|
371
|
+
// src/features/billing/components/cards/InvoicesSummaryCard.tsx
|
|
372
|
+
import { ChevronRight as ChevronRight3, ReceiptIcon } from "lucide-react";
|
|
373
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
374
|
+
function getStatusBadgeVariant2(status) {
|
|
375
|
+
switch (status) {
|
|
376
|
+
case "paid" /* PAID */:
|
|
377
|
+
return "default";
|
|
378
|
+
case "open" /* OPEN */:
|
|
379
|
+
return "secondary";
|
|
380
|
+
case "uncollectible" /* UNCOLLECTIBLE */:
|
|
381
|
+
case "void" /* VOID */:
|
|
382
|
+
return "destructive";
|
|
383
|
+
default:
|
|
384
|
+
return "outline";
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
__name(getStatusBadgeVariant2, "getStatusBadgeVariant");
|
|
388
|
+
function formatDate2(date) {
|
|
389
|
+
return new Date(date).toLocaleDateString(void 0, {
|
|
390
|
+
year: "numeric",
|
|
391
|
+
month: "short",
|
|
392
|
+
day: "numeric"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
__name(formatDate2, "formatDate");
|
|
396
|
+
function formatAmount(amount, currency) {
|
|
397
|
+
const currencyCode = currency?.toUpperCase() || "USD";
|
|
398
|
+
return new Intl.NumberFormat(void 0, {
|
|
399
|
+
style: "currency",
|
|
400
|
+
currency: currencyCode
|
|
401
|
+
}).format(amount / 100);
|
|
402
|
+
}
|
|
403
|
+
__name(formatAmount, "formatAmount");
|
|
404
|
+
function InvoicesSummaryCard({ invoices, loading, error, onViewAllClick }) {
|
|
405
|
+
if (loading) {
|
|
406
|
+
return /* @__PURE__ */ jsxs4(Card, { children: [
|
|
407
|
+
/* @__PURE__ */ jsxs4(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
408
|
+
/* @__PURE__ */ jsx4(CardTitle, { className: "text-sm font-medium", children: "Recent Invoices" }),
|
|
409
|
+
/* @__PURE__ */ jsx4(ReceiptIcon, { className: "h-4 w-4 text-muted-foreground" })
|
|
410
|
+
] }),
|
|
411
|
+
/* @__PURE__ */ jsxs4(CardContent, { children: [
|
|
412
|
+
/* @__PURE__ */ jsx4(Skeleton, { className: "h-6 w-24 mb-2" }),
|
|
413
|
+
/* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-32 mb-1" }),
|
|
414
|
+
/* @__PURE__ */ jsx4(Skeleton, { className: "h-4 w-20" })
|
|
415
|
+
] })
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
if (error) {
|
|
419
|
+
return /* @__PURE__ */ jsxs4(Card, { children: [
|
|
420
|
+
/* @__PURE__ */ jsxs4(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
421
|
+
/* @__PURE__ */ jsx4(CardTitle, { className: "text-sm font-medium", children: "Recent Invoices" }),
|
|
422
|
+
/* @__PURE__ */ jsx4(ReceiptIcon, { className: "h-4 w-4 text-muted-foreground" })
|
|
423
|
+
] }),
|
|
424
|
+
/* @__PURE__ */ jsx4(CardContent, { children: /* @__PURE__ */ jsx4("p", { className: "text-sm text-destructive", children: error }) })
|
|
425
|
+
] });
|
|
426
|
+
}
|
|
427
|
+
const latestInvoice = invoices[0];
|
|
428
|
+
const paidInvoices = invoices.filter((inv) => inv.status === "paid" /* PAID */);
|
|
429
|
+
const openInvoices = invoices.filter((inv) => inv.status === "open" /* OPEN */);
|
|
430
|
+
return /* @__PURE__ */ jsxs4(Card, { className: "cursor-pointer hover:bg-accent/50 transition-colors", onClick: onViewAllClick, children: [
|
|
431
|
+
/* @__PURE__ */ jsxs4(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
432
|
+
/* @__PURE__ */ jsx4(CardTitle, { className: "text-sm font-medium", children: "Recent Invoices" }),
|
|
433
|
+
/* @__PURE__ */ jsx4(ReceiptIcon, { className: "h-4 w-4 text-muted-foreground" })
|
|
434
|
+
] }),
|
|
435
|
+
/* @__PURE__ */ jsx4(CardContent, { children: invoices.length === 0 ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
436
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xl font-bold text-muted-foreground", children: "No invoices yet" }),
|
|
437
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xs text-muted-foreground", children: "Invoices will appear after your first billing cycle" })
|
|
438
|
+
] }) : latestInvoice ? /* @__PURE__ */ jsxs4("div", { className: "space-y-2", children: [
|
|
439
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-2", children: [
|
|
440
|
+
/* @__PURE__ */ jsx4("p", { className: "text-xl font-bold", children: formatAmount(latestInvoice.total, latestInvoice.currency) }),
|
|
441
|
+
/* @__PURE__ */ jsx4(Badge, { variant: getStatusBadgeVariant2(latestInvoice.status), children: latestInvoice.status })
|
|
442
|
+
] }),
|
|
443
|
+
/* @__PURE__ */ jsx4("p", { className: "text-sm text-muted-foreground", children: latestInvoice.stripeInvoiceNumber || `Invoice from ${formatDate2(latestInvoice.periodStart)}` }),
|
|
444
|
+
/* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-4 text-xs text-muted-foreground", children: [
|
|
445
|
+
paidInvoices.length > 0 && /* @__PURE__ */ jsxs4("span", { children: [
|
|
446
|
+
paidInvoices.length,
|
|
447
|
+
" paid"
|
|
448
|
+
] }),
|
|
449
|
+
openInvoices.length > 0 && /* @__PURE__ */ jsxs4("span", { className: "text-orange-600", children: [
|
|
450
|
+
openInvoices.length,
|
|
451
|
+
" open"
|
|
452
|
+
] }),
|
|
453
|
+
/* @__PURE__ */ jsxs4("span", { className: "flex items-center", children: [
|
|
454
|
+
"View all",
|
|
455
|
+
/* @__PURE__ */ jsx4(ChevronRight3, { className: "h-3 w-3 ml-1" })
|
|
456
|
+
] })
|
|
457
|
+
] })
|
|
458
|
+
] }) : null })
|
|
459
|
+
] });
|
|
460
|
+
}
|
|
461
|
+
__name(InvoicesSummaryCard, "InvoicesSummaryCard");
|
|
462
|
+
|
|
463
|
+
// src/features/billing/components/cards/BillingUsageSummaryCard.tsx
|
|
464
|
+
import { Activity, ChevronRight as ChevronRight4 } from "lucide-react";
|
|
465
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
466
|
+
function formatNumber(value) {
|
|
467
|
+
return new Intl.NumberFormat(void 0, {
|
|
468
|
+
notation: "compact",
|
|
469
|
+
compactDisplay: "short"
|
|
470
|
+
}).format(value);
|
|
471
|
+
}
|
|
472
|
+
__name(formatNumber, "formatNumber");
|
|
473
|
+
function BillingUsageSummaryCard({
|
|
474
|
+
meters,
|
|
475
|
+
summaries,
|
|
476
|
+
loading,
|
|
477
|
+
error,
|
|
478
|
+
onViewDetailsClick
|
|
479
|
+
}) {
|
|
480
|
+
if (loading) {
|
|
481
|
+
return /* @__PURE__ */ jsxs5(Card, { children: [
|
|
482
|
+
/* @__PURE__ */ jsxs5(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
483
|
+
/* @__PURE__ */ jsx5(CardTitle, { className: "text-sm font-medium", children: "Usage This Month" }),
|
|
484
|
+
/* @__PURE__ */ jsx5(Activity, { className: "h-4 w-4 text-muted-foreground" })
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsxs5(CardContent, { children: [
|
|
487
|
+
/* @__PURE__ */ jsx5(Skeleton, { className: "h-6 w-24 mb-2" }),
|
|
488
|
+
/* @__PURE__ */ jsx5(Skeleton, { className: "h-4 w-32" })
|
|
489
|
+
] })
|
|
490
|
+
] });
|
|
491
|
+
}
|
|
492
|
+
if (error) {
|
|
493
|
+
return /* @__PURE__ */ jsxs5(Card, { children: [
|
|
494
|
+
/* @__PURE__ */ jsxs5(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
495
|
+
/* @__PURE__ */ jsx5(CardTitle, { className: "text-sm font-medium", children: "Usage This Month" }),
|
|
496
|
+
/* @__PURE__ */ jsx5(Activity, { className: "h-4 w-4 text-muted-foreground" })
|
|
497
|
+
] }),
|
|
498
|
+
/* @__PURE__ */ jsx5(CardContent, { children: /* @__PURE__ */ jsx5("p", { className: "text-sm text-destructive", children: error }) })
|
|
499
|
+
] });
|
|
500
|
+
}
|
|
501
|
+
const totalUsage = Object.values(summaries).reduce((acc, summary) => {
|
|
502
|
+
return acc + (summary?.aggregatedValue || 0);
|
|
503
|
+
}, 0);
|
|
504
|
+
const primaryMeter = meters.find((m) => summaries[m.id]?.aggregatedValue);
|
|
505
|
+
const primarySummary = primaryMeter ? summaries[primaryMeter.id] : null;
|
|
506
|
+
return /* @__PURE__ */ jsxs5(Card, { className: "cursor-pointer hover:bg-accent/50 transition-colors", onClick: onViewDetailsClick, children: [
|
|
507
|
+
/* @__PURE__ */ jsxs5(CardHeader, { className: "flex flex-row items-center justify-between space-y-0 pb-2", children: [
|
|
508
|
+
/* @__PURE__ */ jsx5(CardTitle, { className: "text-sm font-medium", children: "Usage This Month" }),
|
|
509
|
+
/* @__PURE__ */ jsx5(Activity, { className: "h-4 w-4 text-muted-foreground" })
|
|
510
|
+
] }),
|
|
511
|
+
/* @__PURE__ */ jsx5(CardContent, { children: meters.length === 0 ? /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
512
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xl font-bold text-muted-foreground", children: "No meters" }),
|
|
513
|
+
/* @__PURE__ */ jsx5("p", { className: "text-xs text-muted-foreground", children: "No usage meters are configured" })
|
|
514
|
+
] }) : /* @__PURE__ */ jsxs5("div", { className: "space-y-2", children: [
|
|
515
|
+
/* @__PURE__ */ jsxs5("p", { className: "text-xl font-bold", children: [
|
|
516
|
+
formatNumber(totalUsage),
|
|
517
|
+
" units"
|
|
518
|
+
] }),
|
|
519
|
+
primaryMeter && primarySummary && /* @__PURE__ */ jsxs5("p", { className: "text-sm text-muted-foreground", children: [
|
|
520
|
+
primaryMeter.displayName,
|
|
521
|
+
": ",
|
|
522
|
+
formatNumber(primarySummary.aggregatedValue)
|
|
523
|
+
] }),
|
|
524
|
+
meters.length > 1 && /* @__PURE__ */ jsxs5("p", { className: "text-xs text-muted-foreground", children: [
|
|
525
|
+
"Across ",
|
|
526
|
+
meters.length,
|
|
527
|
+
" meters"
|
|
528
|
+
] }),
|
|
529
|
+
/* @__PURE__ */ jsxs5("span", { className: "flex items-center text-xs text-muted-foreground", children: [
|
|
530
|
+
"View details",
|
|
531
|
+
/* @__PURE__ */ jsx5(ChevronRight4, { className: "h-3 w-3 ml-1" })
|
|
532
|
+
] })
|
|
533
|
+
] }) })
|
|
534
|
+
] });
|
|
535
|
+
}
|
|
536
|
+
__name(BillingUsageSummaryCard, "BillingUsageSummaryCard");
|
|
537
|
+
|
|
538
|
+
// src/features/billing/components/containers/BillingDashboardContainer.tsx
|
|
539
|
+
import { CreditCard as CreditCard4, Loader2 as Loader23, Wallet as Wallet2 } from "lucide-react";
|
|
540
|
+
import { useCallback as useCallback2, useEffect as useEffect8, useState as useState14 } from "react";
|
|
541
|
+
|
|
542
|
+
// src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx
|
|
543
|
+
import { CreditCard as CreditCard2 } from "lucide-react";
|
|
544
|
+
import { useEffect as useEffect3, useState as useState4 } from "react";
|
|
545
|
+
|
|
546
|
+
// src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx
|
|
547
|
+
import { CardElement, useElements, useStripe } from "@stripe/react-stripe-js";
|
|
548
|
+
import { useEffect, useState as useState2 } from "react";
|
|
549
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
550
|
+
function PaymentMethodEditor({ open, onOpenChange, onSuccess }) {
|
|
551
|
+
const stripe = useStripe();
|
|
552
|
+
const elements = useElements();
|
|
553
|
+
const [setupIntent, setSetupIntent] = useState2(null);
|
|
554
|
+
const [loading, setLoading] = useState2(true);
|
|
555
|
+
const [isSubmitting, setIsSubmitting] = useState2(false);
|
|
556
|
+
const [error, setError] = useState2(null);
|
|
557
|
+
const [setAsDefault, setSetAsDefault] = useState2(true);
|
|
558
|
+
useEffect(() => {
|
|
559
|
+
const fetchSetupIntent = /* @__PURE__ */ __name(async () => {
|
|
560
|
+
setLoading(true);
|
|
561
|
+
try {
|
|
562
|
+
const intent = await StripeCustomerService.createSetupIntent();
|
|
563
|
+
setSetupIntent(intent);
|
|
564
|
+
} catch (err) {
|
|
565
|
+
console.error("[PaymentMethodEditor] Failed to create setup intent:", err);
|
|
566
|
+
setError("Failed to initialize payment form. Please try again.");
|
|
567
|
+
} finally {
|
|
568
|
+
setLoading(false);
|
|
569
|
+
}
|
|
570
|
+
}, "fetchSetupIntent");
|
|
571
|
+
if (open) {
|
|
572
|
+
fetchSetupIntent();
|
|
573
|
+
}
|
|
574
|
+
}, [open]);
|
|
575
|
+
const handleSubmit = /* @__PURE__ */ __name(async (e) => {
|
|
576
|
+
e.preventDefault();
|
|
577
|
+
if (!stripe || !elements || !setupIntent) {
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
setIsSubmitting(true);
|
|
581
|
+
setError(null);
|
|
582
|
+
try {
|
|
583
|
+
const cardElement = elements.getElement(CardElement);
|
|
584
|
+
if (!cardElement) {
|
|
585
|
+
throw new Error("Card element not found");
|
|
586
|
+
}
|
|
587
|
+
const { error: stripeError, setupIntent: confirmedSetupIntent } = await stripe.confirmCardSetup(
|
|
588
|
+
setupIntent.clientSecret,
|
|
589
|
+
{
|
|
590
|
+
payment_method: {
|
|
591
|
+
card: cardElement
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
);
|
|
595
|
+
if (stripeError) {
|
|
596
|
+
console.error("[PaymentMethodEditor] Stripe error:", stripeError);
|
|
597
|
+
setError(stripeError.message || "Failed to add payment method. Please check your card details.");
|
|
598
|
+
setIsSubmitting(false);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (setAsDefault && confirmedSetupIntent?.payment_method) {
|
|
602
|
+
await StripeCustomerService.setDefaultPaymentMethod({
|
|
603
|
+
paymentMethodId: typeof confirmedSetupIntent.payment_method === "string" ? confirmedSetupIntent.payment_method : confirmedSetupIntent.payment_method.id
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
onSuccess();
|
|
607
|
+
onOpenChange(false);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error("[PaymentMethodEditor] Error:", err);
|
|
610
|
+
setError(err.message || "An unexpected error occurred. Please try again.");
|
|
611
|
+
} finally {
|
|
612
|
+
setIsSubmitting(false);
|
|
613
|
+
}
|
|
614
|
+
}, "handleSubmit");
|
|
615
|
+
return /* @__PURE__ */ jsx6(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs6(DialogContent, { className: "max-w-md", children: [
|
|
616
|
+
/* @__PURE__ */ jsxs6(DialogHeader, { children: [
|
|
617
|
+
/* @__PURE__ */ jsx6(DialogTitle, { children: "Add Payment Method" }),
|
|
618
|
+
/* @__PURE__ */ jsx6(DialogDescription, { children: "Add a new payment method to your account. Your card information is securely processed by Stripe." })
|
|
619
|
+
] }),
|
|
620
|
+
loading && /* @__PURE__ */ jsx6("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", children: "Loading payment form..." }) }),
|
|
621
|
+
!loading && setupIntent && /* @__PURE__ */ jsxs6("form", { onSubmit: handleSubmit, className: "flex flex-col gap-y-4", children: [
|
|
622
|
+
/* @__PURE__ */ jsx6("div", { className: "rounded-md border border-gray-300 p-3", children: /* @__PURE__ */ jsx6(
|
|
623
|
+
CardElement,
|
|
624
|
+
{
|
|
625
|
+
options: {
|
|
626
|
+
style: {
|
|
627
|
+
base: {
|
|
628
|
+
fontSize: "16px",
|
|
629
|
+
color: "#424770",
|
|
630
|
+
"::placeholder": {
|
|
631
|
+
color: "#aab7c4"
|
|
632
|
+
}
|
|
633
|
+
},
|
|
634
|
+
invalid: {
|
|
635
|
+
color: "#9e2146"
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
) }),
|
|
641
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-x-2", children: [
|
|
642
|
+
/* @__PURE__ */ jsx6(
|
|
643
|
+
Checkbox,
|
|
644
|
+
{
|
|
645
|
+
id: "setAsDefault",
|
|
646
|
+
checked: setAsDefault,
|
|
647
|
+
onCheckedChange: (checked) => setSetAsDefault(!!checked)
|
|
648
|
+
}
|
|
649
|
+
),
|
|
650
|
+
/* @__PURE__ */ jsx6(Label, { htmlFor: "setAsDefault", className: "text-sm font-normal", children: "Set as default payment method" })
|
|
651
|
+
] }),
|
|
652
|
+
error && /* @__PURE__ */ jsx6(Alert, { variant: "destructive", className: "bg-red-50 border-red-200", children: /* @__PURE__ */ jsx6(AlertDescription, { children: error }) }),
|
|
653
|
+
/* @__PURE__ */ jsxs6("div", { className: "flex justify-end gap-x-2", children: [
|
|
654
|
+
/* @__PURE__ */ jsx6(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isSubmitting, children: "Cancel" }),
|
|
655
|
+
/* @__PURE__ */ jsx6(Button, { type: "submit", disabled: !stripe || isSubmitting, children: isSubmitting ? "Processing..." : "Add Card" })
|
|
656
|
+
] })
|
|
657
|
+
] }),
|
|
658
|
+
!loading && !setupIntent && error && /* @__PURE__ */ jsx6(Alert, { variant: "destructive", className: "bg-red-50 border-red-200", children: /* @__PURE__ */ jsx6(AlertDescription, { children: error }) })
|
|
659
|
+
] }) });
|
|
660
|
+
}
|
|
661
|
+
__name(PaymentMethodEditor, "PaymentMethodEditor");
|
|
662
|
+
|
|
663
|
+
// src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx
|
|
664
|
+
import { MoreVertical } from "lucide-react";
|
|
665
|
+
import { useEffect as useEffect2, useState as useState3 } from "react";
|
|
666
|
+
import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
667
|
+
var brandIcons = {
|
|
668
|
+
visa: "\u{1F4B3}",
|
|
669
|
+
mastercard: "\u{1F4B3}",
|
|
670
|
+
amex: "\u{1F4B3}",
|
|
671
|
+
discover: "\u{1F4B3}"
|
|
672
|
+
};
|
|
673
|
+
function PaymentMethodCard({ paymentMethod, onUpdate }) {
|
|
674
|
+
const [loading, setLoading] = useState3(false);
|
|
675
|
+
const [customer, setCustomer] = useState3(null);
|
|
676
|
+
const [showRemoveDialog, setShowRemoveDialog] = useState3(false);
|
|
677
|
+
useEffect2(() => {
|
|
678
|
+
const loadCustomer = /* @__PURE__ */ __name(async () => {
|
|
679
|
+
try {
|
|
680
|
+
const fetchedCustomer = await StripeCustomerService.getCustomer();
|
|
681
|
+
setCustomer(fetchedCustomer);
|
|
682
|
+
} catch (error) {
|
|
683
|
+
console.error("[PaymentMethodCard] Failed to load customer:", error);
|
|
684
|
+
}
|
|
685
|
+
}, "loadCustomer");
|
|
686
|
+
loadCustomer();
|
|
687
|
+
}, []);
|
|
688
|
+
const isDefault = customer?.defaultPaymentMethodId === paymentMethod.id;
|
|
689
|
+
const brand = paymentMethod.card?.brand || "card";
|
|
690
|
+
const last4 = paymentMethod.card?.last4 || "****";
|
|
691
|
+
const expMonth = paymentMethod.card?.expMonth || 0;
|
|
692
|
+
const expYear = paymentMethod.card?.expYear || 0;
|
|
693
|
+
const brandIcon = brandIcons[brand.toLowerCase()] || "\u{1F4B3}";
|
|
694
|
+
const handleSetDefault = /* @__PURE__ */ __name(async () => {
|
|
695
|
+
setLoading(true);
|
|
696
|
+
try {
|
|
697
|
+
await StripeCustomerService.setDefaultPaymentMethod({ paymentMethodId: paymentMethod.id });
|
|
698
|
+
onUpdate();
|
|
699
|
+
} catch (error) {
|
|
700
|
+
console.error("[PaymentMethodCard] Failed to set as default:", error);
|
|
701
|
+
} finally {
|
|
702
|
+
setLoading(false);
|
|
703
|
+
}
|
|
704
|
+
}, "handleSetDefault");
|
|
705
|
+
const handleRemove = /* @__PURE__ */ __name(async () => {
|
|
706
|
+
setLoading(true);
|
|
707
|
+
try {
|
|
708
|
+
await StripeCustomerService.removePaymentMethod({ paymentMethodId: paymentMethod.id });
|
|
709
|
+
setShowRemoveDialog(false);
|
|
710
|
+
onUpdate();
|
|
711
|
+
} catch (error) {
|
|
712
|
+
console.error("[PaymentMethodCard] Failed to remove:", error);
|
|
713
|
+
setLoading(false);
|
|
714
|
+
}
|
|
715
|
+
}, "handleRemove");
|
|
716
|
+
return /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
717
|
+
/* @__PURE__ */ jsxs7(Card, { className: "relative", children: [
|
|
718
|
+
isDefault && /* @__PURE__ */ jsx7(Badge, { className: "absolute right-2 top-2 bg-green-100 text-green-800 hover:bg-green-100", children: "Default" }),
|
|
719
|
+
/* @__PURE__ */ jsxs7(CardHeader, { className: "flex flex-row items-center justify-between pb-2", children: [
|
|
720
|
+
/* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-x-2", children: [
|
|
721
|
+
/* @__PURE__ */ jsx7("span", { className: "text-2xl", children: brandIcon }),
|
|
722
|
+
/* @__PURE__ */ jsx7("span", { className: "text-sm font-medium capitalize", children: brand })
|
|
723
|
+
] }),
|
|
724
|
+
/* @__PURE__ */ jsxs7(DropdownMenu, { children: [
|
|
725
|
+
/* @__PURE__ */ jsx7(DropdownMenuTrigger, { children: /* @__PURE__ */ jsx7(Button, { render: /* @__PURE__ */ jsx7("div", {}), nativeButton: false, variant: "ghost", size: "sm", disabled: loading, className: "h-8 w-8 p-0", children: /* @__PURE__ */ jsx7(MoreVertical, { className: "h-4 w-4" }) }) }),
|
|
726
|
+
/* @__PURE__ */ jsxs7(DropdownMenuContent, { align: "end", children: [
|
|
727
|
+
!isDefault && /* @__PURE__ */ jsx7(DropdownMenuItem, { onClick: handleSetDefault, disabled: loading, children: "Set as Default" }),
|
|
728
|
+
/* @__PURE__ */ jsx7(DropdownMenuItem, { onClick: () => setShowRemoveDialog(true), disabled: loading, className: "text-red-600", children: "Remove" })
|
|
729
|
+
] })
|
|
730
|
+
] })
|
|
731
|
+
] }),
|
|
732
|
+
/* @__PURE__ */ jsx7(CardContent, { children: /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-y-1", children: [
|
|
733
|
+
/* @__PURE__ */ jsxs7("p", { className: "text-lg font-semibold", children: [
|
|
734
|
+
"\u2022\u2022\u2022\u2022 ",
|
|
735
|
+
last4
|
|
736
|
+
] }),
|
|
737
|
+
/* @__PURE__ */ jsxs7("p", { className: "text-sm text-muted-foreground", children: [
|
|
738
|
+
"Expires ",
|
|
739
|
+
String(expMonth).padStart(2, "0"),
|
|
740
|
+
"/",
|
|
741
|
+
expYear
|
|
742
|
+
] })
|
|
743
|
+
] }) })
|
|
744
|
+
] }),
|
|
745
|
+
/* @__PURE__ */ jsx7(AlertDialog, { open: showRemoveDialog, onOpenChange: setShowRemoveDialog, children: /* @__PURE__ */ jsxs7(AlertDialogContent, { children: [
|
|
746
|
+
/* @__PURE__ */ jsxs7(AlertDialogHeader, { children: [
|
|
747
|
+
/* @__PURE__ */ jsx7(AlertDialogTitle, { children: "Remove Payment Method" }),
|
|
748
|
+
/* @__PURE__ */ jsxs7(AlertDialogDescription, { children: [
|
|
749
|
+
"Are you sure you want to remove this payment method? This action cannot be undone.",
|
|
750
|
+
isDefault && " This is your default payment method."
|
|
751
|
+
] })
|
|
752
|
+
] }),
|
|
753
|
+
/* @__PURE__ */ jsxs7(AlertDialogFooter, { children: [
|
|
754
|
+
/* @__PURE__ */ jsx7(AlertDialogCancel, { disabled: loading, children: "Cancel" }),
|
|
755
|
+
/* @__PURE__ */ jsx7(AlertDialogAction, { onClick: handleRemove, disabled: loading, className: "bg-red-600 hover:bg-red-700", children: loading ? "Removing..." : "Remove" })
|
|
756
|
+
] })
|
|
757
|
+
] }) })
|
|
758
|
+
] });
|
|
759
|
+
}
|
|
760
|
+
__name(PaymentMethodCard, "PaymentMethodCard");
|
|
761
|
+
|
|
762
|
+
// src/features/billing/stripe-customer/components/lists/PaymentMethodsList.tsx
|
|
763
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
764
|
+
function PaymentMethodsList({ paymentMethods, onUpdate }) {
|
|
765
|
+
return /* @__PURE__ */ jsx8("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3", children: paymentMethods.map((paymentMethod) => /* @__PURE__ */ jsx8(PaymentMethodCard, { paymentMethod, onUpdate }, paymentMethod.id)) });
|
|
766
|
+
}
|
|
767
|
+
__name(PaymentMethodsList, "PaymentMethodsList");
|
|
768
|
+
|
|
769
|
+
// src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx
|
|
770
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
771
|
+
function PaymentMethodsContainer() {
|
|
772
|
+
const [paymentMethods, setPaymentMethods] = useState4([]);
|
|
773
|
+
const [loading, setLoading] = useState4(true);
|
|
774
|
+
const [showAddPaymentMethod, setShowAddPaymentMethod] = useState4(false);
|
|
775
|
+
const loadPaymentMethods = /* @__PURE__ */ __name(async () => {
|
|
776
|
+
setLoading(true);
|
|
777
|
+
try {
|
|
778
|
+
const fetchedPaymentMethods = await StripeCustomerService.listPaymentMethods();
|
|
779
|
+
setPaymentMethods(fetchedPaymentMethods);
|
|
780
|
+
} catch (error) {
|
|
781
|
+
console.error("[PaymentMethodsContainer] Failed to load payment methods:", error);
|
|
782
|
+
} finally {
|
|
783
|
+
setLoading(false);
|
|
784
|
+
}
|
|
785
|
+
}, "loadPaymentMethods");
|
|
786
|
+
useEffect3(() => {
|
|
787
|
+
loadPaymentMethods();
|
|
788
|
+
}, []);
|
|
789
|
+
if (loading) {
|
|
790
|
+
return /* @__PURE__ */ jsx9("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx9("p", { className: "text-muted-foreground", children: "Loading payment methods..." }) });
|
|
791
|
+
}
|
|
792
|
+
return /* @__PURE__ */ jsxs8("div", { className: "flex w-full flex-col gap-y-6", children: [
|
|
793
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
|
|
794
|
+
/* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-x-3", children: [
|
|
795
|
+
/* @__PURE__ */ jsx9(CreditCard2, { className: "h-8 w-8" }),
|
|
796
|
+
/* @__PURE__ */ jsx9("h1", { className: "text-3xl font-bold", children: "Payment Methods" })
|
|
797
|
+
] }),
|
|
798
|
+
/* @__PURE__ */ jsx9(Button, { onClick: () => setShowAddPaymentMethod(true), children: "Add Payment Method" })
|
|
799
|
+
] }),
|
|
800
|
+
paymentMethods.length === 0 && /* @__PURE__ */ jsxs8("div", { className: "flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed border-gray-300 bg-muted/50 p-12", children: [
|
|
801
|
+
/* @__PURE__ */ jsx9(CreditCard2, { className: "h-16 w-16 text-muted-foreground" }),
|
|
802
|
+
/* @__PURE__ */ jsxs8("div", { className: "text-center", children: [
|
|
803
|
+
/* @__PURE__ */ jsx9("h3", { className: "mb-2 text-xl font-semibold", children: "No payment methods" }),
|
|
804
|
+
/* @__PURE__ */ jsx9("p", { className: "mb-4 text-muted-foreground", children: "Add a payment method to enable subscriptions and secure checkout." }),
|
|
805
|
+
/* @__PURE__ */ jsx9(Button, { onClick: () => setShowAddPaymentMethod(true), children: "Add Your First Card" })
|
|
806
|
+
] })
|
|
807
|
+
] }),
|
|
808
|
+
paymentMethods.length > 0 && /* @__PURE__ */ jsx9(PaymentMethodsList, { paymentMethods, onUpdate: loadPaymentMethods }),
|
|
809
|
+
showAddPaymentMethod && /* @__PURE__ */ jsx9(
|
|
810
|
+
PaymentMethodEditor,
|
|
811
|
+
{
|
|
812
|
+
open: showAddPaymentMethod,
|
|
813
|
+
onOpenChange: setShowAddPaymentMethod,
|
|
814
|
+
onSuccess: loadPaymentMethods
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] });
|
|
818
|
+
}
|
|
819
|
+
__name(PaymentMethodsContainer, "PaymentMethodsContainer");
|
|
820
|
+
|
|
821
|
+
// src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx
|
|
822
|
+
import { useEffect as useEffect4, useState as useState6 } from "react";
|
|
823
|
+
|
|
824
|
+
// src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx
|
|
825
|
+
import { useState as useState5 } from "react";
|
|
826
|
+
|
|
827
|
+
// src/features/billing/components/utils/currency.ts
|
|
828
|
+
function formatInterval(price) {
|
|
829
|
+
if (price.priceType === "one_time" || !price.recurring) {
|
|
830
|
+
return "one-time";
|
|
831
|
+
}
|
|
832
|
+
const { interval, intervalCount } = price.recurring;
|
|
833
|
+
if (intervalCount === 1) {
|
|
834
|
+
return `/${interval}`;
|
|
835
|
+
}
|
|
836
|
+
const pluralInterval = interval === "day" ? "days" : interval === "week" ? "weeks" : interval === "month" ? "months" : "years";
|
|
837
|
+
return `/${intervalCount} ${pluralInterval}`;
|
|
838
|
+
}
|
|
839
|
+
__name(formatInterval, "formatInterval");
|
|
840
|
+
function formatCurrency(amount, currency) {
|
|
841
|
+
if (amount === void 0) return "$0.00";
|
|
842
|
+
const dollars = amount / 100;
|
|
843
|
+
try {
|
|
844
|
+
return new Intl.NumberFormat("en-US", {
|
|
845
|
+
style: "currency",
|
|
846
|
+
currency: currency.toUpperCase(),
|
|
847
|
+
minimumFractionDigits: 2,
|
|
848
|
+
maximumFractionDigits: 2
|
|
849
|
+
}).format(dollars);
|
|
850
|
+
} catch (error) {
|
|
851
|
+
console.error("Error formatting currency:", error);
|
|
852
|
+
return `$${dollars.toFixed(2)}`;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
__name(formatCurrency, "formatCurrency");
|
|
856
|
+
|
|
857
|
+
// src/features/billing/components/utils/date.ts
|
|
858
|
+
function formatDate3(date) {
|
|
859
|
+
if (!date) return "N/A";
|
|
860
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
861
|
+
try {
|
|
862
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
863
|
+
month: "short",
|
|
864
|
+
day: "numeric",
|
|
865
|
+
year: "numeric"
|
|
866
|
+
}).format(dateObj);
|
|
867
|
+
} catch (error) {
|
|
868
|
+
console.error("Error formatting date:", error);
|
|
869
|
+
return "Invalid Date";
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
__name(formatDate3, "formatDate");
|
|
873
|
+
|
|
874
|
+
// src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx
|
|
875
|
+
import { Download, ExternalLink as ExternalLink2, RefreshCw } from "lucide-react";
|
|
876
|
+
|
|
877
|
+
// src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx
|
|
878
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
879
|
+
var statusConfig = {
|
|
880
|
+
["draft" /* DRAFT */]: {
|
|
881
|
+
label: "Draft",
|
|
882
|
+
color: "bg-gray-100 text-gray-800"
|
|
883
|
+
},
|
|
884
|
+
["open" /* OPEN */]: {
|
|
885
|
+
label: "Open",
|
|
886
|
+
color: "bg-blue-100 text-blue-800"
|
|
887
|
+
},
|
|
888
|
+
["paid" /* PAID */]: {
|
|
889
|
+
label: "Paid",
|
|
890
|
+
color: "bg-green-100 text-green-800"
|
|
891
|
+
},
|
|
892
|
+
["void" /* VOID */]: {
|
|
893
|
+
label: "Void",
|
|
894
|
+
color: "bg-gray-100 text-gray-800"
|
|
895
|
+
},
|
|
896
|
+
["uncollectible" /* UNCOLLECTIBLE */]: {
|
|
897
|
+
label: "Uncollectible",
|
|
898
|
+
color: "bg-red-100 text-red-800"
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
function InvoiceStatusBadge({ status }) {
|
|
902
|
+
const config = statusConfig[status] || statusConfig["draft" /* DRAFT */];
|
|
903
|
+
return /* @__PURE__ */ jsx10("span", { className: `${config.color} text-xs px-2 py-1 rounded-full font-medium`, children: config.label });
|
|
904
|
+
}
|
|
905
|
+
__name(InvoiceStatusBadge, "InvoiceStatusBadge");
|
|
906
|
+
|
|
907
|
+
// src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx
|
|
908
|
+
import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
909
|
+
function InvoiceDetails({ invoice, open, onOpenChange, onInvoiceChange }) {
|
|
910
|
+
const handleDownloadPDF = /* @__PURE__ */ __name(() => {
|
|
911
|
+
if (invoice.stripePdfUrl) {
|
|
912
|
+
window.open(invoice.stripePdfUrl, "_blank");
|
|
913
|
+
}
|
|
914
|
+
}, "handleDownloadPDF");
|
|
915
|
+
const handleRetryPayment = /* @__PURE__ */ __name(async () => {
|
|
916
|
+
}, "handleRetryPayment");
|
|
917
|
+
const handleViewInStripe = /* @__PURE__ */ __name(() => {
|
|
918
|
+
if (invoice.stripeHostedInvoiceUrl) {
|
|
919
|
+
window.open(invoice.stripeHostedInvoiceUrl, "_blank");
|
|
920
|
+
}
|
|
921
|
+
}, "handleViewInStripe");
|
|
922
|
+
const getInvoiceNumber = /* @__PURE__ */ __name(() => {
|
|
923
|
+
if (invoice.stripeInvoiceNumber) {
|
|
924
|
+
return invoice.stripeInvoiceNumber;
|
|
925
|
+
}
|
|
926
|
+
return invoice.stripeInvoiceId.slice(-8);
|
|
927
|
+
}, "getInvoiceNumber");
|
|
928
|
+
const productName = invoice.subscription?.price?.product?.name || "Subscription";
|
|
929
|
+
return /* @__PURE__ */ jsx11(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs9(DialogContent, { className: "max-w-2xl", children: [
|
|
930
|
+
/* @__PURE__ */ jsxs9(DialogHeader, { children: [
|
|
931
|
+
/* @__PURE__ */ jsxs9(DialogTitle, { children: [
|
|
932
|
+
"Invoice ",
|
|
933
|
+
getInvoiceNumber()
|
|
934
|
+
] }),
|
|
935
|
+
/* @__PURE__ */ jsx11(DialogDescription, { children: formatDate3(invoice.periodStart) })
|
|
936
|
+
] }),
|
|
937
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-6", children: [
|
|
938
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex items-center gap-x-3", children: [
|
|
939
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Status:" }),
|
|
940
|
+
/* @__PURE__ */ jsx11(InvoiceStatusBadge, { status: invoice.status })
|
|
941
|
+
] }),
|
|
942
|
+
/* @__PURE__ */ jsxs9("div", { className: "grid grid-cols-2 gap-4", children: [
|
|
943
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
944
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Billing Period:" }),
|
|
945
|
+
/* @__PURE__ */ jsxs9("p", { className: "font-medium", children: [
|
|
946
|
+
formatDate3(invoice.periodStart),
|
|
947
|
+
" - ",
|
|
948
|
+
formatDate3(invoice.periodEnd)
|
|
949
|
+
] })
|
|
950
|
+
] }),
|
|
951
|
+
invoice.dueDate && /* @__PURE__ */ jsxs9("div", { children: [
|
|
952
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Due Date:" }),
|
|
953
|
+
/* @__PURE__ */ jsx11("p", { className: "font-medium", children: formatDate3(invoice.dueDate) })
|
|
954
|
+
] }),
|
|
955
|
+
invoice.paidAt && /* @__PURE__ */ jsxs9("div", { children: [
|
|
956
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Paid Date:" }),
|
|
957
|
+
/* @__PURE__ */ jsx11("p", { className: "font-medium", children: formatDate3(invoice.paidAt) })
|
|
958
|
+
] }),
|
|
959
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
960
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Attempt Count:" }),
|
|
961
|
+
/* @__PURE__ */ jsx11("p", { className: "font-medium", children: invoice.attemptCount })
|
|
962
|
+
] })
|
|
963
|
+
] }),
|
|
964
|
+
/* @__PURE__ */ jsxs9("div", { children: [
|
|
965
|
+
/* @__PURE__ */ jsx11("h4", { className: "text-sm font-medium text-muted-foreground mb-2", children: "Line Items" }),
|
|
966
|
+
/* @__PURE__ */ jsx11("div", { className: "border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs9("table", { className: "w-full", children: [
|
|
967
|
+
/* @__PURE__ */ jsx11("thead", { className: "bg-muted", children: /* @__PURE__ */ jsxs9("tr", { children: [
|
|
968
|
+
/* @__PURE__ */ jsx11("th", { className: "text-left p-3 text-sm font-medium", children: "Description" }),
|
|
969
|
+
/* @__PURE__ */ jsx11("th", { className: "text-right p-3 text-sm font-medium", children: "Amount" })
|
|
970
|
+
] }) }),
|
|
971
|
+
/* @__PURE__ */ jsx11("tbody", { children: /* @__PURE__ */ jsxs9("tr", { className: "border-t", children: [
|
|
972
|
+
/* @__PURE__ */ jsx11("td", { className: "p-3", children: productName }),
|
|
973
|
+
/* @__PURE__ */ jsx11("td", { className: "p-3 text-right", children: formatCurrency(invoice.subtotal, invoice.currency) })
|
|
974
|
+
] }) })
|
|
975
|
+
] }) })
|
|
976
|
+
] }),
|
|
977
|
+
/* @__PURE__ */ jsxs9("div", { className: "space-y-2 border-t pt-4", children: [
|
|
978
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex justify-between", children: [
|
|
979
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Subtotal:" }),
|
|
980
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium", children: formatCurrency(invoice.subtotal, invoice.currency) })
|
|
981
|
+
] }),
|
|
982
|
+
invoice.tax !== void 0 && invoice.tax > 0 && /* @__PURE__ */ jsxs9("div", { className: "flex justify-between", children: [
|
|
983
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Tax:" }),
|
|
984
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium", children: formatCurrency(invoice.tax, invoice.currency) })
|
|
985
|
+
] }),
|
|
986
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex justify-between text-lg font-semibold border-t pt-2", children: [
|
|
987
|
+
/* @__PURE__ */ jsx11("span", { children: "Total:" }),
|
|
988
|
+
/* @__PURE__ */ jsx11("span", { children: formatCurrency(invoice.total, invoice.currency) })
|
|
989
|
+
] }),
|
|
990
|
+
invoice.amountPaid > 0 && /* @__PURE__ */ jsxs9("div", { className: "flex justify-between", children: [
|
|
991
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Amount Paid:" }),
|
|
992
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium text-green-600", children: formatCurrency(invoice.amountPaid, invoice.currency) })
|
|
993
|
+
] }),
|
|
994
|
+
invoice.amountRemaining > 0 && /* @__PURE__ */ jsxs9("div", { className: "flex justify-between", children: [
|
|
995
|
+
/* @__PURE__ */ jsx11("span", { className: "text-sm font-medium text-muted-foreground", children: "Amount Due:" }),
|
|
996
|
+
/* @__PURE__ */ jsx11("span", { className: "font-medium text-red-600", children: formatCurrency(invoice.amountRemaining, invoice.currency) })
|
|
997
|
+
] })
|
|
998
|
+
] }),
|
|
999
|
+
/* @__PURE__ */ jsxs9("div", { className: "flex flex-wrap gap-2 pt-4 border-t", children: [
|
|
1000
|
+
invoice.stripePdfUrl && /* @__PURE__ */ jsxs9(Button, { variant: "outline", onClick: handleDownloadPDF, children: [
|
|
1001
|
+
/* @__PURE__ */ jsx11(Download, { className: "mr-2 h-4 w-4" }),
|
|
1002
|
+
"Download PDF"
|
|
1003
|
+
] }),
|
|
1004
|
+
invoice.status === "open" /* OPEN */ && invoice.attempted && /* @__PURE__ */ jsxs9(Button, { variant: "default", onClick: handleRetryPayment, children: [
|
|
1005
|
+
/* @__PURE__ */ jsx11(RefreshCw, { className: "mr-2 h-4 w-4" }),
|
|
1006
|
+
"Retry Payment"
|
|
1007
|
+
] }),
|
|
1008
|
+
invoice.stripeHostedInvoiceUrl && /* @__PURE__ */ jsxs9(Button, { variant: "outline", onClick: handleViewInStripe, children: [
|
|
1009
|
+
/* @__PURE__ */ jsx11(ExternalLink2, { className: "mr-2 h-4 w-4" }),
|
|
1010
|
+
"View in Stripe"
|
|
1011
|
+
] })
|
|
1012
|
+
] })
|
|
1013
|
+
] })
|
|
1014
|
+
] }) });
|
|
1015
|
+
}
|
|
1016
|
+
__name(InvoiceDetails, "InvoiceDetails");
|
|
1017
|
+
|
|
1018
|
+
// src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx
|
|
1019
|
+
import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1020
|
+
function InvoicesList({ invoices, onInvoicesChange }) {
|
|
1021
|
+
const [selectedInvoice, setSelectedInvoice] = useState5(null);
|
|
1022
|
+
const handleRowClick = /* @__PURE__ */ __name((invoice) => {
|
|
1023
|
+
setSelectedInvoice(invoice);
|
|
1024
|
+
}, "handleRowClick");
|
|
1025
|
+
const getInvoiceNumber = /* @__PURE__ */ __name((invoice) => {
|
|
1026
|
+
if (invoice.stripeInvoiceNumber) {
|
|
1027
|
+
return invoice.stripeInvoiceNumber;
|
|
1028
|
+
}
|
|
1029
|
+
return invoice.stripeInvoiceId.slice(-8);
|
|
1030
|
+
}, "getInvoiceNumber");
|
|
1031
|
+
return /* @__PURE__ */ jsxs10(Fragment2, { children: [
|
|
1032
|
+
/* @__PURE__ */ jsx12("div", { className: "border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs10(Table, { children: [
|
|
1033
|
+
/* @__PURE__ */ jsx12(TableHeader, { className: "bg-muted", children: /* @__PURE__ */ jsxs10(TableRow, { children: [
|
|
1034
|
+
/* @__PURE__ */ jsx12(TableHead, { children: "Invoice #" }),
|
|
1035
|
+
/* @__PURE__ */ jsx12(TableHead, { children: "Date" }),
|
|
1036
|
+
/* @__PURE__ */ jsx12(TableHead, { children: "Status" }),
|
|
1037
|
+
/* @__PURE__ */ jsx12(TableHead, { className: "text-right", children: "Amount" }),
|
|
1038
|
+
/* @__PURE__ */ jsx12(TableHead, { children: "Period" })
|
|
1039
|
+
] }) }),
|
|
1040
|
+
/* @__PURE__ */ jsx12(TableBody, { children: invoices.map((invoice) => {
|
|
1041
|
+
const invoiceNumber = getInvoiceNumber(invoice);
|
|
1042
|
+
const date = formatDate3(invoice.periodStart);
|
|
1043
|
+
const amount = formatCurrency(invoice.total, invoice.currency);
|
|
1044
|
+
const period = `${formatDate3(invoice.periodStart)} - ${formatDate3(invoice.periodEnd)}`;
|
|
1045
|
+
return /* @__PURE__ */ jsxs10(
|
|
1046
|
+
TableRow,
|
|
1047
|
+
{
|
|
1048
|
+
onClick: () => handleRowClick(invoice),
|
|
1049
|
+
className: "cursor-pointer hover:bg-muted/50",
|
|
1050
|
+
children: [
|
|
1051
|
+
/* @__PURE__ */ jsx12(TableCell, { className: "font-medium", children: invoiceNumber }),
|
|
1052
|
+
/* @__PURE__ */ jsx12(TableCell, { className: "text-muted-foreground text-sm", children: date }),
|
|
1053
|
+
/* @__PURE__ */ jsx12(TableCell, { children: /* @__PURE__ */ jsx12(InvoiceStatusBadge, { status: invoice.status }) }),
|
|
1054
|
+
/* @__PURE__ */ jsx12(TableCell, { className: "text-right font-medium", children: amount }),
|
|
1055
|
+
/* @__PURE__ */ jsx12(TableCell, { className: "text-muted-foreground text-sm", children: period })
|
|
1056
|
+
]
|
|
1057
|
+
},
|
|
1058
|
+
invoice.id
|
|
1059
|
+
);
|
|
1060
|
+
}) })
|
|
1061
|
+
] }) }),
|
|
1062
|
+
selectedInvoice && /* @__PURE__ */ jsx12(
|
|
1063
|
+
InvoiceDetails,
|
|
1064
|
+
{
|
|
1065
|
+
invoice: selectedInvoice,
|
|
1066
|
+
open: !!selectedInvoice,
|
|
1067
|
+
onOpenChange: (open) => !open && setSelectedInvoice(null),
|
|
1068
|
+
onInvoiceChange: () => {
|
|
1069
|
+
onInvoicesChange();
|
|
1070
|
+
setSelectedInvoice(null);
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
)
|
|
1074
|
+
] });
|
|
1075
|
+
}
|
|
1076
|
+
__name(InvoicesList, "InvoicesList");
|
|
1077
|
+
|
|
1078
|
+
// src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx
|
|
1079
|
+
import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
1080
|
+
function InvoicesContainer() {
|
|
1081
|
+
const [invoices, setInvoices] = useState6([]);
|
|
1082
|
+
const [loading, setLoading] = useState6(true);
|
|
1083
|
+
const [statusFilter, setStatusFilter] = useState6("all");
|
|
1084
|
+
const loadInvoices = /* @__PURE__ */ __name(async () => {
|
|
1085
|
+
setLoading(true);
|
|
1086
|
+
try {
|
|
1087
|
+
const params = statusFilter !== "all" ? { status: statusFilter } : void 0;
|
|
1088
|
+
const data = await StripeInvoiceService.listInvoices(params);
|
|
1089
|
+
setInvoices(data);
|
|
1090
|
+
} catch (error) {
|
|
1091
|
+
console.error("[InvoicesContainer] Failed to load invoices:", error);
|
|
1092
|
+
setInvoices([]);
|
|
1093
|
+
} finally {
|
|
1094
|
+
setLoading(false);
|
|
1095
|
+
}
|
|
1096
|
+
}, "loadInvoices");
|
|
1097
|
+
useEffect4(() => {
|
|
1098
|
+
loadInvoices();
|
|
1099
|
+
}, [statusFilter]);
|
|
1100
|
+
const handleFilterChange = /* @__PURE__ */ __name((value) => {
|
|
1101
|
+
setStatusFilter(value);
|
|
1102
|
+
}, "handleFilterChange");
|
|
1103
|
+
return /* @__PURE__ */ jsxs11("div", { className: "space-y-4", children: [
|
|
1104
|
+
/* @__PURE__ */ jsx13(Tabs, { value: statusFilter, onValueChange: handleFilterChange, children: /* @__PURE__ */ jsxs11(TabsList, { children: [
|
|
1105
|
+
/* @__PURE__ */ jsx13(TabsTrigger, { value: "all", children: "All" }),
|
|
1106
|
+
/* @__PURE__ */ jsx13(TabsTrigger, { value: "paid" /* PAID */, children: "Paid" }),
|
|
1107
|
+
/* @__PURE__ */ jsx13(TabsTrigger, { value: "open" /* OPEN */, children: "Open" }),
|
|
1108
|
+
/* @__PURE__ */ jsx13(TabsTrigger, { value: "void" /* VOID */, children: "Void" }),
|
|
1109
|
+
/* @__PURE__ */ jsx13(TabsTrigger, { value: "uncollectible" /* UNCOLLECTIBLE */, children: "Uncollectible" })
|
|
1110
|
+
] }) }),
|
|
1111
|
+
loading && /* @__PURE__ */ jsx13("div", { className: "text-center py-8 text-muted-foreground", children: "Loading invoices..." }),
|
|
1112
|
+
!loading && invoices.length === 0 && /* @__PURE__ */ jsxs11("div", { className: "border border-dashed border-gray-300 rounded-lg p-8 text-center", children: [
|
|
1113
|
+
/* @__PURE__ */ jsx13("p", { className: "text-lg font-medium text-muted-foreground mb-2", children: "No invoices yet" }),
|
|
1114
|
+
/* @__PURE__ */ jsx13("p", { className: "text-sm text-muted-foreground", children: "Invoices will appear here after your first billing cycle" })
|
|
1115
|
+
] }),
|
|
1116
|
+
!loading && invoices.length > 0 && /* @__PURE__ */ jsx13(InvoicesList, { invoices, onInvoicesChange: loadInvoices })
|
|
1117
|
+
] });
|
|
1118
|
+
}
|
|
1119
|
+
__name(InvoicesContainer, "InvoicesContainer");
|
|
1120
|
+
|
|
1121
|
+
// src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx
|
|
1122
|
+
import { CheckCircle as CheckCircle2, CreditCard as CreditCard3, Loader2 as Loader22 } from "lucide-react";
|
|
1123
|
+
import { useEffect as useEffect6, useState as useState12 } from "react";
|
|
1124
|
+
import { v4 as v42 } from "uuid";
|
|
1125
|
+
|
|
1126
|
+
// src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts
|
|
1127
|
+
import { useStripe as useStripe2 } from "@stripe/react-stripe-js";
|
|
1128
|
+
import { useCallback, useState as useState7 } from "react";
|
|
1129
|
+
function useConfirmSubscriptionPayment() {
|
|
1130
|
+
const stripe = useStripe2();
|
|
1131
|
+
const [isConfirming, setIsConfirming] = useState7(false);
|
|
1132
|
+
const confirmPayment = useCallback(
|
|
1133
|
+
async (clientSecret) => {
|
|
1134
|
+
if (!stripe) {
|
|
1135
|
+
console.error("[useConfirmSubscriptionPayment] Stripe not initialized");
|
|
1136
|
+
return {
|
|
1137
|
+
success: false,
|
|
1138
|
+
error: "Payment system not initialized. Please refresh the page and try again."
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
if (!clientSecret) {
|
|
1142
|
+
console.error("[useConfirmSubscriptionPayment] No client secret provided");
|
|
1143
|
+
return {
|
|
1144
|
+
success: false,
|
|
1145
|
+
error: "Payment confirmation failed. Missing payment details."
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
setIsConfirming(true);
|
|
1149
|
+
try {
|
|
1150
|
+
const { error: stripeError, paymentIntent } = await stripe.confirmCardPayment(clientSecret);
|
|
1151
|
+
if (stripeError) {
|
|
1152
|
+
console.error("[useConfirmSubscriptionPayment] Stripe error:", stripeError);
|
|
1153
|
+
return {
|
|
1154
|
+
success: false,
|
|
1155
|
+
error: stripeError.message || "Payment confirmation failed. Please try again."
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
if (paymentIntent?.status === "succeeded") {
|
|
1159
|
+
return { success: true };
|
|
1160
|
+
}
|
|
1161
|
+
if (paymentIntent?.status === "requires_action") {
|
|
1162
|
+
return {
|
|
1163
|
+
success: false,
|
|
1164
|
+
error: "Additional authentication required. Please complete the verification."
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
return {
|
|
1168
|
+
success: false,
|
|
1169
|
+
error: "Payment could not be completed. Please try again."
|
|
1170
|
+
};
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
console.error("[useConfirmSubscriptionPayment] Unexpected error:", err);
|
|
1173
|
+
return {
|
|
1174
|
+
success: false,
|
|
1175
|
+
error: err.message || "An unexpected error occurred during payment confirmation."
|
|
1176
|
+
};
|
|
1177
|
+
} finally {
|
|
1178
|
+
setIsConfirming(false);
|
|
1179
|
+
}
|
|
1180
|
+
},
|
|
1181
|
+
[stripe]
|
|
1182
|
+
);
|
|
1183
|
+
return {
|
|
1184
|
+
confirmPayment,
|
|
1185
|
+
isConfirming
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
__name(useConfirmSubscriptionPayment, "useConfirmSubscriptionPayment");
|
|
1189
|
+
|
|
1190
|
+
// src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx
|
|
1191
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
1192
|
+
import { useState as useState8 } from "react";
|
|
1193
|
+
import { useForm } from "react-hook-form";
|
|
1194
|
+
import { z } from "zod";
|
|
1195
|
+
import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1196
|
+
var formSchema = z.object({
|
|
1197
|
+
cancelImmediately: z.boolean(),
|
|
1198
|
+
reason: z.string().optional()
|
|
1199
|
+
});
|
|
1200
|
+
function CancelSubscriptionDialog({
|
|
1201
|
+
subscription,
|
|
1202
|
+
open,
|
|
1203
|
+
onOpenChange,
|
|
1204
|
+
onSuccess
|
|
1205
|
+
}) {
|
|
1206
|
+
const [isSubmitting, setIsSubmitting] = useState8(false);
|
|
1207
|
+
const form = useForm({
|
|
1208
|
+
resolver: zodResolver(formSchema),
|
|
1209
|
+
defaultValues: {
|
|
1210
|
+
cancelImmediately: false,
|
|
1211
|
+
reason: ""
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
const cancelImmediately = form.watch("cancelImmediately");
|
|
1215
|
+
const onSubmit = /* @__PURE__ */ __name(async (values) => {
|
|
1216
|
+
setIsSubmitting(true);
|
|
1217
|
+
try {
|
|
1218
|
+
await StripeSubscriptionService.cancelSubscription({
|
|
1219
|
+
id: subscription.id,
|
|
1220
|
+
cancelImmediately: values.cancelImmediately
|
|
1221
|
+
});
|
|
1222
|
+
onSuccess();
|
|
1223
|
+
onOpenChange(false);
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
console.error("[CancelSubscriptionDialog] Failed to cancel subscription:", error);
|
|
1226
|
+
} finally {
|
|
1227
|
+
setIsSubmitting(false);
|
|
1228
|
+
}
|
|
1229
|
+
}, "onSubmit");
|
|
1230
|
+
const periodEndDate = formatDate3(subscription.currentPeriodEnd);
|
|
1231
|
+
return /* @__PURE__ */ jsx14(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs12(DialogContent, { className: "max-w-md", children: [
|
|
1232
|
+
/* @__PURE__ */ jsxs12(DialogHeader, { children: [
|
|
1233
|
+
/* @__PURE__ */ jsx14(DialogTitle, { children: "Cancel Subscription" }),
|
|
1234
|
+
/* @__PURE__ */ jsx14(DialogDescription, { children: "Are you sure you want to cancel this subscription? This action cannot be undone." })
|
|
1235
|
+
] }),
|
|
1236
|
+
/* @__PURE__ */ jsx14(Form, { ...form, children: /* @__PURE__ */ jsxs12("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-col gap-y-4", children: [
|
|
1237
|
+
/* @__PURE__ */ jsx14(FormCheckbox, { form, id: "cancelImmediately", name: "Cancel Immediately" }),
|
|
1238
|
+
cancelImmediately ? /* @__PURE__ */ jsx14("div", { className: "bg-red-50 border border-red-200 rounded-lg p-3 text-sm text-red-800", children: "Your subscription will be canceled immediately and you will lose access right away." }) : /* @__PURE__ */ jsxs12("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-3 text-sm text-blue-800", children: [
|
|
1239
|
+
"Your subscription will remain active until ",
|
|
1240
|
+
periodEndDate,
|
|
1241
|
+
". You can continue using the service until then."
|
|
1242
|
+
] }),
|
|
1243
|
+
/* @__PURE__ */ jsx14(
|
|
1244
|
+
FormTextarea,
|
|
1245
|
+
{
|
|
1246
|
+
form,
|
|
1247
|
+
id: "reason",
|
|
1248
|
+
name: "Reason (Optional)",
|
|
1249
|
+
placeholder: "Let us know why you're canceling...",
|
|
1250
|
+
className: "min-h-24"
|
|
1251
|
+
}
|
|
1252
|
+
),
|
|
1253
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex gap-x-2 justify-end pt-2", children: [
|
|
1254
|
+
/* @__PURE__ */ jsx14(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: isSubmitting, children: "Keep Subscription" }),
|
|
1255
|
+
/* @__PURE__ */ jsx14(Button, { type: "submit", variant: "destructive", disabled: isSubmitting, children: isSubmitting ? "Canceling..." : "Confirm Cancellation" })
|
|
1256
|
+
] })
|
|
1257
|
+
] }) })
|
|
1258
|
+
] }) });
|
|
1259
|
+
}
|
|
1260
|
+
__name(CancelSubscriptionDialog, "CancelSubscriptionDialog");
|
|
1261
|
+
|
|
1262
|
+
// src/features/billing/stripe-subscription/components/forms/SubscriptionEditor.tsx
|
|
1263
|
+
import { CheckCircle, Loader2 } from "lucide-react";
|
|
1264
|
+
import { useEffect as useEffect5, useState as useState9 } from "react";
|
|
1265
|
+
import { v4 } from "uuid";
|
|
1266
|
+
|
|
1267
|
+
// src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx
|
|
1268
|
+
import { Check } from "lucide-react";
|
|
1269
|
+
import { jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1270
|
+
function PricingCard({ price, isCurrentPlan = false, isSelected = false, isDisabled = false, isLoading = false, onSelect }) {
|
|
1271
|
+
const description = price.description || price.nickname || "Standard";
|
|
1272
|
+
const features = price.features || [];
|
|
1273
|
+
const formattedPrice = formatCurrency(price.unitAmount, price.currency);
|
|
1274
|
+
const interval = formatInterval(price);
|
|
1275
|
+
const handleKeyDown = /* @__PURE__ */ __name((e) => {
|
|
1276
|
+
if ((e.key === "Enter" || e.key === " ") && !isDisabled && !isCurrentPlan) {
|
|
1277
|
+
e.preventDefault();
|
|
1278
|
+
onSelect(price);
|
|
1279
|
+
}
|
|
1280
|
+
}, "handleKeyDown");
|
|
1281
|
+
const handleClick = /* @__PURE__ */ __name(() => {
|
|
1282
|
+
if (!isDisabled && !isCurrentPlan && !isLoading) {
|
|
1283
|
+
onSelect(price);
|
|
1284
|
+
}
|
|
1285
|
+
}, "handleClick");
|
|
1286
|
+
return /* @__PURE__ */ jsxs13(
|
|
1287
|
+
Card,
|
|
1288
|
+
{
|
|
1289
|
+
role: "radio",
|
|
1290
|
+
"aria-checked": isSelected,
|
|
1291
|
+
"aria-label": `${description} plan at ${formattedPrice} ${interval}`,
|
|
1292
|
+
tabIndex: isDisabled ? -1 : 0,
|
|
1293
|
+
onKeyDown: handleKeyDown,
|
|
1294
|
+
onClick: handleClick,
|
|
1295
|
+
className: cn(
|
|
1296
|
+
"relative cursor-pointer transition-all duration-200 flex flex-col h-full",
|
|
1297
|
+
"focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
|
1298
|
+
isCurrentPlan && "bg-muted/30",
|
|
1299
|
+
isSelected && !isCurrentPlan && "ring-2 ring-primary",
|
|
1300
|
+
!isDisabled && !isCurrentPlan && "hover:shadow-md hover:border-primary/50",
|
|
1301
|
+
isDisabled && "opacity-50 pointer-events-none",
|
|
1302
|
+
isLoading && "pointer-events-none"
|
|
1303
|
+
),
|
|
1304
|
+
children: [
|
|
1305
|
+
isCurrentPlan && /* @__PURE__ */ jsx15(Badge, { variant: "secondary", className: "absolute top-2 right-2", children: "Current" }),
|
|
1306
|
+
/* @__PURE__ */ jsx15(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsx15("h3", { className: "font-semibold text-lg", children: description }) }),
|
|
1307
|
+
/* @__PURE__ */ jsxs13(CardContent, { className: "pb-4 grow", children: [
|
|
1308
|
+
/* @__PURE__ */ jsxs13("div", { className: "mb-4", children: [
|
|
1309
|
+
/* @__PURE__ */ jsx15("span", { className: "text-3xl font-bold", children: formattedPrice }),
|
|
1310
|
+
/* @__PURE__ */ jsx15("span", { className: "text-muted-foreground ml-1", children: interval })
|
|
1311
|
+
] }),
|
|
1312
|
+
features.length > 0 && /* @__PURE__ */ jsx15("ul", { className: "space-y-2", children: features.map((feature, index) => /* @__PURE__ */ jsxs13("li", { className: "flex items-start gap-2", children: [
|
|
1313
|
+
/* @__PURE__ */ jsx15(Check, { className: "h-4 w-4 text-green-500 mt-0.5 shrink-0" }),
|
|
1314
|
+
/* @__PURE__ */ jsx15("span", { className: "text-sm text-muted-foreground", children: feature })
|
|
1315
|
+
] }, index)) })
|
|
1316
|
+
] }),
|
|
1317
|
+
/* @__PURE__ */ jsx15(CardFooter, { children: /* @__PURE__ */ jsx15(
|
|
1318
|
+
Button,
|
|
1319
|
+
{
|
|
1320
|
+
variant: isCurrentPlan ? "secondary" : isSelected ? "default" : "outline",
|
|
1321
|
+
className: "w-full",
|
|
1322
|
+
disabled: isDisabled || isCurrentPlan || isLoading,
|
|
1323
|
+
children: isLoading ? "Processing..." : isCurrentPlan ? "Current Plan" : isSelected ? "Selected" : "Select Plan"
|
|
1324
|
+
}
|
|
1325
|
+
) })
|
|
1326
|
+
]
|
|
1327
|
+
}
|
|
1328
|
+
);
|
|
1329
|
+
}
|
|
1330
|
+
__name(PricingCard, "PricingCard");
|
|
1331
|
+
|
|
1332
|
+
// src/features/billing/stripe-subscription/components/widgets/PricingCardsGrid.tsx
|
|
1333
|
+
import { jsx as jsx16, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1334
|
+
function PricingCardsGrid({
|
|
1335
|
+
products,
|
|
1336
|
+
pricesByProduct,
|
|
1337
|
+
currentPriceId,
|
|
1338
|
+
selectedPriceId,
|
|
1339
|
+
loadingPriceId,
|
|
1340
|
+
loading = false,
|
|
1341
|
+
onSelectPrice
|
|
1342
|
+
}) {
|
|
1343
|
+
if (loading) {
|
|
1344
|
+
return /* @__PURE__ */ jsx16(PricingCardsGridSkeleton, {});
|
|
1345
|
+
}
|
|
1346
|
+
if (products.length === 0) {
|
|
1347
|
+
return /* @__PURE__ */ jsx16("div", { className: "text-center py-8 text-muted-foreground", children: "No plans available" });
|
|
1348
|
+
}
|
|
1349
|
+
return /* @__PURE__ */ jsx16("div", { className: "space-y-8", role: "radiogroup", "aria-label": "Available pricing plans", children: products.map((product) => {
|
|
1350
|
+
const prices = pricesByProduct.get(product.id) || [];
|
|
1351
|
+
if (prices.length === 0) return null;
|
|
1352
|
+
const sortedPrices = [...prices].sort((a, b) => (a.unitAmount ?? 0) - (b.unitAmount ?? 0));
|
|
1353
|
+
return /* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
1354
|
+
/* @__PURE__ */ jsx16("h3", { className: "text-lg font-semibold", children: product.name }),
|
|
1355
|
+
/* @__PURE__ */ jsx16("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4", children: sortedPrices.map((price) => /* @__PURE__ */ jsx16(
|
|
1356
|
+
PricingCard,
|
|
1357
|
+
{
|
|
1358
|
+
price,
|
|
1359
|
+
isCurrentPlan: price.stripePriceId === currentPriceId,
|
|
1360
|
+
isSelected: price.stripePriceId === selectedPriceId,
|
|
1361
|
+
isLoading: price.stripePriceId === loadingPriceId,
|
|
1362
|
+
onSelect: onSelectPrice
|
|
1363
|
+
},
|
|
1364
|
+
price.stripePriceId
|
|
1365
|
+
)) })
|
|
1366
|
+
] }, product.id);
|
|
1367
|
+
}) });
|
|
1368
|
+
}
|
|
1369
|
+
__name(PricingCardsGrid, "PricingCardsGrid");
|
|
1370
|
+
function PricingCardsGridSkeleton() {
|
|
1371
|
+
return /* @__PURE__ */ jsx16("div", { className: "space-y-8", children: [1, 2].map((productIndex) => /* @__PURE__ */ jsxs14("div", { className: "space-y-4", children: [
|
|
1372
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-6 w-32" }),
|
|
1373
|
+
/* @__PURE__ */ jsx16("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4", children: [1, 2, 3].map((cardIndex) => /* @__PURE__ */ jsxs14(Card, { className: "animate-pulse", children: [
|
|
1374
|
+
/* @__PURE__ */ jsx16(CardHeader, { className: "pb-2", children: /* @__PURE__ */ jsx16(Skeleton, { className: "h-5 w-24" }) }),
|
|
1375
|
+
/* @__PURE__ */ jsxs14(CardContent, { className: "pb-4", children: [
|
|
1376
|
+
/* @__PURE__ */ jsxs14("div", { className: "mb-4", children: [
|
|
1377
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-9 w-20 inline-block" }),
|
|
1378
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-12 inline-block ml-2" })
|
|
1379
|
+
] }),
|
|
1380
|
+
/* @__PURE__ */ jsxs14("div", { className: "space-y-2", children: [
|
|
1381
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
1382
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-4 rounded-full" }),
|
|
1383
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-32" })
|
|
1384
|
+
] }),
|
|
1385
|
+
/* @__PURE__ */ jsxs14("div", { className: "flex items-center gap-2", children: [
|
|
1386
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-4 rounded-full" }),
|
|
1387
|
+
/* @__PURE__ */ jsx16(Skeleton, { className: "h-4 w-28" })
|
|
1388
|
+
] })
|
|
1389
|
+
] })
|
|
1390
|
+
] }),
|
|
1391
|
+
/* @__PURE__ */ jsx16(CardFooter, { children: /* @__PURE__ */ jsx16(Skeleton, { className: "h-9 w-full" }) })
|
|
1392
|
+
] }, cardIndex)) })
|
|
1393
|
+
] }, productIndex)) });
|
|
1394
|
+
}
|
|
1395
|
+
__name(PricingCardsGridSkeleton, "PricingCardsGridSkeleton");
|
|
1396
|
+
|
|
1397
|
+
// src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx
|
|
1398
|
+
import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1399
|
+
function ProrationPreview({ preview }) {
|
|
1400
|
+
return /* @__PURE__ */ jsxs15("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [
|
|
1401
|
+
/* @__PURE__ */ jsx17("h4", { className: "font-semibold text-blue-900 mb-3", children: "Proration Breakdown" }),
|
|
1402
|
+
/* @__PURE__ */ jsxs15("div", { className: "space-y-2", children: [
|
|
1403
|
+
preview.lineItems.map((item, index) => /* @__PURE__ */ jsxs15("div", { className: "flex justify-between text-sm", children: [
|
|
1404
|
+
/* @__PURE__ */ jsx17("span", { className: "text-blue-800", children: item.description }),
|
|
1405
|
+
/* @__PURE__ */ jsx17("span", { className: `font-medium ${item.amount < 0 ? "text-green-600" : "text-blue-900"}`, children: formatCurrency(item.amount, preview.currency) })
|
|
1406
|
+
] }, index)),
|
|
1407
|
+
/* @__PURE__ */ jsx17("div", { className: "border-t border-blue-200 pt-2 mt-2", children: /* @__PURE__ */ jsxs15("div", { className: "flex justify-between font-semibold", children: [
|
|
1408
|
+
/* @__PURE__ */ jsx17("span", { className: "text-blue-900", children: "Net Due Today" }),
|
|
1409
|
+
/* @__PURE__ */ jsx17("span", { className: "text-blue-900", children: formatCurrency(preview.immediateCharge, preview.currency) })
|
|
1410
|
+
] }) }),
|
|
1411
|
+
preview.lineItems.length > 0 && preview.lineItems[0].period && /* @__PURE__ */ jsxs15("div", { className: "text-xs text-blue-700 mt-2", children: [
|
|
1412
|
+
"Next invoice on ",
|
|
1413
|
+
formatDate3(preview.lineItems[0].period.end),
|
|
1414
|
+
" for",
|
|
1415
|
+
" ",
|
|
1416
|
+
formatCurrency(preview.amountDue, preview.currency)
|
|
1417
|
+
] })
|
|
1418
|
+
] })
|
|
1419
|
+
] });
|
|
1420
|
+
}
|
|
1421
|
+
__name(ProrationPreview, "ProrationPreview");
|
|
1422
|
+
|
|
1423
|
+
// src/features/billing/stripe-subscription/components/forms/SubscriptionEditor.tsx
|
|
1424
|
+
import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1425
|
+
function SubscriptionEditor({
|
|
1426
|
+
subscription,
|
|
1427
|
+
open,
|
|
1428
|
+
onOpenChange,
|
|
1429
|
+
onSuccess,
|
|
1430
|
+
onAddPaymentMethod
|
|
1431
|
+
}) {
|
|
1432
|
+
const { confirmPayment, isConfirming } = useConfirmSubscriptionPayment();
|
|
1433
|
+
const [products, setProducts] = useState9([]);
|
|
1434
|
+
const [pricesByProduct, setPricesByProduct] = useState9(/* @__PURE__ */ new Map());
|
|
1435
|
+
const [loading, setLoading] = useState9(true);
|
|
1436
|
+
const [selectedPriceId, setSelectedPriceId] = useState9(null);
|
|
1437
|
+
const [loadingPriceId, setLoadingPriceId] = useState9(null);
|
|
1438
|
+
const [prorationPreview, setProrationPreview] = useState9(null);
|
|
1439
|
+
const [loadingProration, setLoadingProration] = useState9(false);
|
|
1440
|
+
const [hasPaymentMethod, setHasPaymentMethod] = useState9(true);
|
|
1441
|
+
const [loadingPaymentMethods, setLoadingPaymentMethods] = useState9(true);
|
|
1442
|
+
const [paymentRequiredError, setPaymentRequiredError] = useState9(false);
|
|
1443
|
+
const [paymentConfirmationState, setPaymentConfirmationState] = useState9("idle");
|
|
1444
|
+
const [paymentError, setPaymentError] = useState9(null);
|
|
1445
|
+
const currentPriceId = subscription?.price?.id;
|
|
1446
|
+
const isEditMode = !!subscription;
|
|
1447
|
+
useEffect5(() => {
|
|
1448
|
+
const checkPaymentMethods = /* @__PURE__ */ __name(async () => {
|
|
1449
|
+
if (subscription) {
|
|
1450
|
+
setLoadingPaymentMethods(false);
|
|
1451
|
+
return;
|
|
1452
|
+
}
|
|
1453
|
+
setLoadingPaymentMethods(true);
|
|
1454
|
+
try {
|
|
1455
|
+
const paymentMethods = await StripeCustomerService.listPaymentMethods();
|
|
1456
|
+
const hasMethod = paymentMethods.length > 0;
|
|
1457
|
+
setHasPaymentMethod(hasMethod);
|
|
1458
|
+
} catch (error) {
|
|
1459
|
+
console.error("[SubscriptionEditor] Failed to check payment methods:", error);
|
|
1460
|
+
setHasPaymentMethod(false);
|
|
1461
|
+
} finally {
|
|
1462
|
+
setLoadingPaymentMethods(false);
|
|
1463
|
+
}
|
|
1464
|
+
}, "checkPaymentMethods");
|
|
1465
|
+
if (open) {
|
|
1466
|
+
checkPaymentMethods();
|
|
1467
|
+
}
|
|
1468
|
+
}, [open, subscription]);
|
|
1469
|
+
useEffect5(() => {
|
|
1470
|
+
const loadData = /* @__PURE__ */ __name(async () => {
|
|
1471
|
+
setLoading(true);
|
|
1472
|
+
try {
|
|
1473
|
+
const fetchedProducts = await StripeProductService.listProducts({ active: true });
|
|
1474
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1475
|
+
for (const product of fetchedProducts) {
|
|
1476
|
+
if (product.stripePrices && product.stripePrices.length > 0) {
|
|
1477
|
+
grouped.set(product.id, product.stripePrices);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
setProducts(fetchedProducts);
|
|
1481
|
+
setPricesByProduct(grouped);
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
console.error("[SubscriptionEditor] Failed to load products/prices:", error);
|
|
1484
|
+
} finally {
|
|
1485
|
+
setLoading(false);
|
|
1486
|
+
}
|
|
1487
|
+
}, "loadData");
|
|
1488
|
+
if (open) {
|
|
1489
|
+
loadData();
|
|
1490
|
+
}
|
|
1491
|
+
}, [open]);
|
|
1492
|
+
useEffect5(() => {
|
|
1493
|
+
const loadProration = /* @__PURE__ */ __name(async () => {
|
|
1494
|
+
if (!subscription || !selectedPriceId || selectedPriceId === currentPriceId) {
|
|
1495
|
+
setProrationPreview(null);
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
setLoadingProration(true);
|
|
1499
|
+
try {
|
|
1500
|
+
const preview = await StripeSubscriptionService.getProrationPreview({
|
|
1501
|
+
subscriptionId: subscription.id,
|
|
1502
|
+
newPriceId: selectedPriceId
|
|
1503
|
+
});
|
|
1504
|
+
setProrationPreview(preview);
|
|
1505
|
+
} catch (error) {
|
|
1506
|
+
console.error("[SubscriptionEditor] Failed to load proration preview:", error);
|
|
1507
|
+
setProrationPreview(null);
|
|
1508
|
+
} finally {
|
|
1509
|
+
setLoadingProration(false);
|
|
1510
|
+
}
|
|
1511
|
+
}, "loadProration");
|
|
1512
|
+
loadProration();
|
|
1513
|
+
}, [selectedPriceId, subscription, currentPriceId]);
|
|
1514
|
+
const handleSelectPrice = /* @__PURE__ */ __name(async (price) => {
|
|
1515
|
+
const priceId = price.id;
|
|
1516
|
+
if (isEditMode) {
|
|
1517
|
+
setSelectedPriceId(priceId);
|
|
1518
|
+
} else {
|
|
1519
|
+
setLoadingPriceId(priceId);
|
|
1520
|
+
setSelectedPriceId(priceId);
|
|
1521
|
+
setPaymentError(null);
|
|
1522
|
+
setPaymentConfirmationState("idle");
|
|
1523
|
+
try {
|
|
1524
|
+
const result = await StripeSubscriptionService.createSubscription({
|
|
1525
|
+
id: v4(),
|
|
1526
|
+
priceId
|
|
1527
|
+
});
|
|
1528
|
+
if (result.meta.requiresAction && result.meta.clientSecret) {
|
|
1529
|
+
setPaymentConfirmationState("confirming");
|
|
1530
|
+
const confirmation = await confirmPayment(result.meta.clientSecret);
|
|
1531
|
+
if (!confirmation.success) {
|
|
1532
|
+
console.error("[SubscriptionEditor] Payment confirmation failed:", confirmation.error);
|
|
1533
|
+
setPaymentConfirmationState("error");
|
|
1534
|
+
setPaymentError(confirmation.error || "Payment confirmation failed");
|
|
1535
|
+
setLoadingPriceId(null);
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
await StripeSubscriptionService.syncSubscription({
|
|
1539
|
+
subscriptionId: result.subscription.id
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
setPaymentConfirmationState("success");
|
|
1543
|
+
setTimeout(() => {
|
|
1544
|
+
onSuccess();
|
|
1545
|
+
onOpenChange(false);
|
|
1546
|
+
}, 1e3);
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
console.error("[SubscriptionEditor] Failed to create subscription:", error);
|
|
1549
|
+
if (error?.status === 402 || error?.response?.status === 402) {
|
|
1550
|
+
setPaymentRequiredError(true);
|
|
1551
|
+
setHasPaymentMethod(false);
|
|
1552
|
+
} else {
|
|
1553
|
+
setPaymentConfirmationState("error");
|
|
1554
|
+
setPaymentError(error?.message || "Failed to create subscription");
|
|
1555
|
+
}
|
|
1556
|
+
setLoadingPriceId(null);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}, "handleSelectPrice");
|
|
1560
|
+
const handleConfirmPlanChange = /* @__PURE__ */ __name(async () => {
|
|
1561
|
+
if (!subscription || !selectedPriceId) return;
|
|
1562
|
+
setLoadingPriceId(selectedPriceId);
|
|
1563
|
+
try {
|
|
1564
|
+
await StripeSubscriptionService.changePlan({
|
|
1565
|
+
id: subscription.id,
|
|
1566
|
+
newPriceId: selectedPriceId
|
|
1567
|
+
});
|
|
1568
|
+
onSuccess();
|
|
1569
|
+
onOpenChange(false);
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
console.error("[SubscriptionEditor] Failed to change plan:", error);
|
|
1572
|
+
} finally {
|
|
1573
|
+
setLoadingPriceId(null);
|
|
1574
|
+
}
|
|
1575
|
+
}, "handleConfirmPlanChange");
|
|
1576
|
+
const handleCancel = /* @__PURE__ */ __name(() => {
|
|
1577
|
+
setSelectedPriceId(null);
|
|
1578
|
+
setProrationPreview(null);
|
|
1579
|
+
}, "handleCancel");
|
|
1580
|
+
return /* @__PURE__ */ jsx18(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs16(DialogContent, { className: "max-w-4xl max-h-[90vh] overflow-y-auto", children: [
|
|
1581
|
+
/* @__PURE__ */ jsxs16(DialogHeader, { children: [
|
|
1582
|
+
/* @__PURE__ */ jsx18(DialogTitle, { children: subscription ? "Change Plan" : "Subscribe to a Plan" }),
|
|
1583
|
+
/* @__PURE__ */ jsx18(DialogDescription, { children: subscription ? "Select a new plan to switch to. You'll see a proration preview before confirming." : "Choose a plan to start your subscription." })
|
|
1584
|
+
] }),
|
|
1585
|
+
loadingPaymentMethods && !subscription ? /* @__PURE__ */ jsx18("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx18("div", { className: "text-muted-foreground", children: "Checking payment methods..." }) }) : !hasPaymentMethod && !subscription ? /* @__PURE__ */ jsxs16(Alert, { variant: "destructive", children: [
|
|
1586
|
+
/* @__PURE__ */ jsx18(AlertTitle, { children: "Payment Method Required" }),
|
|
1587
|
+
/* @__PURE__ */ jsxs16(AlertDescription, { className: "mt-2", children: [
|
|
1588
|
+
/* @__PURE__ */ jsx18("p", { className: "mb-4", children: paymentRequiredError ? "Your subscription could not be created because no payment method is on file." : "You need to add a payment method before you can subscribe to a plan." }),
|
|
1589
|
+
onAddPaymentMethod && /* @__PURE__ */ jsx18(Button, { onClick: onAddPaymentMethod, variant: "outline", children: "Add Payment Method" })
|
|
1590
|
+
] })
|
|
1591
|
+
] }) : paymentConfirmationState === "confirming" || isConfirming ? /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center justify-center py-12 space-y-4", children: [
|
|
1592
|
+
/* @__PURE__ */ jsx18(Loader2, { className: "h-8 w-8 animate-spin text-primary" }),
|
|
1593
|
+
/* @__PURE__ */ jsxs16("div", { className: "text-center", children: [
|
|
1594
|
+
/* @__PURE__ */ jsx18("p", { className: "font-medium", children: "Processing payment..." }),
|
|
1595
|
+
/* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: "Please complete any verification if prompted." })
|
|
1596
|
+
] })
|
|
1597
|
+
] }) : paymentConfirmationState === "success" ? /* @__PURE__ */ jsxs16("div", { className: "flex flex-col items-center justify-center py-12 space-y-4", children: [
|
|
1598
|
+
/* @__PURE__ */ jsx18(CheckCircle, { className: "h-12 w-12 text-green-500" }),
|
|
1599
|
+
/* @__PURE__ */ jsxs16("div", { className: "text-center", children: [
|
|
1600
|
+
/* @__PURE__ */ jsx18("p", { className: "font-medium text-green-600", children: "Payment successful!" }),
|
|
1601
|
+
/* @__PURE__ */ jsx18("p", { className: "text-sm text-muted-foreground", children: "Your subscription is now active." })
|
|
1602
|
+
] })
|
|
1603
|
+
] }) : paymentConfirmationState === "error" ? /* @__PURE__ */ jsx18("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs16(Alert, { variant: "destructive", children: [
|
|
1604
|
+
/* @__PURE__ */ jsx18(AlertTitle, { children: "Payment Failed" }),
|
|
1605
|
+
/* @__PURE__ */ jsxs16(AlertDescription, { className: "mt-2", children: [
|
|
1606
|
+
/* @__PURE__ */ jsx18("p", { className: "mb-4", children: paymentError || "We couldn't process your payment. Please try again." }),
|
|
1607
|
+
/* @__PURE__ */ jsx18(
|
|
1608
|
+
Button,
|
|
1609
|
+
{
|
|
1610
|
+
onClick: () => {
|
|
1611
|
+
setPaymentConfirmationState("idle");
|
|
1612
|
+
setPaymentError(null);
|
|
1613
|
+
setLoadingPriceId(null);
|
|
1614
|
+
},
|
|
1615
|
+
variant: "outline",
|
|
1616
|
+
children: "Try Again"
|
|
1617
|
+
}
|
|
1618
|
+
)
|
|
1619
|
+
] })
|
|
1620
|
+
] }) }) : /* @__PURE__ */ jsxs16("div", { className: "space-y-6", children: [
|
|
1621
|
+
/* @__PURE__ */ jsx18(
|
|
1622
|
+
PricingCardsGrid,
|
|
1623
|
+
{
|
|
1624
|
+
products,
|
|
1625
|
+
pricesByProduct,
|
|
1626
|
+
currentPriceId,
|
|
1627
|
+
selectedPriceId: selectedPriceId ?? void 0,
|
|
1628
|
+
loadingPriceId: loadingPriceId ?? void 0,
|
|
1629
|
+
loading,
|
|
1630
|
+
onSelectPrice: handleSelectPrice
|
|
1631
|
+
}
|
|
1632
|
+
),
|
|
1633
|
+
isEditMode && loadingProration && /* @__PURE__ */ jsx18("div", { className: "bg-muted/50 rounded-lg p-4 text-sm text-muted-foreground text-center", children: "Loading proration preview..." }),
|
|
1634
|
+
isEditMode && prorationPreview && !loadingProration && /* @__PURE__ */ jsxs16("div", { className: "space-y-4", children: [
|
|
1635
|
+
/* @__PURE__ */ jsx18(ProrationPreview, { preview: prorationPreview }),
|
|
1636
|
+
/* @__PURE__ */ jsxs16("div", { className: "flex justify-end gap-3", children: [
|
|
1637
|
+
/* @__PURE__ */ jsx18(Button, { variant: "outline", onClick: handleCancel, disabled: !!loadingPriceId, children: "Cancel" }),
|
|
1638
|
+
/* @__PURE__ */ jsx18(Button, { onClick: handleConfirmPlanChange, disabled: !!loadingPriceId, children: loadingPriceId ? "Processing..." : "Confirm Plan Change" })
|
|
1639
|
+
] })
|
|
1640
|
+
] })
|
|
1641
|
+
] })
|
|
1642
|
+
] }) });
|
|
1643
|
+
}
|
|
1644
|
+
__name(SubscriptionEditor, "SubscriptionEditor");
|
|
1645
|
+
|
|
1646
|
+
// src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx
|
|
1647
|
+
import { useState as useState11 } from "react";
|
|
1648
|
+
|
|
1649
|
+
// src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx
|
|
1650
|
+
import { useState as useState10 } from "react";
|
|
1651
|
+
|
|
1652
|
+
// src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx
|
|
1653
|
+
import { jsx as jsx19 } from "react/jsx-runtime";
|
|
1654
|
+
var statusConfig2 = {
|
|
1655
|
+
["active" /* ACTIVE */]: {
|
|
1656
|
+
label: "Active",
|
|
1657
|
+
color: "bg-green-100 text-green-800"
|
|
1658
|
+
},
|
|
1659
|
+
["trialing" /* TRIALING */]: {
|
|
1660
|
+
label: "Trial",
|
|
1661
|
+
color: "bg-blue-100 text-blue-800"
|
|
1662
|
+
},
|
|
1663
|
+
["past_due" /* PAST_DUE */]: {
|
|
1664
|
+
label: "Past Due",
|
|
1665
|
+
color: "bg-red-100 text-red-800"
|
|
1666
|
+
},
|
|
1667
|
+
["canceled" /* CANCELED */]: {
|
|
1668
|
+
label: "Canceled",
|
|
1669
|
+
color: "bg-gray-100 text-gray-800"
|
|
1670
|
+
},
|
|
1671
|
+
["paused" /* PAUSED */]: {
|
|
1672
|
+
label: "Paused",
|
|
1673
|
+
color: "bg-yellow-100 text-yellow-800"
|
|
1674
|
+
},
|
|
1675
|
+
["unpaid" /* UNPAID */]: {
|
|
1676
|
+
label: "Unpaid",
|
|
1677
|
+
color: "bg-orange-100 text-orange-800"
|
|
1678
|
+
},
|
|
1679
|
+
["incomplete" /* INCOMPLETE */]: {
|
|
1680
|
+
label: "Incomplete",
|
|
1681
|
+
color: "bg-gray-100 text-gray-800"
|
|
1682
|
+
},
|
|
1683
|
+
["incomplete_expired" /* INCOMPLETE_EXPIRED */]: {
|
|
1684
|
+
label: "Expired",
|
|
1685
|
+
color: "bg-gray-100 text-gray-800"
|
|
1686
|
+
}
|
|
1687
|
+
};
|
|
1688
|
+
var cancelingConfig = {
|
|
1689
|
+
label: "Canceling",
|
|
1690
|
+
color: "bg-amber-100 text-amber-800"
|
|
1691
|
+
};
|
|
1692
|
+
function SubscriptionStatusBadge({ status, cancelAtPeriodEnd }) {
|
|
1693
|
+
const config = cancelAtPeriodEnd ? cancelingConfig : statusConfig2[status] || statusConfig2["canceled" /* CANCELED */];
|
|
1694
|
+
return /* @__PURE__ */ jsx19("span", { className: `${config.color} text-xs px-2 py-1 rounded-full font-medium`, children: config.label });
|
|
1695
|
+
}
|
|
1696
|
+
__name(SubscriptionStatusBadge, "SubscriptionStatusBadge");
|
|
1697
|
+
|
|
1698
|
+
// src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx
|
|
1699
|
+
import { Fragment as Fragment3, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1700
|
+
function formatPlanName2(price) {
|
|
1701
|
+
if (!price) return "N/A";
|
|
1702
|
+
const productName = price.product?.name || "";
|
|
1703
|
+
const nickname = price.nickname || "";
|
|
1704
|
+
let interval = "";
|
|
1705
|
+
if (price.recurring?.interval) {
|
|
1706
|
+
const intervalMap = {
|
|
1707
|
+
day: "Daily",
|
|
1708
|
+
week: "Weekly",
|
|
1709
|
+
month: "Monthly",
|
|
1710
|
+
year: "Yearly"
|
|
1711
|
+
};
|
|
1712
|
+
interval = intervalMap[price.recurring.interval] || price.recurring.interval;
|
|
1713
|
+
}
|
|
1714
|
+
const parts = [productName, nickname].filter(Boolean);
|
|
1715
|
+
const planLabel = parts.join(" - ");
|
|
1716
|
+
return interval ? `${planLabel} (${interval})` : planLabel || "N/A";
|
|
1717
|
+
}
|
|
1718
|
+
__name(formatPlanName2, "formatPlanName");
|
|
1719
|
+
function formatBillingAmount(price) {
|
|
1720
|
+
if (!price?.unitAmount) return "N/A";
|
|
1721
|
+
return formatCurrency(price.unitAmount, price.currency);
|
|
1722
|
+
}
|
|
1723
|
+
__name(formatBillingAmount, "formatBillingAmount");
|
|
1724
|
+
function SubscriptionDetails({
|
|
1725
|
+
subscription,
|
|
1726
|
+
open,
|
|
1727
|
+
onOpenChange,
|
|
1728
|
+
onSubscriptionChange
|
|
1729
|
+
}) {
|
|
1730
|
+
const [showEdit, setShowEdit] = useState10(false);
|
|
1731
|
+
const [showCancel, setShowCancel] = useState10(false);
|
|
1732
|
+
const [isProcessing, setIsProcessing] = useState10(false);
|
|
1733
|
+
const handlePause = /* @__PURE__ */ __name(async () => {
|
|
1734
|
+
setIsProcessing(true);
|
|
1735
|
+
try {
|
|
1736
|
+
await StripeSubscriptionService.pauseSubscription({ subscriptionId: subscription.id });
|
|
1737
|
+
onSubscriptionChange();
|
|
1738
|
+
} catch (error) {
|
|
1739
|
+
console.error("[SubscriptionDetails] Failed to pause subscription:", error);
|
|
1740
|
+
} finally {
|
|
1741
|
+
setIsProcessing(false);
|
|
1742
|
+
}
|
|
1743
|
+
}, "handlePause");
|
|
1744
|
+
const handleResume = /* @__PURE__ */ __name(async () => {
|
|
1745
|
+
setIsProcessing(true);
|
|
1746
|
+
try {
|
|
1747
|
+
await StripeSubscriptionService.resumeSubscription({ subscriptionId: subscription.id });
|
|
1748
|
+
onSubscriptionChange();
|
|
1749
|
+
} catch (error) {
|
|
1750
|
+
console.error("[SubscriptionDetails] Failed to resume subscription:", error);
|
|
1751
|
+
} finally {
|
|
1752
|
+
setIsProcessing(false);
|
|
1753
|
+
}
|
|
1754
|
+
}, "handleResume");
|
|
1755
|
+
const handleManageViaPortal = /* @__PURE__ */ __name(async () => {
|
|
1756
|
+
try {
|
|
1757
|
+
const { url } = await StripeCustomerService.createPortalSession();
|
|
1758
|
+
window.open(url, "_blank");
|
|
1759
|
+
} catch (error) {
|
|
1760
|
+
console.error("[SubscriptionDetails] Failed to create portal session:", error);
|
|
1761
|
+
}
|
|
1762
|
+
}, "handleManageViaPortal");
|
|
1763
|
+
const canPause = subscription.status === "active" /* ACTIVE */;
|
|
1764
|
+
const canResume = subscription.status === "paused" /* PAUSED */;
|
|
1765
|
+
const canCancel = subscription.status === "active" /* ACTIVE */ || subscription.status === "trialing" /* TRIALING */ || subscription.status === "paused" /* PAUSED */;
|
|
1766
|
+
return /* @__PURE__ */ jsxs17(Fragment3, { children: [
|
|
1767
|
+
/* @__PURE__ */ jsx20(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs17(DialogContent, { className: "max-w-2xl", children: [
|
|
1768
|
+
/* @__PURE__ */ jsxs17(DialogHeader, { children: [
|
|
1769
|
+
/* @__PURE__ */ jsx20(DialogTitle, { children: "Subscription Details" }),
|
|
1770
|
+
/* @__PURE__ */ jsx20(DialogDescription, { children: "View and manage your subscription" })
|
|
1771
|
+
] }),
|
|
1772
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-6", children: [
|
|
1773
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex items-center gap-x-3", children: [
|
|
1774
|
+
/* @__PURE__ */ jsx20("span", { className: "text-sm font-medium text-muted-foreground", children: "Status:" }),
|
|
1775
|
+
/* @__PURE__ */ jsx20(SubscriptionStatusBadge, { status: subscription.status, cancelAtPeriodEnd: subscription.cancelAtPeriodEnd })
|
|
1776
|
+
] }),
|
|
1777
|
+
/* @__PURE__ */ jsxs17("div", { className: "space-y-2", children: [
|
|
1778
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex justify-between", children: [
|
|
1779
|
+
/* @__PURE__ */ jsx20("span", { className: "text-sm font-medium text-muted-foreground", children: "Plan:" }),
|
|
1780
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium", children: formatPlanName2(subscription.price) })
|
|
1781
|
+
] }),
|
|
1782
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex justify-between", children: [
|
|
1783
|
+
/* @__PURE__ */ jsx20("span", { className: "text-sm font-medium text-muted-foreground", children: "Billing Amount:" }),
|
|
1784
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium", children: formatBillingAmount(subscription.price) })
|
|
1785
|
+
] })
|
|
1786
|
+
] }),
|
|
1787
|
+
/* @__PURE__ */ jsx20("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs17("div", { className: "flex justify-between", children: [
|
|
1788
|
+
/* @__PURE__ */ jsx20("span", { className: "text-sm font-medium text-muted-foreground", children: "Current Period:" }),
|
|
1789
|
+
/* @__PURE__ */ jsxs17("span", { className: "font-medium", children: [
|
|
1790
|
+
formatDate3(subscription.currentPeriodStart),
|
|
1791
|
+
" - ",
|
|
1792
|
+
formatDate3(subscription.currentPeriodEnd)
|
|
1793
|
+
] })
|
|
1794
|
+
] }) }),
|
|
1795
|
+
subscription.trialEnd && /* @__PURE__ */ jsxs17("div", { className: "flex justify-between", children: [
|
|
1796
|
+
/* @__PURE__ */ jsx20("span", { className: "text-sm font-medium text-muted-foreground", children: "Trial Ends:" }),
|
|
1797
|
+
/* @__PURE__ */ jsx20("span", { className: "font-medium", children: formatDate3(subscription.trialEnd) })
|
|
1798
|
+
] }),
|
|
1799
|
+
subscription.cancelAtPeriodEnd && /* @__PURE__ */ jsx20("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-3", children: /* @__PURE__ */ jsxs17("p", { className: "text-sm text-yellow-800", children: [
|
|
1800
|
+
"This subscription will be canceled at the end of the current period on",
|
|
1801
|
+
" ",
|
|
1802
|
+
formatDate3(subscription.currentPeriodEnd),
|
|
1803
|
+
"."
|
|
1804
|
+
] }) }),
|
|
1805
|
+
/* @__PURE__ */ jsxs17("div", { className: "flex flex-wrap gap-2 pt-4 border-t", children: [
|
|
1806
|
+
/* @__PURE__ */ jsx20(Button, { variant: "default", onClick: () => setShowEdit(true), children: "Change Plan" }),
|
|
1807
|
+
canPause && /* @__PURE__ */ jsx20(Button, { variant: "outline", onClick: handlePause, disabled: isProcessing, children: isProcessing ? "Pausing..." : "Pause" }),
|
|
1808
|
+
canResume && /* @__PURE__ */ jsx20(Button, { variant: "outline", onClick: handleResume, disabled: isProcessing, children: isProcessing ? "Resuming..." : "Resume" }),
|
|
1809
|
+
canCancel && /* @__PURE__ */ jsx20(Button, { variant: "destructive", onClick: () => setShowCancel(true), children: "Cancel" }),
|
|
1810
|
+
/* @__PURE__ */ jsx20(Button, { variant: "outline", onClick: handleManageViaPortal, children: "Manage via Portal" })
|
|
1811
|
+
] })
|
|
1812
|
+
] })
|
|
1813
|
+
] }) }),
|
|
1814
|
+
showEdit && /* @__PURE__ */ jsx20(
|
|
1815
|
+
SubscriptionEditor,
|
|
1816
|
+
{
|
|
1817
|
+
subscription,
|
|
1818
|
+
open: showEdit,
|
|
1819
|
+
onOpenChange: setShowEdit,
|
|
1820
|
+
onSuccess: () => {
|
|
1821
|
+
onSubscriptionChange();
|
|
1822
|
+
setShowEdit(false);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
),
|
|
1826
|
+
showCancel && /* @__PURE__ */ jsx20(
|
|
1827
|
+
CancelSubscriptionDialog,
|
|
1828
|
+
{
|
|
1829
|
+
subscription,
|
|
1830
|
+
open: showCancel,
|
|
1831
|
+
onOpenChange: setShowCancel,
|
|
1832
|
+
onSuccess: () => {
|
|
1833
|
+
onSubscriptionChange();
|
|
1834
|
+
setShowCancel(false);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
)
|
|
1838
|
+
] });
|
|
1839
|
+
}
|
|
1840
|
+
__name(SubscriptionDetails, "SubscriptionDetails");
|
|
1841
|
+
|
|
1842
|
+
// src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx
|
|
1843
|
+
import { Fragment as Fragment4, jsx as jsx21, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1844
|
+
function formatPlanName3(price) {
|
|
1845
|
+
if (!price) return "N/A";
|
|
1846
|
+
const productName = price.product?.name || "";
|
|
1847
|
+
const nickname = price.nickname || "";
|
|
1848
|
+
let interval = "";
|
|
1849
|
+
if (price.recurring?.interval) {
|
|
1850
|
+
const intervalMap = {
|
|
1851
|
+
day: "Daily",
|
|
1852
|
+
week: "Weekly",
|
|
1853
|
+
month: "Monthly",
|
|
1854
|
+
year: "Yearly"
|
|
1855
|
+
};
|
|
1856
|
+
interval = intervalMap[price.recurring.interval] || price.recurring.interval;
|
|
1857
|
+
}
|
|
1858
|
+
const parts = [productName, nickname].filter(Boolean);
|
|
1859
|
+
const planLabel = parts.join(" - ");
|
|
1860
|
+
return interval ? `${planLabel} (${interval})` : planLabel || "N/A";
|
|
1861
|
+
}
|
|
1862
|
+
__name(formatPlanName3, "formatPlanName");
|
|
1863
|
+
function SubscriptionsList({ subscriptions, onSubscriptionsChange }) {
|
|
1864
|
+
const [selectedSub, setSelectedSub] = useState11(null);
|
|
1865
|
+
const handleRowClick = /* @__PURE__ */ __name((subscription) => {
|
|
1866
|
+
setSelectedSub(subscription);
|
|
1867
|
+
}, "handleRowClick");
|
|
1868
|
+
return /* @__PURE__ */ jsxs18(Fragment4, { children: [
|
|
1869
|
+
/* @__PURE__ */ jsx21("div", { className: "border rounded-lg overflow-hidden", children: /* @__PURE__ */ jsxs18(Table, { children: [
|
|
1870
|
+
/* @__PURE__ */ jsx21(TableHeader, { className: "bg-muted", children: /* @__PURE__ */ jsxs18(TableRow, { children: [
|
|
1871
|
+
/* @__PURE__ */ jsx21(TableHead, { children: "Status" }),
|
|
1872
|
+
/* @__PURE__ */ jsx21(TableHead, { children: "Plan" }),
|
|
1873
|
+
/* @__PURE__ */ jsx21(TableHead, { children: "Period" }),
|
|
1874
|
+
/* @__PURE__ */ jsx21(TableHead, { className: "text-right", children: "Amount" })
|
|
1875
|
+
] }) }),
|
|
1876
|
+
/* @__PURE__ */ jsx21(TableBody, { children: subscriptions.map((subscription) => {
|
|
1877
|
+
const price = subscription.price;
|
|
1878
|
+
const amount = price?.unitAmount ? formatCurrency(price.unitAmount, price.currency) : "N/A";
|
|
1879
|
+
const period = `${formatDate3(subscription.currentPeriodStart)} - ${formatDate3(subscription.currentPeriodEnd)}`;
|
|
1880
|
+
return /* @__PURE__ */ jsxs18(
|
|
1881
|
+
TableRow,
|
|
1882
|
+
{
|
|
1883
|
+
onClick: () => handleRowClick(subscription),
|
|
1884
|
+
className: "cursor-pointer hover:bg-muted/50",
|
|
1885
|
+
children: [
|
|
1886
|
+
/* @__PURE__ */ jsx21(TableCell, { children: /* @__PURE__ */ jsx21(SubscriptionStatusBadge, { status: subscription.status, cancelAtPeriodEnd: subscription.cancelAtPeriodEnd }) }),
|
|
1887
|
+
/* @__PURE__ */ jsx21(TableCell, { className: "font-medium", children: formatPlanName3(price) }),
|
|
1888
|
+
/* @__PURE__ */ jsx21(TableCell, { className: "text-muted-foreground text-sm", children: period }),
|
|
1889
|
+
/* @__PURE__ */ jsx21(TableCell, { className: "text-right font-medium", children: amount })
|
|
1890
|
+
]
|
|
1891
|
+
},
|
|
1892
|
+
subscription.id
|
|
1893
|
+
);
|
|
1894
|
+
}) })
|
|
1895
|
+
] }) }),
|
|
1896
|
+
selectedSub && /* @__PURE__ */ jsx21(
|
|
1897
|
+
SubscriptionDetails,
|
|
1898
|
+
{
|
|
1899
|
+
subscription: selectedSub,
|
|
1900
|
+
open: !!selectedSub,
|
|
1901
|
+
onOpenChange: (open) => !open && setSelectedSub(null),
|
|
1902
|
+
onSubscriptionChange: () => {
|
|
1903
|
+
onSubscriptionsChange();
|
|
1904
|
+
setSelectedSub(null);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
)
|
|
1908
|
+
] });
|
|
1909
|
+
}
|
|
1910
|
+
__name(SubscriptionsList, "SubscriptionsList");
|
|
1911
|
+
|
|
1912
|
+
// src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx
|
|
1913
|
+
import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1914
|
+
function SubscriptionsContainer() {
|
|
1915
|
+
const { confirmPayment, isConfirming } = useConfirmSubscriptionPayment();
|
|
1916
|
+
const [subscriptions, setSubscriptions] = useState12([]);
|
|
1917
|
+
const [loading, setLoading] = useState12(true);
|
|
1918
|
+
const [showCreateSubscription, setShowCreateSubscription] = useState12(false);
|
|
1919
|
+
const [showPaymentMethodEditor, setShowPaymentMethodEditor] = useState12(false);
|
|
1920
|
+
const [products, setProducts] = useState12([]);
|
|
1921
|
+
const [pricesByProduct, setPricesByProduct] = useState12(/* @__PURE__ */ new Map());
|
|
1922
|
+
const [loadingPricing, setLoadingPricing] = useState12(false);
|
|
1923
|
+
const [hasPaymentMethod, setHasPaymentMethod] = useState12(null);
|
|
1924
|
+
const [pendingPriceId, setPendingPriceId] = useState12(null);
|
|
1925
|
+
const [creatingSubscription, setCreatingSubscription] = useState12(false);
|
|
1926
|
+
const [paymentConfirmationState, setPaymentConfirmationState] = useState12("idle");
|
|
1927
|
+
const [paymentError, setPaymentError] = useState12(null);
|
|
1928
|
+
const loadSubscriptions = /* @__PURE__ */ __name(async () => {
|
|
1929
|
+
setLoading(true);
|
|
1930
|
+
try {
|
|
1931
|
+
const fetchedSubscriptions = await StripeSubscriptionService.listSubscriptions();
|
|
1932
|
+
setSubscriptions(fetchedSubscriptions);
|
|
1933
|
+
} catch (error) {
|
|
1934
|
+
console.error("[SubscriptionsContainer] Failed to load subscriptions:", error);
|
|
1935
|
+
} finally {
|
|
1936
|
+
setLoading(false);
|
|
1937
|
+
}
|
|
1938
|
+
}, "loadSubscriptions");
|
|
1939
|
+
const loadPricingData = /* @__PURE__ */ __name(async () => {
|
|
1940
|
+
setLoadingPricing(true);
|
|
1941
|
+
try {
|
|
1942
|
+
const fetchedProducts = await StripeProductService.listProducts({ active: true });
|
|
1943
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1944
|
+
for (const product of fetchedProducts) {
|
|
1945
|
+
if (product.stripePrices && product.stripePrices.length > 0) {
|
|
1946
|
+
grouped.set(product.id, product.stripePrices);
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
setProducts(fetchedProducts);
|
|
1950
|
+
setPricesByProduct(grouped);
|
|
1951
|
+
} catch (error) {
|
|
1952
|
+
console.error("[SubscriptionsContainer] Failed to load pricing data:", error);
|
|
1953
|
+
} finally {
|
|
1954
|
+
setLoadingPricing(false);
|
|
1955
|
+
}
|
|
1956
|
+
}, "loadPricingData");
|
|
1957
|
+
const checkPaymentMethod = /* @__PURE__ */ __name(async () => {
|
|
1958
|
+
try {
|
|
1959
|
+
const paymentMethods = await StripeCustomerService.listPaymentMethods();
|
|
1960
|
+
const hasMethod = paymentMethods.length > 0;
|
|
1961
|
+
setHasPaymentMethod(hasMethod);
|
|
1962
|
+
} catch (error) {
|
|
1963
|
+
console.error("[SubscriptionsContainer] Failed to check payment methods:", error);
|
|
1964
|
+
setHasPaymentMethod(false);
|
|
1965
|
+
}
|
|
1966
|
+
}, "checkPaymentMethod");
|
|
1967
|
+
const createSubscriptionWithPrice = /* @__PURE__ */ __name(async (priceId) => {
|
|
1968
|
+
setCreatingSubscription(true);
|
|
1969
|
+
setPaymentError(null);
|
|
1970
|
+
setPaymentConfirmationState("idle");
|
|
1971
|
+
try {
|
|
1972
|
+
const result = await StripeSubscriptionService.createSubscription({
|
|
1973
|
+
id: v42(),
|
|
1974
|
+
priceId
|
|
1975
|
+
});
|
|
1976
|
+
if (result.meta.requiresAction && result.meta.clientSecret) {
|
|
1977
|
+
setPaymentConfirmationState("confirming");
|
|
1978
|
+
const confirmation = await confirmPayment(result.meta.clientSecret);
|
|
1979
|
+
if (!confirmation.success) {
|
|
1980
|
+
console.error("[SubscriptionsContainer] Payment confirmation failed:", confirmation.error);
|
|
1981
|
+
setPaymentConfirmationState("error");
|
|
1982
|
+
setPaymentError(confirmation.error || "Payment confirmation failed");
|
|
1983
|
+
setCreatingSubscription(false);
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
await StripeSubscriptionService.syncSubscription({
|
|
1987
|
+
subscriptionId: result.subscription.id
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
setPaymentConfirmationState("success");
|
|
1991
|
+
await loadSubscriptions();
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
console.error("[SubscriptionsContainer] Failed to create subscription:", error);
|
|
1994
|
+
if (error?.status === 402 || error?.response?.status === 402) {
|
|
1995
|
+
setPendingPriceId(priceId);
|
|
1996
|
+
setHasPaymentMethod(false);
|
|
1997
|
+
setShowPaymentMethodEditor(true);
|
|
1998
|
+
} else {
|
|
1999
|
+
setPaymentConfirmationState("error");
|
|
2000
|
+
setPaymentError(error?.message || "Failed to create subscription");
|
|
2001
|
+
}
|
|
2002
|
+
} finally {
|
|
2003
|
+
setCreatingSubscription(false);
|
|
2004
|
+
}
|
|
2005
|
+
}, "createSubscriptionWithPrice");
|
|
2006
|
+
const handleSelectPrice = /* @__PURE__ */ __name(async (price) => {
|
|
2007
|
+
const priceId = price.id;
|
|
2008
|
+
if (!hasPaymentMethod) {
|
|
2009
|
+
setPendingPriceId(priceId);
|
|
2010
|
+
setShowPaymentMethodEditor(true);
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
await createSubscriptionWithPrice(priceId);
|
|
2014
|
+
}, "handleSelectPrice");
|
|
2015
|
+
const handlePaymentMethodSuccess = /* @__PURE__ */ __name(async () => {
|
|
2016
|
+
setShowPaymentMethodEditor(false);
|
|
2017
|
+
setHasPaymentMethod(true);
|
|
2018
|
+
if (pendingPriceId) {
|
|
2019
|
+
await createSubscriptionWithPrice(pendingPriceId);
|
|
2020
|
+
setPendingPriceId(null);
|
|
2021
|
+
}
|
|
2022
|
+
}, "handlePaymentMethodSuccess");
|
|
2023
|
+
useEffect6(() => {
|
|
2024
|
+
loadSubscriptions();
|
|
2025
|
+
}, []);
|
|
2026
|
+
useEffect6(() => {
|
|
2027
|
+
if (!loading && subscriptions.length === 0) {
|
|
2028
|
+
loadPricingData();
|
|
2029
|
+
checkPaymentMethod();
|
|
2030
|
+
}
|
|
2031
|
+
}, [loading, subscriptions.length]);
|
|
2032
|
+
const criticalSubscriptions = subscriptions.filter(
|
|
2033
|
+
(sub) => sub.status === "past_due" /* PAST_DUE */ || sub.status === "trialing" /* TRIALING */ && sub.trialEnd && new Date(sub.trialEnd).getTime() - (/* @__PURE__ */ new Date()).getTime() <= 7 * 24 * 60 * 60 * 1e3
|
|
2034
|
+
);
|
|
2035
|
+
if (loading) {
|
|
2036
|
+
return /* @__PURE__ */ jsx22("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx22("p", { className: "text-muted-foreground", children: "Loading subscriptions..." }) });
|
|
2037
|
+
}
|
|
2038
|
+
return /* @__PURE__ */ jsxs19("div", { className: "flex w-full flex-col gap-y-6", children: [
|
|
2039
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex items-center justify-between", children: [
|
|
2040
|
+
/* @__PURE__ */ jsxs19("div", { className: "flex items-center gap-x-3", children: [
|
|
2041
|
+
/* @__PURE__ */ jsx22(CreditCard3, { className: "h-8 w-8" }),
|
|
2042
|
+
/* @__PURE__ */ jsx22("h1", { className: "text-3xl font-bold", children: "Subscriptions" })
|
|
2043
|
+
] }),
|
|
2044
|
+
subscriptions.length > 0 && /* @__PURE__ */ jsx22(Button, { onClick: () => setShowCreateSubscription(true), children: "Subscribe to a Plan" })
|
|
2045
|
+
] }),
|
|
2046
|
+
criticalSubscriptions.map((subscription) => /* @__PURE__ */ jsx22(BillingAlertBanner, { subscription }, subscription.id)),
|
|
2047
|
+
subscriptions.length === 0 && /* @__PURE__ */ jsxs19("div", { className: "space-y-6", children: [
|
|
2048
|
+
(paymentConfirmationState === "confirming" || isConfirming) && /* @__PURE__ */ jsxs19("div", { className: "flex flex-col items-center justify-center py-12 space-y-4 bg-muted/50 rounded-lg", children: [
|
|
2049
|
+
/* @__PURE__ */ jsx22(Loader22, { className: "h-8 w-8 animate-spin text-primary" }),
|
|
2050
|
+
/* @__PURE__ */ jsxs19("div", { className: "text-center", children: [
|
|
2051
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium", children: "Processing payment..." }),
|
|
2052
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Please complete any verification if prompted." })
|
|
2053
|
+
] })
|
|
2054
|
+
] }),
|
|
2055
|
+
paymentConfirmationState === "success" && /* @__PURE__ */ jsxs19("div", { className: "flex flex-col items-center justify-center py-8 space-y-4 bg-green-50 rounded-lg border border-green-200", children: [
|
|
2056
|
+
/* @__PURE__ */ jsx22(CheckCircle2, { className: "h-12 w-12 text-green-500" }),
|
|
2057
|
+
/* @__PURE__ */ jsxs19("div", { className: "text-center", children: [
|
|
2058
|
+
/* @__PURE__ */ jsx22("p", { className: "font-medium text-green-600", children: "Payment successful!" }),
|
|
2059
|
+
/* @__PURE__ */ jsx22("p", { className: "text-sm text-muted-foreground", children: "Your subscription is now active." })
|
|
2060
|
+
] })
|
|
2061
|
+
] }),
|
|
2062
|
+
paymentConfirmationState === "error" && /* @__PURE__ */ jsxs19(Alert, { variant: "destructive", children: [
|
|
2063
|
+
/* @__PURE__ */ jsx22(AlertTitle, { children: "Payment Failed" }),
|
|
2064
|
+
/* @__PURE__ */ jsxs19(AlertDescription, { className: "mt-2", children: [
|
|
2065
|
+
/* @__PURE__ */ jsx22("p", { className: "mb-4", children: paymentError || "We couldn't process your payment. Please try again." }),
|
|
2066
|
+
/* @__PURE__ */ jsx22(
|
|
2067
|
+
Button,
|
|
2068
|
+
{
|
|
2069
|
+
onClick: () => {
|
|
2070
|
+
setPaymentConfirmationState("idle");
|
|
2071
|
+
setPaymentError(null);
|
|
2072
|
+
},
|
|
2073
|
+
variant: "outline",
|
|
2074
|
+
children: "Try Again"
|
|
2075
|
+
}
|
|
2076
|
+
)
|
|
2077
|
+
] })
|
|
2078
|
+
] }),
|
|
2079
|
+
paymentConfirmationState === "idle" && !isConfirming && /* @__PURE__ */ jsxs19(Fragment5, { children: [
|
|
2080
|
+
/* @__PURE__ */ jsxs19("div", { className: "text-center", children: [
|
|
2081
|
+
/* @__PURE__ */ jsx22(CreditCard3, { className: "text-muted-foreground mx-auto h-16 w-16 mb-4" }),
|
|
2082
|
+
/* @__PURE__ */ jsx22("h3", { className: "mb-2 text-xl font-semibold", children: "Choose Your Plan" }),
|
|
2083
|
+
/* @__PURE__ */ jsx22("p", { className: "text-muted-foreground mb-6", children: "Select a subscription plan to get started with our services." })
|
|
2084
|
+
] }),
|
|
2085
|
+
/* @__PURE__ */ jsx22(
|
|
2086
|
+
PricingCardsGrid,
|
|
2087
|
+
{
|
|
2088
|
+
products,
|
|
2089
|
+
pricesByProduct,
|
|
2090
|
+
loading: loadingPricing,
|
|
2091
|
+
loadingPriceId: creatingSubscription ? pendingPriceId ?? void 0 : void 0,
|
|
2092
|
+
onSelectPrice: handleSelectPrice
|
|
2093
|
+
}
|
|
2094
|
+
)
|
|
2095
|
+
] })
|
|
2096
|
+
] }),
|
|
2097
|
+
subscriptions.length > 0 && /* @__PURE__ */ jsx22(SubscriptionsList, { subscriptions, onSubscriptionsChange: loadSubscriptions }),
|
|
2098
|
+
showCreateSubscription && /* @__PURE__ */ jsx22(
|
|
2099
|
+
SubscriptionEditor,
|
|
2100
|
+
{
|
|
2101
|
+
open: showCreateSubscription,
|
|
2102
|
+
onOpenChange: setShowCreateSubscription,
|
|
2103
|
+
onSuccess: loadSubscriptions,
|
|
2104
|
+
onAddPaymentMethod: () => {
|
|
2105
|
+
setShowCreateSubscription(false);
|
|
2106
|
+
setShowPaymentMethodEditor(true);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
),
|
|
2110
|
+
showPaymentMethodEditor && /* @__PURE__ */ jsx22(
|
|
2111
|
+
PaymentMethodEditor,
|
|
2112
|
+
{
|
|
2113
|
+
open: showPaymentMethodEditor,
|
|
2114
|
+
onOpenChange: (open) => {
|
|
2115
|
+
setShowPaymentMethodEditor(open);
|
|
2116
|
+
if (!open) {
|
|
2117
|
+
setPendingPriceId(null);
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
onSuccess: handlePaymentMethodSuccess
|
|
2121
|
+
}
|
|
2122
|
+
)
|
|
2123
|
+
] });
|
|
2124
|
+
}
|
|
2125
|
+
__name(SubscriptionsContainer, "SubscriptionsContainer");
|
|
2126
|
+
|
|
2127
|
+
// src/features/billing/stripe-usage/components/containers/UsageContainer.tsx
|
|
2128
|
+
import { Activity as Activity3 } from "lucide-react";
|
|
2129
|
+
import { useEffect as useEffect7, useState as useState13 } from "react";
|
|
2130
|
+
|
|
2131
|
+
// src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx
|
|
2132
|
+
import { Activity as Activity2 } from "lucide-react";
|
|
2133
|
+
import { jsx as jsx23, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
2134
|
+
function getProgressColor(percentage) {
|
|
2135
|
+
if (percentage === null) return "bg-blue-500";
|
|
2136
|
+
if (percentage >= 90) return "bg-red-500";
|
|
2137
|
+
if (percentage >= 75) return "bg-orange-500";
|
|
2138
|
+
return "bg-green-500";
|
|
2139
|
+
}
|
|
2140
|
+
__name(getProgressColor, "getProgressColor");
|
|
2141
|
+
function formatDate4(date) {
|
|
2142
|
+
if (!date) return "N/A";
|
|
2143
|
+
try {
|
|
2144
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
2145
|
+
month: "short",
|
|
2146
|
+
day: "numeric"
|
|
2147
|
+
}).format(new Date(date));
|
|
2148
|
+
} catch (error) {
|
|
2149
|
+
return "Invalid Date";
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
__name(formatDate4, "formatDate");
|
|
2153
|
+
function UsageSummaryCard({ meter, summary }) {
|
|
2154
|
+
const currentUsage = summary?.aggregatedValue ?? 0;
|
|
2155
|
+
const limit = meter.limit;
|
|
2156
|
+
const percentage = limit && limit > 0 ? currentUsage / limit * 100 : null;
|
|
2157
|
+
const progressColor = getProgressColor(percentage);
|
|
2158
|
+
const progressWidth = percentage !== null ? Math.min(percentage, 100) : 0;
|
|
2159
|
+
const displayName = meter.displayName || meter.eventName;
|
|
2160
|
+
const hasLimit = limit !== null && limit !== void 0;
|
|
2161
|
+
return /* @__PURE__ */ jsxs20(Card, { children: [
|
|
2162
|
+
/* @__PURE__ */ jsxs20(CardHeader, { className: "flex flex-row items-center gap-x-3 pb-3", children: [
|
|
2163
|
+
/* @__PURE__ */ jsx23("div", { className: "flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 text-blue-600", children: /* @__PURE__ */ jsx23(Activity2, { className: "h-5 w-5" }) }),
|
|
2164
|
+
/* @__PURE__ */ jsxs20("div", { className: "flex flex-col", children: [
|
|
2165
|
+
/* @__PURE__ */ jsx23("h3", { className: "font-semibold", children: displayName }),
|
|
2166
|
+
/* @__PURE__ */ jsx23("p", { className: "text-xs text-gray-500", children: meter.id })
|
|
2167
|
+
] })
|
|
2168
|
+
] }),
|
|
2169
|
+
/* @__PURE__ */ jsxs20(CardContent, { className: "flex flex-col gap-y-4", children: [
|
|
2170
|
+
/* @__PURE__ */ jsxs20("div", { children: [
|
|
2171
|
+
/* @__PURE__ */ jsx23("p", { className: "text-3xl font-bold", children: currentUsage.toLocaleString() }),
|
|
2172
|
+
hasLimit && /* @__PURE__ */ jsxs20("p", { className: "text-sm text-gray-500", children: [
|
|
2173
|
+
"of ",
|
|
2174
|
+
limit.toLocaleString(),
|
|
2175
|
+
" used"
|
|
2176
|
+
] })
|
|
2177
|
+
] }),
|
|
2178
|
+
hasLimit ? /* @__PURE__ */ jsxs20("div", { className: "flex flex-col gap-y-2", children: [
|
|
2179
|
+
/* @__PURE__ */ jsx23("div", { className: "h-2 w-full overflow-hidden rounded-full bg-gray-200", children: /* @__PURE__ */ jsx23("div", { className: `h-full transition-all ${progressColor}`, style: { width: `${progressWidth}%` } }) }),
|
|
2180
|
+
/* @__PURE__ */ jsxs20("p", { className: "text-sm text-gray-500", children: [
|
|
2181
|
+
percentage?.toFixed(1),
|
|
2182
|
+
"% used"
|
|
2183
|
+
] })
|
|
2184
|
+
] }) : /* @__PURE__ */ jsx23("p", { className: "text-sm text-gray-500", children: "No limit set" }),
|
|
2185
|
+
summary && summary.start && summary.end && /* @__PURE__ */ jsx23("div", { className: "border-t pt-3", children: /* @__PURE__ */ jsxs20("p", { className: "text-xs text-gray-500", children: [
|
|
2186
|
+
"Period: ",
|
|
2187
|
+
formatDate4(summary.start),
|
|
2188
|
+
" - ",
|
|
2189
|
+
formatDate4(summary.end)
|
|
2190
|
+
] }) })
|
|
2191
|
+
] })
|
|
2192
|
+
] });
|
|
2193
|
+
}
|
|
2194
|
+
__name(UsageSummaryCard, "UsageSummaryCard");
|
|
2195
|
+
|
|
2196
|
+
// src/features/billing/stripe-usage/components/widgets/UsageSummaryCards.tsx
|
|
2197
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
2198
|
+
function UsageSummaryCards({ meters, summaries }) {
|
|
2199
|
+
return /* @__PURE__ */ jsx24("div", { className: "grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3", children: meters.map((meter) => /* @__PURE__ */ jsx24(UsageSummaryCard, { meter, summary: summaries[meter.id] || null }, meter.id)) });
|
|
2200
|
+
}
|
|
2201
|
+
__name(UsageSummaryCards, "UsageSummaryCards");
|
|
2202
|
+
|
|
2203
|
+
// src/features/billing/stripe-usage/components/containers/UsageContainer.tsx
|
|
2204
|
+
import { jsx as jsx25, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
2205
|
+
function UsageContainer() {
|
|
2206
|
+
const [meters, setMeters] = useState13([]);
|
|
2207
|
+
const [summaries, setSummaries] = useState13({});
|
|
2208
|
+
const [loading, setLoading] = useState13(true);
|
|
2209
|
+
const [subscriptions, setSubscriptions] = useState13([]);
|
|
2210
|
+
useEffect7(() => {
|
|
2211
|
+
loadUsageData();
|
|
2212
|
+
}, []);
|
|
2213
|
+
const loadUsageData = /* @__PURE__ */ __name(async () => {
|
|
2214
|
+
setLoading(true);
|
|
2215
|
+
try {
|
|
2216
|
+
const fetchedSubscriptions = await StripeSubscriptionService.listSubscriptions();
|
|
2217
|
+
setSubscriptions(fetchedSubscriptions);
|
|
2218
|
+
const hasMeteredSubscriptions2 = fetchedSubscriptions.some((sub) => sub.price?.recurring?.usageType === "metered");
|
|
2219
|
+
if (!hasMeteredSubscriptions2) {
|
|
2220
|
+
setLoading(false);
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const fetchedMeters = await StripeUsageService.listMeters();
|
|
2224
|
+
setMeters(fetchedMeters);
|
|
2225
|
+
const summariesMap = {};
|
|
2226
|
+
const now = /* @__PURE__ */ new Date();
|
|
2227
|
+
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
2228
|
+
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
2229
|
+
for (const meter of fetchedMeters) {
|
|
2230
|
+
try {
|
|
2231
|
+
const meterSummaries = await StripeUsageService.getMeterSummaries({
|
|
2232
|
+
meterId: meter.id,
|
|
2233
|
+
startTime: startOfMonth,
|
|
2234
|
+
endTime: endOfMonth
|
|
2235
|
+
});
|
|
2236
|
+
summariesMap[meter.id] = meterSummaries.length > 0 ? meterSummaries[0] : null;
|
|
2237
|
+
} catch (error) {
|
|
2238
|
+
console.error(`[UsageContainer] Failed to load summaries for meter ${meter.id}:`, error);
|
|
2239
|
+
summariesMap[meter.id] = null;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
setSummaries(summariesMap);
|
|
2243
|
+
} catch (error) {
|
|
2244
|
+
console.error("[UsageContainer] Failed to load usage data:", error);
|
|
2245
|
+
} finally {
|
|
2246
|
+
setLoading(false);
|
|
2247
|
+
}
|
|
2248
|
+
}, "loadUsageData");
|
|
2249
|
+
const hasMeteredSubscriptions = subscriptions.some((sub) => sub.price?.recurring?.usageType === "metered");
|
|
2250
|
+
if (!loading && !hasMeteredSubscriptions) {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
if (loading) {
|
|
2254
|
+
return /* @__PURE__ */ jsx25("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx25("p", { className: "text-muted-foreground", children: "Loading usage data..." }) });
|
|
2255
|
+
}
|
|
2256
|
+
return /* @__PURE__ */ jsxs21("div", { className: "flex w-full flex-col gap-y-6", children: [
|
|
2257
|
+
/* @__PURE__ */ jsxs21("div", { className: "flex items-center gap-x-3", children: [
|
|
2258
|
+
/* @__PURE__ */ jsx25(Activity3, { className: "h-8 w-8" }),
|
|
2259
|
+
/* @__PURE__ */ jsx25("h1", { className: "text-3xl font-bold", children: "Usage Tracking" })
|
|
2260
|
+
] }),
|
|
2261
|
+
meters.length === 0 && /* @__PURE__ */ jsxs21("div", { className: "bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed border-gray-300 p-12", children: [
|
|
2262
|
+
/* @__PURE__ */ jsx25(Activity3, { className: "text-muted-foreground h-16 w-16" }),
|
|
2263
|
+
/* @__PURE__ */ jsxs21("div", { className: "text-center", children: [
|
|
2264
|
+
/* @__PURE__ */ jsx25("h3", { className: "mb-2 text-xl font-semibold", children: "No usage meters configured" }),
|
|
2265
|
+
/* @__PURE__ */ jsx25("p", { className: "text-muted-foreground", children: "Usage tracking will appear here when you have metered subscriptions with configured meters." })
|
|
2266
|
+
] })
|
|
2267
|
+
] }),
|
|
2268
|
+
meters.length > 0 && /* @__PURE__ */ jsx25(UsageSummaryCards, { meters, summaries })
|
|
2269
|
+
] });
|
|
2270
|
+
}
|
|
2271
|
+
__name(UsageContainer, "UsageContainer");
|
|
2272
|
+
|
|
2273
|
+
// src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx
|
|
2274
|
+
import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
|
|
2275
|
+
function formatDateTime(date) {
|
|
2276
|
+
if (!date) return "N/A";
|
|
2277
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
2278
|
+
try {
|
|
2279
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
2280
|
+
month: "short",
|
|
2281
|
+
day: "numeric",
|
|
2282
|
+
year: "numeric",
|
|
2283
|
+
hour: "numeric",
|
|
2284
|
+
minute: "2-digit"
|
|
2285
|
+
}).format(dateObj);
|
|
2286
|
+
} catch (error) {
|
|
2287
|
+
return "Invalid Date";
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
__name(formatDateTime, "formatDateTime");
|
|
2291
|
+
function UsageHistoryTable({ usageRecords }) {
|
|
2292
|
+
if (usageRecords.length === 0) {
|
|
2293
|
+
return /* @__PURE__ */ jsx26("div", { className: "rounded-lg border p-8 text-center", children: /* @__PURE__ */ jsx26("p", { className: "text-muted-foreground", children: "No usage history available." }) });
|
|
2294
|
+
}
|
|
2295
|
+
return /* @__PURE__ */ jsxs22("div", { className: "flex w-full flex-col gap-y-4", children: [
|
|
2296
|
+
/* @__PURE__ */ jsx26("h2", { className: "text-xl font-semibold", children: "Usage History" }),
|
|
2297
|
+
/* @__PURE__ */ jsx26("div", { className: "overflow-hidden rounded-lg border", children: /* @__PURE__ */ jsxs22(Table, { children: [
|
|
2298
|
+
/* @__PURE__ */ jsx26(TableHeader, { className: "bg-muted", children: /* @__PURE__ */ jsxs22(TableRow, { children: [
|
|
2299
|
+
/* @__PURE__ */ jsx26(TableHead, { children: "Date & Time" }),
|
|
2300
|
+
/* @__PURE__ */ jsx26(TableHead, { children: "Meter Event" }),
|
|
2301
|
+
/* @__PURE__ */ jsx26(TableHead, { className: "text-right", children: "Quantity" }),
|
|
2302
|
+
/* @__PURE__ */ jsx26(TableHead, { children: "Event ID" })
|
|
2303
|
+
] }) }),
|
|
2304
|
+
/* @__PURE__ */ jsx26(TableBody, { children: usageRecords.map((record) => {
|
|
2305
|
+
const dateTime = formatDateTime(record.timestamp);
|
|
2306
|
+
const quantity = record.quantity.toLocaleString();
|
|
2307
|
+
return /* @__PURE__ */ jsxs22(TableRow, { children: [
|
|
2308
|
+
/* @__PURE__ */ jsx26(TableCell, { className: "font-medium", children: dateTime }),
|
|
2309
|
+
/* @__PURE__ */ jsx26(TableCell, { className: "text-muted-foreground", children: record.meterEventName }),
|
|
2310
|
+
/* @__PURE__ */ jsx26(TableCell, { className: "text-right font-medium", children: quantity }),
|
|
2311
|
+
/* @__PURE__ */ jsx26(TableCell, { className: "text-muted-foreground text-sm font-mono", children: record.stripeEventId })
|
|
2312
|
+
] }, record.id);
|
|
2313
|
+
}) })
|
|
2314
|
+
] }) })
|
|
2315
|
+
] });
|
|
2316
|
+
}
|
|
2317
|
+
__name(UsageHistoryTable, "UsageHistoryTable");
|
|
2318
|
+
|
|
2319
|
+
// src/features/billing/components/modals/BillingDetailModal.tsx
|
|
2320
|
+
import { jsx as jsx27, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
2321
|
+
function BillingDetailModal({
|
|
2322
|
+
open,
|
|
2323
|
+
onOpenChange,
|
|
2324
|
+
title,
|
|
2325
|
+
children,
|
|
2326
|
+
className
|
|
2327
|
+
}) {
|
|
2328
|
+
return /* @__PURE__ */ jsx27(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs23(DialogContent, { className: className ?? "max-w-4xl max-h-[90vh] overflow-y-auto", children: [
|
|
2329
|
+
/* @__PURE__ */ jsx27(DialogHeader, { children: /* @__PURE__ */ jsx27(DialogTitle, { children: title }) }),
|
|
2330
|
+
children
|
|
2331
|
+
] }) });
|
|
2332
|
+
}
|
|
2333
|
+
__name(BillingDetailModal, "BillingDetailModal");
|
|
2334
|
+
|
|
2335
|
+
// src/features/billing/components/widgets/BillingAlertBanner.tsx
|
|
2336
|
+
import { AlertCircle } from "lucide-react";
|
|
2337
|
+
import { jsx as jsx28, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2338
|
+
function BillingAlertBanner({ subscription, onUpdatePayment, onAddPayment }) {
|
|
2339
|
+
if (subscription.status === "past_due" /* PAST_DUE */) {
|
|
2340
|
+
return /* @__PURE__ */ jsxs24("div", { className: "bg-red-50 border border-red-200 rounded-lg p-4 flex items-start gap-x-3", children: [
|
|
2341
|
+
/* @__PURE__ */ jsx28(AlertCircle, { className: "h-5 w-5 text-red-600 mt-0.5" }),
|
|
2342
|
+
/* @__PURE__ */ jsxs24("div", { className: "flex-1", children: [
|
|
2343
|
+
/* @__PURE__ */ jsx28("h3", { className: "font-semibold text-red-900", children: "Payment Failed" }),
|
|
2344
|
+
/* @__PURE__ */ jsx28("p", { className: "text-sm text-red-700 mt-1", children: "Your last payment failed. Please update your payment method to avoid service interruption." })
|
|
2345
|
+
] }),
|
|
2346
|
+
onUpdatePayment && /* @__PURE__ */ jsx28(Button, { variant: "outline", size: "sm", onClick: onUpdatePayment, className: "border-red-300 text-red-700", children: "Update Payment Method" })
|
|
2347
|
+
] });
|
|
2348
|
+
}
|
|
2349
|
+
if (subscription.status === "trialing" /* TRIALING */ && subscription.trialEnd) {
|
|
2350
|
+
const trialEnd = new Date(subscription.trialEnd);
|
|
2351
|
+
const now = /* @__PURE__ */ new Date();
|
|
2352
|
+
const daysRemaining = Math.ceil((trialEnd.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
|
|
2353
|
+
if (daysRemaining <= 7) {
|
|
2354
|
+
return /* @__PURE__ */ jsxs24("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4 flex items-start gap-x-3", children: [
|
|
2355
|
+
/* @__PURE__ */ jsx28(AlertCircle, { className: "h-5 w-5 text-yellow-600 mt-0.5" }),
|
|
2356
|
+
/* @__PURE__ */ jsxs24("div", { className: "flex-1", children: [
|
|
2357
|
+
/* @__PURE__ */ jsx28("h3", { className: "font-semibold text-yellow-900", children: "Trial Ending Soon" }),
|
|
2358
|
+
/* @__PURE__ */ jsxs24("p", { className: "text-sm text-yellow-700 mt-1", children: [
|
|
2359
|
+
"Your trial ends in ",
|
|
2360
|
+
daysRemaining,
|
|
2361
|
+
" ",
|
|
2362
|
+
daysRemaining === 1 ? "day" : "days",
|
|
2363
|
+
". Add a payment method to continue your subscription."
|
|
2364
|
+
] })
|
|
2365
|
+
] }),
|
|
2366
|
+
onAddPayment && /* @__PURE__ */ jsx28(Button, { variant: "outline", size: "sm", onClick: onAddPayment, className: "border-yellow-300 text-yellow-700", children: "Add Payment Method" })
|
|
2367
|
+
] });
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return null;
|
|
2371
|
+
}
|
|
2372
|
+
__name(BillingAlertBanner, "BillingAlertBanner");
|
|
2373
|
+
|
|
2374
|
+
// src/features/billing/components/containers/BillingDashboardContainer.tsx
|
|
2375
|
+
import { Fragment as Fragment6, jsx as jsx29, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2376
|
+
function BillingDashboardContainer() {
|
|
2377
|
+
const [data, setData] = useState14({
|
|
2378
|
+
customer: null,
|
|
2379
|
+
subscriptions: [],
|
|
2380
|
+
paymentMethods: [],
|
|
2381
|
+
invoices: [],
|
|
2382
|
+
meters: [],
|
|
2383
|
+
meterSummaries: {}
|
|
2384
|
+
});
|
|
2385
|
+
const [loading, setLoading] = useState14({
|
|
2386
|
+
customer: true,
|
|
2387
|
+
subscriptions: true,
|
|
2388
|
+
paymentMethods: true,
|
|
2389
|
+
invoices: true,
|
|
2390
|
+
usage: true
|
|
2391
|
+
});
|
|
2392
|
+
const [errors, setErrors] = useState14({
|
|
2393
|
+
customer: null,
|
|
2394
|
+
subscriptions: null,
|
|
2395
|
+
paymentMethods: null,
|
|
2396
|
+
invoices: null,
|
|
2397
|
+
usage: null
|
|
2398
|
+
});
|
|
2399
|
+
const [activeModal, setActiveModal] = useState14(null);
|
|
2400
|
+
const [noCustomerExists, setNoCustomerExists] = useState14(false);
|
|
2401
|
+
const [creatingCustomer, setCreatingCustomer] = useState14(false);
|
|
2402
|
+
const hasMeteredSubscriptions = useCallback2(() => {
|
|
2403
|
+
return data.subscriptions.some((sub) => sub.price?.recurring?.usageType === "metered");
|
|
2404
|
+
}, [data.subscriptions]);
|
|
2405
|
+
const fetchAllData = useCallback2(async () => {
|
|
2406
|
+
setNoCustomerExists(false);
|
|
2407
|
+
let customer = null;
|
|
2408
|
+
try {
|
|
2409
|
+
customer = await StripeCustomerService.getCustomer();
|
|
2410
|
+
setData((prev) => ({ ...prev, customer }));
|
|
2411
|
+
setErrors((prev) => ({ ...prev, customer: null }));
|
|
2412
|
+
setNoCustomerExists(false);
|
|
2413
|
+
} catch (error) {
|
|
2414
|
+
console.error("[BillingDashboard] Failed to load customer:", error);
|
|
2415
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2416
|
+
if (errorMessage.includes("Not Found") || errorMessage.includes("not found")) {
|
|
2417
|
+
setNoCustomerExists(true);
|
|
2418
|
+
setLoading({
|
|
2419
|
+
customer: false,
|
|
2420
|
+
subscriptions: false,
|
|
2421
|
+
paymentMethods: false,
|
|
2422
|
+
invoices: false,
|
|
2423
|
+
usage: false
|
|
2424
|
+
});
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
setErrors((prev) => ({ ...prev, customer: "Failed to load billing account" }));
|
|
2428
|
+
} finally {
|
|
2429
|
+
setLoading((prev) => ({ ...prev, customer: false }));
|
|
2430
|
+
}
|
|
2431
|
+
if (customer) {
|
|
2432
|
+
const fetchSubscriptions = /* @__PURE__ */ __name(async () => {
|
|
2433
|
+
try {
|
|
2434
|
+
const subscriptions2 = await StripeSubscriptionService.listSubscriptions();
|
|
2435
|
+
setData((prev) => ({ ...prev, subscriptions: subscriptions2 }));
|
|
2436
|
+
setErrors((prev) => ({ ...prev, subscriptions: null }));
|
|
2437
|
+
return subscriptions2;
|
|
2438
|
+
} catch (error) {
|
|
2439
|
+
console.error("[BillingDashboard] Failed to load subscriptions:", error);
|
|
2440
|
+
setErrors((prev) => ({ ...prev, subscriptions: "Failed to load subscriptions" }));
|
|
2441
|
+
return [];
|
|
2442
|
+
} finally {
|
|
2443
|
+
setLoading((prev) => ({ ...prev, subscriptions: false }));
|
|
2444
|
+
}
|
|
2445
|
+
}, "fetchSubscriptions");
|
|
2446
|
+
const fetchPaymentMethods = /* @__PURE__ */ __name(async () => {
|
|
2447
|
+
try {
|
|
2448
|
+
const paymentMethods = await StripeCustomerService.listPaymentMethods();
|
|
2449
|
+
setData((prev) => ({ ...prev, paymentMethods }));
|
|
2450
|
+
setErrors((prev) => ({ ...prev, paymentMethods: null }));
|
|
2451
|
+
} catch (error) {
|
|
2452
|
+
console.error("[BillingDashboard] Failed to load payment methods:", error);
|
|
2453
|
+
setErrors((prev) => ({ ...prev, paymentMethods: "Failed to load payment methods" }));
|
|
2454
|
+
} finally {
|
|
2455
|
+
setLoading((prev) => ({ ...prev, paymentMethods: false }));
|
|
2456
|
+
}
|
|
2457
|
+
}, "fetchPaymentMethods");
|
|
2458
|
+
const fetchInvoices = /* @__PURE__ */ __name(async () => {
|
|
2459
|
+
try {
|
|
2460
|
+
const invoices = await StripeInvoiceService.listInvoices();
|
|
2461
|
+
setData((prev) => ({ ...prev, invoices }));
|
|
2462
|
+
setErrors((prev) => ({ ...prev, invoices: null }));
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
console.error("[BillingDashboard] Failed to load invoices:", error);
|
|
2465
|
+
setErrors((prev) => ({ ...prev, invoices: "Failed to load invoices" }));
|
|
2466
|
+
} finally {
|
|
2467
|
+
setLoading((prev) => ({ ...prev, invoices: false }));
|
|
2468
|
+
}
|
|
2469
|
+
}, "fetchInvoices");
|
|
2470
|
+
const [subscriptions] = await Promise.all([fetchSubscriptions(), fetchPaymentMethods(), fetchInvoices()]);
|
|
2471
|
+
const hasMetered = subscriptions.some(
|
|
2472
|
+
(sub) => sub.price?.recurring?.usageType === "metered"
|
|
2473
|
+
);
|
|
2474
|
+
if (hasMetered) {
|
|
2475
|
+
await fetchUsageData();
|
|
2476
|
+
} else {
|
|
2477
|
+
setLoading((prev) => ({ ...prev, usage: false }));
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
}, []);
|
|
2481
|
+
const handleCreateCustomer = /* @__PURE__ */ __name(async () => {
|
|
2482
|
+
setCreatingCustomer(true);
|
|
2483
|
+
try {
|
|
2484
|
+
await StripeCustomerService.createCustomer();
|
|
2485
|
+
setNoCustomerExists(false);
|
|
2486
|
+
await fetchAllData();
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
console.error("[BillingDashboard] Failed to create customer:", error);
|
|
2489
|
+
setErrors((prev) => ({ ...prev, customer: "Failed to set up billing" }));
|
|
2490
|
+
} finally {
|
|
2491
|
+
setCreatingCustomer(false);
|
|
2492
|
+
}
|
|
2493
|
+
}, "handleCreateCustomer");
|
|
2494
|
+
const fetchUsageData = /* @__PURE__ */ __name(async () => {
|
|
2495
|
+
try {
|
|
2496
|
+
const meters = await StripeUsageService.listMeters();
|
|
2497
|
+
setData((prev) => ({ ...prev, meters }));
|
|
2498
|
+
const summariesMap = {};
|
|
2499
|
+
const now = /* @__PURE__ */ new Date();
|
|
2500
|
+
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
2501
|
+
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
2502
|
+
for (const meter of meters) {
|
|
2503
|
+
try {
|
|
2504
|
+
const meterSummaries = await StripeUsageService.getMeterSummaries({
|
|
2505
|
+
meterId: meter.id,
|
|
2506
|
+
startTime: startOfMonth,
|
|
2507
|
+
endTime: endOfMonth
|
|
2508
|
+
});
|
|
2509
|
+
summariesMap[meter.id] = meterSummaries.length > 0 ? meterSummaries[0] : null;
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
console.error(`[BillingDashboard] Failed to load summaries for meter ${meter.id}:`, error);
|
|
2512
|
+
summariesMap[meter.id] = null;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
setData((prev) => ({ ...prev, meterSummaries: summariesMap }));
|
|
2516
|
+
setErrors((prev) => ({ ...prev, usage: null }));
|
|
2517
|
+
} catch (error) {
|
|
2518
|
+
console.error("[BillingDashboard] Failed to load usage data:", error);
|
|
2519
|
+
setErrors((prev) => ({ ...prev, usage: "Failed to load usage data" }));
|
|
2520
|
+
} finally {
|
|
2521
|
+
setLoading((prev) => ({ ...prev, usage: false }));
|
|
2522
|
+
}
|
|
2523
|
+
}, "fetchUsageData");
|
|
2524
|
+
const refreshData = useCallback2(async () => {
|
|
2525
|
+
setLoading({
|
|
2526
|
+
customer: true,
|
|
2527
|
+
subscriptions: true,
|
|
2528
|
+
paymentMethods: true,
|
|
2529
|
+
invoices: true,
|
|
2530
|
+
usage: true
|
|
2531
|
+
});
|
|
2532
|
+
await fetchAllData();
|
|
2533
|
+
}, [fetchAllData]);
|
|
2534
|
+
useEffect8(() => {
|
|
2535
|
+
fetchAllData();
|
|
2536
|
+
}, [fetchAllData]);
|
|
2537
|
+
const criticalSubscriptions = data.subscriptions.filter(
|
|
2538
|
+
(sub) => sub.status === "past_due" /* PAST_DUE */ || sub.status === "trialing" /* TRIALING */ && sub.trialEnd && new Date(sub.trialEnd).getTime() - (/* @__PURE__ */ new Date()).getTime() <= 7 * 24 * 60 * 60 * 1e3
|
|
2539
|
+
);
|
|
2540
|
+
const handleModalClose = /* @__PURE__ */ __name((open) => {
|
|
2541
|
+
if (!open) {
|
|
2542
|
+
setActiveModal(null);
|
|
2543
|
+
refreshData();
|
|
2544
|
+
}
|
|
2545
|
+
}, "handleModalClose");
|
|
2546
|
+
const getModalTitle = /* @__PURE__ */ __name((type) => {
|
|
2547
|
+
switch (type) {
|
|
2548
|
+
case "subscriptions":
|
|
2549
|
+
return "Manage Subscriptions";
|
|
2550
|
+
case "payment-methods":
|
|
2551
|
+
return "Payment Methods";
|
|
2552
|
+
case "invoices":
|
|
2553
|
+
return "Invoice History";
|
|
2554
|
+
case "usage":
|
|
2555
|
+
return "Usage Tracking";
|
|
2556
|
+
default:
|
|
2557
|
+
return "";
|
|
2558
|
+
}
|
|
2559
|
+
}, "getModalTitle");
|
|
2560
|
+
const isInitialLoading = loading.customer && !noCustomerExists && !data.customer;
|
|
2561
|
+
return /* @__PURE__ */ jsxs25("div", { className: "flex w-full flex-col gap-y-6", children: [
|
|
2562
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-x-3", children: [
|
|
2563
|
+
/* @__PURE__ */ jsx29(Wallet2, { className: "h-8 w-8" }),
|
|
2564
|
+
/* @__PURE__ */ jsx29("h1", { className: "text-3xl font-bold", children: "Billing" })
|
|
2565
|
+
] }),
|
|
2566
|
+
isInitialLoading && /* @__PURE__ */ jsx29(Card, { children: /* @__PURE__ */ jsx29(CardContent, { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ jsx29(Loader23, { className: "h-8 w-8 animate-spin text-muted-foreground" }) }) }),
|
|
2567
|
+
noCustomerExists && !isInitialLoading && /* @__PURE__ */ jsxs25(Card, { children: [
|
|
2568
|
+
/* @__PURE__ */ jsxs25(CardHeader, { className: "text-center", children: [
|
|
2569
|
+
/* @__PURE__ */ jsx29("div", { className: "mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-primary/10", children: /* @__PURE__ */ jsx29(CreditCard4, { className: "h-8 w-8 text-primary" }) }),
|
|
2570
|
+
/* @__PURE__ */ jsx29(CardTitle, { children: "Set Up Billing" }),
|
|
2571
|
+
/* @__PURE__ */ jsx29(CardDescription, { children: "Your company doesn't have a billing account yet. Set one up to manage subscriptions, payment methods, and view invoices." })
|
|
2572
|
+
] }),
|
|
2573
|
+
/* @__PURE__ */ jsx29(CardContent, { className: "flex justify-center pb-8", children: /* @__PURE__ */ jsx29(Button, { onClick: handleCreateCustomer, disabled: creatingCustomer, size: "lg", children: creatingCustomer ? /* @__PURE__ */ jsxs25(Fragment6, { children: [
|
|
2574
|
+
/* @__PURE__ */ jsx29(Loader23, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
2575
|
+
"Setting up..."
|
|
2576
|
+
] }) : "Set Up Billing Account" }) }),
|
|
2577
|
+
errors.customer && /* @__PURE__ */ jsx29(CardContent, { className: "pt-0", children: /* @__PURE__ */ jsx29("p", { className: "text-center text-sm text-destructive", children: errors.customer }) })
|
|
2578
|
+
] }),
|
|
2579
|
+
!noCustomerExists && !isInitialLoading && /* @__PURE__ */ jsxs25(Fragment6, { children: [
|
|
2580
|
+
criticalSubscriptions.map((subscription) => /* @__PURE__ */ jsx29(
|
|
2581
|
+
BillingAlertBanner,
|
|
2582
|
+
{
|
|
2583
|
+
subscription,
|
|
2584
|
+
onUpdatePayment: () => setActiveModal("payment-methods"),
|
|
2585
|
+
onAddPayment: () => setActiveModal("payment-methods")
|
|
2586
|
+
},
|
|
2587
|
+
subscription.id
|
|
2588
|
+
)),
|
|
2589
|
+
/* @__PURE__ */ jsxs25("div", { className: "grid gap-4 md:grid-cols-2", children: [
|
|
2590
|
+
/* @__PURE__ */ jsx29(
|
|
2591
|
+
SubscriptionSummaryCard,
|
|
2592
|
+
{
|
|
2593
|
+
subscriptions: data.subscriptions,
|
|
2594
|
+
loading: loading.subscriptions,
|
|
2595
|
+
error: errors.subscriptions || void 0,
|
|
2596
|
+
onManageClick: () => setActiveModal("subscriptions")
|
|
2597
|
+
}
|
|
2598
|
+
),
|
|
2599
|
+
/* @__PURE__ */ jsx29(
|
|
2600
|
+
PaymentMethodSummaryCard,
|
|
2601
|
+
{
|
|
2602
|
+
paymentMethods: data.paymentMethods,
|
|
2603
|
+
defaultPaymentMethodId: data.customer?.defaultPaymentMethodId,
|
|
2604
|
+
loading: loading.paymentMethods,
|
|
2605
|
+
error: errors.paymentMethods || void 0,
|
|
2606
|
+
onManageClick: () => setActiveModal("payment-methods")
|
|
2607
|
+
}
|
|
2608
|
+
),
|
|
2609
|
+
/* @__PURE__ */ jsx29(
|
|
2610
|
+
CustomerInfoCard,
|
|
2611
|
+
{
|
|
2612
|
+
customer: data.customer,
|
|
2613
|
+
loading: loading.customer,
|
|
2614
|
+
error: errors.customer || void 0
|
|
2615
|
+
}
|
|
2616
|
+
),
|
|
2617
|
+
/* @__PURE__ */ jsx29(
|
|
2618
|
+
InvoicesSummaryCard,
|
|
2619
|
+
{
|
|
2620
|
+
invoices: data.invoices,
|
|
2621
|
+
loading: loading.invoices,
|
|
2622
|
+
error: errors.invoices || void 0,
|
|
2623
|
+
onViewAllClick: () => setActiveModal("invoices")
|
|
2624
|
+
}
|
|
2625
|
+
),
|
|
2626
|
+
hasMeteredSubscriptions() && /* @__PURE__ */ jsx29(
|
|
2627
|
+
BillingUsageSummaryCard,
|
|
2628
|
+
{
|
|
2629
|
+
meters: data.meters,
|
|
2630
|
+
summaries: data.meterSummaries,
|
|
2631
|
+
loading: loading.usage,
|
|
2632
|
+
error: errors.usage || void 0,
|
|
2633
|
+
onViewDetailsClick: () => setActiveModal("usage")
|
|
2634
|
+
}
|
|
2635
|
+
)
|
|
2636
|
+
] }),
|
|
2637
|
+
/* @__PURE__ */ jsx29(
|
|
2638
|
+
BillingDetailModal,
|
|
2639
|
+
{
|
|
2640
|
+
open: activeModal === "subscriptions",
|
|
2641
|
+
onOpenChange: handleModalClose,
|
|
2642
|
+
title: getModalTitle("subscriptions"),
|
|
2643
|
+
children: /* @__PURE__ */ jsx29(SubscriptionsContainer, {})
|
|
2644
|
+
}
|
|
2645
|
+
),
|
|
2646
|
+
/* @__PURE__ */ jsx29(
|
|
2647
|
+
BillingDetailModal,
|
|
2648
|
+
{
|
|
2649
|
+
open: activeModal === "payment-methods",
|
|
2650
|
+
onOpenChange: handleModalClose,
|
|
2651
|
+
title: getModalTitle("payment-methods"),
|
|
2652
|
+
children: /* @__PURE__ */ jsx29(PaymentMethodsContainer, {})
|
|
2653
|
+
}
|
|
2654
|
+
),
|
|
2655
|
+
/* @__PURE__ */ jsx29(
|
|
2656
|
+
BillingDetailModal,
|
|
2657
|
+
{
|
|
2658
|
+
open: activeModal === "invoices",
|
|
2659
|
+
onOpenChange: handleModalClose,
|
|
2660
|
+
title: getModalTitle("invoices"),
|
|
2661
|
+
children: /* @__PURE__ */ jsx29(InvoicesContainer, {})
|
|
2662
|
+
}
|
|
2663
|
+
),
|
|
2664
|
+
/* @__PURE__ */ jsx29(
|
|
2665
|
+
BillingDetailModal,
|
|
2666
|
+
{
|
|
2667
|
+
open: activeModal === "usage",
|
|
2668
|
+
onOpenChange: handleModalClose,
|
|
2669
|
+
title: getModalTitle("usage"),
|
|
2670
|
+
children: /* @__PURE__ */ jsx29(UsageContainer, {})
|
|
2671
|
+
}
|
|
2672
|
+
)
|
|
2673
|
+
] })
|
|
2674
|
+
] });
|
|
2675
|
+
}
|
|
2676
|
+
__name(BillingDashboardContainer, "BillingDashboardContainer");
|
|
2677
|
+
|
|
2678
|
+
// src/features/billing/components/providers/StripeProvider.tsx
|
|
2679
|
+
import { Elements } from "@stripe/react-stripe-js";
|
|
2680
|
+
import { loadStripe } from "@stripe/stripe-js";
|
|
2681
|
+
import { useMemo } from "react";
|
|
2682
|
+
import { Fragment as Fragment7, jsx as jsx30 } from "react/jsx-runtime";
|
|
2683
|
+
var stripePromiseCache = null;
|
|
2684
|
+
function getStripePromise(publishableKey) {
|
|
2685
|
+
if (!publishableKey) {
|
|
2686
|
+
return Promise.resolve(null);
|
|
2687
|
+
}
|
|
2688
|
+
if (stripePromiseCache?.key === publishableKey) {
|
|
2689
|
+
return stripePromiseCache.promise;
|
|
2690
|
+
}
|
|
2691
|
+
const promise = loadStripe(publishableKey);
|
|
2692
|
+
stripePromiseCache = { key: publishableKey, promise };
|
|
2693
|
+
return promise;
|
|
2694
|
+
}
|
|
2695
|
+
__name(getStripePromise, "getStripePromise");
|
|
2696
|
+
function StripeProvider({ children }) {
|
|
2697
|
+
const publishableKey = getStripePublishableKey();
|
|
2698
|
+
const stripePromise = useMemo(() => getStripePromise(publishableKey), [publishableKey]);
|
|
2699
|
+
const options = useMemo(() => ({}), []);
|
|
2700
|
+
if (!publishableKey) {
|
|
2701
|
+
return /* @__PURE__ */ jsx30(Fragment7, { children });
|
|
2702
|
+
}
|
|
2703
|
+
return /* @__PURE__ */ jsx30(Elements, { stripe: stripePromise, options, children });
|
|
2704
|
+
}
|
|
2705
|
+
__name(StripeProvider, "StripeProvider");
|
|
2706
|
+
function isStripeConfigured() {
|
|
2707
|
+
return !!getStripePublishableKey();
|
|
2708
|
+
}
|
|
2709
|
+
__name(isStripeConfigured, "isStripeConfigured");
|
|
2710
|
+
|
|
2711
|
+
// src/features/billing/stripe-price/components/forms/PriceEditor.tsx
|
|
2712
|
+
import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
|
|
2713
|
+
import { AlertCircle as AlertCircle2, PlusIcon, XIcon } from "lucide-react";
|
|
2714
|
+
import { useState as useState15 } from "react";
|
|
2715
|
+
import { useForm as useForm2 } from "react-hook-form";
|
|
2716
|
+
import { v4 as v43 } from "uuid";
|
|
2717
|
+
import { z as z2 } from "zod";
|
|
2718
|
+
import { jsx as jsx31, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2719
|
+
function PriceEditor({ productId, price, open, onOpenChange, onSuccess }) {
|
|
2720
|
+
const [isSubmitting, setIsSubmitting] = useState15(false);
|
|
2721
|
+
const formSchema2 = z2.object({
|
|
2722
|
+
unitAmount: z2.preprocess(
|
|
2723
|
+
(val) => typeof val === "string" ? parseFloat(val) : val,
|
|
2724
|
+
z2.number().min(0, { message: "Amount must be 0 or greater" })
|
|
2725
|
+
),
|
|
2726
|
+
currency: z2.string().min(1, { message: "Currency is required" }),
|
|
2727
|
+
interval: z2.enum(["one_time", "day", "week", "month", "year"]),
|
|
2728
|
+
intervalCount: z2.preprocess(
|
|
2729
|
+
(val) => val === "" || val === void 0 ? void 0 : typeof val === "string" ? parseInt(val, 10) : val,
|
|
2730
|
+
z2.number().min(1).optional()
|
|
2731
|
+
),
|
|
2732
|
+
usageType: z2.enum(["licensed", "metered"]).optional(),
|
|
2733
|
+
nickname: z2.string().optional(),
|
|
2734
|
+
active: z2.boolean(),
|
|
2735
|
+
description: z2.string().optional(),
|
|
2736
|
+
features: z2.array(z2.string())
|
|
2737
|
+
});
|
|
2738
|
+
const isEditMode = !!price;
|
|
2739
|
+
const defaultUnitAmount = price?.unitAmount ? price.unitAmount / 100 : 0;
|
|
2740
|
+
const form = useForm2({
|
|
2741
|
+
resolver: zodResolver2(formSchema2),
|
|
2742
|
+
defaultValues: {
|
|
2743
|
+
unitAmount: defaultUnitAmount,
|
|
2744
|
+
currency: price?.currency || "usd",
|
|
2745
|
+
interval: price?.priceType === "one_time" ? "one_time" : price?.recurring?.interval || "month",
|
|
2746
|
+
intervalCount: price?.recurring?.intervalCount || 1,
|
|
2747
|
+
usageType: price?.recurring?.usageType || "licensed",
|
|
2748
|
+
nickname: price?.nickname || "",
|
|
2749
|
+
active: price?.active ?? true,
|
|
2750
|
+
description: price?.description || "",
|
|
2751
|
+
features: price?.features || []
|
|
2752
|
+
}
|
|
2753
|
+
});
|
|
2754
|
+
const watchInterval = form.watch("interval");
|
|
2755
|
+
const isRecurring = watchInterval !== "one_time";
|
|
2756
|
+
const onSubmit = /* @__PURE__ */ __name(async (values) => {
|
|
2757
|
+
setIsSubmitting(true);
|
|
2758
|
+
try {
|
|
2759
|
+
const unitAmountInCents = Math.round(values.unitAmount * 100);
|
|
2760
|
+
if (isEditMode) {
|
|
2761
|
+
await StripePriceService.updatePrice({
|
|
2762
|
+
id: price.id,
|
|
2763
|
+
nickname: values.nickname || void 0,
|
|
2764
|
+
description: values.description || void 0,
|
|
2765
|
+
features: values.features.filter((f) => f.trim()) || void 0
|
|
2766
|
+
});
|
|
2767
|
+
} else {
|
|
2768
|
+
const createInput = {
|
|
2769
|
+
id: v43(),
|
|
2770
|
+
productId,
|
|
2771
|
+
currency: values.currency,
|
|
2772
|
+
unitAmount: unitAmountInCents
|
|
2773
|
+
};
|
|
2774
|
+
if (isRecurring) {
|
|
2775
|
+
createInput.recurring = {
|
|
2776
|
+
interval: values.interval,
|
|
2777
|
+
intervalCount: values.intervalCount || 1,
|
|
2778
|
+
usageType: values.usageType || "licensed"
|
|
2779
|
+
};
|
|
2780
|
+
}
|
|
2781
|
+
if (values.nickname) {
|
|
2782
|
+
createInput.nickname = values.nickname;
|
|
2783
|
+
}
|
|
2784
|
+
if (values.description) {
|
|
2785
|
+
createInput.description = values.description;
|
|
2786
|
+
}
|
|
2787
|
+
const filteredFeatures = values.features.filter((f) => f.trim());
|
|
2788
|
+
if (filteredFeatures.length > 0) {
|
|
2789
|
+
createInput.features = filteredFeatures;
|
|
2790
|
+
}
|
|
2791
|
+
await StripePriceService.createPrice(createInput);
|
|
2792
|
+
}
|
|
2793
|
+
onSuccess();
|
|
2794
|
+
onOpenChange(false);
|
|
2795
|
+
} catch (error) {
|
|
2796
|
+
console.error("[PriceEditor] Failed to save price:", error);
|
|
2797
|
+
} finally {
|
|
2798
|
+
setIsSubmitting(false);
|
|
2799
|
+
}
|
|
2800
|
+
}, "onSubmit");
|
|
2801
|
+
const currencyOptions = [
|
|
2802
|
+
{ id: "usd", text: "USD ($)" },
|
|
2803
|
+
{ id: "eur", text: "EUR (\u20AC)" },
|
|
2804
|
+
{ id: "gbp", text: "GBP (\xA3)" }
|
|
2805
|
+
];
|
|
2806
|
+
const intervalOptions = [
|
|
2807
|
+
{ id: "one_time", text: "One-time" },
|
|
2808
|
+
{ id: "day", text: "Daily" },
|
|
2809
|
+
{ id: "week", text: "Weekly" },
|
|
2810
|
+
{ id: "month", text: "Monthly" },
|
|
2811
|
+
{ id: "year", text: "Yearly" }
|
|
2812
|
+
];
|
|
2813
|
+
const usageTypeOptions = [
|
|
2814
|
+
{ id: "licensed", text: "Licensed (per unit)" },
|
|
2815
|
+
{ id: "metered", text: "Metered (usage-based)" }
|
|
2816
|
+
];
|
|
2817
|
+
return /* @__PURE__ */ jsx31(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs26(DialogContent, { className: "max-w-2xl", children: [
|
|
2818
|
+
/* @__PURE__ */ jsxs26(DialogHeader, { children: [
|
|
2819
|
+
/* @__PURE__ */ jsx31(DialogTitle, { children: isEditMode ? "Edit Price" : "Create Price" }),
|
|
2820
|
+
/* @__PURE__ */ jsx31(DialogDescription, { children: isEditMode ? "Update the price details. Note: Only nickname and active status can be changed." : "Create a new price for this product" })
|
|
2821
|
+
] }),
|
|
2822
|
+
isEditMode && /* @__PURE__ */ jsxs26("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4 flex gap-x-3", children: [
|
|
2823
|
+
/* @__PURE__ */ jsx31(AlertCircle2, { className: "h-5 w-5 text-blue-600 flex-shrink-0 mt-0.5" }),
|
|
2824
|
+
/* @__PURE__ */ jsxs26("div", { className: "text-sm text-blue-800", children: [
|
|
2825
|
+
/* @__PURE__ */ jsx31("p", { className: "font-semibold mb-1", children: "Stripe Price Immutability" }),
|
|
2826
|
+
/* @__PURE__ */ jsx31("p", { children: "Due to Stripe's architecture, only the nickname and active status can be modified after creation. To change amount, currency, or billing interval, create a new price." })
|
|
2827
|
+
] })
|
|
2828
|
+
] }),
|
|
2829
|
+
/* @__PURE__ */ jsx31(Form, { ...form, children: /* @__PURE__ */ jsxs26("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-col gap-y-4", children: [
|
|
2830
|
+
/* @__PURE__ */ jsxs26("div", { className: "grid grid-cols-2 gap-x-4", children: [
|
|
2831
|
+
/* @__PURE__ */ jsx31(
|
|
2832
|
+
FormInput,
|
|
2833
|
+
{
|
|
2834
|
+
form,
|
|
2835
|
+
id: "unitAmount",
|
|
2836
|
+
name: "Amount (in dollars)",
|
|
2837
|
+
placeholder: "9.99",
|
|
2838
|
+
disabled: isEditMode,
|
|
2839
|
+
isRequired: true
|
|
2840
|
+
}
|
|
2841
|
+
),
|
|
2842
|
+
/* @__PURE__ */ jsx31(FormSelect, { form, id: "currency", name: "Currency", values: currencyOptions, disabled: isEditMode })
|
|
2843
|
+
] }),
|
|
2844
|
+
/* @__PURE__ */ jsx31(
|
|
2845
|
+
FormSelect,
|
|
2846
|
+
{
|
|
2847
|
+
form,
|
|
2848
|
+
id: "interval",
|
|
2849
|
+
name: "Billing Interval",
|
|
2850
|
+
values: intervalOptions,
|
|
2851
|
+
disabled: isEditMode
|
|
2852
|
+
}
|
|
2853
|
+
),
|
|
2854
|
+
isRecurring && /* @__PURE__ */ jsxs26("div", { className: "grid grid-cols-2 gap-x-4", children: [
|
|
2855
|
+
/* @__PURE__ */ jsx31(
|
|
2856
|
+
FormInput,
|
|
2857
|
+
{
|
|
2858
|
+
form,
|
|
2859
|
+
id: "intervalCount",
|
|
2860
|
+
name: "Interval Count",
|
|
2861
|
+
placeholder: "1",
|
|
2862
|
+
type: "number",
|
|
2863
|
+
disabled: isEditMode
|
|
2864
|
+
}
|
|
2865
|
+
),
|
|
2866
|
+
/* @__PURE__ */ jsx31(
|
|
2867
|
+
FormSelect,
|
|
2868
|
+
{
|
|
2869
|
+
form,
|
|
2870
|
+
id: "usageType",
|
|
2871
|
+
name: "Usage Type",
|
|
2872
|
+
values: usageTypeOptions,
|
|
2873
|
+
disabled: isEditMode
|
|
2874
|
+
}
|
|
2875
|
+
)
|
|
2876
|
+
] }),
|
|
2877
|
+
/* @__PURE__ */ jsx31(
|
|
2878
|
+
FormInput,
|
|
2879
|
+
{
|
|
2880
|
+
form,
|
|
2881
|
+
id: "nickname",
|
|
2882
|
+
name: "Nickname (optional)",
|
|
2883
|
+
placeholder: "e.g., Standard Plan, Pro Tier"
|
|
2884
|
+
}
|
|
2885
|
+
),
|
|
2886
|
+
/* @__PURE__ */ jsx31(
|
|
2887
|
+
FormTextarea,
|
|
2888
|
+
{
|
|
2889
|
+
form,
|
|
2890
|
+
id: "description",
|
|
2891
|
+
name: "Description (optional)",
|
|
2892
|
+
placeholder: "Describe what this price tier includes...",
|
|
2893
|
+
className: "min-h-24"
|
|
2894
|
+
}
|
|
2895
|
+
),
|
|
2896
|
+
/* @__PURE__ */ jsxs26(FormItem, { children: [
|
|
2897
|
+
/* @__PURE__ */ jsx31(FormLabel, { children: "Features (optional)" }),
|
|
2898
|
+
/* @__PURE__ */ jsxs26("div", { className: "space-y-2", children: [
|
|
2899
|
+
form.watch("features").map((_, index) => /* @__PURE__ */ jsxs26("div", { className: "flex gap-2", children: [
|
|
2900
|
+
/* @__PURE__ */ jsx31(FormControl, { children: /* @__PURE__ */ jsx31(
|
|
2901
|
+
Input,
|
|
2902
|
+
{
|
|
2903
|
+
...form.register(`features.${index}`),
|
|
2904
|
+
placeholder: `Feature ${index + 1}`,
|
|
2905
|
+
className: "flex-1"
|
|
2906
|
+
}
|
|
2907
|
+
) }),
|
|
2908
|
+
/* @__PURE__ */ jsx31(
|
|
2909
|
+
Button,
|
|
2910
|
+
{
|
|
2911
|
+
type: "button",
|
|
2912
|
+
variant: "outline",
|
|
2913
|
+
size: "icon",
|
|
2914
|
+
onClick: () => {
|
|
2915
|
+
const currentFeatures = form.getValues("features");
|
|
2916
|
+
form.setValue(
|
|
2917
|
+
"features",
|
|
2918
|
+
currentFeatures.filter((_2, i) => i !== index)
|
|
2919
|
+
);
|
|
2920
|
+
},
|
|
2921
|
+
children: /* @__PURE__ */ jsx31(XIcon, { className: "h-4 w-4" })
|
|
2922
|
+
}
|
|
2923
|
+
)
|
|
2924
|
+
] }, index)),
|
|
2925
|
+
/* @__PURE__ */ jsxs26(
|
|
2926
|
+
Button,
|
|
2927
|
+
{
|
|
2928
|
+
type: "button",
|
|
2929
|
+
variant: "outline",
|
|
2930
|
+
size: "sm",
|
|
2931
|
+
onClick: () => {
|
|
2932
|
+
const currentFeatures = form.getValues("features");
|
|
2933
|
+
form.setValue("features", [...currentFeatures, ""]);
|
|
2934
|
+
},
|
|
2935
|
+
className: "mt-2",
|
|
2936
|
+
children: [
|
|
2937
|
+
/* @__PURE__ */ jsx31(PlusIcon, { className: "h-4 w-4 mr-2" }),
|
|
2938
|
+
"Add Feature"
|
|
2939
|
+
]
|
|
2940
|
+
}
|
|
2941
|
+
)
|
|
2942
|
+
] })
|
|
2943
|
+
] }),
|
|
2944
|
+
/* @__PURE__ */ jsx31(FormCheckbox, { form, id: "active", name: "Active" }),
|
|
2945
|
+
/* @__PURE__ */ jsx31(CommonEditorButtons, { isEdit: isEditMode, form, disabled: isSubmitting, setOpen: onOpenChange })
|
|
2946
|
+
] }) })
|
|
2947
|
+
] }) });
|
|
2948
|
+
}
|
|
2949
|
+
__name(PriceEditor, "PriceEditor");
|
|
2950
|
+
|
|
2951
|
+
// src/features/billing/stripe-price/components/lists/PricesList.tsx
|
|
2952
|
+
import { Archive, DollarSign, Edit, RotateCcw } from "lucide-react";
|
|
2953
|
+
import { useEffect as useEffect9, useState as useState16 } from "react";
|
|
2954
|
+
import { jsx as jsx32, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2955
|
+
function PricesList({ productId, onPricesChange }) {
|
|
2956
|
+
const [prices, setPrices] = useState16([]);
|
|
2957
|
+
const [loading, setLoading] = useState16(true);
|
|
2958
|
+
const [showCreatePrice, setShowCreatePrice] = useState16(false);
|
|
2959
|
+
const [editingPrice, setEditingPrice] = useState16(null);
|
|
2960
|
+
const [priceToArchive, setPriceToArchive] = useState16(null);
|
|
2961
|
+
const [priceToReactivate, setPriceToReactivate] = useState16(null);
|
|
2962
|
+
const [archivingPriceId, setArchivingPriceId] = useState16(null);
|
|
2963
|
+
const [reactivatingPriceId, setReactivatingPriceId] = useState16(null);
|
|
2964
|
+
const loadPrices = /* @__PURE__ */ __name(async () => {
|
|
2965
|
+
setLoading(true);
|
|
2966
|
+
try {
|
|
2967
|
+
const fetchedPrices = await StripePriceService.listPrices({ productId });
|
|
2968
|
+
setPrices(fetchedPrices);
|
|
2969
|
+
} catch (error) {
|
|
2970
|
+
console.error("[PricesList] Failed to load prices:", error);
|
|
2971
|
+
} finally {
|
|
2972
|
+
setLoading(false);
|
|
2973
|
+
}
|
|
2974
|
+
}, "loadPrices");
|
|
2975
|
+
useEffect9(() => {
|
|
2976
|
+
loadPrices();
|
|
2977
|
+
}, [productId]);
|
|
2978
|
+
const handleArchive = /* @__PURE__ */ __name(async () => {
|
|
2979
|
+
if (!priceToArchive) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
setArchivingPriceId(priceToArchive.id);
|
|
2983
|
+
try {
|
|
2984
|
+
await StripePriceService.archivePrice({ id: priceToArchive.id });
|
|
2985
|
+
setPriceToArchive(null);
|
|
2986
|
+
await loadPrices();
|
|
2987
|
+
onPricesChange();
|
|
2988
|
+
} catch (error) {
|
|
2989
|
+
console.error("[PricesList] Failed to archive price:", error);
|
|
2990
|
+
} finally {
|
|
2991
|
+
setArchivingPriceId(null);
|
|
2992
|
+
}
|
|
2993
|
+
}, "handleArchive");
|
|
2994
|
+
const handleReactivate = /* @__PURE__ */ __name(async () => {
|
|
2995
|
+
if (!priceToReactivate) {
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
setReactivatingPriceId(priceToReactivate.id);
|
|
2999
|
+
try {
|
|
3000
|
+
await StripePriceService.reactivatePrice({ id: priceToReactivate.id });
|
|
3001
|
+
setPriceToReactivate(null);
|
|
3002
|
+
await loadPrices();
|
|
3003
|
+
onPricesChange();
|
|
3004
|
+
} catch (error) {
|
|
3005
|
+
console.error("[PricesList] Failed to reactivate price:", error);
|
|
3006
|
+
} finally {
|
|
3007
|
+
setReactivatingPriceId(null);
|
|
3008
|
+
}
|
|
3009
|
+
}, "handleReactivate");
|
|
3010
|
+
const formatInterval2 = /* @__PURE__ */ __name((price) => {
|
|
3011
|
+
if (price.priceType === "one_time") {
|
|
3012
|
+
return "one-time";
|
|
3013
|
+
}
|
|
3014
|
+
if (price.recurring) {
|
|
3015
|
+
const count = price.recurring.intervalCount;
|
|
3016
|
+
const interval = price.recurring.interval;
|
|
3017
|
+
if (count === 1) {
|
|
3018
|
+
return `/ ${interval}`;
|
|
3019
|
+
} else {
|
|
3020
|
+
return `/ ${count} ${interval}s`;
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
return "";
|
|
3024
|
+
}, "formatInterval");
|
|
3025
|
+
if (loading) {
|
|
3026
|
+
return /* @__PURE__ */ jsx32("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx32("p", { className: "text-muted-foreground", children: "Loading prices..." }) });
|
|
3027
|
+
}
|
|
3028
|
+
return /* @__PURE__ */ jsxs27("div", { className: "flex flex-col gap-y-4", children: [
|
|
3029
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-center justify-between mb-4", children: [
|
|
3030
|
+
/* @__PURE__ */ jsx32("h4", { className: "text-lg font-semibold", children: "Prices" }),
|
|
3031
|
+
/* @__PURE__ */ jsx32(Button, { size: "sm", onClick: () => setShowCreatePrice(true), children: "Add Price" })
|
|
3032
|
+
] }),
|
|
3033
|
+
prices.length === 0 && /* @__PURE__ */ jsxs27("div", { className: "bg-background flex flex-col items-center justify-center gap-y-3 rounded-lg border border-dashed p-8", children: [
|
|
3034
|
+
/* @__PURE__ */ jsx32(DollarSign, { className: "text-muted-foreground h-12 w-12" }),
|
|
3035
|
+
/* @__PURE__ */ jsx32("p", { className: "text-muted-foreground text-sm", children: "No prices yet. Add a price to enable subscriptions." }),
|
|
3036
|
+
/* @__PURE__ */ jsx32(Button, { size: "sm", onClick: () => setShowCreatePrice(true), children: "Add Price" })
|
|
3037
|
+
] }),
|
|
3038
|
+
prices.length > 0 && /* @__PURE__ */ jsx32("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: prices.map((price) => {
|
|
3039
|
+
const isArchiving = archivingPriceId === price.id;
|
|
3040
|
+
const isReactivating = reactivatingPriceId === price.id;
|
|
3041
|
+
return /* @__PURE__ */ jsxs27("div", { className: "border rounded-lg bg-white p-4 hover:shadow-sm transition-shadow", children: [
|
|
3042
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-start justify-between mb-3", children: [
|
|
3043
|
+
/* @__PURE__ */ jsx32(DollarSign, { className: "h-5 w-5 text-primary" }),
|
|
3044
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex gap-1", children: [
|
|
3045
|
+
/* @__PURE__ */ jsx32(Button, { variant: "ghost", size: "sm", onClick: () => setEditingPrice(price), className: "h-8 w-8 p-0", children: /* @__PURE__ */ jsx32(Edit, { className: "h-4 w-4" }) }),
|
|
3046
|
+
price.active ? /* @__PURE__ */ jsx32(
|
|
3047
|
+
Button,
|
|
3048
|
+
{
|
|
3049
|
+
variant: "ghost",
|
|
3050
|
+
size: "sm",
|
|
3051
|
+
onClick: () => setPriceToArchive(price),
|
|
3052
|
+
className: "h-8 w-8 p-0",
|
|
3053
|
+
disabled: isArchiving,
|
|
3054
|
+
children: /* @__PURE__ */ jsx32(Archive, { className: "h-4 w-4" })
|
|
3055
|
+
}
|
|
3056
|
+
) : /* @__PURE__ */ jsx32(
|
|
3057
|
+
Button,
|
|
3058
|
+
{
|
|
3059
|
+
variant: "ghost",
|
|
3060
|
+
size: "sm",
|
|
3061
|
+
onClick: () => setPriceToReactivate(price),
|
|
3062
|
+
className: "h-8 w-8 p-0",
|
|
3063
|
+
disabled: isReactivating,
|
|
3064
|
+
children: /* @__PURE__ */ jsx32(RotateCcw, { className: "h-4 w-4" })
|
|
3065
|
+
}
|
|
3066
|
+
)
|
|
3067
|
+
] })
|
|
3068
|
+
] }),
|
|
3069
|
+
/* @__PURE__ */ jsx32("div", { className: "mb-2", children: /* @__PURE__ */ jsxs27("div", { className: "text-2xl font-bold", children: [
|
|
3070
|
+
formatCurrency(price.unitAmount, price.currency),
|
|
3071
|
+
" ",
|
|
3072
|
+
/* @__PURE__ */ jsx32("span", { className: "text-muted-foreground text-sm font-normal", children: formatInterval2(price) })
|
|
3073
|
+
] }) }),
|
|
3074
|
+
price.metadata?.nickname && /* @__PURE__ */ jsx32("p", { className: "text-sm font-medium mb-2", children: price.metadata.nickname }),
|
|
3075
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex flex-wrap gap-2", children: [
|
|
3076
|
+
price.active ? /* @__PURE__ */ jsx32("span", { className: "bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full font-medium", children: "Active" }) : /* @__PURE__ */ jsx32("span", { className: "bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full font-medium", children: "Inactive" }),
|
|
3077
|
+
price.recurring?.usageType === "metered" && /* @__PURE__ */ jsx32("span", { className: "bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full font-medium", children: "Metered" }),
|
|
3078
|
+
/* @__PURE__ */ jsx32("span", { className: "bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full font-medium uppercase", children: price.currency })
|
|
3079
|
+
] })
|
|
3080
|
+
] }, price.id);
|
|
3081
|
+
}) }),
|
|
3082
|
+
showCreatePrice && /* @__PURE__ */ jsx32(
|
|
3083
|
+
PriceEditor,
|
|
3084
|
+
{
|
|
3085
|
+
productId,
|
|
3086
|
+
open: showCreatePrice,
|
|
3087
|
+
onOpenChange: setShowCreatePrice,
|
|
3088
|
+
onSuccess: () => {
|
|
3089
|
+
loadPrices();
|
|
3090
|
+
onPricesChange();
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
),
|
|
3094
|
+
editingPrice && /* @__PURE__ */ jsx32(
|
|
3095
|
+
PriceEditor,
|
|
3096
|
+
{
|
|
3097
|
+
productId,
|
|
3098
|
+
price: editingPrice,
|
|
3099
|
+
open: !!editingPrice,
|
|
3100
|
+
onOpenChange: (open) => !open && setEditingPrice(null),
|
|
3101
|
+
onSuccess: () => {
|
|
3102
|
+
loadPrices();
|
|
3103
|
+
onPricesChange();
|
|
3104
|
+
setEditingPrice(null);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
),
|
|
3108
|
+
/* @__PURE__ */ jsx32(AlertDialog, { open: !!priceToArchive, onOpenChange: (open) => !open && setPriceToArchive(null), children: /* @__PURE__ */ jsxs27(AlertDialogContent, { children: [
|
|
3109
|
+
/* @__PURE__ */ jsxs27(AlertDialogHeader, { children: [
|
|
3110
|
+
/* @__PURE__ */ jsx32(AlertDialogTitle, { children: "Archive Price" }),
|
|
3111
|
+
/* @__PURE__ */ jsxs27(AlertDialogDescription, { children: [
|
|
3112
|
+
"Are you sure you want to archive the price for",
|
|
3113
|
+
" ",
|
|
3114
|
+
priceToArchive && `${formatCurrency(priceToArchive.unitAmount, priceToArchive.currency)} ${formatInterval2(priceToArchive)}`,
|
|
3115
|
+
"? This will prevent new subscriptions but existing ones will continue."
|
|
3116
|
+
] })
|
|
3117
|
+
] }),
|
|
3118
|
+
/* @__PURE__ */ jsxs27(AlertDialogFooter, { children: [
|
|
3119
|
+
/* @__PURE__ */ jsx32(AlertDialogCancel, { disabled: !!archivingPriceId, children: "Cancel" }),
|
|
3120
|
+
/* @__PURE__ */ jsx32(
|
|
3121
|
+
AlertDialogAction,
|
|
3122
|
+
{
|
|
3123
|
+
onClick: handleArchive,
|
|
3124
|
+
disabled: !!archivingPriceId,
|
|
3125
|
+
className: "bg-red-600 hover:bg-red-700",
|
|
3126
|
+
children: archivingPriceId ? "Archiving..." : "Archive"
|
|
3127
|
+
}
|
|
3128
|
+
)
|
|
3129
|
+
] })
|
|
3130
|
+
] }) }),
|
|
3131
|
+
/* @__PURE__ */ jsx32(AlertDialog, { open: !!priceToReactivate, onOpenChange: (open) => !open && setPriceToReactivate(null), children: /* @__PURE__ */ jsxs27(AlertDialogContent, { children: [
|
|
3132
|
+
/* @__PURE__ */ jsxs27(AlertDialogHeader, { children: [
|
|
3133
|
+
/* @__PURE__ */ jsx32(AlertDialogTitle, { children: "Reactivate Price" }),
|
|
3134
|
+
/* @__PURE__ */ jsxs27(AlertDialogDescription, { children: [
|
|
3135
|
+
"Are you sure you want to reactivate the price for",
|
|
3136
|
+
" ",
|
|
3137
|
+
priceToReactivate && `${formatCurrency(priceToReactivate.unitAmount, priceToReactivate.currency)} ${formatInterval2(priceToReactivate)}`,
|
|
3138
|
+
"? This will allow new subscriptions again."
|
|
3139
|
+
] })
|
|
3140
|
+
] }),
|
|
3141
|
+
/* @__PURE__ */ jsxs27(AlertDialogFooter, { children: [
|
|
3142
|
+
/* @__PURE__ */ jsx32(AlertDialogCancel, { disabled: !!reactivatingPriceId, children: "Cancel" }),
|
|
3143
|
+
/* @__PURE__ */ jsx32(
|
|
3144
|
+
AlertDialogAction,
|
|
3145
|
+
{
|
|
3146
|
+
onClick: handleReactivate,
|
|
3147
|
+
disabled: !!reactivatingPriceId,
|
|
3148
|
+
className: "bg-green-600 hover:bg-green-700",
|
|
3149
|
+
children: reactivatingPriceId ? "Reactivating..." : "Reactivate"
|
|
3150
|
+
}
|
|
3151
|
+
)
|
|
3152
|
+
] })
|
|
3153
|
+
] }) })
|
|
3154
|
+
] });
|
|
3155
|
+
}
|
|
3156
|
+
__name(PricesList, "PricesList");
|
|
3157
|
+
|
|
3158
|
+
// src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx
|
|
3159
|
+
import { Package as Package2 } from "lucide-react";
|
|
3160
|
+
import { useEffect as useEffect10, useState as useState19 } from "react";
|
|
3161
|
+
|
|
3162
|
+
// src/features/billing/stripe-product/components/forms/ProductEditor.tsx
|
|
3163
|
+
import { zodResolver as zodResolver3 } from "@hookform/resolvers/zod";
|
|
3164
|
+
import { useState as useState17 } from "react";
|
|
3165
|
+
import { useForm as useForm3 } from "react-hook-form";
|
|
3166
|
+
import { v4 as v44 } from "uuid";
|
|
3167
|
+
import { z as z3 } from "zod";
|
|
3168
|
+
import { jsx as jsx33, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3169
|
+
function ProductEditor({ product, open, onOpenChange, onSuccess }) {
|
|
3170
|
+
const [isSubmitting, setIsSubmitting] = useState17(false);
|
|
3171
|
+
const formSchema2 = z3.object({
|
|
3172
|
+
name: z3.string().min(1, { message: "Product name is required" }),
|
|
3173
|
+
description: z3.string().optional(),
|
|
3174
|
+
active: z3.boolean()
|
|
3175
|
+
});
|
|
3176
|
+
const form = useForm3({
|
|
3177
|
+
resolver: zodResolver3(formSchema2),
|
|
3178
|
+
defaultValues: {
|
|
3179
|
+
name: product?.name || "",
|
|
3180
|
+
description: product?.description || "",
|
|
3181
|
+
active: product?.active ?? true
|
|
3182
|
+
}
|
|
3183
|
+
});
|
|
3184
|
+
const onSubmit = /* @__PURE__ */ __name(async (values) => {
|
|
3185
|
+
setIsSubmitting(true);
|
|
3186
|
+
try {
|
|
3187
|
+
if (product) {
|
|
3188
|
+
await StripeProductService.updateProduct({
|
|
3189
|
+
id: product.id,
|
|
3190
|
+
name: values.name,
|
|
3191
|
+
description: values.description,
|
|
3192
|
+
active: values.active
|
|
3193
|
+
});
|
|
3194
|
+
} else {
|
|
3195
|
+
await StripeProductService.createProduct({
|
|
3196
|
+
id: v44(),
|
|
3197
|
+
name: values.name,
|
|
3198
|
+
description: values.description,
|
|
3199
|
+
active: values.active
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
onSuccess();
|
|
3203
|
+
onOpenChange(false);
|
|
3204
|
+
} catch (error) {
|
|
3205
|
+
console.error("[ProductEditor] Failed to save product:", error);
|
|
3206
|
+
} finally {
|
|
3207
|
+
setIsSubmitting(false);
|
|
3208
|
+
}
|
|
3209
|
+
}, "onSubmit");
|
|
3210
|
+
return /* @__PURE__ */ jsx33(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs28(DialogContent, { className: "max-w-2xl", children: [
|
|
3211
|
+
/* @__PURE__ */ jsxs28(DialogHeader, { children: [
|
|
3212
|
+
/* @__PURE__ */ jsx33(DialogTitle, { children: product ? "Edit Product" : "Create Product" }),
|
|
3213
|
+
/* @__PURE__ */ jsx33(DialogDescription, { children: product ? `Update the details for ${product.name}` : "Create a new product to offer to your customers" })
|
|
3214
|
+
] }),
|
|
3215
|
+
/* @__PURE__ */ jsx33(Form, { ...form, children: /* @__PURE__ */ jsxs28("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-col gap-y-4", children: [
|
|
3216
|
+
/* @__PURE__ */ jsx33(FormInput, { form, id: "name", name: "Product Name", placeholder: "Enter product name", isRequired: true }),
|
|
3217
|
+
/* @__PURE__ */ jsx33(
|
|
3218
|
+
FormTextarea,
|
|
3219
|
+
{
|
|
3220
|
+
form,
|
|
3221
|
+
id: "description",
|
|
3222
|
+
name: "Description",
|
|
3223
|
+
placeholder: "Enter product description (optional)",
|
|
3224
|
+
className: "min-h-32"
|
|
3225
|
+
}
|
|
3226
|
+
),
|
|
3227
|
+
/* @__PURE__ */ jsx33(FormCheckbox, { form, id: "active", name: "Active" }),
|
|
3228
|
+
/* @__PURE__ */ jsx33(CommonEditorButtons, { isEdit: !!product, form, disabled: isSubmitting, setOpen: onOpenChange })
|
|
3229
|
+
] }) })
|
|
3230
|
+
] }) });
|
|
3231
|
+
}
|
|
3232
|
+
__name(ProductEditor, "ProductEditor");
|
|
3233
|
+
|
|
3234
|
+
// src/features/billing/stripe-product/components/lists/ProductsList.tsx
|
|
3235
|
+
import { Archive as Archive2, ChevronDown, ChevronUp, Edit as Edit2, Package, RefreshCw as RefreshCw2 } from "lucide-react";
|
|
3236
|
+
import { useState as useState18 } from "react";
|
|
3237
|
+
import { jsx as jsx34, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
3238
|
+
function ProductsList({ products, onProductsChange }) {
|
|
3239
|
+
const [expandedProductId, setExpandedProductId] = useState18(null);
|
|
3240
|
+
const [editingProduct, setEditingProduct] = useState18(null);
|
|
3241
|
+
const [archivingProductId, setArchivingProductId] = useState18(null);
|
|
3242
|
+
const [reactivatingProductId, setReactivatingProductId] = useState18(null);
|
|
3243
|
+
const [productToArchive, setProductToArchive] = useState18(null);
|
|
3244
|
+
const [productToReactivate, setProductToReactivate] = useState18(null);
|
|
3245
|
+
const handleArchive = /* @__PURE__ */ __name(async () => {
|
|
3246
|
+
if (!productToArchive) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
setArchivingProductId(productToArchive.id);
|
|
3250
|
+
try {
|
|
3251
|
+
const archivedProduct = await StripeProductService.archiveProduct({ id: productToArchive.id });
|
|
3252
|
+
setProductToArchive(null);
|
|
3253
|
+
onProductsChange();
|
|
3254
|
+
} catch (error) {
|
|
3255
|
+
console.error("[ProductsList] Failed to archive product:", error);
|
|
3256
|
+
} finally {
|
|
3257
|
+
setArchivingProductId(null);
|
|
3258
|
+
}
|
|
3259
|
+
}, "handleArchive");
|
|
3260
|
+
const handleReactivate = /* @__PURE__ */ __name(async () => {
|
|
3261
|
+
if (!productToReactivate) {
|
|
3262
|
+
return;
|
|
3263
|
+
}
|
|
3264
|
+
setReactivatingProductId(productToReactivate.id);
|
|
3265
|
+
try {
|
|
3266
|
+
const reactivatedProduct = await StripeProductService.reactivateProduct({ id: productToReactivate.id });
|
|
3267
|
+
setProductToReactivate(null);
|
|
3268
|
+
onProductsChange();
|
|
3269
|
+
} catch (error) {
|
|
3270
|
+
} finally {
|
|
3271
|
+
setReactivatingProductId(null);
|
|
3272
|
+
}
|
|
3273
|
+
}, "handleReactivate");
|
|
3274
|
+
const toggleExpand = /* @__PURE__ */ __name((productId) => {
|
|
3275
|
+
setExpandedProductId(expandedProductId === productId ? null : productId);
|
|
3276
|
+
}, "toggleExpand");
|
|
3277
|
+
return /* @__PURE__ */ jsxs29("div", { className: "flex flex-col gap-y-4", children: [
|
|
3278
|
+
products.map((product) => {
|
|
3279
|
+
const isExpanded = expandedProductId === product.id;
|
|
3280
|
+
const isArchiving = archivingProductId === product.id;
|
|
3281
|
+
const isReactivating = reactivatingProductId === product.id;
|
|
3282
|
+
return /* @__PURE__ */ jsxs29("div", { className: "border rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow", children: [
|
|
3283
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex items-center justify-between p-6", children: [
|
|
3284
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-x-4 flex-1", children: [
|
|
3285
|
+
/* @__PURE__ */ jsx34(Package, { className: "h-6 w-6 text-primary" }),
|
|
3286
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex-1", children: [
|
|
3287
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-x-3", children: [
|
|
3288
|
+
/* @__PURE__ */ jsx34("h3", { className: "text-lg font-semibold", children: product.name }),
|
|
3289
|
+
product.active ? /* @__PURE__ */ jsx34("span", { className: "bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full font-medium", children: "Active" }) : /* @__PURE__ */ jsx34("span", { className: "bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full font-medium", children: "Inactive" })
|
|
3290
|
+
] }),
|
|
3291
|
+
product.description && /* @__PURE__ */ jsx34("p", { className: "text-muted-foreground text-sm mt-1", children: product.description })
|
|
3292
|
+
] })
|
|
3293
|
+
] }),
|
|
3294
|
+
/* @__PURE__ */ jsxs29("div", { className: "flex items-center gap-x-2", children: [
|
|
3295
|
+
/* @__PURE__ */ jsxs29(Button, { variant: "outline", size: "sm", onClick: () => setEditingProduct(product), children: [
|
|
3296
|
+
/* @__PURE__ */ jsx34(Edit2, { className: "h-4 w-4 mr-1" }),
|
|
3297
|
+
"Edit"
|
|
3298
|
+
] }),
|
|
3299
|
+
product.active ? /* @__PURE__ */ jsxs29(
|
|
3300
|
+
Button,
|
|
3301
|
+
{
|
|
3302
|
+
variant: "outline",
|
|
3303
|
+
size: "sm",
|
|
3304
|
+
onClick: () => setProductToArchive(product),
|
|
3305
|
+
disabled: isArchiving,
|
|
3306
|
+
children: [
|
|
3307
|
+
/* @__PURE__ */ jsx34(Archive2, { className: "h-4 w-4 mr-1" }),
|
|
3308
|
+
isArchiving ? "Archiving..." : "Archive"
|
|
3309
|
+
]
|
|
3310
|
+
}
|
|
3311
|
+
) : /* @__PURE__ */ jsxs29(
|
|
3312
|
+
Button,
|
|
3313
|
+
{
|
|
3314
|
+
variant: "outline",
|
|
3315
|
+
size: "sm",
|
|
3316
|
+
onClick: () => setProductToReactivate(product),
|
|
3317
|
+
disabled: isReactivating,
|
|
3318
|
+
children: [
|
|
3319
|
+
/* @__PURE__ */ jsx34(RefreshCw2, { className: "h-4 w-4 mr-1" }),
|
|
3320
|
+
isReactivating ? "Reactivating..." : "Reactivate"
|
|
3321
|
+
]
|
|
3322
|
+
}
|
|
3323
|
+
),
|
|
3324
|
+
/* @__PURE__ */ jsx34(Button, { variant: "ghost", size: "sm", onClick: () => toggleExpand(product.id), children: isExpanded ? /* @__PURE__ */ jsx34(ChevronUp, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx34(ChevronDown, { className: "h-5 w-5" }) })
|
|
3325
|
+
] })
|
|
3326
|
+
] }),
|
|
3327
|
+
isExpanded && /* @__PURE__ */ jsx34("div", { className: "border-t bg-muted/30 p-6", children: /* @__PURE__ */ jsx34(PricesList, { productId: product.id, onPricesChange: onProductsChange }) })
|
|
3328
|
+
] }, product.id);
|
|
3329
|
+
}),
|
|
3330
|
+
editingProduct && /* @__PURE__ */ jsx34(
|
|
3331
|
+
ProductEditor,
|
|
3332
|
+
{
|
|
3333
|
+
product: editingProduct,
|
|
3334
|
+
open: !!editingProduct,
|
|
3335
|
+
onOpenChange: (open) => !open && setEditingProduct(null),
|
|
3336
|
+
onSuccess: () => {
|
|
3337
|
+
onProductsChange();
|
|
3338
|
+
setEditingProduct(null);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
),
|
|
3342
|
+
/* @__PURE__ */ jsx34(AlertDialog, { open: !!productToArchive, onOpenChange: (open) => !open && setProductToArchive(null), children: /* @__PURE__ */ jsxs29(AlertDialogContent, { children: [
|
|
3343
|
+
/* @__PURE__ */ jsxs29(AlertDialogHeader, { children: [
|
|
3344
|
+
/* @__PURE__ */ jsx34(AlertDialogTitle, { children: "Archive Product" }),
|
|
3345
|
+
/* @__PURE__ */ jsxs29(AlertDialogDescription, { children: [
|
|
3346
|
+
'Are you sure you want to archive "',
|
|
3347
|
+
productToArchive?.name,
|
|
3348
|
+
'"? This will deactivate it and it will no longer be available for new subscriptions.'
|
|
3349
|
+
] })
|
|
3350
|
+
] }),
|
|
3351
|
+
/* @__PURE__ */ jsxs29(AlertDialogFooter, { children: [
|
|
3352
|
+
/* @__PURE__ */ jsx34(AlertDialogCancel, { disabled: !!archivingProductId, children: "Cancel" }),
|
|
3353
|
+
/* @__PURE__ */ jsx34(
|
|
3354
|
+
AlertDialogAction,
|
|
3355
|
+
{
|
|
3356
|
+
onClick: handleArchive,
|
|
3357
|
+
disabled: !!archivingProductId,
|
|
3358
|
+
className: "bg-red-600 hover:bg-red-700",
|
|
3359
|
+
children: archivingProductId ? "Archiving..." : "Archive"
|
|
3360
|
+
}
|
|
3361
|
+
)
|
|
3362
|
+
] })
|
|
3363
|
+
] }) }),
|
|
3364
|
+
/* @__PURE__ */ jsx34(AlertDialog, { open: !!productToReactivate, onOpenChange: (open) => !open && setProductToReactivate(null), children: /* @__PURE__ */ jsxs29(AlertDialogContent, { children: [
|
|
3365
|
+
/* @__PURE__ */ jsxs29(AlertDialogHeader, { children: [
|
|
3366
|
+
/* @__PURE__ */ jsx34(AlertDialogTitle, { children: "Reactivate Product" }),
|
|
3367
|
+
/* @__PURE__ */ jsxs29(AlertDialogDescription, { children: [
|
|
3368
|
+
'Are you sure you want to reactivate "',
|
|
3369
|
+
productToReactivate?.name,
|
|
3370
|
+
'"? This will make it available for new subscriptions again.'
|
|
3371
|
+
] })
|
|
3372
|
+
] }),
|
|
3373
|
+
/* @__PURE__ */ jsxs29(AlertDialogFooter, { children: [
|
|
3374
|
+
/* @__PURE__ */ jsx34(AlertDialogCancel, { disabled: !!reactivatingProductId, children: "Cancel" }),
|
|
3375
|
+
/* @__PURE__ */ jsx34(
|
|
3376
|
+
AlertDialogAction,
|
|
3377
|
+
{
|
|
3378
|
+
onClick: handleReactivate,
|
|
3379
|
+
disabled: !!reactivatingProductId,
|
|
3380
|
+
className: "bg-green-600 hover:bg-green-700",
|
|
3381
|
+
children: reactivatingProductId ? "Reactivating..." : "Reactivate"
|
|
3382
|
+
}
|
|
3383
|
+
)
|
|
3384
|
+
] })
|
|
3385
|
+
] }) })
|
|
3386
|
+
] });
|
|
3387
|
+
}
|
|
3388
|
+
__name(ProductsList, "ProductsList");
|
|
3389
|
+
|
|
3390
|
+
// src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx
|
|
3391
|
+
import { jsx as jsx35, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
3392
|
+
function ProductsAdminContainer() {
|
|
3393
|
+
const { hasRole } = useCurrentUserContext();
|
|
3394
|
+
const [products, setProducts] = useState19([]);
|
|
3395
|
+
const [loading, setLoading] = useState19(true);
|
|
3396
|
+
const [showCreateProduct, setShowCreateProduct] = useState19(false);
|
|
3397
|
+
if (!hasRole(getRoleId().Administrator)) {
|
|
3398
|
+
return /* @__PURE__ */ jsx35("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx35("p", { className: "text-red-600 font-semibold", children: "Permission denied. Administrator access required." }) });
|
|
3399
|
+
}
|
|
3400
|
+
const loadProducts = /* @__PURE__ */ __name(async () => {
|
|
3401
|
+
setLoading(true);
|
|
3402
|
+
try {
|
|
3403
|
+
const fetchedProducts = await StripeProductService.listProducts();
|
|
3404
|
+
setProducts(fetchedProducts);
|
|
3405
|
+
} catch (error) {
|
|
3406
|
+
console.error("[ProductsAdminContainer] Failed to load products:", error);
|
|
3407
|
+
} finally {
|
|
3408
|
+
setLoading(false);
|
|
3409
|
+
}
|
|
3410
|
+
}, "loadProducts");
|
|
3411
|
+
useEffect10(() => {
|
|
3412
|
+
loadProducts();
|
|
3413
|
+
}, []);
|
|
3414
|
+
if (loading) {
|
|
3415
|
+
return /* @__PURE__ */ jsx35("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx35("p", { className: "text-muted-foreground", children: "Loading products..." }) });
|
|
3416
|
+
}
|
|
3417
|
+
return /* @__PURE__ */ jsxs30("div", { className: "flex w-full flex-col gap-y-6", children: [
|
|
3418
|
+
/* @__PURE__ */ jsxs30("div", { className: "flex items-center justify-between", children: [
|
|
3419
|
+
/* @__PURE__ */ jsxs30("div", { className: "flex items-center gap-x-3", children: [
|
|
3420
|
+
/* @__PURE__ */ jsx35(Package2, { className: "h-8 w-8" }),
|
|
3421
|
+
/* @__PURE__ */ jsx35("h1", { className: "text-3xl font-bold", children: "Product & Price Management" })
|
|
3422
|
+
] }),
|
|
3423
|
+
/* @__PURE__ */ jsx35(Button, { onClick: () => setShowCreateProduct(true), children: "Create Product" })
|
|
3424
|
+
] }),
|
|
3425
|
+
products.length === 0 && /* @__PURE__ */ jsxs30("div", { className: "bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed p-12", children: [
|
|
3426
|
+
/* @__PURE__ */ jsx35(Package2, { className: "text-muted-foreground h-16 w-16" }),
|
|
3427
|
+
/* @__PURE__ */ jsxs30("div", { className: "text-center", children: [
|
|
3428
|
+
/* @__PURE__ */ jsx35("h3", { className: "mb-2 text-xl font-semibold", children: "No products yet" }),
|
|
3429
|
+
/* @__PURE__ */ jsx35("p", { className: "text-muted-foreground mb-4", children: "Create your first product to start offering subscriptions to your customers." }),
|
|
3430
|
+
/* @__PURE__ */ jsx35(Button, { onClick: () => setShowCreateProduct(true), children: "Create Your First Product" })
|
|
3431
|
+
] })
|
|
3432
|
+
] }),
|
|
3433
|
+
products.length > 0 && /* @__PURE__ */ jsx35(ProductsList, { products, onProductsChange: loadProducts }),
|
|
3434
|
+
showCreateProduct && /* @__PURE__ */ jsx35(ProductEditor, { open: showCreateProduct, onOpenChange: setShowCreateProduct, onSuccess: loadProducts })
|
|
3435
|
+
] });
|
|
3436
|
+
}
|
|
3437
|
+
__name(ProductsAdminContainer, "ProductsAdminContainer");
|
|
3438
|
+
export {
|
|
3439
|
+
BillingAlertBanner,
|
|
3440
|
+
BillingDashboardContainer,
|
|
3441
|
+
BillingDetailModal,
|
|
3442
|
+
BillingUsageSummaryCard,
|
|
3443
|
+
CancelSubscriptionDialog,
|
|
3444
|
+
CustomerInfoCard,
|
|
3445
|
+
InvoiceDetails,
|
|
3446
|
+
InvoiceStatusBadge,
|
|
3447
|
+
InvoicesContainer,
|
|
3448
|
+
InvoicesList,
|
|
3449
|
+
InvoicesSummaryCard,
|
|
3450
|
+
PaymentMethodCard,
|
|
3451
|
+
PaymentMethodEditor,
|
|
3452
|
+
PaymentMethodSummaryCard,
|
|
3453
|
+
PaymentMethodsContainer,
|
|
3454
|
+
PaymentMethodsList,
|
|
3455
|
+
PriceEditor,
|
|
3456
|
+
PricesList,
|
|
3457
|
+
PricingCard,
|
|
3458
|
+
PricingCardsGrid,
|
|
3459
|
+
ProductEditor,
|
|
3460
|
+
ProductsAdminContainer,
|
|
3461
|
+
ProductsList,
|
|
3462
|
+
ProrationPreview,
|
|
3463
|
+
StripeProvider,
|
|
3464
|
+
SubscriptionDetails,
|
|
3465
|
+
SubscriptionEditor,
|
|
3466
|
+
SubscriptionStatusBadge,
|
|
3467
|
+
SubscriptionSummaryCard,
|
|
3468
|
+
SubscriptionsContainer,
|
|
3469
|
+
SubscriptionsList,
|
|
3470
|
+
UsageContainer,
|
|
3471
|
+
UsageHistoryTable,
|
|
3472
|
+
UsageSummaryCard,
|
|
3473
|
+
UsageSummaryCards,
|
|
3474
|
+
formatCurrency,
|
|
3475
|
+
formatDate3 as formatDate,
|
|
3476
|
+
formatInterval,
|
|
3477
|
+
isStripeConfigured
|
|
3478
|
+
};
|
|
3479
|
+
//# sourceMappingURL=index.mjs.map
|