@carlonicora/nextjs-jsonapi 5.6.0 → 5.7.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 (87) hide show
  1. package/dist/{BlockNoteEditor-NEYG5L6W.mjs → BlockNoteEditor-R3WXUFHX.mjs} +4 -4
  2. package/dist/{BlockNoteEditor-AE3ZTEEH.js → BlockNoteEditor-ZHXXFWUJ.js} +19 -19
  3. package/dist/{BlockNoteEditor-AE3ZTEEH.js.map → BlockNoteEditor-ZHXXFWUJ.js.map} +1 -1
  4. package/dist/{BlockNoteViewer-3OUT5UFY.mjs → BlockNoteViewer-BVVN6D3T.mjs} +2 -2
  5. package/dist/{BlockNoteViewer-4CFKQM7U.js → BlockNoteViewer-SD3ZFIXD.js} +3 -3
  6. package/dist/{BlockNoteViewer-4CFKQM7U.js.map → BlockNoteViewer-SD3ZFIXD.js.map} +1 -1
  7. package/dist/billing/index.d.mts +33 -43
  8. package/dist/billing/index.d.ts +33 -43
  9. package/dist/billing/index.js +590 -574
  10. package/dist/billing/index.js.map +1 -1
  11. package/dist/billing/index.mjs +28 -12
  12. package/dist/billing/index.mjs.map +1 -1
  13. package/dist/{chunk-3I5AAJXV.js → chunk-2TXE57LE.js} +3 -3
  14. package/dist/{chunk-3I5AAJXV.js.map → chunk-2TXE57LE.js.map} +1 -1
  15. package/dist/{chunk-HKZAESS6.js → chunk-5TH2DSAH.js} +17 -12
  16. package/dist/chunk-5TH2DSAH.js.map +1 -0
  17. package/dist/{chunk-SFYPXCK4.mjs → chunk-CFB6FHIY.mjs} +638 -536
  18. package/dist/{chunk-SFYPXCK4.mjs.map → chunk-CFB6FHIY.mjs.map} +1 -1
  19. package/dist/{chunk-EYA2F6AN.mjs → chunk-FK26NX3S.mjs} +16 -11
  20. package/dist/chunk-FK26NX3S.mjs.map +1 -0
  21. package/dist/{chunk-53BAULDP.js → chunk-IJME5SAM.js} +4 -4
  22. package/dist/{chunk-53BAULDP.js.map → chunk-IJME5SAM.js.map} +1 -1
  23. package/dist/{chunk-KQHBONL4.mjs → chunk-OKCV4GPJ.mjs} +2 -2
  24. package/dist/{chunk-BTPUYUQ3.mjs → chunk-RGQYFSJF.mjs} +2 -2
  25. package/dist/{chunk-Y7RR57EW.mjs → chunk-WCHLCOZG.mjs} +2 -2
  26. package/dist/{chunk-RL3PL3KV.js → chunk-XOSC3FFP.js} +8 -8
  27. package/dist/{chunk-RL3PL3KV.js.map → chunk-XOSC3FFP.js.map} +1 -1
  28. package/dist/{chunk-LQ4DK4JF.js → chunk-ZCOPOO7T.js} +1314 -1212
  29. package/dist/chunk-ZCOPOO7T.js.map +1 -0
  30. package/dist/client/index.js +4 -4
  31. package/dist/client/index.mjs +3 -3
  32. package/dist/components/index.js +4 -4
  33. package/dist/components/index.mjs +3 -3
  34. package/dist/contexts/index.js +4 -4
  35. package/dist/contexts/index.mjs +3 -3
  36. package/dist/core/index.d.mts +9 -3
  37. package/dist/core/index.d.ts +9 -3
  38. package/dist/core/index.js +10 -2
  39. package/dist/core/index.js.map +1 -1
  40. package/dist/core/index.mjs +9 -1
  41. package/dist/{format-options-D6c7FNDi.d.mts → currency-DT_dveBx.d.mts} +41 -8
  42. package/dist/{format-options-BtX3o0Yu.d.ts → currency-pK9eRYRC.d.ts} +41 -8
  43. package/dist/email-automations/index.js +92 -92
  44. package/dist/email-automations/index.mjs +3 -3
  45. package/dist/features/analytics/index.js +121 -121
  46. package/dist/features/analytics/index.mjs +4 -4
  47. package/dist/features/analytics/server.js +4 -4
  48. package/dist/features/analytics/server.mjs +2 -2
  49. package/dist/features/blog/index.js +150 -150
  50. package/dist/features/blog/index.mjs +3 -3
  51. package/dist/features/blog/server.js +16 -16
  52. package/dist/features/blog/server.mjs +1 -1
  53. package/dist/features/help/index.js +37 -37
  54. package/dist/features/help/index.mjs +3 -3
  55. package/dist/features/tokenusage/index.js +124 -124
  56. package/dist/features/tokenusage/index.mjs +4 -4
  57. package/dist/index.d.mts +2 -2
  58. package/dist/index.d.ts +2 -2
  59. package/dist/index.js +11 -3
  60. package/dist/index.js.map +1 -1
  61. package/dist/index.mjs +10 -2
  62. package/dist/server/index.js +3 -3
  63. package/dist/server/index.mjs +2 -2
  64. package/package.json +1 -1
  65. package/src/core/index.ts +3 -0
  66. package/src/features/billing/contexts/CouponContext.tsx +0 -2
  67. package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +5 -2
  68. package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +6 -5
  69. package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +67 -45
  70. package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +304 -212
  71. package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +4 -3
  72. package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +11 -7
  73. package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +6 -4
  74. package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +14 -7
  75. package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +2 -2
  76. package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +3 -2
  77. package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +11 -4
  78. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +1 -1
  79. package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +15 -8
  80. package/dist/chunk-EYA2F6AN.mjs.map +0 -1
  81. package/dist/chunk-HKZAESS6.js.map +0 -1
  82. package/dist/chunk-LQ4DK4JF.js.map +0 -1
  83. /package/dist/{BlockNoteEditor-NEYG5L6W.mjs.map → BlockNoteEditor-R3WXUFHX.mjs.map} +0 -0
  84. /package/dist/{BlockNoteViewer-3OUT5UFY.mjs.map → BlockNoteViewer-BVVN6D3T.mjs.map} +0 -0
  85. /package/dist/{chunk-KQHBONL4.mjs.map → chunk-OKCV4GPJ.mjs.map} +0 -0
  86. /package/dist/{chunk-BTPUYUQ3.mjs.map → chunk-RGQYFSJF.mjs.map} +0 -0
  87. /package/dist/{chunk-Y7RR57EW.mjs.map → chunk-WCHLCOZG.mjs.map} +0 -0
@@ -124,10 +124,12 @@ export class StripeCoupon extends AbstractApiData implements StripeCouponInterfa
124
124
 
125
125
  const attributes = response.data.attributes;
126
126
  if (data.stripeCouponId) attributes.stripeCouponId = data.stripeCouponId;
127
- if (data.percentOff !== undefined) attributes.percentOff = data.percentOff;
128
- if (data.amountOff !== undefined) attributes.amountOff = data.amountOff;
129
- if (data.currency) attributes.currency = data.currency;
130
- if (data.durationInMonths !== undefined) attributes.durationInMonths = data.durationInMonths;
127
+ if (data.discountType === "percent_off") attributes.percentOff = data.percentOff;
128
+ else {
129
+ attributes.amountOff = data.amountOff;
130
+ attributes.currency = data.currency;
131
+ }
132
+ if (data.duration === "repeating") attributes.durationInMonths = data.durationInMonths;
131
133
  if (data.productIds && data.productIds.length > 0) attributes.productIds = data.productIds;
132
134
  if (data.redeemBy !== undefined) attributes.redeemBy = data.redeemBy.toISOString();
133
135
  if (data.maxRedemptions !== undefined) attributes.maxRedemptions = data.maxRedemptions;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { ColumnDef } from "@tanstack/react-table";
4
4
  import { useTranslations } from "next-intl";
5
- import { useMemo } from "react";
5
+ import { useMemo, useState } from "react";
6
6
  import { Badge } from "../../../../shadcnui";
7
7
  import { registerTableGenerator, TableContent, UseTableStructureHook } from "../../../../hooks";
8
8
  import { formatDate, useBillingFormatOptions } from "../../components/utils";
@@ -20,8 +20,12 @@ export type CouponCodeStatus = "active" | "inactive" | "expired" | "used_up";
20
20
  * they win over a plain inactive flag. Places follow the same rule as the
21
21
  * backend: the smaller of the code's and the coupon's remaining limits.
22
22
  */
23
- export function couponCodeStatus(code: StripeCouponCodeInterface, coupon?: StripeCouponInterface): CouponCodeStatus {
24
- if (code.expiresAt && code.expiresAt.getTime() <= Date.now()) return "expired";
23
+ export function couponCodeStatus(
24
+ code: StripeCouponCodeInterface,
25
+ coupon: StripeCouponInterface | undefined,
26
+ now: Date,
27
+ ): CouponCodeStatus {
28
+ if (code.expiresAt && code.expiresAt.getTime() <= now.getTime()) return "expired";
25
29
  const left = couponPlacesLeft(code, coupon ?? { maxRedemptions: undefined, timesRedeemed: 0 });
26
30
  if (left === 0) return "used_up";
27
31
  return code.active ? "active" : "inactive";
@@ -43,6 +47,9 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
43
47
  // The codes list is mounted on the coupon's own page, so the coupon whose
44
48
  // limits share the places is the context's, not whatever the code includes.
45
49
  const { coupon } = useCouponContext();
50
+ // Read once on mount: render stays pure, and "expired" is judged against the
51
+ // moment the list was opened.
52
+ const [now] = useState(() => new Date());
46
53
 
47
54
  const tableData = useMemo(() => {
48
55
  return params.data.map((code: StripeCouponCodeInterface) => {
@@ -61,7 +68,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
61
68
  header: t("billing.admin.coupons.fields.code"),
62
69
  cell: ({ row }: { row: TableContent<StripeCouponCodeInterface> }) => {
63
70
  const code: StripeCouponCodeInterface = row.original.jsonApiData;
64
- return <span className="text-xs font-medium">{code.code}</span>;
71
+ return <span className="text-xs">{code.code}</span>;
65
72
  },
66
73
  enableSorting: false,
67
74
  enableHiding: false,
@@ -123,7 +130,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
123
130
  header: t("billing.admin.coupons.fields.status"),
124
131
  cell: ({ row }: { row: TableContent<StripeCouponCodeInterface> }) => {
125
132
  const code: StripeCouponCodeInterface = row.original.jsonApiData;
126
- const status = couponCodeStatus(code, code.coupon ?? coupon);
133
+ const status = couponCodeStatus(code, code.coupon ?? coupon, now);
127
134
  return <Badge variant={STATUS_VARIANT[status]}>{t(`billing.admin.coupons.codes.status.${status}`)}</Badge>;
128
135
  },
129
136
  enableSorting: false,
@@ -135,7 +142,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
135
142
  header: "",
136
143
  cell: ({ row }: { row: TableContent<StripeCouponCodeInterface> }) => {
137
144
  const code: StripeCouponCodeInterface = row.original.jsonApiData;
138
- return <CouponCodeActions code={code} status={couponCodeStatus(code, code.coupon ?? coupon)} />;
145
+ return <CouponCodeActions code={code} status={couponCodeStatus(code, code.coupon ?? coupon, now)} />;
139
146
  },
140
147
  enableSorting: false,
141
148
  enableHiding: false,
@@ -146,7 +153,7 @@ export const useStripeCouponCodeTableStructure: UseTableStructureHook<
146
153
  return params.fields.map((field) => fieldColumnMap[field]?.()).filter((col) => col !== undefined) as ColumnDef<
147
154
  TableContent<StripeCouponCodeInterface>
148
155
  >[];
149
- }, [params.fields, fieldColumnMap, t, coupon]);
156
+ }, [params.fields, fieldColumnMap, t, coupon, now]);
150
157
 
151
158
  return useMemo(() => ({ data: tableData, columns: columns }), [tableData, columns]);
152
159
  };
@@ -4,10 +4,10 @@ import { ColumnDef } from "@tanstack/react-table";
4
4
  import { useTranslations } from "next-intl";
5
5
  import { useMemo } from "react";
6
6
  import { Badge, Link } from "../../../../shadcnui";
7
- import { Modules } from "../../../../core";
8
7
  import { registerTableGenerator, TableContent, usePageUrlGenerator, UseTableStructureHook } from "../../../../hooks";
9
8
  import { useBillingFormatOptions } from "../../components/utils";
10
9
  import { StripeCouponFields } from "../data/stripe-coupon.fields";
10
+ import { STRIPE_COUPON_DETAIL_PAGE_URL } from "../stripe-coupon.module";
11
11
  import { StripeCouponInterface } from "../data/stripe-coupon.interface";
12
12
  import { formatCouponDiscount } from "../utils/format-coupon-discount";
13
13
 
@@ -35,7 +35,7 @@ export const useStripeCouponTableStructure: UseTableStructureHook<StripeCouponIn
35
35
  header: t("billing.admin.coupons.fields.name"),
36
36
  cell: ({ row }: { row: TableContent<StripeCouponInterface> }) => {
37
37
  const coupon: StripeCouponInterface = row.original.jsonApiData;
38
- return <Link href={generateUrl({ page: Modules.StripeCoupon, id: coupon.id })}>{coupon.name}</Link>;
38
+ return <Link href={generateUrl({ page: STRIPE_COUPON_DETAIL_PAGE_URL, id: coupon.id })}>{coupon.name}</Link>;
39
39
  },
40
40
  enableSorting: false,
41
41
  enableHiding: false,
@@ -7,8 +7,9 @@ export const STRIPE_COUPON_CODE_AUTOMATIC_SEGMENT = "automatic";
7
7
  export const StripeCouponCodeModule = (factory: ModuleFactory) =>
8
8
  factory({
9
9
  name: "stripe-coupon-codes",
10
- // Codes are managed on their coupon's page.
11
- pageUrl: "/administration/coupons",
10
+ // Codes are listed on the products page (Codes tab), same value as
11
+ // StripeProductModule. A code has no page of its own.
12
+ pageUrl: "/administration/products",
12
13
  model: StripeCouponCode,
13
14
  moduleId: "85feb4c7-ff22-416a-b856-8dafcf28ff26",
14
15
  });
@@ -5,13 +5,20 @@ import { StripeCoupon } from "./data/stripe-coupon";
5
5
  /** The coupon endpoint's non-resource segment that re-reads its counts from Stripe. */
6
6
  export const STRIPE_COUPON_REFRESH_SEGMENT = "refresh";
7
7
 
8
+ /**
9
+ * Route of a coupon's own page (`/administration/coupons/<id>`). It is not the
10
+ * module's pageUrl: there is no coupons list route, the list is the Codes tab of
11
+ * the products page. Links to one coupon build on this instead.
12
+ */
13
+ export const STRIPE_COUPON_DETAIL_PAGE_URL = "/administration/coupons";
14
+
8
15
  export const StripeCouponModule = (factory: ModuleFactory) =>
9
16
  factory({
10
17
  name: "stripe-coupons",
11
- // The administration route this entity is served at. `name` above stays the
12
- // API endpoint; this drives generateUrl / rewriteUrl / EditorSheet
13
- // navigation, so it must match the real Next.js route.
14
- pageUrl: "/administration/coupons",
18
+ // The coupons list lives on the products page (Codes tab), so the module
19
+ // points there, same value as StripeProductModule. Links to a single coupon
20
+ // use STRIPE_COUPON_DETAIL_PAGE_URL.
21
+ pageUrl: "/administration/products",
15
22
  model: StripeCoupon,
16
23
  moduleId: "50bdb0d2-539c-4358-b609-d6beab248bdb",
17
24
  icon: TicketPercentIcon,
@@ -118,7 +118,7 @@ export function PromoCodeInput({
118
118
  <p className="text-success text-sm">{formatDuration(appliedCode)}</p>
119
119
  </div>
120
120
  <div className="space-y-2">
121
- <p className="text-muted-foreground text-sm">{t("billing.ui.promo_code.use_different")}</p>
121
+ <p className="text-muted-foreground text-xs/relaxed">{t("billing.ui.promo_code.use_different")}</p>
122
122
  {codeInput}
123
123
  </div>
124
124
  </div>
@@ -19,8 +19,14 @@ export function shouldLookUpAutomatic(code: PromotionCodeValidationResult | null
19
19
  return !code || code.automatic === true;
20
20
  }
21
21
 
22
- function errorStatus(error: any): number | undefined {
23
- return error?.status ?? error?.response?.status;
22
+ /** `value[key]` when `value` is an object, else undefined. */
23
+ function read(value: unknown, key: string): unknown {
24
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined;
25
+ }
26
+
27
+ function errorStatus(error: unknown): number | undefined {
28
+ const status = read(error, "status") ?? read(read(error, "response"), "status");
29
+ return typeof status === "number" ? status : undefined;
24
30
  }
25
31
 
26
32
  /**
@@ -29,10 +35,11 @@ function errorStatus(error: any): number | undefined {
29
35
  * attaches to the thrown error as `body`:
30
36
  * `{ message, errors: [{ status, code, title, detail, meta: { context: { code } } }] }`.
31
37
  */
32
- function errorCode(error: any): string | undefined {
33
- const body = error?.body;
34
- const first = body?.errors?.[0];
35
- const code = first?.code ?? first?.meta?.context?.code ?? body?.code;
38
+ function errorCode(error: unknown): string | undefined {
39
+ const body = read(error, "body");
40
+ const errors = read(body, "errors");
41
+ const first = Array.isArray(errors) ? errors[0] : undefined;
42
+ const code = read(first, "code") ?? read(read(read(first, "meta"), "context"), "code") ?? read(body, "code");
36
43
  return typeof code === "string" ? code : undefined;
37
44
  }
38
45
 
@@ -40,7 +47,7 @@ function errorCode(error: any): string | undefined {
40
47
  * True when the API refused the payment because the automatic discount has no
41
48
  * places left (HTTP 410, code `discount_sold_out`). Nothing was charged.
42
49
  */
43
- export function isDiscountSoldOut(error: any): boolean {
50
+ export function isDiscountSoldOut(error: unknown): boolean {
44
51
  return errorStatus(error) === 410 && errorCode(error) === "discount_sold_out";
45
52
  }
46
53
 
@@ -49,6 +56,6 @@ export function isDiscountSoldOut(error: any): boolean {
49
56
  * showed is no longer valid (HTTP 422, code `discount_unavailable`). Nothing was
50
57
  * charged.
51
58
  */
52
- export function isDiscountUnavailable(error: any): boolean {
59
+ export function isDiscountUnavailable(error: unknown): boolean {
53
60
  return errorStatus(error) === 422 && errorCode(error) === "discount_unavailable";
54
61
  }