From 91f66f345d950f3eb014a08c2ad9806bb7b7b528 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 4 May 2023 15:18:10 -0500 Subject: [PATCH] backup and restore prompts --- pnpm-lock.yaml | 12 ++++---- src/components/App.tsx | 2 ++ src/components/BalanceBox.tsx | 44 ++++++---------------------- src/components/OnboardWarning.tsx | 48 +++++++++++++++++++++++++++++++ src/components/SeedWords.tsx | 29 ++++++++++++------- src/components/Toaster.tsx | 4 +-- src/components/layout/index.tsx | 4 +++ src/routes/Backup.tsx | 44 ++++++++++++++++++++++++++++ src/state/megaStore.tsx | 36 +++++++++++++++++++---- 9 files changed, 164 insertions(+), 59 deletions(-) create mode 100644 src/components/OnboardWarning.tsx create mode 100644 src/routes/Backup.tsx diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8fc8fde..b7890b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2331,7 +2331,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001482 - electron-to-chromium: 1.4.381 + electron-to-chromium: 1.4.382 node-releases: 2.0.10 update-browserslist-db: 1.0.11(browserslist@4.21.5) @@ -2614,8 +2614,8 @@ packages: jake: 10.8.5 dev: true - /electron-to-chromium@1.4.381: - resolution: {integrity: sha512-jSbS1KRmmGO6SwssmVQpCy1jENfCJT6hN36W6Dxj5HXUj59cUn6yd4gv5113mfATo6aEj/mPUTkMZmNknFXj6Q==} + /electron-to-chromium@1.4.382: + resolution: {integrity: sha512-czMavlW52VIPgutbVL9JnZIZuFijzsG1ww/1z2Otu1r1q+9Qe2bTsH3My3sZarlvwyqHM6+mnZfEnt2Vr4dsIg==} /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -4636,7 +4636,7 @@ packages: terser: 5.17.1 undici: 5.22.0 vite: 4.3.4(@types/node@18.16.3) - vite-plugin-inspect: 0.7.24(rollup@3.21.4)(vite@4.3.4) + vite-plugin-inspect: 0.7.25(rollup@3.21.4)(vite@4.3.4) vite-plugin-solid: 2.7.0(solid-js@1.7.4)(vite@4.3.4) wait-on: 6.0.1(debug@4.3.4) transitivePeerDependencies: @@ -5032,8 +5032,8 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - /vite-plugin-inspect@0.7.24(rollup@3.21.4)(vite@4.3.4): - resolution: {integrity: sha512-XyrhTxYF+5X8CH0PFmYJhs8WGJMOa2UxwUftTaT0FiMm24VfUp+UsAh7xDZI3doPOiB5GxKEizDGxdU98Ay+Vg==} + /vite-plugin-inspect@0.7.25(rollup@3.21.4)(vite@4.3.4): + resolution: {integrity: sha512-11j3hG3stRfFkoI+adIDX+KvZueWNgd9lFGdh7lgm0IjGqpP6luCQAMSSnHHV7AZXaTE06X+bUG3M68diz8ZyA==} engines: {node: '>=14'} peerDependencies: vite: ^3.1.0 || ^4.0.0 diff --git a/src/components/App.tsx b/src/components/App.tsx index 26e6105..778727a 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -6,6 +6,7 @@ import ReloadPrompt from "~/components/Reload"; import { A } from 'solid-start'; import { Activity } from './Activity'; import settings from '~/assets/icons/settings.svg'; +import { OnboardWarning } from './OnboardWarning'; export default function App() { return ( @@ -16,6 +17,7 @@ export default function App() { logo Settings + diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index d0856c8..b0a2fd1 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -17,54 +17,26 @@ function SyncingIndicator() { } export default function BalanceBox() { - const [state, _] = useMegaStore(); - - const fetchOnchainBalance = async () => { - console.log("Refetching onchain balance"); - await state.node_manager?.sync(); - const balance = await state.node_manager?.get_balance(); - return balance - }; - - // TODO: it's hacky to do these separately, but ln doesn't need the sync so I don't want to wait - const fetchLnBalance = async () => { - console.log("Refetching ln balance"); - const balance = await state.node_manager?.get_balance(); - return balance - }; - - const [onChainBalance, { refetch: refetchOnChainBalance }] = createResource(fetchOnchainBalance); - const [lnBalance, { refetch: refetchLnBalance }] = createResource(fetchLnBalance); - - function refetchBalance() { - refetchLnBalance(); - refetchOnChainBalance(); - } + const [state, actions] = useMegaStore(); return ( <> - }> - - - - + - }> - }> -
- -
-
+ }> +
+ +
- +
Unconfirmed Balance
- {prettyPrintAmount(onChainBalance()?.unconfirmed)} SATS + {prettyPrintAmount(state.balance?.unconfirmed)} SATS
diff --git a/src/components/OnboardWarning.tsx b/src/components/OnboardWarning.tsx new file mode 100644 index 0000000..9a70c8d --- /dev/null +++ b/src/components/OnboardWarning.tsx @@ -0,0 +1,48 @@ +import { Show, createSignal, onMount } from "solid-js"; +import { Button, ButtonLink, SmallHeader, VStack } from "./layout"; +import { useMegaStore } from "~/state/megaStore"; + +export function OnboardWarning() { + const [state, actions] = useMegaStore(); + const [dismissedBackup, setDismissedBackup] = createSignal(false); + + onMount(() => { + actions.sync() + }) + + function hasMoney() { + return state.balance?.confirmed || state.balance?.lightning || state.balance?.unconfirmed + } + + return ( + <> + {/* TODO: show this once we have a restore flow */} + +
+ Welcome! + +

+ Do you want to restore an existing Mutiny Wallet? +

+
+ + +
+
+
+
+ +
+ Secure your funds +

+ You have money stored in this browser. Let's make sure you have a backup. +

+
+ Backup + +
+
+
+ + ) +} \ No newline at end of file diff --git a/src/components/SeedWords.tsx b/src/components/SeedWords.tsx index 8c70b43..1df63e0 100644 --- a/src/components/SeedWords.tsx +++ b/src/components/SeedWords.tsx @@ -1,27 +1,36 @@ -import { Match, Switch, createSignal } from "solid-js" +import { For, Match, Switch, createMemo, createSignal } from "solid-js" -export function SeedWords(props: { words: string }) { +export function SeedWords(props: { words: string, setHasSeen?: (hasSeen: boolean) => void }) { const [shouldShow, setShouldShow] = createSignal(false) function toggleShow() { setShouldShow(!shouldShow()) + if (shouldShow()) { + props.setHasSeen?.(true) + } } - return (
+    const splitWords = createMemo(() => props.words.split(" "))
+
+    return (
) + ) } \ No newline at end of file diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index 3383d69..4cca368 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -31,7 +31,7 @@ export function ToastItem(props: { toastId: number, title: string, description: return (
-
+
{props.title} @@ -43,7 +43,7 @@ export function ToastItem(props: { toastId: number, title: string, description:

- + Close
diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx index 9afa276..6044e18 100644 --- a/src/components/layout/index.tsx +++ b/src/components/layout/index.tsx @@ -103,6 +103,10 @@ const SmallAmount: ParentComponent<{ amount: number | bigint }> = (props) => { return (

{props.amount.toLocaleString()} SATS

) } +export const NiceP: ParentComponent = (props) => { + return (

{props.children}

) +} + export { SmallHeader, Card, diff --git a/src/routes/Backup.tsx b/src/routes/Backup.tsx new file mode 100644 index 0000000..813a002 --- /dev/null +++ b/src/routes/Backup.tsx @@ -0,0 +1,44 @@ +import { Button, DefaultMain, LargeHeader, NiceP, NodeManagerGuard, SafeArea, VStack } from "~/components/layout"; +import NavBar from "~/components/NavBar"; +import { useNavigate } from 'solid-start'; +import { BackButton } from '~/components/layout/BackButton'; +import { SeedWords } from '~/components/SeedWords'; +import { useMegaStore } from '~/state/megaStore'; +import { Show, createSignal } from 'solid-js'; + +export default function App() { + const [store, actions] = useMegaStore(); + const navigate = useNavigate(); + + const [hasSeenBackup, setHasSeenBackup] = createSignal(false); + + function wroteDownTheWords() { + actions.setHasBackedUp() + navigate("/") + } + + return ( + + + + + Backup + + Let's get these funds secured. + We'll show you 12 words. You write down the 12 words. + + If you clear your browser history, or lose your device, these 12 words are the only way you can restore your wallet. + + Mutiny is self-custodial. It's all up to you... + + + You are responsible for your funds! + + + + + + + + ); +} diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 8530c07..7b2c43c 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -1,5 +1,6 @@ -// Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js +/* @refresh reload */ +// Inspired by https://github.com/solidjs/solid-realworld/blob/main/src/store/index.js import { ParentComponent, createContext, createEffect, onCleanup, onMount, useContext } from "solid-js"; import { createStore } from "solid-js/store"; import { NodeManagerSettingStrings, setupNodeManager } from "~/logic/nodeManagerSetup"; @@ -16,14 +17,19 @@ export type MegaStore = [{ user_status: UserStatus; scan_result?: ParsedParams; balance?: MutinyBalance; + is_syncing?: boolean; last_sync?: number; price: number + has_backed_up: boolean, + dismissed_restore_prompt: boolean }, { fetchUserStatus(): Promise; setupNodeManager(settings?: NodeManagerSettingStrings): Promise; setWaitlistId(waitlist_id: string): void; setScanResult(scan_result: ParsedParams | undefined): void; sync(): Promise; + dismissRestorePrompt(): void; + setHasBackedUp(): void; }]; export const Provider: ParentComponent = (props) => { @@ -33,7 +39,12 @@ export const Provider: ParentComponent = (props) => { user_status: undefined as UserStatus, scan_result: undefined as ParsedParams | undefined, // TODO: wire this up to real price once we have caching - price: 30000 + price: 30000, + has_backed_up: localStorage.getItem("has_backed_up") === "true", + balance: undefined as MutinyBalance | undefined, + last_sync: undefined as number | undefined, + is_syncing: false, + dismissed_restore_prompt: localStorage.getItem("dismissed_restore_prompt") === "true" }); const actions = { @@ -69,14 +80,29 @@ export const Provider: ParentComponent = (props) => { async sync(): Promise { console.time("BDK Sync Time") try { - await state.node_manager?.sync() + if (state.node_manager && !state.is_syncing) { + setState({ is_syncing: true }) + await state.node_manager?.sync() + const balance = await state.node_manager?.get_balance(); + setState({ balance, last_sync: Date.now() }) + } } catch (e) { console.error(e); + } finally { + setState({ is_syncing: false }) } console.timeEnd("BDK Sync Time") }, setScanResult(scan_result: ParsedParams) { setState({ scan_result }) + }, + setHasBackedUp() { + localStorage.setItem("has_backed_up", "true") + setState({ has_backed_up: true }) + }, + dismissRestorePrompt() { + localStorage.setItem("dismissed_restore_prompt", "true") + setState({ dismissed_restore_prompt: true }) } }; @@ -101,8 +127,8 @@ export const Provider: ParentComponent = (props) => { }); createEffect(() => { - const interval = setInterval(() => { - if (state.node_manager) actions.sync(); + const interval = setInterval(async () => { + await actions.sync(); }, 60 * 1000); // Poll every minute onCleanup(() => {