"use client"; import { FormEvent, useEffect, useMemo, useState } from "react"; import Image from "next/image"; import { getApiUrl } from "@/utils/api"; import { isAuthDisabled } from "@/utils/auth"; import { formatFastApiDetail, UNAUTHORIZED_DETAIL } from "@/utils/authErrors"; import { notify } from "@/components/ui/sonner"; import { sanitizeAnalyticsError } from "@/utils/analytics"; import { MixpanelEvent, trackEvent } from "@/utils/mixpanel"; type AuthStatus = { configured: boolean; authenticated: boolean; username: string | null; }; const initialStatus: AuthStatus = { configured: false, authenticated: false, username: null, }; export default function AuthGate() { const [status, setStatus] = useState(initialStatus); const [isLoading, setIsLoading] = useState(true); const [isRedirecting, setIsRedirecting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const isSetupMode = useMemo(() => !status.configured, [status.configured]); useEffect(() => { if (isAuthDisabled()) { trackEvent(MixpanelEvent.Auth_Status_Checked, { configured: true, authenticated: true, auth_disabled: true, }); setStatus({ configured: true, authenticated: true, username: "electron", }); setIsLoading(false); return; } void refreshStatus(); }, []); useEffect(() => { if ( typeof window === "undefined" || isLoading || !status.authenticated || isRedirecting ) { return; } setIsRedirecting(true); window.location.replace("/"); }, [isLoading, isRedirecting, status.authenticated]); useEffect(() => { if (typeof window === "undefined" || isLoading) { return; } const params = new URLSearchParams(window.location.search); if (params.get("reason") === "unauthorized") { if (status.configured && !status.authenticated) { notify.error("Unauthorized", "Sign in to view this page.", { id: "auth-unauthorized-redirect", duration: 5000, }); } window.history.replaceState({}, "", window.location.pathname); } }, [isLoading, status.authenticated, status.configured]); const refreshStatus = async () => { setIsLoading(true); try { const response = await fetch(getApiUrl("/api/v1/auth/status"), { method: "GET", cache: "no-store", credentials: "include", }); if (!response.ok) { throw new Error("Could not load login state"); } const data = (await response.json()) as AuthStatus; trackEvent(MixpanelEvent.Auth_Status_Checked, { configured: Boolean(data.configured), authenticated: Boolean(data.authenticated), auth_disabled: false, }); setStatus({ configured: Boolean(data.configured), authenticated: Boolean(data.authenticated), username: data.username ?? null, }); } catch (fetchError) { console.error(fetchError); trackEvent(MixpanelEvent.Auth_Status_Checked, { configured: false, authenticated: false, auth_disabled: false, error_message: sanitizeAnalyticsError( fetchError, "Could not load login state" ), }); notify.error( "Could not load login", "We could not connect to the login service. Please refresh and try again." ); } finally { setIsLoading(false); } }; const handleSubmit = async (event: FormEvent) => { event.preventDefault(); const cleanedUsername = username.trim(); if (cleanedUsername.length < 3) { notify.warning( "Username too short", "Your username must be at least 3 characters." ); return; } if (password.length < 6) { notify.warning( "Password too short", "Your password must be at least 6 characters." ); return; } if (isSetupMode && password !== confirmPassword) { notify.warning( "Passwords do not match", "Make sure both password fields match before continuing." ); return; } setIsSubmitting(true); trackEvent( isSetupMode ? MixpanelEvent.Auth_Setup_Started : MixpanelEvent.Auth_SignIn_Started, { username_length: cleanedUsername.length, } ); try { const response = await fetch( getApiUrl(isSetupMode ? "/api/v1/auth/setup" : "/api/v1/auth/login"), { method: "POST", credentials: "include", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: cleanedUsername, password, }), } ); const payload = await response.json(); if (!response.ok) { const detail = formatFastApiDetail(payload?.detail); trackEvent( isSetupMode ? MixpanelEvent.Auth_Setup_Failed : MixpanelEvent.Auth_SignIn_Failed, { status_code: response.status, error_message: sanitizeAnalyticsError( detail, isSetupMode ? "Could not create account" : "Sign-in failed" ), } ); if (response.status === 401) { notify.error( "Sign-in failed", detail === UNAUTHORIZED_DETAIL ? "The username or password is incorrect. Please try again." : detail ); } else { notify.error( isSetupMode ? "Could not create account" : "Sign-in failed", detail || "Something went wrong. Please try again." ); } return; } if (isSetupMode) { trackEvent(MixpanelEvent.Auth_Setup_Completed, { username_length: cleanedUsername.length, }); setStatus({ configured: true, authenticated: false, username: (payload as AuthStatus).username ?? cleanedUsername, }); setPassword(""); setConfirmPassword(""); notify.success("Account created", "Sign in with your new username and password to continue.", { duration: 6000, }); return; } setStatus({ configured: Boolean((payload as AuthStatus).configured), authenticated: Boolean((payload as AuthStatus).authenticated), username: (payload as AuthStatus).username ?? cleanedUsername, }); trackEvent(MixpanelEvent.Auth_SignIn_Completed, { username_length: cleanedUsername.length, }); setPassword(""); setConfirmPassword(""); notify.success( "Signed in", "Welcome back. Loading your workspace." ); } catch (submitError) { console.error(submitError); trackEvent( isSetupMode ? MixpanelEvent.Auth_Setup_Failed : MixpanelEvent.Auth_SignIn_Failed, { status_code: null, error_message: sanitizeAnalyticsError( submitError, isSetupMode ? "Could not create account" : "Login unavailable" ), } ); notify.error( "Login unavailable", "The login service is unavailable right now. Please try again in a moment." ); } finally { setIsSubmitting(false); } }; if (isLoading || isRedirecting || status.authenticated) { return (
Presenton

Presenton

Preparing your workspace…

); } return (

Secure instance

{isSetupMode ? "Create your admin login" : "Sign in to continue"}

{isSetupMode ? "One-time setup for this deployment. You will use the same username and password on future visits." : "This deployment is protected. Enter your credentials to open the app."}

setUsername(event.target.value)} placeholder="your-admin-user" className="w-full rounded-[11px] border border-[#EDEEEF] bg-white px-4 py-3 font-syne text-sm text-black outline-none transition placeholder:text-[#999999] focus:border-[#a49cfc] focus:ring-2 focus:ring-[#5146E5]/20" disabled={isSubmitting} />
setPassword(event.target.value)} placeholder="At least 6 characters" className="w-full rounded-[11px] border border-[#EDEEEF] bg-white px-4 py-3 font-syne text-sm text-black outline-none transition placeholder:text-[#999999] focus:border-[#a49cfc] focus:ring-2 focus:ring-[#5146E5]/20" disabled={isSubmitting} />
{isSetupMode ? (
setConfirmPassword(event.target.value)} placeholder="Re-enter your password" className="w-full rounded-[11px] border border-[#EDEEEF] bg-white px-4 py-3 font-syne text-sm text-black outline-none transition placeholder:text-[#999999] focus:border-[#a49cfc] focus:ring-2 focus:ring-[#5146E5]/20" disabled={isSubmitting} />
) : null} {!isSetupMode && status.configured ? (

Setup is complete for this instance. Use the username and password you configured.

) : null}
); }