@flaredev/cli 0.4.0 → 0.4.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/index.js +194 -92
- package/package.json +2 -2
- package/templates/app/app/dashboard/actions.ts +38 -5
- package/templates/app/app/globals.css +0 -6
- package/templates/app/app/layout.tsx +37 -28
- package/templates/app/components/dashboard/fields/file-field.tsx +5 -1
- package/templates/app/components/dashboard/fields/relation-field.tsx +25 -3
- package/templates/app/components/dashboard/import-dialog.tsx +2 -2
- package/templates/app/components/dashboard/record-actions.tsx +2 -2
- package/templates/app/components/dashboard/record-detail.tsx +5 -2
- package/templates/app/components/dashboard/resource-form-dialog.tsx +3 -3
- package/templates/app/components/dashboard/resource-form-page.tsx +64 -61
- package/templates/app/components/dashboard/resource-form.tsx +165 -165
- package/templates/app/components/dashboard/resource-table-toolbar.tsx +2 -2
- package/templates/app/components/dashboard/resource-table.tsx +8 -5
- package/templates/app/components/dashboard/row-actions.tsx +2 -2
- package/templates/locks/package-lock.json +9 -9
- package/templates/locks/pnpm-lock.yaml +12 -12
- package/templates/locks/yarn.lock +3 -3
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import Link from "next/link";
|
|
4
|
-
import { useRouter } from "next/navigation";
|
|
5
|
-
import { useMemo, useState, useTransition, type FormEvent } from "react";
|
|
6
|
-
import { createValidators, formValuesToInput, initialFormValues, issuesByField, storedFields, type
|
|
7
|
-
import { toast } from "sonner";
|
|
8
|
-
import { createRecordAction, updateRecordAction } from "@/app/dashboard/actions";
|
|
9
|
-
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
10
|
-
import { Button } from "@/components/ui/button";
|
|
11
|
-
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
|
12
|
-
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
13
|
-
import { Spinner } from "@/components/ui/spinner";
|
|
14
|
-
import { FieldWidget, type RelationMeta } from "./fields/field-widget";
|
|
15
|
-
|
|
16
|
-
export interface ResourceFormProps {
|
|
17
|
-
resource:
|
|
18
|
-
mode: "create" | "edit";
|
|
19
|
-
/** Record id (edit mode). */
|
|
20
|
-
id?: string;
|
|
21
|
-
record?: Record<string, unknown> | null;
|
|
22
|
-
/** belongsTo field key → related resource metadata and the current value's title. */
|
|
23
|
-
relations?: Record<string, RelationMeta & { initialTitle?: string }>;
|
|
24
|
-
/** Where Cancel and a successful save go, when the form owns the page. */
|
|
25
|
-
listHref: string;
|
|
26
|
-
/** In a dialog: close it instead of navigating, and drop the card around the fields. */
|
|
27
|
-
onDone?: () => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const WIDE = new Set(["text", "file"]);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Create/edit form rendered from field metadata. Values are validated in the browser
|
|
34
|
-
* with the descriptor's zod schemas, then again on the server by the same store the
|
|
35
|
-
* REST API uses; server errors (e.g. unique conflicts) land on their fields.
|
|
36
|
-
*/
|
|
37
|
-
export function ResourceForm({ resource, mode, id, record, relations = {}, listHref, onDone }: ResourceFormProps) {
|
|
38
|
-
const router = useRouter();
|
|
39
|
-
const validators = useMemo(() => createValidators(resource), [resource]);
|
|
40
|
-
const fields = useMemo(() => storedFields(resource), [resource]);
|
|
41
|
-
const [values, setValues] = useState(() => initialFormValues(resource, record));
|
|
42
|
-
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
43
|
-
const [pending, startTransition] = useTransition();
|
|
44
|
-
|
|
45
|
-
const setValue = (key: string, value: string | boolean) => {
|
|
46
|
-
setValues((current) => ({ ...current, [key]: value }));
|
|
47
|
-
if (errors[key]) setErrors(({ [key]: _removed, ...rest }) => rest);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
function focusFirstError(fieldErrors: Record<string, string>) {
|
|
51
|
-
const first = fields.find(([key]) => fieldErrors[key]);
|
|
52
|
-
if (first) document.getElementById(`field-${first[0]}`)?.focus();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function onSubmit(event: FormEvent) {
|
|
56
|
-
event.preventDefault();
|
|
57
|
-
const input = formValuesToInput(resource, values, mode);
|
|
58
|
-
const parsed = (mode === "create" ? validators.create : validators.update).safeParse(input);
|
|
59
|
-
if (!parsed.success) {
|
|
60
|
-
const fieldErrors = issuesByField(parsed.error.issues);
|
|
61
|
-
setErrors(fieldErrors);
|
|
62
|
-
focusFirstError(fieldErrors);
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
startTransition(async () => {
|
|
67
|
-
const result = mode === "create" ? await createRecordAction(resource.name, input) : await updateRecordAction(resource.name, id!, input);
|
|
68
|
-
if (result.ok) {
|
|
69
|
-
toast.success(`${resource.label} ${mode === "create" ? "created" : "saved"}.`);
|
|
70
|
-
if (onDone) onDone();
|
|
71
|
-
else router.push(listHref);
|
|
72
|
-
router.refresh();
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const fieldErrors = result.issues ? issuesByField(result.issues) : result.field ? { [result.field]: result.error } : { _form: result.error };
|
|
76
|
-
setErrors(fieldErrors);
|
|
77
|
-
focusFirstError(fieldErrors);
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const Frame = onDone ? DialogFrame : Card;
|
|
82
|
-
const Body = onDone ? DialogBody : CardContent;
|
|
83
|
-
const Footer = onDone ? DialogFooterRow : CardFooter;
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<form onSubmit={onSubmit} noValidate>
|
|
87
|
-
<Frame>
|
|
88
|
-
<Body>
|
|
89
|
-
{errors._form && (
|
|
90
|
-
<Alert variant="destructive" className="mb-6">
|
|
91
|
-
<AlertDescription>{errors._form}</AlertDescription>
|
|
92
|
-
</Alert>
|
|
93
|
-
)}
|
|
94
|
-
<FieldGroup className="grid gap-6 md:grid-cols-2">
|
|
95
|
-
{fields.map(([key, def]) => {
|
|
96
|
-
const error = errors[key];
|
|
97
|
-
const widgetId = `field-${key}`;
|
|
98
|
-
const horizontal = def.kind === "boolean";
|
|
99
|
-
return (
|
|
100
|
-
<Field
|
|
101
|
-
key={key}
|
|
102
|
-
data-invalid={error ? true : undefined}
|
|
103
|
-
className={WIDE.has(def.kind) ? "md:col-span-2" : undefined}
|
|
104
|
-
>
|
|
105
|
-
<FieldLabel htmlFor={widgetId}>
|
|
106
|
-
{def.label}
|
|
107
|
-
{def.required && def.kind !== "boolean" && (
|
|
108
|
-
<span className="ml-0.5 text-muted-foreground" aria-hidden>
|
|
109
|
-
*
|
|
110
|
-
</span>
|
|
111
|
-
)}
|
|
112
|
-
</FieldLabel>
|
|
113
|
-
<div className={horizontal ? "flex h-9 items-center" : undefined}>
|
|
114
|
-
<FieldWidget
|
|
115
|
-
id={widgetId}
|
|
116
|
-
name={key}
|
|
117
|
-
resourceName={resource.name}
|
|
118
|
-
fieldKey={key}
|
|
119
|
-
field={def}
|
|
120
|
-
value={values[key] ?? ""}
|
|
121
|
-
onChange={(value) => setValue(key, value)}
|
|
122
|
-
invalid={Boolean(error)}
|
|
123
|
-
disabled={pending}
|
|
124
|
-
relation={relations[key]}
|
|
125
|
-
/>
|
|
126
|
-
</div>
|
|
127
|
-
{def.helpText && <FieldDescription>{def.helpText}</FieldDescription>}
|
|
128
|
-
{error && <FieldError>{error}</FieldError>}
|
|
129
|
-
</Field>
|
|
130
|
-
);
|
|
131
|
-
})}
|
|
132
|
-
</FieldGroup>
|
|
133
|
-
</Body>
|
|
134
|
-
<Footer className="flex justify-end gap-2 border-t pt-4">
|
|
135
|
-
{onDone ? (
|
|
136
|
-
<Button type="button" variant="outline" onClick={onDone} disabled={pending}>
|
|
137
|
-
Cancel
|
|
138
|
-
</Button>
|
|
139
|
-
) : (
|
|
140
|
-
<Button variant="outline" asChild>
|
|
141
|
-
<Link href={listHref}>Cancel</Link>
|
|
142
|
-
</Button>
|
|
143
|
-
)}
|
|
144
|
-
<Button type="submit" disabled={pending}>
|
|
145
|
-
{pending && <Spinner data-icon="inline-start" />}
|
|
146
|
-
{mode === "create" ? `Create ${resource.label.toLowerCase()}` : "Save changes"}
|
|
147
|
-
</Button>
|
|
148
|
-
</Footer>
|
|
149
|
-
</Frame>
|
|
150
|
-
</form>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/** Plain wrappers so the same form works in a page (a card) and in a dialog (no card). */
|
|
155
|
-
function DialogFrame({ children }: { children: React.ReactNode }) {
|
|
156
|
-
return <div className="flex flex-col gap-4">{children}</div>;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function DialogBody({ children }: { children: React.ReactNode }) {
|
|
160
|
-
return <div className="max-h-[60vh] overflow-y-auto px-1">{children}</div>;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function DialogFooterRow({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
164
|
-
return <div className={className}>{children}</div>;
|
|
165
|
-
}
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from "next/link";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { useMemo, useState, useTransition, type FormEvent } from "react";
|
|
6
|
+
import { createValidators, formValuesToInput, initialFormValues, issuesByField, storedFields, type ClientResource } from "@flaredev/core";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
import { createRecordAction, updateRecordAction } from "@/app/dashboard/actions";
|
|
9
|
+
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
10
|
+
import { Button } from "@/components/ui/button";
|
|
11
|
+
import { Card, CardContent, CardFooter } from "@/components/ui/card";
|
|
12
|
+
import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field";
|
|
13
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
14
|
+
import { FieldWidget, type RelationMeta } from "./fields/field-widget";
|
|
15
|
+
|
|
16
|
+
export interface ResourceFormProps {
|
|
17
|
+
resource: ClientResource;
|
|
18
|
+
mode: "create" | "edit";
|
|
19
|
+
/** Record id (edit mode). */
|
|
20
|
+
id?: string;
|
|
21
|
+
record?: Record<string, unknown> | null;
|
|
22
|
+
/** belongsTo field key → related resource metadata and the current value's title. */
|
|
23
|
+
relations?: Record<string, RelationMeta & { initialTitle?: string }>;
|
|
24
|
+
/** Where Cancel and a successful save go, when the form owns the page. */
|
|
25
|
+
listHref: string;
|
|
26
|
+
/** In a dialog: close it instead of navigating, and drop the card around the fields. */
|
|
27
|
+
onDone?: () => void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const WIDE = new Set(["text", "file"]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Create/edit form rendered from field metadata. Values are validated in the browser
|
|
34
|
+
* with the descriptor's zod schemas, then again on the server by the same store the
|
|
35
|
+
* REST API uses; server errors (e.g. unique conflicts) land on their fields.
|
|
36
|
+
*/
|
|
37
|
+
export function ResourceForm({ resource, mode, id, record, relations = {}, listHref, onDone }: ResourceFormProps) {
|
|
38
|
+
const router = useRouter();
|
|
39
|
+
const validators = useMemo(() => createValidators(resource), [resource]);
|
|
40
|
+
const fields = useMemo(() => storedFields(resource), [resource]);
|
|
41
|
+
const [values, setValues] = useState(() => initialFormValues(resource, record));
|
|
42
|
+
const [errors, setErrors] = useState<Record<string, string>>({});
|
|
43
|
+
const [pending, startTransition] = useTransition();
|
|
44
|
+
|
|
45
|
+
const setValue = (key: string, value: string | boolean) => {
|
|
46
|
+
setValues((current) => ({ ...current, [key]: value }));
|
|
47
|
+
if (errors[key]) setErrors(({ [key]: _removed, ...rest }) => rest);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function focusFirstError(fieldErrors: Record<string, string>) {
|
|
51
|
+
const first = fields.find(([key]) => fieldErrors[key]);
|
|
52
|
+
if (first) document.getElementById(`field-${first[0]}`)?.focus();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function onSubmit(event: FormEvent) {
|
|
56
|
+
event.preventDefault();
|
|
57
|
+
const input = formValuesToInput(resource, values, mode);
|
|
58
|
+
const parsed = (mode === "create" ? validators.create : validators.update).safeParse(input);
|
|
59
|
+
if (!parsed.success) {
|
|
60
|
+
const fieldErrors = issuesByField(parsed.error.issues);
|
|
61
|
+
setErrors(fieldErrors);
|
|
62
|
+
focusFirstError(fieldErrors);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
startTransition(async () => {
|
|
67
|
+
const result = mode === "create" ? await createRecordAction(resource.name, input) : await updateRecordAction(resource.name, id!, input);
|
|
68
|
+
if (result.ok) {
|
|
69
|
+
toast.success(`${resource.label} ${mode === "create" ? "created" : "saved"}.`);
|
|
70
|
+
if (onDone) onDone();
|
|
71
|
+
else router.push(listHref);
|
|
72
|
+
router.refresh();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const fieldErrors = result.issues ? issuesByField(result.issues) : result.field ? { [result.field]: result.error } : { _form: result.error };
|
|
76
|
+
setErrors(fieldErrors);
|
|
77
|
+
focusFirstError(fieldErrors);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const Frame = onDone ? DialogFrame : Card;
|
|
82
|
+
const Body = onDone ? DialogBody : CardContent;
|
|
83
|
+
const Footer = onDone ? DialogFooterRow : CardFooter;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<form onSubmit={onSubmit} noValidate>
|
|
87
|
+
<Frame>
|
|
88
|
+
<Body>
|
|
89
|
+
{errors._form && (
|
|
90
|
+
<Alert variant="destructive" className="mb-6">
|
|
91
|
+
<AlertDescription>{errors._form}</AlertDescription>
|
|
92
|
+
</Alert>
|
|
93
|
+
)}
|
|
94
|
+
<FieldGroup className="grid gap-6 md:grid-cols-2">
|
|
95
|
+
{fields.map(([key, def]) => {
|
|
96
|
+
const error = errors[key];
|
|
97
|
+
const widgetId = `field-${key}`;
|
|
98
|
+
const horizontal = def.kind === "boolean";
|
|
99
|
+
return (
|
|
100
|
+
<Field
|
|
101
|
+
key={key}
|
|
102
|
+
data-invalid={error ? true : undefined}
|
|
103
|
+
className={WIDE.has(def.kind) ? "md:col-span-2" : undefined}
|
|
104
|
+
>
|
|
105
|
+
<FieldLabel htmlFor={widgetId}>
|
|
106
|
+
{def.label}
|
|
107
|
+
{def.required && def.kind !== "boolean" && (
|
|
108
|
+
<span className="ml-0.5 text-muted-foreground" aria-hidden>
|
|
109
|
+
*
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</FieldLabel>
|
|
113
|
+
<div className={horizontal ? "flex h-9 items-center" : undefined}>
|
|
114
|
+
<FieldWidget
|
|
115
|
+
id={widgetId}
|
|
116
|
+
name={key}
|
|
117
|
+
resourceName={resource.name}
|
|
118
|
+
fieldKey={key}
|
|
119
|
+
field={def}
|
|
120
|
+
value={values[key] ?? ""}
|
|
121
|
+
onChange={(value) => setValue(key, value)}
|
|
122
|
+
invalid={Boolean(error)}
|
|
123
|
+
disabled={pending}
|
|
124
|
+
relation={relations[key]}
|
|
125
|
+
/>
|
|
126
|
+
</div>
|
|
127
|
+
{def.helpText && <FieldDescription>{def.helpText}</FieldDescription>}
|
|
128
|
+
{error && <FieldError>{error}</FieldError>}
|
|
129
|
+
</Field>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
</FieldGroup>
|
|
133
|
+
</Body>
|
|
134
|
+
<Footer className="flex justify-end gap-2 border-t pt-4">
|
|
135
|
+
{onDone ? (
|
|
136
|
+
<Button type="button" variant="outline" onClick={onDone} disabled={pending}>
|
|
137
|
+
Cancel
|
|
138
|
+
</Button>
|
|
139
|
+
) : (
|
|
140
|
+
<Button variant="outline" asChild>
|
|
141
|
+
<Link href={listHref}>Cancel</Link>
|
|
142
|
+
</Button>
|
|
143
|
+
)}
|
|
144
|
+
<Button type="submit" disabled={pending}>
|
|
145
|
+
{pending && <Spinner data-icon="inline-start" />}
|
|
146
|
+
{mode === "create" ? `Create ${resource.label.toLowerCase()}` : "Save changes"}
|
|
147
|
+
</Button>
|
|
148
|
+
</Footer>
|
|
149
|
+
</Frame>
|
|
150
|
+
</form>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Plain wrappers so the same form works in a page (a card) and in a dialog (no card). */
|
|
155
|
+
function DialogFrame({ children }: { children: React.ReactNode }) {
|
|
156
|
+
return <div className="flex flex-col gap-4">{children}</div>;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function DialogBody({ children }: { children: React.ReactNode }) {
|
|
160
|
+
return <div className="max-h-[60vh] overflow-y-auto px-1">{children}</div>;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function DialogFooterRow({ children, className }: { children: React.ReactNode; className?: string }) {
|
|
164
|
+
return <div className={className}>{children}</div>;
|
|
165
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
|
4
4
|
import { useState, type FormEvent } from "react";
|
|
5
5
|
import { SearchIcon, XIcon } from "lucide-react";
|
|
6
|
-
import { optionLabel, storedFields, type
|
|
6
|
+
import { optionLabel, storedFields, type ClientResource } from "@flaredev/core";
|
|
7
7
|
import { Button } from "@/components/ui/button";
|
|
8
8
|
import { InputGroup, InputGroupAddon, InputGroupInput } from "@/components/ui/input-group";
|
|
9
9
|
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
@@ -12,7 +12,7 @@ import { hrefWith } from "./query";
|
|
|
12
12
|
const ALL = "__all__";
|
|
13
13
|
|
|
14
14
|
/** Search box and enum/boolean filters for ResourceTable; all state goes into the URL. */
|
|
15
|
-
export function ResourceTableToolbar({ resource }: { resource:
|
|
15
|
+
export function ResourceTableToolbar({ resource }: { resource: ClientResource }) {
|
|
16
16
|
const router = useRouter();
|
|
17
17
|
const pathname = usePathname();
|
|
18
18
|
const params = new URLSearchParams(useSearchParams().toString());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
2
|
import { ArrowDownIcon, ArrowUpIcon, ChevronLeftIcon, ChevronRightIcon, ChevronsUpDownIcon, PlusIcon } from "lucide-react";
|
|
3
|
-
import { formatValue, optionLabel, statusTone, storedFields, type Resource, type StoredField } from "@flaredev/core";
|
|
3
|
+
import { clientResource, formatValue, optionLabel, statusTone, storedFields, type Resource, type StoredField } from "@flaredev/core";
|
|
4
4
|
import { isSortable } from "@flaredev/core/server";
|
|
5
5
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
6
6
|
import { Badge } from "@/components/ui/badge";
|
|
@@ -44,6 +44,9 @@ const NUMERIC = new Set(["int", "float"]);
|
|
|
44
44
|
*/
|
|
45
45
|
export async function ResourceTable({ resource, searchParams }: { resource: Resource; searchParams: SearchParams }) {
|
|
46
46
|
await requireAccess(resource, "read");
|
|
47
|
+
// Hooks and computed values are functions, and a function can't be sent to the browser.
|
|
48
|
+
// This is the same resource without them, for the client components below.
|
|
49
|
+
const forClient = clientResource(resource);
|
|
47
50
|
const basePath = resourcePath(resource);
|
|
48
51
|
const permissions = await adminPermissions(resource.name);
|
|
49
52
|
const params = toSearchParams(searchParams);
|
|
@@ -102,15 +105,15 @@ export async function ResourceTable({ resource, searchParams }: { resource: Reso
|
|
|
102
105
|
<TableSelection ids={rowIds}>
|
|
103
106
|
<div className="flex flex-col gap-4">
|
|
104
107
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
105
|
-
<ResourceTableToolbar resource={
|
|
108
|
+
<ResourceTableToolbar resource={forClient} />
|
|
106
109
|
<div className="flex flex-wrap items-center gap-2">
|
|
107
110
|
<SaveViewButton resourceName={resource.name} label={resource.pluralLabel} />
|
|
108
111
|
<ColumnMenu columns={allColumns.map(({ key, label }) => ({ key, label }))} visible={columns.map((column) => column.key)} />
|
|
109
112
|
<ExportButton resourceName={resource.name} pluralLabel={resource.pluralLabel} />
|
|
110
|
-
{permissions.create && <ImportDialog resource={
|
|
113
|
+
{permissions.create && <ImportDialog resource={forClient} />}
|
|
111
114
|
{permissions.create &&
|
|
112
115
|
(modalForms ? (
|
|
113
|
-
<NewRecordButton resource={
|
|
116
|
+
<NewRecordButton resource={forClient} relations={relations} listHref={basePath} />
|
|
114
117
|
) : (
|
|
115
118
|
<Button asChild>
|
|
116
119
|
<Link href={resourcePath(resource, "new")}>
|
|
@@ -281,7 +284,7 @@ export async function ResourceTable({ resource, searchParams }: { resource: Reso
|
|
|
281
284
|
})}
|
|
282
285
|
<TableCell className="text-right">
|
|
283
286
|
<RowActions
|
|
284
|
-
resource={
|
|
287
|
+
resource={forClient}
|
|
285
288
|
id={id}
|
|
286
289
|
record={modalForms ? row : undefined}
|
|
287
290
|
relations={relations}
|
|
@@ -5,7 +5,7 @@ import { useRouter } from "next/navigation";
|
|
|
5
5
|
import { useState, useTransition } from "react";
|
|
6
6
|
import { ExternalLinkIcon, MoreHorizontalIcon, PencilIcon, Trash2Icon } from "lucide-react";
|
|
7
7
|
import { toast } from "sonner";
|
|
8
|
-
import type {
|
|
8
|
+
import type { ClientResource } from "@flaredev/core";
|
|
9
9
|
import { deleteRecordAction } from "@/app/dashboard/actions";
|
|
10
10
|
import {
|
|
11
11
|
AlertDialog,
|
|
@@ -43,7 +43,7 @@ export function RowActions({
|
|
|
43
43
|
canUpdate = true,
|
|
44
44
|
canDelete = true,
|
|
45
45
|
}: {
|
|
46
|
-
resource:
|
|
46
|
+
resource: ClientResource;
|
|
47
47
|
id: string;
|
|
48
48
|
/** The record itself, when the form opens in a dialog. */
|
|
49
49
|
record?: Record<string, unknown>;
|
|
@@ -4973,9 +4973,9 @@
|
|
|
4973
4973
|
}
|
|
4974
4974
|
},
|
|
4975
4975
|
"node_modules/@vitejs/plugin-rsc/node_modules/magic-string": {
|
|
4976
|
-
"version": "1.4.
|
|
4977
|
-
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.
|
|
4978
|
-
"integrity": "sha512-
|
|
4976
|
+
"version": "1.4.2",
|
|
4977
|
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.2.tgz",
|
|
4978
|
+
"integrity": "sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==",
|
|
4979
4979
|
"devOptional": true,
|
|
4980
4980
|
"license": "MIT",
|
|
4981
4981
|
"dependencies": {
|
|
@@ -6253,9 +6253,9 @@
|
|
|
6253
6253
|
}
|
|
6254
6254
|
},
|
|
6255
6255
|
"node_modules/electron-to-chromium": {
|
|
6256
|
-
"version": "1.5.
|
|
6257
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
6258
|
-
"integrity": "sha512-
|
|
6256
|
+
"version": "1.5.438",
|
|
6257
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.438.tgz",
|
|
6258
|
+
"integrity": "sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==",
|
|
6259
6259
|
"license": "ISC",
|
|
6260
6260
|
"peer": true
|
|
6261
6261
|
},
|
|
@@ -7124,9 +7124,9 @@
|
|
|
7124
7124
|
"license": "MIT"
|
|
7125
7125
|
},
|
|
7126
7126
|
"node_modules/node-releases": {
|
|
7127
|
-
"version": "2.0.
|
|
7128
|
-
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.
|
|
7129
|
-
"integrity": "sha512-
|
|
7127
|
+
"version": "2.0.57",
|
|
7128
|
+
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.57.tgz",
|
|
7129
|
+
"integrity": "sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==",
|
|
7130
7130
|
"license": "MIT",
|
|
7131
7131
|
"peer": true,
|
|
7132
7132
|
"engines": {
|
|
@@ -2640,8 +2640,8 @@ packages:
|
|
|
2640
2640
|
sqlite3:
|
|
2641
2641
|
optional: true
|
|
2642
2642
|
|
|
2643
|
-
electron-to-chromium@1.5.
|
|
2644
|
-
resolution: {integrity: sha512-
|
|
2643
|
+
electron-to-chromium@1.5.438:
|
|
2644
|
+
resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==}
|
|
2645
2645
|
|
|
2646
2646
|
emoji-regex-xs@2.0.1:
|
|
2647
2647
|
resolution: {integrity: sha512-1QFuh8l7LqUcKe24LsPUNzjrzJQ7pgRwp1QMcZ5MX6mFplk2zQ08NVCM84++1cveaUUYtcCYHmeFEuNg16sU4g==}
|
|
@@ -2950,8 +2950,8 @@ packages:
|
|
|
2950
2950
|
magic-string@0.30.21:
|
|
2951
2951
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
|
2952
2952
|
|
|
2953
|
-
magic-string@1.4.
|
|
2954
|
-
resolution: {integrity: sha512-
|
|
2953
|
+
magic-string@1.4.2:
|
|
2954
|
+
resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==}
|
|
2955
2955
|
|
|
2956
2956
|
merge-stream@2.0.0:
|
|
2957
2957
|
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
|
|
@@ -3039,8 +3039,8 @@ packages:
|
|
|
3039
3039
|
neo-async@2.6.2:
|
|
3040
3040
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
|
3041
3041
|
|
|
3042
|
-
node-releases@2.0.
|
|
3043
|
-
resolution: {integrity: sha512-
|
|
3042
|
+
node-releases@2.0.57:
|
|
3043
|
+
resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==}
|
|
3044
3044
|
engines: {node: '>=18'}
|
|
3045
3045
|
|
|
3046
3046
|
obug@3.0.0:
|
|
@@ -5214,7 +5214,7 @@ snapshots:
|
|
|
5214
5214
|
'@rolldown/pluginutils': 1.0.1
|
|
5215
5215
|
es-module-lexer: 2.3.2
|
|
5216
5216
|
estree-walker: 3.0.3
|
|
5217
|
-
magic-string: 1.4.
|
|
5217
|
+
magic-string: 1.4.2
|
|
5218
5218
|
obug: 3.0.0
|
|
5219
5219
|
react: 19.3.0
|
|
5220
5220
|
react-dom: 19.3.0(react@19.3.0)
|
|
@@ -5388,8 +5388,8 @@ snapshots:
|
|
|
5388
5388
|
dependencies:
|
|
5389
5389
|
baseline-browser-mapping: 2.11.25
|
|
5390
5390
|
caniuse-lite: 1.0.30001810
|
|
5391
|
-
electron-to-chromium: 1.5.
|
|
5392
|
-
node-releases: 2.0.
|
|
5391
|
+
electron-to-chromium: 1.5.438
|
|
5392
|
+
node-releases: 2.0.57
|
|
5393
5393
|
update-browserslist-db: 1.3.3(browserslist@4.29.0)
|
|
5394
5394
|
|
|
5395
5395
|
buffer-from@1.1.2: {}
|
|
@@ -5459,7 +5459,7 @@ snapshots:
|
|
|
5459
5459
|
optionalDependencies:
|
|
5460
5460
|
kysely: 0.29.6
|
|
5461
5461
|
|
|
5462
|
-
electron-to-chromium@1.5.
|
|
5462
|
+
electron-to-chromium@1.5.438: {}
|
|
5463
5463
|
|
|
5464
5464
|
emoji-regex-xs@2.0.1: {}
|
|
5465
5465
|
|
|
@@ -5780,7 +5780,7 @@ snapshots:
|
|
|
5780
5780
|
dependencies:
|
|
5781
5781
|
'@jridgewell/sourcemap-codec': 1.6.0
|
|
5782
5782
|
|
|
5783
|
-
magic-string@1.4.
|
|
5783
|
+
magic-string@1.4.2:
|
|
5784
5784
|
dependencies:
|
|
5785
5785
|
'@jridgewell/sourcemap-codec': 1.6.0
|
|
5786
5786
|
|
|
@@ -5827,7 +5827,7 @@ snapshots:
|
|
|
5827
5827
|
|
|
5828
5828
|
neo-async@2.6.2: {}
|
|
5829
5829
|
|
|
5830
|
-
node-releases@2.0.
|
|
5830
|
+
node-releases@2.0.57: {}
|
|
5831
5831
|
|
|
5832
5832
|
obug@3.0.0: {}
|
|
5833
5833
|
|
|
@@ -2879,9 +2879,9 @@ magic-string@^0.30.11, magic-string@^0.30.21:
|
|
|
2879
2879
|
"@jridgewell/sourcemap-codec" "^1.5.5"
|
|
2880
2880
|
|
|
2881
2881
|
magic-string@^1.2.3:
|
|
2882
|
-
version "1.4.
|
|
2883
|
-
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.
|
|
2884
|
-
integrity sha512-
|
|
2882
|
+
version "1.4.2"
|
|
2883
|
+
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-1.4.2.tgz#508aef4aac7319496dc074ec60ea511c49daf138"
|
|
2884
|
+
integrity sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==
|
|
2885
2885
|
dependencies:
|
|
2886
2886
|
"@jridgewell/sourcemap-codec" "^1.6.0"
|
|
2887
2887
|
|