Просмотр исходного кода

✨ feat(big-shadcn): 新增shadcn-ui用户认证系统

- 创建完整的用户认证体系,包含登录、注册、用户中心功能
- 新增错误页面、404页面、受保护路由组件
- 集成AuthProvider上下文管理用户状态和token
- 构建响应式首页、登录页、注册页和个人中心页面
- 添加数据大屏开发指令文档,提供最佳实践和组件模板
yourname 7 месяцев назад
Родитель
Сommit
4193fb0e56

+ 376 - 0
.roo/commands/shadcn-dashboard.md

@@ -0,0 +1,376 @@
+---
+description: "Shadcn-ui 数据大屏开发指令"
+---
+
+## 概述
+
+基于 `src/client/admin-shadcn/pages/Dashboard.tsx` 中数据大屏的实现,提取可复用的数据可视化开发模式和最佳实践,适用于基于 Shadcn-ui 的数据大屏和仪表板开发。
+
+## 核心特性
+
+### 1. 响应式布局系统
+- **网格布局**:使用 CSS Grid 和 Flexbox 实现响应式卡片布局
+- **断点设计**:支持 sm, md, lg, xl, 2xl 五个断点
+- **自适应卡片**:卡片宽度根据屏幕尺寸自动调整
+
+### 2. 数据可视化组件
+- **图表集成**:基于 Recharts 的图表组件封装
+- **统计卡片**:标准化的 KPI 展示卡片
+- **实时数据**:支持数据实时更新和刷新
+
+### 3. 主题一致性
+- **深色/浅色主题**:自动适配系统主题
+- **色彩规范**:使用 Tailwind CSS 色彩系统
+- **动画效果**:平滑的过渡和加载动画
+
+## 开发模板
+
+### 基础结构模板
+
+```typescript
+// 1. 核心导入
+import { useState, useEffect } from 'react';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Button } from '@/client/components/ui/button';
+import { RefreshCw, TrendingUp, TrendingDown } from 'lucide-react';
+import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
+
+// 2. 数据类型定义
+interface DashboardData {
+  totalUsers: number;
+  totalRevenue: number;
+  monthlyGrowth: number;
+  chartData: Array<{
+    name: string;
+    value: number;
+    growth: number;
+  }>;
+}
+
+// 3. 组件状态
+const [data, setData] = useState<DashboardData | null>(null);
+const [loading, setLoading] = useState(true);
+const [lastUpdated, setLastUpdated] = useState<Date>(new Date());
+```
+
+### 统计卡片模板
+
+#### 基础统计卡片
+```typescript
+<Card>
+  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+    <CardTitle className="text-sm font-medium">总用户数</CardTitle>
+    <Users className="h-4 w-4 text-muted-foreground" />
+  </CardHeader>
+  <CardContent>
+    <div className="text-2xl font-bold">{data?.totalUsers.toLocaleString()}</div>
+    <p className="text-xs text-muted-foreground">
+      {data?.monthlyGrowth > 0 ? '+' : ''}{data?.monthlyGrowth}% 较上月
+    </p>
+  </CardContent>
+</Card>
+```
+
+#### 带趋势指标卡片
+```typescript
+<Card>
+  <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
+    <CardTitle className="text-sm font-medium">总收入</CardTitle>
+    <DollarSign className="h-4 w-4 text-muted-foreground" />
+  </CardHeader>
+  <CardContent>
+    <div className="text-2xl font-bold">${data?.totalRevenue.toLocaleString()}</div>
+    <div className="flex items-center mt-1">
+      {data?.monthlyGrowth > 0 ? (
+        <TrendingUp className="h-4 w-4 text-green-500 mr-1" />
+      ) : (
+        <TrendingDown className="h-4 w-4 text-red-500 mr-1" />
+      )}
+      <p className={`text-xs ${data?.monthlyGrowth > 0 ? 'text-green-500' : 'text-red-500'}`}>
+        {Math.abs(data?.monthlyGrowth || 0)}% 较上月
+      </p>
+    </div>
+  </CardContent>
+</Card>
+```
+
+### 图表组件模板
+
+#### 柱状图
+```typescript
+<Card className="col-span-4">
+  <CardHeader>
+    <CardTitle>月度数据概览</CardTitle>
+    <CardDescription>过去12个月的数据趋势</CardDescription>
+  </CardHeader>
+  <CardContent className="pl-2">
+    <ResponsiveContainer width="100%" height={350}>
+      <BarChart data={chartData}>
+        <CartesianGrid strokeDasharray="3 3" />
+        <XAxis dataKey="name" />
+        <YAxis />
+        <Tooltip />
+        <Legend />
+        <Bar dataKey="value" fill="#8884d8" />
+      </BarChart>
+    </ResponsiveContainer>
+  </CardContent>
+</Card>
+```
+
+#### 折线图
+```typescript
+<Card className="col-span-4">
+  <CardHeader>
+    <CardTitle>趋势分析</CardTitle>
+    <CardDescription>数据变化趋势</CardDescription>
+  </CardHeader>
+  <CardContent>
+    <ResponsiveContainer width="100%" height={300}>
+      <LineChart data={lineChartData}>
+        <CartesianGrid strokeDasharray="3 3" />
+        <XAxis dataKey="name" />
+        <YAxis />
+        <Tooltip />
+        <Legend />
+        <Line type="monotone" dataKey="users" stroke="#8884d8" strokeWidth={2} />
+        <Line type="monotone" dataKey="revenue" stroke="#82ca9d" strokeWidth={2} />
+      </LineChart>
+    </ResponsiveContainer>
+  </CardContent>
+</Card>
+```
+
+#### 饼图
+```typescript
+<Card className="col-span-3">
+  <CardHeader>
+    <CardTitle>数据分布</CardTitle>
+    <CardDescription>按类别分布</CardDescription>
+  </CardHeader>
+  <CardContent>
+    <ResponsiveContainer width="100%" height={300}>
+      <PieChart>
+        <Pie
+          data={pieData}
+          cx="50%"
+          cy="50%"
+          labelLine={false}
+          label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
+          outerRadius={80}
+          fill="#8884d8"
+          dataKey="value"
+        >
+          {pieData.map((entry, index) => (
+            <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
+          ))}
+        </Pie>
+        <Tooltip />
+      </PieChart>
+    </ResponsiveContainer>
+  </CardContent>
+</Card>
+```
+
+### 响应式网格布局
+
+#### 基础网格
+```typescript
+<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
+  {/* 统计卡片 */}
+  <Card>...</Card>
+  <Card>...</Card>
+  <Card>...</Card>
+  <Card>...</Card>
+</div>
+```
+
+#### 复杂网格
+```typescript
+<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
+  <Card className="col-span-4">...</Card>
+  <Card className="col-span-3">...</Card>
+</div>
+```
+
+### 数据加载状态
+
+#### 骨架屏
+```typescript
+import { Skeleton } from '@/client/components/ui/skeleton';
+
+const LoadingCard = () => (
+  <Card>
+    <CardHeader>
+      <Skeleton className="h-4 w-[100px]" />
+    </CardHeader>
+    <CardContent>
+      <Skeleton className="h-8 w-[150px] mb-2" />
+      <Skeleton className="h-3 w-[100px]" />
+    </CardContent>
+  </Card>
+);
+```
+
+#### 加载动画
+```typescript
+import { Loader2 } from 'lucide-react';
+
+const LoadingSpinner = () => (
+  <div className="flex items-center justify-center h-[400px]">
+    <Loader2 className="h-8 w-8 animate-spin" />
+  </div>
+);
+```
+
+### 实时数据更新
+
+#### 自动刷新
+```typescript
+const Dashboard = () => {
+  const [data, setData] = useState<DashboardData | null>(null);
+  const [autoRefresh, setAutoRefresh] = useState(true);
+
+  useEffect(() => {
+    fetchData();
+    
+    if (autoRefresh) {
+      const interval = setInterval(() => {
+        fetchData();
+      }, 30000); // 30秒刷新一次
+      
+      return () => clearInterval(interval);
+    }
+  }, [autoRefresh]);
+
+  const fetchData = async () => {
+    try {
+      const response = await apiClient.$get();
+      setData(response.data);
+    } catch (error) {
+      console.error('Failed to fetch dashboard data:', error);
+    }
+  };
+
+  return (
+    <div>
+      <div className="flex justify-between items-center mb-4">
+        <h1 className="text-3xl font-bold">数据大屏</h1>
+        <Button 
+          variant="outline" 
+          size="sm"
+          onClick={() => fetchData()}
+        >
+          <RefreshCw className="h-4 w-4 mr-2" />
+          刷新数据
+        </Button>
+      </div>
+      {/* 数据展示 */}
+    </div>
+  );
+};
+```
+
+## 主题配置
+
+### 颜色配置
+```typescript
+const COLORS = {
+  primary: '#8884d8',
+  secondary: '#82ca9d',
+  accent: '#ffc658',
+  danger: '#ff7c7c',
+  warning: '#ffb347',
+  success: '#00c49f',
+};
+```
+
+### 图表主题
+```typescript
+const chartConfig = {
+  users: {
+    label: "用户数",
+    color: "#2563eb",
+  },
+  revenue: {
+    label: "收入",
+    color: "#16a34a",
+  },
+  orders: {
+    label: "订单数",
+    color: "#9333ea",
+  },
+} satisfies ChartConfig;
+```
+
+## 最佳实践
+
+### 1. 性能优化
+- 使用 `useMemo` 缓存计算数据
+- 实现数据分页加载
+- 使用虚拟滚动处理大量数据
+
+### 2. 用户体验
+- 提供数据刷新功能
+- 显示最后更新时间
+- 添加数据加载状态
+
+### 3. 无障碍设计
+- 为图表添加描述性标签
+- 提供键盘导航支持
+- 使用高对比度颜色
+
+### 4. 响应式设计
+```typescript
+// 响应式断点
+const breakpoints = {
+  sm: 640,
+  md: 768,
+  lg: 1024,
+  xl: 1280,
+  2xl: 1536,
+};
+```
+
+## 完整示例
+
+### 综合数据大屏
+```typescript
+import { Dashboard } from '@/client/admin-shadcn/pages/Dashboard';
+
+const DashboardPage = () => {
+  return (
+    <div className="flex-1 space-y-4 p-4 md:p-8 pt-6">
+      <div className="flex items-center justify-between space-y-2">
+        <h2 className="text-3xl font-bold tracking-tight">数据大屏</h2>
+        <div className="flex items-center space-x-2">
+          <Button variant="outline" size="sm">
+            <RefreshCw className="h-4 w-4 mr-2" />
+            刷新
+          </Button>
+        </div>
+      </div>
+      
+      <Dashboard />
+    </div>
+  );
+};
+```
+
+## 组件导入清单
+
+```typescript
+// UI 组件
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Button } from '@/client/components/ui/button';
+import { Skeleton } from '@/client/components/ui/skeleton';
+
+// 图表组件
+import { BarChart, Bar, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
+
+// 图标
+import { Users, DollarSign, TrendingUp, TrendingDown, RefreshCw, Activity } from 'lucide-react';
+
+// 工具
+import { useState, useEffect, useMemo } from 'react';
+import { format } from 'date-fns';

+ 65 - 0
src/client/big-shadcn/components/ErrorPage.tsx

@@ -0,0 +1,65 @@
+import React from 'react';
+import { useRouteError, useNavigate } from 'react-router';
+import { AlertCircle, RotateCcw, Home } from 'lucide-react';
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Alert, AlertDescription, AlertTitle } from '@/client/components/ui/alert';
+
+export const ErrorPage = () => {
+  const navigate = useNavigate();
+  const error = useRouteError() as any;
+  const errorMessage = error?.statusText || error?.message || '未知错误';
+  
+  return (
+    <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
+      <Card className="w-full max-w-md border-0 shadow-xl">
+        <CardHeader className="bg-destructive/10">
+          <CardTitle className="flex items-center space-x-2 text-destructive">
+            <AlertCircle className="h-6 w-6" />
+            <span>发生错误</span>
+          </CardTitle>
+          <CardDescription>
+            抱歉,页面加载时遇到了问题
+          </CardDescription>
+        </CardHeader>
+        
+        <CardContent className="space-y-4">
+          <Alert variant="destructive">
+            <AlertCircle className="h-4 w-4" />
+            <AlertTitle>错误详情</AlertTitle>
+            <AlertDescription>
+              {error?.message || '未知错误'}
+            </AlertDescription>
+          </Alert>
+          
+          {error?.stack && (
+            <div className="space-y-2">
+              <h4 className="text-sm font-medium text-muted-foreground">技术信息</h4>
+              <pre className="text-xs text-muted-foreground bg-muted/50 p-3 rounded-lg overflow-x-auto max-h-40">
+                {error.stack}
+              </pre>
+            </div>
+          )}
+        </CardContent>
+        
+        <CardFooter className="flex gap-2">
+          <Button
+            onClick={() => navigate(0)}
+            className="flex-1"
+            variant="outline"
+          >
+            <RotateCcw className="h-4 w-4 mr-2" />
+            重新加载
+          </Button>
+          <Button
+            onClick={() => navigate('/')}
+            className="flex-1"
+          >
+            <Home className="h-4 w-4 mr-2" />
+            返回首页
+          </Button>
+        </CardFooter>
+      </Card>
+    </div>
+  );
+};

+ 49 - 0
src/client/big-shadcn/components/NotFoundPage.tsx

@@ -0,0 +1,49 @@
+import React from 'react';
+import { useNavigate } from 'react-router';
+import { ArrowLeft, Home } from 'lucide-react';
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
+
+export const NotFoundPage = () => {
+  const navigate = useNavigate();
+  
+  return (
+    <div className="flex flex-col items-center justify-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 p-4">
+      <Card className="w-full max-w-md border-0 shadow-xl">
+        <CardHeader className="text-center">
+          <div className="mx-auto flex h-20 w-20 items-center justify-center rounded-full bg-primary/10 mb-4">
+            <span className="text-4xl font-bold text-primary">404</span>
+          </div>
+          <CardTitle className="text-2xl">页面未找到</CardTitle>
+          <CardDescription>
+            抱歉,您访问的页面不存在或已被移除
+          </CardDescription>
+        </CardHeader>
+        
+        <CardContent className="text-center">
+          <p className="text-muted-foreground">
+            请检查URL是否正确,或尝试以下操作:
+          </p>
+        </CardContent>
+        
+        <CardFooter className="flex gap-2">
+          <Button
+            onClick={() => navigate(-1)}
+            variant="outline"
+            className="flex-1"
+          >
+            <ArrowLeft className="h-4 w-4 mr-2" />
+            返回上一页
+          </Button>
+          <Button
+            onClick={() => navigate('/')}
+            className="flex-1"
+          >
+            <Home className="h-4 w-4 mr-2" />
+            返回首页
+          </Button>
+        </CardFooter>
+      </Card>
+    </div>
+  );
+};

+ 44 - 0
src/client/big-shadcn/components/ProtectedRoute.tsx

@@ -0,0 +1,44 @@
+import React, { useEffect } from 'react';
+import { useNavigate } from 'react-router';
+import { useAuth } from '../hooks/AuthProvider';
+import { Skeleton } from '@/client/components/ui/skeleton';
+import { Card, CardContent } from '@/client/components/ui/card';
+
+export const ProtectedRoute = ({ children }: { children: React.ReactNode }) => {
+  const { isAuthenticated, isLoading } = useAuth();
+  const navigate = useNavigate();
+  
+  useEffect(() => {
+    // 只有在加载完成且未认证时才重定向
+    if (!isLoading && !isAuthenticated) {
+      navigate('/login', { replace: true });
+    }
+  }, [isAuthenticated, isLoading, navigate]);
+  
+  // 显示加载状态,直到认证检查完成
+  if (isLoading) {
+    return (
+      <div className="flex justify-center items-center min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
+        <Card className="border-0 shadow-lg">
+          <CardContent className="flex flex-col items-center space-y-4 py-12 px-8">
+            <div className="relative">
+              <Skeleton className="h-12 w-12 rounded-full" />
+              <div className="absolute inset-0 rounded-full bg-primary/20 animate-pulse" />
+            </div>
+            <div className="space-y-2 text-center">
+              <Skeleton className="h-4 w-32" />
+              <Skeleton className="h-3 w-24" />
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+    );
+  }
+  
+  // 如果未认证且不再加载中,不显示任何内容(等待重定向)
+  if (!isAuthenticated) {
+    return null;
+  }
+  
+  return children;
+};

+ 140 - 0
src/client/big-shadcn/hooks/AuthProvider.tsx

@@ -0,0 +1,140 @@
+import React, { useState, useEffect, createContext, useContext } from 'react';
+
+import {
+  useQuery,
+  useQueryClient,
+} from '@tanstack/react-query';
+import axios from 'axios';
+import 'dayjs/locale/zh-cn';
+import type {
+  AuthContextType
+} from '@/share/types';
+import { authClient } from '@/client/api';
+import type { InferResponseType, InferRequestType } from 'hono/client';
+
+export type User = InferResponseType<typeof authClient.me.$get, 200>;
+
+
+// 创建认证上下文
+const AuthContext = createContext<AuthContextType<User> | null>(null);
+
+// 认证提供器组件
+export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
+  const [user, setUser] = useState<User | null>(null);
+  const [token, setToken] = useState<string | null>(localStorage.getItem('token'));
+  const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
+  const queryClient = useQueryClient();
+
+  // 声明handleLogout函数
+  const handleLogout = async () => {
+    try {
+      // 如果已登录,调用登出API
+      if (token) {
+        await authClient.logout.$post();
+      }
+    } catch (error) {
+      console.error('登出请求失败:', error);
+    } finally {
+      // 清除本地状态
+      setToken(null);
+      setUser(null);
+      setIsAuthenticated(false);
+      localStorage.removeItem('token');
+      // 清除Authorization头
+      delete axios.defaults.headers.common['Authorization'];
+      console.log('登出时已删除全局Authorization头');
+      // 清除所有查询缓存
+      queryClient.clear();
+    }
+  };
+
+  // 使用useQuery检查登录状态
+  const { isFetching: isLoading } = useQuery({
+    queryKey: ['auth', 'status', token],
+    queryFn: async () => {
+      if (!token) {
+        setIsAuthenticated(false);
+        setUser(null);
+        return null;
+      }
+
+      try {
+        // 设置全局默认请求头
+        axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
+        // 使用API验证当前用户
+        const res = await authClient.me.$get();
+        if (res.status !== 200) {
+          const result = await res.json();
+          throw new Error(result.message)
+        }
+        const currentUser = await res.json();
+        setUser(currentUser);
+        setIsAuthenticated(true);
+        return { isValid: true, user: currentUser };
+      } catch (error) {
+        return { isValid: false };
+      }
+    },
+    enabled: !!token,
+    refetchOnWindowFocus: false,
+    retry: false
+  });
+
+  const handleLogin = async (username: string, password: string, latitude?: number, longitude?: number): Promise<void> => {
+    try {
+      // 使用AuthAPI登录
+      const response = await authClient.login.$post({
+        json: {
+          username,
+          password
+        }
+      })
+      if (response.status !== 200) {
+        const result = await response.json()
+        throw new Error(result.message);
+      }
+
+      const result = await response.json()
+
+      // 保存token和用户信息
+      const { token: newToken, user: newUser } = result;
+
+      // 设置全局默认请求头
+      axios.defaults.headers.common['Authorization'] = `Bearer ${newToken}`;
+
+      // 保存状态
+      setToken(newToken);
+      setUser(newUser);
+      setIsAuthenticated(true);
+      localStorage.setItem('token', newToken);
+
+    } catch (error) {
+      console.error('登录失败:', error);
+      throw error;
+    }
+  };
+
+  return (
+    <AuthContext.Provider
+      value={{
+        user,
+        token,
+        login: handleLogin,
+        logout: handleLogout,
+        isAuthenticated,
+        isLoading
+      }}
+    >
+      {children}
+    </AuthContext.Provider>
+  );
+};
+
+// 使用上下文的钩子
+export const useAuth = () => {
+  const context = useContext(AuthContext);
+  if (!context) {
+    throw new Error('useAuth必须在AuthProvider内部使用');
+  }
+  return context;
+};

+ 36 - 0
src/client/big-shadcn/index.tsx

@@ -0,0 +1,36 @@
+import { createRoot } from 'react-dom/client'
+import { getGlobalConfig } from '../utils/utils'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { AuthProvider } from './hooks/AuthProvider'
+import { RouterProvider } from 'react-router-dom'
+import { router } from './routes'
+import { Toaster } from '@/client/components/ui/sonner'
+
+// 创建QueryClient实例
+const queryClient = new QueryClient();
+
+// 应用入口组件
+const App = () => {
+  return (
+    <QueryClientProvider client={queryClient}>
+      <AuthProvider>
+        <RouterProvider router={router} />
+        <Toaster 
+          position="top-right"
+          expand={false}
+          richColors
+          closeButton
+          duration={3000}
+        />
+      </AuthProvider>
+    </QueryClientProvider>
+  )
+};
+
+const rootElement = document.getElementById('root')
+if (rootElement) {
+  const root = createRoot(rootElement)
+  root.render(
+    <App />
+  )
+}

+ 14 - 0
src/client/big-shadcn/layouts/MainLayout.tsx

@@ -0,0 +1,14 @@
+import React, { useState, useEffect, useMemo } from 'react';
+import {
+  Outlet
+} from 'react-router';
+
+/**
+ * 主布局组件
+ * 包含侧边栏、顶部导航和内容区域
+ */
+export const MainLayout = () => {
+  return (
+    <Outlet />
+  );
+};

+ 210 - 0
src/client/big-shadcn/pages/HomePage.tsx

@@ -0,0 +1,210 @@
+import React from 'react';
+import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
+import { useNavigate } from 'react-router-dom';
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
+import { Badge } from '@/client/components/ui/badge';
+import { 
+  ShieldCheck, 
+  UserCircle, 
+  Smartphone, 
+  LogIn, 
+  UserPlus,
+  ExternalLink
+} from 'lucide-react';
+
+const HomePage: React.FC = () => {
+  const { user } = useAuth();
+  const navigate = useNavigate();
+
+  return (
+    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
+      {/* 顶部导航 */}
+      <header className="sticky top-0 z-50 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
+        <div className="container mx-auto flex h-16 items-center justify-between">
+          <div className="flex items-center space-x-2">
+            <ShieldCheck className="h-6 w-6 text-primary" />
+            <h1 className="text-xl font-bold">网站首页</h1>
+          </div>
+          
+          <div className="flex items-center space-x-4">
+            {user ? (
+              <div 
+                className="flex items-center space-x-3 cursor-pointer hover:bg-muted/50 rounded-lg px-3 py-2 transition-colors"
+                onClick={() => navigate('/member')}
+              >
+                <Avatar className="h-8 w-8">
+                  <AvatarImage src={`https://avatar.vercel.sh/${user.username}`} />
+                  <AvatarFallback className="bg-primary text-primary-foreground">
+                    {user.username?.charAt(0).toUpperCase()}
+                  </AvatarFallback>
+                </Avatar>
+                <span className="hidden md:inline text-sm font-medium">{user.username}</span>
+              </div>
+            ) : (
+              <div className="flex items-center space-x-2">
+                <Button
+                  variant="ghost"
+                  size="sm"
+                  onClick={() => navigate('/login')}
+                  className="flex items-center space-x-1"
+                >
+                  <LogIn className="h-4 w-4" />
+                  <span>登录</span>
+                </Button>
+                <Button
+                  size="sm"
+                  onClick={() => navigate('/register')}
+                  className="flex items-center space-x-1"
+                >
+                  <UserPlus className="h-4 w-4" />
+                  <span>注册</span>
+                </Button>
+              </div>
+            )}
+          </div>
+        </div>
+      </header>
+
+      {/* 主内容区 */}
+      <main className="container mx-auto py-8">
+        <div className="mx-auto max-w-6xl space-y-8">
+          {/* 欢迎区域 */}
+          <Card className="border-0 shadow-lg">
+            <CardHeader>
+              <CardTitle className="text-3xl font-bold bg-gradient-to-r from-primary to-purple-600 bg-clip-text text-transparent">
+                欢迎使用网站模板
+              </CardTitle>
+              <CardDescription className="text-lg">
+                这是一个现代化的网站首页模板,采用 shadcn/ui 设计系统构建
+              </CardDescription>
+            </CardHeader>
+            <CardContent>
+              <p className="text-muted-foreground">
+                已集成完整的用户认证系统,包含登录、注册和用户中心功能。
+                使用 React 19 + TypeScript + shadcn/ui + Tailwind CSS 技术栈。
+              </p>
+            </CardContent>
+          </Card>
+
+          {/* 功能特性卡片 */}
+          <div className="grid gap-6 md:grid-cols-3">
+            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
+              <CardHeader>
+                <div className="flex items-center space-x-2">
+                  <div className="p-2 bg-blue-100 rounded-lg">
+                    <ShieldCheck className="h-6 w-6 text-blue-600" />
+                  </div>
+                  <CardTitle className="text-lg">用户认证</CardTitle>
+                </div>
+              </CardHeader>
+              <CardContent>
+                <CardDescription>
+                  完整的登录、注册功能,基于 JWT 的现代化认证系统
+                </CardDescription>
+                <Badge variant="secondary" className="mt-2">安全认证</Badge>
+              </CardContent>
+            </Card>
+
+            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
+              <CardHeader>
+                <div className="flex items-center space-x-2">
+                  <div className="p-2 bg-green-100 rounded-lg">
+                    <UserCircle className="h-6 w-6 text-green-600" />
+                  </div>
+                  <CardTitle className="text-lg">用户中心</CardTitle>
+                </div>
+              </CardHeader>
+              <CardContent>
+                <CardDescription>
+                  用户可以查看和管理个人信息,支持头像上传和个人资料编辑
+                </CardDescription>
+                <Badge variant="secondary" className="mt-2">个人管理</Badge>
+              </CardContent>
+            </Card>
+
+            <Card className="border-0 shadow-lg hover:shadow-xl transition-shadow">
+              <CardHeader>
+                <div className="flex items-center space-x-2">
+                  <div className="p-2 bg-purple-100 rounded-lg">
+                    <Smartphone className="h-6 w-6 text-purple-600" />
+                  </div>
+                  <CardTitle className="text-lg">响应式设计</CardTitle>
+                </div>
+              </CardHeader>
+              <CardContent>
+                <CardDescription>
+                  适配各种设备屏幕,采用移动端优先的设计理念
+                </CardDescription>
+                <Badge variant="secondary" className="mt-2">移动优先</Badge>
+              </CardContent>
+            </Card>
+          </div>
+
+          {/* 快速链接 */}
+          <Card className="border-0 shadow-lg">
+            <CardHeader>
+              <CardTitle className="flex items-center space-x-2">
+                <ExternalLink className="h-5 w-5" />
+                <span>快速访问</span>
+              </CardTitle>
+            </CardHeader>
+            <CardContent>
+              <div className="flex flex-wrap gap-4">
+                <Button
+                  variant="outline"
+                  onClick={() => window.open('/admin', '_blank')}
+                  className="flex items-center space-x-2"
+                >
+                  <span>管理后台</span>
+                  <ExternalLink className="h-4 w-4" />
+                </Button>
+                <Button
+                  variant="outline"
+                  onClick={() => window.open('/ui', '_blank')}
+                  className="flex items-center space-x-2"
+                >
+                  <span>API 文档</span>
+                  <ExternalLink className="h-4 w-4" />
+                </Button>
+              </div>
+            </CardContent>
+          </Card>
+        </div>
+      </main>
+
+      {/* 页脚 */}
+      <footer className="border-t bg-background">
+        <div className="container mx-auto py-6">
+          <div className="flex flex-col items-center justify-center space-y-2">
+            <p className="text-sm text-muted-foreground">
+              网站模板 ©{new Date().getFullYear()} Created with React & shadcn/ui
+            </p>
+            <div className="flex items-center space-x-4 text-sm">
+              <Button
+                variant="link"
+                size="sm"
+                onClick={() => window.open('/admin', '_blank')}
+                className="text-muted-foreground hover:text-primary"
+              >
+                管理后台
+              </Button>
+              <span className="text-muted-foreground">•</span>
+              <Button
+                variant="link"
+                size="sm"
+                onClick={() => window.open('/ui', '_blank')}
+                className="text-muted-foreground hover:text-primary"
+              >
+                API 文档
+              </Button>
+            </div>
+          </div>
+        </div>
+      </footer>
+    </div>
+  );
+};
+
+export default HomePage;

+ 133 - 0
src/client/big-shadcn/pages/LoginPage.tsx

@@ -0,0 +1,133 @@
+import React from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
+import { toast } from 'sonner';
+
+import { Button } from '@/client/components/ui/button';
+import { Input } from '@/client/components/ui/input';
+import { Label } from '@/client/components/ui/label';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
+import { Eye, EyeOff, User, Lock } from 'lucide-react';
+
+const loginSchema = z.object({
+  username: z.string().min(3, '用户名至少3个字符'),
+  password: z.string().min(6, '密码至少6个字符'),
+});
+
+type LoginFormData = z.infer<typeof loginSchema>;
+
+const LoginPage: React.FC = () => {
+  const { login } = useAuth();
+  const navigate = useNavigate();
+  
+  const form = useForm<LoginFormData>({
+    resolver: zodResolver(loginSchema),
+    defaultValues: {
+      username: '',
+      password: '',
+    },
+  });
+
+  const onSubmit = async (data: LoginFormData) => {
+    try {
+      await login(data.username, data.password);
+      toast.success('登录成功!');
+      navigate('/');
+    } catch (error) {
+      toast.error(error instanceof Error ? error.message : '登录失败,请检查用户名和密码');
+    }
+  };
+
+  return (
+    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4 py-12">
+      <Card className="w-full max-w-md border-0 shadow-xl">
+        <CardHeader className="space-y-1">
+          <CardTitle className="text-2xl font-bold text-center">欢迎回来</CardTitle>
+          <CardDescription className="text-center">
+            登录您的账号以继续
+          </CardDescription>
+        </CardHeader>
+        
+        <CardContent>
+          <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
+              <FormField
+                control={form.control}
+                name="username"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>用户名</FormLabel>
+                    <FormControl>
+                      <div className="relative">
+                        <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+                        <Input
+                          placeholder="请输入用户名"
+                          className="pl-10"
+                          {...field}
+                        />
+                      </div>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="password"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>密码</FormLabel>
+                    <FormControl>
+                      <div className="relative">
+                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+                        <Input
+                          type="password"
+                          placeholder="请输入密码"
+                          className="pl-10"
+                          {...field}
+                        />
+                      </div>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <Button 
+                type="submit" 
+                className="w-full"
+                disabled={form.formState.isSubmitting}
+              >
+                {form.formState.isSubmitting ? '登录中...' : '登录'}
+              </Button>
+            </form>
+          </Form>
+        </CardContent>
+
+        <CardFooter className="flex flex-col space-y-4">
+          <div className="text-sm text-center">
+            <span className="text-muted-foreground">还没有账号?</span>
+            <Button
+              variant="link"
+              className="px-1"
+              asChild
+            >
+              <Link to="/register">立即注册</Link>
+            </Button>
+          </div>
+          
+          <div className="text-xs text-center text-muted-foreground">
+            <p>测试账号:admin / admin123</p>
+          </div>
+        </CardFooter>
+      </Card>
+    </div>
+  );
+};
+
+export default LoginPage;

+ 234 - 0
src/client/big-shadcn/pages/MemberPage.tsx

@@ -0,0 +1,234 @@
+import React from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
+import { Button } from '@/client/components/ui/button';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Avatar, AvatarFallback, AvatarImage } from '@/client/components/ui/avatar';
+import { Badge } from '@/client/components/ui/badge';
+import { Separator } from '@/client/components/ui/separator';
+import { 
+  User, 
+  MapPin, 
+  Globe, 
+  Calendar, 
+  LogOut, 
+  Settings,
+  UserCog,
+  ShieldCheck,
+  Clock
+} from 'lucide-react';
+import { format } from 'date-fns';
+import { zhCN } from 'date-fns/locale';
+
+const MemberPage: React.FC = () => {
+  const navigate = useNavigate();
+  const { user, logout } = useAuth();
+
+  if (!user) {
+    return (
+      <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4">
+        <Card className="max-w-md">
+          <CardHeader>
+            <CardTitle>用户不存在</CardTitle>
+            <CardDescription>请先登录后再访问此页面</CardDescription>
+          </CardHeader>
+          <CardContent>
+            <Button onClick={() => navigate('/')} className="w-full">
+              返回首页
+            </Button>
+          </CardContent>
+        </Card>
+      </div>
+    );
+  }
+
+  return (
+    <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100">
+      <div className="container mx-auto py-8">
+        <div className="mx-auto max-w-4xl space-y-8">
+          {/* 用户资料卡片 */}
+          <Card className="border-0 shadow-lg">
+            <CardContent className="pt-6">
+              <div className="flex flex-col items-center space-y-4">
+                <Avatar className="h-24 w-24">
+                  <AvatarImage 
+                    src={user.avatar || `https://avatar.vercel.sh/${user.username}`} 
+                    alt={user.nickname || user.username}
+                  />
+                  <AvatarFallback className="text-2xl bg-primary text-primary-foreground">
+                    {user.username?.charAt(0).toUpperCase()}
+                  </AvatarFallback>
+                </Avatar>
+                
+                <div className="text-center space-y-2">
+                  <h1 className="text-3xl font-bold">{user.nickname || user.username}</h1>
+                  <p className="text-muted-foreground">@{user.username}</p>
+                </div>
+
+                <div className="flex items-center space-x-4">
+                  <div className="text-center">
+                    <p className="text-2xl font-bold">0</p>
+                    <p className="text-sm text-muted-foreground">内容</p>
+                  </div>
+                  <Separator orientation="vertical" className="h-8" />
+                  <div className="text-center">
+                    <p className="text-2xl font-bold">0</p>
+                    <p className="text-sm text-muted-foreground">关注</p>
+                  </div>
+                  <Separator orientation="vertical" className="h-8" />
+                  <div className="text-center">
+                    <p className="text-2xl font-bold">0</p>
+                    <p className="text-sm text-muted-foreground">粉丝</p>
+                  </div>
+                </div>
+
+                <div className="flex items-center space-x-2">
+                  <Button 
+                    onClick={() => navigate('/profile/edit')}
+                    className="flex items-center space-x-2"
+                  >
+                    <UserCog className="h-4 w-4" />
+                    <span>编辑资料</span>
+                  </Button>
+                  
+                  <Button 
+                    variant="outline"
+                    onClick={async () => {
+                      await logout();
+                      navigate('/');
+                    }}
+                    className="flex items-center space-x-2"
+                  >
+                    <LogOut className="h-4 w-4" />
+                    <span>退出登录</span>
+                  </Button>
+                </div>
+              </div>
+            </CardContent>
+          </Card>
+
+          {/* 个人资料详情 */}
+          <Card className="border-0 shadow-lg">
+            <CardHeader>
+              <CardTitle className="flex items-center space-x-2">
+                <User className="h-5 w-5" />
+                <span>个人资料</span>
+              </CardTitle>
+            </CardHeader>
+            <CardContent className="space-y-6">
+              <div className="grid gap-4">
+                <div className="space-y-1">
+                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                    <User className="h-4 w-4" />
+                    <span>用户名</span>
+                  </div>
+                  <p className="font-medium">{user.username}</p>
+                </div>
+
+                <div className="space-y-1">
+                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                    <ShieldCheck className="h-4 w-4" />
+                    <span>邮箱</span>
+                  </div>
+                  <p className="font-medium">{user.email || '未设置'}</p>
+                </div>
+
+                {(user as any).location && (
+                  <div className="space-y-1">
+                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                      <MapPin className="h-4 w-4" />
+                      <span>位置</span>
+                    </div>
+                    <p className="font-medium">{(user as any).location}</p>
+                  </div>
+                )}
+
+                {(user as any).website && (
+                  <div className="space-y-1">
+                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                      <Globe className="h-4 w-4" />
+                      <span>网站</span>
+                    </div>
+                    <a 
+                      href={(user as any).website}
+                      target="_blank"
+                      rel="noopener noreferrer"
+                      className="font-medium text-primary hover:underline"
+                    >
+                      {(user as any).website}
+                    </a>
+                  </div>
+                )}
+
+                {(user as any).bio && (
+                  <div className="space-y-1">
+                    <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                      <User className="h-4 w-4" />
+                      <span>个人简介</span>
+                    </div>
+                    <p className="font-medium">{(user as any).bio}</p>
+                  </div>
+                )}
+              </div>
+
+              <Separator />
+
+              <div className="grid gap-4 md:grid-cols-2">
+                <div className="space-y-1">
+                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                    <Calendar className="h-4 w-4" />
+                    <span>注册时间</span>
+                  </div>
+                  <p className="font-medium">
+                    {user.createdAt ? format(new Date(user.createdAt), 'yyyy年MM月dd日', { locale: zhCN }) : '未知'}
+                  </p>
+                </div>
+
+                <div className="space-y-1">
+                  <div className="flex items-center space-x-2 text-sm text-muted-foreground">
+                    <Clock className="h-4 w-4" />
+                    <span>最后登录</span>
+                  </div>
+                  <p className="font-medium">
+                    {user.updatedAt ? format(new Date(user.updatedAt), 'yyyy年MM月dd日 HH:mm', { locale: zhCN }) : '从未登录'}
+                  </p>
+                </div>
+              </div>
+            </CardContent>
+          </Card>
+
+          {/* 设置区域 */}
+          <Card className="border-0 shadow-lg">
+            <CardHeader>
+              <CardTitle className="flex items-center space-x-2">
+                <Settings className="h-5 w-5" />
+                <span>账号设置</span>
+              </CardTitle>
+            </CardHeader>
+            <CardContent className="space-y-4">
+              <Button 
+                variant="outline" 
+                className="w-full justify-start"
+                onClick={() => navigate('/profile/security')}
+              >
+                <ShieldCheck className="h-4 w-4 mr-2" />
+                <span>安全设置</span>
+              </Button>
+              
+              <Button 
+                variant="outline" 
+                className="w-full justify-start"
+                onClick={() => navigate('/profile/preferences')}
+              >
+                <Settings className="h-4 w-4 mr-2" />
+                <span>偏好设置</span>
+              </Button>
+            </CardContent>
+          </Card>
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default MemberPage;

+ 178 - 0
src/client/big-shadcn/pages/RegisterPage.tsx

@@ -0,0 +1,178 @@
+import React from 'react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from 'zod';
+import { Link, useNavigate } from 'react-router-dom';
+import { useAuth } from '@/client/home-shadcn/hooks/AuthProvider';
+import { toast } from 'sonner';
+import { authClient } from '@/client/api';
+
+import { Button } from '@/client/components/ui/button';
+import { Input } from '@/client/components/ui/input';
+import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
+import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
+import { Eye, EyeOff, User, Lock } from 'lucide-react';
+
+const registerSchema = z.object({
+  username: z.string()
+    .min(3, '用户名至少3个字符')
+    .max(20, '用户名不能超过20个字符')
+    .regex(/^[a-zA-Z0-9_-]+$/, '用户名只能包含字母、数字、下划线和连字符'),
+  password: z.string()
+    .min(6, '密码至少6个字符')
+    .max(30, '密码不能超过30个字符'),
+  confirmPassword: z.string(),
+}).refine((data) => data.password === data.confirmPassword, {
+  message: '两次密码输入不一致',
+  path: ['confirmPassword'],
+});
+
+type RegisterFormData = z.infer<typeof registerSchema>;
+
+const RegisterPage: React.FC = () => {
+  const { login } = useAuth();
+  const navigate = useNavigate();
+  
+  const form = useForm<RegisterFormData>({
+    resolver: zodResolver(registerSchema),
+    defaultValues: {
+      username: '',
+      password: '',
+      confirmPassword: '',
+    },
+  });
+
+  const onSubmit = async (data: RegisterFormData) => {
+    try {
+      const response = await authClient.register.$post({
+        json: {
+          username: data.username,
+          password: data.password,
+        }
+      });
+      
+      if (response.status !== 201) {
+        const result = await response.json();
+        throw new Error(result.message || '注册失败');
+      }
+      
+      // 注册成功后自动登录
+      await login(data.username, data.password);
+      toast.success('注册成功!正在为您登录...');
+      navigate('/');
+    } catch (error) {
+      toast.error(error instanceof Error ? error.message : '注册失败,请稍后重试');
+    }
+  };
+
+  return (
+    <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4 py-12">
+      <Card className="w-full max-w-md border-0 shadow-xl">
+        <CardHeader className="space-y-1">
+          <CardTitle className="text-2xl font-bold text-center">创建账号</CardTitle>
+          <CardDescription className="text-center">
+            填写以下信息创建新账号
+          </CardDescription>
+        </CardHeader>
+        
+        <CardContent>
+          <Form {...form}>
+            <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
+              <FormField
+                control={form.control}
+                name="username"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>用户名</FormLabel>
+                    <FormControl>
+                      <div className="relative">
+                        <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+                        <Input
+                          placeholder="请输入用户名"
+                          className="pl-10"
+                          {...field}
+                        />
+                      </div>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="password"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>密码</FormLabel>
+                    <FormControl>
+                      <div className="relative">
+                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+                        <Input
+                          type="password"
+                          placeholder="请输入密码"
+                          className="pl-10"
+                          {...field}
+                        />
+                      </div>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <FormField
+                control={form.control}
+                name="confirmPassword"
+                render={({ field }) => (
+                  <FormItem>
+                    <FormLabel>确认密码</FormLabel>
+                    <FormControl>
+                      <div className="relative">
+                        <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
+                        <Input
+                          type="password"
+                          placeholder="请再次输入密码"
+                          className="pl-10"
+                          {...field}
+                        />
+                      </div>
+                    </FormControl>
+                    <FormMessage />
+                  </FormItem>
+                )}
+              />
+
+              <Button 
+                type="submit" 
+                className="w-full"
+                disabled={form.formState.isSubmitting}
+              >
+                {form.formState.isSubmitting ? '注册中...' : '注册账号'}
+              </Button>
+            </form>
+          </Form>
+        </CardContent>
+
+        <CardFooter className="flex flex-col space-y-4">
+          <div className="text-sm text-center">
+            <span className="text-muted-foreground">已有账号?</span>
+            <Button
+              variant="link"
+              className="px-1"
+              asChild
+            >
+              <Link to="/login">立即登录</Link>
+            </Button>
+          </div>
+          
+          <div className="text-xs text-center text-muted-foreground">
+            <p>注册即表示您同意我们的服务条款</p>
+          </div>
+        </CardFooter>
+      </Card>
+    </div>
+  );
+};
+
+export default RegisterPage;

+ 49 - 0
src/client/big-shadcn/routes.tsx

@@ -0,0 +1,49 @@
+import React from 'react';
+import { createBrowserRouter, Navigate } from 'react-router';
+import { ProtectedRoute } from './components/ProtectedRoute';
+import { ErrorPage } from './components/ErrorPage';
+import { NotFoundPage } from './components/NotFoundPage';
+import HomePage from './pages/HomePage';
+import { MainLayout } from './layouts/MainLayout';
+import LoginPage from './pages/LoginPage';
+import RegisterPage from './pages/RegisterPage';
+import MemberPage from './pages/MemberPage';
+
+export const router = createBrowserRouter([
+  {
+    path: '/',
+    element: <HomePage />
+  },
+  {
+    path: '/login',
+    element: <LoginPage />
+  },
+  {
+    path: '/register',
+    element: <RegisterPage />
+  },
+  {
+    path: '/member',
+    element: (
+      <ProtectedRoute>
+        <MainLayout />
+      </ProtectedRoute>
+    ),
+    children: [
+      {
+        path: '',
+        element: <MemberPage />
+      },
+      {
+        path: '*',
+        element: <NotFoundPage />,
+        errorElement: <ErrorPage />
+      },
+    ],
+  },
+  {
+    path: '*',
+    element: <NotFoundPage />,
+    errorElement: <ErrorPage />
+  },
+]);