好的,让我们详细规划第 5 部分:用户认证 UI。我们将这部分分为 3 天,每天专注于特定的认证相关组件和功能。
第 5 部分:用户认证 UI(3 天)
Day 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>
);
};- 更新登录页面
在
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>
);
};- 添加受保护路由组件
在
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: 注册页面和表单
- 创建注册表单组件
在
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>
);
};- 更新注册页面
在
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: 用户认证状态管理和导航
- 更新 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;
};- 更新导航组件
在
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>
);
};- 更新 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 的主要部分,包括登录和注册表单、受保护路由、认证状态管理以及导航组件。这些组件和功能将为用户提供一个完整的认证体验,同时确保只有经过认证的用户才能访问受保护的路由。