tranth3truong's picture
Deploy public Scribe-only CarePath Space
cc678b9
Raw
History Blame Contribute Delete
4.44 kB
import { useEffect, useState } from "react";
import "./App.css";
import logoUrl from "./assets/carepath.svg";
import { createSession } from "./api";
import { AdminReview } from "./components/AdminReview";
import { ConsentGate, type ConsentPayload } from "./components/ConsentGate";
import { DeviceCheck, type DeviceCheckResult } from "./components/DeviceCheck";
import { InterpreterConsole } from "./components/InterpreterConsole";
import { IntentQuiz, type Intent } from "./components/IntentQuiz";
import { copy, initialLanguage, persistLanguage, type Language } from "./copy";
function ProductShell({ language, setLanguage }: { language: Language; setLanguage: (language: Language) => void }) {
const text = copy[language];
const publicSiteUrl = import.meta.env.VITE_PUBLIC_SITE_URL;
const allProductsHref = (() => {
if (!publicSiteUrl) return "/";
const url = new URL(publicSiteUrl);
url.searchParams.set("lang", language);
return url.href;
})();
return (
<header className="product-shell" lang={language}>
<nav className="product-breadcrumb" aria-label={text.breadcrumb}>
<ol>
<li>
<a className="product-brand" href="/" aria-label="CarePath">
<img src={logoUrl} alt="" />
<span>CarePath</span>
</a>
</li>
<li aria-hidden="true">/</li>
<li aria-current="page">
{text.productName}
</li>
</ol>
</nav>
<p className="product-status">
<span aria-hidden="true" />
{text.status}
</p>
<div className="product-shell__actions">
<a className="all-products" href={allProductsHref}>
{text.allProducts}
</a>
<div className="language-toggle" role="group" aria-label={text.language}>
<button
aria-pressed={language === "vi"}
type="button"
onClick={() => setLanguage("vi")}
>
VI
</button>
<button
aria-pressed={language === "en"}
type="button"
onClick={() => setLanguage("en")}
>
EN
</button>
</div>
</div>
</header>
);
}
function App() {
const [language, setLanguage] = useState<Language>(initialLanguage);
const [sessionId, setSessionId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const [starting, setStarting] = useState(false);
const [intent, setIntent] = useState<Intent | null>(null);
const [deviceCheck, setDeviceCheck] = useState<DeviceCheckResult | null>(null);
const [hash, setHash] = useState(() => window.location.hash);
useEffect(() => {
persistLanguage(language);
document.documentElement.lang = language;
document.title = copy[language].title;
}, [language]);
useEffect(() => {
const onHashChange = () => setHash(window.location.hash);
window.addEventListener("hashchange", onHashChange);
return () => window.removeEventListener("hashchange", onHashChange);
}, []);
useEffect(() => {
window.scrollTo(0, 0);
}, [deviceCheck, hash, intent, sessionId]);
async function handleConsent(consent: ConsentPayload) {
setStarting(true);
setError(null);
try {
const result = await createSession({ consent });
setSessionId(result.session_id);
} catch {
setError(copy[language].consent.startError);
} finally {
setStarting(false);
}
}
let content;
if (hash === "#/kiem-duyet") {
content = <AdminReview language={language} />;
} else if (sessionId && !deviceCheck) {
content = <DeviceCheck language={language} onComplete={setDeviceCheck} />;
} else if (sessionId) {
content = <InterpreterConsole deviceId={deviceCheck?.deviceId} initialSpeaker={intent?.direction === "en-vi" ? "patient" : "doctor"} language={language} onComplete={() => { setSessionId(null); setDeviceCheck(null); }} sessionId={sessionId} voiceReady={deviceCheck?.voiceReady} />;
} else if (!intent) {
content = <IntentQuiz language={language} onComplete={setIntent} />;
} else {
content = <ConsentGate error={error} isSubmitting={starting} language={language} onConsent={handleConsent} />;
}
return (
<div className="product-app" lang={language}>
<ProductShell language={language} setLanguage={setLanguage} />
{content}
</div>
);
}
export default App;