// ============================================================================ // 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 (
Run 1,000 parallel calls. Hindi + Hinglish + English. Lead qualification, demo bookings, callbacks — all hands-off.
{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 && (