Simpl-Resultat/src/components/dashboard/DashboardEmptyState.tsx
le king fu 64a07f359f feat(onboarding): visit steps, eight rows, "Got it" and one-time initialization
Two visit steps join the getting-started path: "Browse categories and
keywords" (5th) and "Browse the reports" (8th). Nothing in the database
counts them: they are done by "Got it" on their card, stored as
onboarding_acknowledged_<key> in user_preferences (no migration).

- computeChecklist: eight rows; the visit rows are locked until the
  profile's visit steps are initialized, so a finished path does not
  flash a "Next step" before they are marked done.
- initializeVisitSteps: once per profile, after the first successful
  load with the Budget entitlement known; acknowledges both steps when
  every other step is done or skipped, then sets the flag. No
  transaction (D16): idempotent writes, the flag last.
- Reducer: ACKNOWLEDGE wins over an older snapshot like SKIP but keeps
  the card latch, so the card says "Done." and offers the next step.
- StepCard: "Got it" before "Skip this step" when onAcknowledge is
  given; OnboardingStepCard passes it for a visit step.
- Banner counter, card rank and end of path wait for
  budgetReady && visitStepsInitialized; the list shows the visit rows
  "pending" until then.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:32:20 -04:00

56 lines
1.8 KiB
TypeScript

import { useTranslation } from "react-i18next";
import { LayoutDashboard } from "lucide-react";
import EmptyState from "../shared/EmptyState";
import OnboardingChecklist from "./OnboardingChecklist";
import type { Checklist } from "../../utils/onboardingChecklist";
import type { ChecklistItemKey } from "../../shared/types";
interface DashboardEmptyStateProps {
checklist: Checklist;
budgetReady: boolean;
/** The visit steps are initialized (#425): their rows stay "pending" until then. */
visitStepsInitialized: boolean;
/** The user dismissed the checklist: the welcome card stays, the list goes. */
hidden: boolean;
onHide: () => void;
/** "Resume" on a skipped row of the list (#397). */
onResume: (key: ChecklistItemKey) => void;
}
/**
* Dashboard of a profile without any transaction (#352): a welcome card with
* the detailed getting-started checklist and one primary action towards the
* import page, in place of widgets showing 0 $. The getting-started banner
* stays off this page (D11): the card already holds the list.
*/
export default function DashboardEmptyState({
checklist,
budgetReady,
visitStepsInitialized,
hidden,
onHide,
onResume,
}: DashboardEmptyStateProps) {
const { t } = useTranslation();
return (
<EmptyState
icon={<LayoutDashboard size={40} />}
title={t("dashboard.empty.title")}
description={t("dashboard.empty.description")}
primaryAction={{ label: t("dashboard.empty.cta"), to: "/import" }}
>
{!hidden && (
<OnboardingChecklist
items={checklist.items}
done={checklist.done}
total={checklist.total}
budgetReady={budgetReady}
visitStepsInitialized={visitStepsInitialized}
onHide={onHide}
onResume={onResume}
/>
)}
</EmptyState>
);
}