// ============================================================================ // public/screens/auth.jsx — Login + Signup (real, backend-wired) // ---------------------------------------------------------------------------- // Mirrors the design from voais_ui/screens/dashboard.jsx::AuthScreen, but // every interaction calls VoaisAPI and surfaces real errors / loading states. // On success, onAuth(sessionObject) is invoked so the shell can route to the // dashboard. // ============================================================================ const { useState, useEffect, useRef, useCallback } = React; function AuthScreen({ onAuth }) { const [mode, setMode] = useState("login"); // "login" | "signup" | "admin" const [showPw, setShowPw] = useState(false); const [error, setError] = useState(null); const [info, setInfo] = useState(null); const [submitting, setSubmitting] = useState(false); const [needsSlugForLogin, setNeedsSlugForLogin] = useState(false); // Signup form state const [signup, setSignup] = useState({ name: "", company: "", email: "", password: "", slug: "", industry: "", agree: true, }); const [slugTouched, setSlugTouched] = useState(false); const [slugStatus, setSlugStatus] = useState(null); // null | "checking" | "free" | "taken" | "invalid" // Login form state const [login, setLogin] = useState({ email: "", password: "", slug: "", remember: true }); // Admin login state const [admin, setAdmin] = useState({ id: "", password: "" }); // Platform config (sets initial mode and toggles). const [config, setConfig] = useState({ platformName: "VoAIs Call", signupOpen: true, trialDays: 14 }); // Load /api/config on mount — also sniff for "?admin" in URL to flip to admin mode. useEffect(() => { VoaisAPI.config().then((r) => { if (r.ok && r.data) setConfig((c) => ({ ...c, ...r.data })); }).catch(() => {}); if (location.hash === "#admin" || location.search.indexOf("admin") >= 0) { setMode("admin"); } }, []); // Live slug availability check — debounced. useEffect(() => { if (mode !== "signup") return; const raw = signup.slug.trim().toLowerCase(); if (!raw) { setSlugStatus(null); return; } setSlugStatus("checking"); const handle = setTimeout(async () => { const r = await VoaisAPI.auth.checkSlug(raw); if (r.ok) { setSlugStatus(r.data.available ? "free" : (r.data.reason === "invalid" ? "invalid" : "taken")); } else { setSlugStatus(null); } }, 400); return () => clearTimeout(handle); }, [signup.slug, mode]); // Auto-derive slug from company name once, unless user has typed in slug field. useEffect(() => { if (mode !== "signup") return; if (slugTouched) return; const s = slugify(signup.company); setSignup((v) => ({ ...v, slug: s })); }, [signup.company, slugTouched, mode]); // ── Handlers ────────────────────────────────────────────────────────── const handleLogin = useCallback(async (e) => { if (e) e.preventDefault(); setError(null); if (!login.email || !login.password) { setError("Email and password are required."); return; } setSubmitting(true); try { const r = await VoaisAPI.auth.login({ email: login.email.trim(), password: login.password, slug: needsSlugForLogin ? login.slug.trim().toLowerCase() : undefined, }); if (r.ok) { onAuth(VoaisAPI.getSession()); return; } if (r.status === 409 && r.data?.needsSlug) { setNeedsSlugForLogin(true); setError(r.data.msg || "Enter your workspace URL to continue."); } else { setError(r.data?.msg || "Login failed. Please try again."); } } catch (err) { setError("Network error. Please retry."); } finally { setSubmitting(false); } }, [login, needsSlugForLogin, onAuth]); const handleSignup = useCallback(async (e) => { if (e) e.preventDefault(); setError(null); if (!signup.name.trim()) return setError("Enter your full name."); if (!signup.company.trim()) return setError("Enter your company / organization name."); if (!signup.email.trim()) return setError("Enter your work email."); if (signup.password.length < 8) return setError("Password must be at least 8 characters."); if (!signup.agree) return setError("Please agree to the Terms and Privacy Policy."); if (slugStatus === "taken") return setError("That workspace URL is already taken."); if (slugStatus === "invalid")return setError("Workspace URL must be 3–48 chars: lowercase letters, numbers, hyphens."); setSubmitting(true); try { const r = await VoaisAPI.auth.signup({ name: signup.name.trim(), company: signup.company.trim(), email: signup.email.trim(), password: signup.password, slug: signup.slug.trim().toLowerCase() || undefined, industry: signup.industry || undefined, }); if (r.ok) { onAuth(VoaisAPI.getSession()); return; } setError(r.data?.msg || "Sign-up failed. Please try again."); } catch (err) { setError("Network error. Please retry."); } finally { setSubmitting(false); } }, [signup, slugStatus, onAuth]); const handleAdminLogin = useCallback(async (e) => { if (e) e.preventDefault(); setError(null); if (!admin.id || !admin.password) { setError("Both fields are required."); return; } setSubmitting(true); try { const r = await VoaisAPI.auth.adminLogin({ email: admin.id.trim(), password: admin.password, }); if (r.ok) { onAuth(VoaisAPI.getSession()); return; } setError(r.data?.msg || "Login failed. Please try again."); } catch (err) { setError("Network error. Please retry."); } finally { setSubmitting(false); } }, [admin, onAuth]); // Mode switch resets transient state. const switchMode = (m) => { setMode(m); setError(null); setInfo(null); setNeedsSlugForLogin(false); }; // ── Render ──────────────────────────────────────────────────────────── return (
{/* ===== Left — brand / marketing side ===== */}
VoAIs Call
by Hidrogen
v1.0 · Now in production

Replace your call center with autonomous AI agents.

Run 1,000 parallel calls. Hindi + Hinglish + English. Lead qualification, demo bookings, callbacks — all hands-off.

{[ { k: "1,284", v: "calls running now" }, { k: "63.2%", v: "average connect rate" }, { k: "₹1.40", v: "per qualified lead" }, { k: "780ms", v: "AI response latency" }, ].map((x, i) => (
{x.k}
{x.v}
))}
Trusted by Mercedes-Benz · IIM Lucknow · STPI · MeitY · Brij Tower
{/* decorative waveform */}
{Array.from({ length: 80 }).map((_, i) => { const x = i * 8; const h = 80 + 100 * Math.abs(Math.sin(i * 0.4)) + 60 * Math.abs(Math.cos(i * 0.27)); return ; })}
{/* ===== Right — the form ===== */}

{mode === "login" ? "Welcome back" : mode === "signup" ? "Create your workspace" : "Super-admin sign in"}

{mode === "login" && "Sign in to continue managing your calling campaigns."} {mode === "signup" && `Start a free ${config.trialDays}-day trial. No credit card required.`} {mode === "admin" && "Platform administration. Tenant operators use the regular sign-in."}

{/* Error + info banners */} {error && (
{error}
)} {info && (
{info}
)} {/* ===== LOGIN ===== */} {mode === "login" && (
setLogin({ ...login, email: e.target.value })}/> setLogin({ ...login, password: v })} autoComplete="current-password" /> {needsSlugForLogin && ( setLogin({ ...login, slug: e.target.value })}/> )} {submitting ? "Signing in…" : "Sign in"} {!submitting && }
)} {/* ===== SIGNUP ===== */} {mode === "signup" && (
setSignup({ ...signup, name: e.target.value })}/> setSignup({ ...signup, company: e.target.value })}/> }>
voais.in/t/ { setSlugTouched(true); setSignup({ ...signup, slug: e.target.value.toLowerCase() }); }}/>
setSignup({ ...signup, email: e.target.value })}/> setSignup({ ...signup, password: v })} autoComplete="new-password" /> {submitting ? "Creating…" : "Create workspace"} {!submitting && }
)} {/* ===== ADMIN LOGIN ===== */} {mode === "admin" && (
setAdmin({ ...admin, id: e.target.value })}/> setAdmin({ ...admin, password: v })} autoComplete="current-password" /> {submitting ? "Signing in…" : "Sign in as admin"} {!submitting && }
)} {/* Mode switcher footer */}
{mode === "login" && config.signupOpen && ( <>New to VoAIs? { e.preventDefault(); switchMode("signup"); }}>Create an account )} {mode === "login" && !config.signupOpen && ( <>Signups are invite-only. { e.preventDefault(); setInfo("Contact your workspace admin for an invite."); }}>Need access? )} {mode === "signup" && ( <>Already have an account? { e.preventDefault(); switchMode("login"); }}>Sign in )} {mode === "admin" && ( <>Not an admin? { e.preventDefault(); switchMode("login"); }}>Workspace sign-in )}
SOC 2 Type II + DPDP compliant
India data residency · TRAI/DND compliant calling.
); } // ── Helpers ──────────────────────────────────────────────────────────── // Password input with show/hide affordance. function PasswordInput({ value, onChange, show, onShow, autoComplete, ...rest }) { return (
onChange(e.target.value)} {...rest}/>
); } function SlugHint({ status, slug }) { if (!slug) return This is the URL of your workspace.; if (status === "checking") return Checking availability…; if (status === "free") return ✓ Available.; if (status === "taken") return Already taken — try a different one.; if (status === "invalid") return Use 3–48 lowercase letters, numbers, hyphens.; return null; } function slugify(name) { return String(name || "") .toLowerCase() .normalize("NFKD") .replace(/[\u0300-\u036f]/g, "") .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, "") .slice(0, 48); } // Expose globally so app.jsx can route to it. window.AuthScreen = AuthScreen;