feat(onboarding): dashboard empty state, getting-started checklist and re-show button (#352) #378

Merged
maximus merged 2 commits from issue-352-dashboard-empty-state into main 2026-10-04 12:51:19 +00:00
Showing only changes of commit 3f3531392e - Show all commits

View file

@ -12,24 +12,25 @@ import {
} from "../../services/onboardingService"; } from "../../services/onboardingService";
import { logError } from "../../services/logService"; import { logError } from "../../services/logService";
// Re-show button of the getting-started checklist (#352). `hidden` = the // Getting-started checklist preference, for its re-show button (#352).
// preference was "1" at mount; `shown` = the button was clicked and the // `dismissed` = the preference was "1" at mount, the button is offered;
// preference removed, so a confirmation line replaces the button. // `restored` = the button was clicked and the preference removed, so a
type ChecklistButtonState = "unknown" | "visible" | "hidden" | "shown"; // confirmation line replaces it; `shown` = nothing to offer.
type ChecklistPreference = "unknown" | "shown" | "dismissed" | "restored";
export default function UsersSettingsPage() { export default function UsersSettingsPage() {
const { t } = useTranslation(); const { t } = useTranslation();
const [checklistButton, setChecklistButton] = useState<ChecklistButtonState>("unknown"); const [checklistPref, setChecklistPref] = useState<ChecklistPreference>("unknown");
useEffect(() => { useEffect(() => {
let cancelled = false; let cancelled = false;
(async () => { (async () => {
try { try {
const value = await getPreference(ONBOARDING_CHECKLIST_HIDDEN_KEY); const value = await getPreference(ONBOARDING_CHECKLIST_HIDDEN_KEY);
if (!cancelled) setChecklistButton(value === "1" ? "hidden" : "visible"); if (!cancelled) setChecklistPref(value === "1" ? "dismissed" : "shown");
} catch (e) { } catch (e) {
// Informational control: without the preference, keep it out of the way. // Informational control: without the preference, keep it out of the way.
if (!cancelled) setChecklistButton("visible"); if (!cancelled) setChecklistPref("shown");
logError("Settings: failed to read the checklist preference", e); logError("Settings: failed to read the checklist preference", e);
} }
})(); })();
@ -41,7 +42,7 @@ export default function UsersSettingsPage() {
const handleShowChecklist = async () => { const handleShowChecklist = async () => {
try { try {
await showChecklist(); await showChecklist();
setChecklistButton("shown"); setChecklistPref("restored");
} catch (e) { } catch (e) {
logError("Settings: failed to show the checklist", e); logError("Settings: failed to show the checklist", e);
} }
@ -77,11 +78,11 @@ export default function UsersSettingsPage() {
<h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--muted-foreground)]"> <h2 className="text-sm font-semibold uppercase tracking-wider text-[var(--muted-foreground)]">
{t("settings.users.sections.userGuide")} {t("settings.users.sections.userGuide")}
</h2> </h2>
{(checklistButton === "hidden" || checklistButton === "shown") && ( {(checklistPref === "dismissed" || checklistPref === "restored") && (
<div className="flex flex-wrap items-center gap-3 rounded-xl border border-[var(--border)] bg-[var(--card)] p-4 text-sm"> <div className="flex flex-wrap items-center gap-3 rounded-xl border border-[var(--border)] bg-[var(--card)] p-4 text-sm">
<ListChecks size={18} className="shrink-0 text-[var(--muted-foreground)]" aria-hidden="true" /> <ListChecks size={18} className="shrink-0 text-[var(--muted-foreground)]" aria-hidden="true" />
<span className="flex-1 min-w-[12rem]">{t("settings.users.onboarding.label")}</span> <span className="flex-1 min-w-[12rem]">{t("settings.users.onboarding.label")}</span>
{checklistButton === "hidden" ? ( {checklistPref === "dismissed" ? (
<button <button
type="button" type="button"
onClick={handleShowChecklist} onClick={handleShowChecklist}