RegisterPage.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. import React, { useState } from 'react';
  2. import { useForm } from 'react-hook-form';
  3. import { EyeIcon, EyeSlashIcon, UserIcon, LockClosedIcon } from '@heroicons/react/24/outline';
  4. import { useNavigate } from 'react-router-dom';
  5. import { useAuth } from '@/client/home/hooks/AuthProvider';
  6. import { authClient } from '@/client/api';
  7. const RegisterPage: React.FC = () => {
  8. const { register, handleSubmit, watch, formState: { errors } } = useForm();
  9. const [showPassword, setShowPassword] = useState(false);
  10. const [showConfirmPassword, setShowConfirmPassword] = useState(false);
  11. const [loading, setLoading] = useState(false);
  12. const { login } = useAuth();
  13. const navigate = useNavigate();
  14. const password = watch('password', '');
  15. const onSubmit = async (data: any) => {
  16. try {
  17. setLoading(true);
  18. // 调用注册API
  19. const response = await authClient.register.$post({
  20. json: {
  21. username: data.username,
  22. password: data.password,
  23. }
  24. });
  25. if (response.status !== 201) {
  26. const result = await response.json();
  27. throw new Error(result.message || '注册失败');
  28. }
  29. // 注册成功后自动登录
  30. await login(data.username, data.password);
  31. // 跳转到首页
  32. navigate('/');
  33. } catch (error) {
  34. console.error('Registration error:', error);
  35. alert((error as Error).message || '注册失败,请稍后重试');
  36. } finally {
  37. setLoading(false);
  38. }
  39. };
  40. return (
  41. <div className="flex justify-center items-center min-h-screen bg-gray-100">
  42. <div className="w-full max-w-md bg-white rounded-lg shadow-md overflow-hidden">
  43. <div className="p-6 sm:p-8">
  44. <div className="text-center mb-8">
  45. <h2 className="text-2xl font-bold text-gray-900">账号注册</h2>
  46. <p className="mt-2 text-sm text-gray-600">创建新账号以开始使用</p>
  47. </div>
  48. <form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
  49. <div>
  50. <label htmlFor="username" className="block text-sm font-medium text-gray-700 mb-1">
  51. 用户名
  52. </label>
  53. <div className="relative">
  54. <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
  55. <UserIcon className="h-5 w-5 text-gray-400" />
  56. </div>
  57. <input
  58. id="username"
  59. type="text"
  60. className={`w-full pl-10 pr-3 py-2 border ${errors.username ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
  61. placeholder="请输入用户名"
  62. {...register('username', {
  63. required: '用户名不能为空',
  64. minLength: { value: 3, message: '用户名至少3个字符' },
  65. maxLength: { value: 20, message: '用户名不能超过20个字符' }
  66. })}
  67. />
  68. </div>
  69. {errors.username && (
  70. <p className="mt-1 text-sm text-red-600">{errors.username.message?.toString()}</p>
  71. )}
  72. </div>
  73. <div>
  74. <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1">
  75. 密码
  76. </label>
  77. <div className="relative">
  78. <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
  79. <LockClosedIcon className="h-5 w-5 text-gray-400" />
  80. </div>
  81. <input
  82. id="password"
  83. type={showPassword ? 'text' : 'password'}
  84. className={`w-full pl-10 pr-10 py-2 border ${errors.password ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
  85. placeholder="请输入密码"
  86. {...register('password', {
  87. required: '密码不能为空',
  88. minLength: { value: 6, message: '密码至少6个字符' },
  89. maxLength: { value: 30, message: '密码不能超过30个字符' }
  90. })}
  91. />
  92. <button
  93. type="button"
  94. className="absolute inset-y-0 right-0 pr-3 flex items-center"
  95. onClick={() => setShowPassword(!showPassword)}
  96. >
  97. {showPassword ? (
  98. <EyeSlashIcon className="h-5 w-5 text-gray-400" />
  99. ) : (
  100. <EyeIcon className="h-5 w-5 text-gray-400" />
  101. )}
  102. </button>
  103. </div>
  104. {errors.password && (
  105. <p className="mt-1 text-sm text-red-600">{errors.password.message?.toString()}</p>
  106. )}
  107. </div>
  108. <div>
  109. <label htmlFor="confirmPassword" className="block text-sm font-medium text-gray-700 mb-1">
  110. 确认密码
  111. </label>
  112. <div className="relative">
  113. <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
  114. <LockClosedIcon className="h-5 w-5 text-gray-400" />
  115. </div>
  116. <input
  117. id="confirmPassword"
  118. type={showConfirmPassword ? 'text' : 'password'}
  119. className={`w-full pl-10 pr-10 py-2 border ${errors.confirmPassword ? 'border-red-300' : 'border-gray-300'} rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent`}
  120. placeholder="请再次输入密码"
  121. {...register('confirmPassword', {
  122. required: '请确认密码',
  123. validate: value => value === password || '两次密码输入不一致'
  124. })}
  125. />
  126. <button
  127. type="button"
  128. className="absolute inset-y-0 right-0 pr-3 flex items-center"
  129. onClick={() => setShowConfirmPassword(!showConfirmPassword)}
  130. >
  131. {showConfirmPassword ? (
  132. <EyeSlashIcon className="h-5 w-5 text-gray-400" />
  133. ) : (
  134. <EyeIcon className="h-5 w-5 text-gray-400" />
  135. )}
  136. </button>
  137. </div>
  138. {errors.confirmPassword && (
  139. <p className="mt-1 text-sm text-red-600">{errors.confirmPassword.message?.toString()}</p>
  140. )}
  141. </div>
  142. <div>
  143. <button
  144. type="submit"
  145. disabled={loading}
  146. className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
  147. >
  148. {loading ? '注册中...' : '注册'}
  149. </button>
  150. </div>
  151. </form>
  152. <div className="mt-6">
  153. <div className="relative">
  154. <div className="absolute inset-0 flex items-center">
  155. <div className="w-full border-t border-gray-300"></div>
  156. </div>
  157. <div className="relative flex justify-center text-sm">
  158. <span className="px-2 bg-white text-gray-500">已有账号?</span>
  159. </div>
  160. </div>
  161. <div className="mt-4">
  162. <button
  163. type="button"
  164. onClick={() => navigate('/login')}
  165. className="w-full flex justify-center py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
  166. >
  167. 返回登录
  168. </button>
  169. </div>
  170. </div>
  171. </div>
  172. </div>
  173. </div>
  174. );
  175. };
  176. export default RegisterPage;