diff --git a/kez-chat/web/src/App.svelte b/kez-chat/web/src/App.svelte index 736b89a..d5c1a5b 100644 --- a/kez-chat/web/src/App.svelte +++ b/kez-chat/web/src/App.svelte @@ -10,6 +10,7 @@ import CreateAccount from "./routes/CreateAccount.svelte"; import Restore from "./routes/Restore.svelte"; import Unlock from "./routes/Unlock.svelte"; + import Welcome from "./routes/Welcome.svelte"; import Identity from "./routes/Identity.svelte"; import Claims from "./routes/Claims.svelte"; import AddClaim from "./routes/AddClaim.svelte"; @@ -21,6 +22,7 @@ "/create": CreateAccount, "/restore": Restore, "/unlock": Unlock, + "/welcome": Welcome, "/chats": Messages, "/identity": Identity, "/claims": Claims, @@ -29,7 +31,7 @@ }; // App routes show the nav chrome; everything else (auth flow) is full-bleed. - const APP_ROUTES = ["/chats", "/identity", "/claims", "/claims/add", "/settings"]; + const APP_ROUTES = ["/welcome", "/chats", "/identity", "/claims", "/claims/add", "/settings"]; const showNav = $derived(!!session.unlocked && APP_ROUTES.includes($location)); onMount(async () => { diff --git a/kez-chat/web/src/lib/onboarding.svelte.ts b/kez-chat/web/src/lib/onboarding.svelte.ts new file mode 100644 index 0000000..4e0f551 --- /dev/null +++ b/kez-chat/web/src/lib/onboarding.svelte.ts @@ -0,0 +1,48 @@ +// First-run onboarding state. Two persisted flags: +// • onboarded — user finished/skipped the Getting Started checklist +// • seedAcked — user confirmed they backed up their recovery seed +// Both in localStorage; exposed as Svelte 5 $state so the "finish setup" +// nudge reactively disappears. Everything else in the checklist derives +// from real app state (claims, biometric, notification permission). + +const ONBOARDED = "kez-chat:onboarded"; +const SEED_ACKED = "kez-chat:seed_acked"; + +function read(key: string): boolean { + try { + return localStorage.getItem(key) === "1"; + } catch { + return false; + } +} +function write(key: string, val: boolean) { + try { + if (val) localStorage.setItem(key, "1"); + else localStorage.removeItem(key); + } catch { + /* private mode — fine */ + } +} + +class Onboarding { + onboarded = $state(read(ONBOARDED)); + seedAcked = $state(read(SEED_ACKED)); + + finish() { + this.onboarded = true; + write(ONBOARDED, true); + } + + ackSeed() { + this.seedAcked = true; + write(SEED_ACKED, true); + } + + /** Re-open the checklist (e.g. from Settings → Getting started). */ + reopen() { + this.onboarded = false; + write(ONBOARDED, false); + } +} + +export const onboarding = new Onboarding(); diff --git a/kez-chat/web/src/routes/Messages.svelte b/kez-chat/web/src/routes/Messages.svelte index fd0c2d3..2d80e95 100644 --- a/kez-chat/web/src/routes/Messages.svelte +++ b/kez-chat/web/src/routes/Messages.svelte @@ -6,6 +6,7 @@ import { lookup, lookupByPrimary, ApiError } from "../lib/api.js"; import { inboxService } from "../lib/inbox-service.svelte.js"; import { verifySubject } from "../lib/verify.js"; + import { onboarding } from "../lib/onboarding.svelte.js"; import EmojiButton from "../lib/EmojiButton.svelte"; import Avatar from "../lib/Avatar.svelte"; import VerifiedBadge from "../lib/VerifiedBadge.svelte"; @@ -318,6 +319,13 @@ {/if} + + {#if !onboarding.onboarded} + + ✦Finish setting up your account→ + + {/if} +