feat(web): screens from docs/mock — focus flow, today, meds, doctor, profile
- /flow renders the dialog engine's current step as a mock screen (consent,
age, onboarding and scales with progress, mood 0–10, free text, dose
reminder, crisis with hotlines and breathing, safety plan), so every
engine scenario gets the design without duplicating its logic
- today / meds / doctor / profile / messages tabs; navy sidebar and
emergency rail on desktop, emergency block and tab bar on phones
- API: GET /me/{token}/today (today's doses, week marks, next visit, demo
clock time); demo ЕМИАС sign-in seeds the persona's synthetic profile and
prescription from the dashboard fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,12 @@ import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { ApiError, api } from "../api/client";
|
||||
import type { AuthResult, DemoPersona } from "../api/types";
|
||||
import { AppShell, BackLink, Notice } from "../components/AppShell";
|
||||
import { AppShell, Notice, TopBar } from "../components/AppShell";
|
||||
import { Icon } from "../components/Icon";
|
||||
import { ru } from "../content/ru";
|
||||
import { useSession } from "../session/SessionContext";
|
||||
|
||||
// A2 · Вход через ЕМИАС (демо: одноразовый код от врача или синтетический пациент)
|
||||
export function EmiasSignIn() {
|
||||
const t = ru.emias;
|
||||
const navigate = useNavigate();
|
||||
@@ -26,11 +27,11 @@ export function EmiasSignIn() {
|
||||
|
||||
const finish = (auth: AuthResult) => {
|
||||
signIn(auth);
|
||||
navigate("/chat", { replace: true });
|
||||
navigate("/flow", { replace: true });
|
||||
};
|
||||
|
||||
const fail = (e: unknown) => {
|
||||
setError(e instanceof ApiError && e.status === 401 ? t.codeError : t.networkError);
|
||||
setError(e instanceof ApiError && e.status === 401 ? t.codeError : ru.common.networkError);
|
||||
setBusy(false);
|
||||
};
|
||||
|
||||
@@ -57,81 +58,64 @@ export function EmiasSignIn() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AppShell>
|
||||
<main className="page" id="main">
|
||||
<div className="narrow">
|
||||
<BackLink to="/">{t.back}</BackLink>
|
||||
<section className="panel" aria-labelledby="emias-title">
|
||||
<h1 id="emias-title" className="h2">
|
||||
{t.title}
|
||||
</h1>
|
||||
<Notice>{t.demoNotice}</Notice>
|
||||
<AppShell signedIn={false} focus>
|
||||
<div className="stack narrow">
|
||||
<TopBar onBack={() => navigate("/")} />
|
||||
<header className="head">
|
||||
<h1 className="h1">{t.title}</h1>
|
||||
<p className="lead">{t.lead}</p>
|
||||
</header>
|
||||
|
||||
<form className="form" onSubmit={submit} noValidate>
|
||||
<div className={error ? "field field--error" : "field"}>
|
||||
<label htmlFor="code" className="field__label">
|
||||
{t.codeLabel}
|
||||
</label>
|
||||
<input
|
||||
id="code"
|
||||
className="input"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder={t.codePlaceholder}
|
||||
autoComplete="one-time-code"
|
||||
autoCapitalize="characters"
|
||||
spellCheck={false}
|
||||
maxLength={32}
|
||||
aria-invalid={error ? true : undefined}
|
||||
aria-describedby={error ? "code-error" : "code-hint"}
|
||||
/>
|
||||
{error ? (
|
||||
<p id="code-error" className="field__error" role="alert">
|
||||
{error}
|
||||
</p>
|
||||
) : (
|
||||
<p id="code-hint" className="field__hint">
|
||||
{t.codeHint}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button type="submit" className="btn btn--primary btn--block" disabled={busy || !code.trim()}>
|
||||
{busy ? t.submitting : t.submit}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="divider" role="separator">
|
||||
<span>{t.or}</span>
|
||||
<form className="stack stack--sm" onSubmit={submit} noValidate>
|
||||
<label htmlFor="code" className="visually-hidden">
|
||||
{t.codeLabel}
|
||||
</label>
|
||||
<input
|
||||
id="code"
|
||||
className={error ? "code-input is-error" : "code-input"}
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder={t.codePlaceholder}
|
||||
autoComplete="one-time-code"
|
||||
autoCapitalize="characters"
|
||||
spellCheck={false}
|
||||
maxLength={32}
|
||||
aria-invalid={error ? true : undefined}
|
||||
aria-describedby={error ? "code-error" : undefined}
|
||||
/>
|
||||
{error && (
|
||||
<div id="code-error" role="alert">
|
||||
<Notice tone="danger">{error}</Notice>
|
||||
</div>
|
||||
)}
|
||||
<button type="submit" className="btn btn--primary" disabled={busy || !code.trim()}>
|
||||
{busy ? t.submitting : t.submit}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<h2 className="h3">{t.demoTitle}</h2>
|
||||
<p className="muted">{t.demoLead}</p>
|
||||
{personas === null ? null : personas.length === 0 ? (
|
||||
<p className="muted">{t.demoEmpty}</p>
|
||||
) : (
|
||||
<ul className="list">
|
||||
{personas.map((p) => (
|
||||
<li key={p.token}>
|
||||
<button type="button" className="list-row" onClick={() => demo(p)} disabled={busy}>
|
||||
<span className="avatar" aria-hidden="true">
|
||||
{p.display_name.charAt(0)}
|
||||
</span>
|
||||
<span className="list-row__text">
|
||||
<span className="list-row__title">{p.display_name}</span>
|
||||
<span className="list-row__sub">{t.demoDay(p.therapy_day)}</span>
|
||||
</span>
|
||||
<span className="list-row__action">
|
||||
{t.demoAction}
|
||||
<Icon name="arrowRight" size={18} />
|
||||
</span>
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
<Notice>{t.demoNotice}</Notice>
|
||||
|
||||
<div className="divider" role="separator">
|
||||
<span>{t.or}</span>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<section className="stack stack--sm" aria-labelledby="demo-title">
|
||||
<h2 id="demo-title" className="h3">
|
||||
{t.demoTitle}
|
||||
</h2>
|
||||
<p className="muted">{t.demoLead}</p>
|
||||
{personas?.map((p) => (
|
||||
<button key={p.token} type="button" className="row-card" onClick={() => demo(p)} disabled={busy}>
|
||||
<span className="avatar">{p.display_name.charAt(0)}</span>
|
||||
<span className="row-card__text">
|
||||
<span className="row-card__title">{p.display_name}</span>
|
||||
<span className="row-card__sub">{t.demoDay(p.therapy_day)}</span>
|
||||
</span>
|
||||
<Icon name="chevronRight" />
|
||||
</button>
|
||||
))}
|
||||
</section>
|
||||
</div>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user