@carlonicora/nextjs-jsonapi 5.6.0 → 5.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/{BlockNoteEditor-AE3ZTEEH.js → BlockNoteEditor-ARUXU5AV.js} +19 -19
  2. package/dist/{BlockNoteEditor-AE3ZTEEH.js.map → BlockNoteEditor-ARUXU5AV.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-NEYG5L6W.mjs → BlockNoteEditor-MNNISNVN.mjs} +4 -4
  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 +591 -574
  10. package/dist/billing/index.js.map +1 -1
  11. package/dist/billing/index.mjs +30 -13
  12. package/dist/billing/index.mjs.map +1 -1
  13. package/dist/{chunk-SFYPXCK4.mjs → chunk-2FNSDBJG.mjs} +641 -536
  14. package/dist/chunk-2FNSDBJG.mjs.map +1 -0
  15. package/dist/{chunk-3I5AAJXV.js → chunk-2TXE57LE.js} +3 -3
  16. package/dist/{chunk-3I5AAJXV.js.map → chunk-2TXE57LE.js.map} +1 -1
  17. package/dist/{chunk-LQ4DK4JF.js → chunk-2WK35GO2.js} +1317 -1212
  18. package/dist/chunk-2WK35GO2.js.map +1 -0
  19. package/dist/{chunk-HKZAESS6.js → chunk-5TH2DSAH.js} +17 -12
  20. package/dist/chunk-5TH2DSAH.js.map +1 -0
  21. package/dist/{chunk-EYA2F6AN.mjs → chunk-FK26NX3S.mjs} +16 -11
  22. package/dist/chunk-FK26NX3S.mjs.map +1 -0
  23. package/dist/{chunk-53BAULDP.js → chunk-IJME5SAM.js} +4 -4
  24. package/dist/{chunk-53BAULDP.js.map → chunk-IJME5SAM.js.map} +1 -1
  25. package/dist/{chunk-KQHBONL4.mjs → chunk-OKCV4GPJ.mjs} +2 -2
  26. package/dist/{chunk-BTPUYUQ3.mjs → chunk-RGQYFSJF.mjs} +2 -2
  27. package/dist/{chunk-Y7RR57EW.mjs → chunk-WCHLCOZG.mjs} +2 -2
  28. package/dist/{chunk-RL3PL3KV.js → chunk-XOSC3FFP.js} +8 -8
  29. package/dist/{chunk-RL3PL3KV.js.map → chunk-XOSC3FFP.js.map} +1 -1
  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/containers/CouponContainer.tsx +2 -2
  68. package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +5 -2
  69. package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +6 -5
  70. package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +67 -45
  71. package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +294 -212
  72. package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +4 -3
  73. package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +11 -7
  74. package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +6 -4
  75. package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +14 -7
  76. package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +2 -2
  77. package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +3 -2
  78. package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +11 -4
  79. package/src/features/billing/stripe-coupon/utils/__tests__/coupon-page-module.spec.ts +32 -0
  80. package/src/features/billing/stripe-coupon/utils/coupon-page-module.ts +13 -0
  81. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +1 -1
  82. package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +15 -8
  83. package/dist/chunk-EYA2F6AN.mjs.map +0 -1
  84. package/dist/chunk-HKZAESS6.js.map +0 -1
  85. package/dist/chunk-LQ4DK4JF.js.map +0 -1
  86. package/dist/chunk-SFYPXCK4.mjs.map +0 -1
  87. /package/dist/{BlockNoteEditor-NEYG5L6W.mjs.map → BlockNoteEditor-MNNISNVN.mjs.map} +0 -0
  88. /package/dist/{BlockNoteViewer-3OUT5UFY.mjs.map → BlockNoteViewer-BVVN6D3T.mjs.map} +0 -0
  89. /package/dist/{chunk-KQHBONL4.mjs.map → chunk-OKCV4GPJ.mjs.map} +0 -0
  90. /package/dist/{chunk-BTPUYUQ3.mjs.map → chunk-RGQYFSJF.mjs.map} +0 -0
  91. /package/dist/{chunk-Y7RR57EW.mjs.map → chunk-WCHLCOZG.mjs.map} +0 -0
@@ -1,57 +1,74 @@
1
1
  "use client";
2
2
 
3
3
  import { useTranslations } from "next-intl";
4
+ import { useState } from "react";
5
+ import { FieldValues, UseFormReturn } from "react-hook-form";
4
6
  import { z } from "zod";
5
7
  import { FormCheckbox, FormDateTime, FormInput, FormSelect, FormSwitch } from "../../../../../components";
6
8
  import { FieldDescription } from "../../../../../shadcnui";
7
9
  import { StripeCouponCodeInput } from "../../data/stripe-coupon-code.interface";
8
10
 
9
11
  /**
10
- * Optional whole number of at least 1. The fields render as
11
- * `<FormInput type="number">`, which hands back `+""` (0) for a cleared input,
12
- * so 0, "" and NaN all mean "not set" — every limit here has a floor of 1.
12
+ * What a number or decimal `<FormInput>` holds: "" until something is typed, a
13
+ * number from `type="number"` (`+""` is 0 when cleared, NaN on garbage), a
14
+ * string from `type="decimal"` (may use a comma as the separator).
15
+ *
16
+ * The schemas below validate this raw value and never transform it, so the
17
+ * form's values have one type before and after validation. The `parse*`
18
+ * functions turn it into the number that is sent, on submit.
13
19
  */
14
- export const optionalPositiveInt = () =>
15
- z.preprocess(
16
- (value) =>
17
- value === undefined ||
18
- value === null ||
19
- value === "" ||
20
- value === 0 ||
21
- (typeof value === "number" && Number.isNaN(value))
22
- ? undefined
23
- : typeof value === "string"
24
- ? parseInt(value, 10)
25
- : value,
26
- z.number().int().min(1).optional(),
27
- );
20
+ type RawNumber = string | number | null | undefined;
21
+
22
+ const rawNumber = () => z.union([z.string(), z.number()]).nullish();
23
+
24
+ /** The issues `schema` reports for `value`, as plain messages. */
25
+ function issueMessages(schema: z.ZodType, value: unknown): string[] {
26
+ const result = schema.safeParse(value);
27
+ return result.success ? [] : result.error.issues.map((issue) => issue.message);
28
+ }
29
+
30
+ /**
31
+ * Optional whole number of at least 1. 0, "" and NaN all mean "not set": every
32
+ * limit here has a floor of 1.
33
+ */
34
+ export function parseOptionalPositiveInt(value: RawNumber): number | undefined {
35
+ if (value === undefined || value === null || value === "" || value === 0) return undefined;
36
+ if (typeof value === "number") return Number.isNaN(value) ? undefined : value;
37
+ return parseInt(value, 10);
38
+ }
28
39
 
29
40
  /**
30
- * Optional decimal in MAJOR units, from `<FormInput type="decimal">` (a string
31
- * that may use a comma as the separator). Converted to minor units on submit,
32
- * exactly as PriceEditor does (`Math.round(value * 100)`).
41
+ * Optional decimal in MAJOR units. Converted to minor units on submit, exactly
42
+ * as PriceEditor does (`Math.round(value * 100)`).
33
43
  */
44
+ export function parseOptionalDecimal(value: RawNumber): number | undefined {
45
+ if (typeof value === "string") return value.trim() === "" ? undefined : parseFloat(value.replace(",", "."));
46
+ if (value === undefined || value === null || Number.isNaN(value)) return undefined;
47
+ return value;
48
+ }
49
+
50
+ const positiveIntCheck = z.number().int().min(1).optional();
51
+ const decimalCheck = z.number().optional();
52
+
53
+ export const optionalPositiveInt = () =>
54
+ rawNumber().superRefine((value, ctx) => {
55
+ for (const message of issueMessages(positiveIntCheck, parseOptionalPositiveInt(value)))
56
+ ctx.addIssue({ code: "custom", message });
57
+ });
58
+
34
59
  export const optionalDecimal = () =>
35
- z.preprocess(
36
- (value) =>
37
- typeof value === "string"
38
- ? value.trim() === ""
39
- ? undefined
40
- : parseFloat(value.replace(",", "."))
41
- : value === null || (typeof value === "number" && Number.isNaN(value))
42
- ? undefined
43
- : value,
44
- z.number().optional(),
45
- );
60
+ rawNumber().superRefine((value, ctx) => {
61
+ for (const message of issueMessages(decimalCheck, parseOptionalDecimal(value)))
62
+ ctx.addIssue({ code: "custom", message });
63
+ });
46
64
 
47
- /** Mirrors StripeCouponCodePostAttributesDTO. */
48
65
  /**
49
66
  * Optional point in time from `<FormDateTime allowEmpty>`, which writes "" when
50
67
  * the value is cleared (FormDateTime.tsx:136, :241).
51
68
  */
52
- export const optionalDate = () =>
53
- z.preprocess((value) => (value === "" || value === null ? undefined : value), z.date().optional());
69
+ export const optionalDate = () => z.union([z.date(), z.literal("")]).nullish();
54
70
 
71
+ /** Mirrors StripeCouponCodePostAttributesDTO. */
55
72
  export const couponCodeSchema = (t: (key: string) => string) =>
56
73
  z
57
74
  .object({
@@ -65,7 +82,8 @@ export const couponCodeSchema = (t: (key: string) => string) =>
65
82
  automatic: z.boolean(),
66
83
  })
67
84
  .superRefine((values, ctx) => {
68
- if (values.minimumAmount !== undefined && values.minimumAmount <= 0)
85
+ const minimumAmount = parseOptionalDecimal(values.minimumAmount);
86
+ if (minimumAmount !== undefined && minimumAmount <= 0)
69
87
  ctx.addIssue({
70
88
  code: "custom",
71
89
  path: ["minimumAmount"],
@@ -78,10 +96,10 @@ export type CouponCodeFormValues = z.infer<ReturnType<typeof couponCodeSchema>>;
78
96
  /**
79
97
  * Every text/number input starts as "" (never undefined): FormInput spreads
80
98
  * `field.value` onto a Base UI input, and undefined → value flips it from
81
- * uncontrolled to controlled (console warning). The schema preprocessors map
82
- * "" back to undefined, so empty fields are still sent as absent.
99
+ * uncontrolled to controlled (console warning). The parse functions map ""
100
+ * to undefined, so empty fields are still sent as absent.
83
101
  */
84
- export const couponCodeDefaults = (): z.input<ReturnType<typeof couponCodeSchema>> => ({
102
+ export const couponCodeDefaults = (): CouponCodeFormValues => ({
85
103
  code: "",
86
104
  firstTimeTransaction: false,
87
105
  minimumAmount: "",
@@ -99,13 +117,15 @@ export const toCouponCodeInput = (values: CouponCodeFormValues): Omit<StripeCoup
99
117
  automatic: values.automatic,
100
118
  };
101
119
  if (values.code?.trim()) input.code = values.code.trim();
102
- if (values.minimumAmount !== undefined) {
103
- input.minimumAmount = Math.round(values.minimumAmount * 100);
120
+ const minimumAmount = parseOptionalDecimal(values.minimumAmount);
121
+ if (minimumAmount !== undefined) {
122
+ input.minimumAmount = Math.round(minimumAmount * 100);
104
123
  input.minimumAmountCurrency = values.minimumAmountCurrency;
105
124
  }
106
125
  if (values.stripeCustomerId?.trim()) input.stripeCustomerId = values.stripeCustomerId.trim();
107
126
  if (values.expiresAt) input.expiresAt = values.expiresAt;
108
- if (values.maxRedemptions !== undefined) input.maxRedemptions = values.maxRedemptions;
127
+ const maxRedemptions = parseOptionalPositiveInt(values.maxRedemptions);
128
+ if (maxRedemptions !== undefined) input.maxRedemptions = maxRedemptions;
109
129
  return input;
110
130
  };
111
131
 
@@ -115,15 +135,17 @@ export const couponCurrencyOptions = [
115
135
  { id: "gbp", text: "GBP (£)" },
116
136
  ];
117
137
 
118
- type CouponCodeFieldsProps = {
119
- form: any;
138
+ type CouponCodeFieldsProps<T extends FieldValues> = {
139
+ form: UseFormReturn<T>;
120
140
  /** "" for CouponCodeEditor, "firstCode." when nested inside CouponEditor. */
121
141
  prefix?: string;
122
142
  };
123
143
 
124
144
  /** The fields of a customer-facing code, shared by CouponEditor (first code) and CouponCodeEditor. */
125
- export function CouponCodeFields({ form, prefix = "" }: CouponCodeFieldsProps) {
145
+ export function CouponCodeFields<T extends FieldValues>({ form, prefix = "" }: CouponCodeFieldsProps<T>) {
126
146
  const t = useTranslations();
147
+ // Read once on mount: render stays pure.
148
+ const [now] = useState(() => new Date());
127
149
 
128
150
  return (
129
151
  <div className="flex w-full flex-col gap-y-4">
@@ -171,7 +193,7 @@ export function CouponCodeFields({ form, prefix = "" }: CouponCodeFieldsProps) {
171
193
  form={form}
172
194
  id={`${prefix}expiresAt`}
173
195
  name={t("billing.admin.coupons.fields.expiresAt")}
174
- minDate={new Date()}
196
+ minDate={now}
175
197
  allowEmpty
176
198
  />
177
199
  <FormInput