lazyconvex 0.0.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 (70) hide show
  1. package/README.md +926 -0
  2. package/dist/components/index.mjs +937 -0
  3. package/dist/error-D4GuI0ot.mjs +71 -0
  4. package/dist/file-field-BqVgy8xY.mjs +205 -0
  5. package/dist/form-BXJK_j10.d.mts +99 -0
  6. package/dist/index.d.mts +433 -0
  7. package/dist/index.mjs +1 -0
  8. package/dist/index2.d.mts +5 -0
  9. package/dist/index3.d.mts +35 -0
  10. package/dist/index4.d.mts +101 -0
  11. package/dist/index5.d.mts +842 -0
  12. package/dist/next/index.mjs +151 -0
  13. package/dist/org-CmJBb8z-.d.mts +56 -0
  14. package/dist/react/index.mjs +158 -0
  15. package/dist/retry.d.mts +12 -0
  16. package/dist/retry.mjs +35 -0
  17. package/dist/schema.d.mts +23 -0
  18. package/dist/schema.mjs +15 -0
  19. package/dist/server/index.mjs +2572 -0
  20. package/dist/types-DWBVRtit.d.mts +322 -0
  21. package/dist/use-online-status-CMr73Jlk.mjs +155 -0
  22. package/dist/use-upload-DtELytQi.mjs +95 -0
  23. package/dist/zod.d.mts +18 -0
  24. package/dist/zod.mjs +87 -0
  25. package/package.json +40 -0
  26. package/src/components/editors-section.tsx +86 -0
  27. package/src/components/fields.tsx +884 -0
  28. package/src/components/file-field.tsx +234 -0
  29. package/src/components/form.tsx +191 -0
  30. package/src/components/index.ts +11 -0
  31. package/src/components/offline-indicator.tsx +15 -0
  32. package/src/components/org-avatar.tsx +13 -0
  33. package/src/components/permission-guard.tsx +36 -0
  34. package/src/components/role-badge.tsx +14 -0
  35. package/src/components/suspense-wrap.tsx +8 -0
  36. package/src/index.ts +40 -0
  37. package/src/next/active-org.ts +33 -0
  38. package/src/next/auth.ts +9 -0
  39. package/src/next/image.ts +134 -0
  40. package/src/next/index.ts +3 -0
  41. package/src/react/form-meta.ts +53 -0
  42. package/src/react/form.ts +201 -0
  43. package/src/react/index.ts +8 -0
  44. package/src/react/org.tsx +96 -0
  45. package/src/react/use-active-org.ts +48 -0
  46. package/src/react/use-bulk-selection.ts +47 -0
  47. package/src/react/use-online-status.ts +21 -0
  48. package/src/react/use-optimistic.ts +54 -0
  49. package/src/react/use-upload.ts +101 -0
  50. package/src/retry.ts +47 -0
  51. package/src/schema.ts +30 -0
  52. package/src/server/cache-crud.ts +175 -0
  53. package/src/server/check-schema.ts +29 -0
  54. package/src/server/child.ts +98 -0
  55. package/src/server/crud.ts +384 -0
  56. package/src/server/db.ts +7 -0
  57. package/src/server/error.ts +39 -0
  58. package/src/server/file.ts +372 -0
  59. package/src/server/helpers.ts +214 -0
  60. package/src/server/index.ts +12 -0
  61. package/src/server/org-crud.ts +307 -0
  62. package/src/server/org-helpers.ts +54 -0
  63. package/src/server/org.ts +572 -0
  64. package/src/server/schema-helpers.ts +107 -0
  65. package/src/server/setup.ts +138 -0
  66. package/src/server/test-crud.ts +211 -0
  67. package/src/server/test.ts +554 -0
  68. package/src/server/types.ts +392 -0
  69. package/src/server/unique.ts +28 -0
  70. package/src/zod.ts +141 -0
@@ -0,0 +1,937 @@
1
+ import { n as FileApiProvider, r as FileFieldImpl, t as FileApiContext } from "../file-field-BqVgy8xY.mjs";
2
+ import { unwrapZod } from "../zod.mjs";
3
+ import "../error-D4GuI0ot.mjs";
4
+ import { n as useForm$1, r as useFormMutation$1, t as useOnlineStatus } from "../use-online-status-CMr73Jlk.mjs";
5
+ import "../use-upload-DtELytQi.mjs";
6
+ import { Suspense, createContext, use, useEffect, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Avatar, AvatarFallback, AvatarImage } from "@a/ui/avatar";
9
+ import { Button } from "@a/ui/button";
10
+ import { Card, CardContent, CardHeader, CardTitle } from "@a/ui/card";
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@a/ui/select";
12
+ import { CalendarIcon, Check, ChevronsUpDown, Star, UserPlus, X } from "lucide-react";
13
+ import { cn } from "@a/ui";
14
+ import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@a/ui/command";
15
+ import { Field, FieldError, FieldLabel } from "@a/ui/field";
16
+ import { Input } from "@a/ui/input";
17
+ import { Popover, PopoverContent, PopoverTrigger } from "@a/ui/popover";
18
+ import { Slider } from "@a/ui/slider";
19
+ import { Spinner } from "@a/ui/spinner";
20
+ import { Switch } from "@a/ui/switch";
21
+ import { Textarea } from "@a/ui/textarea";
22
+ import { format } from "date-fns";
23
+ import dynamic from "next/dynamic";
24
+ import { toast } from "sonner";
25
+ import { Dialog, DialogContent } from "@a/ui/dialog";
26
+ import { useNavigationGuard } from "next-navigation-guard";
27
+ import { Badge } from "@a/ui/badge";
28
+ import Link from "next/link";
29
+
30
+ //#region src/components/editors-section.tsx
31
+ const EditorsSection = ({ editorsList, members, onAdd, onRemove }) => {
32
+ const editorIds = new Set(editorsList.map((e) => e.userId)), available = [];
33
+ for (const m of members) if (!editorIds.has(m.userId)) available.push(m);
34
+ return /* @__PURE__ */ jsxs(Card, {
35
+ "data-testid": "editors-section",
36
+ children: [/* @__PURE__ */ jsxs(CardHeader, {
37
+ className: "flex flex-row items-center justify-between",
38
+ children: [/* @__PURE__ */ jsx(CardTitle, { children: "Editors" }), available.length > 0 ? /* @__PURE__ */ jsxs(Select, {
39
+ onValueChange: onAdd,
40
+ children: [/* @__PURE__ */ jsxs(SelectTrigger, {
41
+ className: "w-40",
42
+ "data-testid": "add-editor-trigger",
43
+ children: [/* @__PURE__ */ jsx(UserPlus, { className: "mr-2 size-4" }), /* @__PURE__ */ jsx(SelectValue, { placeholder: "Add editor" })]
44
+ }), /* @__PURE__ */ jsx(SelectContent, { children: available.map((m) => /* @__PURE__ */ jsx(SelectItem, {
45
+ value: m.userId,
46
+ children: m.user?.name ?? m.user?.email ?? "Unknown"
47
+ }, m.userId)) })]
48
+ }) : null]
49
+ }), editorsList.length > 0 ? /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("div", {
50
+ className: "divide-y",
51
+ children: editorsList.map((e) => /* @__PURE__ */ jsxs("div", {
52
+ className: "flex items-center gap-3 py-2",
53
+ "data-testid": `editor-item-${e.userId}`,
54
+ children: [
55
+ /* @__PURE__ */ jsx(Avatar, {
56
+ className: "size-7",
57
+ children: /* @__PURE__ */ jsx(AvatarFallback, {
58
+ className: "text-xs",
59
+ children: e.name.slice(0, 2).toUpperCase() || "??"
60
+ })
61
+ }),
62
+ /* @__PURE__ */ jsx("span", {
63
+ className: "flex-1 text-sm",
64
+ children: e.name || e.email
65
+ }),
66
+ /* @__PURE__ */ jsx(Button, {
67
+ "data-testid": `remove-editor-${e.userId}`,
68
+ onClick: () => onRemove(e.userId),
69
+ size: "icon",
70
+ variant: "ghost",
71
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
72
+ })
73
+ ]
74
+ }, e.userId))
75
+ }) }) : /* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx("p", {
76
+ className: "text-sm text-muted-foreground",
77
+ children: "No editors assigned"
78
+ }) })]
79
+ });
80
+ };
81
+
82
+ //#endregion
83
+ //#region src/components/fields.tsx
84
+ const Calendar = dynamic(async () => import("@a/ui/calendar").then((m) => ({ default: m.Calendar })), {
85
+ loading: () => /* @__PURE__ */ jsx("div", { className: "h-64 w-full animate-pulse rounded-md bg-muted" }),
86
+ ssr: false
87
+ }), HEX_COLOR_REGEX = /^#[\dA-Fa-f]{6}$/, DynamicFileField = dynamic(async () => import("../file-field-BqVgy8xY.mjs").then((n) => n.i), {
88
+ loading: () => /* @__PURE__ */ jsx("div", { className: "h-32 w-full animate-pulse rounded-lg bg-muted" }),
89
+ ssr: false
90
+ }), FormContext = createContext(null), useFCtx = () => {
91
+ const c = use(FormContext);
92
+ if (!c) throw new Error("Field must be inside <Form>");
93
+ return c;
94
+ }, useField = (name, kind) => {
95
+ const ctx = useFCtx(), info = ctx.meta[name];
96
+ if (!info) throw new Error(`Unknown field: ${name}`);
97
+ if (info.kind !== kind) throw new Error(`Field ${name} is not ${kind}`);
98
+ return {
99
+ form: ctx.form,
100
+ info,
101
+ schema: ctx.schema
102
+ };
103
+ }, defaultEnumOptions = (schema, name) => {
104
+ const { schema: inner } = unwrapZod(schema.shape[name]);
105
+ if (inner && "options" in inner) return inner.options.map((v) => ({
106
+ label: v.charAt(0).toUpperCase() + v.slice(1),
107
+ value: v
108
+ }));
109
+ throw new Error(`Choose: field "${name}" has no enum options. Pass options prop.`);
110
+ }, fields = {
111
+ Arr: ({ className, "data-testid": testId, disabled, inputClassName, label, name, placeholder, tagClassName, transform }) => {
112
+ const { form, info } = useField(name, "stringArray");
113
+ return /* @__PURE__ */ jsx(form.Field, {
114
+ mode: "array",
115
+ name,
116
+ children: (f) => {
117
+ const tags = f.state.value ?? [], inv = f.state.meta.isTouched && !f.state.meta.isValid, mx = info.max, tid = testId ?? f.name, errorId = `${f.name}-error`;
118
+ return /* @__PURE__ */ jsxs(Field, {
119
+ "data-invalid": inv,
120
+ "data-testid": tid,
121
+ children: [
122
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
123
+ htmlFor: f.name,
124
+ children: label
125
+ }) : null,
126
+ /* @__PURE__ */ jsxs("div", {
127
+ className: cn("relative flex min-h-10 w-full flex-wrap items-center gap-0.75 rounded-md border border-input bg-transparent p-1 text-sm transition-[color,box-shadow] disabled:cursor-not-allowed disabled:opacity-50 has-[input:focus-visible]:border-ring has-[input:focus-visible]:ring-[3px] has-[input:focus-visible]:ring-ring/50 dark:bg-background", className),
128
+ children: [tags.map((t, i) => /* @__PURE__ */ jsxs("p", {
129
+ className: cn("flex h-7 items-center gap-0.5 rounded-full bg-muted pr-1.5 pl-3 transition-all duration-300 hover:bg-input", tagClassName, disabled && "cursor-not-allowed opacity-50 *:cursor-not-allowed"),
130
+ children: [/* @__PURE__ */ jsx("span", {
131
+ className: "mb-px",
132
+ children: t
133
+ }), /* @__PURE__ */ jsx(X, {
134
+ className: "size-4 cursor-pointer rounded-full stroke-1 p-0.5 text-muted-foreground transition-all duration-300 hover:scale-110 hover:bg-background hover:stroke-2 hover:text-destructive active:scale-75",
135
+ onClick: () => {
136
+ if (!disabled) f.removeValue(i);
137
+ }
138
+ })]
139
+ }, t)), /* @__PURE__ */ jsx("input", {
140
+ "aria-describedby": inv ? errorId : void 0,
141
+ "aria-invalid": inv,
142
+ className: cn("peer ml-1 w-0 flex-1 outline-none placeholder:text-muted-foreground placeholder:capitalize", tags.length ? "placeholder:opacity-0" : "pl-1", inputClassName),
143
+ disabled,
144
+ id: f.name,
145
+ name: f.name,
146
+ onBlur: f.handleBlur,
147
+ onKeyDown: (e) => {
148
+ const { value } = e.currentTarget;
149
+ if (e.key === "Enter") {
150
+ e.preventDefault();
151
+ if (!value.trim()) return;
152
+ const v = transform ? transform(value) : value;
153
+ if (tags.includes(v)) {
154
+ toast.error("Item duplicated");
155
+ return;
156
+ }
157
+ if (mx && tags.length + 1 > mx) {
158
+ toast.error(`Max ${mx}`);
159
+ return;
160
+ }
161
+ f.handleChange([...new Set([...tags, v])]);
162
+ e.currentTarget.value = "";
163
+ } else if (e.key === "Backspace" && tags.length && !value.trim()) {
164
+ e.preventDefault();
165
+ f.removeValue(tags.length - 1);
166
+ }
167
+ },
168
+ placeholder: tags.length ? void 0 : placeholder
169
+ })]
170
+ }),
171
+ inv ? /* @__PURE__ */ jsx(FieldError, {
172
+ errors: f.state.meta.errors,
173
+ id: errorId
174
+ }) : null
175
+ ]
176
+ });
177
+ }
178
+ });
179
+ },
180
+ Choose: ({ "data-testid": testId, label, name, options: explicitOptions, placeholder }) => {
181
+ const { form, schema } = useField(name, "string"), options = explicitOptions ?? defaultEnumOptions(schema, name);
182
+ return /* @__PURE__ */ jsx(form.Field, {
183
+ name,
184
+ children: (f) => {
185
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`;
186
+ return /* @__PURE__ */ jsxs(Field, {
187
+ "data-invalid": inv,
188
+ "data-testid": tid,
189
+ children: [
190
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
191
+ htmlFor: f.name,
192
+ children: label
193
+ }) : null,
194
+ /* @__PURE__ */ jsxs(Select, {
195
+ name: f.name,
196
+ onValueChange: (v) => f.handleChange(v),
197
+ value: f.state.value ?? "",
198
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
199
+ "aria-describedby": inv ? errorId : void 0,
200
+ "aria-invalid": inv,
201
+ id: f.name,
202
+ onBlur: f.handleBlur,
203
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder })
204
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((o) => /* @__PURE__ */ jsx(SelectItem, {
205
+ value: o.value,
206
+ children: o.label
207
+ }, o.value)) })]
208
+ }),
209
+ inv ? /* @__PURE__ */ jsx(FieldError, {
210
+ errors: f.state.meta.errors,
211
+ id: errorId
212
+ }) : null
213
+ ]
214
+ });
215
+ }
216
+ });
217
+ },
218
+ Colorpick: ({ "data-testid": testId, label, name }) => {
219
+ const { form } = useField(name, "string");
220
+ return /* @__PURE__ */ jsx(form.Field, {
221
+ name,
222
+ children: (f) => {
223
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`, val = f.state.value ?? "#000000";
224
+ return /* @__PURE__ */ jsxs(Field, {
225
+ "data-invalid": inv,
226
+ "data-testid": tid,
227
+ children: [
228
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
229
+ htmlFor: f.name,
230
+ children: label
231
+ }) : null,
232
+ /* @__PURE__ */ jsxs("div", {
233
+ className: "flex gap-2",
234
+ children: [/* @__PURE__ */ jsx("input", {
235
+ "aria-describedby": inv ? errorId : void 0,
236
+ "aria-invalid": inv,
237
+ className: "size-10 cursor-pointer rounded-md border border-input",
238
+ id: f.name,
239
+ name: f.name,
240
+ onBlur: f.handleBlur,
241
+ onChange: (e) => f.handleChange(e.target.value),
242
+ type: "color",
243
+ value: val
244
+ }), /* @__PURE__ */ jsx(Input, {
245
+ className: "flex-1 font-mono",
246
+ onBlur: f.handleBlur,
247
+ onChange: (e) => {
248
+ const v = e.target.value;
249
+ if (HEX_COLOR_REGEX.test(v)) f.handleChange(v);
250
+ },
251
+ placeholder: "#000000",
252
+ value: val
253
+ })]
254
+ }),
255
+ inv ? /* @__PURE__ */ jsx(FieldError, {
256
+ errors: f.state.meta.errors,
257
+ id: errorId
258
+ }) : null
259
+ ]
260
+ });
261
+ }
262
+ });
263
+ },
264
+ Combobox: ({ "data-testid": testId, emptyText = "No results found.", label, name, options, placeholder = "Select...", searchPlaceholder = "Search..." }) => {
265
+ const { form } = useField(name, "string"), [open, setOpen] = useState(false);
266
+ return /* @__PURE__ */ jsx(form.Field, {
267
+ name,
268
+ children: (f) => {
269
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`, selected = options.find((o) => o.value === f.state.value), listId = `${f.name}-listbox`;
270
+ return /* @__PURE__ */ jsxs(Field, {
271
+ "data-invalid": inv,
272
+ "data-testid": tid,
273
+ children: [
274
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
275
+ htmlFor: f.name,
276
+ children: label
277
+ }) : null,
278
+ /* @__PURE__ */ jsxs(Popover, {
279
+ onOpenChange: setOpen,
280
+ open,
281
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
282
+ asChild: true,
283
+ children: /* @__PURE__ */ jsxs(Button, {
284
+ "aria-controls": listId,
285
+ "aria-describedby": inv ? errorId : void 0,
286
+ "aria-expanded": open,
287
+ "aria-invalid": inv,
288
+ className: "w-full justify-between font-normal",
289
+ id: f.name,
290
+ onBlur: f.handleBlur,
291
+ role: "combobox",
292
+ variant: "outline",
293
+ children: [selected ? selected.label : /* @__PURE__ */ jsx("span", {
294
+ className: "text-muted-foreground",
295
+ children: placeholder
296
+ }), /* @__PURE__ */ jsx(ChevronsUpDown, { className: "ml-2 size-4 shrink-0 opacity-50" })]
297
+ })
298
+ }), /* @__PURE__ */ jsx(PopoverContent, {
299
+ className: "w-(--radix-popover-trigger-width) p-0",
300
+ children: /* @__PURE__ */ jsxs(Command, { children: [/* @__PURE__ */ jsx(CommandInput, { placeholder: searchPlaceholder }), /* @__PURE__ */ jsxs(CommandList, {
301
+ id: listId,
302
+ children: [/* @__PURE__ */ jsx(CommandEmpty, { children: emptyText }), /* @__PURE__ */ jsx(CommandGroup, { children: options.map((o) => /* @__PURE__ */ jsxs(CommandItem, {
303
+ onSelect: () => {
304
+ f.handleChange(o.value === f.state.value ? "" : o.value);
305
+ setOpen(false);
306
+ },
307
+ value: o.label,
308
+ children: [/* @__PURE__ */ jsx(Check, { className: cn("mr-2 size-4", f.state.value === o.value ? "opacity-100" : "opacity-0") }), o.label]
309
+ }, o.value)) })]
310
+ })] })
311
+ })]
312
+ }),
313
+ inv ? /* @__PURE__ */ jsx(FieldError, {
314
+ errors: f.state.meta.errors,
315
+ id: errorId
316
+ }) : null
317
+ ]
318
+ });
319
+ }
320
+ });
321
+ },
322
+ Datepick: ({ clearable = true, "data-testid": testId, disabled, label, name, placeholder = "Pick a date" }) => {
323
+ const { form } = useField(name, "number");
324
+ return /* @__PURE__ */ jsx(form.Field, {
325
+ name,
326
+ children: (f) => {
327
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, ts = f.state.value, dateVal = ts ? new Date(ts) : void 0, tid = testId ?? f.name, errorId = `${f.name}-error`;
328
+ return /* @__PURE__ */ jsxs(Field, {
329
+ "data-invalid": inv,
330
+ "data-testid": tid,
331
+ children: [
332
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
333
+ htmlFor: f.name,
334
+ children: label
335
+ }) : null,
336
+ /* @__PURE__ */ jsxs("div", {
337
+ className: "flex gap-1",
338
+ children: [/* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
339
+ asChild: true,
340
+ children: /* @__PURE__ */ jsxs(Button, {
341
+ "aria-describedby": inv ? errorId : void 0,
342
+ "aria-invalid": inv,
343
+ className: cn("flex-1 justify-start text-left font-normal", !dateVal && "text-muted-foreground"),
344
+ "data-testid": `${tid}-trigger`,
345
+ disabled,
346
+ id: f.name,
347
+ variant: "outline",
348
+ children: [/* @__PURE__ */ jsx(CalendarIcon, { className: "mr-2 size-4" }), dateVal ? format(dateVal, "PPP") : placeholder]
349
+ })
350
+ }), /* @__PURE__ */ jsx(PopoverContent, {
351
+ align: "start",
352
+ className: "w-auto p-0",
353
+ "data-testid": `${tid}-calendar`,
354
+ children: /* @__PURE__ */ jsx(Calendar, {
355
+ mode: "single",
356
+ onSelect: (d) => {
357
+ f.handleChange(d ? d.getTime() : null);
358
+ f.handleBlur();
359
+ },
360
+ selected: dateVal
361
+ })
362
+ })] }), clearable && dateVal ? /* @__PURE__ */ jsx(Button, {
363
+ "data-testid": `${tid}-clear`,
364
+ disabled,
365
+ onClick: () => {
366
+ f.handleChange(null);
367
+ f.handleBlur();
368
+ },
369
+ size: "icon",
370
+ type: "button",
371
+ variant: "outline",
372
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
373
+ }) : null]
374
+ }),
375
+ inv ? /* @__PURE__ */ jsx(FieldError, {
376
+ errors: f.state.meta.errors,
377
+ id: errorId
378
+ }) : null
379
+ ]
380
+ });
381
+ }
382
+ });
383
+ },
384
+ Err: ({ error }) => error ? /* @__PURE__ */ jsx("p", {
385
+ className: "rounded-lg bg-destructive/10 p-3 text-sm text-destructive",
386
+ role: "alert",
387
+ children: error.message
388
+ }) : null,
389
+ File: ({ accept, className, compressImg, "data-testid": testId, disabled, label, maxSize, name }) => {
390
+ const { form } = useField(name, "file");
391
+ return /* @__PURE__ */ jsx(form.Field, {
392
+ name,
393
+ children: (f) => /* @__PURE__ */ jsx(DynamicFileField, {
394
+ accept,
395
+ className,
396
+ compressImg,
397
+ "data-testid": testId,
398
+ disabled,
399
+ field: f,
400
+ label,
401
+ maxSize
402
+ })
403
+ });
404
+ },
405
+ Files: ({ accept, className, compressImg, "data-testid": testId, disabled, label, max, maxSize, name }) => {
406
+ const { form, info } = useField(name, "files");
407
+ return /* @__PURE__ */ jsx(form.Field, {
408
+ mode: "array",
409
+ name,
410
+ children: (f) => /* @__PURE__ */ jsx(DynamicFileField, {
411
+ accept,
412
+ className,
413
+ compressImg,
414
+ "data-testid": testId,
415
+ disabled,
416
+ field: f,
417
+ label,
418
+ max: max ?? info.max,
419
+ maxSize,
420
+ multiple: true
421
+ })
422
+ });
423
+ },
424
+ MultiSelect: ({ "data-testid": testId, label, name, options, placeholder }) => {
425
+ const { form, info } = useField(name, "stringArray");
426
+ return /* @__PURE__ */ jsx(form.Field, {
427
+ mode: "array",
428
+ name,
429
+ children: (f) => {
430
+ const selected = f.state.value ?? [], inv = f.state.meta.isTouched && !f.state.meta.isValid, mx = info.max, tid = testId ?? f.name, errorId = `${f.name}-error`;
431
+ return /* @__PURE__ */ jsxs(Field, {
432
+ "data-invalid": inv,
433
+ "data-testid": tid,
434
+ children: [
435
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
436
+ htmlFor: f.name,
437
+ children: label
438
+ }) : null,
439
+ /* @__PURE__ */ jsxs(Select, {
440
+ name: f.name,
441
+ onValueChange: (v) => {
442
+ if (selected.includes(v)) f.handleChange(selected.filter((x) => x !== v));
443
+ else {
444
+ if (mx && selected.length >= mx) {
445
+ toast.error(`Max ${mx}`);
446
+ return;
447
+ }
448
+ f.handleChange([...selected, v]);
449
+ }
450
+ },
451
+ value: "",
452
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
453
+ "aria-describedby": inv ? errorId : void 0,
454
+ "aria-invalid": inv,
455
+ id: f.name,
456
+ onBlur: f.handleBlur,
457
+ children: /* @__PURE__ */ jsx(SelectValue, { placeholder: selected.length ? `${selected.length} selected` : placeholder })
458
+ }), /* @__PURE__ */ jsx(SelectContent, { children: options.map((o) => /* @__PURE__ */ jsx(SelectItem, {
459
+ className: selected.includes(o.value) ? "bg-accent" : "",
460
+ value: o.value,
461
+ children: o.label
462
+ }, o.value)) })]
463
+ }),
464
+ selected.length ? /* @__PURE__ */ jsx("div", {
465
+ className: "flex flex-wrap gap-1",
466
+ children: selected.map((v) => {
467
+ return /* @__PURE__ */ jsxs("p", {
468
+ className: "flex h-7 items-center gap-0.5 rounded-full bg-muted pr-1.5 pl-3 text-sm transition-all duration-300 hover:bg-input",
469
+ children: [/* @__PURE__ */ jsx("span", {
470
+ className: "mb-px",
471
+ children: options.find((o) => o.value === v)?.label ?? v
472
+ }), /* @__PURE__ */ jsx(X, {
473
+ className: "size-4 cursor-pointer rounded-full stroke-1 p-0.5 text-muted-foreground transition-all duration-300 hover:scale-110 hover:bg-background hover:stroke-2 hover:text-destructive active:scale-75",
474
+ onClick: () => f.handleChange(selected.filter((x) => x !== v))
475
+ })]
476
+ }, v);
477
+ })
478
+ }) : null,
479
+ inv ? /* @__PURE__ */ jsx(FieldError, {
480
+ errors: f.state.meta.errors,
481
+ id: errorId
482
+ }) : null
483
+ ]
484
+ });
485
+ }
486
+ });
487
+ },
488
+ Num: ({ "data-testid": testId, label, name, ...props }) => {
489
+ const { form } = useField(name, "number");
490
+ return /* @__PURE__ */ jsx(form.Field, {
491
+ name,
492
+ children: (f) => {
493
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`;
494
+ return /* @__PURE__ */ jsxs(Field, {
495
+ "data-invalid": inv,
496
+ "data-testid": tid,
497
+ children: [
498
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
499
+ htmlFor: f.name,
500
+ children: label
501
+ }) : null,
502
+ /* @__PURE__ */ jsx(Input, {
503
+ "aria-describedby": inv ? errorId : void 0,
504
+ "aria-invalid": inv,
505
+ id: f.name,
506
+ name: f.name,
507
+ onBlur: f.handleBlur,
508
+ onChange: (e) => {
509
+ const { value, valueAsNumber } = e.currentTarget;
510
+ f.handleChange(value === "" || Number.isNaN(valueAsNumber) ? void 0 : valueAsNumber);
511
+ },
512
+ type: "number",
513
+ value: f.state.value ?? "",
514
+ ...props
515
+ }),
516
+ inv ? /* @__PURE__ */ jsx(FieldError, {
517
+ errors: f.state.meta.errors,
518
+ id: errorId
519
+ }) : null
520
+ ]
521
+ });
522
+ }
523
+ });
524
+ },
525
+ Rating: ({ "data-testid": testId, label, max = 5, name }) => {
526
+ const { form } = useField(name, "number");
527
+ return /* @__PURE__ */ jsx(form.Field, {
528
+ name,
529
+ children: (f) => {
530
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`, val = f.state.value ?? 0;
531
+ return /* @__PURE__ */ jsxs(Field, {
532
+ "data-invalid": inv,
533
+ "data-testid": tid,
534
+ children: [
535
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
536
+ htmlFor: f.name,
537
+ children: label
538
+ }) : null,
539
+ /* @__PURE__ */ jsx("div", {
540
+ className: "flex gap-1",
541
+ children: Array.from({ length: max }, (_, i) => i + 1).map((i) => /* @__PURE__ */ jsx(Star, {
542
+ className: cn("size-6 cursor-pointer transition-all", i <= val ? "fill-yellow-400 text-yellow-400" : "text-muted-foreground hover:text-yellow-400"),
543
+ onBlur: f.handleBlur,
544
+ onClick: () => f.handleChange(i)
545
+ }, i))
546
+ }),
547
+ inv ? /* @__PURE__ */ jsx(FieldError, {
548
+ errors: f.state.meta.errors,
549
+ id: errorId
550
+ }) : null
551
+ ]
552
+ });
553
+ }
554
+ });
555
+ },
556
+ Slider: ({ "data-testid": testId, label, max = 100, min = 0, name, step = 1 }) => {
557
+ const { form } = useField(name, "number");
558
+ return /* @__PURE__ */ jsx(form.Field, {
559
+ name,
560
+ children: (f) => {
561
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`, val = f.state.value ?? min;
562
+ return /* @__PURE__ */ jsxs(Field, {
563
+ "data-invalid": inv,
564
+ "data-testid": tid,
565
+ children: [
566
+ /* @__PURE__ */ jsxs("div", {
567
+ className: "flex items-center justify-between",
568
+ children: [label ? /* @__PURE__ */ jsx(FieldLabel, {
569
+ htmlFor: f.name,
570
+ children: label
571
+ }) : null, /* @__PURE__ */ jsx("span", {
572
+ className: "text-sm text-muted-foreground",
573
+ children: val
574
+ })]
575
+ }),
576
+ /* @__PURE__ */ jsx(Slider, {
577
+ "aria-describedby": inv ? errorId : void 0,
578
+ "aria-invalid": inv,
579
+ id: f.name,
580
+ max,
581
+ min,
582
+ name: f.name,
583
+ onBlur: f.handleBlur,
584
+ onValueChange: ([v]) => f.handleChange(v),
585
+ step,
586
+ value: [val]
587
+ }),
588
+ inv ? /* @__PURE__ */ jsx(FieldError, {
589
+ errors: f.state.meta.errors,
590
+ id: errorId
591
+ }) : null
592
+ ]
593
+ });
594
+ }
595
+ });
596
+ },
597
+ Submit: ({ children, disabled, Icon, ...props }) => {
598
+ const { form } = useFCtx();
599
+ return /* @__PURE__ */ jsx(form.Subscribe, {
600
+ selector: (s) => s.isSubmitting,
601
+ children: (pending) => /* @__PURE__ */ jsxs(Button, {
602
+ disabled: disabled ?? pending,
603
+ type: "submit",
604
+ ...props,
605
+ children: [pending ? /* @__PURE__ */ jsx(Spinner, {}) : Icon ? /* @__PURE__ */ jsx(Icon, {}) : null, children]
606
+ })
607
+ });
608
+ },
609
+ Text: ({ asyncDebounceMs = 300, asyncValidate, "data-testid": testId, label, maxLength, multiline, name, ...props }) => {
610
+ const { form } = useField(name, "string");
611
+ return /* @__PURE__ */ jsx(form.Field, {
612
+ asyncDebounceMs,
613
+ name,
614
+ validators: asyncValidate ? { onChangeAsync: async ({ value }) => {
615
+ return await asyncValidate(value ?? "");
616
+ } } : void 0,
617
+ children: (f) => {
618
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, validating = f.state.meta.isValidating, C = multiline ? Textarea : Input, val = f.state.value ?? "", tid = testId ?? f.name, errorId = `${f.name}-error`;
619
+ return /* @__PURE__ */ jsxs(Field, {
620
+ "data-invalid": inv,
621
+ "data-testid": tid,
622
+ children: [
623
+ /* @__PURE__ */ jsxs("div", {
624
+ className: "flex items-center justify-between",
625
+ children: [label ? /* @__PURE__ */ jsx(FieldLabel, {
626
+ htmlFor: f.name,
627
+ children: label
628
+ }) : null, /* @__PURE__ */ jsxs("div", {
629
+ className: "flex items-center gap-2",
630
+ children: [validating ? /* @__PURE__ */ jsxs("div", {
631
+ className: "flex items-center gap-1 text-xs text-muted-foreground",
632
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: "Validating..." })]
633
+ }) : null, maxLength ? /* @__PURE__ */ jsxs("span", {
634
+ className: "text-xs text-muted-foreground",
635
+ children: [
636
+ String(val).length,
637
+ "/",
638
+ maxLength
639
+ ]
640
+ }) : null]
641
+ })]
642
+ }),
643
+ /* @__PURE__ */ jsx(C, {
644
+ "aria-describedby": inv ? errorId : void 0,
645
+ "aria-invalid": inv,
646
+ id: f.name,
647
+ maxLength,
648
+ name: f.name,
649
+ onBlur: f.handleBlur,
650
+ onChange: (e) => f.handleChange(e.target.value),
651
+ value: val,
652
+ ...props
653
+ }),
654
+ inv ? /* @__PURE__ */ jsx(FieldError, {
655
+ errors: f.state.meta.errors,
656
+ id: errorId
657
+ }) : null
658
+ ]
659
+ });
660
+ }
661
+ });
662
+ },
663
+ Timepick: ({ "data-testid": testId, label, name, placeholder = "HH:MM" }) => {
664
+ const { form } = useField(name, "string");
665
+ return /* @__PURE__ */ jsx(form.Field, {
666
+ name,
667
+ children: (f) => {
668
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`;
669
+ return /* @__PURE__ */ jsxs(Field, {
670
+ "data-invalid": inv,
671
+ "data-testid": tid,
672
+ children: [
673
+ label ? /* @__PURE__ */ jsx(FieldLabel, {
674
+ htmlFor: f.name,
675
+ children: label
676
+ }) : null,
677
+ /* @__PURE__ */ jsx(Input, {
678
+ "aria-describedby": inv ? errorId : void 0,
679
+ "aria-invalid": inv,
680
+ id: f.name,
681
+ name: f.name,
682
+ onBlur: f.handleBlur,
683
+ onChange: (e) => f.handleChange(e.target.value),
684
+ placeholder,
685
+ type: "time",
686
+ value: f.state.value ?? ""
687
+ }),
688
+ inv ? /* @__PURE__ */ jsx(FieldError, {
689
+ errors: f.state.meta.errors,
690
+ id: errorId
691
+ }) : null
692
+ ]
693
+ });
694
+ }
695
+ });
696
+ },
697
+ Toggle: ({ "data-testid": testId, falseLabel, name, trueLabel }) => {
698
+ const { form } = useField(name, "boolean");
699
+ return /* @__PURE__ */ jsx(form.Field, {
700
+ name,
701
+ children: (f) => {
702
+ const inv = f.state.meta.isTouched && !f.state.meta.isValid, tid = testId ?? f.name, errorId = `${f.name}-error`;
703
+ return /* @__PURE__ */ jsxs(Field, {
704
+ "data-invalid": inv,
705
+ "data-testid": tid,
706
+ children: [/* @__PURE__ */ jsxs("div", {
707
+ className: "flex items-center gap-2",
708
+ children: [/* @__PURE__ */ jsx(Switch, {
709
+ "aria-describedby": inv ? errorId : void 0,
710
+ "aria-invalid": inv,
711
+ checked: f.state.value ?? false,
712
+ id: f.name,
713
+ name: f.name,
714
+ onBlur: f.handleBlur,
715
+ onCheckedChange: (v) => f.handleChange(v)
716
+ }), /* @__PURE__ */ jsx(FieldLabel, {
717
+ htmlFor: f.name,
718
+ children: f.state.value ? trueLabel : falseLabel ?? trueLabel
719
+ })]
720
+ }), inv ? /* @__PURE__ */ jsx(FieldError, {
721
+ errors: f.state.meta.errors,
722
+ id: errorId
723
+ }) : null]
724
+ });
725
+ }
726
+ });
727
+ }
728
+ };
729
+
730
+ //#endregion
731
+ //#region src/components/form.tsx
732
+ const ConflictDialog = ({ conflict, onResolve }) => /* @__PURE__ */ jsx(Dialog, {
733
+ open: Boolean(conflict),
734
+ children: /* @__PURE__ */ jsxs(DialogContent, {
735
+ className: "[&>button]:hidden",
736
+ onEscapeKeyDown: () => onResolve("cancel"),
737
+ onInteractOutside: () => onResolve("cancel"),
738
+ children: [
739
+ /* @__PURE__ */ jsx("h2", {
740
+ className: "text-lg font-semibold",
741
+ children: "Conflict Detected"
742
+ }),
743
+ /* @__PURE__ */ jsx("p", {
744
+ className: "text-sm text-muted-foreground",
745
+ children: "This record was modified by someone else. Choose how to resolve the conflict."
746
+ }),
747
+ conflict?.current || conflict?.incoming ? /* @__PURE__ */ jsxs("div", {
748
+ className: "space-y-3",
749
+ children: [conflict.current ? /* @__PURE__ */ jsxs("div", {
750
+ className: "rounded-lg bg-muted p-3",
751
+ children: [/* @__PURE__ */ jsx("p", {
752
+ className: "mb-1 text-xs font-medium text-muted-foreground",
753
+ children: "Server version:"
754
+ }), /* @__PURE__ */ jsx("pre", {
755
+ className: "text-xs",
756
+ children: JSON.stringify(conflict.current, null, 2)
757
+ })]
758
+ }) : null, conflict.incoming ? /* @__PURE__ */ jsxs("div", {
759
+ className: "rounded-lg bg-muted p-3",
760
+ children: [/* @__PURE__ */ jsx("p", {
761
+ className: "mb-1 text-xs font-medium text-muted-foreground",
762
+ children: "Your version:"
763
+ }), /* @__PURE__ */ jsx("pre", {
764
+ className: "text-xs",
765
+ children: JSON.stringify(conflict.incoming, null, 2)
766
+ })]
767
+ }) : null]
768
+ }) : null,
769
+ /* @__PURE__ */ jsxs("div", {
770
+ className: "flex justify-end gap-2",
771
+ children: [
772
+ /* @__PURE__ */ jsx(Button, {
773
+ onClick: () => onResolve("cancel"),
774
+ variant: "outline",
775
+ children: "Cancel"
776
+ }),
777
+ /* @__PURE__ */ jsx(Button, {
778
+ onClick: () => onResolve("reload"),
779
+ variant: "outline",
780
+ children: "Reload"
781
+ }),
782
+ /* @__PURE__ */ jsx(Button, {
783
+ onClick: () => onResolve("overwrite"),
784
+ variant: "destructive",
785
+ children: "Overwrite"
786
+ })
787
+ ]
788
+ })
789
+ ]
790
+ })
791
+ });
792
+ const withGuard = (base) => {
793
+ const dirty = base.isDirty || base.isPending, guard = useNavigationGuard({ enabled: dirty });
794
+ useEffect(() => {
795
+ if (!dirty) return;
796
+ const h = (e) => {
797
+ e.preventDefault();
798
+ e.returnValue = "";
799
+ };
800
+ window.addEventListener("beforeunload", h);
801
+ return () => window.removeEventListener("beforeunload", h);
802
+ }, [dirty]);
803
+ return {
804
+ ...base,
805
+ guard
806
+ };
807
+ }, useForm = (opts) => withGuard(useForm$1(opts)), useFormMutation = (opts) => withGuard(useFormMutation$1(opts)), Form = ({ className, form: { conflict, error, guard, instance, meta, resolveConflict, schema }, render, showError = true }) => /* @__PURE__ */ jsxs(FormContext, {
808
+ value: {
809
+ form: instance,
810
+ meta,
811
+ schema
812
+ },
813
+ children: [
814
+ /* @__PURE__ */ jsxs("form", {
815
+ className,
816
+ onSubmit: (e) => {
817
+ e.preventDefault();
818
+ instance.handleSubmit();
819
+ },
820
+ children: [showError && error ? /* @__PURE__ */ jsx("p", {
821
+ className: "mb-4 rounded-lg bg-destructive/10 p-3 text-sm text-destructive",
822
+ role: "alert",
823
+ children: error.message
824
+ }) : null, render(fields)]
825
+ }),
826
+ /* @__PURE__ */ jsx(ConflictDialog, {
827
+ conflict,
828
+ onResolve: resolveConflict
829
+ }),
830
+ /* @__PURE__ */ jsx(Dialog, {
831
+ open: guard.active,
832
+ children: /* @__PURE__ */ jsxs(DialogContent, {
833
+ className: "[&>button]:hidden",
834
+ onEscapeKeyDown: guard.reject,
835
+ onInteractOutside: guard.reject,
836
+ children: [/* @__PURE__ */ jsx("p", { children: "You have unsaved changes. Are you sure you want to leave?" }), /* @__PURE__ */ jsxs("div", {
837
+ className: "flex justify-end gap-2",
838
+ children: [/* @__PURE__ */ jsx(Button, {
839
+ onClick: guard.reject,
840
+ variant: "outline",
841
+ children: "Cancel"
842
+ }), /* @__PURE__ */ jsx(Button, {
843
+ onClick: guard.accept,
844
+ variant: "destructive",
845
+ children: "Discard"
846
+ })]
847
+ })]
848
+ })
849
+ })
850
+ ]
851
+ }), AutoSaveIndicator = ({ lastSaved }) => {
852
+ const [, forceUpdate] = useState(0);
853
+ useEffect(() => {
854
+ if (!lastSaved) return;
855
+ const id = setInterval(() => forceUpdate((n) => n + 1), 1e4);
856
+ return () => clearInterval(id);
857
+ }, [lastSaved]);
858
+ if (!lastSaved) return null;
859
+ const ago = Math.round((Date.now() - lastSaved) / 1e3);
860
+ return /* @__PURE__ */ jsx("span", {
861
+ className: "text-xs text-muted-foreground",
862
+ children: ago < 5 ? "Saved" : `Saved ${ago}s ago`
863
+ });
864
+ };
865
+
866
+ //#endregion
867
+ //#region src/components/offline-indicator.tsx
868
+ const OfflineIndicator = () => {
869
+ if (useOnlineStatus()) return null;
870
+ return /* @__PURE__ */ jsx("div", {
871
+ className: "fixed bottom-4 left-4 z-50 rounded-md bg-destructive px-4 py-2 text-sm font-medium text-destructive-foreground shadow-lg",
872
+ children: "You are offline"
873
+ });
874
+ };
875
+
876
+ //#endregion
877
+ //#region src/components/org-avatar.tsx
878
+ const sizes = {
879
+ lg: "size-12",
880
+ md: "size-8",
881
+ sm: "size-6"
882
+ }, OrgAvatar = ({ avatarUrl, name, size = "md" }) => /* @__PURE__ */ jsxs(Avatar, {
883
+ className: sizes[size],
884
+ children: [avatarUrl ? /* @__PURE__ */ jsx(AvatarImage, { src: avatarUrl }) : null, /* @__PURE__ */ jsx(AvatarFallback, { children: name.slice(0, 2).toUpperCase() })]
885
+ });
886
+
887
+ //#endregion
888
+ //#region src/components/permission-guard.tsx
889
+ const PermissionGuard = ({ backHref, backLabel, canAccess, children, resource }) => {
890
+ if (!canAccess) return /* @__PURE__ */ jsxs("div", {
891
+ className: "flex flex-col items-center gap-4 py-12",
892
+ children: [
893
+ /* @__PURE__ */ jsx(Badge, {
894
+ variant: "secondary",
895
+ children: "View only"
896
+ }),
897
+ /* @__PURE__ */ jsxs("p", {
898
+ className: "text-muted-foreground",
899
+ children: [
900
+ "You don't have edit permission for this ",
901
+ resource,
902
+ "."
903
+ ]
904
+ }),
905
+ /* @__PURE__ */ jsx(Button, {
906
+ asChild: true,
907
+ variant: "outline",
908
+ children: /* @__PURE__ */ jsxs(Link, {
909
+ href: backHref,
910
+ children: ["Back to ", backLabel]
911
+ })
912
+ })
913
+ ]
914
+ });
915
+ return children;
916
+ };
917
+
918
+ //#endregion
919
+ //#region src/components/role-badge.tsx
920
+ const variants = {
921
+ admin: "secondary",
922
+ member: "outline",
923
+ owner: "default"
924
+ }, RoleBadge = ({ role }) => /* @__PURE__ */ jsx(Badge, {
925
+ variant: variants[role],
926
+ children: role
927
+ });
928
+
929
+ //#endregion
930
+ //#region src/components/suspense-wrap.tsx
931
+ var suspense_wrap_default = (f) => () => /* @__PURE__ */ jsx(Suspense, {
932
+ fallback: "",
933
+ children: f()
934
+ });
935
+
936
+ //#endregion
937
+ export { AutoSaveIndicator, ConflictDialog, EditorsSection, FileApiContext, FileApiProvider, FileFieldImpl, Form, FormContext, OfflineIndicator, OrgAvatar, PermissionGuard, RoleBadge, fields, suspense_wrap_default as suspenseWrap, useForm, useFormMutation };