Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: app router support #76

Merged
merged 12 commits into from Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/smart-books-grab.md
@@ -0,0 +1,7 @@
---
"@premieroctet/next-admin": major
---

# New feature

App router is now supported. You can find an exemple of its usage in the example app.
4 changes: 3 additions & 1 deletion .github/workflows/e2e.yml
Expand Up @@ -33,7 +33,9 @@ jobs:
- name: Start server
run: yarn start:example &
- name: Run tests
run: yarn turbo test:e2e
run: |
yarn turbo test:e2e
BASE_URL=http://localhost:3000/pagerouter/admin yarn test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
Expand Down
4 changes: 2 additions & 2 deletions apps/docs/package.json
Expand Up @@ -11,8 +11,8 @@
"dependencies": {
"@premieroctet/next-admin": "*",
"next": "^13.1.1",
"nextra": "^2.7.1",
"nextra-theme-docs": "^2.7.1",
"nextra": "^2.13.2",
"nextra-theme-docs": "^2.13.2",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
171 changes: 119 additions & 52 deletions apps/docs/pages/docs/api-docs.mdx
@@ -1,80 +1,147 @@
import { Tabs } from "nextra/components";

# API

## `nextAdminRouter` function
## Functions

`nextAdminRouter` is a function that returns a promise of a _Node Router_ that you can use in your getServerSideProps function to start using Next Admin.
foyarash marked this conversation as resolved.
Show resolved Hide resolved
<Tabs items={['App router', 'Page router']}>
<Tabs.Tab>
The following is used only for App router.

Usage example:
## `getPropsFromParams` function

```ts
// pages/api/admin/[[...nextadmin]].ts
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```
`getPropsFromParams` is a function that returns the props for the [`NextAdmin`](#nextadmin--component) component. It accepts one argument which is an object with the following properties:

It takes 3 parameters:
- `params`: the array of route params retrieved from the [optional catch-all segment](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#optional-catch-all-segments)
- `searchParams`: the query params [retrieved from the page](https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional)
- `options`: the [options](#next-admin-options) object
- `schema`: the json schema generated by the `prisma generate` command
- `prisma`: your Prisma client instance
- `action`: the [server action](https://nextjs.org/docs/app/api-reference/functions/server-actions) used to submit the form. It should be your own action, that wraps the `submitForm` action imported from `@premieroctet/next-admin/dist/actions`.

- Your Prisma client instance, _required_
- Your Prisma schema, _required_
</Tabs.Tab>
<Tabs.Tab>
The following is used only for Page router

and an _optional_ object of type [`NextAdminOptions`](#nextadminoptions) to customize your admin with the following properties:
## `nextAdminRouter` function

```ts
import { NextAdminOptions } from "@premieroctet/next-admin";
`nextAdminRouter` is a function that returns a promise of a _Node Router_ that you can use in your getServerSideProps function to start using Next Admin. Its usage is only related to Page router.

const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.email} / ${user.name}`,
},
},
};
Usage example:

const adminRouter = await nextAdminRouter(prisma, schema, options);
```

## Authentication
```ts
// pages/api/admin/[[...nextadmin]].ts
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```

The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check to the `getServerSideProps` function:
It takes 3 parameters:

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication
- Your Prisma client instance, _required_
- Your Prisma schema, _required_

```ts
// pages/api/admin/[[...nextadmin]].ts
and an _optional_ object of type [`NextAdminOptions`](#next-admin-options) to customize your admin with the following properties:

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check
```ts
import { NextAdminOptions } from "@premieroctet/next-admin";

if (!isAdmin) {
return {
redirect: {
destination: "/",
permanent: false,
const options: NextAdminOptions = {
model: {
User: {
toString: (user) => `${user.email} / ${user.name}`,
},
},
};
}

const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/nextAdminRouter"
);
return nextAdminRouter(client).run(req, res);
};
```
const adminRouter = await nextAdminRouter(prisma, schema, options);
```

</Tabs.Tab>
</Tabs>

## Authentication

<Tabs items={['App router', 'Page router']}>
<Tabs.Tab>
The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check in the page:

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication

```ts
// app/admin/[[...nextadmin]]/page.tsx

export default async function AdminPage({
params,
searchParams,
}: {
params: { [key: string]: string[] };
searchParams: { [key: string]: string | string[] | undefined } | undefined;
}) {
const session = await getServerSession(authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check

if (!isAdmin) {
redirect('/', { permanent: false })
}

const props = await getPropsFromParams({
params: params.nextadmin,
searchParams,
options,
prisma,
schema,
action: submitFormAction,
});

return <NextAdmin {...props} dashboard={Dashboard} />;
}
```

</Tabs.Tab>
<Tabs.Tab>
The library does not provide an authentication system. If you want to add your own, you can do so by adding a role check to the `getServerSideProps` function:

> The following example uses [next-auth](https://next-auth.js.org/) to handle authentication

```ts
// pages/api/admin/[[...nextadmin]].ts

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getServerSession(req, res, authOptions);
const isAdmin = session?.user?.role === "SUPERADMIN"; // your role check

if (!isAdmin) {
return {
redirect: {
destination: "/",
permanent: false,
},
};
}

const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/nextAdminRouter"
);
return nextAdminRouter(client).run(req, res);
};
```

</Tabs.Tab>
</Tabs>

## `<NextAdmin />` component

`<NextAdmin />` is a React component that contains the entire UI of Next Admin. It can take several props:

- `AdminComponentProps`, which are passed by the [router function](#nextadminrouter-function) via getServerSideProps
- `options` used to customize the UI, like field formatters for example
- `options` used to customize the UI, like field formatters for example. Do not use with App router.
- `dashboard` used to customize the rendered dashboard

> ⚠️ : Do not override these `AdminComponentProps` props, they are used internally by Next Admin.
Expand Down
131 changes: 100 additions & 31 deletions apps/docs/pages/docs/getting-started.mdx
@@ -1,3 +1,5 @@
import { Tabs, Callout } from "nextra/components";

# Getting Started

## Installation
Expand Down Expand Up @@ -39,39 +41,106 @@ yarn run prisma generate

This will create a `json-schema.json` file in the `prisma/json-schema` directory.

Then create an `admin` folder in the `pages` directory.

Within this directory, create a `[[...nextadmin]].tsx` file and copy this code:

```tsx
// pages/admin/[[...nextadmin]].tsx
import { GetServerSideProps, GetServerSidePropsResult } from "next";
import { NextAdmin, AdminComponentProps } from "@premieroctet/next-admin";
import schema from "./../../prisma/json-schema/json-schema.json"; // import the json-schema.json file
import "@premieroctet/next-admin/dist/styles.css";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default function Admin(props: AdminComponentProps) {
return <NextAdmin {...props} />;
}

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```

The nextAdminRouter function accepts a third optional parameter, which is a Next Admin [options](/docs/api-docs#next-admin-options) object.
<Tabs items={['App router', 'Page router']}>
<Tabs.Tab>
```tsx
// app/admin/[[...nextadmin]]/page.tsx
import { NextAdmin } from "@premieroctet/next-admin";
import { getPropsFromParams } from "@premieroctet/next-admin/dist/appRouter";
import "@premieroctet/next-admin/dist/styles.css";
import Dashboard from "../../../components/Dashboard";
import { options } from "../../../options";
import { prisma } from "../../../prisma";
import schema from "../../../prisma/json-schema/json-schema.json";
import "../../../styles.css";
import { submitFormAction } from "../../../actions/nextadmin";

export default async function AdminPage({
params,
searchParams,
}: {
params: { [key: string]: string[] };
searchParams: { [key: string]: string | string[] | undefined } | undefined;
}) {
const props = await getPropsFromParams({
params: params.nextadmin,
searchParams,
options,
prisma,
schema,
action: submitFormAction,
});

return <NextAdmin {...props} dashboard={Dashboard} />;
}
```

<Callout emoji="⚠️">

Passing the `options` prop like you'd do on Page router will result in an error in case you
have functions defined inside the options object (formatter, handlers, etc.).
Make sure to pass no option at all.

</Callout>

<Callout emoji="⚠️">

Make sure to not use `"use client"` in the page.

</Callout>

You will also need to create the action:

```tsx
// actions/nextadmin.ts
"use server";
import { ActionParams } from "@premieroctet/next-admin";
import { submitForm } from "@premieroctet/next-admin/dist/actions";
import { prisma } from "../prisma";
import { options } from "../options";

export const submitFormAction = async (
params: ActionParams,
formData: FormData
) => {
return submitForm({ ...params, options, prisma }, formData);
};
```

</Tabs.Tab>
<Tabs.Tab>
```tsx
// pages/admin/[[...nextadmin]].tsx
import { GetServerSideProps, GetServerSidePropsResult } from "next";
import { NextAdmin, AdminComponentProps } from "@premieroctet/next-admin";
import schema from "./../../prisma/json-schema/json-schema.json"; // import the json-schema.json file
import "@premieroctet/next-admin/dist/styles.css";
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default function Admin(props: AdminComponentProps) {
return <NextAdmin {...props} />;
}

export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const { nextAdminRouter } = await import(
"@premieroctet/next-admin/dist/router"
);
const adminRouter = await nextAdminRouter(prisma, schema);
return adminRouter.run(req, res) as Promise<
GetServerSidePropsResult<{ [key: string]: any }>
>;
};
```

The `nextAdminRouter` function accepts a third optional parameter, which is a Next Admin [options](/docs/api-docs#next-admin-options) object.

</Tabs.Tab>
</Tabs>

## Usage

Once you have created the `[[...nextadmin]].tsx` file, you can start the server and go to the `/admin` route.
Once done, you can navigate to the `/admin` route.

You should be able to see the admin dashboard.
12 changes: 12 additions & 0 deletions apps/example/actions/nextadmin.ts
@@ -0,0 +1,12 @@
"use server";
import { ActionParams } from "@premieroctet/next-admin";
import { submitForm } from "@premieroctet/next-admin/dist/actions";
import { prisma } from "../prisma";
import { options } from "../options";

export const submitFormAction = async (
params: ActionParams,
formData: FormData
) => {
return submitForm({ ...params, options, prisma }, formData);
};