Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions apps/docs/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
24 changes: 24 additions & 0 deletions apps/docs/pages/docs/i18n.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,30 @@ The following keys are accepted:
"The text displayed in the multiselect widget in list display mode to toggle the select dialog",
defaultValue: '"Select items"',
},
{
name: "form.widgets.multiselect.create",
description:
"The text displayed in the multiselect widget create button when allowCreate is enabled",
defaultValue: '"Create item"',
},
{
name: "form.widgets.select.create",
description:
"The text displayed in the select widget create button when allowCreate is enabled",
defaultValue: '"Create item"',
},
{
name: "form.widgets.multiselect.edit",
description:
"The text displayed in the multiselect widget edit button when allowEdit is enabled",
defaultValue: '"Edit item"',
},
{
name: "form.widgets.select.edit",
description:
"The text displayed in the select widget edit button when allowEdit is enabled",
defaultValue: '"Edit item"',
},
{
name: "form.widgets.scalar_array.add",
description:
Expand Down
6 changes: 6 additions & 0 deletions packages/examples-common/messages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ export default {
},
multiselect: {
select: "Sélectionner",
create: "Créer un élément",
edit: "Modifier un élément",
},
select: {
create: "Créer un élément",
edit: "Modifier un élément",
},
},
user: {
Expand Down
4 changes: 4 additions & 0 deletions packages/examples-common/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const createOptions = (
},
posts: {
display: "table",
allowCreate: true,
},
avatar: {
format: "file",
Expand Down Expand Up @@ -297,6 +298,7 @@ export const createOptions = (
display: "list",
orderField: "order",
relationshipSearchField: "category",
allowCreate: true,
},
images: {
format: "file",
Expand Down Expand Up @@ -355,6 +357,8 @@ export const createOptions = (
display: "list",
relationshipSearchField: "post",
orderField: "order",
allowCreate: true,
allowEdit: true,
},
},
},
Expand Down
3 changes: 2 additions & 1 deletion packages/examples-common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"scripts": {
"dev": "tsc -w",
"build": "tsc",
"clean": "rm -rf dist"
"clean": "rm -rf dist",
"typecheck": "tsc --noEmit"
},
"exports": {
"./components": "./dist/components/index.js",
Expand Down
93 changes: 90 additions & 3 deletions packages/next-admin/src/appHandler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import cloneDeep from "lodash.clonedeep";
import { createEdgeRouter } from "next-connect";
import { HookError } from "./exceptions/HookError";
import { handleOptionsSearch } from "./handlers/options";
Expand All @@ -12,17 +13,18 @@ import {
RequestContext,
ServerAction,
} from "./types";
import { getSchema, initGlobals } from "./utils/globals";
import { getSchemaForResource, getSchemas } from "./utils/jsonSchema";
import { hasPermission } from "./utils/permissions";
import { getRawData } from "./utils/prisma";
import { getDataItem, getRawData } from "./utils/prisma";
import {
formatId,
getFormValuesFromFormData,
getModelIdProperty,
getResourceFromParams,
getResources,
transformSchema,
} from "./utils/server";
import { getSchema, initGlobals } from "./utils/globals";
import { PrismaClient } from "./types-prisma";

export const createHandler = <P extends string = "nextadmin">({
apiBasePath,
Expand Down Expand Up @@ -52,6 +54,88 @@ export const createHandler = <P extends string = "nextadmin">({
}

router
.get(`${apiBasePath}/:model/schema/:id?`, async (req, ctx) => {
try {
const resources = getResources(options);
const params = await ctx.params;
const resource = getResourceFromParams(
[params[paramKey][0]],
resources
);

if (!resource) {
return Response.json(
{ error: "Resource not found" },
{ status: 404 }
);
}

const id =
params[paramKey].length > 2 ? params[paramKey][2] : undefined;
const edit = options?.model?.[resource]?.edit;

let deepCopySchema = await transformSchema(
resource,
//@ts-expect-error
edit,
options
)(cloneDeep(getSchema()));

const resourceSchema = getSchemaForResource(deepCopySchema, resource);

if (id) {
const formattedId = formatId(resource, id);

const { data, relationshipsRawData } = await getDataItem({
prisma,
resource,
resourceId: formattedId,
options,
});

const { uiSchema, schema } = getSchemas(
data,
resourceSchema,
edit?.fields as EditFieldsOptions<typeof resource>
);

return Response.json({
data,
modelSchema: schema,
uiSchema,
relationshipsRawData,
resource,
});
}

const relationshipsRawData = await getRawData({
prisma,
resource,
resourceIds: [],
maxDepth: 2,
});

const { uiSchema, schema } = getSchemas(
null,
resourceSchema,
edit?.fields as EditFieldsOptions<typeof resource>
);

return Response.json({
data: null,
modelSchema: schema,
uiSchema,
relationshipsRawData,
resource,
});
} catch (e) {
console.error("Error in GET schema endpoint:", e);
return Response.json(
{ error: (e as Error)?.message || "Unknown error occurred" },
{ status: 500 }
);
}
})
.get(`${apiBasePath}/:model/raw`, async (req, ctx) => {
const resources = getResources(options);
const params = await ctx.params;
Expand Down Expand Up @@ -89,6 +173,9 @@ export const createHandler = <P extends string = "nextadmin">({

return Response.json(data);
})
.get(`${apiBasePath}/:model/:id`, async (req, ctx) => {
return Response.json({ error: "Not implemented" }, { status: 200 });
})
.post(`${apiBasePath}/:model/actions/:id`, async (req, ctx) => {
const resources = getResources(options);
const params = await ctx.params;
Expand Down
41 changes: 31 additions & 10 deletions packages/next-admin/src/components/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
InformationCircleIcon,
TrashIcon,
} from "@heroicons/react/24/outline";
import RjsfForm from "@rjsf/core";
import type { FormProps as RjsfFormProps } from "@rjsf/core";
import RjsfForm from "@rjsf/core";
import {
BaseInputTemplateProps,
ErrorSchema,
Expand Down Expand Up @@ -48,6 +48,7 @@
Permission,
} from "../types";
import { getSchemas } from "../utils/jsonSchema";
import { getSubmitButtonOptions } from "../utils/rjsf";
import { formatLabel, isFileUploadFormat, slugify } from "../utils/tools";
import FormHeader from "./FormHeader";
import ArrayField from "./inputs/ArrayField";
Expand All @@ -69,7 +70,6 @@
TooltipRoot,
TooltipTrigger,
} from "./radix/Tooltip";
import { getSubmitButtonOptions } from "../utils/rjsf";

const RichTextField = lazy(() => import("./inputs/RichText/RichTextField"));

Expand All @@ -82,12 +82,14 @@
TextareaWidget: TextareaWidget,
};

const Form = ({
export const Form = ({
data,
schema,
resource,
validation: validationProp,
customInputs,
onSubmitCallback,
isEmbedded,
}: FormProps) => {
const [validation, setValidation] = useState(validationProp);
const { basePath, options, apiBasePath } = useConfig();
Expand Down Expand Up @@ -203,7 +205,7 @@
</div>
);
},
[isPending, id]

Check warning on line 208 in packages/next-admin/src/components/Form.tsx

View workflow job for this annotation

GitHub Actions / start

React Hook useMemo has missing dependencies: 'basePath', 'canCreate', 'canDelete', 'canEdit', 'edit', 'resource', 'router', 'runSingleDeletion', 'showMessage', and 't'. Either include them or remove the dependency array
);

const extraErrors: ErrorSchema | undefined = validation?.reduce(
Expand All @@ -229,6 +231,7 @@
body: formData,
}
);
debugger;
const result = await response.json();
if (result?.validation) {
setValidation(result.validation);
Expand All @@ -240,6 +243,12 @@
cleanAll();
}
if (result?.deleted) {

if (onSubmitCallback) {
onSubmitCallback(result);
return;
}

return router.replace({
pathname: `${basePath}/${slugify(resource)}`,
query: {
Expand All @@ -251,6 +260,12 @@
});
}
if (result?.created) {

if (onSubmitCallback) {
onSubmitCallback(result);
return;
}

const pathname = result?.redirect
? `${basePath}/${slugify(resource)}`
: `${basePath}/${slugify(resource)}/${result.createdId}`;
Expand All @@ -266,6 +281,12 @@
});
}
if (result?.updated) {

if (onSubmitCallback) {
onSubmitCallback(result);
return;
}

const pathname = result?.redirect
? `${basePath}/${slugify(resource)}`
: location.pathname;
Expand Down Expand Up @@ -297,7 +318,7 @@
setIsPending(false);
}
},
[apiBasePath, id]

Check warning on line 321 in packages/next-admin/src/components/Form.tsx

View workflow job for this annotation

GitHub Actions / start

React Hook useCallback has missing dependencies: 'basePath', 'cleanAll', 'onSubmitCallback', 'resource', 'router', 'setFormData', 'showMessage', and 't'. Either include them or remove the dependency array. If 'onSubmitCallback' changes too often, find the parent component that defines it and wrap that definition in useCallback
);

const fields: RjsfFormProps["fields"] = useMemo(
Expand All @@ -306,9 +327,9 @@
const customInput = customInputs?.[props.name as Field<ModelName>];
const improvedCustomInput = customInput
? cloneElement(customInput, {
...customInput.props,
mode: edit ? "edit" : "create",
})
...customInput.props,
mode: edit ? "edit" : "create",
})
: undefined;
return <ArrayField {...props} customInput={improvedCustomInput} />;
},
Expand Down Expand Up @@ -605,7 +626,7 @@
extraErrors={extraErrors}
fields={fields}
disabled={allDisabled}
formContext={{ isPending, schema }}
formContext={{ isPending, schema, parentId: id }}
templates={templates}
widgets={widgets}
ref={ref}
Expand All @@ -613,14 +634,14 @@
/>
);
}),
[submitButton]

Check warning on line 637 in packages/next-admin/src/components/Form.tsx

View workflow job for this annotation

GitHub Actions / start

React Hook useMemo has missing dependencies: 'CustomForm', 'allDisabled', 'extraErrors', 'fields', 'id', 'isPending', 'schema', 'schemas', 'setFormData', and 'templates'. Either include them or remove the dependency array
);

return (
<div className="relative h-full">
<div className="bg-nextadmin-background-default dark:bg-dark-nextadmin-background-default max-w-full p-4 align-middle sm:p-8">
<div className={clsx(!isEmbedded && "bg-nextadmin-background-default dark:bg-dark-nextadmin-background-default p-4 sm:p-8", "max-w-full align-middle ")}>
<Message className="-mt-2 mb-2 sm:-mt-4 sm:mb-4" />
<div className="bg-nextadmin-background-default dark:bg-dark-nextadmin-background-emphasis border-nextadmin-border-default dark:border-dark-nextadmin-border-default max-w-screen-md rounded-lg border p-4 sm:p-8">
<div className={clsx(!isEmbedded && "bg-nextadmin-background-default dark:bg-dark-nextadmin-background-emphasis border-nextadmin-border-default dark:border-dark-nextadmin-border-default rounded-lg border", "max-w-screen-md p-4 sm:p-8")}>
<RjsfFormComponent ref={formRef} />
</div>
</div>
Expand All @@ -635,7 +656,7 @@
}: FormProps) => {
return (
<ClientActionDialogProvider componentsMap={clientActionsComponents}>
<FormHeader {...props} />
{props?.isEmbedded ? null : <FormHeader {...props} />}
<FormDataProvider
data={props.data}
relationshipsRawData={relationshipsRawData}
Expand Down
5 changes: 3 additions & 2 deletions packages/next-admin/src/components/inputs/ArrayField.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { FieldProps } from "@rjsf/utils";
import type { CustomInputProps, Enumeration, FormProps } from "../../types";
import FileWidget from "./FileWidget/FileWidget";
import MultiSelectWidget from "./MultiSelect/MultiSelectWidget";
import ScalarArrayField from "./ScalarArray/ScalarArrayField";
import FileWidget from "./FileWidget/FileWidget";

const ArrayField = (
props: FieldProps & { customInput?: React.ReactElement<CustomInputProps> }
Expand All @@ -22,7 +22,7 @@ const ArrayField = (

const field =
resourceDefinition.properties[
name as keyof typeof resourceDefinition.properties
name as keyof typeof resourceDefinition.properties
];

if (field?.__nextadmin?.kind === "scalar" && field?.__nextadmin?.isList) {
Expand Down Expand Up @@ -66,6 +66,7 @@ const ArrayField = (
required={required}
schema={schema}
options={options}
formContext={formContext}
/>
);
};
Expand Down
Loading
Loading