// auth.jsx — Sign in / Sign up screens (wired to MedSync backend) const { useState: useStateA, useEffect: useEffectA, useRef: useRefA } = React; async function postAuth(path, body) { const r = await fetch(path, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(body), }); let data = null; try { data = await r.json(); } catch (_) {} if (!r.ok) { const msg = (data && (data.detail || data.message)) || `Request failed (${r.status})`; throw new Error(typeof msg === "string" ? msg : "Sign-in failed"); } return data || {}; } function applyAuthSuccess(data) { // Update window globals (used by api.jsx) so subsequent fetches carry the // bearer + active account; the parent screen also passes the payload to the // store via onSignIn(payload) so React state stays in sync. if (data && data.token && window.MSApi) window.MSApi.setToken(data.token); if (data && data.active_account && data.active_account.id && window.MSApi) { window.MSApi.setAccountId(data.active_account.id); } if (data && data.doctor) window._currentDoctor = data.doctor; if (data && data.active_account) window._currentAccount = data.active_account; if (data && data.accounts) window._currentAccounts = data.accounts; if (data && data.active_role) window._currentRole = data.active_role; } function AuthShell({ children, side }) { return (
MedSync
Clinic operating system
{children}
HIPAA compliant · SOC 2 Type II · Help Privacy
{side}
); } function GoogleSignInButton({ onCredential, onError }) { const hostRef = useRefA(null); const credRef = useRefA(onCredential); const errRef = useRefA(onError); const [ready, setReady] = useStateA(false); const [missing, setMissing] = useStateA(false); // Keep latest callbacks without re-initializing GSI. useEffectA(() => { credRef.current = onCredential; }, [onCredential]); useEffectA(() => { errRef.current = onError; }, [onError]); useEffectA(() => { let cancelled = false; let attempts = 0; const tick = () => { if (cancelled) return; const clientId = window.MS_GOOGLE_CLIENT_ID; if (!clientId) { if (attempts++ > 50) { setMissing(true); return; } setTimeout(tick, 100); return; } if (!(window.google && window.google.accounts && window.google.accounts.id)) { if (attempts++ > 80) { setMissing(true); return; } setTimeout(tick, 100); return; } try { if (window.__msGsiInitedFor !== clientId) { window.google.accounts.id.initialize({ client_id: clientId, callback: (resp) => { const cb = credRef.current; const er = errRef.current; if (resp && resp.credential) cb && cb(resp.credential); else er && er("Google did not return a credential."); }, ux_mode: "popup", auto_select: false, }); window.__msGsiInitedFor = clientId; } if (hostRef.current) { hostRef.current.innerHTML = ""; window.google.accounts.id.renderButton(hostRef.current, { type: "standard", theme: "outline", size: "large", text: "continue_with", shape: "pill", logo_alignment: "left", width: 360, }); setReady(true); } } catch (e) { const er = errRef.current; er && er(String(e && e.message || e)); } }; tick(); return () => { cancelled = true; }; }, []); if (missing) { return (
Google sign-in is not configured. Set GOOGLE_CLIENT_ID on the server.
); } return (
); } function SignIn({ onSignIn, onSwitch }) { const [email, setEmail] = useStateA(""); const [password, setPassword] = useStateA(""); const [showPw, setShowPw] = useStateA(false); const [remember, setRemember] = useStateA(true); const [pending, setPending] = useStateA(false); const [error, setError] = useStateA(""); const finish = (data) => { applyAuthSuccess(data); onSignIn(data); }; async function handleGoogle(credential) { setError(""); setPending(true); try { const data = await postAuth("/api/auth/google", { credential }); finish(data); } catch (e) { setError(e.message || "Google sign-in failed."); } finally { setPending(false); } } async function handlePassword(e) { e.preventDefault(); if (!email || !password) { setError("Enter your email and password."); return; } setError(""); setPending(true); try { const data = await postAuth("/api/auth/login", { email, password }); finish(data); } catch (err) { setError(err.message || "Sign-in failed."); } finally { setPending(false); } } return (
MedSync for care teams

Clinical work,
kept in sync.

MedSync records visits, creates transcripts and clinical notes, manages patients, appointments, notifications, and follow-ups in one workspace. Compliance is built in with encrypted PHI, audit logging, consent checks, and HIPAA-ready workflows.

AI
Transcription and notes
PHI
Encrypted at rest
HIPAA
Audit-first design
} >

Welcome back

Sign in to continue to your clinic.
setError(m)}/>
or
setEmail(e.target.value)} type="email" placeholder="you@clinic.com" autoComplete="email"/>
Forgot?
setPassword(e.target.value)} type={showPw ? "text" : "password"} placeholder="••••••••••••" autoComplete="current-password"/>
{error && (
{error}
)}
New to MedSync? Create an account
); } function SignUp({ onDone, onSwitch }) { const [step, setStep] = useStateA(0); const [data, setData] = useStateA({ clinicSize: "small" }); const [pending, setPending] = useStateA(false); const [error, setError] = useStateA(""); const set = (k, v) => setData(d => ({...d, [k]: v})); const SIZES = [ { id: "solo", label: "Solo", sub: "1 provider" }, { id: "small", label: "Small group", sub: "2–10 providers" }, { id: "mid", label: "Mid-size", sub: "11–50 providers" }, { id: "large", label: "Large", sub: "50+ providers" }, ]; async function handleGoogle(credential) { setError(""); setPending(true); try { const resp = await postAuth("/api/auth/google", { credential }); applyAuthSuccess(resp); onDone(resp); } catch (e) { setError(e.message || "Google sign-up failed."); } finally { setPending(false); } } async function handleCreate() { if (pending) return; if (!data.first || !data.last || !data.email || !data.pw) { setStep(0); setError("Please fill in your name, email, and password."); return; } const missing = passwordRequirements(data.pw).filter(item => !item.ok); if (missing.length) { setStep(0); setError("Password needs: " + missing.map(item => item.label.toLowerCase()).join(", ") + "."); return; } setError(""); setPending(true); try { const fullName = `${data.first} ${data.last}`.trim(); const resp = await postAuth("/api/auth/register", { name: fullName, email: data.email, password: data.pw, specialty: data.specialty || null, }); applyAuthSuccess(resp); onDone(resp); } catch (e) { setError(e.message || "Could not create your account."); } finally { setPending(false); } } return (
Get started

Set up your clinic in under 5 minutes.

} >
{[0, 1].map(i => (
))}
{step === 0 && (

Create your account

Start with the basics. We'll set up your clinic next.
setError(m)}/>
or
set("first", e.target.value)}/>
set("last", e.target.value)}/>
set("email", e.target.value)}/>
set("pw", e.target.value)}/>
{error && (
{error}
)}
)} {step === 1 && (

Tell us about your clinic

We'll set up your workspace and invite your team.
set("clinicName", e.target.value)}/>
{SIZES.map(s => ( ))}
{error && (
{error}
)}
By creating an account, you agree to our Terms and HIPAA BAA.
)}
Already have an account? Sign in
); } function passwordRequirements(value) { const text = value || ""; return [ { key: "length", label: "8+ characters", ok: text.length >= 8 }, { key: "upper", label: "uppercase letter", ok: /[A-Z]/.test(text) }, { key: "lower", label: "lowercase letter", ok: /[a-z]/.test(text) }, { key: "number", label: "number", ok: /\d/.test(text) }, { key: "symbol", label: "special character", ok: /[^A-Za-z0-9]/.test(text) }, ]; } function PasswordStrength({ value }) { const requirements = passwordRequirements(value); const met = requirements.filter(item => item.ok).length; const score = value ? Math.min(4, met) : 0; const labels = ["Too short", "Weak", "Fair", "Good", "Strong"]; const colors = ["var(--ink-5)", "var(--accent-danger)", "var(--accent-warn)", "var(--accent-success)", "var(--accent-success)"]; return (
{[0,1,2,3].map(i => (
))} {value ? labels[score] : ""}
{requirements.map(item => ( {item.ok ? : } {item.label} ))}
); } Object.assign(window, { SignIn, SignUp });