Yaltopia-FIFA/components/auth/signup-form.tsx
Kirubel-Kibru-Yaltopia 89440985f1
Some checks failed
Deploy to Cloudflare Workers / deploy (push) Has been cancelled
x
2026-05-24 21:46:10 +03:00

222 lines
6.6 KiB
TypeScript

"use client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { createClient } from "@/lib/supabase/client";
import { formatAuthNetworkError } from "@/lib/supabase/env";
import { formatAuthError, formatSignupError } from "@/lib/supabase/auth-errors";
import { ensureUserProfile } from "@/lib/auth/resolve-portal-role";
import type { PortalRole } from "@/lib/auth/roles";
import { loginPathForRole, PORTAL_ROUTES } from "@/lib/auth/roles";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { PasswordInput } from "@/components/ui/password-input";
import { Label } from "@/components/ui/label";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
async function signupViaApi(
email: string,
password: string,
displayName: string,
portalRole: PortalRole
) {
const res = await fetch("/api/auth/signup", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password, displayName, portalRole }),
});
const json = (await res.json()) as {
success: boolean;
error?: string;
useClient?: boolean;
needsEmailConfirmation?: boolean;
};
return { ok: res.ok && json.success, status: res.status, ...json };
}
export function SignupForm({
portalRole,
title,
description,
}: {
portalRole: PortalRole;
title: string;
description: string;
}) {
const router = useRouter();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [displayName, setDisplayName] = useState("");
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const loginHref = loginPathForRole(portalRole);
const portalLabel =
portalRole === "league_master" ? "League Master" : "Team Manager";
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError(null);
try {
const trimmedEmail = email.trim();
const api = await signupViaApi(
trimmedEmail,
password,
displayName,
portalRole
);
if (api.ok) {
if (api.needsEmailConfirmation) {
setError(
`Confirm your email, then sign in at ${portalLabel} login.`
);
return;
}
const supabase = createClient();
const { error: signInError } = await supabase.auth.signInWithPassword({
email: trimmedEmail,
password,
});
if (signInError) {
setError(
`Account created. Sign in at ${loginHref} (${signInError.message})`
);
return;
}
router.push(PORTAL_ROUTES[portalRole]);
router.refresh();
return;
}
if (api.status !== 503 && !api.useClient) {
setError(formatSignupError(api.error ?? "Signup failed"));
return;
}
const supabase = createClient();
const { data, error: authError } = await supabase.auth.signUp({
email: trimmedEmail,
password,
options: {
data: { display_name: displayName, portal_role: portalRole },
},
});
if (authError) {
setError(formatAuthError(authError));
return;
}
if (data.user && !data.session) {
setError(
`Confirm your email, then sign in at ${portalLabel} login.`
);
return;
}
if (data.user) {
const sync = await ensureUserProfile(
supabase,
data.user,
portalRole,
displayName
);
if (sync.error === "schema") {
setError(
"Account created but database needs an update. Run: npm run db:push — then sign in."
);
return;
}
if (!sync.ok && sync.error) {
setError(formatSignupError(sync.error));
return;
}
}
router.push(PORTAL_ROUTES[portalRole]);
router.refresh();
} catch (err) {
setError(formatAuthNetworkError(err));
} finally {
setLoading(false);
}
}
return (
<div className="retro-grid flex min-h-screen items-center justify-center px-4 py-12">
<Card className="retro-card w-full max-w-md border-border/80">
<CardHeader>
<p className="font-display text-[10px] font-bold uppercase tracking-[0.2em] text-neon">
Yaltopia FIFA
</p>
<CardTitle className="font-display text-xl font-black uppercase tracking-wide">
{title}
</CardTitle>
<CardDescription>{description}</CardDescription>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label>Display name</Label>
<Input
value={displayName}
onChange={(e) => setDisplayName(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label>Email</Label>
<Input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
autoComplete="email"
/>
</div>
<div className="space-y-2">
<Label>Password</Label>
<PasswordInput
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={6}
autoComplete="new-password"
/>
</div>
{error && (
<p className="whitespace-pre-line text-sm text-red-400">{error}</p>
)}
<Button type="submit" variant="neon" className="w-full" disabled={loading}>
{loading ? "Creating account…" : "Create account"}
</Button>
</form>
<p className="mt-4 text-center text-sm text-muted-foreground">
<Link href={loginHref} className="text-neon hover:underline">
Sign in
</Link>
{portalRole === "manager" && (
<>
{" · "}
<Link href="/" className="hover:underline">
Home
</Link>
</>
)}
</p>
</CardContent>
</Card>
</div>
);
}