@carlonicora/nextjs-jsonapi 6.17.0 → 6.19.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-IGUHTERQ.mjs → BlockNoteEditor-5HSL74IX.mjs} +2 -2
- package/dist/{BlockNoteEditor-FAUTV5FW.js → BlockNoteEditor-6RZXGMSD.js} +9 -9
- package/dist/{BlockNoteEditor-FAUTV5FW.js.map → BlockNoteEditor-6RZXGMSD.js.map} +1 -1
- package/dist/billing/index.js +378 -378
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-4OCBNCKH.js → chunk-B4ZH2FOC.js} +359 -308
- package/dist/chunk-B4ZH2FOC.js.map +1 -0
- package/dist/{chunk-RINMQQZH.mjs → chunk-HEDHJAKT.mjs} +59 -8
- package/dist/chunk-HEDHJAKT.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +13 -2
- package/dist/components/index.d.ts +13 -2
- package/dist/components/index.js +2 -2
- package/dist/components/index.mjs +1 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/email-automations/index.js +78 -78
- package/dist/email-automations/index.mjs +1 -1
- package/dist/features/analytics/index.js +96 -96
- package/dist/features/analytics/index.mjs +1 -1
- package/dist/features/blog/index.js +93 -93
- package/dist/features/blog/index.mjs +1 -1
- package/dist/features/help/index.js +31 -31
- package/dist/features/help/index.mjs +1 -1
- package/dist/features/tokenusage/index.js +102 -102
- package/dist/features/tokenusage/index.mjs +1 -1
- package/package.json +1 -1
- package/src/components/forms/EditorSheet.tsx +21 -1
- package/src/components/forms/FormDateTime.tsx +55 -3
- package/src/components/forms/__tests__/EditorSheet.spec.tsx +40 -1
- package/dist/chunk-4OCBNCKH.js.map +0 -1
- package/dist/chunk-RINMQQZH.mjs.map +0 -1
- /package/dist/{BlockNoteEditor-IGUHTERQ.mjs.map → BlockNoteEditor-5HSL74IX.mjs.map} +0 -0
|
@@ -88,6 +88,11 @@ const sizeClasses: Record<EditorSheetSize, string> = {
|
|
|
88
88
|
export type EditorSheetProps<T extends FieldValues> = {
|
|
89
89
|
form: UseFormReturn<T>;
|
|
90
90
|
onSubmit: (values: T) => Promise<{ id: string } | void>;
|
|
91
|
+
/** Runs after validation and before `onSubmit`. Return `false` (or a promise
|
|
92
|
+
* resolving to `false`) to abort the submit: `onSubmit` is not called and the
|
|
93
|
+
* sheet stays open with the form unchanged. Use it for a last confirmation
|
|
94
|
+
* (e.g. "this information will be deleted") that must not close the sheet. */
|
|
95
|
+
onBeforeSubmit?: (values: T) => Promise<boolean> | boolean;
|
|
91
96
|
onReset: () => T;
|
|
92
97
|
isFormDirty?: () => boolean;
|
|
93
98
|
|
|
@@ -144,6 +149,7 @@ export type EditorSheetProps<T extends FieldValues> = {
|
|
|
144
149
|
export function EditorSheet<T extends FieldValues>({
|
|
145
150
|
form,
|
|
146
151
|
onSubmit,
|
|
152
|
+
onBeforeSubmit,
|
|
147
153
|
onReset,
|
|
148
154
|
isFormDirty: isFormDirtyProp,
|
|
149
155
|
entityType,
|
|
@@ -222,6 +228,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
222
228
|
const wrappedOnSubmit = useCallback(
|
|
223
229
|
async (values: T) => {
|
|
224
230
|
try {
|
|
231
|
+
if (onBeforeSubmit && !(await onBeforeSubmit(values))) return;
|
|
225
232
|
const result = await onSubmit(values);
|
|
226
233
|
setOpen(false);
|
|
227
234
|
if (result && onSaved) {
|
|
@@ -249,7 +256,20 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
249
256
|
});
|
|
250
257
|
}
|
|
251
258
|
},
|
|
252
|
-
[
|
|
259
|
+
[
|
|
260
|
+
onSubmit,
|
|
261
|
+
onBeforeSubmit,
|
|
262
|
+
setOpen,
|
|
263
|
+
onSuccess,
|
|
264
|
+
onSaved,
|
|
265
|
+
onRevalidate,
|
|
266
|
+
onNavigate,
|
|
267
|
+
generateUrl,
|
|
268
|
+
module,
|
|
269
|
+
isEdit,
|
|
270
|
+
propagateChanges,
|
|
271
|
+
t,
|
|
272
|
+
],
|
|
253
273
|
);
|
|
254
274
|
|
|
255
275
|
const headerTitle =
|
|
@@ -19,6 +19,13 @@ import {
|
|
|
19
19
|
import { cn } from "../../utils";
|
|
20
20
|
import { FormFieldWrapper } from "./FormFieldWrapper";
|
|
21
21
|
|
|
22
|
+
// Unwrapped rounding: setMinutes(60) carries into the hour, so 10:58 becomes 11:00
|
|
23
|
+
function snapToFiveMinutes(date: Date): Date {
|
|
24
|
+
const snapped = new Date(date);
|
|
25
|
+
snapped.setMinutes(Math.round(snapped.getMinutes() / 5) * 5, 0, 0);
|
|
26
|
+
return snapped;
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
export function FormDateTime({
|
|
23
30
|
form,
|
|
24
31
|
id,
|
|
@@ -30,6 +37,8 @@ export function FormDateTime({
|
|
|
30
37
|
placeholder,
|
|
31
38
|
isRequired,
|
|
32
39
|
description,
|
|
40
|
+
reference,
|
|
41
|
+
markedDate,
|
|
33
42
|
}: {
|
|
34
43
|
form: any;
|
|
35
44
|
id: string;
|
|
@@ -41,6 +50,10 @@ export function FormDateTime({
|
|
|
41
50
|
defaultMonth?: Date;
|
|
42
51
|
isRequired?: boolean;
|
|
43
52
|
description?: string;
|
|
53
|
+
// `date` alone: ring on the day, month fallback, empty-field initial time; no row.
|
|
54
|
+
// `label` renders the "label: date" row; `actionLabel` adds the button inside that row (ignored without `label`).
|
|
55
|
+
reference?: { date: Date; label?: string; actionLabel?: string };
|
|
56
|
+
markedDate?: Date;
|
|
44
57
|
}) {
|
|
45
58
|
const [open, setOpen] = useState<boolean>(false);
|
|
46
59
|
const t = useI18nTranslations();
|
|
@@ -49,7 +62,7 @@ export function FormDateTime({
|
|
|
49
62
|
|
|
50
63
|
const [displayMonth, setDisplayMonth] = useState<Date>(() => {
|
|
51
64
|
const currentValue = form.getValues(id);
|
|
52
|
-
return currentValue || defaultMonth || new Date();
|
|
65
|
+
return currentValue || defaultMonth || reference?.date || new Date();
|
|
53
66
|
});
|
|
54
67
|
|
|
55
68
|
// Locale-aware date-time formatter
|
|
@@ -69,8 +82,23 @@ export function FormDateTime({
|
|
|
69
82
|
// Format date-time for display
|
|
70
83
|
const formatDateTime = (date: Date): string => dateTimeFormatter.format(date);
|
|
71
84
|
|
|
72
|
-
|
|
73
|
-
|
|
85
|
+
// An empty field starts its time selects at the reference "now" when one is given
|
|
86
|
+
// Snapped as an instant (not by wrapping minutes) so 10:58 starts at 11:00
|
|
87
|
+
const initialTime = reference ? snapToFiveMinutes(reference.date) : new Date();
|
|
88
|
+
const [selectedHours, setSelectedHours] = useState<number>(initialTime.getHours());
|
|
89
|
+
const [selectedMinutes, setSelectedMinutes] = useState<number>(roundToNearestFiveMinutes(initialTime.getMinutes()));
|
|
90
|
+
|
|
91
|
+
const dayModifiers = {
|
|
92
|
+
...(reference ? { reference: reference.date } : {}),
|
|
93
|
+
...(markedDate ? { marked: markedDate } : {}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// Reference = outlined ring (offset so it stays visible around the selected fill);
|
|
97
|
+
// marked = underline on the day number. Both differ from today's bg-muted.
|
|
98
|
+
const dayModifiersClassNames = {
|
|
99
|
+
reference: "rounded-(--cell-radius) ring-1 ring-primary ring-offset-1 ring-offset-background",
|
|
100
|
+
marked: "[&>button]:underline [&>button]:decoration-2 [&>button]:underline-offset-4",
|
|
101
|
+
};
|
|
74
102
|
|
|
75
103
|
const hoursOptions = Array.from({ length: 24 }, (_, i) => {
|
|
76
104
|
const hour = i;
|
|
@@ -95,6 +123,16 @@ export function FormDateTime({
|
|
|
95
123
|
return (Math.round(minutes / 5) * 5) % 60;
|
|
96
124
|
}
|
|
97
125
|
|
|
126
|
+
const handleUseReference = () => {
|
|
127
|
+
if (!reference) return;
|
|
128
|
+
const referenceDate = snapToFiveMinutes(reference.date);
|
|
129
|
+
form.setValue(id, referenceDate);
|
|
130
|
+
if (onChange) onChange(referenceDate);
|
|
131
|
+
setDisplayMonth(referenceDate);
|
|
132
|
+
setSelectedHours(referenceDate.getHours());
|
|
133
|
+
setSelectedMinutes(referenceDate.getMinutes());
|
|
134
|
+
};
|
|
135
|
+
|
|
98
136
|
const handleTimeChange = (hours: number, minutes: number) => {
|
|
99
137
|
const currentDate = form.getValues(id);
|
|
100
138
|
if (currentDate) {
|
|
@@ -170,7 +208,21 @@ export function FormDateTime({
|
|
|
170
208
|
weekStartsOn={1}
|
|
171
209
|
startMonth={new Date(1900, 0)}
|
|
172
210
|
endMonth={new Date(new Date().getFullYear() + 10, 11)}
|
|
211
|
+
modifiers={dayModifiers}
|
|
212
|
+
modifiersClassNames={dayModifiersClassNames}
|
|
173
213
|
/>
|
|
214
|
+
{reference?.label && (
|
|
215
|
+
<div className="flex flex-row items-center justify-between gap-x-4 border-t pt-4">
|
|
216
|
+
<span className="text-muted-foreground text-sm">
|
|
217
|
+
{reference.label}: {formatDateTime(reference.date)}
|
|
218
|
+
</span>
|
|
219
|
+
{reference.actionLabel && (
|
|
220
|
+
<Button type="button" variant="outline" size="sm" onClick={handleUseReference}>
|
|
221
|
+
{reference.actionLabel}
|
|
222
|
+
</Button>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
174
226
|
<div className="flex flex-row items-end justify-center space-x-4">
|
|
175
227
|
<div className="flex flex-col space-y-2">
|
|
176
228
|
<Label htmlFor="hours-select">{t(`common.hours`)}</Label>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { act, render, screen } from "@testing-library/react";
|
|
2
|
+
import { act, render, screen, waitFor } from "@testing-library/react";
|
|
3
3
|
import { Activity } from "react";
|
|
4
4
|
import userEvent from "@testing-library/user-event";
|
|
5
5
|
import { useForm } from "react-hook-form";
|
|
@@ -234,6 +234,45 @@ describe("EditorSheet", () => {
|
|
|
234
234
|
});
|
|
235
235
|
});
|
|
236
236
|
|
|
237
|
+
describe("onBeforeSubmit", () => {
|
|
238
|
+
it("does not call onSubmit and keeps the sheet open when it returns false", async () => {
|
|
239
|
+
const onSubmit = vi.fn().mockResolvedValue({ id: "abc" });
|
|
240
|
+
const onBeforeSubmit = vi.fn().mockResolvedValue(false);
|
|
241
|
+
render(<TestEditor onSubmit={onSubmit} onBeforeSubmit={onBeforeSubmit} />);
|
|
242
|
+
|
|
243
|
+
await userEvent.click(screen.getByText("ui.buttons.create"));
|
|
244
|
+
await userEvent.type(screen.getByTestId("name-input"), "Test Name");
|
|
245
|
+
await userEvent.click(screen.getByTestId("modal-button-create"));
|
|
246
|
+
|
|
247
|
+
expect(onBeforeSubmit).toHaveBeenCalledWith(expect.objectContaining({ name: "Test Name" }));
|
|
248
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
249
|
+
expect(screen.getByTestId("name-input")).toHaveValue("Test Name");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("calls onSubmit and closes the sheet when it returns true", async () => {
|
|
253
|
+
const onSubmit = vi.fn().mockResolvedValue({ id: "abc" });
|
|
254
|
+
const onBeforeSubmit = vi.fn().mockReturnValue(true);
|
|
255
|
+
render(<TestEditor onSubmit={onSubmit} onBeforeSubmit={onBeforeSubmit} />);
|
|
256
|
+
|
|
257
|
+
await userEvent.click(screen.getByText("ui.buttons.create"));
|
|
258
|
+
await userEvent.type(screen.getByTestId("name-input"), "Test Name");
|
|
259
|
+
await userEvent.click(screen.getByTestId("modal-button-create"));
|
|
260
|
+
|
|
261
|
+
expect(onBeforeSubmit).toHaveBeenCalledTimes(1);
|
|
262
|
+
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({ name: "Test Name" }));
|
|
263
|
+
await waitFor(() => expect(screen.queryByTestId("name-input")).toBeNull());
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("is not called when validation fails", async () => {
|
|
267
|
+
const onBeforeSubmit = vi.fn().mockReturnValue(true);
|
|
268
|
+
render(<TestEditor dialogOpen={true} onBeforeSubmit={onBeforeSubmit} />);
|
|
269
|
+
|
|
270
|
+
await userEvent.click(screen.getByTestId("modal-button-create"));
|
|
271
|
+
|
|
272
|
+
expect(onBeforeSubmit).not.toHaveBeenCalled();
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
237
276
|
describe("onSaved callback", () => {
|
|
238
277
|
it("calls onSaved on create (no isEdit gate)", async () => {
|
|
239
278
|
const onSaved = vi.fn();
|