Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
147 lines
4.9 KiB
TypeScript
147 lines
4.9 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import { formatAuthNetworkError } from "@/lib/supabase/env";
|
|
import { formatAuthError, isAuthRateLimitError } from "@/lib/supabase/auth-errors";
|
|
import {
|
|
formatCooldownSeconds,
|
|
getResetEmailCooldownRemainingMs,
|
|
RESET_EMAIL_COOLDOWN_MS,
|
|
startResetEmailCooldown,
|
|
} from "@/lib/auth/reset-email-cooldown";
|
|
import type { PortalRole } from "@/lib/auth/roles";
|
|
import { loginPathForRole } from "@/lib/auth/roles";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
function resetRedirectUrl(portal: PortalRole) {
|
|
const origin =
|
|
typeof window !== "undefined" ? window.location.origin : "";
|
|
const next = `/reset-password?portal=${portal}`;
|
|
return `${origin}/auth/callback?next=${encodeURIComponent(next)}`;
|
|
}
|
|
|
|
export function ForgotPasswordForm({ portal }: { portal: PortalRole }) {
|
|
const [email, setEmail] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [sent, setSent] = useState(false);
|
|
const [cooldownMs, setCooldownMs] = useState(0);
|
|
|
|
const loginHref = loginPathForRole(portal);
|
|
const portalLabel = portal === "league_master" ? "League Master" : "Team Manager";
|
|
|
|
useEffect(() => {
|
|
setCooldownMs(getResetEmailCooldownRemainingMs());
|
|
const id = window.setInterval(() => {
|
|
const remaining = getResetEmailCooldownRemainingMs();
|
|
setCooldownMs(remaining);
|
|
if (remaining <= 0) window.clearInterval(id);
|
|
}, 1000);
|
|
return () => window.clearInterval(id);
|
|
}, [sent, error]);
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
|
|
const remaining = getResetEmailCooldownRemainingMs();
|
|
if (remaining > 0) {
|
|
setError(
|
|
`Please wait ${formatCooldownSeconds(remaining)} seconds before requesting another email.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError(null);
|
|
try {
|
|
const supabase = createClient();
|
|
const { error: resetError } = await supabase.auth.resetPasswordForEmail(
|
|
email.trim(),
|
|
{ redirectTo: resetRedirectUrl(portal) }
|
|
);
|
|
if (resetError) {
|
|
setError(formatAuthError(resetError));
|
|
if (isAuthRateLimitError(resetError)) {
|
|
startResetEmailCooldown();
|
|
setCooldownMs(RESET_EMAIL_COOLDOWN_MS);
|
|
}
|
|
return;
|
|
}
|
|
startResetEmailCooldown();
|
|
setCooldownMs(RESET_EMAIL_COOLDOWN_MS);
|
|
setSent(true);
|
|
} catch (err) {
|
|
setError(formatAuthNetworkError(err));
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
const submitDisabled = loading || cooldownMs > 0;
|
|
|
|
if (sent) {
|
|
return (
|
|
<div className="space-y-4 text-center">
|
|
<p className="text-sm text-muted-foreground">
|
|
If an account exists for{" "}
|
|
<strong className="text-foreground">{email}</strong>, we sent a reset
|
|
link. Check your inbox and spam folder.
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
The link opens a page to set a new password, then signs you into the{" "}
|
|
{portalLabel} portal.
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
Did not get it? Wait at least an hour (Supabase email limits) before
|
|
trying again, or use Authentication → Users in the Supabase dashboard.
|
|
</p>
|
|
<Button variant="outline" className="w-full" asChild>
|
|
<Link href={loginHref}>Back to sign in</Link>
|
|
</Button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">Email</Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
required
|
|
autoComplete="email"
|
|
placeholder="you@example.com"
|
|
/>
|
|
</div>
|
|
{error && (
|
|
<p className="whitespace-pre-line text-sm text-red-400">{error}</p>
|
|
)}
|
|
{cooldownMs > 0 && !error && (
|
|
<p className="text-xs text-muted-foreground">
|
|
You can request another email in{" "}
|
|
{formatCooldownSeconds(cooldownMs)}s.
|
|
</p>
|
|
)}
|
|
<Button type="submit" className="w-full" disabled={submitDisabled}>
|
|
{loading
|
|
? "Sending…"
|
|
: cooldownMs > 0
|
|
? `Wait ${formatCooldownSeconds(cooldownMs)}s`
|
|
: "Send reset link"}
|
|
</Button>
|
|
<p className="text-center text-sm text-muted-foreground">
|
|
<Link href={loginHref} className="hover:underline">
|
|
Back to sign in
|
|
</Link>
|
|
</p>
|
|
</form>
|
|
);
|
|
}
|