forgot-password-form-panel.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. "use client";
  2. import { useTranslations } from "next-intl";
  3. import { Link } from "@/i18n/navigation";
  4. import { useState } from "react";
  5. import { ApiError } from "@/lib/api";
  6. import { sendForgotPasswordEmail } from "@/lib/auth-api";
  7. import { KeyRound, CheckCircle2 } from "lucide-react";
  8. import {
  9. AuthCard,
  10. AuthCloseButton,
  11. AuthHeader,
  12. AuthIconBadge,
  13. AuthPageScene,
  14. authErrorCls,
  15. authFooterLinkCls,
  16. authInputCls,
  17. authLabelCls,
  18. authPrimaryBtnCls,
  19. } from "@/components/auth/auth-ui";
  20. type Props = {
  21. layout: "fullscreen" | "embedded";
  22. onDismiss?: () => void;
  23. onBackToLogin?: () => void;
  24. };
  25. export function ForgotPasswordFormPanel({ layout, onDismiss, onBackToLogin }: Props) {
  26. const t = useTranslations("auth");
  27. const [email, setEmail] = useState("");
  28. const [done, setDone] = useState(false);
  29. const [submitting, setSubmitting] = useState(false);
  30. const [err, setErr] = useState<string | null>(null);
  31. const embedded = layout === "embedded";
  32. async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
  33. e.preventDefault();
  34. if (submitting) return;
  35. setErr(null);
  36. try {
  37. setSubmitting(true);
  38. await sendForgotPasswordEmail(email.trim());
  39. setDone(true);
  40. } catch (error) {
  41. if (error instanceof ApiError && error.message.trim()) {
  42. setErr(error.message);
  43. } else {
  44. setErr(t("errorApi"));
  45. }
  46. } finally {
  47. setSubmitting(false);
  48. }
  49. }
  50. return (
  51. <AuthPageScene layout={layout}>
  52. <AuthCard layout={layout}>
  53. {embedded && onDismiss ? <AuthCloseButton onClick={onDismiss} /> : null}
  54. <AuthIconBadge icon={KeyRound} layout={layout} />
  55. <AuthHeader title={t("forgotTitle")} subtitle={t("forgotHint")} layout={layout} />
  56. {!done ? (
  57. <form onSubmit={onSubmit} className="space-y-5">
  58. <div>
  59. <label className={authLabelCls}>{t("email")}</label>
  60. <input
  61. type="email"
  62. required
  63. value={email}
  64. onChange={(e) => setEmail(e.target.value)}
  65. className={authInputCls}
  66. placeholder="admin@example.com"
  67. />
  68. </div>
  69. {err ? (
  70. <div className={authErrorCls} role="alert">
  71. {err}
  72. </div>
  73. ) : null}
  74. <button type="submit" disabled={submitting} className={authPrimaryBtnCls}>
  75. {submitting ? "发送中..." : t("resetBtn")}
  76. </button>
  77. </form>
  78. ) : (
  79. <div className="rounded-2xl border border-emerald-400/30 bg-emerald-500/10 p-6 text-center shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]">
  80. <CheckCircle2 size={40} className="mx-auto mb-4 text-emerald-400" />
  81. <p className="text-base font-bold text-emerald-300">邮件已发送</p>
  82. <p className="mt-2 text-sm text-emerald-400/80">请前往您的邮箱查收密码重置链接。</p>
  83. </div>
  84. )}
  85. <p className="mt-8 text-center text-sm text-slate-400/90">
  86. 想起密码了?{" "}
  87. {embedded && onBackToLogin ? (
  88. <button type="button" onClick={onBackToLogin} className={authFooterLinkCls}>
  89. {t("toLogin")}
  90. </button>
  91. ) : (
  92. <Link href="/auth/login" className={authFooterLinkCls}>
  93. {t("toLogin")}
  94. </Link>
  95. )}
  96. </p>
  97. </AuthCard>
  98. </AuthPageScene>
  99. );
  100. }