diff --git a/docs/data/toolpad/core/components/account/account.md b/docs/data/toolpad/core/components/account/account.md index 3ec8951eb22..7b5121fb8d3 100644 --- a/docs/data/toolpad/core/components/account/account.md +++ b/docs/data/toolpad/core/components/account/account.md @@ -8,6 +8,10 @@ components: Account

A component that renders an account management dropdown for your application.

+:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + The `Account` component is a quick and easy way to display an account management menu for authenticated users. It is deeply integrated with the `SignInPage` and `DashboardLayout` components, meaning that it automatically appears in the top navigation bar inside `DashboardLayout` once your users have signed in through the `SignInPage`. ## States diff --git a/docs/data/toolpad/core/components/app-provider/app-provider.md b/docs/data/toolpad/core/components/app-provider/app-provider.md index 7b2ad0af85b..62b4b38b6e9 100644 --- a/docs/data/toolpad/core/components/app-provider/app-provider.md +++ b/docs/data/toolpad/core/components/app-provider/app-provider.md @@ -8,6 +8,10 @@ components: AppProvider

The app provider component provides the necessary context to easily set up a Toolpad application.

+:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + By wrapping an application at the root level with an `AppProvider` component, many of Toolpad's features (such as routing, navigation and theming) can be automatically enabled to their fullest extent, abstracting away complexity and helping you focus on the details that matter. ## Basic diff --git a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md index 44d47c03e83..341fb7170a0 100644 --- a/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md +++ b/docs/data/toolpad/core/components/dashboard-layout/dashboard-layout.md @@ -8,6 +8,10 @@ components: AppProvider, DashboardLayout, Account

The dashboard layout component provides a customizable out-of-the-box layout for a typical dashboard page.

+:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + The `DashboardLayout` component is a quick, easy way to provide a standard full-screen layout with a header and sidebar to any dashboard page, as well as ready-to-use and easy to customize navigation and branding. Many features of this component are configurable through the [AppProvider](https://mui.com/toolpad/core/react-app-provider/) component that must wrap it to provide the necessary context. diff --git a/docs/data/toolpad/core/components/page-container/page-container.md b/docs/data/toolpad/core/components/page-container/page-container.md index 81cd357c368..a5006b5a18a 100644 --- a/docs/data/toolpad/core/components/page-container/page-container.md +++ b/docs/data/toolpad/core/components/page-container/page-container.md @@ -8,6 +8,10 @@ components: PageContainer, PageContainerToolbar

A component that wraps page content and provides a title, breadcrumbs, and page actions.

+:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + `PageContainer` is the ideal wrapper for the content of your dashboard. It shows the current page title, and provides breadcrumbs to navigate back into the current hierarchy. It makes your page responsive through the use of the Material UI Container component under the hood. Just like [`DashboardLayout`](/toolpad/core/react-dashboard-layout/), `PageContainer` uses the navigation structure that is defined in the [`AppProvider`](/toolpad/core/react-app-provider/) to build up its breadcrumbs and title. diff --git a/docs/data/toolpad/core/components/persistent-state/persistent-state.md b/docs/data/toolpad/core/components/persistent-state/persistent-state.md index 026c8e03d72..7fe9549b123 100644 --- a/docs/data/toolpad/core/components/persistent-state/persistent-state.md +++ b/docs/data/toolpad/core/components/persistent-state/persistent-state.md @@ -7,7 +7,11 @@ title: Persistent storage state

Hooks for synchronizing React state with browser storage.

-Toolpad provides a set of primitives that harmonize how you deal with persisting global state in the browser. Through our hooks you can synchronise React state with local storage or session storage, or in the url as a query parameter. These hooks all follow a similar philosophy: they identify their data with a unique key and support rich data types through the use of codecs. +:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + +Toolpad Core provides a set of hooks that abstract dealing with persisting global state in the browser. Through these hooks you can synchronise React state with local storage or session storage, or in the url as a query parameter. These hooks all follow a similar philosophy: they identify their data with a unique key and support rich data types through the use of codecs. The hook's signature intentionally resembles the `React.useState` hook. Where the first parameter represents the key under which to store the state in the browser, and the second parameter corresponds to the initial value. An optional third parameter can be used to configure the hook. diff --git a/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md b/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md index 1c856c51ad0..85d7fe8c878 100644 --- a/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md +++ b/docs/data/toolpad/core/components/sign-in-page/sign-in-page.md @@ -8,6 +8,10 @@ components: SignInPage, Account, NotificationsProvider

A customizable sign-in UI component that abstracts away the pain needed to wire together a secure authentication page for your application.

+:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + The `SignInPage` component is a quick way to generate a ready-to-use authentication page with multiple OAuth providers, or a credentials form. ## OAuth diff --git a/docs/data/toolpad/core/components/use-dialogs/use-dialogs.md b/docs/data/toolpad/core/components/use-dialogs/use-dialogs.md index afb54c320a2..58504d097fd 100644 --- a/docs/data/toolpad/core/components/use-dialogs/use-dialogs.md +++ b/docs/data/toolpad/core/components/use-dialogs/use-dialogs.md @@ -8,7 +8,11 @@ components: DialogsProvider

Imperative APIs to open and interact with dialogs.

-Toolpad core offers a set of abstractions that makes interacting with dialogs simpler. It has an imperative API to open and close dialogs, and allows dialogs to be stacked on top of each other. +:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + +Toolpad Core offers a set of abstractions that makes interacting with dialogs simpler. It has an imperative API to open and close dialogs, and allows dialogs to be stacked on top of each other. First thing you need to do is install the DialogsProvider at the root of your application. diff --git a/docs/data/toolpad/core/components/use-notifications/use-notifications.md b/docs/data/toolpad/core/components/use-notifications/use-notifications.md index 4a4fde97fea..d47afbe6c18 100644 --- a/docs/data/toolpad/core/components/use-notifications/use-notifications.md +++ b/docs/data/toolpad/core/components/use-notifications/use-notifications.md @@ -8,7 +8,11 @@ components: NotificationsProvider

Imperative APIs to show and interact with application notifications.

-Toolpad core offers a set of abstractions that make it easier to interact with notifications. Notifications are used to give short updates to the user about things that are happening during the application lifetime. They appear at the bottom of the screen. The Toolpad API allows for opening multiple notifications concurrenlty. +:::info +If this is your first time using Toolpad Core, it's recommended to read about the [basic concepts](/toolpad/core/introduction/base-concepts/) first. +::: + +Toolpad Core offers a set of abstractions that make it easier to interact with notifications. Notifications are used to give short updates to the user about things that are happening during the application lifetime. They appear at the bottom of the screen. The Toolpad API allows for opening multiple notifications concurrenlty. First thing you need to do to get access to the notifications APIs is install the NotificationsProvider. diff --git a/docs/data/toolpad/core/introduction/base-concepts.md b/docs/data/toolpad/core/introduction/base-concepts.md new file mode 100644 index 00000000000..f742f389b75 --- /dev/null +++ b/docs/data/toolpad/core/introduction/base-concepts.md @@ -0,0 +1,107 @@ +--- +title: Toolpad Core - Base Concepts +--- + +# Base concepts + +

Understand the fundamental concepts of Toolpad Core to effectively integrate and use it in your projects.

+ +## Imports + +Toolpad Core components can be imported directly from the `@toolpad/core` package. This allows you to use them alongside your existing MaterialĀ UI or other components. + +```tsx +import Button from '@mui/material/Button'; +import { DashboardLayout } from '@toolpad/core/DashboardLayout'; +``` + +## Component Hierarchy + +The Toolpad Core library is designed to work under different React runtimes such as Next.js, Vite, or even your custom setup. Many of its components rely on functionality of the specific runtime they are used under. The key component in making the components runtime-aware is the `AppProvider`. + +### App Provider + +The `AppProvider` acts as a bridge between your application's runtime and Toolpad components. It should wrap your entire application or the part of your application where you want to use Toolpad components. + +```tsx +import { AppProvider } from '@toolpad/core/AppProvider'; + +function MyApp({ Component, pageProps }) { + return ( + + + + ); +} +``` + +By wrapping your application with `AppProvider`, you ensure that all other Toolpad components you use have access to the necessary context and functionality. + +The `AppProvider` component accepts props to configure the app's navigation, theme, branding, router, authentication, and session, like so: + +```tsx + + {props.children} + +``` + +Head over to the [AppProvider](/toolpad/core/react-app-provider/) page for more details and examples of the usage of all props. + +:::info +Toolpad Core doesn't handle routing itself. Instead, it's designed to integrate seamlessly with your existing routing solution, whether you're using: + +- Next.js App Router +- Next.js Pages Router +- React Router +- Or any other routing library which implements the same interface + +You can pass the router implementation to the `AppProvider` component using the `router` prop. + +::: + +:::success +If you are using Next.js, use the `AppProvider` exported from `@toolpad/core/nextjs`. This automatically sets up the router for you and you do not need to pass the `router` prop. +::: + +## Slots + +Toolpad Core uses slots for component customization. Slots allow you to override specific parts of a component, providing flexibility in styling and functionality. You can also pass additional props to specific slots using the `slotProps` prop. + +Here's an example using the `SignInPage` component: + +```tsx +import { SignInPage } from '@toolpad/core/SignInPage'; +function MyComponent() { + return ( + + Custom Button + + ); +} +``` + +In this example: + +- The `slots` prop allows you to replace entire parts of the component. +- The `slotProps` prop lets you pass additional props to specific slots. + +## Next Steps + +Now that you understand the basic concepts of Toolpad Core, you're ready to start integrating it into your project. Head over to the [integration docs](/toolpad/core/introduction/integration/) to learn more. diff --git a/docs/data/toolpad/core/introduction/installation.md b/docs/data/toolpad/core/introduction/installation.md index 6b93e49fbd2..6a8bc1a8bba 100644 --- a/docs/data/toolpad/core/introduction/installation.md +++ b/docs/data/toolpad/core/introduction/installation.md @@ -4,6 +4,44 @@ title: Toolpad Core - Installation # Installation +## Manual Installation + +Use your preferred package manager to install `@toolpad/core` in your project: + + + +```bash npm +npm install -S @toolpad/core +``` + +```bash yarn +yarn add @toolpad/core +``` + +```bash pnpm +pnpm add @toolpad/core +``` + + + +The Toolpad Core package has a peer dependency on `@mui/material` and `@mui/icons-material`. If you aren't using these already in your project, you can install them with: + + + +```bash npm +npm install -S @mui/material @mui/icons-material @emotion/react @emotion/styled +``` + +```bash yarn +yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled +``` + +```bash pnpm +pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled +``` + + + ## Automatic Installation

Learn how to install Toolpad Core in your local environment.

@@ -74,41 +112,3 @@ yarn dev {{"component": "modules/components/DocsImage.tsx", "src": "/static/toolpad/docs/core/installation-1.png", "alt": "Toolpad Core entry point", "caption": "Starting with Toolpad Core", "zoom": true, "indent": 1 }} 5. Installation is complete! Begin building your project by making edits to `(dashboard/page/page.tsx`. To understand how to leverage Toolpad Core to build dashboards quickly, [see the detailed tutorial](/toolpad/core/introduction/tutorial/). - -## Manual Installation - -Use your preferred package manager to install `@toolpad/core` in your project: - - - -```bash npm -npm install -S @toolpad/core -``` - -```bash yarn -yarn add @toolpad/core -``` - -```bash pnpm -pnpm add @toolpad/core -``` - - - -The Toolpad Core package has a peer dependency on `@mui/material` and `@mui/icons-material`. If you aren't using these already in your project, you can install them with: - - - -```bash npm -npm install -S @mui/material @mui/icons-material @emotion/react @emotion/styled -``` - -```bash yarn -yarn add @mui/material @mui/icons-material @emotion/react @emotion/styled -``` - -```bash pnpm -pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled -``` - - diff --git a/docs/data/toolpad/core/introduction/integration.md b/docs/data/toolpad/core/introduction/integration.md new file mode 100644 index 00000000000..874f8326f77 --- /dev/null +++ b/docs/data/toolpad/core/introduction/integration.md @@ -0,0 +1,769 @@ +--- +title: Toolpad Core - Integration +description: How to integrate Toolpad Core into your existing project. +--- + +# Integration + +This guide will walk you through the process of adding Toolpad Core to an existing project. + +## Installation + + + +```bash npm +npm install -S @toolpad/core +``` + +```bash yarn +yarn add @toolpad/core +``` + +```bash pnpm +pnpm 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 (e.g., `app/layout.tsx`), wrap your application with the `AppProvider`: + +```tsx title="app/layout.tsx" +import { AppProvider } from '@toolpad/core'; +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 (e.g., `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 Credentials from 'next-auth/providers/credentials'; +import type { Provider } from 'next-auth/providers'; +const providers: Provider[] = [ + GitHub({ + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }), + Credentials({ + credentials: { + email: { label: 'Email Address', type: 'email' }, + password: { label: 'Password', type: 'password' }, + }, + authorize(c) { + if (c.password !== 'password') { + return null; + } + return { + id: 'test', + name: 'Test User', + email: String(c.email), + }; + }, + }), +]; + +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 + }, + }, +}); +``` + +:::warning + +This file is only for demonstration purposes and allows signing in with `password` as the password. You should use a more secure method for authentication in a production environment, preferably OAuth with your own `CLIENT_ID` and `CLIENT_SECRET`. Find more details on to get these values in the [Auth.js documentation](https://authjs.dev/guides/configuring-github). + +::: + +#### 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 type { AuthProvider } from '@toolpad/core'; +import { SignInPage } 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, { + ...(formData && { + email: formData.get('email'), + password: formData.get('password'), + }), + 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.type === 'CredentialsSignin' + ? 'Invalid credentials.' + : 'An error with Auth.js occurred.', + 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" }} + +## 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 (e.g., `pages/_app.tsx`), wrap your application with the `AppProvider`: + +```tsx title="pages/_app.tsx" +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 { createTheme } from '@mui/material/styles'; + +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 lightTheme = createTheme(); + +const darkTheme = createTheme({ palette: { mode: 'dark' } }); + +const theme = { + light: lightTheme, + dark: darkTheme, +}; + +export default theme; + +export default function AppLayout({ Component, pageProps }: AppPropsWithLayout) { + return ( + + + + + + {children} + + + ); +} + +export default function App(props: AppPropsWithLayout) { + const { + Component, + pageProps: { session, ...pageProps }, + } = props; + + const getLayout = Component.getLayout ?? getDefaultLayout; + + let pageContent = getLayout(); + pageContent = {pageContent}; + + return ( + + {pageContent} + + ); +} +``` + +:::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 (e.g., `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 Credentials from 'next-auth/providers/credentials'; +import type { Provider } from 'next-auth/providers'; + +const providers: Provider[] = [ + GitHub({ + clientId: process.env.GITHUB_CLIENT_ID, + clientSecret: process.env.GITHUB_CLIENT_SECRET, + }), + Credentials({ + credentials: { + email: { label: 'Email Address', type: 'email' }, + password: { label: 'Password', type: 'password' }, + }, + authorize(c) { + if (c.password !== 'password') { + return null; + } + return { + id: 'test', + name: 'Test User', + email: String(c.email), + }; + }, + }), +]; + +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 + }, + }, +}); +``` + +:::warning + +This file is only for demonstration purposes and allows signing in with `password` as the password. You should use a more secure method for authentication in a production environment, preferably OAuth with your own `CLIENT_ID` and `CLIENT_SECRET`. Find more details on to get these values in the [Auth.js documentation](https://authjs.dev/guides/configuring-github). + +::: + +#### 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'; +import { SessionProvider, signIn, signOut, useSession } from 'next-auth/react'; +import LinearProgress from '@mui/material/LinearProgress'; +import theme from '../theme'; + +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, + formData + ? { + email: formData.get('email') as string, + password: formData.get('password') as string, + redirect: false, + } + : { callbackUrl: callbackUrl ?? '/' }, + ); + if (signInResponse && signInResponse.error) { + // Handle Auth.js errors + return { + error: + signInResponse.error === 'CredentialsSignin' + ? 'Invalid credentials' + : 'An error with Auth.js occurred', + type: signInResponse.error, + }; + } + // If the sign in was successful, + // manually redirect to the callback URL + // since the `redirect: false` option was used + // to be able to display error messages on the same page without a full page reload + if (provider.id === 'credentials') { + router.push(callbackUrl ?? '/'); + } + 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" }} diff --git a/docs/data/toolpad/core/pages.ts b/docs/data/toolpad/core/pages.ts index 500f23828fe..0830d61d8ee 100644 --- a/docs/data/toolpad/core/pages.ts +++ b/docs/data/toolpad/core/pages.ts @@ -16,6 +16,14 @@ const pages: MuiPage[] = [ pathname: '/toolpad/core/introduction/installation', title: 'Installation', }, + { + pathname: '/toolpad/core/introduction/base-concepts', + title: 'Base concepts', + }, + { + pathname: '/toolpad/core/introduction/integration', + title: 'Integration', + }, { pathname: '/toolpad/core/introduction/tutorial', title: 'Tutorial', diff --git a/docs/pages/toolpad/core/introduction/base-concepts.js b/docs/pages/toolpad/core/introduction/base-concepts.js new file mode 100644 index 00000000000..df0fd9bde3b --- /dev/null +++ b/docs/pages/toolpad/core/introduction/base-concepts.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/introduction/base-concepts.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/pages/toolpad/core/introduction/integration.js b/docs/pages/toolpad/core/introduction/integration.js new file mode 100644 index 00000000000..2e29678e8da --- /dev/null +++ b/docs/pages/toolpad/core/introduction/integration.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/introduction/integration.md?muiMarkdown'; + +export default function Page() { + return ; +} diff --git a/docs/public/static/toolpad/docs/core/integration-nextjs-app-dark.png b/docs/public/static/toolpad/docs/core/integration-nextjs-app-dark.png new file mode 100644 index 00000000000..f592dc8f180 Binary files /dev/null and b/docs/public/static/toolpad/docs/core/integration-nextjs-app-dark.png differ diff --git a/docs/public/static/toolpad/docs/core/integration-nextjs-app.png b/docs/public/static/toolpad/docs/core/integration-nextjs-app.png new file mode 100644 index 00000000000..1a9c8147d13 Binary files /dev/null and b/docs/public/static/toolpad/docs/core/integration-nextjs-app.png differ diff --git a/docs/public/static/toolpad/docs/core/integration-nextjs-pages-dark.png b/docs/public/static/toolpad/docs/core/integration-nextjs-pages-dark.png new file mode 100644 index 00000000000..cc1ba3671ed Binary files /dev/null and b/docs/public/static/toolpad/docs/core/integration-nextjs-pages-dark.png differ diff --git a/docs/public/static/toolpad/docs/core/integration-nextjs-pages.png b/docs/public/static/toolpad/docs/core/integration-nextjs-pages.png new file mode 100644 index 00000000000..202060737c6 Binary files /dev/null and b/docs/public/static/toolpad/docs/core/integration-nextjs-pages.png differ diff --git a/playground/nextjs-pages/src/auth.ts b/playground/nextjs-pages/src/auth.ts index a4739a3e913..b76886aa9ca 100644 --- a/playground/nextjs-pages/src/auth.ts +++ b/playground/nextjs-pages/src/auth.ts @@ -61,7 +61,6 @@ export const { handlers, auth } = NextAuth({ secret: process.env.AUTH_SECRET, pages: { signIn: '/auth/signin', - signOut: '/auth/signout', }, callbacks: { authorized({ auth: session, request: { nextUrl } }) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 040ab51bfa8..b458440c1e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2066,7 +2066,7 @@ packages: resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} engines: {node: '>=6.9.0'} peerDependencies: - '@babel/core': ^7.0.0-0 + '@babel/core': ^7.25.2 '@babel/preset-typescript@7.24.7': resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} @@ -2139,7 +2139,7 @@ packages: '@docsearch/react@3.6.1': resolution: {integrity: sha512-qXZkEPvybVhSXj0K7U3bXc233tk5e8PfhoZ6MhPOiik/qUQxYC+Dn9DnoS7CxHQQhHfCvTiN0eY9M12oRghEXw==} peerDependencies: - '@types/react': '>= 16.8.0 < 19.0.0' + '@types/react': ^18.3.4 react: '>= 16.8.0 < 19.0.0' react-dom: '>= 16.8.0 < 19.0.0' search-insights: '>= 1 < 3'