From 98db0c29d2ef5df2175a79049360897224e02bd1 Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:37:16 +0300 Subject: [PATCH 1/6] docs --- .../ReactRouter.js | 0 .../ReactRouter.tsx | 0 .../ReactRouter.tsx.preview | 0 .../core/integrations/nextjs-approuter.md | 233 ++++ .../core/integrations/nextjs-pagesrouter.md | 414 +++++++ .../toolpad/core/integrations/react-router.md | 399 +++++++ .../toolpad/core/introduction/integration.md | 1062 ----------------- docs/data/toolpad/core/pages.ts | 22 +- .../core/integrations/nextjs-approuter.js | 7 + .../core/integrations/nextjs-pagesrouter.js | 7 + .../react-router.js} | 2 +- 11 files changed, 1079 insertions(+), 1067 deletions(-) rename docs/data/toolpad/core/{introduction => integrations}/ReactRouter.js (100%) rename docs/data/toolpad/core/{introduction => integrations}/ReactRouter.tsx (100%) rename docs/data/toolpad/core/{introduction => integrations}/ReactRouter.tsx.preview (100%) create mode 100644 docs/data/toolpad/core/integrations/nextjs-approuter.md create mode 100644 docs/data/toolpad/core/integrations/nextjs-pagesrouter.md create mode 100644 docs/data/toolpad/core/integrations/react-router.md delete mode 100644 docs/data/toolpad/core/introduction/integration.md create mode 100644 docs/pages/toolpad/core/integrations/nextjs-approuter.js create mode 100644 docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js rename docs/pages/toolpad/core/{introduction/integration.js => integrations/react-router.js} (65%) diff --git a/docs/data/toolpad/core/introduction/ReactRouter.js b/docs/data/toolpad/core/integrations/ReactRouter.js similarity index 100% rename from docs/data/toolpad/core/introduction/ReactRouter.js rename to docs/data/toolpad/core/integrations/ReactRouter.js diff --git a/docs/data/toolpad/core/introduction/ReactRouter.tsx b/docs/data/toolpad/core/integrations/ReactRouter.tsx similarity index 100% rename from docs/data/toolpad/core/introduction/ReactRouter.tsx rename to docs/data/toolpad/core/integrations/ReactRouter.tsx diff --git a/docs/data/toolpad/core/introduction/ReactRouter.tsx.preview b/docs/data/toolpad/core/integrations/ReactRouter.tsx.preview similarity index 100% rename from docs/data/toolpad/core/introduction/ReactRouter.tsx.preview rename to docs/data/toolpad/core/integrations/ReactRouter.tsx.preview diff --git a/docs/data/toolpad/core/integrations/nextjs-approuter.md b/docs/data/toolpad/core/integrations/nextjs-approuter.md new file mode 100644 index 00000000000..6513e571f1d --- /dev/null +++ b/docs/data/toolpad/core/integrations/nextjs-approuter.md @@ -0,0 +1,233 @@ +--- +title: Next.js - Integration +--- + +# Next.js App Router + +

This guide walks you through adding Toolpad Core to an existing Next.js app.

+ +## Wrap your application with `AppProvider` + +In your root layout file (for example, `app/layout.tsx`), wrap your application with the `AppProvider`: + +```tsx title="app/layout.tsx" +import { AppProvider } from '@toolpad/core/AppProvider'; +import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter'; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} +``` + +You can find details on the `AppProvider` props on the [AppProvider](/toolpad/core/react-app-provider/) page. + +:::info +The `AppRouterCacheProvider` component is not required to use Toolpad Core, but it's recommended to use it to ensure that the styles are appended to the `` and not rendering in the ``. + +See the [MaterialĀ UI Next.js integration docs](https://mui.com/material-ui/integrations/nextjs/) for more details. +::: + +## Create a dashboard layout + +Create a layout file for your dashboard pages (for example, `app/(dashboard)/layout.tsx`): + +```tsx title="app/(dashboard)/layout.tsx" +import * as React from 'react'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { PageContainer } from '@toolpad/core/PageContainer'; + +export default function DashboardPagesLayout(props: { children: React.ReactNode }) { + return ( + + {props.children} + + ); +} +``` + +The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation. + +## Create a dashboard page + +Now you can create pages within your dashboard. For example, a home page (`app/(dashboard)/page.tsx`): + +```tsx title="app/(dashboard)/page.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function Page() { + return Welcome to a page in the dashboard!; +} +``` + +That's it! You have now integrated Toolpad Core into your Next.js app. + +## (Optional) Add a second page + +Create a new page in the dashboard, for example, `app/(dashboard)/orders/page.tsx`: + +```tsx title="app/(dashboard)/orders/page.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function OrdersPage() { + return Welcome to the orders page!; +} +``` + +To add this page to the navigation, add it to the `NAVIGATION` variable: + +```ts title="app/layout.tsx" +export const NAVIGATION = [ + // ... + { + segment: 'orders', + title: 'Orders', + icon: , + }, + // ... +]; +``` + +## (Optional) Set up authentication + +If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup: + +### Install the dependencies + +```bash +npm install next-auth@beta +``` + +### Create an `auth.ts` file + +```ts title="auth.ts" +import NextAuth from 'next-auth'; +import GitHub from 'next-auth/providers/github'; +import type { Provider } from 'next-auth/providers'; + +const providers: Provider[] = [ + GitHub({ + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }), +]; + +export const providerMap = providers.map((provider) => { + if (typeof provider === 'function') { + const providerData = provider(); + return { id: providerData.id, name: providerData.name }; + } + return { id: provider.id, name: provider.name }; +}); + +export const { handlers, auth, signIn, signOut } = NextAuth({ + providers, + secret: process.env.AUTH_SECRET, + pages: { + signIn: '/auth/signin', + }, + callbacks: { + authorized({ auth: session, request: { nextUrl } }) { + const isLoggedIn = !!session?.user; + const isPublicPage = nextUrl.pathname.startsWith('/public'); + + if (isPublicPage || isLoggedIn) { + return true; + } + + return false; // Redirect unauthenticated users to login page + }, + }, +}); +``` + +### Create a sign-in page + +Use the `SignInPage` component to add a sign-in page to your app. For example, `app/auth/signin/page.tsx`: + +```tsx title="app/auth/signin/page.tsx" +import * as React from 'react'; +import { SignInPage, type AuthProvider } from '@toolpad/core/SignInPage'; +import { AuthError } from 'next-auth'; +import { providerMap, signIn } from '../../../auth'; + +export default function SignIn() { + return ( + { + 'use server'; + try { + return await signIn(provider.id, { + redirectTo: callbackUrl ?? '/', + }); + } catch (error) { + // The desired flow for successful sign in in all cases + // and unsuccessful sign in for OAuth providers will cause a `redirect`, + // and `redirect` is a throwing function, so we need to re-throw + // to allow the redirect to happen + // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642 + // Detect a `NEXT_REDIRECT` error and re-throw it + if (error instanceof Error && error.message === 'NEXT_REDIRECT') { + throw error; + } + // Handle Auth.js errors + if (error instanceof AuthError) { + return { + error: error.message, + type: error.type, + }; + } + // An error boundary must exist to handle unknown errors + return { + error: 'Something went wrong.', + type: 'UnknownError', + }; + } + }} + /> + ); +} +``` + +### Create a route handler for sign-in + +`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth]/route.ts`: + +```ts title="app/api/auth/[...nextauth]/route.ts" +import { handlers } from '../../../../auth'; + +export const { GET, POST } = handlers; +``` + +### Add a middleware + +Add a middleware to your app to protect your dashboard pages: + +```ts title="middleware.ts" +export { auth as middleware } from './auth'; + +export const config = { + // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher + matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], +}; +``` + +That's it! You now have Toolpad Core integrated into your Next.js App Router app with authentication setup: + +{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-app.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-app-dark.png", "alt": "Next.js App Router with Toolpad Core", "caption": "Next.js App Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }} + +:::info +For a full working example with authentication included, see the [Toolpad Core Next.js App with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs/) +::: diff --git a/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md b/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md new file mode 100644 index 00000000000..69f5a4a2086 --- /dev/null +++ b/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md @@ -0,0 +1,414 @@ +--- +title: Next.js - Integration +--- + +# Next.js Pages Router + +

This guide walks you through adding Toolpad Core to an existing Next.js app.

+ +## Wrap your application with `AppProvider` + +In your root layout file (for example, `pages/_app.tsx`), wrap your application with the `AppProvider`: + +```tsx title="pages/_app.tsx" +import * as React from 'react'; +import { AppProvider } from '@toolpad/core/nextjs'; +import { PageContainer } from '@toolpad/core/PageContainer'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import Head from 'next/head'; +import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; +import type { Navigation } from '@toolpad/core/AppProvider'; + +const NAVIGATION: Navigation = [ + { + kind: 'header', + title: 'Main items', + }, + { + segment: '', + title: 'Dashboard', + icon: , + }, +]; + +const BRANDING = { + title: 'My Toolpad Core App', +}; + +export default function App({ Component }: { Component: React.ElementType }) { + return ( + + + + + + + + + + + + + ); +} +``` + +:::info +The `AppCacheProvider` component is not required to use Toolpad Core, but it's recommended. + +See the [Material UI Next.js Pages Router integration docs](https://mui.com/material-ui/integrations/nextjs/#configuration-2) for more details. +::: + +## Modify `_document.tsx` + +Modify `_document.tsx` to include the `DocumentHeadTags` component: + +```tsx title="pages/_document.tsx" +import * as React from 'react'; +import { + Html, + Head, + Main, + NextScript, + DocumentProps, + DocumentContext, +} from 'next/document'; +import { + DocumentHeadTags, + DocumentHeadTagsProps, + documentGetInitialProps, +} from '@mui/material-nextjs/v14-pagesRouter'; + +export default function Document(props: DocumentProps & DocumentHeadTagsProps) { + return ( + + + + + + +
+ + + + ); +} + +Document.getInitialProps = async (ctx: DocumentContext) => { + const finalProps = await documentGetInitialProps(ctx); + return finalProps; +}; +``` + +## Add a dashboard page + +Create a dashboard page (for example, `pages/index.tsx`): + +```tsx title="pages/index.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function HomePage() { + return Welcome to Toolpad!; +} +``` + +## (Optional) Add a second page + +Create a new page in the dashboard, for example, `pages/orders/index.tsx`: + +```tsx title="pages/orders/index.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function OrdersPage() { + return Welcome to the orders page!; +} +``` + +To add this page to the navigation, add it to the `NAVIGATION` variable: + +```ts title="pages/_app.tsx" +export const NAVIGATION = [ + // ... + { + segment: 'orders', + title: 'Orders', + icon: , + }, + // ... +]; +``` + +## (Optional) Set up authentication + +If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup: + +### Install the dependencies + +```bash +npm install next-auth@beta +``` + +### Create an `auth.ts` file + +```ts title="auth.ts" +import NextAuth from 'next-auth'; +import GitHub from 'next-auth/providers/github'; +import type { Provider } from 'next-auth/providers'; + +const providers: Provider[] = [ + GitHub({ + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }), +]; + +export const providerMap = providers.map((provider) => { + if (typeof provider === 'function') { + const providerData = provider(); + return { id: providerData.id, name: providerData.name }; + } + return { id: provider.id, name: provider.name }; +}); + +export const { handlers, auth } = NextAuth({ + providers, + secret: process.env.AUTH_SECRET, + pages: { + signIn: '/auth/signin', + }, + callbacks: { + authorized({ auth: session, request: { nextUrl } }) { + const isLoggedIn = !!session?.user; + const isPublicPage = nextUrl.pathname.startsWith('/public'); + + if (isPublicPage || isLoggedIn) { + return true; + } + + return false; // Redirect unauthenticated users to login page + }, + }, +}); +``` + +### Modify `_app.tsx` + +Modify `_app.tsx` to include the `authentication` prop and other helpers: + +```tsx title="pages/_app.tsx" +import * as React from 'react'; +import { AppProvider } from '@toolpad/core/nextjs'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { PageContainer } from '@toolpad/core/PageContainer'; +import Head from 'next/head'; +import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; +import type { NextPage } from 'next'; +import type { AppProps } from 'next/app'; +import type { Navigation } from '@toolpad/core/AppProvider'; +import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react'; +import LinearProgress from '@mui/material/LinearProgress'; + +export type NextPageWithLayout

= NextPage & { + getLayout?: (page: React.ReactElement) => React.ReactNode; + requireAuth?: boolean; +}; + +type AppPropsWithLayout = AppProps & { + Component: NextPageWithLayout; +}; + +const NAVIGATION: Navigation = [ + { + kind: 'header', + title: 'Main items', + }, + { + segment: '', + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const BRANDING = { + title: 'My Toolpad Core App', +}; + +const AUTHENTICATION = { + signIn, + signOut, +}; + +function getDefaultLayout(page: React.ReactElement) { + return ( + + {page} + + ); +} + +function RequireAuth({ children }: { children: React.ReactNode }) { + const { status } = useSession(); + + if (status === 'loading') { + return ; + } + + return children; +} + +function AppLayout({ children }: { children: React.ReactNode }) { + const { data: session } = useSession(); + return ( + + + + + + {children} + + + ); +} + +export default function App(props: AppPropsWithLayout) { + const { + Component, + pageProps: { session, ...pageProps }, + } = props; + + const getLayout = Component.getLayout ?? getDefaultLayout; + const requireAuth = Component.requireAuth ?? true; + + let pageContent = getLayout(); + if (requireAuth) { + pageContent = {pageContent}; + } + pageContent = {pageContent}; + + return ( + + {pageContent} + + ); +} +``` + +### Create a sign-in page + +Use the `SignInPage` component to add a sign-in page to your app. For example, `pages/auth/signin.tsx`: + +```tsx title="pages/auth/signin.tsx" +import * as React from 'react'; +import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next'; +import Link from '@mui/material/Link'; +import { SignInPage } from '@toolpad/core/SignInPage'; +import { signIn } from 'next-auth/react'; +import { useRouter } from 'next/router'; +import { auth, providerMap } from '../../auth'; + +export default function SignIn({ + providers, +}: InferGetServerSidePropsType) { + const router = useRouter(); + return ( + { + try { + const signInResponse = await signIn(provider.id, { + callbackUrl: callbackUrl ?? '/', + }); + if (signInResponse && signInResponse.error) { + // Handle Auth.js errors + return { + error: signInResponse.error.message, + type: signInResponse.error, + }; + } + return {}; + } catch (error) { + // An error boundary must exist to handle unknown errors + return { + error: 'Something went wrong.', + type: 'UnknownError', + }; + } + }} + /> + ); +} + +SignIn.getLayout = (page: React.ReactNode) => page; + +SignIn.requireAuth = false; + +export async function getServerSideProps(context: GetServerSidePropsContext) { + const session = await auth(context); + + // If the user is already logged in, redirect. + // Note: Make sure not to redirect to the same page + // To avoid an infinite loop! + if (session) { + return { redirect: { destination: '/' } }; + } + + return { + props: { + providers: providerMap, + }, + }; +} +``` + +### Create a route handler for sign-in + +`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth].ts`: + +```ts title="app/api/auth/[...nextauth].ts" +import { handlers } from '../../../../auth'; + +export const { GET, POST } = handlers; +``` + +:::warning + +Note that this file is a route handler and must be placed in the `app` directory, even if the rest of your app is in the `pages` directory. Know more in the [Auth.js documentation](https://authjs.dev/getting-started/installation#configure). + +::: + +#### Add a middleware + +Add a middleware to your app to protect your dashboard pages: + +```ts title="middleware.ts" +export { auth as middleware } from './auth'; + +export const config = { + // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher + matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], +}; +``` + +That's it! You now have Toolpad Core integrated into your Next.js Pages Router app with authentication setup: + +{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-pages.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-pages-dark.png", "alt": "Next.js Pages Router with Toolpad Core", "caption": "Next.js Pages Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }} + +:::info +For a full working example with authentication included, see the [Toolpad Core Next.js Pages app with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-pages/) +::: diff --git a/docs/data/toolpad/core/integrations/react-router.md b/docs/data/toolpad/core/integrations/react-router.md new file mode 100644 index 00000000000..1b0dde51476 --- /dev/null +++ b/docs/data/toolpad/core/integrations/react-router.md @@ -0,0 +1,399 @@ +--- +title: React router - Integration +--- + +# React Router + +

To integrate Toolpad Core into a single-page app (with Vite, for example) using React Router, follow these steps.

+ +## Wrap all your pages in an `AppProvider` + +In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that will wrap the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`. + +You must use the `` component from `react-router-dom` in this root layout element or component. + +```tsx title="src/main.tsx" +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import App from './App'; +import DashboardPage from './pages'; +import OrdersPage from './pages/orders'; + +const router = createBrowserRouter([ + { + Component: App, // root layout route + }, +]); + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); +``` + +```tsx title="src/App.tsx" +import * as React from 'react'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; +import { AppProvider } from '@toolpad/core/react-router-dom'; +import { Outlet } from 'react-router-dom'; +import type { Navigation } from '@toolpad/core'; + +const NAVIGATION: Navigation = [ + { + kind: 'header', + title: 'Main items', + }, + { + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const BRANDING = { + title: 'My Toolpad Core App', +}; + +export default function App() { + return ( + + + + ); +} +``` + +## Create a dashboard layout + +Create a layout file for your dashboard pages (e.g.: `src/layouts/dashboard.tsx`), to also be used as a layout route with the `` component from `react-router-dom`: + +```tsx title="src/layouts/dashboard.tsx" +import * as React from 'react'; +import { Outlet } from 'react-router-dom'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { PageContainer } from '@toolpad/core/PageContainer'; + +export default function Layout() { + return ( + + + + + + ); +} +``` + +The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation. + +You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created in step 1. + +```tsx title="src/main.tsx" +import Layout from './layouts/dashboard'; + +//... +const router = createBrowserRouter([ + { + Component: App, // root layout route + children: [ + { + path: '/', + Component: Layout, + }, + ], + }, +]); +//... +``` + +## Create pages + +Create a dashboard page (e.g.: `src/pages/index.tsx`) and an orders page (`src/pages/orders.tsx`). + +```tsx title="src/pages/index.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function DashboardPage() { + return Welcome to Toolpad!; +} +``` + +```tsx title="src/pages/orders.tsx" +import * as React from 'react'; +import Typography from '@mui/material/Typography'; + +export default function OrdersPage() { + return Welcome to the Toolpad orders!; +} +``` + +You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created in step 2, they will automatically be wrapped with that dashboard layout: + +```tsx title="src/main.tsx" +import DashboardPage from './pages'; +import OrdersPage from './pages/orders'; + +//... +const router = createBrowserRouter([ + { + Component: App, // root layout route + children: [ + { + path: '/', + Component: Layout, + children: [ + { + path: '', + Component: DashboardPage, + }, + { + path: 'orders', + Component: OrdersPage, + }, + ], + }, + ], + }, +]); +//... +``` + +That's it! You now have Toolpad Core integrated into your single-page app with React Router! + +{{"demo": "ReactRouter.js", "height": 500, "iframe": true, "hideToolbar": true}} + +:::info +For a full working example, see the [Toolpad Core Vite app with React Router example](https://github.com/mui/toolpad/tree/master/examples/core/vite/) +::: + +## (Optional) Set up authentication + +You can use the `SignInPage` component to add authentication along with an external authentication provider of your choice. The following code demonstrates the code required to set up authentication with a mock provider. + +### Define a `SessionContext` to act as the mock authentication provider + +```tsx title="src/SessionContext.ts" +import * as React from 'react'; +import type { Session } from '@toolpad/core'; + +export interface SessionContextValue { + session: Session | null; + setSession: (session: Session | null) => void; +} + +export const SessionContext = React.createContext({ + session: {}, + setSession: () => {}, +}); + +export function useSession() { + return React.useContext(SessionContext); +} +``` + +### Add the mock authentication and session data to the `AppProvider` + +```tsx title="src/App.tsx" +import * as React from 'react'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; +import { AppProvider } from '@toolpad/core/react-router-dom'; +import { Outlet, useNavigate } from 'react-router-dom'; +import type { Navigation, Session } from '@toolpad/core'; +import { SessionContext } from './SessionContext'; + +const NAVIGATION: Navigation = [ + { + kind: 'header', + title: 'Main items', + }, + { + title: 'Dashboard', + icon: , + }, + { + segment: 'orders', + title: 'Orders', + icon: , + }, +]; + +const BRANDING = { + title: 'My Toolpad Core App', +}; + +export default function App() { + const [session, setSession] = React.useState(null); + const navigate = useNavigate(); + + const signIn = React.useCallback(() => { + navigate('/sign-in'); + }, [navigate]); + + const signOut = React.useCallback(() => { + setSession(null); + navigate('/sign-in'); + }, [navigate]); + + const sessionContextValue = React.useMemo( + () => ({ session, setSession }), + [session, setSession], + ); + + return ( + + + + + + ); +} +``` + +### Protect routes inside the dashboard layout + +```tsx title="src/layouts/dashboard.tsx" +import * as React from 'react'; +import { Outlet, Navigate, useLocation } from 'react-router-dom'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +import { PageContainer } from '@toolpad/core/PageContainer'; +import { useSession } from '../SessionContext'; + +export default function Layout() { + const { session } = useSession(); + const location = useLocation(); + + if (!session) { + // Add the `callbackUrl` search parameter + const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`; + + return ; + } + + return ( + + + + + + ); +} +``` + +You can protect any page or groups of pages through this mechanism. + +### Use the `SignInPage` component to create a sign-in page + +```tsx title="src/pages/signIn.tsx" +'use client'; +import * as React from 'react'; +import { SignInPage } from '@toolpad/core/SignInPage'; +import type { Session } from '@toolpad/core/AppProvider'; +import { useNavigate } from 'react-router-dom'; +import { useSession } from '../SessionContext'; + +const fakeAsyncGetSession = async (formData: any): Promise => { + return new Promise((resolve, reject) => { + setTimeout(() => { + if (formData.get('password') === 'password') { + resolve({ + user: { + name: 'Bharat Kashyap', + email: formData.get('email') || '', + image: 'https://avatars.githubusercontent.com/u/19550456', + }, + }); + } + reject(new Error('Incorrect credentials.')); + }, 1000); + }); +}; + +export default function SignIn() { + const { setSession } = useSession(); + const navigate = useNavigate(); + return ( + { + // Demo session + try { + const session = await fakeAsyncGetSession(formData); + if (session) { + setSession(session); + navigate(callbackUrl || '/', { replace: true }); + return {}; + } + } catch (error) { + return { + error: error instanceof Error ? error.message : 'An error occurred', + }; + } + return {}; + }} + /> + ); +} +``` + +### Add the sign in page to the router + +```tsx title="src/main.tsx" +import * as React from 'react'; +import * as ReactDOM from 'react-dom/client'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import App from './App'; +import Layout from './layouts/dashboard'; +import DashboardPage from './pages'; +import OrdersPage from './pages/orders'; +import SignInPage from './pages/signIn'; + +const router = createBrowserRouter([ + { + Component: App, + children: [ + { + path: '/', + Component: Layout, + children: [ + { + path: '/', + Component: DashboardPage, + }, + { + path: '/orders', + Component: OrdersPage, + }, + ], + }, + { + path: '/sign-in', + Component: SignInPage, + }, + ], + }, +]); + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); +``` + +:::info +For a full working example, see the [Toolpad Core Vite app with React Router and authentication example](https://github.com/mui/toolpad/tree/master/examples/core/auth-vite/) +::: diff --git a/docs/data/toolpad/core/introduction/integration.md b/docs/data/toolpad/core/introduction/integration.md deleted file mode 100644 index 01569a2b7dd..00000000000 --- a/docs/data/toolpad/core/introduction/integration.md +++ /dev/null @@ -1,1062 +0,0 @@ ---- -title: Toolpad Core - Integration ---- - -# Integration - -

This guide walks you through adding Toolpad Core to an existing project.

- -## Installation - - - -```bash npm -npm install @toolpad/core -``` - -```bash pnpm -pnpm add @toolpad/core -``` - -```bash yarn -yarn add @toolpad/core -``` - - - -## Next.js App Router - -Use the following steps to integrate Toolpad Core into your Next.js app: - -### 1. Wrap your application with `AppProvider` - -In your root layout file (for example, `app/layout.tsx`), wrap your application with the `AppProvider`: - -```tsx title="app/layout.tsx" -import { AppProvider } from '@toolpad/core/AppProvider'; -import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter'; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - {children} - - - ); -} -``` - -You can find details on the `AppProvider` props on the [AppProvider](/toolpad/core/react-app-provider/) page. - -:::info -The `AppRouterCacheProvider` component is not required to use Toolpad Core, but it's recommended to use it to ensure that the styles are appended to the `` and not rendering in the ``. - -See the [MaterialĀ UI Next.js integration docs](https://mui.com/material-ui/integrations/nextjs/) for more details. -::: - -### 2. Create a dashboard layout - -Create a layout file for your dashboard pages (for example, `app/(dashboard)/layout.tsx`): - -```tsx title="app/(dashboard)/layout.tsx" -import * as React from 'react'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import { PageContainer } from '@toolpad/core/PageContainer'; - -export default function DashboardPagesLayout(props: { children: React.ReactNode }) { - return ( - - {props.children} - - ); -} -``` - -The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation. - -### 3. Create a dashboard page - -Now you can create pages within your dashboard. For example, a home page (`app/(dashboard)/page.tsx`): - -```tsx title="app/(dashboard)/page.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function Page() { - return Welcome to a page in the dashboard!; -} -``` - -That's it! You have now integrated Toolpad Core into your Next.js app. - -### 4. (Optional) Add a second page - -Create a new page in the dashboard, for example, `app/(dashboard)/orders/page.tsx`: - -```tsx title="app/(dashboard)/orders/page.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function OrdersPage() { - return Welcome to the orders page!; -} -``` - -To add this page to the navigation, add it to the `NAVIGATION` variable: - -```ts title="app/layout.tsx" -export const NAVIGATION = [ - // ... - { - segment: 'orders', - title: 'Orders', - icon: , - }, - // ... -]; -``` - -### 5. (Optional) Set up authentication - -If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup: - -#### a. Install the dependencies - -```bash -npm install next-auth@beta -``` - -#### b. Create an `auth.ts` file - -```ts title="auth.ts" -import NextAuth from 'next-auth'; -import GitHub from 'next-auth/providers/github'; -import type { Provider } from 'next-auth/providers'; - -const providers: Provider[] = [ - GitHub({ - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - }), -]; - -export const providerMap = providers.map((provider) => { - if (typeof provider === 'function') { - const providerData = provider(); - return { id: providerData.id, name: providerData.name }; - } - return { id: provider.id, name: provider.name }; -}); - -export const { handlers, auth, signIn, signOut } = NextAuth({ - providers, - secret: process.env.AUTH_SECRET, - pages: { - signIn: '/auth/signin', - }, - callbacks: { - authorized({ auth: session, request: { nextUrl } }) { - const isLoggedIn = !!session?.user; - const isPublicPage = nextUrl.pathname.startsWith('/public'); - - if (isPublicPage || isLoggedIn) { - return true; - } - - return false; // Redirect unauthenticated users to login page - }, - }, -}); -``` - -#### c. Create a sign-in page - -Use the `SignInPage` component to add a sign-in page to your app. For example, `app/auth/signin/page.tsx`: - -```tsx title="app/auth/signin/page.tsx" -import * as React from 'react'; -import { SignInPage, type AuthProvider } from '@toolpad/core/SignInPage'; -import { AuthError } from 'next-auth'; -import { providerMap, signIn } from '../../../auth'; - -export default function SignIn() { - return ( - { - 'use server'; - try { - return await signIn(provider.id, { - redirectTo: callbackUrl ?? '/', - }); - } catch (error) { - // The desired flow for successful sign in in all cases - // and unsuccessful sign in for OAuth providers will cause a `redirect`, - // and `redirect` is a throwing function, so we need to re-throw - // to allow the redirect to happen - // Source: https://github.com/vercel/next.js/issues/49298#issuecomment-1542055642 - // Detect a `NEXT_REDIRECT` error and re-throw it - if (error instanceof Error && error.message === 'NEXT_REDIRECT') { - throw error; - } - // Handle Auth.js errors - if (error instanceof AuthError) { - return { - error: error.message, - type: error.type, - }; - } - // An error boundary must exist to handle unknown errors - return { - error: 'Something went wrong.', - type: 'UnknownError', - }; - } - }} - /> - ); -} -``` - -#### d. Create a route handler for sign-in - -`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth]/route.ts`: - -```ts title="app/api/auth/[...nextauth]/route.ts" -import { handlers } from '../../../../auth'; - -export const { GET, POST } = handlers; -``` - -#### e. Add a middleware - -Add a middleware to your app to protect your dashboard pages: - -```ts title="middleware.ts" -export { auth as middleware } from './auth'; - -export const config = { - // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher - matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], -}; -``` - -That's it! You now have Toolpad Core integrated into your Next.js App Router app with authentication setup: - -{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-app.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-app-dark.png", "alt": "Next.js App Router with Toolpad Core", "caption": "Next.js App Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }} - -:::info -For a full working example with authentication included, see the [Toolpad Core Next.js App with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs/) -::: - -## Next.js Pages Router - -To integrate Toolpad Core into your Next.js Pages Router app, follow these steps: - -### 1. Wrap your application with `AppProvider` - -In your root layout file (for example, `pages/_app.tsx`), wrap your application with the `AppProvider`: - -```tsx title="pages/_app.tsx" -import * as React from 'react'; -import { AppProvider } from '@toolpad/core/nextjs'; -import { PageContainer } from '@toolpad/core/PageContainer'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import Head from 'next/head'; -import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; -import type { Navigation } from '@toolpad/core/AppProvider'; - -const NAVIGATION: Navigation = [ - { - kind: 'header', - title: 'Main items', - }, - { - segment: '', - title: 'Dashboard', - icon: , - }, -]; - -const BRANDING = { - title: 'My Toolpad Core App', -}; - -export default function App({ Component }: { Component: React.ElementType }) { - return ( - - - - - - - - - - - - - ); -} -``` - -:::info -The `AppCacheProvider` component is not required to use Toolpad Core, but it's recommended. - -See the [Material UI Next.js Pages Router integration docs](https://mui.com/material-ui/integrations/nextjs/#configuration-2) for more details. -::: - -### 2. Modify `_document.tsx` - -Modify `_document.tsx` to include the `DocumentHeadTags` component: - -```tsx title="pages/_document.tsx" -import * as React from 'react'; -import { - Html, - Head, - Main, - NextScript, - DocumentProps, - DocumentContext, -} from 'next/document'; -import { - DocumentHeadTags, - DocumentHeadTagsProps, - documentGetInitialProps, -} from '@mui/material-nextjs/v14-pagesRouter'; - -export default function Document(props: DocumentProps & DocumentHeadTagsProps) { - return ( - - - - - - -
- - - - ); -} - -Document.getInitialProps = async (ctx: DocumentContext) => { - const finalProps = await documentGetInitialProps(ctx); - return finalProps; -}; -``` - -### 3. Add a dashboard page - -Create a dashboard page (for example, `pages/index.tsx`): - -```tsx title="pages/index.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function HomePage() { - return Welcome to Toolpad!; -} -``` - -### 4. (Optional) Add a second page - -Create a new page in the dashboard, for example, `pages/orders/index.tsx`: - -```tsx title="pages/orders/index.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function OrdersPage() { - return Welcome to the orders page!; -} -``` - -To add this page to the navigation, add it to the `NAVIGATION` variable: - -```ts title="pages/_app.tsx" -export const NAVIGATION = [ - // ... - { - segment: 'orders', - title: 'Orders', - icon: , - }, - // ... -]; -``` - -### 5. (Optional) Set up authentication - -If you want to add authentication, you can use Auth.js with Toolpad Core. Here's an example setup: - -#### a. Install the dependencies - -```bash -npm install next-auth@beta -``` - -#### b. Create an `auth.ts` file - -```ts title="auth.ts" -import NextAuth from 'next-auth'; -import GitHub from 'next-auth/providers/github'; -import type { Provider } from 'next-auth/providers'; - -const providers: Provider[] = [ - GitHub({ - clientId: process.env.GITHUB_CLIENT_ID, - clientSecret: process.env.GITHUB_CLIENT_SECRET, - }), -]; - -export const providerMap = providers.map((provider) => { - if (typeof provider === 'function') { - const providerData = provider(); - return { id: providerData.id, name: providerData.name }; - } - return { id: provider.id, name: provider.name }; -}); - -export const { handlers, auth } = NextAuth({ - providers, - secret: process.env.AUTH_SECRET, - pages: { - signIn: '/auth/signin', - }, - callbacks: { - authorized({ auth: session, request: { nextUrl } }) { - const isLoggedIn = !!session?.user; - const isPublicPage = nextUrl.pathname.startsWith('/public'); - - if (isPublicPage || isLoggedIn) { - return true; - } - - return false; // Redirect unauthenticated users to login page - }, - }, -}); -``` - -#### c. Modify `_app.tsx` - -Modify `_app.tsx` to include the `authentication` prop and other helpers: - -```tsx title="pages/_app.tsx" -import * as React from 'react'; -import { AppProvider } from '@toolpad/core/nextjs'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import { PageContainer } from '@toolpad/core/PageContainer'; -import Head from 'next/head'; -import { AppCacheProvider } from '@mui/material-nextjs/v14-pagesRouter'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; -import type { NextPage } from 'next'; -import type { AppProps } from 'next/app'; -import type { Navigation } from '@toolpad/core/AppProvider'; -import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react'; -import LinearProgress from '@mui/material/LinearProgress'; - -export type NextPageWithLayout

= NextPage & { - getLayout?: (page: React.ReactElement) => React.ReactNode; - requireAuth?: boolean; -}; - -type AppPropsWithLayout = AppProps & { - Component: NextPageWithLayout; -}; - -const NAVIGATION: Navigation = [ - { - kind: 'header', - title: 'Main items', - }, - { - segment: '', - title: 'Dashboard', - icon: , - }, - { - segment: 'orders', - title: 'Orders', - icon: , - }, -]; - -const BRANDING = { - title: 'My Toolpad Core App', -}; - -const AUTHENTICATION = { - signIn, - signOut, -}; - -function getDefaultLayout(page: React.ReactElement) { - return ( - - {page} - - ); -} - -function RequireAuth({ children }: { children: React.ReactNode }) { - const { status } = useSession(); - - if (status === 'loading') { - return ; - } - - return children; -} - -function AppLayout({ children }: { children: React.ReactNode }) { - const { data: session } = useSession(); - return ( - - - - - - {children} - - - ); -} - -export default function App(props: AppPropsWithLayout) { - const { - Component, - pageProps: { session, ...pageProps }, - } = props; - - const getLayout = Component.getLayout ?? getDefaultLayout; - const requireAuth = Component.requireAuth ?? true; - - let pageContent = getLayout(); - if (requireAuth) { - pageContent = {pageContent}; - } - pageContent = {pageContent}; - - return ( - - {pageContent} - - ); -} -``` - -#### d. Create a sign-in page - -Use the `SignInPage` component to add a sign-in page to your app. For example, `pages/auth/signin.tsx`: - -```tsx title="pages/auth/signin.tsx" -import * as React from 'react'; -import type { GetServerSidePropsContext, InferGetServerSidePropsType } from 'next'; -import Link from '@mui/material/Link'; -import { SignInPage } from '@toolpad/core/SignInPage'; -import { signIn } from 'next-auth/react'; -import { useRouter } from 'next/router'; -import { auth, providerMap } from '../../auth'; - -export default function SignIn({ - providers, -}: InferGetServerSidePropsType) { - const router = useRouter(); - return ( - { - try { - const signInResponse = await signIn(provider.id, { - callbackUrl: callbackUrl ?? '/', - }); - if (signInResponse && signInResponse.error) { - // Handle Auth.js errors - return { - error: signInResponse.error.message, - type: signInResponse.error, - }; - } - return {}; - } catch (error) { - // An error boundary must exist to handle unknown errors - return { - error: 'Something went wrong.', - type: 'UnknownError', - }; - } - }} - /> - ); -} - -SignIn.getLayout = (page: React.ReactNode) => page; - -SignIn.requireAuth = false; - -export async function getServerSideProps(context: GetServerSidePropsContext) { - const session = await auth(context); - - // If the user is already logged in, redirect. - // Note: Make sure not to redirect to the same page - // To avoid an infinite loop! - if (session) { - return { redirect: { destination: '/' } }; - } - - return { - props: { - providers: providerMap, - }, - }; -} -``` - -#### e. Create a route handler for sign-in - -`next-auth` requires a route handler for sign-in. Create a file `app/api/auth/[...nextauth].ts`: - -```ts title="app/api/auth/[...nextauth].ts" -import { handlers } from '../../../../auth'; - -export const { GET, POST } = handlers; -``` - -:::warning - -Note that this file is a route handler and must be placed in the `app` directory, even if the rest of your app is in the `pages` directory. Know more in the [Auth.js documentation](https://authjs.dev/getting-started/installation#configure). - -::: - -#### f. Add a middleware - -Add a middleware to your app to protect your dashboard pages: - -```ts title="middleware.ts" -export { auth as middleware } from './auth'; - -export const config = { - // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher - matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], -}; -``` - -That's it! You now have Toolpad Core integrated into your Next.js Pages Router app with authentication setup: - -{{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/integration-nextjs-pages.png", "srcDark": "/static/toolpad/docs/core/integration-nextjs-pages-dark.png", "alt": "Next.js Pages Router with Toolpad Core", "caption": "Next.js Pages Router with Toolpad Core", "zoom": true, "aspectRatio": "1.428" }} - -:::info -For a full working example with authentication included, see the [Toolpad Core Next.js Pages app with Auth.js example](https://github.com/mui/toolpad/tree/master/examples/core/auth-nextjs-pages/) -::: - -## React Router - -To integrate Toolpad Core into a single-page app (with [Vite](https://vite.dev/), for example) using **React Router**, follow these steps: - -### 1. Wrap all your pages in an `AppProvider` - -In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that will wrap the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`. - -You must use the `` component from `react-router-dom` in this root layout element or component. - -```tsx title="src/main.tsx" -import * as React from 'react'; -import * as ReactDOM from 'react-dom/client'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import App from './App'; -import DashboardPage from './pages'; -import OrdersPage from './pages/orders'; - -const router = createBrowserRouter([ - { - Component: App, // root layout route - }, -]); - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -); -``` - -```tsx title="src/App.tsx" -import * as React from 'react'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; -import { AppProvider } from '@toolpad/core/react-router-dom'; -import { Outlet } from 'react-router-dom'; -import type { Navigation } from '@toolpad/core'; - -const NAVIGATION: Navigation = [ - { - kind: 'header', - title: 'Main items', - }, - { - title: 'Dashboard', - icon: , - }, - { - segment: 'orders', - title: 'Orders', - icon: , - }, -]; - -const BRANDING = { - title: 'My Toolpad Core App', -}; - -export default function App() { - return ( - - - - ); -} -``` - -### 2. Create a dashboard layout - -Create a layout file for your dashboard pages (e.g.: `src/layouts/dashboard.tsx`), to also be used as a layout route with the `` component from `react-router-dom`: - -```tsx title="src/layouts/dashboard.tsx" -import * as React from 'react'; -import { Outlet } from 'react-router-dom'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import { PageContainer } from '@toolpad/core/PageContainer'; - -export default function Layout() { - return ( - - - - - - ); -} -``` - -The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation. - -You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created in step 1. - -```tsx title="src/main.tsx" -import Layout from './layouts/dashboard'; - -//... -const router = createBrowserRouter([ - { - Component: App, // root layout route - children: [ - { - path: '/', - Component: Layout, - }, - ], - }, -]); -//... -``` - -### 3. Create pages - -Create a dashboard page (e.g.: `src/pages/index.tsx`) and an orders page (`src/pages/orders.tsx`). - -```tsx title="src/pages/index.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function DashboardPage() { - return Welcome to Toolpad!; -} -``` - -```tsx title="src/pages/orders.tsx" -import * as React from 'react'; -import Typography from '@mui/material/Typography'; - -export default function OrdersPage() { - return Welcome to the Toolpad orders!; -} -``` - -You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created in step 2, they will automatically be wrapped with that dashboard layout: - -```tsx title="src/main.tsx" -import DashboardPage from './pages'; -import OrdersPage from './pages/orders'; - -//... -const router = createBrowserRouter([ - { - Component: App, // root layout route - children: [ - { - path: '/', - Component: Layout, - children: [ - { - path: '', - Component: DashboardPage, - }, - { - path: 'orders', - Component: OrdersPage, - }, - ], - }, - ], - }, -]); -//... -``` - -That's it! You now have Toolpad Core integrated into your single-page app with React Router! - -{{"demo": "ReactRouter.js", "height": 500, "iframe": true, "hideToolbar": true}} - -:::info -For a full working example, see the [Toolpad Core Vite app with React Router example](https://github.com/mui/toolpad/tree/master/examples/core/vite/) -::: - -### 4. (Optional) Set up authentication - -You can use the `SignInPage` component to add authentication along with an external authentication provider of your choice. The following code demonstrates the code required to set up authentication with a mock provider. - -#### a. Define a `SessionContext` to act as the mock authentication provider - -```tsx title="src/SessionContext.ts" -import * as React from 'react'; -import type { Session } from '@toolpad/core'; - -export interface SessionContextValue { - session: Session | null; - setSession: (session: Session | null) => void; -} - -export const SessionContext = React.createContext({ - session: {}, - setSession: () => {}, -}); - -export function useSession() { - return React.useContext(SessionContext); -} -``` - -### b. Add the mock authentication and session data to the `AppProvider` - -```tsx title="src/App.tsx" -import * as React from 'react'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; -import { AppProvider } from '@toolpad/core/react-router-dom'; -import { Outlet, useNavigate } from 'react-router-dom'; -import type { Navigation, Session } from '@toolpad/core'; -import { SessionContext } from './SessionContext'; - -const NAVIGATION: Navigation = [ - { - kind: 'header', - title: 'Main items', - }, - { - title: 'Dashboard', - icon: , - }, - { - segment: 'orders', - title: 'Orders', - icon: , - }, -]; - -const BRANDING = { - title: 'My Toolpad Core App', -}; - -export default function App() { - const [session, setSession] = React.useState(null); - const navigate = useNavigate(); - - const signIn = React.useCallback(() => { - navigate('/sign-in'); - }, [navigate]); - - const signOut = React.useCallback(() => { - setSession(null); - navigate('/sign-in'); - }, [navigate]); - - const sessionContextValue = React.useMemo( - () => ({ session, setSession }), - [session, setSession], - ); - - return ( - - - - - - ); -} -``` - -#### c. Protect routes inside the dashboard layout - -```tsx title="src/layouts/dashboard.tsx" -import * as React from 'react'; -import { Outlet, Navigate, useLocation } from 'react-router-dom'; -import { DashboardLayout } from '@toolpad/core/DashboardLayout'; -import { PageContainer } from '@toolpad/core/PageContainer'; -import { useSession } from '../SessionContext'; - -export default function Layout() { - const { session } = useSession(); - const location = useLocation(); - - if (!session) { - // Add the `callbackUrl` search parameter - const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`; - - return ; - } - - return ( - - - - - - ); -} -``` - -You can protect any page or groups of pages through this mechanism. - -#### d. Use the `SignInPage` component to create a sign-in page - -```tsx title="src/pages/signIn.tsx" -'use client'; -import * as React from 'react'; -import { SignInPage } from '@toolpad/core/SignInPage'; -import type { Session } from '@toolpad/core/AppProvider'; -import { useNavigate } from 'react-router-dom'; -import { useSession } from '../SessionContext'; - -const fakeAsyncGetSession = async (formData: any): Promise => { - return new Promise((resolve, reject) => { - setTimeout(() => { - if (formData.get('password') === 'password') { - resolve({ - user: { - name: 'Bharat Kashyap', - email: formData.get('email') || '', - image: 'https://avatars.githubusercontent.com/u/19550456', - }, - }); - } - reject(new Error('Incorrect credentials.')); - }, 1000); - }); -}; - -export default function SignIn() { - const { setSession } = useSession(); - const navigate = useNavigate(); - return ( - { - // Demo session - try { - const session = await fakeAsyncGetSession(formData); - if (session) { - setSession(session); - navigate(callbackUrl || '/', { replace: true }); - return {}; - } - } catch (error) { - return { - error: error instanceof Error ? error.message : 'An error occurred', - }; - } - return {}; - }} - /> - ); -} -``` - -#### e. Add the sign in page to the router - -```tsx title="src/main.tsx" -import * as React from 'react'; -import * as ReactDOM from 'react-dom/client'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import App from './App'; -import Layout from './layouts/dashboard'; -import DashboardPage from './pages'; -import OrdersPage from './pages/orders'; -import SignInPage from './pages/signIn'; - -const router = createBrowserRouter([ - { - Component: App, - children: [ - { - path: '/', - Component: Layout, - children: [ - { - path: '/', - Component: DashboardPage, - }, - { - path: '/orders', - Component: OrdersPage, - }, - ], - }, - { - path: '/sign-in', - Component: SignInPage, - }, - ], - }, -]); - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -); -``` - -:::info -For a full working example, see the [Toolpad Core Vite app with React Router and authentication example](https://github.com/mui/toolpad/tree/master/examples/core/auth-vite/) -::: diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index 090e958a2a1..7846c425724 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -20,10 +20,6 @@ const pages: MuiPage[] = [ pathname: '/toolpad/core/introduction/base-concepts', title: 'Base concepts', }, - { - pathname: '/toolpad/core/introduction/integration', - title: 'Integration', - }, { pathname: '/toolpad/core/introduction/tutorial', title: 'Tutorial', @@ -42,6 +38,24 @@ const pages: MuiPage[] = [ }, ], }, + { + pathname: '/toolpad/core/integrations-group', + title: 'Integrations', + children: [ + { + pathname: '/toolpad/core/integrations/nextjs-approuter', + title: 'Next.js App Router', + }, + { + pathname: '/toolpad/core/integrations/nextjs-pagesrouter', + title: 'Next.js Page Router', + }, + { + pathname: '/toolpad/core/integrations/react-router', + title: 'React Router', + }, + ], + }, { pathname: '/toolpad/core/components-group', title: 'Components', diff --git a/docs/pages/toolpad/core/integrations/nextjs-approuter.js b/docs/pages/toolpad/core/integrations/nextjs-approuter.js new file mode 100644 index 00000000000..e8bbf70089c --- /dev/null +++ b/docs/pages/toolpad/core/integrations/nextjs-approuter.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-approuter.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js new file mode 100644 index 00000000000..8be2843cb83 --- /dev/null +++ b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js @@ -0,0 +1,7 @@ +import * as React from 'react'; +import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; +import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-pagesrouter.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/toolpad/core/introduction/integration.js b/docs/pages/toolpad/core/integrations/react-router.js similarity index 65% rename from docs/pages/toolpad/core/introduction/integration.js rename to docs/pages/toolpad/core/integrations/react-router.js index 2e29678e8da..b34ba5230fb 100644 --- a/docs/pages/toolpad/core/introduction/integration.js +++ b/docs/pages/toolpad/core/integrations/react-router.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from '../../../../data/toolpad/core/introduction/integration.md?muiMarkdown'; +import * as pageProps from '../../../../data/toolpad/core/integrations/react-router.md?muiMarkdown'; export default function Page() { return ; From 60f6b1cde0fe53e5e78fc6fa48e0732b79c4563b Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:23:19 +0300 Subject: [PATCH 2/6] pages --- docs/data/toolpad/core/pages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index 7846c425724..e41be818fd5 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -48,7 +48,7 @@ const pages: MuiPage[] = [ }, { pathname: '/toolpad/core/integrations/nextjs-pagesrouter', - title: 'Next.js Page Router', + title: 'Next.js Pages Router', }, { pathname: '/toolpad/core/integrations/react-router', From f590402662c1c5fd492416569c4571a1f11ec4ed Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:02:22 +0300 Subject: [PATCH 3/6] better-pathname --- .../core/integrations/{nextjs-approuter.md => app-router.md} | 0 .../integrations/{nextjs-pagesrouter.md => pages-router.md} | 0 docs/data/toolpad/core/pages.ts | 4 ++-- .../core/integrations/{nextjs-approuter.js => app-router.js} | 2 +- .../integrations/{nextjs-pagesrouter.js => pages-router.js} | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename docs/data/toolpad/core/integrations/{nextjs-approuter.md => app-router.md} (100%) rename docs/data/toolpad/core/integrations/{nextjs-pagesrouter.md => pages-router.md} (100%) rename docs/pages/toolpad/core/integrations/{nextjs-approuter.js => app-router.js} (86%) rename docs/pages/toolpad/core/integrations/{nextjs-pagesrouter.js => pages-router.js} (85%) diff --git a/docs/data/toolpad/core/integrations/nextjs-approuter.md b/docs/data/toolpad/core/integrations/app-router.md similarity index 100% rename from docs/data/toolpad/core/integrations/nextjs-approuter.md rename to docs/data/toolpad/core/integrations/app-router.md diff --git a/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md b/docs/data/toolpad/core/integrations/pages-router.md similarity index 100% rename from docs/data/toolpad/core/integrations/nextjs-pagesrouter.md rename to docs/data/toolpad/core/integrations/pages-router.md diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index e41be818fd5..3a28f0bc40e 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -43,11 +43,11 @@ const pages: MuiPage[] = [ title: 'Integrations', children: [ { - pathname: '/toolpad/core/integrations/nextjs-approuter', + pathname: '/toolpad/core/integrations/app-router', title: 'Next.js App Router', }, { - pathname: '/toolpad/core/integrations/nextjs-pagesrouter', + pathname: '/toolpad/core/integrations/pages-router', title: 'Next.js Pages Router', }, { diff --git a/docs/pages/toolpad/core/integrations/nextjs-approuter.js b/docs/pages/toolpad/core/integrations/app-router.js similarity index 86% rename from docs/pages/toolpad/core/integrations/nextjs-approuter.js rename to docs/pages/toolpad/core/integrations/app-router.js index e8bbf70089c..d8ec2556d29 100644 --- a/docs/pages/toolpad/core/integrations/nextjs-approuter.js +++ b/docs/pages/toolpad/core/integrations/app-router.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-approuter.md?muiMarkdown'; +import * as pageProps from '../../../../data/toolpad/core/integrations/app-router.md?muiMarkdown'; export default function Page() { return ; diff --git a/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js b/docs/pages/toolpad/core/integrations/pages-router.js similarity index 85% rename from docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js rename to docs/pages/toolpad/core/integrations/pages-router.js index 8be2843cb83..36ea5d98768 100644 --- a/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js +++ b/docs/pages/toolpad/core/integrations/pages-router.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-pagesrouter.md?muiMarkdown'; +import * as pageProps from '../../../../data/toolpad/core/integrations/pages-router.md?muiMarkdown'; export default function Page() { return ; From 65efddc27322daa92b263b03f9f07ed13f2b2f3b Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Fri, 15 Nov 2024 10:05:53 +0300 Subject: [PATCH 4/6] Vite --- docs/data/toolpad/core/pages.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index 3a28f0bc40e..c427a63d270 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -52,7 +52,7 @@ const pages: MuiPage[] = [ }, { pathname: '/toolpad/core/integrations/react-router', - title: 'React Router', + title: 'Vite with React Router', }, ], }, From 0621f4083aef760ae888c79b0b157fc0c6228129 Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Fri, 22 Nov 2024 15:46:57 +0300 Subject: [PATCH 5/6] Revert "better-pathname" This reverts commit f590402662c1c5fd492416569c4571a1f11ec4ed. --- .../core/integrations/{app-router.md => nextjs-approuter.md} | 0 .../integrations/{pages-router.md => nextjs-pagesrouter.md} | 0 docs/data/toolpad/core/pages.ts | 4 ++-- .../core/integrations/{app-router.js => nextjs-approuter.js} | 2 +- .../integrations/{pages-router.js => nextjs-pagesrouter.js} | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename docs/data/toolpad/core/integrations/{app-router.md => nextjs-approuter.md} (100%) rename docs/data/toolpad/core/integrations/{pages-router.md => nextjs-pagesrouter.md} (100%) rename docs/pages/toolpad/core/integrations/{app-router.js => nextjs-approuter.js} (86%) rename docs/pages/toolpad/core/integrations/{pages-router.js => nextjs-pagesrouter.js} (85%) diff --git a/docs/data/toolpad/core/integrations/app-router.md b/docs/data/toolpad/core/integrations/nextjs-approuter.md similarity index 100% rename from docs/data/toolpad/core/integrations/app-router.md rename to docs/data/toolpad/core/integrations/nextjs-approuter.md diff --git a/docs/data/toolpad/core/integrations/pages-router.md b/docs/data/toolpad/core/integrations/nextjs-pagesrouter.md similarity index 100% rename from docs/data/toolpad/core/integrations/pages-router.md rename to docs/data/toolpad/core/integrations/nextjs-pagesrouter.md diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index c427a63d270..b4c817776c9 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -43,11 +43,11 @@ const pages: MuiPage[] = [ title: 'Integrations', children: [ { - pathname: '/toolpad/core/integrations/app-router', + pathname: '/toolpad/core/integrations/nextjs-approuter', title: 'Next.js App Router', }, { - pathname: '/toolpad/core/integrations/pages-router', + pathname: '/toolpad/core/integrations/nextjs-pagesrouter', title: 'Next.js Pages Router', }, { diff --git a/docs/pages/toolpad/core/integrations/app-router.js b/docs/pages/toolpad/core/integrations/nextjs-approuter.js similarity index 86% rename from docs/pages/toolpad/core/integrations/app-router.js rename to docs/pages/toolpad/core/integrations/nextjs-approuter.js index d8ec2556d29..e8bbf70089c 100644 --- a/docs/pages/toolpad/core/integrations/app-router.js +++ b/docs/pages/toolpad/core/integrations/nextjs-approuter.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from '../../../../data/toolpad/core/integrations/app-router.md?muiMarkdown'; +import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-approuter.md?muiMarkdown'; export default function Page() { return ; diff --git a/docs/pages/toolpad/core/integrations/pages-router.js b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js similarity index 85% rename from docs/pages/toolpad/core/integrations/pages-router.js rename to docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js index 36ea5d98768..8be2843cb83 100644 --- a/docs/pages/toolpad/core/integrations/pages-router.js +++ b/docs/pages/toolpad/core/integrations/nextjs-pagesrouter.js @@ -1,6 +1,6 @@ import * as React from 'react'; import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs'; -import * as pageProps from '../../../../data/toolpad/core/integrations/pages-router.md?muiMarkdown'; +import * as pageProps from '../../../../data/toolpad/core/integrations/nextjs-pagesrouter.md?muiMarkdown'; export default function Page() { return ; From 82f2b82f3b44c3eded37926f22ecfbe62821017d Mon Sep 17 00:00:00 2001 From: Prakhar Gupta <92228082+prakhargupta1@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:40:44 +0300 Subject: [PATCH 6/6] from review --- docs/data/toolpad/core/integrations/react-router.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/data/toolpad/core/integrations/react-router.md b/docs/data/toolpad/core/integrations/react-router.md index 1b0dde51476..e0fd48b0d82 100644 --- a/docs/data/toolpad/core/integrations/react-router.md +++ b/docs/data/toolpad/core/integrations/react-router.md @@ -8,7 +8,7 @@ title: React router - Integration ## Wrap all your pages in an `AppProvider` -In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that will wrap the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`. +In your router configuration (e.g.: `src/main.tsx`), use a shared component or element (e.g.: `src/App.tsx`) as a root **layout route** that wraps the whole application with the `AppProvider` from `@toolpad/core/react-router-dom`. You must use the `` component from `react-router-dom` in this root layout element or component. @@ -93,7 +93,7 @@ export default function Layout() { The [`DashboardLayout`](/toolpad/core/react-dashboard-layout/) component provides a consistent layout for your dashboard pages, including a sidebar, navigation, and header. The [`PageContainer`](/toolpad/core/react-page-container/) component is used to wrap the page content, and provides breadcrumbs for navigation. -You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created in step 1. +You can then add this layout component to your React Router configuration (e.g.: `src/main.tsx`), as a child of the root layout route created above. ```tsx title="src/main.tsx" import Layout from './layouts/dashboard'; @@ -135,7 +135,7 @@ export default function OrdersPage() { } ``` -You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created in step 2, they will automatically be wrapped with that dashboard layout: +You can then add these page components as routes to your React Router configuration (e.g.: `src/main.tsx`). By adding them as children of the layout route created above, they are automatically wrapped with that dashboard layout: ```tsx title="src/main.tsx" import DashboardPage from './pages';