sinka/web/src/components/login-form.tsx
armando cf16bf1fb6 Show integration health in the admin and keep clients on their own screen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:43:18 -03:00

140 lines
5.2 KiB
TypeScript

"use client";
import Image from "next/image";
import { FormEvent, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { api, type SessionUser } from "@/lib/api";
export function LoginForm({
variant,
tenantSlug,
}: {
variant: "admin" | "tenant";
tenantSlug?: string;
}) {
const router = useRouter();
const search = useSearchParams();
const oauthError = {
google_off: "Entrar com o Google está indisponível no momento.",
google_unlinked: "Este e-mail Google não tem acesso à administração.",
google_denied: "Não foi possível entrar com o Google.",
}[search.get("error") ?? ""];
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function onSubmit(event: FormEvent) {
event.preventDefault();
setError("");
setLoading(true);
try {
const result = await api<{ user: SessionUser }>("/api/auth/login", {
method: "POST",
body: JSON.stringify({
email,
password,
tenantSlug: variant === "tenant" ? tenantSlug : undefined,
}),
});
const next = search.get("next");
if (result.user.kind === "platform") {
router.replace(next?.startsWith("/admin") ? next : "/admin");
return;
}
const home = `/${result.user.tenantSlug}`;
router.replace(next?.startsWith(home) ? next : home);
} catch (err) {
setError(err instanceof Error ? err.message : "Falha no login.");
} finally {
setLoading(false);
}
}
return (
<main className="flex min-h-dvh flex-col bg-black text-[#E8F4E5]">
<div className="mx-auto flex w-full max-w-md flex-1 flex-col justify-center px-6 py-16">
<a href="/" className="mb-10 block w-fit">
<Image
src="/brand/sinka-logo.webp"
alt="Sinka"
width={180}
height={58}
className="h-8 w-auto"
priority
/>
</a>
<p className="text-xs font-semibold uppercase tracking-[0.28em] text-[#82C85C]">
{variant === "admin" ? "Administração" : "Operação"}
</p>
<h1 className="font-display mt-2 text-4xl font-extrabold uppercase leading-none">
{variant === "admin" ? "Administração Sinka" : "Entrar"}
</h1>
<p className="mt-4 text-[#E8F4E5]/60">
{variant === "admin"
? "Crie e acompanhe os clientes da Sinka."
: "Entre com e-mail e senha, ou com o Google."}
</p>
<form className="mt-8 flex flex-col gap-4" onSubmit={onSubmit}>
<label className="flex flex-col gap-2 text-sm">
E-mail
<input
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 outline-none focus:border-[#82C85C]"
type="email"
value={email}
onChange={(event) => setEmail(event.target.value)}
required
autoComplete="username"
/>
</label>
<label className="flex flex-col gap-2 text-sm">
Senha
<input
className="rounded-xl border border-white/10 bg-white/5 px-4 py-3 outline-none focus:border-[#82C85C]"
type="password"
value={password}
onChange={(event) => setPassword(event.target.value)}
required
autoComplete="current-password"
/>
</label>
{error || oauthError ? <p className="text-sm text-red-300">{error || oauthError}</p> : null}
<button className="btn-sinka mt-2 justify-center" disabled={loading} type="submit">
{loading ? "Entrando…" : "Entrar"}
</button>
</form>
<p className="mt-5 text-center text-xs uppercase tracking-[0.2em] text-[#E8F4E5]/35">ou</p>
<a
className="btn-google mt-4"
href={`/api/auth/google${
variant === "tenant"
? `?tenant=${encodeURIComponent(tenantSlug ?? "")}&next=${encodeURIComponent(`/${tenantSlug}`)}`
: "?next=/admin"
}`}
>
<svg width="18" height="18" viewBox="0 0 18 18" aria-hidden>
<path
fill="#4285F4"
d="M17.64 9.2c0-.637-.057-1.251-.164-1.84H9v3.481h4.844c-.209 1.125-.843 2.078-1.796 2.717v2.258h2.908c1.702-1.567 2.684-3.874 2.684-6.615z"
/>
<path
fill="#34A853"
d="M9 18c2.43 0 4.467-.806 5.956-2.184l-2.908-2.258c-.806.54-1.837.86-3.048.86-2.344 0-4.328-1.584-5.036-3.711H.957v2.332C2.438 15.983 5.482 18 9 18z"
/>
<path
fill="#FBBC05"
d="M3.964 10.707c-.18-.54-.282-1.117-.282-1.707s.102-1.167.282-1.707V4.961H.957C.348 6.175 0 7.55 0 9s.348 2.825.957 4.039l3.007-2.332z"
/>
<path
fill="#EA4335"
d="M9 3.58c1.321 0 2.508.454 3.44 1.345l2.582-2.58C13.463.891 11.426 0 9 0 5.482 0 2.438 2.017.957 4.961L3.964 7.29C4.672 5.163 6.656 3.58 9 3.58z"
/>
</svg>
Entrar com o Google
</a>
</div>
</main>
);
}