好的,让我们详细规划第 5 部分:用户认证 UI。我们将这部分分为 3 天,每天专注于特定的认证相关组件和功能。

第 5 部分:用户认证 UI(3 天)

Day 1: 登录页面和表单

  1. 创建登录表单组件 在 src/components/LoginForm.tsx 中:
import React from 'react';
import { useForm } from '@mantine/form';
import { TextInput, PasswordInput, Button, Box, Text } from '@mantine/core';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { login } from '../services/api';
 
export const LoginForm: React.FC = () => {
  const navigate = useNavigate();
  const { login: authLogin } = useAuth();
 
  const form = useForm({
    initialValues: {
      email: '',
      password: '',
    },
    validate: {
      email: (value) => (/^\S+@\S+$/.test(value) ? null : 'Invalid email'),
      password: (value) => (value.length < 6 ? 'Password should be at least 6 characters' : null),
    },
  });
 
  const handleSubmit = async (values: { email: string; password: string }) => {
    try {
      const response = await login(values.email, values.password);
      authLogin(response.data.user);
      navigate('/');
    } catch (error) {
      console.error('Login failed', error);
      form.setErrors({ email: 'Invalid email or password' });
    }
  };
 
  return (
    <Box sx={{ maxWidth: 300 }} mx="auto">
      <form onSubmit={form.onSubmit(handleSubmit)}>
        <TextInput
          required
          label="Email"
          placeholder="your@email.com"
          {...form.getInputProps('email')}
        />
        <PasswordInput
          required
          label="Password"
          placeholder="Password"
          mt="md"
          {...form.getInputProps('password')}
        />
        <Button type="submit" fullWidth mt="xl">
          Log in
        </Button>
      </form>
      <Text align="center" mt="md">
        Don't have an account? <Text component="a" href="/register">Register</Text>
      </Text>
    </Box>
  );
};
  1. 更新登录页面 在 src/pages/Login.tsx 中:
import React from 'react';
import { Container, Title } from '@mantine/core';
import { LoginForm } from '../components/LoginForm';
 
export const Login: React.FC = () => {
  return (
    <Container size="xs" padding="xl">
      <Title order={2} align="center" mb="xl">Log in to your account</Title>
      <LoginForm />
    </Container>
  );
};
  1. 添加受保护路由组件 在 src/components/ProtectedRoute.tsx 中:
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
 
export const ProtectedRoute: React.FC = () => {
  const { user } = useAuth();
 
  if (!user) {
    return <Navigate to="/login" replace />;
  }
 
  return <Outlet />;
};

Day 2: 注册页面和表单

  1. 创建注册表单组件 在 src/components/RegisterForm.tsx 中:
import React from 'react';
import { useForm } from '@mantine/form';
import { TextInput, PasswordInput, Button, Box, Text } from '@mantine/core';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
import { register } from '../services/api';
 
export const RegisterForm: React.FC = () => {
  const navigate = useNavigate();
  const { login: authLogin } = useAuth();
 
  const form = useForm({
    initialValues: {
      username: '',
      email: '',
      password: '',
      confirmPassword: '',
    },
    validate: {
      username: (value) => (value.length < 2 ? 'Username should have at least 2 characters' : null),
      Email: (value) => (/^\S+@\S+$/. Test (value) ? Null : 'Invalid email'),
      Password: (value) => (value. Length < 6 ? 'Password should be at least 6 characters' : null),
      confirmPassword: (value, values) => (value !== values.password ? 'Passwords do not match' : null),
    },
  });
 
  const handleSubmit = async (values: { username: string; email: string; password: string }) => {
    try {
      const response = await register(values.username, values.email, values.password);
      authLogin(response.data.user);
      navigate('/');
    } catch (error) {
      console.error('Registration failed', error);
      form.setErrors({ email: 'Email already in use' });
    }
  };
 
  return (
    <Box sx={{ maxWidth: 300 }} mx="auto">
      <form onSubmit={form.onSubmit(handleSubmit)}>
        <TextInput
          required
          label="Username"
          placeholder="Your username"
          {...form.getInputProps('username')}
        />
        <TextInput
          required
          label="Email"
          placeholder="your@email.com"
          mt="md"
          {...form.getInputProps('email')}
        />
        <PasswordInput
          required
          label="Password"
          placeholder="Password"
          mt="md"
          {...form.getInputProps('password')}
        />
        <PasswordInput
          required
          label="Confirm Password"
          placeholder="Confirm your password"
          mt="md"
          {...form.getInputProps('confirmPassword')}
        />
        <Button type="submit" fullWidth mt="xl">
          Register
        </Button>
      </form>
      <Text align="center" mt="md">
        Already have an account? <Text component="a" href="/login">Log in</Text>
      </Text>
    </Box>
  );
};
  1. 更新注册页面 在 src/pages/Register.tsx 中:
import React from 'react';
import { Container, Title } from '@mantine/core';
import { RegisterForm } from '../components/RegisterForm';
 
export const Register: React.FC = () => {
  return (
    <Container size="xs" padding="xl">
      <Title order={2} align="center" mb="xl">Create a new account</Title>
      <RegisterForm />
    </Container>
  );
};

Day 3: 用户认证状态管理和导航

  1. 更新 AuthContext 在 src/context/AuthContext.tsx 中添加加载状态:
import React, { createContext, useState, useContext, useEffect } from 'react';
 
interface User {
  id: string;
  username: string;
  email: string;
}
 
interface AuthContextType {
  user: User | null;
  loading: boolean;
  login: (userData: User) => void;
  logout: () => void;
}
 
const AuthContext = createContext<AuthContextType | undefined>(undefined);
 
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    Const storedUser = localStorage.GetItem ('user');
    If (storedUser) {
      setUser(JSON.parse(storedUser));
    }
    setLoading(false);
  }, []);
 
  const login = (userData: User) => {
    setUser(userData);
    localStorage.setItem('user', JSON.stringify(userData));
  };
 
  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };
 
  return (
    <AuthContext.Provider value={{ user, loading, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
};
 
export const useAuth = () => {
  const context = useContext(AuthContext);
  if (context === undefined) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};
  1. 更新导航组件 在 src/components/Navigation.tsx 中:
import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Button, Group, Text } from '@mantine/core';
import { useAuth } from '../context/AuthContext';
import { logout as apiLogout } from '../services/api';
 
export const Navigation: React.FC = () => {
  const { user, logout } = useAuth();
  const navigate = useNavigate();
 
  const handleLogout = async () => {
    try {
      await apiLogout();
      logout();
      navigate('/login');
    } catch (error) {
      console.error('Logout failed', error);
    }
  };
 
  return (
    <Group position="apart">
      <Text>LightweightTodo Pro</Text>
      <Group>
        {user ? (
          <>
            <Text>Welcome, {user.username}</Text>
            <Button component={Link} to="/">Home</Button>
            <Button onClick={handleLogout}>Logout</Button>
          </>
        ) : (
          <>
            <Button component={Link} to="/login">Login</Button>
            <Button component={Link} to="/register">Register</Button>
          </>
        )}
      </Group>
    </Group>
  );
};
  1. 更新 App 组件以使用 AuthProvider 和 ProtectedRoute 在 src/App.tsx 中:
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import { MantineProvider } from '@mantine/core';
import { AuthProvider } from './context/AuthContext';
import { Layout } from './components/Layout';
import { ProtectedRoute } from './components/ProtectedRoute';
import { Home } from './pages/Home';
import { Login } from './pages/Login';
import { Register } from './pages/Register';
 
const App: React.FC = () => {
  return (
    <MantineProvider withGlobalStyles withNormalizeCSS>
      <AuthProvider>
        <Router>
          <Routes>
            <Route path="/" element={<Layout />}>
              <Route element={<ProtectedRoute />}>
                <Route index element={<Home />} />
              </Route>
              <Route path="login" element={<Login />} />
              <Route path="register" element={<Register />} />
            </Route>
          </Routes>
        </Router>
      </AuthProvider>
    </MantineProvider>
  );
};
 
Export default App;

这个三天的计划涵盖了用户认证 UI 的主要部分,包括登录和注册表单、受保护路由、认证状态管理以及导航组件。这些组件和功能将为用户提供一个完整的认证体验,同时确保只有经过认证的用户才能访问受保护的路由。