Files
ember-market-frontend/app/auth/reset-password/page.tsx
NotII 8517f4c153 Add reset password page and update VendorsCard
Introduces a new password reset page with token validation and form handling. Removes the optional email field from the Vendor interface and its display in the VendorsCard component.
2025-10-16 00:34:50 +01:00

158 lines
4.6 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { fetchClient } from "@/lib/api-client";
import { toast } from "sonner";
interface Vendor {
id: string;
username: string;
}
export default function ResetPasswordPage() {
const router = useRouter();
const searchParams = useSearchParams();
const token = searchParams.get('token');
const [vendor, setVendor] = useState<Vendor | null>(null);
const [loading, setLoading] = useState(true);
const [submitting, setSubmitting] = useState(false);
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
useEffect(() => {
if (!token) {
toast.error("No reset token provided");
router.push("/auth/login");
return;
}
validateToken();
}, [token]);
async function validateToken() {
try {
const data = await fetchClient<{ valid: boolean; vendor?: Vendor; message?: string }>(`/auth/validate-reset-token/${token}`);
if (data.valid && data.vendor) {
setVendor(data.vendor);
} else {
toast.error(data.message || "Invalid or expired token");
router.push("/auth/login");
}
} catch (error: any) {
toast.error("Failed to validate token");
router.push("/auth/login");
} finally {
setLoading(false);
}
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (password !== confirmPassword) {
toast.error("Passwords do not match");
return;
}
if (password.length < 6) {
toast.error("Password must be at least 6 characters");
return;
}
setSubmitting(true);
try {
await fetchClient("/auth/reset-password", {
method: "POST",
body: { token, newPassword: password }
});
toast.success("Password reset successfully");
router.push("/auth/login");
} catch (error: any) {
toast.error(error?.message || "Failed to reset password");
} finally {
setSubmitting(false);
}
}
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
<p className="mt-2 text-sm text-muted-foreground">Validating token...</p>
</div>
</div>
);
}
if (!vendor) {
return null; // Will redirect
}
return (
<div className="min-h-screen flex items-center justify-center bg-background">
<div className="max-w-md w-full space-y-8 p-8">
<div className="text-center">
<h1 className="text-2xl font-bold">Reset Password</h1>
<p className="text-sm text-muted-foreground mt-2">
Reset password for <span className="font-medium">{vendor.username}</span>
</p>
</div>
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="password" className="block text-sm font-medium mb-2">
New Password
</label>
<input
id="password"
type="password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="Enter new password"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="block text-sm font-medium mb-2">
Confirm Password
</label>
<input
id="confirmPassword"
type="password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm"
placeholder="Confirm new password"
/>
</div>
<button
type="submit"
disabled={submitting}
className="w-full rounded-md bg-primary px-3 py-2 text-sm text-primary-foreground disabled:opacity-60"
>
{submitting ? "Resetting..." : "Reset Password"}
</button>
</form>
<div className="text-center">
<a
href="/auth/login"
className="text-sm text-muted-foreground hover:text-foreground"
>
Back to Login
</a>
</div>
</div>
</div>
);
}