From 102bfbf32830febe10b99655723863ebd277aadb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ali=20Emir=20=C5=9Een?= Date: Wed, 7 Dec 2022 15:10:14 +0300 Subject: [PATCH] feat: `Next.js@13` support (#3140) * refactor(nextjs): move current setup to `src/pages` for convenience * feat(nextjs): `appDir` compatible nextjs router instance * feat(nextjs): separate export at `/app` for `appDir` feature * feat(nextjs): add nextjs example with `appDir` * chore(live-previews): bump to `nextjs` 13 * chore(examples): bump to next 13 * refactor(examples): update component name * feat(nextjs): add `routercomponent` to `/app` router provider * chore: add changeset * chore: update check auth * chore: update changeset * chore: fix store incompatible prop type * docs: update live example link * chore: move nextjs examples to `examples/nextjs` * docs: add `app/` example to examples list * docs(nextjs): add `app` support section --- .changeset/khaki-timers-travel.md | 80 +++++++ .../docs/advanced-tutorials/ssr/nextjs.md | 79 ++++++- documentation/docs/examples/next-js/appdir.md | 16 ++ .../docs/examples/next-js/next-js.md | 4 +- documentation/sidebars.js | 5 +- examples/blog/ecommerce/package.json | 2 +- examples/blog/next-refine-pwa/package.json | 2 +- examples/fineFoods/client/package.json | 2 +- examples/i18n/nextjs/package.json | 2 +- .../appdir}/.gitattributes | 0 .../{refine-next => nextjs/appdir}/.gitignore | 0 .../{refine-next => nextjs/appdir}/LICENSE | 0 .../{refine-next => nextjs/appdir}/README.MD | 0 .../appdir/app/[[...refine]]/layout.tsx | 64 ++++++ .../nextjs/appdir/app/[[...refine]]/page.tsx | 5 + examples/nextjs/appdir/app/layout.tsx | 11 + .../appdir}/next-env.d.ts | 0 examples/nextjs/appdir/next.config.js | 10 + examples/nextjs/appdir/package.json | 28 +++ .../appdir}/public/icons/github-icon.svg | 0 .../appdir}/public/icons/linkedin-icon.svg | 0 .../appdir}/public/icons/nextjs-icon.svg | 0 .../appdir}/public/icons/pankod-icon.svg | 0 .../appdir}/public/icons/twitter-icon.svg | 0 .../appdir}/public/icons/youtube-icon.svg | 0 .../appdir}/public/meta.json | 0 examples/nextjs/appdir/src/authProvider.ts | 73 +++++++ .../appdir}/src/components/index.ts | 0 .../appdir}/src/components/posts/create.tsx | 0 .../appdir}/src/components/posts/edit.tsx | 0 .../appdir}/src/components/posts/index.ts | 0 .../appdir}/src/components/posts/list.tsx | 0 .../appdir}/src/components/posts/show.tsx | 0 .../appdir}/src/constants.ts | 0 .../appdir}/src/interfaces/index.ts | 0 .../appdir}/src/styles/global.css | 0 examples/nextjs/appdir/tsconfig.json | 57 +++++ examples/nextjs/base/.gitattributes | 1 + examples/nextjs/base/.gitignore | 8 + examples/nextjs/base/LICENSE | 19 ++ examples/nextjs/base/README.MD | 52 +++++ examples/nextjs/base/next-env.d.ts | 5 + .../{refine-next => nextjs/base}/package.json | 4 +- .../base}/pages/[[...refine]].tsx | 0 .../base}/pages/_app.tsx | 0 .../base}/pages/_document.tsx | 0 .../base}/pages/users/index.tsx | 0 .../nextjs/base/public/icons/github-icon.svg | 1 + .../base/public/icons/linkedin-icon.svg | 1 + .../nextjs/base/public/icons/nextjs-icon.svg | 6 + .../nextjs/base/public/icons/pankod-icon.svg | 33 +++ .../nextjs/base/public/icons/twitter-icon.svg | 1 + .../nextjs/base/public/icons/youtube-icon.svg | 1 + examples/nextjs/base/public/meta.json | 15 ++ .../base}/src/authProvider.ts | 0 examples/nextjs/base/src/components/index.ts | 1 + .../base/src/components/posts/create.tsx | 48 +++++ .../nextjs/base/src/components/posts/edit.tsx | 49 +++++ .../nextjs/base/src/components/posts/index.ts | 4 + .../nextjs/base/src/components/posts/list.tsx | 54 +++++ .../nextjs/base/src/components/posts/show.tsx | 34 +++ examples/nextjs/base/src/constants.ts | 1 + examples/nextjs/base/src/interfaces/index.ts | 12 ++ examples/nextjs/base/src/styles/global.css | 9 + .../base}/tsconfig.json | 0 examples/store/package.json | 2 +- .../product/ProductCard/ProductCard.tsx | 2 +- packages/live-previews/package.json | 2 +- packages/nextjs-router/package.json | 135 +++++++----- packages/nextjs-router/src/app/index.ts | 5 + packages/nextjs-router/src/app/prompt.tsx | 15 ++ .../nextjs-router/src/app/route-component.tsx | 204 ++++++++++++++++++ .../src/app/router-component.tsx | 18 ++ .../nextjs-router/src/app/router-provider.ts | 24 +++ packages/nextjs-router/src/app/use-history.ts | 14 ++ .../nextjs-router/src/app/use-location.ts | 24 +++ packages/nextjs-router/src/app/use-params.ts | 12 ++ .../handle-refine-params.ts} | 19 +- .../refine-link.tsx} | 0 packages/nextjs-router/src/index.ts | 14 +- .../check-authentication.ts} | 0 packages/nextjs-router/src/pages/index.ts | 7 + .../nextjs-router/src/{ => pages}/prompt.tsx | 0 .../route-component.tsx} | 4 +- .../src/pages/router-provider.ts | 15 ++ .../nextjs-router/src/pages/use-history.ts | 13 ++ .../nextjs-router/src/pages/use-location.ts | 23 ++ .../nextjs-router/src/pages/use-params.ts | 15 ++ packages/nextjs-router/src/routerProvider.ts | 41 ---- packages/nextjs-router/tsup.config.ts | 7 +- 90 files changed, 1281 insertions(+), 133 deletions(-) create mode 100644 .changeset/khaki-timers-travel.md create mode 100644 documentation/docs/examples/next-js/appdir.md rename examples/{refine-next => nextjs/appdir}/.gitattributes (100%) rename examples/{refine-next => nextjs/appdir}/.gitignore (100%) rename examples/{refine-next => nextjs/appdir}/LICENSE (100%) rename examples/{refine-next => nextjs/appdir}/README.MD (100%) create mode 100644 examples/nextjs/appdir/app/[[...refine]]/layout.tsx create mode 100644 examples/nextjs/appdir/app/[[...refine]]/page.tsx create mode 100644 examples/nextjs/appdir/app/layout.tsx rename examples/{refine-next => nextjs/appdir}/next-env.d.ts (100%) create mode 100644 examples/nextjs/appdir/next.config.js create mode 100644 examples/nextjs/appdir/package.json rename examples/{refine-next => nextjs/appdir}/public/icons/github-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/icons/linkedin-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/icons/nextjs-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/icons/pankod-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/icons/twitter-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/icons/youtube-icon.svg (100%) rename examples/{refine-next => nextjs/appdir}/public/meta.json (100%) create mode 100644 examples/nextjs/appdir/src/authProvider.ts rename examples/{refine-next => nextjs/appdir}/src/components/index.ts (100%) rename examples/{refine-next => nextjs/appdir}/src/components/posts/create.tsx (100%) rename examples/{refine-next => nextjs/appdir}/src/components/posts/edit.tsx (100%) rename examples/{refine-next => nextjs/appdir}/src/components/posts/index.ts (100%) rename examples/{refine-next => nextjs/appdir}/src/components/posts/list.tsx (100%) rename examples/{refine-next => nextjs/appdir}/src/components/posts/show.tsx (100%) rename examples/{refine-next => nextjs/appdir}/src/constants.ts (100%) rename examples/{refine-next => nextjs/appdir}/src/interfaces/index.ts (100%) rename examples/{refine-next => nextjs/appdir}/src/styles/global.css (100%) create mode 100644 examples/nextjs/appdir/tsconfig.json create mode 100644 examples/nextjs/base/.gitattributes create mode 100644 examples/nextjs/base/.gitignore create mode 100644 examples/nextjs/base/LICENSE create mode 100644 examples/nextjs/base/README.MD create mode 100644 examples/nextjs/base/next-env.d.ts rename examples/{refine-next => nextjs/base}/package.json (92%) rename examples/{refine-next => nextjs/base}/pages/[[...refine]].tsx (100%) rename examples/{refine-next => nextjs/base}/pages/_app.tsx (100%) rename examples/{refine-next => nextjs/base}/pages/_document.tsx (100%) rename examples/{refine-next => nextjs/base}/pages/users/index.tsx (100%) create mode 100644 examples/nextjs/base/public/icons/github-icon.svg create mode 100644 examples/nextjs/base/public/icons/linkedin-icon.svg create mode 100644 examples/nextjs/base/public/icons/nextjs-icon.svg create mode 100644 examples/nextjs/base/public/icons/pankod-icon.svg create mode 100644 examples/nextjs/base/public/icons/twitter-icon.svg create mode 100644 examples/nextjs/base/public/icons/youtube-icon.svg create mode 100644 examples/nextjs/base/public/meta.json rename examples/{refine-next => nextjs/base}/src/authProvider.ts (100%) create mode 100644 examples/nextjs/base/src/components/index.ts create mode 100644 examples/nextjs/base/src/components/posts/create.tsx create mode 100644 examples/nextjs/base/src/components/posts/edit.tsx create mode 100644 examples/nextjs/base/src/components/posts/index.ts create mode 100644 examples/nextjs/base/src/components/posts/list.tsx create mode 100644 examples/nextjs/base/src/components/posts/show.tsx create mode 100644 examples/nextjs/base/src/constants.ts create mode 100644 examples/nextjs/base/src/interfaces/index.ts create mode 100644 examples/nextjs/base/src/styles/global.css rename examples/{refine-next => nextjs/base}/tsconfig.json (100%) create mode 100644 packages/nextjs-router/src/app/index.ts create mode 100644 packages/nextjs-router/src/app/prompt.tsx create mode 100644 packages/nextjs-router/src/app/route-component.tsx create mode 100644 packages/nextjs-router/src/app/router-component.tsx create mode 100644 packages/nextjs-router/src/app/router-provider.ts create mode 100644 packages/nextjs-router/src/app/use-history.ts create mode 100644 packages/nextjs-router/src/app/use-location.ts create mode 100644 packages/nextjs-router/src/app/use-params.ts rename packages/nextjs-router/src/{useParams.ts => common/handle-refine-params.ts} (73%) rename packages/nextjs-router/src/{refineLink.tsx => common/refine-link.tsx} (100%) rename packages/nextjs-router/src/{checkAuthentication.ts => pages/check-authentication.ts} (100%) create mode 100644 packages/nextjs-router/src/pages/index.ts rename packages/nextjs-router/src/{ => pages}/prompt.tsx (100%) rename packages/nextjs-router/src/{nextRouteComponent.tsx => pages/route-component.tsx} (98%) create mode 100644 packages/nextjs-router/src/pages/router-provider.ts create mode 100644 packages/nextjs-router/src/pages/use-history.ts create mode 100644 packages/nextjs-router/src/pages/use-location.ts create mode 100644 packages/nextjs-router/src/pages/use-params.ts delete mode 100644 packages/nextjs-router/src/routerProvider.ts diff --git a/.changeset/khaki-timers-travel.md b/.changeset/khaki-timers-travel.md new file mode 100644 index 000000000000..a783582aa3b5 --- /dev/null +++ b/.changeset/khaki-timers-travel.md @@ -0,0 +1,80 @@ +--- +"@pankod/refine-nextjs-router": major +--- + +- Bumped Next.js to 13 +- Added support for experimental `appDir` option in `next.config.js` to allow for the latest Next.js features. + + +## `pages` directory + +Current support for `pages` directory has not changed and will continue to work as before. It will be supported as long as `Next.js` continues to support and prompts it as the stable way of working with `Next.js`. + +## `appDir` option + +`appDir` option is a new experimental feature in `Next.js` that introduces a bunch of new features. It is currently in beta and is not stable. It is not recommended to use it in production. But can be used alongside the current `pages` directory support. + +To use `appDir` option, you need to add it to your `next.config.js` file. + +```js +// next.config.js +module.exports = { + /* ... */ + experimental: { + appDir: true, + }, +}; +``` + +## Using `appDir` with **refine** + +We've needed to make some changes to the `@pankod/refine-nextjs-router` to make it work with the current structure of the `app` directory feature. To make sure these do not break the current support for `pages` directory, we've added a new exports at the sub path `@pankod/refine-nextjs-router/app` that can be used with the `appDir` option. + +**Note** + +To make optional catch-all routes to work with the `app` directory, you need to define them as directories unlike the option of defining them as files with `pages` directory. + +You need to use `NextRouteComponent` from `@pankod/refine-nextjs-router/app` instead of `NextRouteComponent` from `@pankod/refine-nextjs-router` when using `appDir` option. + +Inside your `layout` file, you need to bind `params` to `routerProvider` to make sure `@pankod/refine-nextjs-router` can work properly with the new structure. + +```tsx +// app/[[...refine]]/layout.tsx +"use client"; + +import routerProvider from "@pankod/refine-nextjs-router/app"; + +const Layout = ({ children, params }) => { + return ( + + {children} + + ); +}; +``` + +**Warning** + +Please note that, unlike the `routerProvider` from the `@pankod/refine-nextjs-router`, `routerProvider` from `@pankod/refine-nextjs-router/app` is a function and you need to bind `params` to make it work properly. + +```tsx +// app/[[...refine]]/page.tsx + +"use client"; + +import { NextRouteComponent } from "@pankod/refine-nextjs-router/app"; + +export default NextRouteComponent; + +``` + +**Warning** + +You need to add `"use client";` directive to both `layout.tsx` and `page.tsx` inside `app/[[...refine]]` directory. + +**Warning** + +`checkAuthentication` does not work with `appDir`. We're aiming to release a substitute for it using middleware but for now its not included in this release. \ No newline at end of file diff --git a/documentation/docs/advanced-tutorials/ssr/nextjs.md b/documentation/docs/advanced-tutorials/ssr/nextjs.md index a0b3d7a14158..52cab47e2db8 100644 --- a/documentation/docs/advanced-tutorials/ssr/nextjs.md +++ b/documentation/docs/advanced-tutorials/ssr/nextjs.md @@ -396,9 +396,86 @@ export const getServerSideProps: GetServerSideProps = async (context) => { `parseTableParams` parses the query string and returns query parameters([refer here for their interfaces][interfaces]). They can be directly used for `dataProvider` methods that accepts them. +## `appDir` Support + +Next.js introduced a new way of defining pages within the `app/` directory. _This new directory has support for layouts, nested routes, and uses Server Components by default._ To learn more about the feature check out [Next.js Beta docs](https://beta.nextjs.org/docs/upgrade-guide) + +**refine** also follows this feature and provides a way to use `appDir` with your **refine** apps. + +:::caution + +`app/` is currently in beta and is **not recommended** for production use in Next.js. In **refine**, we're providing the `app/` support as experimental and not recommended for production use. + +::: + +To start using `app/` with **refine**, you need to set create the **refine** routes in your `/app` directory with the following convention: + +```bash + +your-project +└── app + └── [[...refine]] + ├── layout.tsx + └── page.tsx + +``` + +**Initializing `` in `layout.tsx`** + +```tsx title="app/[[...refine]]/layout.tsx" +"use client"; + +import routerProvider from "@pankod/refine-nextjs-router/app"; + +export default function RefineLayout({ + children, + params, +}: { + children: React.ReactNode; + params: Record<"refine", string[]>; +}) { + return ( + + {children} + + ); +} +``` + +We need to bind the `params` to the `routerProvider` and call it to initialize the `routerProvider`. This is because the `params` are not available via hooks for **refine** to use. + +**Creating `page.tsx`** + +```tsx title="app/[[...refine]]/page.tsx" +"use client"; + +import { NextRouteComponent } from "@pankod/refine-nextjs-router/app"; + +export default NextRouteComponent; +``` + +Note that we're importing `NextRouteComponent` from `@pankod/refine-nextjs-router/app` instead of `@pankod/refine-nextjs-router`. This is because we're using the `app/` directory and we need to import the `app` version of the `NextRouteComponent`. + +:::note +`"use client";` is a directive that instructs Next.js to opt-out from Server Components. This is because **refine** and dependencies are not yet compatible with Server Components. Thats why we're using it in both `layout.tsx` and `page.tsx` files. +::: + +:::note +`checkAuthentication` does not work with `app/` directory. You need to handle the authentication your views while using `app/` directory. + +**refine** aims to provide a middleware for `app/` directory to substitue `checkAuthentication` but it's not available yet. +::: + +:::info +You can find the `app/` directory example with **refine** in [examples/nextjs/appdir](https://github.com/refinedev/refine/tree/next/examples/remix/antd) +::: + ## Live StackBlitz Example - diff --git a/documentation/docs/examples/next-js/appdir.md b/documentation/docs/examples/next-js/appdir.md new file mode 100644 index 000000000000..04d85cb82df0 --- /dev/null +++ b/documentation/docs/examples/next-js/appdir.md @@ -0,0 +1,16 @@ +--- +id: nextjs-appdir +title: With `app/` Directory +example-tags: [next.js,router-provider,antd,experimental] +--- + +**refine** allows you to use the `app/` structure in your Next.js apps. To learn more about the `app/` directory, check out [Next.js beta docs](https://beta.nextjs.org/docs/upgrade-guide). In this example you will find how to use the `app/` directory with refine. + +[Refer to the refine Next.js documentation for more information. →](/docs/advanced-tutorials/ssr/nextjs.md) + +[View Next.js with `app/` Example Source](https://github.com/refinedev/refine/tree/master/examples/nextjs/appdir) + + diff --git a/documentation/docs/examples/next-js/next-js.md b/documentation/docs/examples/next-js/next-js.md index 1a19e4c08693..b4836a6cfb87 100644 --- a/documentation/docs/examples/next-js/next-js.md +++ b/documentation/docs/examples/next-js/next-js.md @@ -8,9 +8,9 @@ example-tags: [next.js,router-provider,antd] [Refer to the refine Next.js documentation for more information. →](/docs/advanced-tutorials/ssr/nextjs.md) -[View Next.js Example Source](https://github.com/refinedev/refine/tree/master/examples/refine-next) +[View Next.js Example Source](https://github.com/refinedev/refine/tree/master/examples/nextjs/base) - diff --git a/documentation/sidebars.js b/documentation/sidebars.js index afab593f60b7..2538a8e45d63 100644 --- a/documentation/sidebars.js +++ b/documentation/sidebars.js @@ -825,7 +825,10 @@ module.exports = { { type: "category", label: "Next.js", - items: ["examples/next-js/nextjs"], + items: [ + "examples/next-js/nextjs", + "examples/next-js/nextjs-appdir", + ], }, { type: "category", diff --git a/examples/blog/ecommerce/package.json b/examples/blog/ecommerce/package.json index d7ac08d3c7a1..c5230ecad86c 100644 --- a/examples/blog/ecommerce/package.json +++ b/examples/blog/ecommerce/package.json @@ -19,7 +19,7 @@ "@pankod/refine-strapi-v4": "^3.39.0", "axios": "^0.26.1", "framer-motion": "^7.5.3", - "next": "^12.1.6", + "next": "^13.0.6", "next-compose-plugins": "^2.2.1", "nookies": "^2.5.2", "react": "^18.0.0", diff --git a/examples/blog/next-refine-pwa/package.json b/examples/blog/next-refine-pwa/package.json index 8913c273e5f4..0d2aa877e0d2 100644 --- a/examples/blog/next-refine-pwa/package.json +++ b/examples/blog/next-refine-pwa/package.json @@ -12,7 +12,7 @@ "@pankod/refine-core": "^3.90.4", "@pankod/refine-nextjs-router": "^3.38.0", "@pankod/refine-simple-rest": "^3.37.4", - "next": "^12.1.6", + "next": "^13.0.6", "next-compose-plugins": "^2.2.1", "react": "^18.0.0", "react-dom": "^18.0.0" diff --git a/examples/fineFoods/client/package.json b/examples/fineFoods/client/package.json index ac84b236d26d..0a18b66d4c22 100644 --- a/examples/fineFoods/client/package.json +++ b/examples/fineFoods/client/package.json @@ -15,7 +15,7 @@ "@pankod/refine-simple-rest": "^3.37.4", "gsap": "^3.8.0", "js-confetti": "^0.9.0", - "next": "^12.1.6", + "next": "^13.0.6", "react": "^18.0.0", "react-dom": "^18.0.0" }, diff --git a/examples/i18n/nextjs/package.json b/examples/i18n/nextjs/package.json index 842275138d04..08c40a17eaf0 100644 --- a/examples/i18n/nextjs/package.json +++ b/examples/i18n/nextjs/package.json @@ -13,7 +13,7 @@ "@pankod/refine-core": "^3.90.4", "@pankod/refine-nextjs-router": "^3.38.0", "@pankod/refine-simple-rest": "^3.37.4", - "next": "^12.1.6", + "next": "^13.0.6", "next-compose-plugins": "^2.2.1", "next-i18next": "^8.9.0", "nookies": "^2.5.2", diff --git a/examples/refine-next/.gitattributes b/examples/nextjs/appdir/.gitattributes similarity index 100% rename from examples/refine-next/.gitattributes rename to examples/nextjs/appdir/.gitattributes diff --git a/examples/refine-next/.gitignore b/examples/nextjs/appdir/.gitignore similarity index 100% rename from examples/refine-next/.gitignore rename to examples/nextjs/appdir/.gitignore diff --git a/examples/refine-next/LICENSE b/examples/nextjs/appdir/LICENSE similarity index 100% rename from examples/refine-next/LICENSE rename to examples/nextjs/appdir/LICENSE diff --git a/examples/refine-next/README.MD b/examples/nextjs/appdir/README.MD similarity index 100% rename from examples/refine-next/README.MD rename to examples/nextjs/appdir/README.MD diff --git a/examples/nextjs/appdir/app/[[...refine]]/layout.tsx b/examples/nextjs/appdir/app/[[...refine]]/layout.tsx new file mode 100644 index 000000000000..3461eaf4f7fb --- /dev/null +++ b/examples/nextjs/appdir/app/[[...refine]]/layout.tsx @@ -0,0 +1,64 @@ +"use client"; + +import React from "react"; + +import { Refine } from "@pankod/refine-core"; +import { + notificationProvider, + Layout, + ErrorComponent, + AuthPage, +} from "@pankod/refine-antd"; +import dataProvider from "@pankod/refine-simple-rest"; +import routerProvider from "@pankod/refine-nextjs-router/app"; +import "@pankod/refine-antd/dist/styles.min.css"; + +import "@styles/global.css"; + +import { authProvider } from "src/authProvider"; +import { API_URL } from "../../src/constants"; + +import { PostList, PostCreate, PostEdit, PostShow } from "@components"; + +export default function RefineLayout({ + children, + params, +}: { + children: React.ReactNode; + params: Record<"refine", string[]>; +}) { + return ( + ( + + )} + Layout={Layout} + catchAll={} + > + {children} + + ); +} diff --git a/examples/nextjs/appdir/app/[[...refine]]/page.tsx b/examples/nextjs/appdir/app/[[...refine]]/page.tsx new file mode 100644 index 000000000000..1f5b182ea9b8 --- /dev/null +++ b/examples/nextjs/appdir/app/[[...refine]]/page.tsx @@ -0,0 +1,5 @@ +"use client"; + +import { NextRouteComponent } from "@pankod/refine-nextjs-router/app"; + +export default NextRouteComponent; diff --git a/examples/nextjs/appdir/app/layout.tsx b/examples/nextjs/appdir/app/layout.tsx new file mode 100644 index 000000000000..e28eaf552a1f --- /dev/null +++ b/examples/nextjs/appdir/app/layout.tsx @@ -0,0 +1,11 @@ +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/examples/refine-next/next-env.d.ts b/examples/nextjs/appdir/next-env.d.ts similarity index 100% rename from examples/refine-next/next-env.d.ts rename to examples/nextjs/appdir/next-env.d.ts diff --git a/examples/nextjs/appdir/next.config.js b/examples/nextjs/appdir/next.config.js new file mode 100644 index 000000000000..18c8a851d1ab --- /dev/null +++ b/examples/nextjs/appdir/next.config.js @@ -0,0 +1,10 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, + swcMinify: true, + experimental: { + appDir: true, + }, +}; + +module.exports = nextConfig; diff --git a/examples/nextjs/appdir/package.json b/examples/nextjs/appdir/package.json new file mode 100644 index 000000000000..1e80e302ee6b --- /dev/null +++ b/examples/nextjs/appdir/package.json @@ -0,0 +1,28 @@ +{ + "name": "refine-nextjs-appdir", + "version": "3.25.0", + "private": true, + "scripts": { + "start": "NODE_OPTIONS=--max_old_space_size=4096 PORT=3002 next dev", + "build": "next build", + "start:prod": "next start", + "lint": "eslint '**/*.{js,jsx,ts,tsx}'" + }, + "dependencies": { + "@pankod/refine-antd": "^3.64.2", + "@pankod/refine-core": "^3.90.4", + "@pankod/refine-nextjs-router": "^3.38.0", + "@pankod/refine-simple-rest": "^3.37.4", + "next": "^13.0.6", + "nookies": "^2.5.2", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "devDependencies": { + "@types/node": "^12.20.11", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@typescript-eslint/parser": "^5.38.1", + "typescript": "^4.7.4" + } +} diff --git a/examples/refine-next/public/icons/github-icon.svg b/examples/nextjs/appdir/public/icons/github-icon.svg similarity index 100% rename from examples/refine-next/public/icons/github-icon.svg rename to examples/nextjs/appdir/public/icons/github-icon.svg diff --git a/examples/refine-next/public/icons/linkedin-icon.svg b/examples/nextjs/appdir/public/icons/linkedin-icon.svg similarity index 100% rename from examples/refine-next/public/icons/linkedin-icon.svg rename to examples/nextjs/appdir/public/icons/linkedin-icon.svg diff --git a/examples/refine-next/public/icons/nextjs-icon.svg b/examples/nextjs/appdir/public/icons/nextjs-icon.svg similarity index 100% rename from examples/refine-next/public/icons/nextjs-icon.svg rename to examples/nextjs/appdir/public/icons/nextjs-icon.svg diff --git a/examples/refine-next/public/icons/pankod-icon.svg b/examples/nextjs/appdir/public/icons/pankod-icon.svg similarity index 100% rename from examples/refine-next/public/icons/pankod-icon.svg rename to examples/nextjs/appdir/public/icons/pankod-icon.svg diff --git a/examples/refine-next/public/icons/twitter-icon.svg b/examples/nextjs/appdir/public/icons/twitter-icon.svg similarity index 100% rename from examples/refine-next/public/icons/twitter-icon.svg rename to examples/nextjs/appdir/public/icons/twitter-icon.svg diff --git a/examples/refine-next/public/icons/youtube-icon.svg b/examples/nextjs/appdir/public/icons/youtube-icon.svg similarity index 100% rename from examples/refine-next/public/icons/youtube-icon.svg rename to examples/nextjs/appdir/public/icons/youtube-icon.svg diff --git a/examples/refine-next/public/meta.json b/examples/nextjs/appdir/public/meta.json similarity index 100% rename from examples/refine-next/public/meta.json rename to examples/nextjs/appdir/public/meta.json diff --git a/examples/nextjs/appdir/src/authProvider.ts b/examples/nextjs/appdir/src/authProvider.ts new file mode 100644 index 000000000000..dc2c87e394df --- /dev/null +++ b/examples/nextjs/appdir/src/authProvider.ts @@ -0,0 +1,73 @@ +import { AuthProvider } from "@pankod/refine-core"; +import nookies from "nookies"; + +const mockUsers = [ + { + email: "admin@refine.dev", + roles: ["admin"], + }, + { + email: "editor@refine.dev", + roles: ["editor"], + }, +]; + +export const authProvider: AuthProvider = { + login: ({ email }) => { + // Suppose we actually send a request to the back end here. + const user = mockUsers.find((item) => item.email === email); + + if (user) { + nookies.set(null, "auth", JSON.stringify(user), { + maxAge: 30 * 24 * 60 * 60, + path: "/", + }); + return Promise.resolve(); + } + + return Promise.reject(); + }, + logout: () => { + nookies.destroy(null, "auth"); + return Promise.resolve(); + }, + checkError: (error) => { + if (error && error.statusCode === 401) { + return Promise.reject(); + } + + return Promise.resolve(); + }, + checkAuth: (ctx) => { + if (ctx) { + if (ctx.cookies?.get?.("auth")) { + return Promise.resolve(); + } else { + return Promise.reject(); + } + } else { + const cookies = nookies.get(null); + if (cookies.auth) { + return Promise.resolve(); + } else { + return Promise.reject(); + } + } + }, + getPermissions: () => { + const auth = nookies.get()["auth"]; + if (auth) { + const parsedUser = JSON.parse(auth); + return Promise.resolve(parsedUser.roles); + } + return Promise.reject(); + }, + getUserIdentity: () => { + const auth = nookies.get()["auth"]; + if (auth) { + const parsedUser = JSON.parse(auth); + return Promise.resolve(parsedUser.username ?? parsedUser.email); + } + return Promise.reject(); + }, +}; diff --git a/examples/refine-next/src/components/index.ts b/examples/nextjs/appdir/src/components/index.ts similarity index 100% rename from examples/refine-next/src/components/index.ts rename to examples/nextjs/appdir/src/components/index.ts diff --git a/examples/refine-next/src/components/posts/create.tsx b/examples/nextjs/appdir/src/components/posts/create.tsx similarity index 100% rename from examples/refine-next/src/components/posts/create.tsx rename to examples/nextjs/appdir/src/components/posts/create.tsx diff --git a/examples/refine-next/src/components/posts/edit.tsx b/examples/nextjs/appdir/src/components/posts/edit.tsx similarity index 100% rename from examples/refine-next/src/components/posts/edit.tsx rename to examples/nextjs/appdir/src/components/posts/edit.tsx diff --git a/examples/refine-next/src/components/posts/index.ts b/examples/nextjs/appdir/src/components/posts/index.ts similarity index 100% rename from examples/refine-next/src/components/posts/index.ts rename to examples/nextjs/appdir/src/components/posts/index.ts diff --git a/examples/refine-next/src/components/posts/list.tsx b/examples/nextjs/appdir/src/components/posts/list.tsx similarity index 100% rename from examples/refine-next/src/components/posts/list.tsx rename to examples/nextjs/appdir/src/components/posts/list.tsx diff --git a/examples/refine-next/src/components/posts/show.tsx b/examples/nextjs/appdir/src/components/posts/show.tsx similarity index 100% rename from examples/refine-next/src/components/posts/show.tsx rename to examples/nextjs/appdir/src/components/posts/show.tsx diff --git a/examples/refine-next/src/constants.ts b/examples/nextjs/appdir/src/constants.ts similarity index 100% rename from examples/refine-next/src/constants.ts rename to examples/nextjs/appdir/src/constants.ts diff --git a/examples/refine-next/src/interfaces/index.ts b/examples/nextjs/appdir/src/interfaces/index.ts similarity index 100% rename from examples/refine-next/src/interfaces/index.ts rename to examples/nextjs/appdir/src/interfaces/index.ts diff --git a/examples/refine-next/src/styles/global.css b/examples/nextjs/appdir/src/styles/global.css similarity index 100% rename from examples/refine-next/src/styles/global.css rename to examples/nextjs/appdir/src/styles/global.css diff --git a/examples/nextjs/appdir/tsconfig.json b/examples/nextjs/appdir/tsconfig.json new file mode 100644 index 000000000000..e46ab227ab7b --- /dev/null +++ b/examples/nextjs/appdir/tsconfig.json @@ -0,0 +1,57 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@components/*": [ + "src/components/*" + ], + "@components": [ + "src/components" + ], + "@styles/*": [ + "src/styles/*" + ], + "@styles": [ + "src/styles" + ], + "@public/*": [ + "public/*" + ], + "@public": [ + "public" + ] + }, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/examples/nextjs/base/.gitattributes b/examples/nextjs/base/.gitattributes new file mode 100644 index 000000000000..176a458f94e0 --- /dev/null +++ b/examples/nextjs/base/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/examples/nextjs/base/.gitignore b/examples/nextjs/base/.gitignore new file mode 100644 index 000000000000..c340a29f40d2 --- /dev/null +++ b/examples/nextjs/base/.gitignore @@ -0,0 +1,8 @@ +# npm +node_modules +# next.js build files +.next +# environment variables +.env +.env.* +!.env.example \ No newline at end of file diff --git a/examples/nextjs/base/LICENSE b/examples/nextjs/base/LICENSE new file mode 100644 index 000000000000..80f82954b75f --- /dev/null +++ b/examples/nextjs/base/LICENSE @@ -0,0 +1,19 @@ +The MIT License (MIT) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +n +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/examples/nextjs/base/README.MD b/examples/nextjs/base/README.MD new file mode 100644 index 000000000000..244db6011f0b --- /dev/null +++ b/examples/nextjs/base/README.MD @@ -0,0 +1,52 @@ +# refine-next + + +This project was generated with [superplate](https://github.com/pankod/superplate). + +## Getting Started + +superplate is a Next.js all-in-one project generator. Create your project with the tools you need without spending hours on setting them up. + +## Available Scripts + +### Running the development server. + +```bash + npm run dev +``` + +### Building for production. + +```bash + npm run build +``` + +### Running the production server. + +```bash + npm run start +``` + +## Learn More + +To learn more about **superplate**, please check out the [Documentation](https://github.com/pankod/superplate). + + +### **CSS / styled-jsx** + +Next.js comes with built-in support for CSS and styled-jsx. Styled-jsx is full, scoped and component-friendly CSS support for JSX (rendered on the server or the client). + +[Go To Documentation](https://github.com/vercel/styled-jsx) + + +### **ESLint** + +A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease. + +[Go To Documentation](https://eslint.org/docs/user-guide/getting-started) + + + +## License + +MIT diff --git a/examples/nextjs/base/next-env.d.ts b/examples/nextjs/base/next-env.d.ts new file mode 100644 index 000000000000..4f11a03dc6cc --- /dev/null +++ b/examples/nextjs/base/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/refine-next/package.json b/examples/nextjs/base/package.json similarity index 92% rename from examples/refine-next/package.json rename to examples/nextjs/base/package.json index 4600a0ba0956..198ff38bc878 100644 --- a/examples/refine-next/package.json +++ b/examples/nextjs/base/package.json @@ -1,5 +1,5 @@ { - "name": "refine-next", + "name": "refine-nextjs-base", "version": "3.25.0", "private": true, "scripts": { @@ -13,7 +13,7 @@ "@pankod/refine-core": "^3.90.4", "@pankod/refine-nextjs-router": "^3.38.0", "@pankod/refine-simple-rest": "^3.37.4", - "next": "^12.1.6", + "next": "^13.0.6", "nookies": "^2.5.2", "react": "^18.0.0", "react-dom": "^18.0.0" diff --git a/examples/refine-next/pages/[[...refine]].tsx b/examples/nextjs/base/pages/[[...refine]].tsx similarity index 100% rename from examples/refine-next/pages/[[...refine]].tsx rename to examples/nextjs/base/pages/[[...refine]].tsx diff --git a/examples/refine-next/pages/_app.tsx b/examples/nextjs/base/pages/_app.tsx similarity index 100% rename from examples/refine-next/pages/_app.tsx rename to examples/nextjs/base/pages/_app.tsx diff --git a/examples/refine-next/pages/_document.tsx b/examples/nextjs/base/pages/_document.tsx similarity index 100% rename from examples/refine-next/pages/_document.tsx rename to examples/nextjs/base/pages/_document.tsx diff --git a/examples/refine-next/pages/users/index.tsx b/examples/nextjs/base/pages/users/index.tsx similarity index 100% rename from examples/refine-next/pages/users/index.tsx rename to examples/nextjs/base/pages/users/index.tsx diff --git a/examples/nextjs/base/public/icons/github-icon.svg b/examples/nextjs/base/public/icons/github-icon.svg new file mode 100644 index 000000000000..1f8321854755 --- /dev/null +++ b/examples/nextjs/base/public/icons/github-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/base/public/icons/linkedin-icon.svg b/examples/nextjs/base/public/icons/linkedin-icon.svg new file mode 100644 index 000000000000..17baff160537 --- /dev/null +++ b/examples/nextjs/base/public/icons/linkedin-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/base/public/icons/nextjs-icon.svg b/examples/nextjs/base/public/icons/nextjs-icon.svg new file mode 100644 index 000000000000..6f04d140b3a6 --- /dev/null +++ b/examples/nextjs/base/public/icons/nextjs-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/examples/nextjs/base/public/icons/pankod-icon.svg b/examples/nextjs/base/public/icons/pankod-icon.svg new file mode 100644 index 000000000000..36e3e97daba3 --- /dev/null +++ b/examples/nextjs/base/public/icons/pankod-icon.svg @@ -0,0 +1,33 @@ + + + + Dark + Created with Sketch. + + + + + + + + \ No newline at end of file diff --git a/examples/nextjs/base/public/icons/twitter-icon.svg b/examples/nextjs/base/public/icons/twitter-icon.svg new file mode 100644 index 000000000000..08022b6888fd --- /dev/null +++ b/examples/nextjs/base/public/icons/twitter-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/base/public/icons/youtube-icon.svg b/examples/nextjs/base/public/icons/youtube-icon.svg new file mode 100644 index 000000000000..b9403e9982dd --- /dev/null +++ b/examples/nextjs/base/public/icons/youtube-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/base/public/meta.json b/examples/nextjs/base/public/meta.json new file mode 100644 index 000000000000..fce9e68e7ccf --- /dev/null +++ b/examples/nextjs/base/public/meta.json @@ -0,0 +1,15 @@ +{ + "name": "refine-next", + "plugins": [ + { + "name": "CSS / styled-jsx", + "description": "Next.js comes with built-in support for CSS and styled-jsx. Styled-jsx is full, scoped and component-friendly CSS support for JSX (rendered on the server or the client).", + "url": "https://github.com/vercel/styled-jsx" + }, + { + "name": "ESLint", + "description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease.", + "url": "https://eslint.org/docs/user-guide/getting-started" + } + ] +} diff --git a/examples/refine-next/src/authProvider.ts b/examples/nextjs/base/src/authProvider.ts similarity index 100% rename from examples/refine-next/src/authProvider.ts rename to examples/nextjs/base/src/authProvider.ts diff --git a/examples/nextjs/base/src/components/index.ts b/examples/nextjs/base/src/components/index.ts new file mode 100644 index 000000000000..4f89127aa97d --- /dev/null +++ b/examples/nextjs/base/src/components/index.ts @@ -0,0 +1 @@ +export * from "./posts"; diff --git a/examples/nextjs/base/src/components/posts/create.tsx b/examples/nextjs/base/src/components/posts/create.tsx new file mode 100644 index 000000000000..d2f2df7c4733 --- /dev/null +++ b/examples/nextjs/base/src/components/posts/create.tsx @@ -0,0 +1,48 @@ +import { + useForm, + useSelect, + Create, + Form, + Select, + Input, +} from "@pankod/refine-antd"; +import { IPost } from "src/interfaces"; + +export const PostCreate: React.FC = () => { + const { formProps, saveButtonProps } = useForm(); + + const { selectProps: categorySelectProps } = useSelect({ + resource: "categories", + }); + + return ( + +
+ + + + + + +
+
+ ); +}; diff --git a/examples/nextjs/base/src/components/posts/edit.tsx b/examples/nextjs/base/src/components/posts/edit.tsx new file mode 100644 index 000000000000..270c8f3444e4 --- /dev/null +++ b/examples/nextjs/base/src/components/posts/edit.tsx @@ -0,0 +1,49 @@ +import { + useForm, + useSelect, + Edit, + Form, + Input, + Select, +} from "@pankod/refine-antd"; +import { IPost } from "src/interfaces"; + +export const PostEdit: React.FC = () => { + const { formProps, saveButtonProps, queryResult } = useForm(); + + const { selectProps: categorySelectProps } = useSelect({ + resource: "categories", + defaultValue: queryResult?.data?.data?.category.id, + }); + + return ( + +
+ + + + + + +
+
+ ); +}; diff --git a/examples/nextjs/base/src/components/posts/index.ts b/examples/nextjs/base/src/components/posts/index.ts new file mode 100644 index 000000000000..ad928d13bc11 --- /dev/null +++ b/examples/nextjs/base/src/components/posts/index.ts @@ -0,0 +1,4 @@ +export * from "./list"; +export * from "./edit"; +export * from "./create"; +export * from "./show"; diff --git a/examples/nextjs/base/src/components/posts/list.tsx b/examples/nextjs/base/src/components/posts/list.tsx new file mode 100644 index 000000000000..c90f692bea1d --- /dev/null +++ b/examples/nextjs/base/src/components/posts/list.tsx @@ -0,0 +1,54 @@ +import { GetListResponse } from "@pankod/refine-core"; +import { + useTable, + List, + Table, + Space, + EditButton, + ShowButton, + DeleteButton, +} from "@pankod/refine-antd"; +import type { IResourceComponentsProps } from "@pankod/refine-core"; +import { IPost } from "../../interfaces"; + +export const PostList: React.FC< + IResourceComponentsProps> +> = ({ initialData }) => { + const { tableProps } = useTable({ + queryOptions: { + initialData, + }, + }); + + return ( + + + + + + + title="Actions" + dataIndex="actions" + render={(_text, record): React.ReactNode => { + return ( + + + + + + ); + }} + /> +
+
+ ); +}; diff --git a/examples/nextjs/base/src/components/posts/show.tsx b/examples/nextjs/base/src/components/posts/show.tsx new file mode 100644 index 000000000000..fb5941490b48 --- /dev/null +++ b/examples/nextjs/base/src/components/posts/show.tsx @@ -0,0 +1,34 @@ +import { useOne, useShow } from "@pankod/refine-core"; +import { Show, Typography, Tag } from "@pankod/refine-antd"; +import { ICategory } from "src/interfaces"; + +const { Title, Text } = Typography; + +export const PostShow: React.FC = () => { + const { queryResult } = useShow(); + const { data, isLoading } = queryResult; + const record = data?.data; + + const { data: categoryData } = useOne({ + resource: "categories", + id: record?.category.id || "", + queryOptions: { + enabled: !!record?.category.id, + }, + }); + + return ( + + Title + {record?.title} + + Status + + {record?.status} + + + Category + {categoryData?.data.title} + + ); +}; diff --git a/examples/nextjs/base/src/constants.ts b/examples/nextjs/base/src/constants.ts new file mode 100644 index 000000000000..4862046b1a27 --- /dev/null +++ b/examples/nextjs/base/src/constants.ts @@ -0,0 +1 @@ +export const API_URL = "https://api.fake-rest.refine.dev"; diff --git a/examples/nextjs/base/src/interfaces/index.ts b/examples/nextjs/base/src/interfaces/index.ts new file mode 100644 index 000000000000..41f9a9eec9e6 --- /dev/null +++ b/examples/nextjs/base/src/interfaces/index.ts @@ -0,0 +1,12 @@ +export interface IPost { + id: string; + title: string; + status: "published" | "draft" | "rejected"; + createdAt: string; + category: ICategory; +} + +export interface ICategory { + id: string; + title: string; +} diff --git a/examples/nextjs/base/src/styles/global.css b/examples/nextjs/base/src/styles/global.css new file mode 100644 index 000000000000..7c7e54918d93 --- /dev/null +++ b/examples/nextjs/base/src/styles/global.css @@ -0,0 +1,9 @@ +html, +body { + margin: 0; + padding: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, + "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Noto Color Emoji"; +} diff --git a/examples/refine-next/tsconfig.json b/examples/nextjs/base/tsconfig.json similarity index 100% rename from examples/refine-next/tsconfig.json rename to examples/nextjs/base/tsconfig.json diff --git a/examples/store/package.json b/examples/store/package.json index 10209ce755c1..4d4e56b47615 100644 --- a/examples/store/package.json +++ b/examples/store/package.json @@ -26,7 +26,7 @@ "keen-slider": "^6.6.3", "lodash.random": "^3.2.0", "lodash.throttle": "^4.1.1", - "next": "^12.1.6", + "next": "^13.0.6", "next-compose-plugins": "^2.2.1", "next-themes": "^0.2.0", "nookies": "^2.5.2", diff --git a/examples/store/src/components/product/ProductCard/ProductCard.tsx b/examples/store/src/components/product/ProductCard/ProductCard.tsx index c8953675c6ce..53097413934c 100644 --- a/examples/store/src/components/product/ProductCard/ProductCard.tsx +++ b/examples/store/src/components/product/ProductCard/ProductCard.tsx @@ -14,7 +14,7 @@ interface ProductCardProps { noNameTag?: boolean; imgProps?: Omit< ImageProps, - "src" | "layout" | "placeholder" | "blurDataURL" + "src" | "layout" | "placeholder" | "blurDataURL" | "alt" >; variant?: "default" | "slim" | "simple"; } diff --git a/packages/live-previews/package.json b/packages/live-previews/package.json index 81afc6db2908..6c9614cd08eb 100644 --- a/packages/live-previews/package.json +++ b/packages/live-previews/package.json @@ -22,7 +22,7 @@ "@pankod/refine-chakra-ui": "^1.0.0", "@pankod/refine-inferencer": "^1.0.0", "lz-string": "^1.4.4", - "next": "^12.1.6", + "next": "^13.0.6", "react": "^18.0.0", "react-dom": "^18.0.0", "react-live": "github:aliemir/react-live" diff --git a/packages/nextjs-router/package.json b/packages/nextjs-router/package.json index 22c2de32168e..698d8f3a2d8b 100644 --- a/packages/nextjs-router/package.json +++ b/packages/nextjs-router/package.json @@ -1,54 +1,85 @@ { - "name": "@pankod/refine-nextjs-router", - "description": "refine Next.js router provider. refine is a React-based framework for building internal tools, rapidly. It ships with Ant Design System, an enterprise-level UI toolkit.", - "version": "3.38.0", - "license": "MIT", - "main": "dist/index.js", - "typings": "dist/index.d.ts", - "private": false, - "files": [ - "dist", - "src" - ], - "engines": { - "node": ">=10" - }, - "scripts": { - "start": "tsup --watch --format esm,cjs,iife --legacy-output", - "build": "tsup --format esm,cjs,iife --legacy-output", - "test": "jest --passWithNoTests --runInBand", - "prepare": "npm run build" - }, - "author": "refine", - "module": "dist/esm/index.js", - "devDependencies": { - "@pankod/refine-core": "^3.74.0", - "@esbuild-plugins/node-resolve": "^0.1.4", - "@types/qs": "^6.9.7", - "jest": "^27.5.1", - "next": "^12.1.6", - "ts-jest": "^27.1.3", - "tslib": "^2.3.1", - "tsup": "^5.11.13" - }, - "dependencies": { - "qs": "^6.10.1" - }, - "peerDependencies": { - "@pankod/refine-core": "^3.23.2", - "next": "*", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0", - "@types/react": "^17.0.0 || ^18.0.0", - "@types/react-dom": "^17.0.0 || ^18.0.0" - }, - "repository": { - "type": "git", - "url": "https://github.com/refinedev/refine.git", - "directory": "packages/nextjs-router" - }, - "gitHead": "17dd5af3e4c9f7f09e1e707362c6f5a9d1cab8a8", - "publishConfig": { - "access": "public" - } + "name": "@pankod/refine-nextjs-router", + "description": "refine Next.js router provider. refine is a React-based framework for building internal tools, rapidly. It ships with Ant Design System, an enterprise-level UI toolkit.", + "version": "3.38.0", + "license": "MIT", + "private": false, + "main": "dist/index.js", + "module": "dist/esm/index.js", + "typings": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/esm/index.js", + "require": "./dist/index.js" + }, + "./app": { + "types": "./dist/app/index.d.ts", + "import": "./dist/esm/app.js", + "require": "./dist/app.js" + }, + "./pages": { + "types": "./dist/pages/index.d.ts", + "import": "./dist/esm/pages.js", + "require": "./dist/pages.js" + } + }, + "typesVersions": { + "*": { + ".": [ + "dist/index.d.ts" + ], + "app": [ + "dist/app/index.d.ts" + ], + "pages": [ + "dist/pages/index.d.ts" + ] + } + }, + "files": [ + "dist", + "src" + ], + "engines": { + "node": ">=10" + }, + "scripts": { + "start": "tsup --watch --format esm,cjs,iife --legacy-output", + "build": "tsup --format esm,cjs,iife --legacy-output", + "test": "jest --passWithNoTests --runInBand", + "prepare": "npm run build" + }, + "author": "refine", + "devDependencies": { + "@pankod/refine-core": "^3.74.0", + "@esbuild-plugins/node-resolve": "^0.1.4", + "@types/qs": "^6.9.7", + "jest": "^27.5.1", + "next": "^13.0.6", + "ts-jest": "^27.1.3", + "tslib": "^2.3.1", + "tsup": "^5.11.13" + }, + "dependencies": { + "qs": "^6.10.1", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@pankod/refine-core": "^3.23.2", + "next": "*", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/refinedev/refine.git", + "directory": "packages/nextjs-router" + }, + "gitHead": "17dd5af3e4c9f7f09e1e707362c6f5a9d1cab8a8", + "publishConfig": { + "access": "public" + } } diff --git a/packages/nextjs-router/src/app/index.ts b/packages/nextjs-router/src/app/index.ts new file mode 100644 index 000000000000..42a8617f737a --- /dev/null +++ b/packages/nextjs-router/src/app/index.ts @@ -0,0 +1,5 @@ +export { routerProvider as default } from "./router-provider"; +export { NextRouteComponent } from "./route-component"; + +export { RefineLink as Link } from "src/common/refine-link"; +export { handleRefineParams } from "src/common/handle-refine-params"; diff --git a/packages/nextjs-router/src/app/prompt.tsx b/packages/nextjs-router/src/app/prompt.tsx new file mode 100644 index 000000000000..33817f5c29d2 --- /dev/null +++ b/packages/nextjs-router/src/app/prompt.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import warnOnce from "warn-once"; + +import type { PromptProps } from "@pankod/refine-core"; + +export const Prompt: React.FC = ({ when }) => { + React.useEffect(() => { + warnOnce( + process.env.NODE_ENV === "development" && (when ?? false), + "Due to `router.events` not being available in Next.js 13, the `Prompt` component is not working. `warnWhenUnsavedChanges` is not supported.", + ); + }, [when]); + + return null; +}; diff --git a/packages/nextjs-router/src/app/route-component.tsx b/packages/nextjs-router/src/app/route-component.tsx new file mode 100644 index 000000000000..2336c5bf7c77 --- /dev/null +++ b/packages/nextjs-router/src/app/route-component.tsx @@ -0,0 +1,204 @@ +import React from "react"; +import { + useRefineContext, + LayoutWrapper, + ErrorComponent, + useResource, + LoginPage as DefaultLoginPage, + CanAccess, + useRouterContext, +} from "@pankod/refine-core"; +import type { ResourceRouterParams } from "@pankod/refine-core"; + +export function NextRouteComponent( + this: { initialRoute?: string }, + props: any, +): React.ReactNode { + const { useHistory, useLocation, useParams } = useRouterContext(); + const { resources } = useResource(); + const { push } = useHistory(); + const { + resource: routeResourceName, + action, + id, + } = useParams(); + + const { pathname } = useLocation(); + const { DashboardPage, catchAll, LoginPage } = useRefineContext(); + + const resource = resources.find( + (res) => + res.name === routeResourceName || res.route === routeResourceName, + ); + + const isServer = typeof window !== "undefined"; + + const renderLoginRouteElement = (): JSX.Element => { + if (LoginPage) return ; + return ; + }; + + if (routeResourceName === "login") { + return renderLoginRouteElement(); + } + + if (pathname === "/") { + if (DashboardPage) { + return ( + + } + params={{ + resource, + }} + > + + + + ); + } else { + if (isServer) { + if (typeof this !== "undefined" && this.initialRoute) { + push(this.initialRoute); + } else { + // push(`${resources.find((el) => el.list).route}`); + + /* + * the above line is a better solution for the initial route + * but in next.js, users can have custom pages through file system + * which makes `list` component of the resource redundant. + * for these cases, we need to redirect to the first resource + * in the resources array, no matter if it has a list component or not. + */ + + push(`/${resources[0].route}`); + } + } + return null; + } + } + + if (resource) { + const { + list, + create, + edit, + show, + name, + canCreate, + canEdit, + canShow, + canDelete, + options, + } = resource; + + const List = list ?? (() => null); + const Create = create ?? (() => null); + const Edit = edit ?? (() => null); + const Show = show ?? (() => null); + + const renderCrud = () => { + switch (action) { + case undefined: { + return ( + } + params={{ + resource, + }} + > + + + ); + } + + case "create": + case "clone": { + return ( + } + params={{ + resource, + }} + > + + + ); + } + + case "edit": { + return ( + } + > + + + ); + } + + case "show": { + return ( + } + > + + + ); + } + } + }; + + return {renderCrud()}; + } + return catchAll ? ( + <>{catchAll} + ) : ( + + + + ); +} diff --git a/packages/nextjs-router/src/app/router-component.tsx b/packages/nextjs-router/src/app/router-component.tsx new file mode 100644 index 000000000000..d3ada2d099b7 --- /dev/null +++ b/packages/nextjs-router/src/app/router-component.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { IRouterProvider, useAuthenticated } from "@pankod/refine-core"; +import { useHistory } from "./use-history"; + +export const RouterComponent: NonNullable< + IRouterProvider["RouterComponent"] +> = ({ children }) => { + const { replace } = useHistory(); + const { isError, isFetching } = useAuthenticated(); + + React.useEffect(() => { + if (isError && !isFetching) { + replace("/login"); + } + }, [isError, isFetching]); + + return children; +}; diff --git a/packages/nextjs-router/src/app/router-provider.ts b/packages/nextjs-router/src/app/router-provider.ts new file mode 100644 index 000000000000..68e9b6522415 --- /dev/null +++ b/packages/nextjs-router/src/app/router-provider.ts @@ -0,0 +1,24 @@ +import { IRouterProvider } from "@pankod/refine-core"; + +import { useHistory } from "./use-history"; +import { useLocation } from "./use-location"; +import { useParams } from "./use-params"; + +import { Prompt } from "./prompt"; +import { RefineLink as Link } from "src/common/refine-link"; +import { RouterComponent } from "./router-component"; + +export function routerProvider(this: { + params?: Record; +}): IRouterProvider { + return { + useHistory, + useLocation, + useParams: useParams.bind({ + params: this.params, + }), + Prompt, + Link, + RouterComponent, + }; +} diff --git a/packages/nextjs-router/src/app/use-history.ts b/packages/nextjs-router/src/app/use-history.ts new file mode 100644 index 000000000000..65b2b2e4dfed --- /dev/null +++ b/packages/nextjs-router/src/app/use-history.ts @@ -0,0 +1,14 @@ +import { useRouter } from "next/navigation"; + +import type { IRouterProvider } from "@pankod/refine-core"; + +export const useHistory: IRouterProvider["useHistory"] = () => { + const router = useRouter(); + const { push, replace, back } = router; + + return { + push, + replace, + goBack: back, + }; +}; diff --git a/packages/nextjs-router/src/app/use-location.ts b/packages/nextjs-router/src/app/use-location.ts new file mode 100644 index 000000000000..62ed28493be2 --- /dev/null +++ b/packages/nextjs-router/src/app/use-location.ts @@ -0,0 +1,24 @@ +import { usePathname, useSearchParams } from "next/navigation"; + +import type { IRouterProvider } from "@pankod/refine-core"; + +export const useLocation: IRouterProvider["useLocation"] = () => { + const pathname = usePathname(); + const query = useSearchParams(); + + const sliceLength = Math.min( + ...[ + (pathname ?? "").indexOf("?") > 0 + ? (pathname ?? "").indexOf("?") + : (pathname ?? "").length, + (pathname ?? "").indexOf("#") > 0 + ? (pathname ?? "").indexOf("#") + : (pathname ?? "").length, + ], + ); + + return { + pathname: (pathname ?? "").slice(0, sliceLength), + search: `?${query.toString()}`, + }; +}; diff --git a/packages/nextjs-router/src/app/use-params.ts b/packages/nextjs-router/src/app/use-params.ts new file mode 100644 index 000000000000..524ac87cf7da --- /dev/null +++ b/packages/nextjs-router/src/app/use-params.ts @@ -0,0 +1,12 @@ +import { handleUseParams, IRouterProvider } from "@pankod/refine-core"; + +import { handleRefineParams } from "src/common/handle-refine-params"; + +export const useParams: IRouterProvider["useParams"] = function (this: { + params?: Record; +}) { + return { + ...handleRefineParams(this?.params?.refine ?? []), + ...handleUseParams(this?.params ?? {}), + } as Params; +}; diff --git a/packages/nextjs-router/src/useParams.ts b/packages/nextjs-router/src/common/handle-refine-params.ts similarity index 73% rename from packages/nextjs-router/src/useParams.ts rename to packages/nextjs-router/src/common/handle-refine-params.ts index f841c94ba34b..d9fc051d485c 100644 --- a/packages/nextjs-router/src/useParams.ts +++ b/packages/nextjs-router/src/common/handle-refine-params.ts @@ -1,10 +1,4 @@ -import { useRouter } from "next/router"; -import { - handleUseParams, - IRouterProvider, - ResourceRouterParams, - RouteAction, -} from "@pankod/refine-core"; +import { ResourceRouterParams, RouteAction } from "@pankod/refine-core"; const actions: RouteAction[] = ["clone", "create", "edit", "show"]; @@ -49,14 +43,3 @@ export const handleRefineParams = ( refineParams?: string | string[], ): ResourceRouterParams | Record => Array.isArray(refineParams) ? composeParams(refineParams) : {}; - -export const useParams: IRouterProvider["useParams"] = () => { - const router = useRouter(); - - const { query } = router; - const { refine } = query; - return { - ...handleRefineParams(refine), - ...handleUseParams(query), - } as Params; -}; diff --git a/packages/nextjs-router/src/refineLink.tsx b/packages/nextjs-router/src/common/refine-link.tsx similarity index 100% rename from packages/nextjs-router/src/refineLink.tsx rename to packages/nextjs-router/src/common/refine-link.tsx diff --git a/packages/nextjs-router/src/index.ts b/packages/nextjs-router/src/index.ts index e80326fb1130..f358744f8526 100644 --- a/packages/nextjs-router/src/index.ts +++ b/packages/nextjs-router/src/index.ts @@ -1,8 +1,10 @@ -export { RouterProvider as default } from "./routerProvider"; +import routerProvider, { + checkAuthentication, + handleRefineParams, + NextRouteComponent, + Link, +} from "./pages"; -export { RefineLink as Link } from "./refineLink"; +export default routerProvider; -export * from "./nextRouteComponent"; -export * from "./checkAuthentication"; - -export { handleRefineParams } from "./useParams"; +export { checkAuthentication, handleRefineParams, NextRouteComponent, Link }; diff --git a/packages/nextjs-router/src/checkAuthentication.ts b/packages/nextjs-router/src/pages/check-authentication.ts similarity index 100% rename from packages/nextjs-router/src/checkAuthentication.ts rename to packages/nextjs-router/src/pages/check-authentication.ts diff --git a/packages/nextjs-router/src/pages/index.ts b/packages/nextjs-router/src/pages/index.ts new file mode 100644 index 000000000000..ba11c8dc1767 --- /dev/null +++ b/packages/nextjs-router/src/pages/index.ts @@ -0,0 +1,7 @@ +export { routerProvider as default } from "./router-provider"; + +export { handleRefineParams } from "src/common/handle-refine-params"; +export { RefineLink as Link } from "src/common/refine-link"; + +export { NextRouteComponent } from "./route-component"; +export { checkAuthentication } from "./check-authentication"; diff --git a/packages/nextjs-router/src/prompt.tsx b/packages/nextjs-router/src/pages/prompt.tsx similarity index 100% rename from packages/nextjs-router/src/prompt.tsx rename to packages/nextjs-router/src/pages/prompt.tsx diff --git a/packages/nextjs-router/src/nextRouteComponent.tsx b/packages/nextjs-router/src/pages/route-component.tsx similarity index 98% rename from packages/nextjs-router/src/nextRouteComponent.tsx rename to packages/nextjs-router/src/pages/route-component.tsx index f4b4e4ea4c33..29702f694781 100644 --- a/packages/nextjs-router/src/nextRouteComponent.tsx +++ b/packages/nextjs-router/src/pages/route-component.tsx @@ -9,9 +9,9 @@ import { } from "@pankod/refine-core"; import type { ResourceRouterParams } from "@pankod/refine-core"; -import { RouterProvider } from "./routerProvider"; +import { routerProvider } from "./router-provider"; -const { useHistory, useLocation, useParams } = RouterProvider; +const { useHistory, useLocation, useParams } = routerProvider; type NextRouteComponentProps = { initialData?: any; diff --git a/packages/nextjs-router/src/pages/router-provider.ts b/packages/nextjs-router/src/pages/router-provider.ts new file mode 100644 index 000000000000..e7c9ae743b73 --- /dev/null +++ b/packages/nextjs-router/src/pages/router-provider.ts @@ -0,0 +1,15 @@ +import { IRouterProvider } from "@pankod/refine-core"; + +import { Prompt } from "./prompt"; +import { RefineLink as Link } from "src/common/refine-link"; +import { useParams } from "./use-params"; +import { useLocation } from "./use-location"; +import { useHistory } from "./use-history"; + +export const routerProvider: IRouterProvider = { + useHistory, + useLocation, + useParams, + Prompt, + Link, +}; diff --git a/packages/nextjs-router/src/pages/use-history.ts b/packages/nextjs-router/src/pages/use-history.ts new file mode 100644 index 000000000000..37edea0f20ba --- /dev/null +++ b/packages/nextjs-router/src/pages/use-history.ts @@ -0,0 +1,13 @@ +import { useRouter } from "next/router"; + +import type { IRouterProvider } from "@pankod/refine-core"; + +export const useHistory: IRouterProvider["useHistory"] = () => { + const router = useRouter(); + const { push, replace, back } = router; + return { + push, + replace, + goBack: back, + }; +}; diff --git a/packages/nextjs-router/src/pages/use-location.ts b/packages/nextjs-router/src/pages/use-location.ts new file mode 100644 index 000000000000..0e65398e44e0 --- /dev/null +++ b/packages/nextjs-router/src/pages/use-location.ts @@ -0,0 +1,23 @@ +import qs from "qs"; +import { useRouter } from "next/router"; + +import type { IRouterProvider } from "@pankod/refine-core"; + +export const useLocation: IRouterProvider["useLocation"] = () => { + const router = useRouter(); + const { query, asPath } = router; + + const queryParams = qs.stringify(query); + + const sliceLength = Math.min( + ...[ + asPath.indexOf("?") > 0 ? asPath.indexOf("?") : asPath.length, + asPath.indexOf("#") > 0 ? asPath.indexOf("#") : asPath.length, + ], + ); + + return { + pathname: asPath.slice(0, sliceLength), + search: queryParams && `?${queryParams}`, + }; +}; diff --git a/packages/nextjs-router/src/pages/use-params.ts b/packages/nextjs-router/src/pages/use-params.ts new file mode 100644 index 000000000000..1c4fb8b04ddf --- /dev/null +++ b/packages/nextjs-router/src/pages/use-params.ts @@ -0,0 +1,15 @@ +import { useRouter } from "next/router"; +import { handleUseParams, IRouterProvider } from "@pankod/refine-core"; + +import { handleRefineParams } from "src/common/handle-refine-params"; + +export const useParams: IRouterProvider["useParams"] = () => { + const router = useRouter(); + + const { query } = router; + const { refine } = query; + return { + ...handleRefineParams(refine), + ...handleUseParams(query), + } as Params; +}; diff --git a/packages/nextjs-router/src/routerProvider.ts b/packages/nextjs-router/src/routerProvider.ts deleted file mode 100644 index f8a4fcc361d1..000000000000 --- a/packages/nextjs-router/src/routerProvider.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useRouter } from "next/router"; -import qs from "qs"; - -import { IRouterProvider } from "@pankod/refine-core"; - -import { Prompt } from "./prompt"; -import { RefineLink } from "./refineLink"; -import { useParams } from "./useParams"; - -export const RouterProvider: IRouterProvider = { - useHistory: () => { - const router = useRouter(); - const { push, replace, back } = router; - return { - push, - replace, - goBack: back, - }; - }, - useLocation: () => { - const router = useRouter(); - const { query, asPath } = router; - - const queryParams = qs.stringify(query); - - const sliceLength = Math.min( - ...[ - asPath.indexOf("?") > 0 ? asPath.indexOf("?") : asPath.length, - asPath.indexOf("#") > 0 ? asPath.indexOf("#") : asPath.length, - ], - ); - - return { - pathname: asPath.slice(0, sliceLength), - search: queryParams && `?${queryParams}`, - }; - }, - useParams, - Prompt, - Link: RefineLink, -}; diff --git a/packages/nextjs-router/tsup.config.ts b/packages/nextjs-router/tsup.config.ts index 12c9fbc7d258..f7fb9c31758f 100644 --- a/packages/nextjs-router/tsup.config.ts +++ b/packages/nextjs-router/tsup.config.ts @@ -2,7 +2,12 @@ import { defineConfig } from "tsup"; import { NodeResolvePlugin } from "@esbuild-plugins/node-resolve"; export default defineConfig({ - entry: ["src/index.ts"], + entry: { + index: "src/index.ts", + pages: "src/pages/index.ts", + app: "src/app/index.ts", + }, + outDir: "dist", splitting: false, sourcemap: true, clean: false,