Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.6 KiB
TypeScript
67 lines
2.6 KiB
TypeScript
import "./OnboardingResumeCard.css";
|
|
import { Play, Sparkles } from "lucide-react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { getOnboardingResumeStep, ONBOARDING_FLOW_STEPS } from "./model-onboarding-state";
|
|
import { trackOnboardingEvent } from "./onboarding-events";
|
|
|
|
interface OnboardingResumeCardProps {
|
|
/** Called when the user clicks "Continue onboarding" */
|
|
onResume: () => void;
|
|
}
|
|
|
|
/**
|
|
* A banner/card that appears when a user has previously started onboarding
|
|
* but dismissed the modal without completing. It allows them to resume
|
|
* from where they left off.
|
|
*/
|
|
export function OnboardingResumeCard({ onResume }: OnboardingResumeCardProps) {
|
|
const { t } = useTranslation("app");
|
|
const resumeStep = getOnboardingResumeStep();
|
|
|
|
// Should not render if no resumable state exists
|
|
if (!resumeStep) {
|
|
return null;
|
|
}
|
|
|
|
const completedCount = resumeStep.completedSteps.length;
|
|
const totalSteps = ONBOARDING_FLOW_STEPS.length;
|
|
const progressText = completedCount > 0
|
|
? t("onboarding.progressText", "{{completed}} of {{total}} step{{pluralS}} complete — You're on the ", { completed: completedCount, total: totalSteps, pluralS: completedCount !== 1 ? "s" : "" })
|
|
: t("onboarding.onTheStep", "You're on the ");
|
|
|
|
return (
|
|
<section
|
|
className="onboarding-resume-card"
|
|
role="region"
|
|
aria-label={t("onboarding.resumeOnboarding", "Resume onboarding")}
|
|
>
|
|
<div className="onboarding-resume-card__main">
|
|
<div className="onboarding-resume-card__icon" aria-hidden="true">
|
|
<Sparkles size={20} />
|
|
</div>
|
|
<div className="onboarding-resume-card__content">
|
|
<h2 className="onboarding-resume-card__title">{t("onboarding.continueSetup", "Continue Setup")}</h2>
|
|
<p className="onboarding-resume-card__description">
|
|
{progressText}<strong>{resumeStep.label}</strong> {t("onboarding.stepContinue", "step. Continue where you left off to complete your dashboard setup.")}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="onboarding-resume-card__actions">
|
|
<button
|
|
className="onboarding-resume-card__resume-btn btn btn-primary btn-sm"
|
|
onClick={() => {
|
|
trackOnboardingEvent("onboarding:resumed", {
|
|
source: "resume-card",
|
|
resumedFromStep: resumeStep.currentStep,
|
|
});
|
|
onResume();
|
|
}}
|
|
>
|
|
<Play size={14} aria-hidden="true" />
|
|
<span>{t("onboarding.continueOnboarding", "Continue onboarding")}</span>
|
|
</button>
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|