@carlonicora/nextjs-jsonapi 3.11.0 → 3.12.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 (57) hide show
  1. package/dist/{BlockNoteEditor-XOVNTXDY.js → BlockNoteEditor-6G27S6P3.js} +19 -19
  2. package/dist/{BlockNoteEditor-XOVNTXDY.js.map → BlockNoteEditor-6G27S6P3.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-NBHDEZ7W.mjs → BlockNoteEditor-CR5KH4AI.mjs} +4 -4
  4. package/dist/{BlockNoteViewer-C2UUOBAF.js → BlockNoteViewer-5MITJ7AD.js} +3 -3
  5. package/dist/{BlockNoteViewer-C2UUOBAF.js.map → BlockNoteViewer-5MITJ7AD.js.map} +1 -1
  6. package/dist/{BlockNoteViewer-TYLVTIUD.mjs → BlockNoteViewer-NJKFUNXP.mjs} +2 -2
  7. package/dist/billing/index.d.mts +27 -0
  8. package/dist/billing/index.d.ts +27 -0
  9. package/dist/billing/index.js +697 -450
  10. package/dist/billing/index.js.map +1 -1
  11. package/dist/billing/index.mjs +282 -35
  12. package/dist/billing/index.mjs.map +1 -1
  13. package/dist/{chunk-V6CGCARD.js → chunk-4ETVCRQD.js} +7 -7
  14. package/dist/{chunk-V6CGCARD.js.map → chunk-4ETVCRQD.js.map} +1 -1
  15. package/dist/{chunk-IMSIVIIC.mjs → chunk-HVNEWDRF.mjs} +2 -2
  16. package/dist/{chunk-XNGI4HDZ.mjs → chunk-JEGL7KHK.mjs} +49 -6
  17. package/dist/{chunk-XNGI4HDZ.mjs.map → chunk-JEGL7KHK.mjs.map} +1 -1
  18. package/dist/{chunk-6J2SQZ3G.js → chunk-L7DZSTFU.js} +2 -1
  19. package/dist/chunk-L7DZSTFU.js.map +1 -0
  20. package/dist/{chunk-YN4LOVL4.js → chunk-OKGGYCO5.js} +904 -861
  21. package/dist/chunk-OKGGYCO5.js.map +1 -0
  22. package/dist/{chunk-M5QAHSCY.mjs → chunk-Q2BREXH3.mjs} +2 -1
  23. package/dist/chunk-Q2BREXH3.mjs.map +1 -0
  24. package/dist/client/index.js +4 -4
  25. package/dist/client/index.mjs +3 -3
  26. package/dist/components/index.js +4 -4
  27. package/dist/components/index.mjs +3 -3
  28. package/dist/contexts/index.js +4 -4
  29. package/dist/contexts/index.mjs +3 -3
  30. package/dist/core/index.d.mts +2 -1
  31. package/dist/core/index.d.ts +2 -1
  32. package/dist/core/index.js +2 -2
  33. package/dist/core/index.mjs +1 -1
  34. package/dist/features/help/index.js +37 -37
  35. package/dist/features/help/index.mjs +3 -3
  36. package/dist/features/tokenusage/index.js +101 -101
  37. package/dist/features/tokenusage/index.mjs +3 -3
  38. package/dist/index.js +3 -3
  39. package/dist/index.mjs +2 -2
  40. package/dist/server/index.js +3 -3
  41. package/dist/server/index.mjs +1 -1
  42. package/package.json +1 -1
  43. package/src/features/billing/i18n-keys.ts +7 -0
  44. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +108 -2
  45. package/src/features/billing/stripe-price/components/forms/__tests__/PriceEditor.spec.tsx +182 -0
  46. package/src/features/billing/stripe-price/components/lists/PricesList.tsx +152 -25
  47. package/src/features/billing/stripe-price/components/lists/__tests__/PricesList.spec.tsx +268 -0
  48. package/src/features/billing/stripe-price/data/__tests__/stripe-price.transfer.spec.ts +265 -0
  49. package/src/features/billing/stripe-price/data/stripe-price.fields.ts +1 -0
  50. package/src/features/billing/stripe-price/data/stripe-price.transfer.ts +194 -0
  51. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +66 -2
  52. package/dist/chunk-6J2SQZ3G.js.map +0 -1
  53. package/dist/chunk-M5QAHSCY.mjs.map +0 -1
  54. package/dist/chunk-YN4LOVL4.js.map +0 -1
  55. /package/dist/{BlockNoteEditor-NBHDEZ7W.mjs.map → BlockNoteEditor-CR5KH4AI.mjs.map} +0 -0
  56. /package/dist/{BlockNoteViewer-TYLVTIUD.mjs.map → BlockNoteViewer-NJKFUNXP.mjs.map} +0 -0
  57. /package/dist/{chunk-IMSIVIIC.mjs.map → chunk-HVNEWDRF.mjs.map} +0 -0
@@ -62,12 +62,12 @@ import {
62
62
  useCurrentUserContext,
63
63
  useDataListRetriever,
64
64
  usePageUrlGenerator
65
- } from "../chunk-XNGI4HDZ.mjs";
65
+ } from "../chunk-JEGL7KHK.mjs";
66
66
  import {
67
67
  getRoleId,
68
68
  getStripePublishableKey,
69
69
  useI18nRouter
70
- } from "../chunk-IMSIVIIC.mjs";
70
+ } from "../chunk-HVNEWDRF.mjs";
71
71
  import {
72
72
  FeatureService,
73
73
  Modules,
@@ -79,8 +79,9 @@ import {
79
79
  StripeSubscriptionService,
80
80
  StripeUsageService,
81
81
  cn,
82
- rehydrate
83
- } from "../chunk-M5QAHSCY.mjs";
82
+ rehydrate,
83
+ showToast
84
+ } from "../chunk-Q2BREXH3.mjs";
84
85
  import "../chunk-AUXK7QSA.mjs";
85
86
  import "../chunk-C7C7VY4F.mjs";
86
87
  import "../chunk-BMVA2IM5.mjs";
@@ -3136,7 +3137,7 @@ __name(PriceArchiver, "PriceArchiver");
3136
3137
  import { zodResolver as zodResolver2 } from "@hookform/resolvers/zod";
3137
3138
  import { PlusIcon, XIcon } from "lucide-react";
3138
3139
  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 { useCallback as useCallback5, useEffect as useEffect10, useMemo as useMemo5, useRef as useRef3, useState as useState16 } from "react";
3140
3141
  import { useForm as useForm2 } from "react-hook-form";
3141
3142
  import { v4 as v42 } from "uuid";
3142
3143
  import { z as z2 } from "zod";
@@ -3144,6 +3145,7 @@ import { jsx as jsx41, jsxs as jsxs36 } from "react/jsx-runtime";
3144
3145
  function PriceEditorInternal({
3145
3146
  productId,
3146
3147
  price,
3148
+ seed,
3147
3149
  propagateChanges,
3148
3150
  onSuccess,
3149
3151
  trigger,
@@ -3207,8 +3209,39 @@ function PriceEditorInternal({
3207
3209
  }),
3208
3210
  [t]
3209
3211
  );
3212
+ const resolvePlatformFeatures = useCallback5(
3213
+ (names) => {
3214
+ const ids = [];
3215
+ const unknown = [];
3216
+ for (const name of names ?? []) {
3217
+ const match = allFeatures.find((feature) => feature.name === name);
3218
+ if (match) ids.push(match.id);
3219
+ else unknown.push(name);
3220
+ }
3221
+ return { ids, unknown };
3222
+ },
3223
+ [allFeatures]
3224
+ );
3210
3225
  const getDefaultValues = useCallback5(() => {
3211
3226
  const coreFeatureIds = allFeatures.filter((feature) => feature.isCore).map((feature) => feature.id);
3227
+ if (!price && seed) {
3228
+ return {
3229
+ unitAmount: seed.unitAmount / 100,
3230
+ currency: seed.currency,
3231
+ interval: seed.interval,
3232
+ intervalCount: seed.intervalCount ?? 1,
3233
+ usageType: seed.usageType ?? "licensed",
3234
+ nickname: seed.nickname ?? "",
3235
+ isTrial: seed.isTrial ?? false,
3236
+ description: seed.description ?? "",
3237
+ features: seed.features ?? [],
3238
+ // `.toString()`, for the same reason the schema keeps the token
3239
+ // pipeline string-based: a seeded `0` must survive as "0" and not be
3240
+ // dropped as falsy.
3241
+ token: seed.token?.toString() ?? "",
3242
+ featureIds: [.../* @__PURE__ */ new Set([...resolvePlatformFeatures(seed.platformFeatures).ids, ...coreFeatureIds])]
3243
+ };
3244
+ }
3212
3245
  return {
3213
3246
  unitAmount: price?.unitAmount ? price.unitAmount / 100 : 0,
3214
3247
  currency: price?.currency ?? "usd",
@@ -3222,11 +3255,28 @@ function PriceEditorInternal({
3222
3255
  token: price?.token?.toString() ?? "",
3223
3256
  featureIds: [.../* @__PURE__ */ new Set([...price?.priceFeatures?.map((f) => f.id) ?? [], ...coreFeatureIds])]
3224
3257
  };
3225
- }, [price, allFeatures]);
3258
+ }, [price, seed, allFeatures, resolvePlatformFeatures]);
3226
3259
  const form = useForm2({
3227
3260
  resolver: zodResolver2(formSchema2),
3228
3261
  defaultValues: getDefaultValues()
3229
3262
  });
3263
+ const hasCaughtUpWithFeatures = useRef3(false);
3264
+ useEffect10(() => {
3265
+ if (isEdit || hasCaughtUpWithFeatures.current || allFeatures.length === 0) return;
3266
+ hasCaughtUpWithFeatures.current = true;
3267
+ if (form.formState.isDirty) return;
3268
+ form.reset(getDefaultValues());
3269
+ }, [allFeatures, isEdit, form, getDefaultValues]);
3270
+ const warnedUnknownFor = useRef3(void 0);
3271
+ useEffect10(() => {
3272
+ if (isEdit || !seed || allFeatures.length === 0) return;
3273
+ const fingerprint = JSON.stringify(seed.platformFeatures ?? []);
3274
+ if (warnedUnknownFor.current === fingerprint) return;
3275
+ warnedUnknownFor.current = fingerprint;
3276
+ const { unknown } = resolvePlatformFeatures(seed.platformFeatures);
3277
+ if (unknown.length > 0)
3278
+ showToast(t("billing.admin.prices.import.warnings.unknownFeatures", { features: unknown.join(", ") }));
3279
+ }, [seed, isEdit, allFeatures, resolvePlatformFeatures, t]);
3230
3280
  const watchInterval = form.watch("interval");
3231
3281
  const isRecurring = watchInterval !== "one_time";
3232
3282
  const currencyOptions = [
@@ -3989,13 +4039,126 @@ function PriceContainer() {
3989
4039
  __name(PriceContainer, "PriceContainer");
3990
4040
 
3991
4041
  // src/features/billing/stripe-price/components/lists/PricesList.tsx
3992
- import { DollarSignIcon } from "lucide-react";
4042
+ import { DollarSignIcon, UploadIcon } from "lucide-react";
3993
4043
  import { useTranslations as useTranslations8 } from "next-intl";
3994
- import { useEffect as useEffect11 } from "react";
3995
- import { jsx as jsx48 } from "react/jsx-runtime";
4044
+ import { Fragment as Fragment11, useCallback as useCallback9, useEffect as useEffect11, useMemo as useMemo9, useRef as useRef4, useState as useState20 } from "react";
4045
+
4046
+ // src/features/billing/stripe-price/data/stripe-price.transfer.ts
4047
+ import { z as z4 } from "zod";
4048
+ var STRIPE_PRICE_TRANSFER_KIND = "stripe-price";
4049
+ var STRIPE_PRICE_TRANSFER_VERSION = 1;
4050
+ var SUPPORTED_VERSIONS = [STRIPE_PRICE_TRANSFER_VERSION];
4051
+ var seedSchema = z4.object({
4052
+ unitAmount: z4.number().min(0),
4053
+ currency: z4.string().min(1),
4054
+ interval: z4.enum(["one_time", "day", "week", "month", "year"]),
4055
+ intervalCount: z4.number().min(1).optional(),
4056
+ usageType: z4.enum(["licensed", "metered"]).optional(),
4057
+ nickname: z4.string().optional(),
4058
+ description: z4.string().optional(),
4059
+ features: z4.array(z4.string()).optional(),
4060
+ token: z4.number().optional(),
4061
+ isTrial: z4.boolean().optional(),
4062
+ platformFeatures: z4.array(z4.string()).optional()
4063
+ });
4064
+ function readOptional(read) {
4065
+ try {
4066
+ return read();
4067
+ } catch {
4068
+ return void 0;
4069
+ }
4070
+ }
4071
+ __name(readOptional, "readOptional");
4072
+ function nonEmptyStrings(values) {
4073
+ if (!Array.isArray(values)) return [];
4074
+ return values.map((value) => value.trim()).filter((value) => value.length > 0);
4075
+ }
4076
+ __name(nonEmptyStrings, "nonEmptyStrings");
4077
+ function priceToSeed(price) {
4078
+ const priceType = readOptional(() => price.priceType);
4079
+ const currency = readOptional(() => price.currency);
4080
+ const recurring = price.recurring;
4081
+ const interval = priceType === "one_time" ? "one_time" : recurring?.interval ?? "month";
4082
+ const seed = {
4083
+ unitAmount: price.unitAmount ?? 0,
4084
+ currency: currency ?? "usd",
4085
+ interval
4086
+ };
4087
+ if (interval !== "one_time") {
4088
+ if (typeof recurring?.intervalCount === "number") seed.intervalCount = recurring.intervalCount;
4089
+ if (recurring?.usageType) seed.usageType = recurring.usageType;
4090
+ }
4091
+ const nickname = price.nickname?.trim();
4092
+ if (nickname) seed.nickname = nickname;
4093
+ const description = price.description?.trim();
4094
+ if (description) seed.description = description;
4095
+ const features = nonEmptyStrings(price.features);
4096
+ if (features.length > 0) seed.features = features;
4097
+ if (typeof price.token === "number" && !Number.isNaN(price.token)) seed.token = price.token;
4098
+ if (typeof price.isTrial === "boolean") seed.isTrial = price.isTrial;
4099
+ const platformFeatures = nonEmptyStrings(price.priceFeatures?.map((feature) => feature.name));
4100
+ if (platformFeatures.length > 0) seed.platformFeatures = platformFeatures;
4101
+ return seed;
4102
+ }
4103
+ __name(priceToSeed, "priceToSeed");
4104
+ function buildStripePriceTransferDocument(price) {
4105
+ return {
4106
+ kind: STRIPE_PRICE_TRANSFER_KIND,
4107
+ version: STRIPE_PRICE_TRANSFER_VERSION,
4108
+ price: priceToSeed(price)
4109
+ };
4110
+ }
4111
+ __name(buildStripePriceTransferDocument, "buildStripePriceTransferDocument");
4112
+ function slugify(value) {
4113
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4114
+ }
4115
+ __name(slugify, "slugify");
4116
+ function stripePriceTransferFilename(price) {
4117
+ const seed = priceToSeed(price);
4118
+ const fromNickname = seed.nickname ? slugify(seed.nickname) : "";
4119
+ const slug = fromNickname || slugify(`${seed.unitAmount}-${seed.currency}`);
4120
+ return `price-${slug || "export"}.json`;
4121
+ }
4122
+ __name(stripePriceTransferFilename, "stripePriceTransferFilename");
4123
+ function parseStripePriceTransferDocument(raw) {
4124
+ let candidate;
4125
+ try {
4126
+ candidate = JSON.parse(raw);
4127
+ } catch {
4128
+ throw new Error("The file is not valid JSON.");
4129
+ }
4130
+ if (typeof candidate !== "object" || candidate === null || Array.isArray(candidate))
4131
+ throw new Error("The file is not a price transfer document.");
4132
+ const document2 = candidate;
4133
+ if (document2.kind !== STRIPE_PRICE_TRANSFER_KIND)
4134
+ throw new Error(
4135
+ `The file is not a price transfer document: expected kind "${STRIPE_PRICE_TRANSFER_KIND}", found ${JSON.stringify(document2.kind)}.`
4136
+ );
4137
+ if (typeof document2.version !== "number" || !SUPPORTED_VERSIONS.includes(document2.version))
4138
+ throw new Error(
4139
+ `Unsupported price transfer version ${JSON.stringify(document2.version)}. Supported: ${SUPPORTED_VERSIONS.join(", ")}.`
4140
+ );
4141
+ const parsed = seedSchema.safeParse(document2.price);
4142
+ if (!parsed.success) {
4143
+ const details = parsed.error.issues.map((issue) => `${issue.path.join(".") || "price"}: ${issue.message}`).join("; ");
4144
+ throw new Error(`The price in the file is not valid. ${details}`);
4145
+ }
4146
+ return {
4147
+ kind: STRIPE_PRICE_TRANSFER_KIND,
4148
+ version: STRIPE_PRICE_TRANSFER_VERSION,
4149
+ price: parsed.data
4150
+ };
4151
+ }
4152
+ __name(parseStripePriceTransferDocument, "parseStripePriceTransferDocument");
4153
+
4154
+ // src/features/billing/stripe-price/components/lists/PricesList.tsx
4155
+ import { Fragment as Fragment12, jsx as jsx48, jsxs as jsxs40 } from "react/jsx-runtime";
3996
4156
  function PricesList({ productId, fullWidth }) {
3997
4157
  const t = useTranslations8();
3998
4158
  const { priceVersion } = usePriceContext();
4159
+ const importInputRef = useRef4(null);
4160
+ const [seed, setSeed] = useState20(void 0);
4161
+ const [seedNonce, setSeedNonce] = useState20(0);
3999
4162
  const data = useDataListRetriever({
4000
4163
  retriever: /* @__PURE__ */ __name((params) => StripePriceService.listPrices({ ...params, productId }), "retriever"),
4001
4164
  retrieverParams: {},
@@ -4004,32 +4167,116 @@ function PricesList({ productId, fullWidth }) {
4004
4167
  useEffect11(() => {
4005
4168
  if (priceVersion > 0) void data.refresh();
4006
4169
  }, [priceVersion]);
4007
- const functions = [/* @__PURE__ */ jsx48(PriceEditor, { productId }, "create-price")];
4008
- return /* @__PURE__ */ jsx48(
4009
- ContentListTable,
4010
- {
4011
- data,
4012
- fields: [
4013
- "nickname" /* nickname */,
4014
- "amount" /* amount */,
4015
- "interval" /* interval */,
4016
- "token" /* token */,
4017
- "status" /* status */
4018
- ],
4019
- tableGeneratorType: Modules.StripePrice,
4020
- functions,
4021
- fullWidth,
4022
- title: t("billing.admin.prices.title"),
4023
- emptyState: /* @__PURE__ */ jsx48(
4024
- EmptyState,
4170
+ const openSeededEditor = useCallback9((next) => {
4171
+ setSeed(next);
4172
+ setSeedNonce((nonce) => nonce + 1);
4173
+ }, []);
4174
+ const onClone = useCallback9(
4175
+ (price) => openSeededEditor(priceToSeed(price)),
4176
+ [openSeededEditor]
4177
+ );
4178
+ const onExport = useCallback9((price) => {
4179
+ const text = JSON.stringify(buildStripePriceTransferDocument(price), null, 2);
4180
+ const blob = new Blob([text], { type: "application/json" });
4181
+ const url = URL.createObjectURL(blob);
4182
+ const a = document.createElement("a");
4183
+ a.href = url;
4184
+ a.download = stripePriceTransferFilename(price);
4185
+ a.click();
4186
+ URL.revokeObjectURL(url);
4187
+ }, []);
4188
+ const tableContext = useMemo9(() => ({ onClone, onExport }), [onClone, onExport]);
4189
+ const onImportFileChange = useCallback9(
4190
+ async (event) => {
4191
+ const input = event.target;
4192
+ const file = input.files?.[0];
4193
+ input.value = "";
4194
+ if (!file) return;
4195
+ try {
4196
+ const transfer = parseStripePriceTransferDocument(await file.text());
4197
+ openSeededEditor(transfer.price);
4198
+ } catch (error) {
4199
+ console.error("[PricesList] price import failed", error);
4200
+ errorToast({
4201
+ title: t("billing.admin.prices.import.errors.title"),
4202
+ error: t("billing.admin.prices.import.errors.invalid")
4203
+ });
4204
+ }
4205
+ },
4206
+ [openSeededEditor, t]
4207
+ );
4208
+ const functions = [
4209
+ /* @__PURE__ */ jsx48(
4210
+ PriceEditor,
4211
+ {
4212
+ productId,
4213
+ onSuccess: () => {
4214
+ }
4215
+ },
4216
+ "create-price"
4217
+ ),
4218
+ /* @__PURE__ */ jsxs40(Fragment11, { children: [
4219
+ /* @__PURE__ */ jsxs40(Button, { variant: "outline", size: "sm", type: "button", onClick: () => importInputRef.current?.click(), children: [
4220
+ /* @__PURE__ */ jsx48(UploadIcon, {}),
4221
+ t("billing.admin.prices.actions.import")
4222
+ ] }),
4223
+ /* @__PURE__ */ jsx48(
4224
+ "input",
4025
4225
  {
4026
- icon: DollarSignIcon,
4027
- title: t("billing.admin.prices.empty.title"),
4028
- description: t("billing.admin.prices.empty.description")
4226
+ ref: importInputRef,
4227
+ type: "file",
4228
+ accept: "application/json",
4229
+ className: "hidden",
4230
+ onChange: onImportFileChange
4029
4231
  }
4030
4232
  )
4031
- }
4032
- );
4233
+ ] }, "import-price")
4234
+ ];
4235
+ return /* @__PURE__ */ jsxs40(Fragment12, { children: [
4236
+ /* @__PURE__ */ jsx48(
4237
+ ContentListTable,
4238
+ {
4239
+ data,
4240
+ fields: [
4241
+ "nickname" /* nickname */,
4242
+ "amount" /* amount */,
4243
+ "interval" /* interval */,
4244
+ "token" /* token */,
4245
+ "status" /* status */,
4246
+ "actions" /* actions */
4247
+ ],
4248
+ tableGeneratorType: Modules.StripePrice,
4249
+ functions,
4250
+ context: tableContext,
4251
+ fullWidth,
4252
+ title: t("billing.admin.prices.title"),
4253
+ emptyState: /* @__PURE__ */ jsx48(
4254
+ EmptyState,
4255
+ {
4256
+ icon: DollarSignIcon,
4257
+ title: t("billing.admin.prices.empty.title"),
4258
+ description: t("billing.admin.prices.empty.description")
4259
+ }
4260
+ )
4261
+ }
4262
+ ),
4263
+ seed && /* @__PURE__ */ jsx48(
4264
+ PriceEditor,
4265
+ {
4266
+ productId,
4267
+ seed,
4268
+ dialogOpen: true,
4269
+ onDialogOpenChange: (open) => {
4270
+ if (!open) setSeed(void 0);
4271
+ },
4272
+ onSuccess: async () => {
4273
+ setSeed(void 0);
4274
+ await data.refresh();
4275
+ }
4276
+ },
4277
+ seedNonce
4278
+ )
4279
+ ] });
4033
4280
  }
4034
4281
  __name(PricesList, "PricesList");
4035
4282
 
@@ -4038,14 +4285,14 @@ import { useTranslations as useTranslations10 } from "next-intl";
4038
4285
 
4039
4286
  // src/features/billing/stripe-product/components/details/ProductDetails.tsx
4040
4287
  import { useTranslations as useTranslations9 } from "next-intl";
4041
- import { jsx as jsx49, jsxs as jsxs40 } from "react/jsx-runtime";
4288
+ import { jsx as jsx49, jsxs as jsxs41 } from "react/jsx-runtime";
4042
4289
  function ProductDetails() {
4043
4290
  const t = useTranslations9();
4044
4291
  const { product } = useProductContext();
4045
4292
  if (!product) return null;
4046
- return /* @__PURE__ */ jsxs40("div", { className: "flex w-full flex-col gap-y-4", children: [
4293
+ return /* @__PURE__ */ jsxs41("div", { className: "flex w-full flex-col gap-y-4", children: [
4047
4294
  /* @__PURE__ */ jsx49(SectionHeader, { children: t("billing.admin.products.tabs.details") }),
4048
- /* @__PURE__ */ jsxs40("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
4295
+ /* @__PURE__ */ jsxs41("div", { className: "grid grid-cols-1 gap-4 md:grid-cols-2", children: [
4049
4296
  /* @__PURE__ */ jsx49(DetailField, { label: t("billing.admin.products.fields.name"), value: product.name }),
4050
4297
  /* @__PURE__ */ jsx49(
4051
4298
  DetailField,