Files
MobilityOps-Public/frontend/src/components/ArchitectureFlow.tsx
T

105 lines
4.5 KiB
TypeScript

import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Icon, type IconName } from "./Icons";
import "../styles-architecture-flow.css";
type ArchitectureStepId = "Frontend" | "Api" | "Database" | "Outbox" | "External";
type ArchitectureZone = "intent" | "transaction" | "edge";
const ARCHITECTURE_STEPS: Array<{
id: ArchitectureStepId;
icon: IconName;
zone: ArchitectureZone;
}> = [
{ id: "Frontend", icon: "user", zone: "intent" },
{ id: "Api", icon: "shield", zone: "transaction" },
{ id: "Database", icon: "audit", zone: "transaction" },
{ id: "Outbox", icon: "activity", zone: "transaction" },
{ id: "External", icon: "integrations", zone: "edge" },
];
const ZONE_TRANSLATION_KEYS: Record<ArchitectureZone, string> = {
intent: "architectureZoneIntent",
transaction: "architectureZoneTransaction",
edge: "architectureZoneEdge",
};
export function ArchitectureFlow() {
const { t } = useTranslation("demo");
const [activeStepId, setActiveStepId] = useState<ArchitectureStepId>("Database");
const activeIndex = ARCHITECTURE_STEPS.findIndex((step) => step.id === activeStepId);
const activeStep = ARCHITECTURE_STEPS[activeIndex];
return (
<div className="architecture-explorer">
<div className="architecture-zones" aria-label={t("about.architectureZonesLabel")}>
<span className="zone-intent"><Icon name="user" /> {t("about.architectureZoneIntent")}</span>
<span className="zone-transaction"><Icon name="shield" /> {t("about.architectureZoneTransaction")}</span>
<span className="zone-edge"><Icon name="integrations" /> {t("about.architectureZoneEdge")}</span>
</div>
<ol className="architecture-flow" aria-label={t("about.architectureFlowLabel")}>
{ARCHITECTURE_STEPS.map((step, index) => {
const isActive = step.id === activeStepId;
const isCommitBoundary = step.id === "Outbox";
return (
<li
className={`architecture-step architecture-step-${step.zone}${isActive ? " is-active" : ""}`}
key={step.id}
>
<button
type="button"
aria-pressed={isActive}
aria-controls="architecture-active-detail"
onClick={() => setActiveStepId(step.id)}
>
<span className="architecture-step-top">
<span className="architecture-step-number">0{index + 1}</span>
<span className="architecture-step-icon"><Icon name={step.icon} /></span>
</span>
<strong>{t(`about.architecture${step.id}`)}</strong>
<small>{t(`about.architectureDetails.${step.id}.summary`)}</small>
<span className="architecture-step-meta">
<span className="architecture-zone-tag">{t(`about.${ZONE_TRANSLATION_KEYS[step.zone]}`)}</span>
{isCommitBoundary ? <span className="architecture-commit-note">→ {t("about.architectureCommitBoundary")}</span> : null}
</span>
</button>
{index < ARCHITECTURE_STEPS.length - 1 ? (
<span className={`architecture-connector${isCommitBoundary ? " is-commit-boundary" : ""}`} aria-hidden="true">
<span className="architecture-signal" />
</span>
) : null}
</li>
);
})}
</ol>
<p className="architecture-interaction-hint">{t("about.architectureInteractionHint")}</p>
<section
className={`architecture-active-detail architecture-active-detail-${activeStep.zone}`}
id="architecture-active-detail"
aria-live="polite"
>
<div className="architecture-active-copy">
<span>{t("about.architectureSelectedStep", { current: activeIndex + 1, total: ARCHITECTURE_STEPS.length })}</span>
<h3>{t(`about.architecture${activeStep.id}`)}</h3>
<p>{t(`about.architectureDetails.${activeStep.id}.body`)}</p>
</div>
<dl className="architecture-active-evidence">
<div>
<dt>{t("about.architectureBoundaryLabel")}</dt>
<dd>{t(`about.${ZONE_TRANSLATION_KEYS[activeStep.zone]}`)}</dd>
</div>
<div>
<dt>{t("about.architectureEvidenceLabel")}</dt>
<dd>
<span>{t(`about.architectureDetails.${activeStep.id}.proofOne`)}</span>
<span>{t(`about.architectureDetails.${activeStep.id}.proofTwo`)}</span>
</dd>
</div>
</dl>
</section>
</div>
);
}