diff --git a/src/components/onboarding/OnboardingFlow.tsx b/src/components/onboarding/OnboardingFlow.tsx index 25f54fa..6f01796 100644 --- a/src/components/onboarding/OnboardingFlow.tsx +++ b/src/components/onboarding/OnboardingFlow.tsx @@ -10,11 +10,36 @@ interface OnboardingFlowProps { // ─── Shared layout ─────────────────────────────────────────────────────────── -function Shell({ children }: { children: React.ReactNode }) { +function Shell({ + children, + step, + totalSteps, +}: { + children: React.ReactNode; + step?: number; + totalSteps?: number; +}) { return (
-
VEGA
+ {/* Logo wordmark */} +
VEGA
+ + {/* Step dots — shown only for the linear create→backup→interests path */} + {step !== undefined && totalSteps !== undefined && ( +
+ {Array.from({ length: totalSteps }).map((_, i) => ( + + ))} +
+ )} + {step === undefined &&
} + {children}
@@ -70,7 +95,7 @@ function InterestsStep({ onComplete }: { onComplete: () => void }) { }; return ( - + What are you into? Pick a few topics to get your feed started. You can always change this later. @@ -157,7 +182,7 @@ function CreateStep({ onNext }: { onNext: (signer: NDKPrivateKeySigner) => void }; return ( - + Your identity is ready. We generated a unique key pair for you. Your public key is @@ -215,8 +240,18 @@ function BackupStep({ signer, onComplete }: { signer: NDKPrivateKeySigner; onCom onComplete(); }; + // "Back up later" escape hatch — runs the SAME login as confirming (this is + // what saves the key to the keychain and signs in); it only skips the + // checkbox gate. The key stays retrievable via Settings → Identity. + const handleSkipBackup = async () => { + setSaving(true); + await loginWithNsec(signer.nsec); + setSaving(false); + onComplete(); + }; + return ( - + Save your secret key. Your secret key is the only way to recover your @@ -272,6 +307,16 @@ function BackupStep({ signer, onComplete }: { signer: NDKPrivateKeySigner; onCom > {saving ? "Setting up…" : "Start using Vega"} + +

+ You can reveal your key any time in Settings → Identity. +

); } diff --git a/src/components/shared/SettingsView.tsx b/src/components/shared/SettingsView.tsx index b130111..37db34a 100644 --- a/src/components/shared/SettingsView.tsx +++ b/src/components/shared/SettingsView.tsx @@ -1,6 +1,7 @@ import { useState, useEffect } from "react"; import { save } from "@tauri-apps/plugin-dialog"; import { writeTextFile } from "@tauri-apps/plugin-fs"; +import { invoke } from "@tauri-apps/api/core"; import { useUserStore } from "../../stores/user"; import { useUIStore } from "../../stores/ui"; import { useWoTStore } from "../../stores/wot"; @@ -206,8 +207,18 @@ function WoTSection() { } function IdentitySection() { - const { npub, loggedIn } = useUserStore(); + const { npub, loggedIn, pubkey } = useUserStore(); const [copied, setCopied] = useState(false); + const [nsec, setNsec] = useState(null); + const [revealed, setRevealed] = useState(false); + const [nsecCopied, setNsecCopied] = useState(false); + + // Load the secret key from the OS keychain. Returns null for read-only + // (npub) and remote-signer (bunker) accounts — the nsec row is hidden then. + useEffect(() => { + if (!pubkey) return; + invoke("load_nsec", { pubkey }).then(setNsec).catch(() => setNsec(null)); + }, [pubkey]); if (!loggedIn || !npub) { return ( @@ -225,6 +236,14 @@ function IdentitySection() { }); }; + const handleCopyNsec = () => { + if (!nsec) return; + navigator.clipboard.writeText(nsec).then(() => { + setNsecCopied(true); + setTimeout(() => setNsecCopied(false), 2000); + }); + }; + return (

Identity

@@ -238,6 +257,33 @@ function IdentitySection() {

Your public key. Safe to share.

+ + {nsec && ( + <> +
+ + {revealed ? nsec : "•".repeat(48)} + + + {revealed && ( + + )} +
+

+ Your secret key — the only way to recover this account. Never share it. Back it up somewhere safe. +

+ + )} ); }