// 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}
{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
}
>
Welcome back
Sign in to continue to your clinic.
setError(m)}/>
or
);
}
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.
30-day free trial — no card required
Import patients & schedule from your current EHR
HIPAA Business Associate Agreement included
Dedicated onboarding specialist
}
>
{step === 0 && (
Create your account
Start with the basics. We'll set up your clinic next.
setError(m)}/>
or
Work email
set("email", e.target.value)}/>
{error && (
{error}
)}
setStep(1)} style={{width: "100%", justifyContent: "center", marginTop: 4}}>Continue
)}
{step === 1 && (
Tell us about your clinic
We'll set up your workspace and invite your team.
Clinic name
set("clinicName", e.target.value)}/>
Specialty
set("specialty", e.target.value)}>
Select…
Primary care / family medicine
Internal medicine
Dental practice
Pediatrics
Multi-specialty group
Urgent care
Mental / behavioral health
Other
Practice size
{SIZES.map(s => (
set("clinicSize", s.id)}>
{s.label}
{s.sub}
))}
{error && (
{error}
)}
setStep(0)} style={{flex: 1, justifyContent: "center"}}>Back
{pending ? "Creating…" : <>Create clinic >}
By creating an account, you agree to our Terms and HIPAA BAA.
)}
);
}
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 });