@carlonicora/nextjs-jsonapi 2.4.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/dist/{BlockNoteEditor-7WGCH2KB.js → BlockNoteEditor-CDTU6DRN.js} +19 -19
  2. package/dist/{BlockNoteEditor-7WGCH2KB.js.map → BlockNoteEditor-CDTU6DRN.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-GWAV5ESO.mjs → BlockNoteEditor-VVAY73V2.mjs} +4 -4
  4. package/dist/billing/index.d.mts +127 -46
  5. package/dist/billing/index.d.ts +127 -46
  6. package/dist/billing/index.js +1243 -1084
  7. package/dist/billing/index.js.map +1 -1
  8. package/dist/billing/index.mjs +983 -824
  9. package/dist/billing/index.mjs.map +1 -1
  10. package/dist/{chunk-V66AZWPG.js → chunk-53B6NPGA.js} +278 -8
  11. package/dist/chunk-53B6NPGA.js.map +1 -0
  12. package/dist/{chunk-M2EGUO2F.mjs → chunk-J54546YC.mjs} +1608 -1419
  13. package/dist/chunk-J54546YC.mjs.map +1 -0
  14. package/dist/{chunk-TOKHHZSP.js → chunk-JKGEJGSD.js} +7 -7
  15. package/dist/{chunk-TOKHHZSP.js.map → chunk-JKGEJGSD.js.map} +1 -1
  16. package/dist/{chunk-3LVSA7IM.mjs → chunk-PHEFWL6L.mjs} +2 -2
  17. package/dist/{chunk-LCCRUWWY.mjs → chunk-TWXKAY34.mjs} +273 -3
  18. package/dist/chunk-TWXKAY34.mjs.map +1 -0
  19. package/dist/{chunk-GYGMEDVS.js → chunk-UGNLKDI6.js} +1012 -823
  20. package/dist/chunk-UGNLKDI6.js.map +1 -0
  21. package/dist/client/index.js +4 -4
  22. package/dist/client/index.mjs +3 -3
  23. package/dist/components/index.d.mts +16 -1
  24. package/dist/components/index.d.ts +16 -1
  25. package/dist/components/index.js +4 -4
  26. package/dist/components/index.mjs +3 -3
  27. package/dist/{tokenusage-admin.module-5wJ_tZTj.d.ts → config-BIjrg5wd.d.ts} +39 -1
  28. package/dist/{tokenusage-admin.module-DQVT4O4j.d.mts → config-BRUjtCd1.d.mts} +39 -1
  29. package/dist/contexts/index.js +4 -4
  30. package/dist/contexts/index.mjs +3 -3
  31. package/dist/core/index.d.mts +22 -3
  32. package/dist/core/index.d.ts +22 -3
  33. package/dist/core/index.js +12 -2
  34. package/dist/core/index.js.map +1 -1
  35. package/dist/core/index.mjs +11 -1
  36. package/dist/features/help/index.js +37 -37
  37. package/dist/features/help/index.mjs +3 -3
  38. package/dist/features/tokenusage/index.d.mts +413 -14
  39. package/dist/features/tokenusage/index.d.ts +413 -14
  40. package/dist/features/tokenusage/index.js +481 -120
  41. package/dist/features/tokenusage/index.js.map +1 -1
  42. package/dist/features/tokenusage/index.mjs +431 -70
  43. package/dist/features/tokenusage/index.mjs.map +1 -1
  44. package/dist/index.d.mts +3 -3
  45. package/dist/index.d.ts +3 -3
  46. package/dist/index.js +13 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/index.mjs +12 -2
  49. package/dist/server/index.js +3 -3
  50. package/dist/server/index.mjs +1 -1
  51. package/package.json +1 -1
  52. package/src/client/index.ts +4 -0
  53. package/src/core/index.ts +10 -0
  54. package/src/core/registry/ModuleRegistry.ts +4 -0
  55. package/src/features/administration/components/AdminIndexContainer.tsx +1 -1
  56. package/src/features/administration/components/__tests__/AdminIndexContainer.spec.tsx +1 -1
  57. package/src/features/billing/contexts/PriceContext.tsx +202 -0
  58. package/src/features/billing/contexts/ProductContext.tsx +185 -0
  59. package/src/features/billing/contexts/__tests__/PriceContext.spec.tsx +89 -0
  60. package/src/features/billing/contexts/__tests__/ProductContext.spec.tsx +81 -0
  61. package/src/features/billing/contexts/index.ts +2 -1
  62. package/src/features/billing/i18n-keys.ts +88 -0
  63. package/src/features/billing/stripe-price/components/containers/PriceContainer.tsx +21 -0
  64. package/src/features/billing/stripe-price/components/containers/index.ts +1 -0
  65. package/src/features/billing/stripe-price/components/details/PriceDetails.tsx +131 -0
  66. package/src/features/billing/stripe-price/components/details/index.ts +1 -0
  67. package/src/features/billing/stripe-price/components/forms/PriceArchiver.tsx +55 -0
  68. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +307 -374
  69. package/src/features/billing/stripe-price/components/forms/index.ts +3 -1
  70. package/src/features/billing/stripe-price/components/index.ts +2 -0
  71. package/src/features/billing/stripe-price/components/lists/PricesList.tsx +49 -267
  72. package/src/features/billing/stripe-price/data/index.ts +1 -0
  73. package/src/features/billing/stripe-price/data/stripe-price.fields.ts +8 -0
  74. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +119 -0
  75. package/src/features/billing/stripe-price/stripe-price.module.ts +5 -1
  76. package/src/features/billing/stripe-product/components/containers/ProductContainer.tsx +45 -0
  77. package/src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx +27 -0
  78. package/src/features/billing/stripe-product/components/containers/index.ts +2 -2
  79. package/src/features/billing/stripe-product/components/details/ProductDetails.tsx +45 -0
  80. package/src/features/billing/stripe-product/components/details/index.ts +1 -0
  81. package/src/features/billing/stripe-product/components/forms/ProductArchiver.tsx +55 -0
  82. package/src/features/billing/stripe-product/components/forms/ProductEditor.tsx +92 -81
  83. package/src/features/billing/stripe-product/components/forms/index.ts +3 -1
  84. package/src/features/billing/stripe-product/components/index.ts +1 -0
  85. package/src/features/billing/stripe-product/components/lists/ProductsList.tsx +45 -187
  86. package/src/features/billing/stripe-product/data/index.ts +1 -0
  87. package/src/features/billing/stripe-product/data/stripe-product.fields.ts +6 -0
  88. package/src/features/billing/stripe-product/hooks/useStripeProductTableStructure.tsx +80 -0
  89. package/src/features/billing/stripe-product/stripe-product.module.ts +5 -1
  90. package/src/features/tokenusage/components/TokenUsageAdminContainer.tsx +20 -4
  91. package/src/features/tokenusage/components/TokenUsageAdminTiles.tsx +31 -18
  92. package/src/features/tokenusage/components/TokenUsageBreakdownTable.tsx +13 -13
  93. package/src/features/tokenusage/components/TokenUsageRankedBar.tsx +24 -7
  94. package/src/features/tokenusage/components/TokenUsageReportContainer.tsx +102 -0
  95. package/src/features/tokenusage/components/TokenUsageReportFilterBar.tsx +34 -0
  96. package/src/features/tokenusage/components/TokenUsageReportTiles.tsx +161 -0
  97. package/src/features/tokenusage/components/TokenUsageTimelineChart.tsx +15 -13
  98. package/src/features/tokenusage/components/__tests__/TokenUsageAdminTiles.spec.tsx +4 -2
  99. package/src/features/tokenusage/components/__tests__/TokenUsageRankedBar.spec.tsx +3 -1
  100. package/src/features/tokenusage/contexts/TokenUsageAdminContext.tsx +6 -3
  101. package/src/features/tokenusage/contexts/TokenUsageReportContext.tsx +199 -0
  102. package/src/features/tokenusage/data/TokenUsageReportService.ts +65 -0
  103. package/src/features/tokenusage/data/index.ts +9 -0
  104. package/src/features/tokenusage/data/tokenusage-report-breakdown.interface.ts +12 -0
  105. package/src/features/tokenusage/data/tokenusage-report-breakdown.ts +94 -0
  106. package/src/features/tokenusage/data/tokenusage-report-summary.interface.ts +12 -0
  107. package/src/features/tokenusage/data/tokenusage-report-summary.ts +87 -0
  108. package/src/features/tokenusage/data/tokenusage-report-timeline.interface.ts +13 -0
  109. package/src/features/tokenusage/data/tokenusage-report-timeline.ts +94 -0
  110. package/src/features/tokenusage/data/tokenusage-report.types.ts +51 -0
  111. package/src/features/tokenusage/i18n-keys.ts +28 -0
  112. package/src/features/tokenusage/index.ts +18 -0
  113. package/src/features/tokenusage/lib/config.ts +22 -0
  114. package/src/features/tokenusage/lib/formatters.ts +100 -0
  115. package/src/features/tokenusage/lib/metrics.ts +8 -26
  116. package/src/features/tokenusage/lib/palette.ts +41 -14
  117. package/src/features/tokenusage/tokenusage-admin.module.ts +4 -0
  118. package/src/features/tokenusage/tokenusage.modules.ts +40 -0
  119. package/src/shadcnui/ui/chart.tsx +30 -3
  120. package/dist/chunk-GYGMEDVS.js.map +0 -1
  121. package/dist/chunk-LCCRUWWY.mjs.map +0 -1
  122. package/dist/chunk-M2EGUO2F.mjs.map +0 -1
  123. package/dist/chunk-V66AZWPG.js.map +0 -1
  124. package/src/features/billing/contexts/BillingContext.tsx +0 -95
  125. package/src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx +0 -98
  126. package/src/features/billing/stripe-product/components/containers/ProductsAdminPageContainer.tsx +0 -27
  127. /package/dist/{BlockNoteEditor-GWAV5ESO.mjs.map → BlockNoteEditor-VVAY73V2.mjs.map} +0 -0
  128. /package/dist/{chunk-3LVSA7IM.mjs.map → chunk-PHEFWL6L.mjs.map} +0 -0
@@ -10,6 +10,7 @@ import {
10
10
  AlertDialogFooter,
11
11
  AlertDialogHeader,
12
12
  AlertDialogTitle,
13
+ AlertTitle,
13
14
  Badge,
14
15
  Button,
15
16
  Card,
@@ -19,7 +20,9 @@ import {
19
20
  CardHeader,
20
21
  CardTitle,
21
22
  Checkbox,
22
- CommonEditorButtons,
23
+ ConfirmDialog,
24
+ ContentListTable,
25
+ DetailField,
23
26
  Dialog,
24
27
  DialogContent,
25
28
  DialogDescription,
@@ -29,6 +32,9 @@ import {
29
32
  DropdownMenuContent,
30
33
  DropdownMenuItem,
31
34
  DropdownMenuTrigger,
35
+ EditorSheet,
36
+ EmptyState,
37
+ FieldLabel,
32
38
  Form,
33
39
  FormCheckbox,
34
40
  FormInput,
@@ -36,6 +42,7 @@ import {
36
42
  FormTextarea,
37
43
  Input,
38
44
  Label,
45
+ Link,
39
46
  RoundPageContainer,
40
47
  SectionHeader,
41
48
  SharedProvider,
@@ -49,12 +56,18 @@ import {
49
56
  Tabs,
50
57
  TabsList,
51
58
  TabsTrigger,
52
- useCurrentUserContext
53
- } from "../chunk-M2EGUO2F.mjs";
59
+ errorToast,
60
+ formatCurrency,
61
+ formatInterval,
62
+ useCurrentUserContext,
63
+ useDataListRetriever,
64
+ usePageUrlGenerator
65
+ } from "../chunk-J54546YC.mjs";
54
66
  import {
55
67
  getRoleId,
56
- getStripePublishableKey
57
- } from "../chunk-3LVSA7IM.mjs";
68
+ getStripePublishableKey,
69
+ useI18nRouter
70
+ } from "../chunk-PHEFWL6L.mjs";
58
71
  import {
59
72
  FeatureService,
60
73
  Modules,
@@ -65,8 +78,9 @@ import {
65
78
  StripePromotionCodeService,
66
79
  StripeSubscriptionService,
67
80
  StripeUsageService,
68
- cn
69
- } from "../chunk-LCCRUWWY.mjs";
81
+ cn,
82
+ rehydrate
83
+ } from "../chunk-TWXKAY34.mjs";
70
84
  import "../chunk-AUXK7QSA.mjs";
71
85
  import "../chunk-C7C7VY4F.mjs";
72
86
  import "../chunk-E5YA5Z5X.mjs";
@@ -853,36 +867,6 @@ import { useEffect as useEffect4, useState as useState6 } from "react";
853
867
  // src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx
854
868
  import { useState as useState5 } from "react";
855
869
 
856
- // src/features/billing/components/utils/currency.ts
857
- function formatInterval(price) {
858
- if (price.priceType === "one_time" || !price.recurring) {
859
- return "one-time";
860
- }
861
- const { interval, intervalCount } = price.recurring;
862
- if (intervalCount === 1) {
863
- return `/${interval}`;
864
- }
865
- const pluralInterval = interval === "day" ? "days" : interval === "week" ? "weeks" : interval === "month" ? "months" : "years";
866
- return `/${intervalCount} ${pluralInterval}`;
867
- }
868
- __name(formatInterval, "formatInterval");
869
- function formatCurrency(amount, currency) {
870
- if (amount === void 0) return "$0.00";
871
- const dollars = amount / 100;
872
- try {
873
- return new Intl.NumberFormat("en-US", {
874
- style: "currency",
875
- currency: currency.toUpperCase(),
876
- minimumFractionDigits: 2,
877
- maximumFractionDigits: 2
878
- }).format(dollars);
879
- } catch (error) {
880
- console.error("Error formatting currency:", error);
881
- return `$${dollars.toFixed(2)}`;
882
- }
883
- }
884
- __name(formatCurrency, "formatCurrency");
885
-
886
870
  // src/features/billing/components/utils/date.ts
887
871
  function formatDate3(date) {
888
872
  if (!date) return "N/A";
@@ -3103,390 +3087,312 @@ function isStripeConfigured() {
3103
3087
  }
3104
3088
  __name(isStripeConfigured, "isStripeConfigured");
3105
3089
 
3106
- // src/features/billing/contexts/BillingContext.tsx
3107
- import { useTranslations } from "next-intl";
3108
- import { createContext, useCallback as useCallback5, useContext, useState as useState16 } from "react";
3090
+ // src/features/billing/contexts/PriceContext.tsx
3091
+ import { useTranslations as useTranslations3 } from "next-intl";
3092
+ import { createContext, useCallback as useCallback6, useContext, useMemo as useMemo6, useState as useState17 } from "react";
3109
3093
 
3110
- // src/features/billing/stripe-product/components/forms/ProductEditor.tsx
3111
- import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
3094
+ // src/features/billing/stripe-price/components/forms/PriceArchiver.tsx
3095
+ import { ArchiveIcon, RotateCcwIcon } from "lucide-react";
3096
+ import { useTranslations } from "next-intl";
3112
3097
  import { useState as useState15 } from "react";
3113
- import { useForm as useForm2 } from "react-hook-form";
3114
- import { v4 as v42 } from "uuid";
3115
- import { z as z2 } from "zod";
3116
- import { jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
3117
- function ProductEditor({ product, open, onOpenChange, onSuccess }) {
3118
- const [isSubmitting, setIsSubmitting] = useState15(false);
3119
- const formSchema2 = z2.object({
3120
- name: z2.string().min(1, { message: "Product name is required" }),
3121
- description: z2.string().min(1, { message: "Description is required" }),
3122
- active: z2.boolean()
3123
- });
3124
- const form = useForm2({
3125
- resolver: zodResolver2(formSchema2),
3126
- defaultValues: {
3127
- name: product?.name || "",
3128
- description: product?.description || "",
3129
- active: product?.active ?? true
3130
- }
3131
- });
3132
- const onSubmit = /* @__PURE__ */ __name(async (values) => {
3133
- setIsSubmitting(true);
3134
- try {
3135
- if (product) {
3136
- await StripeProductService.updateProduct({
3137
- id: product.id,
3138
- name: values.name,
3139
- description: values.description,
3140
- active: values.active
3141
- });
3142
- } else {
3143
- await StripeProductService.createProduct({
3144
- id: v42(),
3145
- name: values.name,
3146
- description: values.description,
3147
- active: values.active
3148
- });
3149
- }
3150
- onSuccess();
3151
- onOpenChange(false);
3152
- } catch (error) {
3153
- console.error("[ProductEditor] Failed to save product:", error);
3154
- } finally {
3155
- setIsSubmitting(false);
3156
- }
3157
- }, "onSubmit");
3158
- return /* @__PURE__ */ jsx40(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs35(DialogContent, { className: "max-w-2xl", children: [
3159
- /* @__PURE__ */ jsxs35(DialogHeader, { children: [
3160
- /* @__PURE__ */ jsx40(DialogTitle, { children: product ? "Edit Product" : "Create Product" }),
3161
- /* @__PURE__ */ jsx40(DialogDescription, { children: product ? `Update the details for ${product.name}` : "Create a new product to offer to your customers" })
3098
+ import { Fragment as Fragment9, jsx as jsx40, jsxs as jsxs35 } from "react/jsx-runtime";
3099
+ function PriceArchiver({ price }) {
3100
+ const t = useTranslations();
3101
+ const { archivePrice, restorePrice } = usePriceContext();
3102
+ const [open, setOpen] = useState15(false);
3103
+ const isArchived = !price.active;
3104
+ const scope = isArchived ? "restore" : "archive";
3105
+ return /* @__PURE__ */ jsxs35(Fragment9, { children: [
3106
+ /* @__PURE__ */ jsxs35(Button, { variant: "outline", size: "sm", onClick: () => setOpen(true), children: [
3107
+ isArchived ? /* @__PURE__ */ jsx40(RotateCcwIcon, {}) : /* @__PURE__ */ jsx40(ArchiveIcon, {}),
3108
+ t(`billing.admin.prices.${scope}.confirm`)
3162
3109
  ] }),
3163
- /* @__PURE__ */ jsx40(Form, { ...form, children: /* @__PURE__ */ jsxs35("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-col gap-y-4", children: [
3164
- /* @__PURE__ */ jsx40(FormInput, { form, id: "name", name: "Product Name", placeholder: "Enter product name", isRequired: true }),
3165
- /* @__PURE__ */ jsx40(
3166
- FormTextarea,
3167
- {
3168
- form,
3169
- id: "description",
3170
- name: "Description",
3171
- placeholder: "Enter product description",
3172
- className: "min-h-32"
3110
+ /* @__PURE__ */ jsx40(
3111
+ ConfirmDialog,
3112
+ {
3113
+ open,
3114
+ onOpenChange: setOpen,
3115
+ title: t(`billing.admin.prices.${scope}.title`),
3116
+ description: t(`billing.admin.prices.${scope}.description`, { label: priceLabel(price) }),
3117
+ confirmLabel: t(`billing.admin.prices.${scope}.confirm`),
3118
+ cancelLabel: t("ui.buttons.cancel"),
3119
+ destructive: !isArchived,
3120
+ onConfirm: async () => {
3121
+ try {
3122
+ if (isArchived) await restorePrice(price.id);
3123
+ else await archivePrice(price.id);
3124
+ } catch (error) {
3125
+ errorToast({ title: t(`billing.admin.prices.${scope}.title`), error });
3126
+ throw error;
3127
+ }
3173
3128
  }
3174
- ),
3175
- /* @__PURE__ */ jsx40(FormCheckbox, { form, id: "active", name: "Active" }),
3176
- /* @__PURE__ */ jsx40(CommonEditorButtons, { isEdit: !!product, form, disabled: isSubmitting, setOpen: onOpenChange })
3177
- ] }) })
3178
- ] }) });
3129
+ }
3130
+ )
3131
+ ] });
3179
3132
  }
3180
- __name(ProductEditor, "ProductEditor");
3181
-
3182
- // src/features/billing/contexts/BillingContext.tsx
3183
- import { jsx as jsx41 } from "react/jsx-runtime";
3184
- var BillingContext = createContext(void 0);
3185
- var BillingProvider = /* @__PURE__ */ __name(({ children }) => {
3186
- const t = useTranslations();
3187
- const { hasRole } = useCurrentUserContext();
3188
- const [showCreateProduct, setShowCreateProduct] = useState16(false);
3189
- const [refreshToken, setRefreshToken] = useState16(0);
3190
- const refresh = useCallback5(() => setRefreshToken((value) => value + 1), []);
3191
- const breadcrumbs = [{ name: t(`billing.admin.products.title`) }];
3192
- const title = /* @__PURE__ */ __name(() => {
3193
- const response = { type: t(`billing.admin.products.title`) };
3194
- if (hasRole(getRoleId().Administrator)) {
3195
- response.functions = [
3196
- /* @__PURE__ */ jsx41(Button, { onClick: () => setShowCreateProduct(true), children: t(`billing.admin.products.create`) }, "productEditorTrigger"),
3197
- showCreateProduct ? /* @__PURE__ */ jsx41(
3198
- ProductEditor,
3199
- {
3200
- open: showCreateProduct,
3201
- onOpenChange: setShowCreateProduct,
3202
- onSuccess: () => {
3203
- setShowCreateProduct(false);
3204
- refresh();
3205
- }
3206
- },
3207
- "productEditor"
3208
- ) : null
3209
- ];
3210
- }
3211
- return response;
3212
- }, "title");
3213
- return /* @__PURE__ */ jsx41(SharedProvider, { value: { breadcrumbs, title: title() }, children: /* @__PURE__ */ jsx41(BillingContext.Provider, { value: { refreshToken, refresh }, children }) });
3214
- }, "BillingProvider");
3215
- var useBillingContext = /* @__PURE__ */ __name(() => {
3216
- const context = useContext(BillingContext);
3217
- if (context === void 0) {
3218
- throw new Error("useBillingContext must be used within a BillingProvider");
3219
- }
3220
- return context;
3221
- }, "useBillingContext");
3222
- var useOptionalBillingContext = /* @__PURE__ */ __name(() => useContext(BillingContext), "useOptionalBillingContext");
3133
+ __name(PriceArchiver, "PriceArchiver");
3223
3134
 
3224
3135
  // src/features/billing/stripe-price/components/forms/PriceEditor.tsx
3225
- import { zodResolver as zodResolver3 } from "@hookform/resolvers/zod";
3226
- import { AlertCircle as AlertCircle3, PlusIcon, XIcon } from "lucide-react";
3227
- import { useEffect as useEffect10, useState as useState17 } from "react";
3228
- import { useForm as useForm3 } from "react-hook-form";
3229
- import { v4 as v43 } from "uuid";
3230
- import { z as z3 } from "zod";
3231
- import { jsx as jsx42, jsxs as jsxs36 } from "react/jsx-runtime";
3232
- function PriceEditor({ productId, price, open, onOpenChange, onSuccess }) {
3233
- const [isSubmitting, setIsSubmitting] = useState17(false);
3234
- const [allFeatures, setAllFeatures] = useState17([]);
3136
+ import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
3137
+ import { PlusIcon, XIcon } from "lucide-react";
3138
+ import { useTranslations as useTranslations2 } from "next-intl";
3139
+ import { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo5, useState as useState16 } from "react";
3140
+ import { useForm as useForm2 } from "react-hook-form";
3141
+ import { v4 as v42 } from "uuid";
3142
+ import { z as z2 } from "zod";
3143
+ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
3144
+ function PriceEditorInternal({
3145
+ productId,
3146
+ price,
3147
+ propagateChanges,
3148
+ onSuccess,
3149
+ trigger,
3150
+ forceShow,
3151
+ onClose,
3152
+ dialogOpen,
3153
+ onDialogOpenChange
3154
+ }) {
3155
+ const t = useTranslations2();
3156
+ const router = useI18nRouter();
3157
+ const { createPrice, updatePrice, productId: contextProductId } = usePriceContext();
3158
+ const [allFeatures, setAllFeatures] = useState16([]);
3159
+ const isEdit = !!price;
3160
+ const targetProductId = productId ?? price?.product?.id ?? contextProductId;
3235
3161
  useEffect10(() => {
3236
3162
  const fetchFeatures = /* @__PURE__ */ __name(async () => {
3237
- try {
3238
- const features = await FeatureService.findMany({});
3239
- setAllFeatures(features);
3240
- } catch (error) {
3241
- console.error("[PriceEditor] Failed to fetch features:", error);
3242
- }
3163
+ setAllFeatures(await FeatureService.findMany({}));
3243
3164
  }, "fetchFeatures");
3244
- fetchFeatures();
3165
+ void fetchFeatures();
3245
3166
  }, []);
3246
- const formSchema2 = z3.object({
3247
- unitAmount: z3.preprocess(
3248
- (val) => typeof val === "string" ? parseFloat(val) : val,
3249
- z3.number().min(0, { message: "Amount must be 0 or greater" })
3250
- ),
3251
- currency: z3.string().min(1, { message: "Currency is required" }),
3252
- interval: z3.enum(["one_time", "day", "week", "month", "year"]),
3253
- intervalCount: z3.preprocess(
3254
- (val) => val === "" || val === void 0 ? void 0 : typeof val === "string" ? parseInt(val, 10) : val,
3255
- z3.number().min(1).optional()
3256
- ),
3257
- usageType: z3.enum(["licensed", "metered"]).optional(),
3258
- nickname: z3.string().optional(),
3259
- active: z3.boolean(),
3260
- isTrial: z3.boolean(),
3261
- description: z3.string().optional(),
3262
- features: z3.array(z3.string()),
3263
- token: z3.string(),
3264
- featureIds: z3.array(z3.string())
3265
- });
3266
- const isEditMode = !!price;
3267
- const defaultUnitAmount = price?.unitAmount ? price.unitAmount / 100 : 0;
3268
- const coreFeatureIds = allFeatures.filter((f) => f.isCore).map((f) => f.id);
3269
- const defaultFeatureIds = [.../* @__PURE__ */ new Set([...price?.priceFeatures?.map((f) => f.id) ?? [], ...coreFeatureIds])];
3270
- const form = useForm3({
3271
- resolver: zodResolver3(formSchema2),
3272
- defaultValues: {
3273
- unitAmount: defaultUnitAmount,
3274
- currency: price?.currency || "usd",
3275
- interval: price?.priceType === "one_time" ? "one_time" : price?.recurring?.interval || "month",
3276
- intervalCount: price?.recurring?.intervalCount || 1,
3277
- usageType: price?.recurring?.usageType || "licensed",
3278
- nickname: price?.nickname || "",
3279
- active: price?.active ?? true,
3167
+ const formSchema2 = useMemo5(
3168
+ () => z2.object({
3169
+ unitAmount: z2.preprocess(
3170
+ (value) => typeof value === "string" ? parseFloat(value) : value,
3171
+ z2.number().min(0, { message: t("billing.admin.prices.errors.amount") })
3172
+ ),
3173
+ currency: z2.string().min(1, { message: t("billing.admin.prices.errors.currency") }),
3174
+ interval: z2.enum(["one_time", "day", "week", "month", "year"]),
3175
+ intervalCount: z2.preprocess(
3176
+ (value) => value === "" || value === void 0 ? void 0 : typeof value === "string" ? parseInt(value, 10) : value,
3177
+ z2.number().min(1).optional()
3178
+ ),
3179
+ usageType: z2.enum(["licensed", "metered"]).optional(),
3180
+ nickname: z2.string().optional(),
3181
+ isTrial: z2.boolean(),
3182
+ description: z2.string().optional(),
3183
+ features: z2.array(z2.string()),
3184
+ token: z2.string(),
3185
+ featureIds: z2.array(z2.string())
3186
+ }),
3187
+ [t]
3188
+ );
3189
+ const getDefaultValues = useCallback5(() => {
3190
+ const coreFeatureIds = allFeatures.filter((feature) => feature.isCore).map((feature) => feature.id);
3191
+ return {
3192
+ unitAmount: price?.unitAmount ? price.unitAmount / 100 : 0,
3193
+ currency: price?.currency ?? "usd",
3194
+ interval: price?.priceType === "one_time" ? "one_time" : price?.recurring?.interval ?? "month",
3195
+ intervalCount: price?.recurring?.intervalCount ?? 1,
3196
+ usageType: price?.recurring?.usageType ?? "licensed",
3197
+ nickname: price?.nickname ?? "",
3280
3198
  isTrial: price?.isTrial ?? false,
3281
- description: price?.description || "",
3282
- features: price?.features || [],
3199
+ description: price?.description ?? "",
3200
+ features: price?.features ?? [],
3283
3201
  token: price?.token?.toString() ?? "",
3284
- featureIds: defaultFeatureIds
3285
- }
3202
+ featureIds: [.../* @__PURE__ */ new Set([...price?.priceFeatures?.map((f) => f.id) ?? [], ...coreFeatureIds])]
3203
+ };
3204
+ }, [price, allFeatures]);
3205
+ const form = useForm2({
3206
+ resolver: zodResolver2(formSchema2),
3207
+ defaultValues: getDefaultValues()
3286
3208
  });
3287
- useEffect10(() => {
3288
- if (open) {
3289
- const currentCoreFeatureIds = allFeatures.filter((f) => f.isCore).map((f) => f.id);
3290
- const resetFeatureIds = [
3291
- .../* @__PURE__ */ new Set([...price?.priceFeatures?.map((f) => f.id) ?? [], ...currentCoreFeatureIds])
3292
- ];
3293
- form.reset({
3294
- unitAmount: price?.unitAmount ? price.unitAmount / 100 : 0,
3295
- currency: price?.currency || "usd",
3296
- interval: price?.priceType === "one_time" ? "one_time" : price?.recurring?.interval || "month",
3297
- intervalCount: price?.recurring?.intervalCount || 1,
3298
- usageType: price?.recurring?.usageType || "licensed",
3299
- nickname: price?.nickname || "",
3300
- active: price?.active ?? true,
3301
- isTrial: price?.isTrial ?? false,
3302
- description: price?.description || "",
3303
- features: price?.features || [],
3304
- token: price?.token?.toString() ?? "",
3305
- featureIds: resetFeatureIds
3306
- });
3307
- }
3308
- }, [open, price?.id, allFeatures]);
3309
3209
  const watchInterval = form.watch("interval");
3310
3210
  const isRecurring = watchInterval !== "one_time";
3311
- const onSubmit = /* @__PURE__ */ __name(async (values) => {
3312
- setIsSubmitting(true);
3313
- try {
3314
- const unitAmountInCents = Math.round(values.unitAmount * 100);
3315
- if (isEditMode) {
3316
- await StripePriceService.updatePrice({
3317
- id: price.id,
3318
- nickname: values.nickname || void 0,
3319
- description: values.description || void 0,
3320
- features: values.features.filter((f) => f.trim()) || void 0,
3321
- token: values.token ? parseInt(values.token, 10) : void 0,
3322
- // Only include isTrial and featureIds for recurring prices
3323
- ...price?.priceType === "recurring" ? { isTrial: values.isTrial, featureIds: values.featureIds } : {}
3324
- });
3325
- } else {
3326
- const createInput = {
3327
- id: v43(),
3328
- productId,
3329
- currency: values.currency,
3330
- unitAmount: unitAmountInCents
3331
- };
3332
- if (isRecurring) {
3333
- createInput.recurring = {
3334
- interval: values.interval,
3335
- intervalCount: values.intervalCount || 1,
3336
- usageType: values.usageType || "licensed"
3337
- };
3338
- }
3339
- if (values.nickname) {
3340
- createInput.nickname = values.nickname;
3341
- }
3342
- if (values.description) {
3343
- createInput.description = values.description;
3344
- }
3345
- const filteredFeatures = values.features.filter((f) => f.trim());
3346
- if (filteredFeatures.length > 0) {
3347
- createInput.features = filteredFeatures;
3348
- }
3349
- if (values.token) {
3350
- createInput.token = parseInt(values.token, 10);
3351
- }
3352
- if (isRecurring) {
3353
- createInput.isTrial = values.isTrial;
3354
- if (values.featureIds.length > 0) {
3355
- createInput.featureIds = values.featureIds;
3356
- }
3357
- }
3358
- await StripePriceService.createPrice(createInput);
3359
- }
3360
- onSuccess();
3361
- onOpenChange(false);
3362
- } catch (error) {
3363
- console.error("[PriceEditor] Failed to save price:", error);
3364
- } finally {
3365
- setIsSubmitting(false);
3366
- }
3367
- }, "onSubmit");
3368
3211
  const currencyOptions = [
3369
3212
  { id: "usd", text: "USD ($)" },
3370
3213
  { id: "eur", text: "EUR (\u20AC)" },
3371
3214
  { id: "gbp", text: "GBP (\xA3)" }
3372
3215
  ];
3373
3216
  const intervalOptions = [
3374
- { id: "one_time", text: "One-time" },
3375
- { id: "day", text: "Daily" },
3376
- { id: "week", text: "Weekly" },
3377
- { id: "month", text: "Monthly" },
3378
- { id: "year", text: "Yearly" }
3217
+ { id: "one_time", text: t("billing.admin.prices.interval.one_time") },
3218
+ { id: "day", text: t("billing.admin.prices.interval.day") },
3219
+ { id: "week", text: t("billing.admin.prices.interval.week") },
3220
+ { id: "month", text: t("billing.admin.prices.interval.month") },
3221
+ { id: "year", text: t("billing.admin.prices.interval.year") }
3379
3222
  ];
3380
3223
  const usageTypeOptions = [
3381
- { id: "licensed", text: "Licensed (per unit)" },
3382
- { id: "metered", text: "Metered (usage-based)" }
3224
+ { id: "licensed", text: t("billing.admin.prices.usage.licensed") },
3225
+ { id: "metered", text: t("billing.admin.prices.usage.metered") }
3383
3226
  ];
3384
- return /* @__PURE__ */ jsx42(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs36(DialogContent, { className: "max-w-2xl", children: [
3385
- /* @__PURE__ */ jsxs36(DialogHeader, { children: [
3386
- /* @__PURE__ */ jsx42(DialogTitle, { children: isEditMode ? "Edit Price" : "Create Price" }),
3387
- /* @__PURE__ */ jsx42(DialogDescription, { children: isEditMode ? "Update the price details. Note: Only nickname and active status can be changed." : "Create a new price for this product" })
3388
- ] }),
3389
- isEditMode && /* @__PURE__ */ jsxs36("div", { className: "bg-primary/10 border-primary/30 flex gap-x-3 rounded-lg border p-4", children: [
3390
- /* @__PURE__ */ jsx42(AlertCircle3, { className: "text-primary mt-0.5 h-5 w-5 flex-shrink-0" }),
3391
- /* @__PURE__ */ jsxs36("div", { className: "text-foreground text-sm", children: [
3392
- /* @__PURE__ */ jsx42("p", { className: "font-semibold mb-1", children: "Stripe Price Immutability" }),
3393
- /* @__PURE__ */ jsx42("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." })
3394
- ] })
3395
- ] }),
3396
- /* @__PURE__ */ jsx42(Form, { ...form, children: /* @__PURE__ */ jsxs36("form", { onSubmit: form.handleSubmit(onSubmit), className: "flex flex-col gap-y-4", children: [
3397
- /* @__PURE__ */ jsxs36("div", { className: "grid grid-cols-2 gap-x-4", children: [
3398
- /* @__PURE__ */ jsx42(
3399
- FormInput,
3227
+ return /* @__PURE__ */ jsx41(
3228
+ EditorSheet,
3229
+ {
3230
+ form,
3231
+ entityType: t("billing.admin.prices.entity"),
3232
+ entityName: price ? priceLabel(price) : void 0,
3233
+ isEdit,
3234
+ module: Modules.StripePrice,
3235
+ size: "lg",
3236
+ propagateChanges,
3237
+ onSuccess,
3238
+ onNavigate: (url) => router.push(url),
3239
+ onSubmit: async (values) => {
3240
+ const trimmedFeatures = values.features.filter((feature) => feature.trim());
3241
+ if (isEdit) {
3242
+ const patch = {
3243
+ id: price.id,
3244
+ nickname: values.nickname || void 0,
3245
+ description: values.description || void 0,
3246
+ features: trimmedFeatures.length > 0 ? trimmedFeatures : void 0,
3247
+ token: values.token ? parseInt(values.token, 10) : void 0,
3248
+ // Stripe one-time prices carry neither a trial flag nor platform
3249
+ // features, so those two only travel for recurring prices.
3250
+ ...price.priceType === "recurring" ? { isTrial: values.isTrial, featureIds: values.featureIds } : {}
3251
+ };
3252
+ return await updatePrice(patch);
3253
+ }
3254
+ if (!targetProductId) throw new Error("PriceEditor requires a productId to create a price");
3255
+ const payload = {
3256
+ id: v42(),
3257
+ productId: targetProductId,
3258
+ currency: values.currency,
3259
+ unitAmount: Math.round(values.unitAmount * 100)
3260
+ };
3261
+ if (isRecurring) {
3262
+ payload.recurring = {
3263
+ interval: values.interval,
3264
+ intervalCount: values.intervalCount ?? 1,
3265
+ usageType: values.usageType ?? "licensed"
3266
+ };
3267
+ payload.isTrial = values.isTrial;
3268
+ if (values.featureIds.length > 0) payload.featureIds = values.featureIds;
3269
+ }
3270
+ if (values.nickname) payload.nickname = values.nickname;
3271
+ if (values.description) payload.description = values.description;
3272
+ if (trimmedFeatures.length > 0) payload.features = trimmedFeatures;
3273
+ if (values.token) payload.token = parseInt(values.token, 10);
3274
+ return await createPrice(payload);
3275
+ },
3276
+ onReset: getDefaultValues,
3277
+ trigger,
3278
+ forceShow,
3279
+ onClose,
3280
+ dialogOpen,
3281
+ onDialogOpenChange,
3282
+ children: /* @__PURE__ */ jsxs36("div", { className: "flex w-full flex-col gap-y-4", children: [
3283
+ isEdit && /* @__PURE__ */ jsxs36(Alert, { children: [
3284
+ /* @__PURE__ */ jsx41(AlertTitle, { children: t("billing.admin.prices.immutability.title") }),
3285
+ /* @__PURE__ */ jsx41(AlertDescription, { children: t("billing.admin.prices.immutability.description") })
3286
+ ] }),
3287
+ /* @__PURE__ */ jsxs36("div", { className: "grid grid-cols-2 gap-x-4", children: [
3288
+ /* @__PURE__ */ jsx41(
3289
+ FormInput,
3290
+ {
3291
+ form,
3292
+ id: "unitAmount",
3293
+ type: "decimal",
3294
+ name: t("billing.admin.prices.fields.amount"),
3295
+ placeholder: t("billing.admin.prices.placeholders.amount"),
3296
+ disabled: isEdit,
3297
+ isRequired: true
3298
+ }
3299
+ ),
3300
+ /* @__PURE__ */ jsx41(
3301
+ FormSelect,
3302
+ {
3303
+ form,
3304
+ id: "currency",
3305
+ name: t("billing.admin.prices.fields.currency"),
3306
+ values: currencyOptions,
3307
+ disabled: isEdit
3308
+ }
3309
+ )
3310
+ ] }),
3311
+ /* @__PURE__ */ jsx41(
3312
+ FormSelect,
3400
3313
  {
3401
3314
  form,
3402
- id: "unitAmount",
3403
- name: "Amount (in dollars)",
3404
- placeholder: "9.99",
3405
- disabled: isEditMode,
3406
- isRequired: true
3315
+ id: "interval",
3316
+ name: t("billing.admin.prices.fields.interval"),
3317
+ values: intervalOptions,
3318
+ disabled: isEdit
3407
3319
  }
3408
3320
  ),
3409
- /* @__PURE__ */ jsx42(FormSelect, { form, id: "currency", name: "Currency", values: currencyOptions, disabled: isEditMode })
3410
- ] }),
3411
- /* @__PURE__ */ jsx42(
3412
- FormSelect,
3413
- {
3414
- form,
3415
- id: "interval",
3416
- name: "Billing Interval",
3417
- values: intervalOptions,
3418
- disabled: isEditMode
3419
- }
3420
- ),
3421
- isRecurring && /* @__PURE__ */ jsxs36("div", { className: "grid grid-cols-2 gap-x-4", children: [
3422
- /* @__PURE__ */ jsx42(
3321
+ isRecurring && /* @__PURE__ */ jsxs36("div", { className: "grid grid-cols-2 gap-x-4", children: [
3322
+ /* @__PURE__ */ jsx41(
3323
+ FormInput,
3324
+ {
3325
+ form,
3326
+ id: "intervalCount",
3327
+ type: "number",
3328
+ name: t("billing.admin.prices.fields.intervalCount"),
3329
+ placeholder: t("billing.admin.prices.placeholders.intervalCount"),
3330
+ disabled: isEdit
3331
+ }
3332
+ ),
3333
+ /* @__PURE__ */ jsx41(
3334
+ FormSelect,
3335
+ {
3336
+ form,
3337
+ id: "usageType",
3338
+ name: t("billing.admin.prices.fields.usageType"),
3339
+ values: usageTypeOptions,
3340
+ disabled: isEdit
3341
+ }
3342
+ )
3343
+ ] }),
3344
+ /* @__PURE__ */ jsx41(
3423
3345
  FormInput,
3424
3346
  {
3425
3347
  form,
3426
- id: "intervalCount",
3427
- name: "Interval Count",
3428
- placeholder: "1",
3429
- type: "number",
3430
- disabled: isEditMode
3348
+ id: "nickname",
3349
+ name: t("billing.admin.prices.fields.nickname"),
3350
+ placeholder: t("billing.admin.prices.placeholders.nickname")
3431
3351
  }
3432
3352
  ),
3433
- /* @__PURE__ */ jsx42(
3434
- FormSelect,
3353
+ /* @__PURE__ */ jsx41(
3354
+ FormTextarea,
3435
3355
  {
3436
3356
  form,
3437
- id: "usageType",
3438
- name: "Usage Type",
3439
- values: usageTypeOptions,
3440
- disabled: isEditMode
3357
+ id: "description",
3358
+ name: t("billing.admin.prices.fields.description"),
3359
+ placeholder: t("billing.admin.prices.placeholders.description"),
3360
+ className: "min-h-24"
3441
3361
  }
3442
- )
3443
- ] }),
3444
- /* @__PURE__ */ jsx42(
3445
- FormInput,
3446
- {
3447
- form,
3448
- id: "nickname",
3449
- name: "Nickname (optional)",
3450
- placeholder: "e.g., Standard Plan, Pro Tier"
3451
- }
3452
- ),
3453
- /* @__PURE__ */ jsx42(
3454
- FormTextarea,
3455
- {
3456
- form,
3457
- id: "description",
3458
- name: "Description (optional)",
3459
- placeholder: "Describe what this price tier includes...",
3460
- className: "min-h-24"
3461
- }
3462
- ),
3463
- /* @__PURE__ */ jsx42(FormInput, { form, id: "token", name: "Token (optional)", placeholder: "Enter token value" }),
3464
- /* @__PURE__ */ jsxs36("div", { className: "space-y-2", children: [
3465
- /* @__PURE__ */ jsx42(Label, { children: "Features (optional)" }),
3466
- /* @__PURE__ */ jsxs36("div", { className: "space-y-2", children: [
3362
+ ),
3363
+ /* @__PURE__ */ jsx41(
3364
+ FormInput,
3365
+ {
3366
+ form,
3367
+ id: "token",
3368
+ type: "number",
3369
+ name: t("billing.admin.prices.fields.token"),
3370
+ placeholder: t("billing.admin.prices.placeholders.token")
3371
+ }
3372
+ ),
3373
+ /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-y-2", children: [
3374
+ /* @__PURE__ */ jsx41(FieldLabel, { children: t("billing.admin.prices.fields.features") }),
3467
3375
  form.watch("features").map((_, index) => /* @__PURE__ */ jsxs36("div", { className: "flex gap-2", children: [
3468
- /* @__PURE__ */ jsx42(
3376
+ /* @__PURE__ */ jsx41(
3469
3377
  Input,
3470
3378
  {
3471
3379
  ...form.register(`features.${index}`),
3472
- placeholder: `Feature ${index + 1}`,
3380
+ placeholder: t("billing.admin.prices.placeholders.feature", { index: index + 1 }),
3473
3381
  className: "flex-1"
3474
3382
  }
3475
3383
  ),
3476
- /* @__PURE__ */ jsx42(
3384
+ /* @__PURE__ */ jsx41(
3477
3385
  Button,
3478
3386
  {
3479
3387
  type: "button",
3480
3388
  variant: "outline",
3481
3389
  size: "icon",
3482
- onClick: () => {
3483
- const currentFeatures = form.getValues("features");
3484
- form.setValue(
3485
- "features",
3486
- currentFeatures.filter((_2, i) => i !== index)
3487
- );
3488
- },
3489
- children: /* @__PURE__ */ jsx42(XIcon, { className: "h-4 w-4" })
3390
+ "aria-label": t("billing.admin.prices.actions.removeFeature"),
3391
+ onClick: () => form.setValue(
3392
+ "features",
3393
+ form.getValues("features").filter((_2, i) => i !== index)
3394
+ ),
3395
+ children: /* @__PURE__ */ jsx41(XIcon, {})
3490
3396
  }
3491
3397
  )
3492
3398
  ] }, index)),
@@ -3496,502 +3402,747 @@ function PriceEditor({ productId, price, open, onOpenChange, onSuccess }) {
3496
3402
  type: "button",
3497
3403
  variant: "outline",
3498
3404
  size: "sm",
3499
- onClick: () => {
3500
- const currentFeatures = form.getValues("features");
3501
- form.setValue("features", [...currentFeatures, ""]);
3502
- },
3503
- className: "mt-2",
3405
+ className: "self-start",
3406
+ onClick: () => form.setValue("features", [...form.getValues("features"), ""]),
3504
3407
  children: [
3505
- /* @__PURE__ */ jsx42(PlusIcon, { className: "h-4 w-4 mr-2" }),
3506
- "Add Feature"
3408
+ /* @__PURE__ */ jsx41(PlusIcon, {}),
3409
+ t("billing.admin.prices.actions.addFeature")
3507
3410
  ]
3508
3411
  }
3509
3412
  )
3510
- ] })
3511
- ] }),
3512
- isRecurring && allFeatures.length > 0 && /* @__PURE__ */ jsxs36("div", { className: "space-y-2", children: [
3513
- /* @__PURE__ */ jsx42(Label, { children: "Platform Features" }),
3514
- /* @__PURE__ */ jsx42("div", { className: "border rounded-md p-4 space-y-2 max-h-48 overflow-y-auto", children: allFeatures.map((feature) => {
3515
- const isCore = feature.isCore;
3516
- const isChecked = form.watch("featureIds").includes(feature.id);
3517
- return /* @__PURE__ */ jsxs36("div", { className: "flex items-center space-x-2", children: [
3518
- /* @__PURE__ */ jsx42(
3519
- "input",
3520
- {
3521
- type: "checkbox",
3522
- id: `feature-${feature.id}`,
3523
- checked: isChecked,
3524
- disabled: isCore,
3525
- onChange: (e) => {
3526
- const currentIds = form.getValues("featureIds");
3527
- if (e.target.checked) {
3528
- form.setValue("featureIds", [...currentIds, feature.id]);
3529
- } else {
3530
- if (!isCore) {
3413
+ ] }),
3414
+ isRecurring && allFeatures.length > 0 && /* @__PURE__ */ jsxs36("div", { className: "flex flex-col gap-y-2", children: [
3415
+ /* @__PURE__ */ jsx41(FieldLabel, { children: t("billing.admin.prices.fields.platformFeatures") }),
3416
+ /* @__PURE__ */ jsx41("div", { className: "flex max-h-48 flex-col gap-y-2 overflow-y-auto rounded-md border p-4", children: allFeatures.map((feature) => {
3417
+ const isChecked = form.watch("featureIds").includes(feature.id);
3418
+ return /* @__PURE__ */ jsxs36("div", { className: "flex items-center gap-x-2", children: [
3419
+ /* @__PURE__ */ jsx41(
3420
+ Checkbox,
3421
+ {
3422
+ id: `feature-${feature.id}`,
3423
+ checked: isChecked,
3424
+ disabled: feature.isCore,
3425
+ onCheckedChange: (checked) => {
3426
+ const current = form.getValues("featureIds");
3427
+ if (checked) form.setValue("featureIds", [.../* @__PURE__ */ new Set([...current, feature.id])]);
3428
+ else if (!feature.isCore)
3531
3429
  form.setValue(
3532
3430
  "featureIds",
3533
- currentIds.filter((id) => id !== feature.id)
3431
+ current.filter((id) => id !== feature.id)
3534
3432
  );
3535
- }
3536
3433
  }
3537
- },
3538
- className: "h-4 w-4 rounded border-gray-300 text-primary focus:ring-primary disabled:opacity-50"
3539
- }
3540
- ),
3541
- /* @__PURE__ */ jsxs36(
3542
- "label",
3543
- {
3544
- htmlFor: `feature-${feature.id}`,
3545
- className: `text-sm ${isCore ? "text-muted-foreground" : ""}`,
3546
- children: [
3547
- feature.name,
3548
- isCore && /* @__PURE__ */ jsx42("span", { className: "ml-2 text-xs text-muted-foreground", children: "(Core - Required)" })
3549
- ]
3550
- }
3551
- )
3552
- ] }, feature.id);
3553
- }) })
3554
- ] }),
3555
- /* @__PURE__ */ jsx42(FormCheckbox, { form, id: "active", name: "Active" }),
3556
- isRecurring && /* @__PURE__ */ jsx42(
3557
- FormCheckbox,
3558
- {
3559
- form,
3560
- id: "isTrial",
3561
- name: "Trial Price",
3562
- description: "Mark this as the trial subscription plan (only one price should be marked as trial)"
3563
- }
3564
- ),
3565
- /* @__PURE__ */ jsx42(CommonEditorButtons, { isEdit: isEditMode, form, disabled: isSubmitting, setOpen: onOpenChange })
3566
- ] }) })
3567
- ] }) });
3434
+ }
3435
+ ),
3436
+ /* @__PURE__ */ jsx41(FieldLabel, { htmlFor: `feature-${feature.id}`, children: feature.name }),
3437
+ feature.isCore && /* @__PURE__ */ jsx41("span", { className: "text-muted-foreground text-xs", children: t("billing.admin.prices.help.core") })
3438
+ ] }, feature.id);
3439
+ }) })
3440
+ ] }),
3441
+ isRecurring && /* @__PURE__ */ jsx41(
3442
+ FormCheckbox,
3443
+ {
3444
+ form,
3445
+ id: "isTrial",
3446
+ name: t("billing.admin.prices.fields.trial"),
3447
+ description: t("billing.admin.prices.help.trial")
3448
+ }
3449
+ )
3450
+ ] })
3451
+ }
3452
+ );
3453
+ }
3454
+ __name(PriceEditorInternal, "PriceEditorInternal");
3455
+ function PriceEditor(props) {
3456
+ return /* @__PURE__ */ jsx41(PriceEditorInternal, { ...props });
3568
3457
  }
3569
3458
  __name(PriceEditor, "PriceEditor");
3570
3459
 
3571
- // src/features/billing/stripe-price/components/lists/PricesList.tsx
3572
- import { Archive, DollarSign, Edit, RotateCcw } from "lucide-react";
3573
- import { useEffect as useEffect11, useState as useState18 } from "react";
3574
- import { jsx as jsx43, jsxs as jsxs37 } from "react/jsx-runtime";
3575
- function PricesList({ productId, onPricesChange }) {
3576
- const [prices, setPrices] = useState18([]);
3577
- const [loading, setLoading] = useState18(true);
3578
- const [showCreatePrice, setShowCreatePrice] = useState18(false);
3579
- const [editingPrice, setEditingPrice] = useState18(null);
3580
- const [priceToArchive, setPriceToArchive] = useState18(null);
3581
- const [priceToReactivate, setPriceToReactivate] = useState18(null);
3582
- const [archivingPriceId, setArchivingPriceId] = useState18(null);
3583
- const [reactivatingPriceId, setReactivatingPriceId] = useState18(null);
3584
- const loadPrices = /* @__PURE__ */ __name(async () => {
3585
- setLoading(true);
3586
- try {
3587
- const fetchedPrices = await StripePriceService.listPrices({ productId });
3588
- setPrices(fetchedPrices);
3589
- } catch (error) {
3590
- console.error("[PricesList] Failed to load prices:", error);
3591
- } finally {
3592
- setLoading(false);
3593
- }
3594
- }, "loadPrices");
3595
- useEffect11(() => {
3596
- loadPrices();
3597
- }, [productId]);
3598
- const handleArchive = /* @__PURE__ */ __name(async () => {
3599
- if (!priceToArchive) {
3600
- return;
3601
- }
3602
- setArchivingPriceId(priceToArchive.id);
3603
- try {
3604
- await StripePriceService.archivePrice({ id: priceToArchive.id });
3605
- setPriceToArchive(null);
3606
- await loadPrices();
3607
- onPricesChange();
3608
- } catch (error) {
3609
- console.error("[PricesList] Failed to archive price:", error);
3610
- } finally {
3611
- setArchivingPriceId(null);
3612
- }
3613
- }, "handleArchive");
3614
- const handleReactivate = /* @__PURE__ */ __name(async () => {
3615
- if (!priceToReactivate) {
3616
- return;
3617
- }
3618
- setReactivatingPriceId(priceToReactivate.id);
3619
- try {
3620
- await StripePriceService.reactivatePrice({ id: priceToReactivate.id });
3621
- setPriceToReactivate(null);
3622
- await loadPrices();
3623
- onPricesChange();
3624
- } catch (error) {
3625
- console.error("[PricesList] Failed to reactivate price:", error);
3626
- } finally {
3627
- setReactivatingPriceId(null);
3628
- }
3629
- }, "handleReactivate");
3630
- const formatInterval2 = /* @__PURE__ */ __name((price) => {
3631
- if (price.priceType === "one_time") {
3632
- return "one-time";
3633
- }
3634
- if (price.recurring) {
3635
- const count = price.recurring.intervalCount;
3636
- const interval = price.recurring.interval;
3637
- if (count === 1) {
3638
- return `/ ${interval}`;
3639
- } else {
3640
- return `/ ${count} ${interval}s`;
3641
- }
3460
+ // src/features/billing/contexts/PriceContext.tsx
3461
+ import { jsx as jsx42 } from "react/jsx-runtime";
3462
+ var PriceContext = createContext(void 0);
3463
+ var defaultContextValue = {
3464
+ price: void 0,
3465
+ setPrice: /* @__PURE__ */ __name(() => {
3466
+ }, "setPrice"),
3467
+ reloadPrice: /* @__PURE__ */ __name(async () => {
3468
+ }, "reloadPrice"),
3469
+ createPrice: /* @__PURE__ */ __name(async () => {
3470
+ throw new Error("createPrice requires a PriceProvider");
3471
+ }, "createPrice"),
3472
+ updatePrice: /* @__PURE__ */ __name(async () => {
3473
+ throw new Error("updatePrice requires a PriceProvider");
3474
+ }, "updatePrice"),
3475
+ archivePrice: /* @__PURE__ */ __name(async () => {
3476
+ }, "archivePrice"),
3477
+ restorePrice: /* @__PURE__ */ __name(async () => {
3478
+ }, "restorePrice"),
3479
+ priceVersion: 0,
3480
+ refreshPrices: /* @__PURE__ */ __name(() => {
3481
+ }, "refreshPrices"),
3482
+ productId: void 0
3483
+ };
3484
+ function priceLabel(price) {
3485
+ return price.nickname ?? `${formatCurrency(price.unitAmount, price.currency)} ${formatInterval(price)}`.trim();
3486
+ }
3487
+ __name(priceLabel, "priceLabel");
3488
+ var PriceProvider = /* @__PURE__ */ __name(({
3489
+ children,
3490
+ dehydratedPrice,
3491
+ publishChrome = true,
3492
+ productId: productIdProp
3493
+ }) => {
3494
+ const t = useTranslations3();
3495
+ const generateUrl = usePageUrlGenerator();
3496
+ const { hasRole } = useCurrentUserContext();
3497
+ const [price, setPrice] = useState17(
3498
+ dehydratedPrice ? rehydrate(Modules.StripePrice, dehydratedPrice) : void 0
3499
+ );
3500
+ const [priceVersion, setPriceVersion] = useState17(0);
3501
+ const refreshPrices = useCallback6(() => setPriceVersion((value2) => value2 + 1), []);
3502
+ const productId = productIdProp ?? price?.product?.id;
3503
+ const reloadPrice = useCallback6(async () => {
3504
+ if (!price?.id) return;
3505
+ setPrice(await StripePriceService.getPrice({ id: price.id }));
3506
+ }, [price?.id]);
3507
+ const createPrice = useCallback6(
3508
+ async (input) => {
3509
+ const saved = await StripePriceService.createPrice(input);
3510
+ refreshPrices();
3511
+ return saved;
3512
+ },
3513
+ [refreshPrices]
3514
+ );
3515
+ const updatePrice = useCallback6(
3516
+ async (input) => {
3517
+ const saved = await StripePriceService.updatePrice(input);
3518
+ setPrice(saved);
3519
+ refreshPrices();
3520
+ return saved;
3521
+ },
3522
+ [refreshPrices]
3523
+ );
3524
+ const archivePrice = useCallback6(
3525
+ async (id) => {
3526
+ const target = id ?? price?.id;
3527
+ if (!target) return;
3528
+ await StripePriceService.archivePrice({ id: target });
3529
+ await reloadPrice();
3530
+ refreshPrices();
3531
+ },
3532
+ [price?.id, reloadPrice, refreshPrices]
3533
+ );
3534
+ const restorePrice = useCallback6(
3535
+ async (id) => {
3536
+ const target = id ?? price?.id;
3537
+ if (!target) return;
3538
+ await StripePriceService.reactivatePrice({ id: target });
3539
+ await reloadPrice();
3540
+ refreshPrices();
3541
+ },
3542
+ [price?.id, reloadPrice, refreshPrices]
3543
+ );
3544
+ const value = useMemo6(
3545
+ () => ({
3546
+ price,
3547
+ setPrice,
3548
+ reloadPrice,
3549
+ createPrice,
3550
+ updatePrice,
3551
+ archivePrice,
3552
+ restorePrice,
3553
+ priceVersion,
3554
+ refreshPrices,
3555
+ productId
3556
+ }),
3557
+ [price, reloadPrice, createPrice, updatePrice, archivePrice, restorePrice, priceVersion, refreshPrices, productId]
3558
+ );
3559
+ if (!publishChrome) return /* @__PURE__ */ jsx42(PriceContext.Provider, { value, children });
3560
+ const breadcrumb = /* @__PURE__ */ __name(() => {
3561
+ const items = [
3562
+ { name: t("billing.admin.products.title"), href: generateUrl({ page: Modules.StripeProduct }) }
3563
+ ];
3564
+ if (price?.product)
3565
+ items.push({
3566
+ name: price.product.name,
3567
+ href: generateUrl({ page: Modules.StripeProduct, id: price.product.id })
3568
+ });
3569
+ if (price) items.push({ name: priceLabel(price) });
3570
+ return items;
3571
+ }, "breadcrumb");
3572
+ const title = /* @__PURE__ */ __name(() => {
3573
+ const response = { type: t("billing.admin.prices.title") };
3574
+ if (!price) return response;
3575
+ response.element = priceLabel(price);
3576
+ if (hasRole(getRoleId().Administrator)) {
3577
+ response.functions = [
3578
+ /* @__PURE__ */ jsx42(PriceArchiver, { price }, "priceArchiver"),
3579
+ /* @__PURE__ */ jsx42(PriceEditor, { price, propagateChanges: setPrice }, "priceEditor")
3580
+ ];
3642
3581
  }
3643
- return "";
3644
- }, "formatInterval");
3645
- if (loading) {
3646
- return /* @__PURE__ */ jsx43("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx43("p", { className: "text-muted-foreground", children: "Loading prices..." }) });
3647
- }
3648
- return /* @__PURE__ */ jsxs37("div", { className: "flex flex-col gap-y-4", children: [
3649
- /* @__PURE__ */ jsxs37("div", { className: "flex items-center justify-between mb-4", children: [
3650
- /* @__PURE__ */ jsx43("h4", { className: "text-lg font-semibold", children: "Prices" }),
3651
- /* @__PURE__ */ jsx43(Button, { size: "sm", onClick: () => setShowCreatePrice(true), children: "Add Price" })
3652
- ] }),
3653
- prices.length === 0 && /* @__PURE__ */ jsxs37("div", { className: "bg-background flex flex-col items-center justify-center gap-y-3 rounded-lg border border-dashed p-8", children: [
3654
- /* @__PURE__ */ jsx43(DollarSign, { className: "text-muted-foreground h-12 w-12" }),
3655
- /* @__PURE__ */ jsx43("p", { className: "text-muted-foreground text-sm", children: "No prices yet. Add a price to enable subscriptions." }),
3656
- /* @__PURE__ */ jsx43(Button, { size: "sm", onClick: () => setShowCreatePrice(true), children: "Add Price" })
3582
+ return response;
3583
+ }, "title");
3584
+ return /* @__PURE__ */ jsx42(SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ jsx42(PriceContext.Provider, { value, children }) });
3585
+ }, "PriceProvider");
3586
+ var usePriceContext = /* @__PURE__ */ __name(() => useContext(PriceContext) ?? defaultContextValue, "usePriceContext");
3587
+
3588
+ // src/features/billing/contexts/ProductContext.tsx
3589
+ import { useTranslations as useTranslations6 } from "next-intl";
3590
+ import { createContext as createContext2, useCallback as useCallback8, useContext as useContext2, useMemo as useMemo8, useState as useState19 } from "react";
3591
+
3592
+ // src/features/billing/stripe-product/components/forms/ProductArchiver.tsx
3593
+ import { ArchiveIcon as ArchiveIcon2, RotateCcwIcon as RotateCcwIcon2 } from "lucide-react";
3594
+ import { useTranslations as useTranslations4 } from "next-intl";
3595
+ import { useState as useState18 } from "react";
3596
+ import { Fragment as Fragment10, jsx as jsx43, jsxs as jsxs37 } from "react/jsx-runtime";
3597
+ function ProductArchiver({ product }) {
3598
+ const t = useTranslations4();
3599
+ const { archiveProduct, restoreProduct } = useProductContext();
3600
+ const [open, setOpen] = useState18(false);
3601
+ const isArchived = !product.active;
3602
+ const scope = isArchived ? "restore" : "archive";
3603
+ return /* @__PURE__ */ jsxs37(Fragment10, { children: [
3604
+ /* @__PURE__ */ jsxs37(Button, { variant: "outline", size: "sm", onClick: () => setOpen(true), children: [
3605
+ isArchived ? /* @__PURE__ */ jsx43(RotateCcwIcon2, {}) : /* @__PURE__ */ jsx43(ArchiveIcon2, {}),
3606
+ t(`billing.admin.products.${scope}.confirm`)
3657
3607
  ] }),
3658
- prices.length > 0 && /* @__PURE__ */ jsx43("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: prices.map((price) => {
3659
- const isArchiving = archivingPriceId === price.id;
3660
- const isReactivating = reactivatingPriceId === price.id;
3661
- return /* @__PURE__ */ jsxs37("div", { className: "border rounded-lg bg-white p-4 hover:shadow-sm transition-shadow", children: [
3662
- /* @__PURE__ */ jsxs37("div", { className: "flex items-start justify-between mb-3", children: [
3663
- /* @__PURE__ */ jsx43(DollarSign, { className: "h-5 w-5 text-primary" }),
3664
- /* @__PURE__ */ jsxs37("div", { className: "flex gap-1", children: [
3665
- /* @__PURE__ */ jsx43(Button, { variant: "ghost", size: "sm", onClick: () => setEditingPrice(price), className: "h-8 w-8 p-0", children: /* @__PURE__ */ jsx43(Edit, { className: "h-4 w-4" }) }),
3666
- price.active ? /* @__PURE__ */ jsx43(
3667
- Button,
3668
- {
3669
- variant: "ghost",
3670
- size: "sm",
3671
- onClick: () => setPriceToArchive(price),
3672
- className: "h-8 w-8 p-0",
3673
- disabled: isArchiving,
3674
- children: /* @__PURE__ */ jsx43(Archive, { className: "h-4 w-4" })
3675
- }
3676
- ) : /* @__PURE__ */ jsx43(
3677
- Button,
3678
- {
3679
- variant: "ghost",
3680
- size: "sm",
3681
- onClick: () => setPriceToReactivate(price),
3682
- className: "h-8 w-8 p-0",
3683
- disabled: isReactivating,
3684
- children: /* @__PURE__ */ jsx43(RotateCcw, { className: "h-4 w-4" })
3685
- }
3686
- )
3687
- ] })
3688
- ] }),
3689
- /* @__PURE__ */ jsx43("div", { className: "mb-2", children: /* @__PURE__ */ jsxs37("div", { className: "text-2xl font-bold", children: [
3690
- formatCurrency(price.unitAmount, price.currency),
3691
- " ",
3692
- /* @__PURE__ */ jsx43("span", { className: "text-muted-foreground text-sm font-normal", children: formatInterval2(price) })
3693
- ] }) }),
3694
- price.metadata?.nickname && /* @__PURE__ */ jsx43("p", { className: "text-sm font-medium mb-2", children: price.metadata.nickname }),
3695
- /* @__PURE__ */ jsxs37("div", { className: "flex flex-wrap gap-2", children: [
3696
- price.active ? /* @__PURE__ */ jsx43(Badge, { variant: "softGreen", children: "Active" }) : /* @__PURE__ */ jsx43(Badge, { variant: "softGray", children: "Inactive" }),
3697
- price.recurring?.usageType === "metered" && /* @__PURE__ */ jsx43(Badge, { variant: "softBlue", children: "Metered" }),
3698
- /* @__PURE__ */ jsx43(Badge, { variant: "softGray", className: "uppercase", children: price.currency })
3699
- ] })
3700
- ] }, price.id);
3701
- }) }),
3702
- showCreatePrice && /* @__PURE__ */ jsx43(
3703
- PriceEditor,
3704
- {
3705
- productId,
3706
- open: showCreatePrice,
3707
- onOpenChange: setShowCreatePrice,
3708
- onSuccess: () => {
3709
- loadPrices();
3710
- onPricesChange();
3711
- }
3712
- }
3713
- ),
3714
- editingPrice && /* @__PURE__ */ jsx43(
3715
- PriceEditor,
3608
+ /* @__PURE__ */ jsx43(
3609
+ ConfirmDialog,
3716
3610
  {
3717
- productId,
3718
- price: editingPrice,
3719
- open: !!editingPrice,
3720
- onOpenChange: (open) => !open && setEditingPrice(null),
3721
- onSuccess: () => {
3722
- loadPrices();
3723
- onPricesChange();
3724
- setEditingPrice(null);
3611
+ open,
3612
+ onOpenChange: setOpen,
3613
+ title: t(`billing.admin.products.${scope}.title`),
3614
+ description: t(`billing.admin.products.${scope}.description`, { name: product.name }),
3615
+ confirmLabel: t(`billing.admin.products.${scope}.confirm`),
3616
+ cancelLabel: t("ui.buttons.cancel"),
3617
+ destructive: !isArchived,
3618
+ onConfirm: async () => {
3619
+ try {
3620
+ if (isArchived) await restoreProduct(product.id);
3621
+ else await archiveProduct(product.id);
3622
+ } catch (error) {
3623
+ errorToast({ title: t(`billing.admin.products.${scope}.title`), error });
3624
+ throw error;
3625
+ }
3725
3626
  }
3726
3627
  }
3727
- ),
3728
- /* @__PURE__ */ jsx43(AlertDialog, { open: !!priceToArchive, onOpenChange: (open) => !open && setPriceToArchive(null), children: /* @__PURE__ */ jsxs37(AlertDialogContent, { children: [
3729
- /* @__PURE__ */ jsxs37(AlertDialogHeader, { children: [
3730
- /* @__PURE__ */ jsx43(AlertDialogTitle, { children: "Archive Price" }),
3731
- /* @__PURE__ */ jsxs37(AlertDialogDescription, { children: [
3732
- "Are you sure you want to archive the price for",
3733
- " ",
3734
- priceToArchive && `${formatCurrency(priceToArchive.unitAmount, priceToArchive.currency)} ${formatInterval2(priceToArchive)}`,
3735
- "? This will prevent new subscriptions but existing ones will continue."
3736
- ] })
3737
- ] }),
3738
- /* @__PURE__ */ jsxs37(AlertDialogFooter, { children: [
3739
- /* @__PURE__ */ jsx43(AlertDialogCancel, { disabled: !!archivingPriceId, children: "Cancel" }),
3740
- /* @__PURE__ */ jsx43(
3741
- AlertDialogAction,
3628
+ )
3629
+ ] });
3630
+ }
3631
+ __name(ProductArchiver, "ProductArchiver");
3632
+
3633
+ // src/features/billing/stripe-product/components/forms/ProductEditor.tsx
3634
+ import { zodResolver as zodResolver3 } from "@hookform/resolvers/zod";
3635
+ import { useTranslations as useTranslations5 } from "next-intl";
3636
+ import { useCallback as useCallback7, useMemo as useMemo7 } from "react";
3637
+ import { useForm as useForm3 } from "react-hook-form";
3638
+ import { v4 as v43 } from "uuid";
3639
+ import { z as z3 } from "zod";
3640
+ import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
3641
+ function ProductEditorInternal({
3642
+ product,
3643
+ propagateChanges,
3644
+ onSuccess,
3645
+ trigger,
3646
+ forceShow,
3647
+ onClose,
3648
+ dialogOpen,
3649
+ onDialogOpenChange
3650
+ }) {
3651
+ const t = useTranslations5();
3652
+ const router = useI18nRouter();
3653
+ const { createProduct, updateProduct } = useProductContext();
3654
+ const isEdit = !!product;
3655
+ const formSchema2 = useMemo7(
3656
+ () => z3.object({
3657
+ id: z3.uuidv4(),
3658
+ name: z3.string().min(1, { message: t("billing.admin.products.errors.name") }),
3659
+ description: z3.string().optional()
3660
+ }),
3661
+ [t]
3662
+ );
3663
+ const getDefaultValues = useCallback7(
3664
+ () => ({
3665
+ id: product?.id ?? v43(),
3666
+ name: product?.name ?? "",
3667
+ description: product?.description ?? ""
3668
+ }),
3669
+ [product]
3670
+ );
3671
+ const form = useForm3({
3672
+ resolver: zodResolver3(formSchema2),
3673
+ defaultValues: getDefaultValues()
3674
+ });
3675
+ return /* @__PURE__ */ jsx44(
3676
+ EditorSheet,
3677
+ {
3678
+ form,
3679
+ entityType: t("billing.admin.products.entity"),
3680
+ entityName: product?.name,
3681
+ isEdit,
3682
+ module: Modules.StripeProduct,
3683
+ size: "md",
3684
+ propagateChanges,
3685
+ onSuccess,
3686
+ onNavigate: (url) => router.push(url),
3687
+ onSubmit: async (values) => {
3688
+ const payload = { id: values.id, name: values.name, description: values.description };
3689
+ return isEdit ? await updateProduct(payload) : await createProduct(payload);
3690
+ },
3691
+ onReset: getDefaultValues,
3692
+ trigger,
3693
+ forceShow,
3694
+ onClose,
3695
+ dialogOpen,
3696
+ onDialogOpenChange,
3697
+ children: /* @__PURE__ */ jsxs38("div", { className: "flex w-full flex-col gap-y-4", children: [
3698
+ /* @__PURE__ */ jsx44(
3699
+ FormInput,
3742
3700
  {
3743
- onClick: handleArchive,
3744
- disabled: !!archivingPriceId,
3745
- className: "bg-red-600 hover:bg-red-700",
3746
- children: archivingPriceId ? "Archiving..." : "Archive"
3701
+ form,
3702
+ id: "name",
3703
+ name: t("billing.admin.products.fields.name"),
3704
+ placeholder: t("billing.admin.products.placeholders.name"),
3705
+ isRequired: true
3747
3706
  }
3748
- )
3749
- ] })
3750
- ] }) }),
3751
- /* @__PURE__ */ jsx43(AlertDialog, { open: !!priceToReactivate, onOpenChange: (open) => !open && setPriceToReactivate(null), children: /* @__PURE__ */ jsxs37(AlertDialogContent, { children: [
3752
- /* @__PURE__ */ jsxs37(AlertDialogHeader, { children: [
3753
- /* @__PURE__ */ jsx43(AlertDialogTitle, { children: "Reactivate Price" }),
3754
- /* @__PURE__ */ jsxs37(AlertDialogDescription, { children: [
3755
- "Are you sure you want to reactivate the price for",
3756
- " ",
3757
- priceToReactivate && `${formatCurrency(priceToReactivate.unitAmount, priceToReactivate.currency)} ${formatInterval2(priceToReactivate)}`,
3758
- "? This will allow new subscriptions again."
3759
- ] })
3760
- ] }),
3761
- /* @__PURE__ */ jsxs37(AlertDialogFooter, { children: [
3762
- /* @__PURE__ */ jsx43(AlertDialogCancel, { disabled: !!reactivatingPriceId, children: "Cancel" }),
3763
- /* @__PURE__ */ jsx43(
3764
- AlertDialogAction,
3707
+ ),
3708
+ /* @__PURE__ */ jsx44(
3709
+ FormTextarea,
3765
3710
  {
3766
- onClick: handleReactivate,
3767
- disabled: !!reactivatingPriceId,
3768
- className: "bg-green-600 hover:bg-green-700",
3769
- children: reactivatingPriceId ? "Reactivating..." : "Reactivate"
3711
+ form,
3712
+ id: "description",
3713
+ name: t("billing.admin.products.fields.description"),
3714
+ placeholder: t("billing.admin.products.placeholders.description"),
3715
+ className: "min-h-32"
3770
3716
  }
3771
3717
  )
3772
3718
  ] })
3773
- ] }) })
3774
- ] });
3719
+ }
3720
+ );
3775
3721
  }
3776
- __name(PricesList, "PricesList");
3777
-
3778
- // src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx
3779
- import { Package as Package2 } from "lucide-react";
3780
- import { useEffect as useEffect12, useState as useState20 } from "react";
3722
+ __name(ProductEditorInternal, "ProductEditorInternal");
3723
+ function ProductEditor(props) {
3724
+ return /* @__PURE__ */ jsx44(ProductEditorInternal, { ...props });
3725
+ }
3726
+ __name(ProductEditor, "ProductEditor");
3781
3727
 
3782
- // src/features/billing/stripe-product/components/lists/ProductsList.tsx
3783
- import { Archive as Archive2, ChevronDown, ChevronUp, Edit as Edit2, Package, RefreshCw as RefreshCw2 } from "lucide-react";
3784
- import { useState as useState19 } from "react";
3785
- import { jsx as jsx44, jsxs as jsxs38 } from "react/jsx-runtime";
3786
- function ProductsList({ products, onProductsChange }) {
3787
- const [expandedProductId, setExpandedProductId] = useState19(null);
3788
- const [editingProduct, setEditingProduct] = useState19(null);
3789
- const [archivingProductId, setArchivingProductId] = useState19(null);
3790
- const [reactivatingProductId, setReactivatingProductId] = useState19(null);
3791
- const [productToArchive, setProductToArchive] = useState19(null);
3792
- const [productToReactivate, setProductToReactivate] = useState19(null);
3793
- const handleArchive = /* @__PURE__ */ __name(async () => {
3794
- if (!productToArchive) {
3795
- return;
3796
- }
3797
- setArchivingProductId(productToArchive.id);
3798
- try {
3799
- const _archivedProduct = await StripeProductService.archiveProduct({ id: productToArchive.id });
3800
- setProductToArchive(null);
3801
- onProductsChange();
3802
- } catch (_error) {
3803
- console.error("[ProductsList] Failed to archive product:", _error);
3804
- } finally {
3805
- setArchivingProductId(null);
3806
- }
3807
- }, "handleArchive");
3808
- const handleReactivate = /* @__PURE__ */ __name(async () => {
3809
- if (!productToReactivate) {
3810
- return;
3811
- }
3812
- setReactivatingProductId(productToReactivate.id);
3813
- try {
3814
- const _reactivatedProduct = await StripeProductService.reactivateProduct({ id: productToReactivate.id });
3815
- setProductToReactivate(null);
3816
- onProductsChange();
3817
- } catch (_error) {
3818
- } finally {
3819
- setReactivatingProductId(null);
3820
- }
3821
- }, "handleReactivate");
3822
- const toggleExpand = /* @__PURE__ */ __name((productId) => {
3823
- setExpandedProductId(expandedProductId === productId ? null : productId);
3824
- }, "toggleExpand");
3825
- return /* @__PURE__ */ jsxs38("div", { className: "flex flex-col gap-y-4", children: [
3826
- products.map((product) => {
3827
- const isExpanded = expandedProductId === product.id;
3828
- const isArchiving = archivingProductId === product.id;
3829
- const isReactivating = reactivatingProductId === product.id;
3830
- return /* @__PURE__ */ jsxs38("div", { className: "border rounded-lg bg-white shadow-sm hover:shadow-md transition-shadow", children: [
3831
- /* @__PURE__ */ jsxs38("div", { className: "flex items-center justify-between p-6", children: [
3832
- /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-x-4 flex-1", children: [
3833
- /* @__PURE__ */ jsx44(Package, { className: "h-6 w-6 text-primary" }),
3834
- /* @__PURE__ */ jsxs38("div", { className: "flex-1", children: [
3835
- /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-x-3", children: [
3836
- /* @__PURE__ */ jsx44("h3", { className: "text-sm font-medium", children: product.name }),
3837
- product.active ? /* @__PURE__ */ jsx44(Badge, { variant: "softGreen", children: "Active" }) : /* @__PURE__ */ jsx44(Badge, { variant: "softGray", children: "Inactive" })
3838
- ] }),
3839
- product.description && /* @__PURE__ */ jsx44("p", { className: "text-muted-foreground text-sm mt-1", children: product.description })
3840
- ] })
3841
- ] }),
3842
- /* @__PURE__ */ jsxs38("div", { className: "flex items-center gap-x-2", children: [
3843
- /* @__PURE__ */ jsxs38(Button, { variant: "outline", size: "sm", onClick: () => setEditingProduct(product), children: [
3844
- /* @__PURE__ */ jsx44(Edit2, { className: "h-4 w-4 mr-1" }),
3845
- "Edit"
3846
- ] }),
3847
- product.active ? /* @__PURE__ */ jsxs38(
3848
- Button,
3849
- {
3850
- variant: "outline",
3851
- size: "sm",
3852
- onClick: () => setProductToArchive(product),
3853
- disabled: isArchiving,
3854
- children: [
3855
- /* @__PURE__ */ jsx44(Archive2, { className: "h-4 w-4 mr-1" }),
3856
- isArchiving ? "Archiving..." : "Archive"
3857
- ]
3858
- }
3859
- ) : /* @__PURE__ */ jsxs38(
3860
- Button,
3861
- {
3862
- variant: "outline",
3863
- size: "sm",
3864
- onClick: () => setProductToReactivate(product),
3865
- disabled: isReactivating,
3866
- children: [
3867
- /* @__PURE__ */ jsx44(RefreshCw2, { className: "h-4 w-4 mr-1" }),
3868
- isReactivating ? "Reactivating..." : "Reactivate"
3869
- ]
3870
- }
3871
- ),
3872
- /* @__PURE__ */ jsx44(Button, { variant: "ghost", size: "sm", onClick: () => toggleExpand(product.id), children: isExpanded ? /* @__PURE__ */ jsx44(ChevronUp, { className: "h-5 w-5" }) : /* @__PURE__ */ jsx44(ChevronDown, { className: "h-5 w-5" }) })
3873
- ] })
3874
- ] }),
3875
- isExpanded && /* @__PURE__ */ jsx44("div", { className: "border-t bg-muted/30 p-6", children: /* @__PURE__ */ jsx44(PricesList, { productId: product.id, onPricesChange: onProductsChange }) })
3876
- ] }, product.id);
3728
+ // src/features/billing/contexts/ProductContext.tsx
3729
+ import { jsx as jsx45 } from "react/jsx-runtime";
3730
+ var ProductContext = createContext2(void 0);
3731
+ var defaultContextValue2 = {
3732
+ product: void 0,
3733
+ setProduct: /* @__PURE__ */ __name(() => {
3734
+ }, "setProduct"),
3735
+ reloadProduct: /* @__PURE__ */ __name(async () => {
3736
+ }, "reloadProduct"),
3737
+ createProduct: /* @__PURE__ */ __name(async () => {
3738
+ throw new Error("createProduct requires a ProductProvider");
3739
+ }, "createProduct"),
3740
+ updateProduct: /* @__PURE__ */ __name(async () => {
3741
+ throw new Error("updateProduct requires a ProductProvider");
3742
+ }, "updateProduct"),
3743
+ archiveProduct: /* @__PURE__ */ __name(async () => {
3744
+ }, "archiveProduct"),
3745
+ restoreProduct: /* @__PURE__ */ __name(async () => {
3746
+ }, "restoreProduct"),
3747
+ catalogueVersion: 0,
3748
+ refreshCatalogue: /* @__PURE__ */ __name(() => {
3749
+ }, "refreshCatalogue")
3750
+ };
3751
+ var ProductProvider = /* @__PURE__ */ __name(({ children, dehydratedProduct, publishChrome = true }) => {
3752
+ const t = useTranslations6();
3753
+ const generateUrl = usePageUrlGenerator();
3754
+ const { hasRole } = useCurrentUserContext();
3755
+ const [product, setProduct] = useState19(
3756
+ dehydratedProduct ? rehydrate(Modules.StripeProduct, dehydratedProduct) : void 0
3757
+ );
3758
+ const [catalogueVersion, setCatalogueVersion] = useState19(0);
3759
+ const refreshCatalogue = useCallback8(() => setCatalogueVersion((value2) => value2 + 1), []);
3760
+ const reloadProduct = useCallback8(async () => {
3761
+ if (!product?.id) return;
3762
+ setProduct(await StripeProductService.getProduct({ id: product.id }));
3763
+ }, [product?.id]);
3764
+ const createProduct = useCallback8(
3765
+ async (input) => {
3766
+ const saved = await StripeProductService.createProduct(input);
3767
+ refreshCatalogue();
3768
+ return saved;
3769
+ },
3770
+ [refreshCatalogue]
3771
+ );
3772
+ const updateProduct = useCallback8(
3773
+ async (input) => {
3774
+ const saved = await StripeProductService.updateProduct(input);
3775
+ setProduct(saved);
3776
+ refreshCatalogue();
3777
+ return saved;
3778
+ },
3779
+ [refreshCatalogue]
3780
+ );
3781
+ const archiveProduct = useCallback8(
3782
+ async (id) => {
3783
+ const target = id ?? product?.id;
3784
+ if (!target) return;
3785
+ await StripeProductService.archiveProduct({ id: target });
3786
+ await reloadProduct();
3787
+ refreshCatalogue();
3788
+ },
3789
+ [product?.id, reloadProduct, refreshCatalogue]
3790
+ );
3791
+ const restoreProduct = useCallback8(
3792
+ async (id) => {
3793
+ const target = id ?? product?.id;
3794
+ if (!target) return;
3795
+ await StripeProductService.reactivateProduct({ id: target });
3796
+ await reloadProduct();
3797
+ refreshCatalogue();
3798
+ },
3799
+ [product?.id, reloadProduct, refreshCatalogue]
3800
+ );
3801
+ const value = useMemo8(
3802
+ () => ({
3803
+ product,
3804
+ setProduct,
3805
+ reloadProduct,
3806
+ createProduct,
3807
+ updateProduct,
3808
+ archiveProduct,
3809
+ restoreProduct,
3810
+ catalogueVersion,
3811
+ refreshCatalogue
3877
3812
  }),
3878
- editingProduct && /* @__PURE__ */ jsx44(
3879
- ProductEditor,
3813
+ [
3814
+ product,
3815
+ reloadProduct,
3816
+ createProduct,
3817
+ updateProduct,
3818
+ archiveProduct,
3819
+ restoreProduct,
3820
+ catalogueVersion,
3821
+ refreshCatalogue
3822
+ ]
3823
+ );
3824
+ if (!publishChrome) return /* @__PURE__ */ jsx45(ProductContext.Provider, { value, children });
3825
+ const breadcrumb = /* @__PURE__ */ __name(() => {
3826
+ const items = [
3880
3827
  {
3881
- product: editingProduct,
3882
- open: !!editingProduct,
3883
- onOpenChange: (open) => !open && setEditingProduct(null),
3884
- onSuccess: () => {
3885
- onProductsChange();
3886
- setEditingProduct(null);
3887
- }
3828
+ name: t("billing.admin.products.title"),
3829
+ // Linked only when it is NOT the current page.
3830
+ ...product ? { href: generateUrl({ page: Modules.StripeProduct }) } : {}
3888
3831
  }
3889
- ),
3890
- /* @__PURE__ */ jsx44(AlertDialog, { open: !!productToArchive, onOpenChange: (open) => !open && setProductToArchive(null), children: /* @__PURE__ */ jsxs38(AlertDialogContent, { children: [
3891
- /* @__PURE__ */ jsxs38(AlertDialogHeader, { children: [
3892
- /* @__PURE__ */ jsx44(AlertDialogTitle, { children: "Archive Product" }),
3893
- /* @__PURE__ */ jsxs38(AlertDialogDescription, { children: [
3894
- 'Are you sure you want to archive "',
3895
- productToArchive?.name,
3896
- '"? This will deactivate it and it will no longer be available for new subscriptions.'
3897
- ] })
3898
- ] }),
3899
- /* @__PURE__ */ jsxs38(AlertDialogFooter, { children: [
3900
- /* @__PURE__ */ jsx44(AlertDialogCancel, { disabled: !!archivingProductId, children: "Cancel" }),
3901
- /* @__PURE__ */ jsx44(
3902
- AlertDialogAction,
3832
+ ];
3833
+ if (product) items.push({ name: product.name });
3834
+ return items;
3835
+ }, "breadcrumb");
3836
+ const title = /* @__PURE__ */ __name(() => {
3837
+ const response = { type: t("billing.admin.products.title") };
3838
+ if (!product) return response;
3839
+ response.element = product.name;
3840
+ if (hasRole(getRoleId().Administrator)) {
3841
+ response.functions = [
3842
+ /* @__PURE__ */ jsx45(ProductArchiver, { product }, "productArchiver"),
3843
+ /* @__PURE__ */ jsx45(ProductEditor, { product, propagateChanges: setProduct }, "productEditor")
3844
+ ];
3845
+ }
3846
+ return response;
3847
+ }, "title");
3848
+ return /* @__PURE__ */ jsx45(SharedProvider, { value: { breadcrumbs: breadcrumb(), title: title() }, children: /* @__PURE__ */ jsx45(ProductContext.Provider, { value, children }) });
3849
+ }, "ProductProvider");
3850
+ var useProductContext = /* @__PURE__ */ __name(() => useContext2(ProductContext) ?? defaultContextValue2, "useProductContext");
3851
+
3852
+ // src/features/billing/stripe-price/components/details/PriceDetails.tsx
3853
+ import { useTranslations as useTranslations7 } from "next-intl";
3854
+ import { jsx as jsx46, jsxs as jsxs39 } from "react/jsx-runtime";
3855
+ function PriceDetails() {
3856
+ const t = useTranslations7();
3857
+ const generateUrl = usePageUrlGenerator();
3858
+ const { price } = usePriceContext();
3859
+ if (!price) return null;
3860
+ const isRecurring = price.priceType === "recurring";
3861
+ return /* @__PURE__ */ jsxs39("div", { className: "flex w-full flex-col gap-y-8", children: [
3862
+ /* @__PURE__ */ jsxs39("div", { className: "flex w-full flex-col gap-y-4", children: [
3863
+ /* @__PURE__ */ jsx46(SectionHeader, { children: t("billing.admin.prices.title") }),
3864
+ /* @__PURE__ */ jsxs39("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
3865
+ /* @__PURE__ */ jsx46(
3866
+ DetailField,
3903
3867
  {
3904
- onClick: handleArchive,
3905
- disabled: !!archivingProductId,
3906
- className: "bg-red-600 hover:bg-red-700",
3907
- children: archivingProductId ? "Archiving..." : "Archive"
3868
+ label: t("billing.admin.prices.fields.product"),
3869
+ value: price.product ? /* @__PURE__ */ jsx46(Link, { href: generateUrl({ page: Modules.StripeProduct, id: price.product.id }), children: price.product.name }) : "\u2014"
3870
+ }
3871
+ ),
3872
+ /* @__PURE__ */ jsx46(
3873
+ DetailField,
3874
+ {
3875
+ label: t("billing.admin.prices.fields.status"),
3876
+ value: /* @__PURE__ */ jsxs39("span", { className: "flex flex-wrap items-center gap-1", children: [
3877
+ price.active ? /* @__PURE__ */ jsx46(Badge, { variant: "softGreen", children: t("billing.admin.prices.status.active") }) : /* @__PURE__ */ jsx46(Badge, { variant: "softGray", children: t("billing.admin.prices.status.archived") }),
3878
+ price.isTrial && /* @__PURE__ */ jsx46(Badge, { variant: "softBlue", children: t("billing.admin.prices.badge.trial") })
3879
+ ] })
3880
+ }
3881
+ ),
3882
+ /* @__PURE__ */ jsx46(
3883
+ DetailField,
3884
+ {
3885
+ label: t("billing.admin.prices.fields.amount"),
3886
+ value: /* @__PURE__ */ jsx46("span", { className: "tabular-nums", children: formatCurrency(price.unitAmount, price.currency) })
3887
+ }
3888
+ ),
3889
+ /* @__PURE__ */ jsx46(DetailField, { label: t("billing.admin.prices.fields.currency"), value: price.currency.toUpperCase() }),
3890
+ /* @__PURE__ */ jsx46(
3891
+ DetailField,
3892
+ {
3893
+ label: t("billing.admin.prices.fields.type"),
3894
+ value: isRecurring ? t("billing.admin.prices.type.recurring") : t("billing.admin.prices.type.oneTime")
3895
+ }
3896
+ ),
3897
+ /* @__PURE__ */ jsx46(DetailField, { label: t("billing.admin.prices.fields.billing"), value: formatInterval(price) }),
3898
+ isRecurring && /* @__PURE__ */ jsx46(
3899
+ DetailField,
3900
+ {
3901
+ label: t("billing.admin.prices.fields.intervalCount"),
3902
+ value: /* @__PURE__ */ jsx46("span", { className: "tabular-nums", children: price.recurring?.intervalCount ?? 1 })
3903
+ }
3904
+ ),
3905
+ isRecurring && /* @__PURE__ */ jsx46(
3906
+ DetailField,
3907
+ {
3908
+ label: t("billing.admin.prices.fields.usageType"),
3909
+ value: price.recurring?.usageType === "metered" ? t("billing.admin.prices.usage.metered") : t("billing.admin.prices.usage.licensed")
3908
3910
  }
3909
3911
  )
3910
3912
  ] })
3911
- ] }) }),
3912
- /* @__PURE__ */ jsx44(AlertDialog, { open: !!productToReactivate, onOpenChange: (open) => !open && setProductToReactivate(null), children: /* @__PURE__ */ jsxs38(AlertDialogContent, { children: [
3913
- /* @__PURE__ */ jsxs38(AlertDialogHeader, { children: [
3914
- /* @__PURE__ */ jsx44(AlertDialogTitle, { children: "Reactivate Product" }),
3915
- /* @__PURE__ */ jsxs38(AlertDialogDescription, { children: [
3916
- 'Are you sure you want to reactivate "',
3917
- productToReactivate?.name,
3918
- '"? This will make it available for new subscriptions again.'
3919
- ] })
3920
- ] }),
3921
- /* @__PURE__ */ jsxs38(AlertDialogFooter, { children: [
3922
- /* @__PURE__ */ jsx44(AlertDialogCancel, { disabled: !!reactivatingProductId, children: "Cancel" }),
3923
- /* @__PURE__ */ jsx44(
3924
- AlertDialogAction,
3913
+ ] }),
3914
+ /* @__PURE__ */ jsxs39("div", { className: "flex w-full flex-col gap-y-4", children: [
3915
+ /* @__PURE__ */ jsx46(SectionHeader, { children: t("billing.admin.prices.fields.platformFeatures") }),
3916
+ /* @__PURE__ */ jsxs39("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
3917
+ /* @__PURE__ */ jsx46(
3918
+ DetailField,
3919
+ {
3920
+ label: t("billing.admin.prices.fields.token"),
3921
+ value: /* @__PURE__ */ jsx46("span", { className: "tabular-nums", children: price.token ?? "\u2014" })
3922
+ }
3923
+ ),
3924
+ /* @__PURE__ */ jsx46(
3925
+ DetailField,
3925
3926
  {
3926
- onClick: handleReactivate,
3927
- disabled: !!reactivatingProductId,
3928
- className: "bg-green-600 hover:bg-green-700",
3929
- children: reactivatingProductId ? "Reactivating..." : "Reactivate"
3927
+ label: t("billing.admin.prices.fields.stripeId"),
3928
+ value: /* @__PURE__ */ jsx46("span", { className: "tabular-nums", children: price.stripePriceId })
3929
+ }
3930
+ ),
3931
+ /* @__PURE__ */ jsx46(
3932
+ DetailField,
3933
+ {
3934
+ label: t("billing.admin.prices.fields.description"),
3935
+ value: price.description ?? "\u2014",
3936
+ className: "md:col-span-2"
3937
+ }
3938
+ ),
3939
+ /* @__PURE__ */ jsx46(
3940
+ DetailField,
3941
+ {
3942
+ label: t("billing.admin.prices.fields.features"),
3943
+ className: "md:col-span-2",
3944
+ value: price.features && price.features.length > 0 ? /* @__PURE__ */ jsx46("ul", { className: "flex flex-col gap-1 text-sm", children: price.features.map((feature) => /* @__PURE__ */ jsx46("li", { children: feature }, feature)) }) : "\u2014"
3945
+ }
3946
+ ),
3947
+ /* @__PURE__ */ jsx46(
3948
+ DetailField,
3949
+ {
3950
+ label: t("billing.admin.prices.fields.platformFeatures"),
3951
+ className: "md:col-span-2",
3952
+ value: price.priceFeatures.length > 0 ? /* @__PURE__ */ jsx46("span", { className: "flex flex-wrap items-center gap-1", children: price.priceFeatures.map((feature) => /* @__PURE__ */ jsx46(Badge, { variant: "softGray", children: feature.name }, feature.id)) }) : "\u2014"
3930
3953
  }
3931
3954
  )
3932
3955
  ] })
3933
- ] }) })
3956
+ ] })
3934
3957
  ] });
3935
3958
  }
3936
- __name(ProductsList, "ProductsList");
3959
+ __name(PriceDetails, "PriceDetails");
3937
3960
 
3938
- // src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx
3939
- import { jsx as jsx45, jsxs as jsxs39 } from "react/jsx-runtime";
3940
- function ProductsAdminContainer() {
3941
- const { hasRole } = useCurrentUserContext();
3942
- const billing = useOptionalBillingContext();
3943
- const [products, setProducts] = useState20([]);
3944
- const [loading, setLoading] = useState20(true);
3945
- const [showCreateProduct, setShowCreateProduct] = useState20(false);
3946
- if (!hasRole(getRoleId().Administrator)) {
3947
- return /* @__PURE__ */ jsx45("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx45("p", { className: "text-destructive text-xs/relaxed", children: "Permission denied. Administrator access required." }) });
3948
- }
3949
- const loadProducts = /* @__PURE__ */ __name(async () => {
3950
- setLoading(true);
3951
- try {
3952
- const fetchedProducts = await StripeProductService.listProducts();
3953
- setProducts(fetchedProducts);
3954
- } catch (error) {
3955
- console.error("[ProductsAdminContainer] Failed to load products:", error);
3956
- } finally {
3957
- setLoading(false);
3961
+ // src/features/billing/stripe-price/components/containers/PriceContainer.tsx
3962
+ import { jsx as jsx47 } from "react/jsx-runtime";
3963
+ function PriceContainer() {
3964
+ const { price } = usePriceContext();
3965
+ if (!price) return null;
3966
+ return /* @__PURE__ */ jsx47(RoundPageContainer, { module: Modules.StripePrice, id: price.id, children: /* @__PURE__ */ jsx47(PriceDetails, {}) });
3967
+ }
3968
+ __name(PriceContainer, "PriceContainer");
3969
+
3970
+ // src/features/billing/stripe-price/components/lists/PricesList.tsx
3971
+ import { DollarSignIcon } from "lucide-react";
3972
+ import { useTranslations as useTranslations8 } from "next-intl";
3973
+ import { useEffect as useEffect11 } from "react";
3974
+ import { jsx as jsx48 } from "react/jsx-runtime";
3975
+ function PricesList({ productId, fullWidth }) {
3976
+ const t = useTranslations8();
3977
+ const { priceVersion } = usePriceContext();
3978
+ const data = useDataListRetriever({
3979
+ retriever: /* @__PURE__ */ __name((params) => StripePriceService.listPrices({ ...params, productId }), "retriever"),
3980
+ retrieverParams: {},
3981
+ module: Modules.StripePrice
3982
+ });
3983
+ useEffect11(() => {
3984
+ if (priceVersion > 0) void data.refresh();
3985
+ }, [priceVersion]);
3986
+ const functions = [/* @__PURE__ */ jsx48(PriceEditor, { productId }, "create-price")];
3987
+ return /* @__PURE__ */ jsx48(
3988
+ ContentListTable,
3989
+ {
3990
+ data,
3991
+ fields: [
3992
+ "nickname" /* nickname */,
3993
+ "amount" /* amount */,
3994
+ "interval" /* interval */,
3995
+ "token" /* token */,
3996
+ "status" /* status */
3997
+ ],
3998
+ tableGeneratorType: Modules.StripePrice,
3999
+ functions,
4000
+ fullWidth,
4001
+ title: t("billing.admin.prices.title"),
4002
+ emptyState: /* @__PURE__ */ jsx48(
4003
+ EmptyState,
4004
+ {
4005
+ icon: DollarSignIcon,
4006
+ title: t("billing.admin.prices.empty.title"),
4007
+ description: t("billing.admin.prices.empty.description")
4008
+ }
4009
+ )
3958
4010
  }
3959
- }, "loadProducts");
3960
- useEffect12(() => {
3961
- loadProducts();
3962
- }, [billing?.refreshToken]);
3963
- if (loading) {
3964
- return /* @__PURE__ */ jsx45("div", { className: "flex h-64 items-center justify-center", children: /* @__PURE__ */ jsx45("p", { className: "text-muted-foreground", children: "Loading products..." }) });
3965
- }
3966
- return /* @__PURE__ */ jsxs39("div", { className: "flex w-full flex-col", children: [
3967
- !billing && /* @__PURE__ */ jsx45("div", { className: "flex w-full items-center justify-end px-4 pt-4", children: /* @__PURE__ */ jsx45(Button, { onClick: () => setShowCreateProduct(true), children: "Create Product" }) }),
3968
- /* @__PURE__ */ jsxs39("div", { className: "flex w-full flex-col gap-y-6 px-4 pb-4", children: [
3969
- products.length === 0 && /* @__PURE__ */ jsxs39("div", { className: "bg-muted/50 flex flex-col items-center justify-center gap-y-4 rounded-lg border-2 border-dashed p-12", children: [
3970
- /* @__PURE__ */ jsx45(Package2, { className: "text-muted-foreground h-16 w-16" }),
3971
- /* @__PURE__ */ jsxs39("div", { className: "text-center", children: [
3972
- /* @__PURE__ */ jsx45("h3", { className: "mb-2 text-sm font-medium", children: "No products yet" }),
3973
- /* @__PURE__ */ jsx45("p", { className: "text-muted-foreground mb-4", children: "Create your first product to start offering subscriptions to your customers." }),
3974
- /* @__PURE__ */ jsx45(Button, { onClick: () => setShowCreateProduct(true), children: "Create Your First Product" })
3975
- ] })
3976
- ] }),
3977
- products.length > 0 && /* @__PURE__ */ jsx45(ProductsList, { products, onProductsChange: loadProducts }),
3978
- showCreateProduct && /* @__PURE__ */ jsx45(ProductEditor, { open: showCreateProduct, onOpenChange: setShowCreateProduct, onSuccess: loadProducts })
4011
+ );
4012
+ }
4013
+ __name(PricesList, "PricesList");
4014
+
4015
+ // src/features/billing/stripe-product/components/containers/ProductContainer.tsx
4016
+ import { useTranslations as useTranslations10 } from "next-intl";
4017
+
4018
+ // src/features/billing/stripe-product/components/details/ProductDetails.tsx
4019
+ import { useTranslations as useTranslations9 } from "next-intl";
4020
+ import { jsx as jsx49, jsxs as jsxs40 } from "react/jsx-runtime";
4021
+ function ProductDetails() {
4022
+ const t = useTranslations9();
4023
+ const { product } = useProductContext();
4024
+ if (!product) return null;
4025
+ return /* @__PURE__ */ jsxs40("div", { className: "flex w-full flex-col gap-y-4", children: [
4026
+ /* @__PURE__ */ jsx49(SectionHeader, { children: t("billing.admin.products.tabs.details") }),
4027
+ /* @__PURE__ */ jsxs40("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
4028
+ /* @__PURE__ */ jsx49(DetailField, { label: t("billing.admin.products.fields.name"), value: product.name }),
4029
+ /* @__PURE__ */ jsx49(
4030
+ DetailField,
4031
+ {
4032
+ label: t("billing.admin.products.fields.status"),
4033
+ value: product.active ? /* @__PURE__ */ jsx49(Badge, { variant: "softGreen", children: t("billing.admin.products.status.active") }) : /* @__PURE__ */ jsx49(Badge, { variant: "softGray", children: t("billing.admin.products.status.archived") })
4034
+ }
4035
+ ),
4036
+ /* @__PURE__ */ jsx49(
4037
+ DetailField,
4038
+ {
4039
+ label: t("billing.admin.products.fields.description"),
4040
+ value: product.description ?? "\u2014",
4041
+ className: "md:col-span-2"
4042
+ }
4043
+ ),
4044
+ /* @__PURE__ */ jsx49(
4045
+ DetailField,
4046
+ {
4047
+ label: t("billing.admin.products.fields.prices"),
4048
+ value: /* @__PURE__ */ jsx49("span", { className: "tabular-nums", children: product.stripePrices.length })
4049
+ }
4050
+ ),
4051
+ /* @__PURE__ */ jsx49(
4052
+ DetailField,
4053
+ {
4054
+ label: t("billing.admin.products.fields.stripeId"),
4055
+ value: /* @__PURE__ */ jsx49("span", { className: "tabular-nums", children: product.stripeProductId })
4056
+ }
4057
+ )
3979
4058
  ] })
3980
4059
  ] });
3981
4060
  }
3982
- __name(ProductsAdminContainer, "ProductsAdminContainer");
4061
+ __name(ProductDetails, "ProductDetails");
4062
+
4063
+ // src/features/billing/stripe-product/components/containers/ProductContainer.tsx
4064
+ import { jsx as jsx50 } from "react/jsx-runtime";
4065
+ function ProductContainer() {
4066
+ const t = useTranslations10();
4067
+ const { product } = useProductContext();
4068
+ if (!product) return null;
4069
+ return /* @__PURE__ */ jsx50(
4070
+ RoundPageContainer,
4071
+ {
4072
+ module: Modules.StripeProduct,
4073
+ id: product.id,
4074
+ tabs: [
4075
+ {
4076
+ sectionKey: "details",
4077
+ label: t("billing.admin.products.tabs.details"),
4078
+ content: /* @__PURE__ */ jsx50(ProductDetails, {})
4079
+ },
4080
+ {
4081
+ key: Modules.StripePrice,
4082
+ label: t("billing.admin.prices.title"),
4083
+ // publishChrome={false}: SharedProvider REPLACES its value for the
4084
+ // subtree (SharedContext.tsx:35-37), so a nested provider publishing
4085
+ // chrome here would blank this page's own title.
4086
+ // No `fillHeight` and no `fullWidth` on the list: a ContentListTable in
4087
+ // a tab keeps its own bordered card and flows with the panel, exactly
4088
+ // as ThreatList does inside GoalContainer.
4089
+ content: /* @__PURE__ */ jsx50(PriceProvider, { productId: product.id, publishChrome: false, children: /* @__PURE__ */ jsx50(PricesList, { productId: product.id }) })
4090
+ }
4091
+ ]
4092
+ }
4093
+ );
4094
+ }
4095
+ __name(ProductContainer, "ProductContainer");
4096
+
4097
+ // src/features/billing/stripe-product/components/lists/ProductsList.tsx
4098
+ import { PackageIcon } from "lucide-react";
4099
+ import { useTranslations as useTranslations11 } from "next-intl";
4100
+ import { useEffect as useEffect12 } from "react";
4101
+ import { jsx as jsx51 } from "react/jsx-runtime";
4102
+ function ProductsList({ fullWidth } = {}) {
4103
+ const t = useTranslations11();
4104
+ const { catalogueVersion } = useProductContext();
4105
+ const data = useDataListRetriever({
4106
+ retriever: /* @__PURE__ */ __name((params) => StripeProductService.listProducts(params), "retriever"),
4107
+ retrieverParams: {},
4108
+ module: Modules.StripeProduct
4109
+ });
4110
+ useEffect12(() => {
4111
+ if (catalogueVersion > 0) void data.refresh();
4112
+ }, [catalogueVersion]);
4113
+ const functions = [/* @__PURE__ */ jsx51(ProductEditor, {}, "create-product")];
4114
+ return /* @__PURE__ */ jsx51(
4115
+ ContentListTable,
4116
+ {
4117
+ data,
4118
+ fields: ["name" /* name */, "status" /* status */, "prices" /* prices */],
4119
+ tableGeneratorType: Modules.StripeProduct,
4120
+ functions,
4121
+ fullWidth,
4122
+ title: t("billing.admin.products.title"),
4123
+ emptyState: /* @__PURE__ */ jsx51(
4124
+ EmptyState,
4125
+ {
4126
+ icon: PackageIcon,
4127
+ title: t("billing.admin.products.empty.title"),
4128
+ description: t("billing.admin.products.empty.description")
4129
+ }
4130
+ )
4131
+ }
4132
+ );
4133
+ }
4134
+ __name(ProductsList, "ProductsList");
3983
4135
 
3984
- // src/features/billing/stripe-product/components/containers/ProductsAdminPageContainer.tsx
3985
- import { jsx as jsx46 } from "react/jsx-runtime";
3986
- function ProductsAdminPageContainer() {
3987
- return /* @__PURE__ */ jsx46(BillingProvider, { children: /* @__PURE__ */ jsx46(RoundPageContainer, { module: Modules.StripeProduct, fullWidth: true, forceHeader: true, children: /* @__PURE__ */ jsx46(ProductsAdminContainer, {}) }) });
4136
+ // src/features/billing/stripe-product/components/containers/ProductsListContainer.tsx
4137
+ import { jsx as jsx52 } from "react/jsx-runtime";
4138
+ function ProductsListContainer() {
4139
+ return /* @__PURE__ */ jsx52(ProductProvider, { children: /* @__PURE__ */ jsx52(RoundPageContainer, { module: Modules.StripeProduct, fullWidth: true, children: /* @__PURE__ */ jsx52(ProductsList, { fullWidth: true }) }) });
3988
4140
  }
3989
- __name(ProductsAdminPageContainer, "ProductsAdminPageContainer");
4141
+ __name(ProductsListContainer, "ProductsListContainer");
3990
4142
  export {
3991
4143
  BillingAlertBanner,
3992
4144
  BillingDashboardContainer,
3993
4145
  BillingDetailModal,
3994
- BillingProvider,
3995
4146
  BillingUsageSummaryCard,
3996
4147
  CancelSubscriptionDialog,
3997
4148
  CustomerInfoCard,
@@ -4007,15 +4158,22 @@ export {
4007
4158
  PaymentMethodSummaryCard,
4008
4159
  PaymentMethodsContainer,
4009
4160
  PaymentMethodsList,
4161
+ PriceArchiver,
4162
+ PriceContainer,
4163
+ PriceDetails,
4010
4164
  PriceEditor,
4165
+ PriceProvider,
4011
4166
  PricesList,
4012
4167
  PricingCard,
4168
+ ProductArchiver,
4169
+ ProductContainer,
4170
+ ProductDetails,
4013
4171
  ProductEditor,
4014
4172
  ProductPricingList,
4015
4173
  ProductPricingRow,
4016
- ProductsAdminContainer,
4017
- ProductsAdminPageContainer,
4174
+ ProductProvider,
4018
4175
  ProductsList,
4176
+ ProductsListContainer,
4019
4177
  PromoCodeInput,
4020
4178
  ProrationPreview,
4021
4179
  StripeProvider,
@@ -4033,7 +4191,8 @@ export {
4033
4191
  formatDate3 as formatDate,
4034
4192
  formatInterval,
4035
4193
  isStripeConfigured,
4036
- useBillingContext,
4037
- useOptionalBillingContext
4194
+ priceLabel,
4195
+ usePriceContext,
4196
+ useProductContext
4038
4197
  };
4039
4198
  //# sourceMappingURL=index.mjs.map