page.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. "use client";
  2. import { useState } from "react";
  3. import Head from "next/head";
  4. import Alert from "../ui/components/Alert";
  5. import { useRouter } from "next/navigation";
  6. import Link from "next/link";
  7. const SECURITY_QUESTIONS = [
  8. "您的出生地是?",
  9. "您的母亲的姓名是?",
  10. "您的最喜欢的颜色是?",
  11. "您的第一所学校名称是?",
  12. "您的宠物名字是?",
  13. "自定义问题",
  14. ];
  15. export default function ResetPasswordPage() {
  16. const router = useRouter();
  17. const [step, setStep] = useState(1);
  18. const [username, setUsername] = useState("");
  19. const [selectedQuestion, setSelectedQuestion] = useState(
  20. SECURITY_QUESTIONS[0]
  21. );
  22. const [customQuestion, setCustomQuestion] = useState("");
  23. const [securityAnswer, setSecurityAnswer] = useState("");
  24. const [newPassword, setNewPassword] = useState("");
  25. const [confirmPassword, setConfirmPassword] = useState("");
  26. const [alert, setAlert] = useState(null);
  27. const handleVerification = async (e) => {
  28. e.preventDefault();
  29. try {
  30. const response = await fetch("/api/auth/verify-security", {
  31. method: "POST",
  32. headers: {
  33. "Content-Type": "application/json",
  34. },
  35. body: JSON.stringify({
  36. username,
  37. securityQuestion:
  38. selectedQuestion === "自定义问题"
  39. ? customQuestion.trim()
  40. : selectedQuestion,
  41. securityAnswer: securityAnswer.trim(),
  42. }),
  43. });
  44. const data = await response.json();
  45. if (data.success) {
  46. setAlert({ type: "success", message: "验证成功" });
  47. setStep(2);
  48. } else {
  49. setAlert({
  50. type: "error",
  51. message: data.error || "验证失败,请重试",
  52. });
  53. }
  54. } catch (error) {
  55. setAlert({ type: "error", message: "发生错误,请稍后重试" });
  56. }
  57. };
  58. const handleResetPassword = async (e) => {
  59. e.preventDefault();
  60. if (newPassword.length < 6) {
  61. setAlert({ type: "error", message: "密码至少需要6个字符" });
  62. return;
  63. }
  64. if (newPassword !== confirmPassword) {
  65. setAlert({ type: "error", message: "两次输入的密码不一致" });
  66. return;
  67. }
  68. try {
  69. const response = await fetch("/api/auth/reset-password", {
  70. method: "POST",
  71. headers: {
  72. "Content-Type": "application/json",
  73. },
  74. body: JSON.stringify({
  75. username,
  76. newPassword,
  77. }),
  78. });
  79. const data = await response.json();
  80. if (data.success) {
  81. setAlert({ type: "success", message: "密码重置成功" });
  82. setTimeout(() => router.push("/login"), 1500);
  83. } else {
  84. setAlert({
  85. type: "error",
  86. message: data.message || "密码重置失败,请重试",
  87. });
  88. }
  89. } catch (error) {
  90. setAlert({ type: "error", message: "发生错误,请稍后重试" });
  91. }
  92. };
  93. return (
  94. <div className="min-h-screen bg-gray-100 flex flex-col justify-center px-4 sm:px-6 lg:px-8">
  95. <Head>
  96. <title>重置密码</title>
  97. </Head>
  98. {/* <div className="w-full max-w-md mx-auto">
  99. <h2 className="mt-6 text-center text-2xl sm:text-3xl font-extrabold text-gray-900">
  100. 重置密码
  101. </h2>
  102. </div> */}
  103. <div className="mt-8 w-full max-w-md mx-auto">
  104. <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
  105. {step === 1 ? (
  106. <form className="space-y-6" onSubmit={handleVerification}>
  107. <div>
  108. <label className="block text-sm font-medium text-gray-700">
  109. 用户名
  110. </label>
  111. <input
  112. type="text"
  113. required
  114. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  115. value={username}
  116. onChange={(e) => setUsername(e.target.value)}
  117. />
  118. </div>
  119. <div>
  120. <label className="block text-sm font-medium text-gray-700">
  121. 密保问题
  122. </label>
  123. <select
  124. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  125. value={selectedQuestion}
  126. onChange={(e) => setSelectedQuestion(e.target.value)}
  127. >
  128. {SECURITY_QUESTIONS.map((question) => (
  129. <option key={question} value={question}>
  130. {question}
  131. </option>
  132. ))}
  133. </select>
  134. </div>
  135. {selectedQuestion === "自定义问题" && (
  136. <div>
  137. <label className="block text-sm font-medium text-gray-700">
  138. 自定义问题内容
  139. </label>
  140. <input
  141. type="text"
  142. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  143. value={customQuestion}
  144. onChange={(e) => setCustomQuestion(e.target.value)}
  145. required
  146. />
  147. </div>
  148. )}
  149. <div>
  150. <label className="block text-sm font-medium text-gray-700">
  151. 密保答案
  152. </label>
  153. <input
  154. type="text"
  155. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  156. value={securityAnswer}
  157. onChange={(e) => setSecurityAnswer(e.target.value)}
  158. required
  159. />
  160. </div>
  161. <button
  162. type="submit"
  163. className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
  164. >
  165. 验证身份
  166. </button>
  167. </form>
  168. ) : (
  169. <form className="space-y-6" onSubmit={handleResetPassword}>
  170. <div>
  171. <label className="block text-sm font-medium text-gray-700">
  172. 新密码
  173. </label>
  174. <input
  175. type="password"
  176. required
  177. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  178. value={newPassword}
  179. onChange={(e) => setNewPassword(e.target.value)}
  180. />
  181. </div>
  182. <div>
  183. <label className="block text-sm font-medium text-gray-700">
  184. 确认新密码
  185. </label>
  186. <input
  187. type="password"
  188. required
  189. className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"
  190. value={confirmPassword}
  191. onChange={(e) => setConfirmPassword(e.target.value)}
  192. />
  193. </div>
  194. <button
  195. type="submit"
  196. className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
  197. >
  198. 重置密码
  199. </button>
  200. </form>
  201. )}
  202. <div className="mt-6">
  203. <div className="relative">
  204. <div className="absolute inset-0 flex items-center">
  205. <div className="w-full border-t border-gray-300" />
  206. </div>
  207. <div className="relative flex justify-center text-sm">
  208. <span className="px-2 bg-white text-gray-500">
  209. {step === 1 ? "记起密码了?" : "需要重新验证?"}
  210. </span>
  211. </div>
  212. </div>
  213. <div className="mt-6">
  214. {step === 1 ? (
  215. <Link
  216. href="/login"
  217. className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
  218. >
  219. 返回登录
  220. </Link>
  221. ) : (
  222. <button
  223. onClick={() => setStep(1)}
  224. className="w-full flex justify-center py-3 px-4 border border-transparent rounded-md shadow-sm text-base font-medium text-indigo-600 bg-white hover:bg-gray-50"
  225. >
  226. 返回验证
  227. </button>
  228. )}
  229. </div>
  230. </div>
  231. </div>
  232. </div>
  233. {alert && (
  234. <Alert
  235. message={alert.message}
  236. type={alert.type}
  237. onClose={() => setAlert(null)}
  238. />
  239. )}
  240. </div>
  241. );
  242. }