@carlonicora/nextjs-jsonapi 1.116.0 → 1.117.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.
- package/dist/{AssistantMessageInterface-Ca0G0vZw.d.mts → AssistantMessageInterface-DH1QwtC_.d.mts} +1 -0
- package/dist/{AssistantMessageInterface-DEZ5AnVl.d.ts → AssistantMessageInterface-giWvsOrX.d.ts} +1 -0
- package/dist/{BlockNoteEditor-R6U4PCQ7.js → BlockNoteEditor-CEWZCORH.js} +9 -9
- package/dist/{BlockNoteEditor-R6U4PCQ7.js.map → BlockNoteEditor-CEWZCORH.js.map} +1 -1
- package/dist/{BlockNoteEditor-RFRK3STN.mjs → BlockNoteEditor-K4T6QOJO.mjs} +2 -2
- package/dist/billing/index.js +299 -299
- package/dist/billing/index.mjs +1 -1
- package/dist/{chunk-SPRFCAQI.js → chunk-56QAXZKI.js} +470 -328
- package/dist/chunk-56QAXZKI.js.map +1 -0
- package/dist/{chunk-RKUUGCYB.mjs → chunk-6D4GJHLK.mjs} +526 -384
- package/dist/chunk-6D4GJHLK.mjs.map +1 -0
- package/dist/client/index.js +2 -2
- package/dist/client/index.mjs +1 -1
- package/dist/components/index.d.mts +69 -22
- package/dist/components/index.d.ts +69 -22
- package/dist/components/index.js +4 -2
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/contexts/index.d.mts +1 -1
- package/dist/contexts/index.d.ts +1 -1
- package/dist/contexts/index.js +2 -2
- package/dist/contexts/index.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/features/help/index.js +30 -30
- package/dist/features/help/index.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/forms/CommonDeleter.tsx +49 -18
- package/src/components/forms/CommonEditorButtons.tsx +4 -2
- package/src/components/forms/CommonEditorHeader.tsx +20 -14
- package/src/components/forms/CommonEditorTrigger.tsx +18 -4
- package/src/components/forms/DateRangeSelector.tsx +55 -19
- package/src/components/forms/EditorSheet.tsx +36 -9
- package/src/components/forms/FileUploader.tsx +51 -21
- package/src/components/forms/FormDate.tsx +6 -2
- package/src/components/forms/FormDateTime.tsx +11 -2
- package/src/components/forms/FormFieldWrapper.tsx +3 -1
- package/src/components/forms/FormInput.tsx +3 -1
- package/src/components/forms/FormPassword.tsx +10 -1
- package/src/components/forms/FormPlaceAutocomplete.tsx +8 -3
- package/src/components/forms/FormSelect.tsx +3 -0
- package/src/components/forms/FormSlider.tsx +3 -1
- package/src/components/forms/FormTextarea.tsx +15 -1
- package/src/components/navigations/Breadcrumb.tsx +31 -5
- package/src/hooks/useSocket.ts +12 -1
- package/src/interfaces/breadcrumb.item.data.interface.ts +1 -0
- package/src/shadcnui/ui/badge.tsx +15 -0
- package/dist/chunk-RKUUGCYB.mjs.map +0 -1
- package/dist/chunk-SPRFCAQI.js.map +0 -1
- /package/dist/{BlockNoteEditor-RFRK3STN.mjs.map → BlockNoteEditor-K4T6QOJO.mjs.map} +0 -0
|
@@ -62,6 +62,17 @@ export type EditorSheetProps<T extends FieldValues> = {
|
|
|
62
62
|
dialogOpen?: boolean;
|
|
63
63
|
onDialogOpenChange?: (open: boolean) => void;
|
|
64
64
|
|
|
65
|
+
/** Render a fully custom footer instead of the default CommonEditorButtons.
|
|
66
|
+
* `setOpen` is the dirty-checked open handler (shows the discard dialog);
|
|
67
|
+
* `closeWithoutConfirm` is the raw setter that bypasses the discard dialog
|
|
68
|
+
* (use after a successful submit when no confirmation is needed). */
|
|
69
|
+
renderFooter?: (props: {
|
|
70
|
+
form: UseFormReturn<T>;
|
|
71
|
+
isEdit: boolean;
|
|
72
|
+
setOpen: (open: boolean) => void;
|
|
73
|
+
closeWithoutConfirm: (open: boolean) => void;
|
|
74
|
+
}) => ReactNode;
|
|
75
|
+
|
|
65
76
|
children: ReactNode;
|
|
66
77
|
};
|
|
67
78
|
|
|
@@ -90,6 +101,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
90
101
|
onClose,
|
|
91
102
|
dialogOpen,
|
|
92
103
|
onDialogOpenChange,
|
|
104
|
+
renderFooter,
|
|
93
105
|
children,
|
|
94
106
|
}: EditorSheetProps<T>) {
|
|
95
107
|
const t = useTranslations();
|
|
@@ -147,6 +159,7 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
147
159
|
<>
|
|
148
160
|
<Sheet open={open} onOpenChange={handleOpenChange}>
|
|
149
161
|
{dialogOpen === undefined &&
|
|
162
|
+
forceShow === undefined &&
|
|
150
163
|
(trigger ? (
|
|
151
164
|
<SheetTrigger>{trigger}</SheetTrigger>
|
|
152
165
|
) : (
|
|
@@ -184,17 +197,31 @@ export function EditorSheet<T extends FieldValues>({
|
|
|
184
197
|
</SheetDescription>
|
|
185
198
|
</SheetHeader>
|
|
186
199
|
<Form {...form}>
|
|
187
|
-
<form
|
|
200
|
+
<form
|
|
201
|
+
onSubmit={(e) => {
|
|
202
|
+
// The Sheet content is portaled, but React synthetic events still
|
|
203
|
+
// bubble up the React tree — so without stopPropagation an inner
|
|
204
|
+
// EditorSheet's submit also triggers the outer form's submit
|
|
205
|
+
// (e.g. a create dialog opened from within another editor).
|
|
206
|
+
e.stopPropagation();
|
|
207
|
+
return form.handleSubmit(wrappedOnSubmit)(e);
|
|
208
|
+
}}
|
|
209
|
+
className="flex min-h-0 flex-1 flex-col"
|
|
210
|
+
>
|
|
188
211
|
<div className="flex-1 overflow-y-auto px-6 py-4">{children}</div>
|
|
189
212
|
<SheetFooter className="shrink-0 border-t px-6 py-4">
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
213
|
+
{renderFooter ? (
|
|
214
|
+
renderFooter({ form, isEdit, setOpen: handleOpenChange, closeWithoutConfirm: setOpen })
|
|
215
|
+
) : (
|
|
216
|
+
<CommonEditorButtons
|
|
217
|
+
form={form}
|
|
218
|
+
setOpen={handleOpenChange}
|
|
219
|
+
isEdit={isEdit}
|
|
220
|
+
disabled={disabled}
|
|
221
|
+
hideSubmit={hideSubmit}
|
|
222
|
+
centerButtons={centerButtons}
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
198
225
|
</SheetFooter>
|
|
199
226
|
</form>
|
|
200
227
|
</Form>
|
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
useCallback,
|
|
11
11
|
useContext,
|
|
12
12
|
useEffect,
|
|
13
|
+
useMemo,
|
|
13
14
|
useRef,
|
|
14
15
|
useState,
|
|
15
16
|
} from "react";
|
|
16
|
-
import { DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
|
+
import { Accept, DropzoneOptions, DropzoneState, FileRejection, useDropzone } from "react-dropzone";
|
|
17
18
|
import { showError } from "../../utils/toast";
|
|
18
|
-
import { buttonVariants, Input } from "../../shadcnui";
|
|
19
|
+
import { buttonVariants, Input, Tooltip, TooltipContent, TooltipTrigger } from "../../shadcnui";
|
|
19
20
|
import { cn } from "../../utils";
|
|
20
21
|
|
|
21
22
|
export type { DropzoneOptions } from "react-dropzone";
|
|
@@ -31,6 +32,7 @@ type FileUploaderContextType = {
|
|
|
31
32
|
setActiveIndex: Dispatch<SetStateAction<number>>;
|
|
32
33
|
orientation: "horizontal" | "vertical";
|
|
33
34
|
direction: DirectionOptions;
|
|
35
|
+
accept?: Accept;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
const FileUploaderContext = createContext<FileUploaderContextType | null>(null);
|
|
@@ -195,6 +197,7 @@ export const FileUploader = forwardRef<HTMLDivElement, FileUploaderProps & React
|
|
|
195
197
|
setActiveIndex,
|
|
196
198
|
orientation,
|
|
197
199
|
direction,
|
|
200
|
+
accept: dropzoneOptions.accept,
|
|
198
201
|
}}
|
|
199
202
|
>
|
|
200
203
|
<div
|
|
@@ -283,35 +286,62 @@ FileUploaderItem.displayName = "FileUploaderItem";
|
|
|
283
286
|
|
|
284
287
|
export const FileInput = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
|
285
288
|
({ className, children, ...props }, ref) => {
|
|
286
|
-
const { dropzoneState, isFileTooBig, isLOF } = useFileUpload();
|
|
289
|
+
const { dropzoneState, isFileTooBig, isLOF, accept } = useFileUpload();
|
|
290
|
+
const t = useTranslations();
|
|
287
291
|
const rootProps = isLOF ? {} : dropzoneState.getRootProps();
|
|
288
292
|
// Get isDragActive from the context for FileInput as well, to ensure it can react if needed, or to simplify its own styling.
|
|
289
293
|
const { isDragActive: parentIsDragActive } = dropzoneState;
|
|
290
294
|
|
|
295
|
+
const acceptedLabels = useMemo(() => {
|
|
296
|
+
if (!accept) return null;
|
|
297
|
+
const extensions = new Set<string>();
|
|
298
|
+
let hasWildcardImages = false;
|
|
299
|
+
for (const [mime, exts] of Object.entries(accept)) {
|
|
300
|
+
if (mime === "image/*") hasWildcardImages = true;
|
|
301
|
+
for (const ext of exts) extensions.add(ext);
|
|
302
|
+
}
|
|
303
|
+
const labels = Array.from(extensions).sort();
|
|
304
|
+
if (hasWildcardImages) labels.push(t("ui.labels.images"));
|
|
305
|
+
return labels.length > 0 ? labels : null;
|
|
306
|
+
}, [accept, t]);
|
|
307
|
+
|
|
308
|
+
const dropArea = (
|
|
309
|
+
<div
|
|
310
|
+
className={cn(
|
|
311
|
+
"w-full rounded-lg duration-300 ease-in-out",
|
|
312
|
+
// Simpler border logic: if parent is drag-active, it controls the border.
|
|
313
|
+
// Otherwise, FileInput can show its own accept/reject/default border.
|
|
314
|
+
{
|
|
315
|
+
"border-green-500": dropzoneState.isDragAccept && !parentIsDragActive,
|
|
316
|
+
"border-red-500": (dropzoneState.isDragReject || isFileTooBig) && !parentIsDragActive,
|
|
317
|
+
"border-gray-300":
|
|
318
|
+
!dropzoneState.isDragAccept && !dropzoneState.isDragReject && !isFileTooBig && !parentIsDragActive,
|
|
319
|
+
},
|
|
320
|
+
// className from props should be last to allow overrides if necessary
|
|
321
|
+
className,
|
|
322
|
+
)}
|
|
323
|
+
{...rootProps}
|
|
324
|
+
>
|
|
325
|
+
{children}
|
|
326
|
+
</div>
|
|
327
|
+
);
|
|
328
|
+
|
|
291
329
|
return (
|
|
292
330
|
<div
|
|
293
331
|
ref={ref}
|
|
294
332
|
{...props}
|
|
295
333
|
className={cn(`relative w-full ${isLOF ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`, className)}
|
|
296
334
|
>
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
},
|
|
308
|
-
// className from props should be last to allow overrides if necessary
|
|
309
|
-
className,
|
|
310
|
-
)}
|
|
311
|
-
{...rootProps}
|
|
312
|
-
>
|
|
313
|
-
{children}
|
|
314
|
-
</div>
|
|
335
|
+
{acceptedLabels ? (
|
|
336
|
+
<Tooltip>
|
|
337
|
+
<TooltipTrigger render={dropArea} />
|
|
338
|
+
<TooltipContent>
|
|
339
|
+
{t("ui.labels.accepted_file_types")}: {acceptedLabels.join(", ")}
|
|
340
|
+
</TooltipContent>
|
|
341
|
+
</Tooltip>
|
|
342
|
+
) : (
|
|
343
|
+
dropArea
|
|
344
|
+
)}
|
|
315
345
|
<Input
|
|
316
346
|
ref={dropzoneState.inputRef}
|
|
317
347
|
disabled={isLOF}
|
|
@@ -26,6 +26,8 @@ export function FormDate({
|
|
|
26
26
|
isRequired = false,
|
|
27
27
|
defaultMonth,
|
|
28
28
|
allowEmpty,
|
|
29
|
+
description,
|
|
30
|
+
futureYears,
|
|
29
31
|
}: {
|
|
30
32
|
form: any;
|
|
31
33
|
id: string;
|
|
@@ -36,6 +38,8 @@ export function FormDate({
|
|
|
36
38
|
isRequired?: boolean;
|
|
37
39
|
defaultMonth?: Date;
|
|
38
40
|
allowEmpty?: boolean;
|
|
41
|
+
description?: string;
|
|
42
|
+
futureYears?: number;
|
|
39
43
|
}) {
|
|
40
44
|
const t = useI18nTranslations();
|
|
41
45
|
const locale = useI18nLocale();
|
|
@@ -117,7 +121,7 @@ export function FormDate({
|
|
|
117
121
|
|
|
118
122
|
return (
|
|
119
123
|
<div className="flex w-full flex-col">
|
|
120
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
124
|
+
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired} description={description}>
|
|
121
125
|
{(field) => (
|
|
122
126
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
123
127
|
<InputGroup>
|
|
@@ -162,7 +166,7 @@ export function FormDate({
|
|
|
162
166
|
month={displayMonth}
|
|
163
167
|
onMonthChange={setDisplayMonth}
|
|
164
168
|
startMonth={new Date(1900, 0)}
|
|
165
|
-
endMonth={new Date(new Date().getFullYear() + 10, 11)}
|
|
169
|
+
endMonth={new Date(new Date().getFullYear() + (futureYears ?? 10), 11)}
|
|
166
170
|
/>
|
|
167
171
|
{allowEmpty !== false && (
|
|
168
172
|
<div className="border-t p-2">
|
|
@@ -27,6 +27,9 @@ export function FormDateTime({
|
|
|
27
27
|
onChange,
|
|
28
28
|
allowEmpty,
|
|
29
29
|
defaultMonth,
|
|
30
|
+
placeholder,
|
|
31
|
+
isRequired,
|
|
32
|
+
description,
|
|
30
33
|
}: {
|
|
31
34
|
form: any;
|
|
32
35
|
id: string;
|
|
@@ -36,6 +39,8 @@ export function FormDateTime({
|
|
|
36
39
|
onChange?: (date?: Date) => Promise<void>;
|
|
37
40
|
allowEmpty?: boolean;
|
|
38
41
|
defaultMonth?: Date;
|
|
42
|
+
isRequired?: boolean;
|
|
43
|
+
description?: string;
|
|
39
44
|
}) {
|
|
40
45
|
const [open, setOpen] = useState<boolean>(false);
|
|
41
46
|
const t = useI18nTranslations();
|
|
@@ -100,7 +105,7 @@ export function FormDateTime({
|
|
|
100
105
|
|
|
101
106
|
return (
|
|
102
107
|
<div className="flex w-full flex-col">
|
|
103
|
-
<FormFieldWrapper form={form} name={id} label={name}>
|
|
108
|
+
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired} description={description}>
|
|
104
109
|
{(field) => (
|
|
105
110
|
<div className="relative flex flex-row">
|
|
106
111
|
<Popover open={open} onOpenChange={setOpen} modal={true}>
|
|
@@ -113,7 +118,11 @@ export function FormDateTime({
|
|
|
113
118
|
/>
|
|
114
119
|
}
|
|
115
120
|
>
|
|
116
|
-
{field.value ?
|
|
121
|
+
{field.value ? (
|
|
122
|
+
formatDateTime(field.value)
|
|
123
|
+
) : (
|
|
124
|
+
<span>{placeholder ? placeholder : t(`common.pick_date_time`)}</span>
|
|
125
|
+
)}
|
|
117
126
|
<CalendarIcon className="ml-auto h-4 w-4 opacity-50" />
|
|
118
127
|
</PopoverTrigger>
|
|
119
128
|
{field.value && allowEmpty !== false && (
|
|
@@ -18,6 +18,7 @@ type FormFieldWrapperProps<T extends FieldValues> = {
|
|
|
18
18
|
description?: string;
|
|
19
19
|
isRequired?: boolean;
|
|
20
20
|
orientation?: "vertical" | "horizontal" | "responsive";
|
|
21
|
+
className?: string;
|
|
21
22
|
children: (field: ControllerRenderProps<T, Path<T>>, fieldState: ControllerFieldState) => ReactNode;
|
|
22
23
|
testId?: string;
|
|
23
24
|
};
|
|
@@ -29,6 +30,7 @@ export function FormFieldWrapper<T extends FieldValues>({
|
|
|
29
30
|
description,
|
|
30
31
|
isRequired,
|
|
31
32
|
orientation = "vertical",
|
|
33
|
+
className,
|
|
32
34
|
children,
|
|
33
35
|
testId,
|
|
34
36
|
}: FormFieldWrapperProps<T>) {
|
|
@@ -37,7 +39,7 @@ export function FormFieldWrapper<T extends FieldValues>({
|
|
|
37
39
|
control={form.control}
|
|
38
40
|
name={name}
|
|
39
41
|
render={({ field, fieldState }) => (
|
|
40
|
-
<Field orientation={orientation} data-invalid={!!fieldState.error} data-testid={testId}>
|
|
42
|
+
<Field orientation={orientation} className={className} data-invalid={!!fieldState.error} data-testid={testId}>
|
|
41
43
|
{label && (
|
|
42
44
|
<FieldLabel>
|
|
43
45
|
{label}
|
|
@@ -9,6 +9,7 @@ export function FormInput({
|
|
|
9
9
|
form,
|
|
10
10
|
id,
|
|
11
11
|
name,
|
|
12
|
+
description,
|
|
12
13
|
placeholder,
|
|
13
14
|
type,
|
|
14
15
|
onBlur,
|
|
@@ -22,6 +23,7 @@ export function FormInput({
|
|
|
22
23
|
form: any;
|
|
23
24
|
id: string;
|
|
24
25
|
name?: string;
|
|
26
|
+
description?: string;
|
|
25
27
|
placeholder?: string;
|
|
26
28
|
type?: "text" | "number" | "currency" | "decimal" | "password" | "link";
|
|
27
29
|
onBlur?: () => Promise<void>;
|
|
@@ -36,7 +38,7 @@ export function FormInput({
|
|
|
36
38
|
|
|
37
39
|
return (
|
|
38
40
|
<div className="flex w-full flex-col">
|
|
39
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
41
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
40
42
|
{(field) => {
|
|
41
43
|
const handleBlur = async (e: React.FocusEvent<HTMLInputElement>) => {
|
|
42
44
|
let value = e.target.value;
|
|
@@ -12,6 +12,7 @@ export function FormPassword({
|
|
|
12
12
|
disabled,
|
|
13
13
|
testId,
|
|
14
14
|
isRequired,
|
|
15
|
+
description,
|
|
15
16
|
}: {
|
|
16
17
|
form: any;
|
|
17
18
|
id: string;
|
|
@@ -21,10 +22,18 @@ export function FormPassword({
|
|
|
21
22
|
disabled?: boolean;
|
|
22
23
|
testId?: string;
|
|
23
24
|
isRequired?: boolean;
|
|
25
|
+
description?: string;
|
|
24
26
|
}) {
|
|
25
27
|
return (
|
|
26
28
|
<div className="flex w-full flex-col">
|
|
27
|
-
<FormFieldWrapper
|
|
29
|
+
<FormFieldWrapper
|
|
30
|
+
form={form}
|
|
31
|
+
name={id}
|
|
32
|
+
label={name}
|
|
33
|
+
isRequired={isRequired}
|
|
34
|
+
description={description}
|
|
35
|
+
testId={testId}
|
|
36
|
+
>
|
|
28
37
|
{(field) => (
|
|
29
38
|
<PasswordInput
|
|
30
39
|
{...field}
|
|
@@ -42,6 +42,7 @@ interface PlaceAutocompleteProps {
|
|
|
42
42
|
form: any;
|
|
43
43
|
id: string;
|
|
44
44
|
name?: string;
|
|
45
|
+
description?: string;
|
|
45
46
|
placeholder?: string;
|
|
46
47
|
disabled?: boolean;
|
|
47
48
|
testId?: string;
|
|
@@ -56,12 +57,15 @@ interface PlaceAutocompleteProps {
|
|
|
56
57
|
* For regions, use: ["administrative_area_level_1", "administrative_area_level_2"]
|
|
57
58
|
*/
|
|
58
59
|
includeTypes?: string[];
|
|
60
|
+
/** Google Places language code for results. Defaults to "en". */
|
|
61
|
+
languageCode?: string;
|
|
59
62
|
}
|
|
60
63
|
|
|
61
64
|
export function FormPlaceAutocomplete({
|
|
62
65
|
form,
|
|
63
66
|
id,
|
|
64
67
|
name,
|
|
68
|
+
description,
|
|
65
69
|
placeholder,
|
|
66
70
|
disabled,
|
|
67
71
|
testId,
|
|
@@ -69,6 +73,7 @@ export function FormPlaceAutocomplete({
|
|
|
69
73
|
onPlaceSelect,
|
|
70
74
|
className,
|
|
71
75
|
includeTypes,
|
|
76
|
+
languageCode = "en",
|
|
72
77
|
}: PlaceAutocompleteProps) {
|
|
73
78
|
const [inputValue, setInputValue] = useState("");
|
|
74
79
|
const [suggestions, setSuggestions] = useState<PlaceSuggestion[]>([]);
|
|
@@ -117,7 +122,7 @@ export function FormPlaceAutocomplete({
|
|
|
117
122
|
body: JSON.stringify({
|
|
118
123
|
input: input,
|
|
119
124
|
...(includeTypes ? { includedPrimaryTypes: includeTypes } : {}),
|
|
120
|
-
languageCode:
|
|
125
|
+
languageCode: languageCode,
|
|
121
126
|
}),
|
|
122
127
|
});
|
|
123
128
|
|
|
@@ -270,7 +275,7 @@ export function FormPlaceAutocomplete({
|
|
|
270
275
|
if (loadError) {
|
|
271
276
|
return (
|
|
272
277
|
<div className="flex w-full flex-col">
|
|
273
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
278
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
274
279
|
{(field) => (
|
|
275
280
|
<Input
|
|
276
281
|
{...field}
|
|
@@ -287,7 +292,7 @@ export function FormPlaceAutocomplete({
|
|
|
287
292
|
|
|
288
293
|
return (
|
|
289
294
|
<div className="flex w-full flex-col" ref={containerRef}>
|
|
290
|
-
<FormFieldWrapper form={form} name={id} label={name} isRequired={isRequired}>
|
|
295
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description} isRequired={isRequired}>
|
|
291
296
|
{(field) => (
|
|
292
297
|
<div className="relative">
|
|
293
298
|
<Input
|
|
@@ -9,6 +9,7 @@ export function FormSelect({
|
|
|
9
9
|
form,
|
|
10
10
|
id,
|
|
11
11
|
name,
|
|
12
|
+
description,
|
|
12
13
|
placeholder,
|
|
13
14
|
disabled,
|
|
14
15
|
values,
|
|
@@ -21,6 +22,7 @@ export function FormSelect({
|
|
|
21
22
|
form: any;
|
|
22
23
|
id: string;
|
|
23
24
|
name?: string;
|
|
25
|
+
description?: string;
|
|
24
26
|
placeholder?: string;
|
|
25
27
|
disabled?: boolean;
|
|
26
28
|
values: { id: string; text: string }[];
|
|
@@ -36,6 +38,7 @@ export function FormSelect({
|
|
|
36
38
|
form={form}
|
|
37
39
|
name={id}
|
|
38
40
|
label={name}
|
|
41
|
+
description={description}
|
|
39
42
|
isRequired={isRequired}
|
|
40
43
|
orientation={useRows ? "horizontal" : "vertical"}
|
|
41
44
|
testId={testId}
|
|
@@ -10,6 +10,7 @@ export function FormSlider({
|
|
|
10
10
|
name,
|
|
11
11
|
disabled,
|
|
12
12
|
showPercentage,
|
|
13
|
+
description,
|
|
13
14
|
}: {
|
|
14
15
|
form: any;
|
|
15
16
|
id: string;
|
|
@@ -17,12 +18,13 @@ export function FormSlider({
|
|
|
17
18
|
placeholder?: string;
|
|
18
19
|
disabled?: boolean;
|
|
19
20
|
showPercentage?: boolean;
|
|
21
|
+
description?: string;
|
|
20
22
|
}) {
|
|
21
23
|
const value = useWatch({ control: form.control, name: id });
|
|
22
24
|
|
|
23
25
|
return (
|
|
24
26
|
<div className="flex w-full flex-col">
|
|
25
|
-
<FormFieldWrapper form={form} name={id} label={name}>
|
|
27
|
+
<FormFieldWrapper form={form} name={id} label={name} description={description}>
|
|
26
28
|
{() => (
|
|
27
29
|
<div>
|
|
28
30
|
{showPercentage && (
|
|
@@ -11,6 +11,9 @@ export function FormTextarea({
|
|
|
11
11
|
className,
|
|
12
12
|
placeholder,
|
|
13
13
|
testId,
|
|
14
|
+
description,
|
|
15
|
+
isRequired,
|
|
16
|
+
textareaRef,
|
|
14
17
|
}: {
|
|
15
18
|
form: any;
|
|
16
19
|
id: string;
|
|
@@ -18,13 +21,24 @@ export function FormTextarea({
|
|
|
18
21
|
placeholder?: string;
|
|
19
22
|
className?: string;
|
|
20
23
|
testId?: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
isRequired?: boolean;
|
|
26
|
+
textareaRef?: React.RefObject<HTMLTextAreaElement>;
|
|
21
27
|
}) {
|
|
22
28
|
return (
|
|
23
29
|
<div className="flex w-full flex-col">
|
|
24
|
-
<FormFieldWrapper
|
|
30
|
+
<FormFieldWrapper
|
|
31
|
+
form={form}
|
|
32
|
+
name={id}
|
|
33
|
+
label={name}
|
|
34
|
+
description={description}
|
|
35
|
+
isRequired={isRequired}
|
|
36
|
+
testId={testId}
|
|
37
|
+
>
|
|
25
38
|
{(field) => (
|
|
26
39
|
<Textarea
|
|
27
40
|
{...field}
|
|
41
|
+
ref={textareaRef}
|
|
28
42
|
className={cn("min-h-96 w-full", className)}
|
|
29
43
|
disabled={form.formState.isSubmitting}
|
|
30
44
|
placeholder={placeholder}
|
|
@@ -45,7 +45,13 @@ function BreadcrumbDesktop({
|
|
|
45
45
|
{items.length > ITEMS_TO_DISPLAY ? (
|
|
46
46
|
<>
|
|
47
47
|
<BreadcrumbItem>
|
|
48
|
-
{items[0].href ?
|
|
48
|
+
{items[0].href ? (
|
|
49
|
+
<Link href={items[0].href} onClick={items[0].onClick}>
|
|
50
|
+
{items[0].name}
|
|
51
|
+
</Link>
|
|
52
|
+
) : (
|
|
53
|
+
<>{items[0].name}</>
|
|
54
|
+
)}
|
|
49
55
|
</BreadcrumbItem>
|
|
50
56
|
<BreadcrumbSeparator />
|
|
51
57
|
<BreadcrumbItem>
|
|
@@ -56,7 +62,9 @@ function BreadcrumbDesktop({
|
|
|
56
62
|
<DropdownMenuContent align="start">
|
|
57
63
|
{items.slice(1, -ITEMS_TO_DISPLAY + 2).map((item, index) => (
|
|
58
64
|
<DropdownMenuItem key={index}>
|
|
59
|
-
<Link href={item.href ? item.href : "#"}
|
|
65
|
+
<Link href={item.href ? item.href : "#"} onClick={item.onClick}>
|
|
66
|
+
{item.name}
|
|
67
|
+
</Link>
|
|
60
68
|
</DropdownMenuItem>
|
|
61
69
|
))}
|
|
62
70
|
</DropdownMenuContent>
|
|
@@ -66,7 +74,13 @@ function BreadcrumbDesktop({
|
|
|
66
74
|
{items.slice(-ITEMS_TO_DISPLAY + 2).map((item, index) => (
|
|
67
75
|
<Fragment key={index}>
|
|
68
76
|
<BreadcrumbItem>
|
|
69
|
-
{item.href ?
|
|
77
|
+
{item.href ? (
|
|
78
|
+
<Link href={item.href} onClick={item.onClick}>
|
|
79
|
+
{item.name}
|
|
80
|
+
</Link>
|
|
81
|
+
) : (
|
|
82
|
+
<>{item.name}</>
|
|
83
|
+
)}
|
|
70
84
|
</BreadcrumbItem>
|
|
71
85
|
{index < items.slice(-ITEMS_TO_DISPLAY + 2).length - 1 && <BreadcrumbSeparator />}
|
|
72
86
|
</Fragment>
|
|
@@ -77,7 +91,13 @@ function BreadcrumbDesktop({
|
|
|
77
91
|
{items.map((item, index) => (
|
|
78
92
|
<Fragment key={index}>
|
|
79
93
|
<BreadcrumbItem>
|
|
80
|
-
{item.href ?
|
|
94
|
+
{item.href ? (
|
|
95
|
+
<Link href={item.href} onClick={item.onClick}>
|
|
96
|
+
{item.name}
|
|
97
|
+
</Link>
|
|
98
|
+
) : (
|
|
99
|
+
<>{item.name}</>
|
|
100
|
+
)}
|
|
81
101
|
</BreadcrumbItem>
|
|
82
102
|
{index < items.length - 1 && <BreadcrumbSeparator />}
|
|
83
103
|
</Fragment>
|
|
@@ -124,7 +144,13 @@ function BreadcrumbMobile({
|
|
|
124
144
|
<DropdownMenuContent align="start">
|
|
125
145
|
{allItems.map((item, index) => (
|
|
126
146
|
<DropdownMenuItem key={index}>
|
|
127
|
-
{item.href ?
|
|
147
|
+
{item.href ? (
|
|
148
|
+
<Link href={item.href} onClick={item.onClick}>
|
|
149
|
+
{item.name}
|
|
150
|
+
</Link>
|
|
151
|
+
) : (
|
|
152
|
+
<>{item.name}</>
|
|
153
|
+
)}
|
|
128
154
|
</DropdownMenuItem>
|
|
129
155
|
))}
|
|
130
156
|
</DropdownMenuContent>
|
package/src/hooks/useSocket.ts
CHANGED
|
@@ -114,7 +114,18 @@ export function useSocket({ token }: UseSocketOptions): UseSocketReturn {
|
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
const handleNotification = (data: any) => {
|
|
117
|
-
|
|
117
|
+
// The socket sends a JSON:API document { data, included }; rehydrate expects
|
|
118
|
+
// { jsonApi, included }. Map it (and guard against an unexpected shape so a
|
|
119
|
+
// malformed payload never throws an uncaught error that breaks the socket).
|
|
120
|
+
const resource = data?.data ?? data?.jsonApi;
|
|
121
|
+
if (!resource?.type) {
|
|
122
|
+
console.warn("[useSocket] ignoring notification with unexpected payload shape", data);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const notification = rehydrate(Modules.Notification, {
|
|
126
|
+
jsonApi: resource,
|
|
127
|
+
included: data?.included || [],
|
|
128
|
+
}) as NotificationInterface;
|
|
118
129
|
if (notification) {
|
|
119
130
|
setSocketNotifications((prev) => {
|
|
120
131
|
const newNotifications = [...prev, notification];
|
|
@@ -17,6 +17,18 @@ const badgeVariants = cva(
|
|
|
17
17
|
"border-border text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground bg-input/20 dark:bg-input/30",
|
|
18
18
|
ghost: "hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
19
19
|
link: "text-primary underline-offset-4 hover:underline",
|
|
20
|
+
warning: "bg-warning text-warning-foreground [a]:hover:bg-warning/80",
|
|
21
|
+
blue: "bg-sky-500 text-primary-foreground [a]:hover:bg-sky-500/80",
|
|
22
|
+
green: "bg-emerald-500 text-primary-foreground [a]:hover:bg-emerald-500/80",
|
|
23
|
+
red: "bg-red-500 text-primary-foreground [a]:hover:bg-red-500/80",
|
|
24
|
+
yellow: "bg-yellow-500 text-primary-foreground [a]:hover:bg-yellow-500/80",
|
|
25
|
+
purple: "bg-purple-500 text-primary-foreground [a]:hover:bg-purple-500/80",
|
|
26
|
+
pink: "bg-pink-500 text-primary-foreground [a]:hover:bg-pink-500/80",
|
|
27
|
+
gray: "bg-gray-500 text-primary-foreground [a]:hover:bg-gray-500/80",
|
|
28
|
+
orange: "bg-orange-500 text-primary-foreground [a]:hover:bg-orange-500/80",
|
|
29
|
+
teal: "bg-teal-500 text-primary-foreground [a]:hover:bg-teal-500/80",
|
|
30
|
+
lime: "bg-lime-500 text-primary-foreground [a]:hover:bg-lime-500/80",
|
|
31
|
+
none: "border-input bg-transparent text-muted-foreground min-w-20 min-h-5",
|
|
20
32
|
},
|
|
21
33
|
},
|
|
22
34
|
defaultVariants: {
|
|
@@ -47,4 +59,7 @@ function Badge({
|
|
|
47
59
|
});
|
|
48
60
|
}
|
|
49
61
|
|
|
62
|
+
type BadgeProps = useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>;
|
|
63
|
+
|
|
50
64
|
export { Badge, badgeVariants };
|
|
65
|
+
export type { BadgeProps };
|