import { useState } from "react"; import { api } from "../lib/api"; type Phase = "idle" | "sending" | "requested" | "approved" | "error"; export default function Login() { // A denied Google login bounces back here with ?access=requested (we recorded it) or // ?access=denied (no usable email). Surface that so the user isn't left on a raw error. const params = new URLSearchParams(window.location.search); const bounced = params.get("access"); // "requested" | "denied" | null const [email, setEmail] = useState(""); const [phase, setPhase] = useState(bounced === "requested" ? "requested" : "idle"); const [error, setError] = useState(""); async function submit(e: React.FormEvent) { e.preventDefault(); setError(""); setPhase("sending"); try { const r = await api.requestAccess(email.trim()); setPhase(r.status === "approved" ? "approved" : "requested"); } catch { setError("Couldn't submit — check the address and try again."); setPhase("error"); } } const done = phase === "requested" || phase === "approved"; return (
Siftlode

A self-hosted, filterable feed of your YouTube subscriptions — sort, tag, and tune out the noise. Sign in with Google to get started; YouTube access is an optional, separate step you control.

Sign in with Google
{phase === "approved" ? (

You're already approved — just sign in with Google above.

) : done ? (

Thanks — your request is in. We'll email you when it's approved.

) : ( <>
No access yet?
{bounced === "denied" && (

That Google account isn't approved. Request access below.

)}
setEmail(e.target.value)} placeholder="you@gmail.com" className="flex-1 min-w-0 bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent" />
{error &&

{error}

} )}
); }