RegisterPage.tsx 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import React from 'react';
  2. import { useForm } from 'react-hook-form';
  3. import { zodResolver } from '@hookform/resolvers/zod';
  4. import { z } from 'zod';
  5. import { Link, useNavigate } from 'react-router-dom';
  6. import { useAuth } from '@/client/home/hooks/AuthProvider';
  7. import { toast } from 'sonner';
  8. import { authClient } from '@/client/api';
  9. import { Button } from '@/client/components/ui/button';
  10. import { Input } from '@/client/components/ui/input';
  11. import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/client/components/ui/card';
  12. import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
  13. import { Eye, EyeOff, User, Lock } from 'lucide-react';
  14. const registerSchema = z.object({
  15. username: z.string()
  16. .min(3, '用户名至少3个字符')
  17. .max(20, '用户名不能超过20个字符')
  18. .regex(/^[a-zA-Z0-9_-]+$/, '用户名只能包含字母、数字、下划线和连字符'),
  19. password: z.string()
  20. .min(6, '密码至少6个字符')
  21. .max(30, '密码不能超过30个字符'),
  22. confirmPassword: z.string(),
  23. }).refine((data) => data.password === data.confirmPassword, {
  24. message: '两次密码输入不一致',
  25. path: ['confirmPassword'],
  26. });
  27. type RegisterFormData = z.infer<typeof registerSchema>;
  28. const RegisterPage: React.FC = () => {
  29. const { login } = useAuth();
  30. const navigate = useNavigate();
  31. const form = useForm<RegisterFormData>({
  32. resolver: zodResolver(registerSchema),
  33. defaultValues: {
  34. username: '',
  35. password: '',
  36. confirmPassword: '',
  37. },
  38. });
  39. const onSubmit = async (data: RegisterFormData) => {
  40. try {
  41. const response = await authClient.register.$post({
  42. json: {
  43. username: data.username,
  44. password: data.password,
  45. }
  46. });
  47. if (response.status !== 201) {
  48. const result = await response.json();
  49. throw new Error(result.message || '注册失败');
  50. }
  51. // 注册成功后自动登录
  52. await login(data.username, data.password);
  53. toast.success('注册成功!正在为您登录...');
  54. navigate('/');
  55. } catch (error) {
  56. toast.error(error instanceof Error ? error.message : '注册失败,请稍后重试');
  57. }
  58. };
  59. return (
  60. <div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 px-4 py-12">
  61. <Card className="w-full max-w-md border-0 shadow-xl">
  62. <CardHeader className="space-y-1">
  63. <CardTitle className="text-2xl font-bold text-center">创建账号</CardTitle>
  64. <CardDescription className="text-center">
  65. 填写以下信息创建新账号
  66. </CardDescription>
  67. </CardHeader>
  68. <CardContent>
  69. <Form {...form}>
  70. <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
  71. <FormField
  72. control={form.control}
  73. name="username"
  74. render={({ field }) => (
  75. <FormItem>
  76. <FormLabel>用户名</FormLabel>
  77. <FormControl>
  78. <div className="relative">
  79. <User className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
  80. <Input
  81. placeholder="请输入用户名"
  82. className="pl-10"
  83. {...field}
  84. />
  85. </div>
  86. </FormControl>
  87. <FormMessage />
  88. </FormItem>
  89. )}
  90. />
  91. <FormField
  92. control={form.control}
  93. name="password"
  94. render={({ field }) => (
  95. <FormItem>
  96. <FormLabel>密码</FormLabel>
  97. <FormControl>
  98. <div className="relative">
  99. <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
  100. <Input
  101. type="password"
  102. placeholder="请输入密码"
  103. className="pl-10"
  104. {...field}
  105. />
  106. </div>
  107. </FormControl>
  108. <FormMessage />
  109. </FormItem>
  110. )}
  111. />
  112. <FormField
  113. control={form.control}
  114. name="confirmPassword"
  115. render={({ field }) => (
  116. <FormItem>
  117. <FormLabel>确认密码</FormLabel>
  118. <FormControl>
  119. <div className="relative">
  120. <Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
  121. <Input
  122. type="password"
  123. placeholder="请再次输入密码"
  124. className="pl-10"
  125. {...field}
  126. />
  127. </div>
  128. </FormControl>
  129. <FormMessage />
  130. </FormItem>
  131. )}
  132. />
  133. <Button
  134. type="submit"
  135. className="w-full"
  136. disabled={form.formState.isSubmitting}
  137. >
  138. {form.formState.isSubmitting ? '注册中...' : '注册账号'}
  139. </Button>
  140. </form>
  141. </Form>
  142. </CardContent>
  143. <CardFooter className="flex flex-col space-y-4">
  144. <div className="text-sm text-center">
  145. <span className="text-muted-foreground">已有账号?</span>
  146. <Button
  147. variant="link"
  148. className="px-1"
  149. asChild
  150. >
  151. <Link to="/login">立即登录</Link>
  152. </Button>
  153. </div>
  154. <div className="text-xs text-center text-muted-foreground">
  155. <p>注册即表示您同意我们的服务条款</p>
  156. </div>
  157. </CardFooter>
  158. </Card>
  159. </div>
  160. );
  161. };
  162. export default RegisterPage;