193 lines
8.2 KiB
TypeScript
193 lines
8.2 KiB
TypeScript
"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>
|
||
);
|
||
}
|