@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.
- package/dist/{BlockNoteEditor-NEYG5L6W.mjs → BlockNoteEditor-R3WXUFHX.mjs} +4 -4
- package/dist/{BlockNoteEditor-AE3ZTEEH.js → BlockNoteEditor-ZHXXFWUJ.js} +19 -19
- package/dist/{BlockNoteEditor-AE3ZTEEH.js.map → BlockNoteEditor-ZHXXFWUJ.js.map} +1 -1
- package/dist/{BlockNoteViewer-3OUT5UFY.mjs → BlockNoteViewer-BVVN6D3T.mjs} +2 -2
- package/dist/{BlockNoteViewer-4CFKQM7U.js → BlockNoteViewer-SD3ZFIXD.js} +3 -3
- package/dist/{BlockNoteViewer-4CFKQM7U.js.map → BlockNoteViewer-SD3ZFIXD.js.map} +1 -1
- package/dist/billing/index.d.mts +33 -43
- package/dist/billing/index.d.ts +33 -43
- package/dist/billing/index.js +590 -574
- package/dist/billing/index.js.map +1 -1
- package/dist/billing/index.mjs +28 -12
- package/dist/billing/index.mjs.map +1 -1
- package/dist/{chunk-3I5AAJXV.js → chunk-2TXE57LE.js} +3 -3
- package/dist/{chunk-3I5AAJXV.js.map → chunk-2TXE57LE.js.map} +1 -1
- package/dist/{chunk-HKZAESS6.js → chunk-5TH2DSAH.js} +17 -12
- package/dist/chunk-5TH2DSAH.js.map +1 -0
- package/dist/{chunk-SFYPXCK4.mjs → chunk-CFB6FHIY.mjs} +638 -536
- package/dist/{chunk-SFYPXCK4.mjs.map → chunk-CFB6FHIY.mjs.map} +1 -1
- package/dist/{chunk-EYA2F6AN.mjs → chunk-FK26NX3S.mjs} +16 -11
- package/dist/chunk-FK26NX3S.mjs.map +1 -0
- package/dist/{chunk-53BAULDP.js → chunk-IJME5SAM.js} +4 -4
- package/dist/{chunk-53BAULDP.js.map → chunk-IJME5SAM.js.map} +1 -1
- package/dist/{chunk-KQHBONL4.mjs → chunk-OKCV4GPJ.mjs} +2 -2
- package/dist/{chunk-BTPUYUQ3.mjs → chunk-RGQYFSJF.mjs} +2 -2
- package/dist/{chunk-Y7RR57EW.mjs → chunk-WCHLCOZG.mjs} +2 -2
- package/dist/{chunk-RL3PL3KV.js → chunk-XOSC3FFP.js} +8 -8
- package/dist/{chunk-RL3PL3KV.js.map → chunk-XOSC3FFP.js.map} +1 -1
- package/dist/{chunk-LQ4DK4JF.js → chunk-ZCOPOO7T.js} +1314 -1212
- package/dist/chunk-ZCOPOO7T.js.map +1 -0
- package/dist/client/index.js +4 -4
- package/dist/client/index.mjs +3 -3
- package/dist/components/index.js +4 -4
- package/dist/components/index.mjs +3 -3
- package/dist/contexts/index.js +4 -4
- package/dist/contexts/index.mjs +3 -3
- package/dist/core/index.d.mts +9 -3
- package/dist/core/index.d.ts +9 -3
- package/dist/core/index.js +10 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +9 -1
- package/dist/{format-options-D6c7FNDi.d.mts → currency-DT_dveBx.d.mts} +41 -8
- package/dist/{format-options-BtX3o0Yu.d.ts → currency-pK9eRYRC.d.ts} +41 -8
- package/dist/email-automations/index.js +92 -92
- package/dist/email-automations/index.mjs +3 -3
- package/dist/features/analytics/index.js +121 -121
- package/dist/features/analytics/index.mjs +4 -4
- package/dist/features/analytics/server.js +4 -4
- package/dist/features/analytics/server.mjs +2 -2
- package/dist/features/blog/index.js +150 -150
- package/dist/features/blog/index.mjs +3 -3
- package/dist/features/blog/server.js +16 -16
- package/dist/features/blog/server.mjs +1 -1
- package/dist/features/help/index.js +37 -37
- package/dist/features/help/index.mjs +3 -3
- package/dist/features/tokenusage/index.js +124 -124
- package/dist/features/tokenusage/index.mjs +4 -4
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +10 -2
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +2 -2
- package/package.json +1 -1
- package/src/core/index.ts +3 -0
- package/src/features/billing/contexts/CouponContext.tsx +0 -2
- package/src/features/billing/stripe-coupon/components/details/CouponDetails.tsx +5 -2
- package/src/features/billing/stripe-coupon/components/forms/CouponCodeEditor.tsx +6 -5
- package/src/features/billing/stripe-coupon/components/forms/CouponCodeFields.tsx +67 -45
- package/src/features/billing/stripe-coupon/components/forms/CouponEditor.tsx +304 -212
- package/src/features/billing/stripe-coupon/components/widgets/CouponCodeActions.tsx +4 -3
- package/src/features/billing/stripe-coupon/data/stripe-coupon.interface.ts +11 -7
- package/src/features/billing/stripe-coupon/data/stripe-coupon.ts +6 -4
- package/src/features/billing/stripe-coupon/hooks/useStripeCouponCodeTableStructure.tsx +14 -7
- package/src/features/billing/stripe-coupon/hooks/useStripeCouponTableStructure.tsx +2 -2
- package/src/features/billing/stripe-coupon/stripe-coupon-code.module.ts +3 -2
- package/src/features/billing/stripe-coupon/stripe-coupon.module.ts +11 -4
- package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +1 -1
- package/src/features/billing/stripe-subscription/hooks/wizard-promotion.ts +15 -8
- package/dist/chunk-EYA2F6AN.mjs.map +0 -1
- package/dist/chunk-HKZAESS6.js.map +0 -1
- package/dist/chunk-LQ4DK4JF.js.map +0 -1
- /package/dist/{BlockNoteEditor-NEYG5L6W.mjs.map → BlockNoteEditor-R3WXUFHX.mjs.map} +0 -0
- /package/dist/{BlockNoteViewer-3OUT5UFY.mjs.map → BlockNoteViewer-BVVN6D3T.mjs.map} +0 -0
- /package/dist/{chunk-KQHBONL4.mjs.map → chunk-OKCV4GPJ.mjs.map} +0 -0
- /package/dist/{chunk-BTPUYUQ3.mjs.map → chunk-RGQYFSJF.mjs.map} +0 -0
- /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
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
31
|
-
*
|
|
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
|
-
|
|
36
|
-
(value)
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
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
|
|
82
|
-
*
|
|
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 = ():
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
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:
|
|
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={
|
|
196
|
+
minDate={now}
|
|
175
197
|
allowEmpty
|
|
176
198
|
/>
|
|
177
199
|
<FormInput
|