React Router
要将 Toolpad Core 集成到使用 React Router 的单页应用(例如使用 Vite)中,请按照以下步骤操作。
将所有页面包裹在 ReactRouterAppProvider
中
在您的路由器配置(例如 src/main.tsx
)中,使用共享组件或元素(例如 src/App.tsx
)作为根布局路由,用来自 @toolpad/core/react-router
的 ReactRouterAppProvider
包裹整个应用程序。
您必须在此根布局元素或组件中使用来自 react-router
的 <Outlet />
组件。
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
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(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
);
import * as React from 'react';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import { ReactRouterAppProvider } from '@toolpad/core/react-router';
import { Outlet } from 'react-router';
import type { Navigation } from '@toolpad/core';
const NAVIGATION: Navigation = [
{
kind: 'header',
title: 'Main items',
},
{
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
];
const BRANDING = {
title: 'My Toolpad Core App',
};
export default function App() {
return (
<ReactRouterAppProvider navigation={NAVIGATION} branding={BRANDING}>
<Outlet />
</ReactRouterAppProvider>
);
}
创建仪表盘布局
为您的仪表盘页面创建一个布局文件(例如 src/layouts/dashboard.tsx
),也用作带有来自 react-router
的 <Outlet />
组件的布局路由
import * as React from 'react';
import { Outlet } from 'react-router';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
export default function Layout() {
return (
<DashboardLayout>
<PageContainer>
<Outlet />
</PageContainer>
</DashboardLayout>
);
}
DashboardLayout
组件为您的仪表盘页面提供一致的布局,包括侧边栏、导航和页眉。PageContainer
组件用于包裹页面内容,并为导航提供面包屑。
然后,您可以将此布局组件添加到您的 React Router 配置(例如 src/main.tsx
)中,作为上面创建的根布局路由的子路由。
import Layout from './layouts/dashboard';
//...
const router = createBrowserRouter([
{
Component: App, // root layout route
children: [
{
path: '/',
Component: Layout,
},
],
},
]);
//...
创建页面
创建一个仪表盘页面(例如 src/pages/index.tsx
)和一个订单页面 (src/pages/orders.tsx
)。
import * as React from 'react';
import Typography from '@mui/material/Typography';
export default function DashboardPage() {
return <Typography>Welcome to Toolpad!</Typography>;
}
import * as React from 'react';
import Typography from '@mui/material/Typography';
export default function OrdersPage() {
return <Typography>Welcome to the Toolpad orders!</Typography>;
}
然后,您可以将这些页面组件作为路由添加到您的 React Router 配置(例如 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,
},
],
},
],
},
]);
//...
就是这样!您现在已将 Toolpad Core 集成到使用 React Router 的单页应用中!
(可选)设置身份验证
您可以使用 SignInPage
组件添加身份验证以及您选择的外部身份验证提供程序。以下代码演示了使用 Firebase 设置身份验证所需的代码。
定义一个 SessionContext
以充当模拟身份验证提供程序
import * as React from 'react';
export interface Session {
user: {
name?: string;
email?: string;
image?: string;
};
}
interface SessionContextType {
session: Session | null;
setSession: (session: Session) => void;
loading: boolean;
}
const SessionContext = React.createContext<SessionContextType>({
session: null,
setSession: () => {},
loading: true,
});
export default SessionContext;
export const useSession = () => React.useContext(SessionContext);
添加 Firebase 身份验证
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
const app = initializeApp({
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGE_SENDER_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
});
export const firebaseAuth = getAuth(app);
export default app;
import {
GoogleAuthProvider,
GithubAuthProvider,
signInWithPopup,
setPersistence,
browserSessionPersistence,
signInWithEmailAndPassword,
signOut,
} from 'firebase/auth';
import { firebaseAuth } from './firebaseConfig';
const googleProvider = new GoogleAuthProvider();
const githubProvider = new GithubAuthProvider();
// Sign in with Google functionality
export const signInWithGoogle = async () => {
try {
return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
const result = await signInWithPopup(firebaseAuth, googleProvider);
return {
success: true,
user: result.user,
error: null,
};
});
} catch (error: any) {
return {
success: false,
user: null,
error: error.message,
};
}
};
// Sign in with GitHub functionality
export const signInWithGithub = async () => {
try {
return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
const result = await signInWithPopup(firebaseAuth, githubProvider);
return {
success: true,
user: result.user,
error: null,
};
});
} catch (error: any) {
return {
success: false,
user: null,
error: error.message,
};
}
};
// Sign in with email and password
export async function signInWithCredentials(email: string, password: string) {
try {
return setPersistence(firebaseAuth, browserSessionPersistence).then(async () => {
const userCredential = await signInWithEmailAndPassword(
firebaseAuth,
email,
password,
);
return {
success: true,
user: userCredential.user,
error: null,
};
});
} catch (error: any) {
return {
success: false,
user: null,
error: error.message || 'Failed to sign in with email/password',
};
}
}
// Sign out functionality
export const firebaseSignOut = async () => {
try {
await signOut(firebaseAuth);
return { success: true };
} catch (error: any) {
return {
success: false,
error: error.message,
};
}
};
// Auth state observer
export const onAuthStateChanged = (callback: (user: any) => void) => {
return firebaseAuth.onAuthStateChanged(callback);
};
将身份验证和会话数据添加到 AppProvider
import * as React from 'react';
import DashboardIcon from '@mui/icons-material/Dashboard';
import ShoppingCartIcon from '@mui/icons-material/ShoppingCart';
import { ReactRouterAppProvider } from '@toolpad/core/react-router';
import { Outlet, useNavigate } from 'react-router';
import type { Navigation } from '@toolpad/core';
import {
firebaseSignOut,
signInWithGoogle,
onAuthStateChanged,
} from './firebase/auth';
import SessionContext, { type Session } from './SessionContext';
const NAVIGATION: Navigation = [
{
kind: 'header',
title: 'Main items',
},
{
title: 'Dashboard',
icon: <DashboardIcon />,
},
{
segment: 'orders',
title: 'Orders',
icon: <ShoppingCartIcon />,
},
];
const BRANDING = {
title: 'My Toolpad Core App',
};
const AUTHENTICATION: Authentication = {
signIn: signInWithGoogle,
signOut: firebaseSignOut,
};
export default function App() {
const [session, setSession] = React.useState<Session | null>(null);
const [loading, setLoading] = React.useState(true);
const sessionContextValue = React.useMemo(
() => ({
session,
setSession,
loading,
}),
[session, loading],
);
React.useEffect(() => {
// Returns an `unsubscribe` function to be called during teardown
const unsubscribe = onAuthStateChanged((user: User | null) => {
if (user) {
setSession({
user: {
name: user.displayName || '',
email: user.email || '',
image: user.photoURL || '',
},
});
} else {
setSession(null);
}
setLoading(false);
});
return () => unsubscribe();
}, []);
return (
<ReactRouterAppProvider
navigation={NAVIGATION}
branding={BRANDING}
session={session}
authentication={AUTHENTICATION}
>
<SessionContext.Provider value={sessionContextValue}>
<Outlet />
</SessionContext.Provider>
</ReactRouterAppProvider>
);
}
保护仪表盘布局内的路由
import * as React from 'react';
import LinearProgress from '@mui/material/LinearProgress';
import { Outlet, Navigate, useLocation } from 'react-router';
import { DashboardLayout } from '@toolpad/core/DashboardLayout';
import { PageContainer } from '@toolpad/core/PageContainer';
import { useSession } from '../SessionContext';
export default function Layout() {
const { session, loading } = useSession();
const location = useLocation();
if (loading) {
return (
<div style={{ width: '100%' }}>
<LinearProgress />
</div>
);
}
if (!session) {
// Add the `callbackUrl` search parameter
const redirectTo = `/sign-in?callbackUrl=${encodeURIComponent(location.pathname)}`;
return <Navigate to={redirectTo} replace />;
}
return (
<DashboardLayout>
<PageContainer>
<Outlet />
</PageContainer>
</DashboardLayout>
);
}
您可以通过此机制保护任何页面或页面组。
使用 SignInPage
组件创建登录页面
'use client';
import * as React from 'react';
import { SignInPage } from '@toolpad/core/SignInPage';
import LinearProgress from '@mui/material/LinearProgress';
import { Navigate, useNavigate } from 'react-router';
import { useSession, type Session } from '../SessionContext';
import {
signInWithGoogle,
signInWithGithub,
signInWithCredentials,
} from '../firebase/auth';
export default function SignIn() {
const { session, setSession, loading } = useSession();
const navigate = useNavigate();
if (loading) {
return <LinearProgress />;
}
if (session) {
return <Navigate to="/" />;
}
return (
<SignInPage
providers={[
{ id: 'google', name: 'Google' },
{ id: 'github', name: 'GitHub' },
{ id: 'credentials', name: 'Credentials' },
]}
signIn={async (provider, formData, callbackUrl) => {
let result;
try {
if (provider.id === 'google') {
result = await signInWithGoogle();
}
if (provider.id === 'github') {
result = await signInWithGithub();
}
if (provider.id === 'credentials') {
const email = formData?.get('email') as string;
const password = formData?.get('password') as string;
if (!email || !password) {
return { error: 'Email and password are required' };
}
result = await signInWithCredentials(email, password);
}
if (result?.success && result?.user) {
// Convert Firebase user to Session format
const userSession: Session = {
user: {
name: result.user.displayName || '',
email: result.user.email || '',
image: result.user.photoURL || '',
},
};
setSession(userSession);
navigate(callbackUrl || '/', { replace: true });
return {};
}
return { error: result?.error || 'Failed to sign in' };
} catch (error) {
return {
error: error instanceof Error ? error.message : 'An error occurred',
};
}
}}
/>
);
}
将登录页面添加到路由器
import * as React from 'react';
import * as ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router';
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(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
);