gh_UserManager/apps/web/app/reset-password/page.tsx
bermooda-company 54d5891edf user
2026-08-23 23:59:14 +03:30

193 lines
8.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useState, useEffect } from "react";
import { Eye, EyeOff, Lock, Fingerprint, ArrowLeft, ArrowRight, CheckCircle } from "lucide-react";
import { useLanguage } from "@/components/language-provider";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
export default function ResetPasswordPage() {
const { lang, dir } = useLanguage();
const [mounted, setMounted] = useState(false);
const [token, setToken] = useState("");
useEffect(() => {
setMounted(true);
const params = new URLSearchParams(window.location.search);
setToken(params.get("token") || "");
}, []);
const Arrow = lang === "fa" ? ArrowRight : ArrowLeft;
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [step, setStep] = useState<"set" | "success">("set");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (password !== confirmPassword) {
setError(lang === "fa" ? "رمزها مطابقت ندارند" : "Passwords do not match");
return;
}
if (password.length < 10) {
setError(lang === "fa" ? "رمز باید حداقل ۱۰ کاراکتر باشد" : "Password must be at least 10 characters");
return;
}
if (!token) {
setError(lang === "fa" ? "توکن نامعتبر است" : "Invalid token");
return;
}
setLoading(true);
try {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/password/reset/confirm/`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token, new_password: password }),
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.detail || data.error || "Reset failed");
}
setStep("success");
} catch (err) {
setError(err instanceof Error ? err.message : "Reset failed");
} finally {
setLoading(false);
}
};
return (
<div className="min-h-screen flex items-center justify-center bg-ink px-4 py-12">
<div className="w-full max-w-md">
<div className="text-center mb-8">
<a href="#top" className="inline-flex items-center gap-2.5 font-semibold text-white mb-6">
<span className="flex h-10 w-10 items-center justify-center rounded-xl bg-brand-gradient shadow-lg shadow-brand-900/40">
<Fingerprint className="h-6 w-6 text-white" />
</span>
<span className="text-xl tracking-tight">{process.env.NEXT_PUBLIC_SITE_NAME || "MyAccount Hamsoo"}</span>
</a>
{step === "set" ? (
<>
<h1 className="text-2xl font-bold text-white">{lang === "fa" ? "تنظیم رمز عبور جدید" : "Set New Password"}</h1>
<p className="mt-2 text-slate-400">
{lang === "fa" ? "رمز عبور جدید خود را وارد کنید" : "Enter your new password"}
</p>
</>
) : (
<>
<div className="mb-4 flex justify-center">
<CheckCircle className="h-16 w-16 text-emerald-400" />
</div>
<h1 className="text-2xl font-bold text-white">{lang === "fa" ? "رمز با موفقیت تغییر کرد" : "Password Changed"}</h1>
<p className="mt-2 text-slate-400">
{lang === "fa" ? "اکنون می‌توانید با رمز جدید وارد شوید" : "You can now sign in with your new password"}
</p>
</>
)}
</div>
<div className="card">
{error && (
<div className="mb-4 rounded-lg bg-rose-500/15 border border-rose-500/30 p-3 text-sm text-rose-300" role="alert">
{error}
</div>
)}
{step === "set" && (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="password" className="block text-sm font-medium text-slate-300 mb-1.5">
{lang === "fa" ? "رمز عبور جدید" : "New Password"}
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-500 rtl:hidden" />
<Lock className="absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-500 hidden rtl:block" />
<input
id="password"
type={showPassword ? "text" : "password"}
autoComplete="new-password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={10}
className="w-full pl-10 pr-12 py-2.5 rounded-lg border border-white/10 bg-white/5 text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-brand-500/50 focus:border-transparent"
placeholder={lang === "fa" ? "حداقل ۱۰ کاراکتر" : "At least 10 characters"}
dir="ltr"
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute top-1/2 -translate-y-1/2 right-3 rtl:left-3 text-slate-500 hover:text-white"
aria-label={showPassword ? (lang === "fa" ? "مخفی کردن رمز" : "Hide password") : (lang === "fa" ? "نمایش رمز" : "Show password")}
>
{showPassword ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium text-slate-300 mb-1.5">
{lang === "fa" ? "تأیید رمز عبور" : "Confirm Password"}
</label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-500 rtl:hidden" />
<Lock className="absolute right-3 top-1/2 -translate-y-1/2 h-5 w-5 text-slate-500 hidden rtl:block" />
<input
id="confirmPassword"
type={showPassword ? "text" : "password"}
autoComplete="new-password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
minLength={10}
className="w-full pl-10 pr-4 py-2.5 rounded-lg border border-white/10 bg-white/5 text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-brand-500/50 focus:border-transparent"
placeholder={lang === "fa" ? "رمز را مجدداً وارد کنید" : "Re-enter password"}
dir="ltr"
/>
</div>
</div>
<Button type="submit" className="w-full" size="lg" disabled={loading}>
{loading
? lang === "fa"
? "در حال ذخیره..."
: "Saving..."
: lang === "fa"
? "ذخیره رمز جدید"
: "Save New Password"}
<Arrow className="h-5 w-5 rtl:rotate-180" />
</Button>
</form>
)}
{step === "success" && (
<div className="text-center space-y-4">
<Button className="w-full" onClick={() => (window.location.href = "/login")}>
<Arrow className="h-5 w-5 rtl:rotate-180" />
{lang === "fa" ? "ورود با رمز جدید" : "Sign In with New Password"}
</Button>
</div>
)}
</div>
<div className="mt-6 text-center text-xs text-slate-500">
<Badge className="mb-2">
<Fingerprint className="h-3 w-3" />
{lang === "fa" ? "مرکز حساب همسو" : "MyAccount Hamsoo"}
</Badge>
</div>
</div>
</div>
);
}