|
@@ -0,0 +1,266 @@
|
|
|
+"use client";
|
|
|
+import { useState } from "react";
|
|
|
+import Head from "next/head";
|
|
|
+import Alert from "../ui/components/Alert";
|
|
|
+import { useRouter } from "next/navigation";
|
|
|
+import Link from "next/link";
|
|
|
+
|
|
|
+const SECURITY_QUESTIONS = [
|
|
|
+ "您的出生地是?",
|
|
|
+ "您的母亲的姓名是?",
|
|
|
+ "您的最喜欢的颜色是?",
|
|
|
+ "您的第一所学校名称是?",
|
|
|
+ "您的宠物名字是?",
|
|
|
+ "自定义问题",
|
|
|
+];
|
|
|
+
|
|
|
+export default function ResetPasswordPage() {
|
|
|
+ const router = useRouter();
|
|
|
+
|
|
|
+ const [step, setStep] = useState(1);
|
|
|
+ const [username, setUsername] = useState("");
|
|
|
+ const [selectedQuestion, setSelectedQuestion] = useState(
|
|
|
+ SECURITY_QUESTIONS[0]
|
|
|
+ );
|
|
|
+ const [customQuestion, setCustomQuestion] = useState("");
|
|
|
+ const [securityAnswer, setSecurityAnswer] = useState("");
|
|
|
+ const [newPassword, setNewPassword] = useState("");
|
|
|
+ const [confirmPassword, setConfirmPassword] = useState("");
|
|
|
+ const [alert, setAlert] = useState(null);
|
|
|
+
|
|
|
+ const handleVerification = async (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch("/api/auth/verify-security", {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ username,
|
|
|
+ securityQuestion:
|
|
|
+ selectedQuestion === "自定义问题"
|
|
|
+ ? customQuestion.trim()
|
|
|
+ : selectedQuestion,
|
|
|
+ securityAnswer: securityAnswer.trim(),
|
|
|
+ }),
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (data.success) {
|
|
|
+ setAlert({ type: "success", message: "验证成功" });
|
|
|
+ setStep(2);
|
|
|
+ } else {
|
|
|
+ setAlert({
|
|
|
+ type: "error",
|
|
|
+ message: data.message || "验证失败,请重试",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ setAlert({ type: "error", message: "发生错误,请稍后重试" });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleResetPassword = async (e) => {
|
|
|
+ e.preventDefault();
|
|
|
+
|
|
|
+ if (newPassword.length < 6) {
|
|
|
+ setAlert({ type: "error", message: "密码至少需要6个字符" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (newPassword !== confirmPassword) {
|
|
|
+ setAlert({ type: "error", message: "两次输入的密码不一致" });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const response = await fetch("/api/auth/reset-password", {
|
|
|
+ method: "POST",
|
|
|
+ headers: {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ body: JSON.stringify({
|
|
|
+ username,
|
|
|
+ newPassword,
|
|
|
+ }),
|
|
|
+ });
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+
|
|
|
+ if (data.success) {
|
|
|
+ setAlert({ type: "success", message: "密码重置成功" });
|
|
|
+ setTimeout(() => router.push("/login"), 1500);
|
|
|
+ } else {
|
|
|
+ setAlert({
|
|
|
+ type: "error",
|
|
|
+ message: data.message || "密码重置失败,请重试",
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ setAlert({ type: "error", message: "发生错误,请稍后重试" });
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="min-h-screen bg-gray-100 flex flex-col justify-center px-4 sm:px-6 lg:px-8">
|
|
|
+ <Head>
|
|
|
+ <title>重置密码</title>
|
|
|
+ </Head>
|
|
|
+
|
|
|
+ <div className="w-full max-w-md mx-auto">
|
|
|
+ <h2 className="mt-6 text-center text-2xl sm:text-3xl font-extrabold text-gray-900">
|
|
|
+ 重置密码
|
|
|
+ </h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="mt-8 w-full max-w-md mx-auto">
|
|
|
+ <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
|
|
+ {step === 1 ? (
|
|
|
+ <form className="space-y-6" onSubmit={handleVerification}>
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 用户名
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ required
|
|
|
+ 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"
|
|
|
+ value={username}
|
|
|
+ onChange={(e) => setUsername(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 密保问题
|
|
|
+ </label>
|
|
|
+ <select
|
|
|
+ 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"
|
|
|
+ value={selectedQuestion}
|
|
|
+ onChange={(e) => setSelectedQuestion(e.target.value)}
|
|
|
+ >
|
|
|
+ {SECURITY_QUESTIONS.map((question) => (
|
|
|
+ <option key={question} value={question}>
|
|
|
+ {question}
|
|
|
+ </option>
|
|
|
+ ))}
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {selectedQuestion === "自定义问题" && (
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 自定义问题内容
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ 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"
|
|
|
+ value={customQuestion}
|
|
|
+ onChange={(e) => setCustomQuestion(e.target.value)}
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 密保答案
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ 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"
|
|
|
+ value={securityAnswer}
|
|
|
+ onChange={(e) => setSecurityAnswer(e.target.value)}
|
|
|
+ required
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ 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"
|
|
|
+ >
|
|
|
+ 验证身份
|
|
|
+ </button>
|
|
|
+ </form>
|
|
|
+ ) : (
|
|
|
+ <form className="space-y-6" onSubmit={handleResetPassword}>
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 新密码
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="password"
|
|
|
+ required
|
|
|
+ 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"
|
|
|
+ value={newPassword}
|
|
|
+ onChange={(e) => setNewPassword(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div>
|
|
|
+ <label className="block text-sm font-medium text-gray-700">
|
|
|
+ 确认新密码
|
|
|
+ </label>
|
|
|
+ <input
|
|
|
+ type="password"
|
|
|
+ required
|
|
|
+ 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"
|
|
|
+ value={confirmPassword}
|
|
|
+ onChange={(e) => setConfirmPassword(e.target.value)}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <button
|
|
|
+ type="submit"
|
|
|
+ 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"
|
|
|
+ >
|
|
|
+ 重置密码
|
|
|
+ </button>
|
|
|
+ </form>
|
|
|
+ )}
|
|
|
+
|
|
|
+ <div className="mt-6">
|
|
|
+ <div className="relative">
|
|
|
+ <div className="absolute inset-0 flex items-center">
|
|
|
+ <div className="w-full border-t border-gray-300" />
|
|
|
+ </div>
|
|
|
+ <div className="relative flex justify-center text-sm">
|
|
|
+ <span className="px-2 bg-white text-gray-500">
|
|
|
+ {step === 1 ? "记起密码了?" : "需要重新验证?"}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div className="mt-6">
|
|
|
+ {step === 1 ? (
|
|
|
+ <Link
|
|
|
+ href="/login"
|
|
|
+ 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"
|
|
|
+ >
|
|
|
+ 返回登录
|
|
|
+ </Link>
|
|
|
+ ) : (
|
|
|
+ <button
|
|
|
+ onClick={() => setStep(1)}
|
|
|
+ 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"
|
|
|
+ >
|
|
|
+ 返回验证
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {alert && (
|
|
|
+ <Alert
|
|
|
+ message={alert.message}
|
|
|
+ type={alert.type}
|
|
|
+ onClose={() => setAlert(null)}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|