mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-19 15:24:25 +01:00
add load bar home and emergency kit
This commit is contained in:
@@ -15,6 +15,7 @@ import plusLogo from "~/assets/mutiny-plus-logo.png";
|
|||||||
import { PendingNwc } from "./PendingNwc";
|
import { PendingNwc } from "./PendingNwc";
|
||||||
import { useI18n } from "~/i18n/context";
|
import { useI18n } from "~/i18n/context";
|
||||||
import { DecryptDialog } from "./DecryptDialog";
|
import { DecryptDialog } from "./DecryptDialog";
|
||||||
|
import { LoadingIndicator } from "./LoadingIndicator";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
@@ -23,6 +24,7 @@ export default function App() {
|
|||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
|
<LoadingIndicator />
|
||||||
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|||||||
67
src/components/LoadingIndicator.tsx
Normal file
67
src/components/LoadingIndicator.tsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { Progress } from "@kobalte/core";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
|
||||||
|
export function LoadingBar(props: { value: number; max: number }) {
|
||||||
|
function valueToStage(value: number) {
|
||||||
|
switch (value) {
|
||||||
|
case 0:
|
||||||
|
return "Just getting started";
|
||||||
|
case 1:
|
||||||
|
return "Checking user status";
|
||||||
|
case 2:
|
||||||
|
return "Double checking something";
|
||||||
|
case 3:
|
||||||
|
return "Downloading";
|
||||||
|
case 4:
|
||||||
|
return "Setup";
|
||||||
|
case 5:
|
||||||
|
return "Done";
|
||||||
|
default:
|
||||||
|
return "Just getting started";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Progress.Root
|
||||||
|
value={props.value}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={props.max}
|
||||||
|
getValueLabel={({ value }) => `Loading: ${valueToStage(value)}`}
|
||||||
|
class="w-full flex flex-col gap-2"
|
||||||
|
>
|
||||||
|
<Progress.ValueLabel class="text-sm text-m-grey-400" />
|
||||||
|
<Progress.Track class="h-6 bg-white/10 rounded">
|
||||||
|
<Progress.Fill class="bg-m-blue rounded h-full w-[var(--kb-progress-fill-width)] transition-[width]" />
|
||||||
|
</Progress.Track>
|
||||||
|
</Progress.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LoadingIndicator() {
|
||||||
|
const [state, _actions] = useMegaStore();
|
||||||
|
|
||||||
|
const loadStageValue = () => {
|
||||||
|
switch (state.load_stage) {
|
||||||
|
case "fresh":
|
||||||
|
return 0;
|
||||||
|
case "checking_user":
|
||||||
|
return 1;
|
||||||
|
case "checking_double_init":
|
||||||
|
return 2;
|
||||||
|
case "downloading":
|
||||||
|
return 3;
|
||||||
|
case "setup":
|
||||||
|
return 4;
|
||||||
|
case "done":
|
||||||
|
return 5;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Show when={state.load_stage !== "done"}>
|
||||||
|
<LoadingBar value={loadStageValue()} max={5} />
|
||||||
|
</Show>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
/* @refresh reload */
|
/* @refresh reload */
|
||||||
|
|
||||||
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||||
import initWaila from "@mutinywallet/waila-wasm";
|
|
||||||
|
|
||||||
export type Network = "bitcoin" | "testnet" | "regtest" | "signet";
|
export type Network = "bitcoin" | "testnet" | "regtest" | "signet";
|
||||||
export type MutinyWalletSettingStrings = {
|
export type MutinyWalletSettingStrings = {
|
||||||
@@ -105,10 +104,8 @@ export async function checkForWasm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function setupMutinyWallet(
|
export async function doubleInitDefense() {
|
||||||
settings?: MutinyWalletSettingStrings,
|
console.log("Starting init...");
|
||||||
password?: string
|
|
||||||
): Promise<MutinyWallet> {
|
|
||||||
// Ultimate defense against getting multiple instances of the wallet running.
|
// Ultimate defense against getting multiple instances of the wallet running.
|
||||||
// If we detect that the wallet has already been initialized in this session, we'll reload the page.
|
// If we detect that the wallet has already been initialized in this session, we'll reload the page.
|
||||||
// A successful stop of the wallet in onCleanup will clear this flag
|
// A successful stop of the wallet in onCleanup will clear this flag
|
||||||
@@ -121,11 +118,17 @@ export async function setupMutinyWallet(
|
|||||||
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
|
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initializeWasm() {
|
||||||
|
// Actually intialize the WASM, this should be the first thing that requires the WASM blob to be downloaded
|
||||||
await initMutinyWallet();
|
await initMutinyWallet();
|
||||||
// Might as well init waila while we're at it
|
}
|
||||||
await initWaila();
|
|
||||||
|
|
||||||
|
export async function setupMutinyWallet(
|
||||||
|
settings?: MutinyWalletSettingStrings,
|
||||||
|
password?: string
|
||||||
|
): Promise<MutinyWallet> {
|
||||||
console.log("Starting setup...");
|
console.log("Starting setup...");
|
||||||
const { network, proxy, esplora, rgs, lsp, auth, subscriptions } =
|
const { network, proxy, esplora, rgs, lsp, auth, subscriptions } =
|
||||||
await setAndGetMutinySettings(settings);
|
await setAndGetMutinySettings(settings);
|
||||||
|
|||||||
58
src/logic/waila.ts
Normal file
58
src/logic/waila.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import initWaila, { PaymentParams } from "@mutinywallet/waila-wasm";
|
||||||
|
import { Result } from "~/utils/typescript";
|
||||||
|
|
||||||
|
// Make sure we've initialzied waila before we try to use it
|
||||||
|
await initWaila();
|
||||||
|
|
||||||
|
export type ParsedParams = {
|
||||||
|
address?: string;
|
||||||
|
invoice?: string;
|
||||||
|
amount_sats?: bigint;
|
||||||
|
network?: string;
|
||||||
|
memo?: string;
|
||||||
|
node_pubkey?: string;
|
||||||
|
lnurl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function toParsedParams(
|
||||||
|
str: string,
|
||||||
|
ourNetwork: string
|
||||||
|
): Result<ParsedParams> {
|
||||||
|
let params;
|
||||||
|
try {
|
||||||
|
params = new PaymentParams(str || "");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return { ok: false, error: new Error("Invalid payment request") };
|
||||||
|
}
|
||||||
|
|
||||||
|
// If WAILA doesn't return a network we should default to our own
|
||||||
|
// If the networks is testnet and we're on signet we should use signet
|
||||||
|
const network = !params.network
|
||||||
|
? ourNetwork
|
||||||
|
: params.network === "testnet" && ourNetwork === "signet"
|
||||||
|
? "signet"
|
||||||
|
: params.network;
|
||||||
|
|
||||||
|
if (network !== ourNetwork) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: new Error(
|
||||||
|
`Destination is for ${params.network} but you're on ${ourNetwork}`
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
value: {
|
||||||
|
address: params.address,
|
||||||
|
invoice: params.invoice,
|
||||||
|
amount_sats: params.amount_sats,
|
||||||
|
network,
|
||||||
|
memo: params.memo,
|
||||||
|
node_pubkey: params.node_pubkey,
|
||||||
|
lnurl: params.lnurl
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,63 +2,9 @@ import Reader from "~/components/Reader";
|
|||||||
import { createEffect, createSignal } from "solid-js";
|
import { createEffect, createSignal } from "solid-js";
|
||||||
import { useNavigate } from "solid-start";
|
import { useNavigate } from "solid-start";
|
||||||
import { Button } from "~/components/layout";
|
import { Button } from "~/components/layout";
|
||||||
import { PaymentParams } from "@mutinywallet/waila-wasm";
|
|
||||||
import { showToast } from "~/components/Toaster";
|
import { showToast } from "~/components/Toaster";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { Result } from "~/utils/typescript";
|
import { toParsedParams } from "~/logic/waila";
|
||||||
|
|
||||||
export type ParsedParams = {
|
|
||||||
address?: string;
|
|
||||||
invoice?: string;
|
|
||||||
amount_sats?: bigint;
|
|
||||||
network?: string;
|
|
||||||
memo?: string;
|
|
||||||
node_pubkey?: string;
|
|
||||||
lnurl?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function toParsedParams(
|
|
||||||
str: string,
|
|
||||||
ourNetwork: string
|
|
||||||
): Result<ParsedParams> {
|
|
||||||
let params;
|
|
||||||
try {
|
|
||||||
params = new PaymentParams(str || "");
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
return { ok: false, error: new Error("Invalid payment request") };
|
|
||||||
}
|
|
||||||
|
|
||||||
// If WAILA doesn't return a network we should default to our own
|
|
||||||
// If the networks is testnet and we're on signet we should use signet
|
|
||||||
const network = !params.network
|
|
||||||
? ourNetwork
|
|
||||||
: params.network === "testnet" && ourNetwork === "signet"
|
|
||||||
? "signet"
|
|
||||||
: params.network;
|
|
||||||
|
|
||||||
if (network !== ourNetwork) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: new Error(
|
|
||||||
`Destination is for ${params.network} but you're on ${ourNetwork}`
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
value: {
|
|
||||||
address: params.address,
|
|
||||||
invoice: params.invoice,
|
|
||||||
amount_sats: params.amount_sats,
|
|
||||||
network,
|
|
||||||
memo: params.memo,
|
|
||||||
node_pubkey: params.node_pubkey,
|
|
||||||
lnurl: params.lnurl
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Scanner() {
|
export default function Scanner() {
|
||||||
const [state, actions] = useMegaStore();
|
const [state, actions] = useMegaStore();
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import { Scan } from "~/assets/svg/Scan";
|
|||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { Contact, MutinyInvoice } from "@mutinywallet/mutiny-wasm";
|
import { Contact, MutinyInvoice } from "@mutinywallet/mutiny-wasm";
|
||||||
import { StyledRadioGroup } from "~/components/layout/Radio";
|
import { StyledRadioGroup } from "~/components/layout/Radio";
|
||||||
import { ParsedParams, toParsedParams } from "./Scanner";
|
|
||||||
import { showToast } from "~/components/Toaster";
|
import { showToast } from "~/components/Toaster";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
import megacheck from "~/assets/icons/megacheck.png";
|
import megacheck from "~/assets/icons/megacheck.png";
|
||||||
@@ -44,6 +43,7 @@ import { SuccessModal } from "~/components/successfail/SuccessModal";
|
|||||||
import { ExternalLink } from "~/components/layout/ExternalLink";
|
import { ExternalLink } from "~/components/layout/ExternalLink";
|
||||||
import { InfoBox } from "~/components/InfoBox";
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
import { useI18n } from "~/i18n/context";
|
import { useI18n } from "~/i18n/context";
|
||||||
|
import { ParsedParams, toParsedParams } from "~/logic/waila";
|
||||||
|
|
||||||
export type SendSource = "lightning" | "onchain";
|
export type SendSource = "lightning" | "onchain";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { DeleteEverything } from "~/components/DeleteEverything";
|
import { DeleteEverything } from "~/components/DeleteEverything";
|
||||||
import { ImportExport } from "~/components/ImportExport";
|
import { ImportExport } from "~/components/ImportExport";
|
||||||
|
import { LoadingIndicator } from "~/components/LoadingIndicator";
|
||||||
import { Logs } from "~/components/Logs";
|
import { Logs } from "~/components/Logs";
|
||||||
import NavBar from "~/components/NavBar";
|
import NavBar from "~/components/NavBar";
|
||||||
import {
|
import {
|
||||||
@@ -13,6 +14,19 @@ import {
|
|||||||
import { BackLink } from "~/components/layout/BackLink";
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
import { ExternalLink } from "~/components/layout/ExternalLink";
|
import { ExternalLink } from "~/components/layout/ExternalLink";
|
||||||
|
|
||||||
|
function EmergencyStack() {
|
||||||
|
return (
|
||||||
|
<VStack>
|
||||||
|
<ImportExport emergency />
|
||||||
|
<Logs />
|
||||||
|
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
||||||
|
<SmallHeader>Danger zone</SmallHeader>
|
||||||
|
<DeleteEverything emergency />
|
||||||
|
</div>
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function EmergencyKit() {
|
export default function EmergencyKit() {
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
@@ -20,6 +34,7 @@ export default function EmergencyKit() {
|
|||||||
<BackLink href="/settings" title="Settings" />
|
<BackLink href="/settings" title="Settings" />
|
||||||
<LargeHeader>Emergency Kit</LargeHeader>
|
<LargeHeader>Emergency Kit</LargeHeader>
|
||||||
<VStack>
|
<VStack>
|
||||||
|
<LoadingIndicator />
|
||||||
<NiceP>
|
<NiceP>
|
||||||
If your wallet seems broken, here are some tools to try
|
If your wallet seems broken, here are some tools to try
|
||||||
to debug and repair it.
|
to debug and repair it.
|
||||||
@@ -31,12 +46,7 @@ export default function EmergencyKit() {
|
|||||||
reach out to us for support.
|
reach out to us for support.
|
||||||
</ExternalLink>
|
</ExternalLink>
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<ImportExport emergency />
|
<EmergencyStack />
|
||||||
<Logs />
|
|
||||||
<div class="rounded-xl p-4 flex flex-col gap-2 bg-m-red overflow-x-hidden">
|
|
||||||
<SmallHeader>Danger zone</SmallHeader>
|
|
||||||
<DeleteEverything emergency />
|
|
||||||
</div>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
<NavBar activeTab="settings" />
|
<NavBar activeTab="settings" />
|
||||||
|
|||||||
@@ -12,20 +12,30 @@ import {
|
|||||||
import { createStore, reconcile } from "solid-js/store";
|
import { createStore, reconcile } from "solid-js/store";
|
||||||
import {
|
import {
|
||||||
MutinyWalletSettingStrings,
|
MutinyWalletSettingStrings,
|
||||||
|
doubleInitDefense,
|
||||||
|
initializeWasm,
|
||||||
setupMutinyWallet
|
setupMutinyWallet
|
||||||
} from "~/logic/mutinyWalletSetup";
|
} from "~/logic/mutinyWalletSetup";
|
||||||
import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||||
import { ParsedParams } from "~/routes/Scanner";
|
|
||||||
import { MutinyTagItem } from "~/utils/tags";
|
import { MutinyTagItem } from "~/utils/tags";
|
||||||
import { checkBrowserCompatibility } from "~/logic/browserCompatibility";
|
import { checkBrowserCompatibility } from "~/logic/browserCompatibility";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
import { timeout } from "~/utils/timeout";
|
import { timeout } from "~/utils/timeout";
|
||||||
import { ActivityItem } from "~/components/Activity";
|
import { ActivityItem } from "~/components/Activity";
|
||||||
|
import { ParsedParams } from "~/logic/waila";
|
||||||
|
|
||||||
const MegaStoreContext = createContext<MegaStore>();
|
const MegaStoreContext = createContext<MegaStore>();
|
||||||
|
|
||||||
type UserStatus = undefined | "new_here" | "waitlisted" | "approved";
|
type UserStatus = undefined | "new_here" | "waitlisted" | "approved";
|
||||||
|
|
||||||
|
export type LoadStage =
|
||||||
|
| "fresh"
|
||||||
|
| "checking_user"
|
||||||
|
| "checking_double_init"
|
||||||
|
| "downloading"
|
||||||
|
| "setup"
|
||||||
|
| "done";
|
||||||
|
|
||||||
export type MegaStore = [
|
export type MegaStore = [
|
||||||
{
|
{
|
||||||
already_approved?: boolean;
|
already_approved?: boolean;
|
||||||
@@ -48,6 +58,7 @@ export type MegaStore = [
|
|||||||
subscription_timestamp?: number;
|
subscription_timestamp?: number;
|
||||||
readonly mutiny_plus: boolean;
|
readonly mutiny_plus: boolean;
|
||||||
needs_password: boolean;
|
needs_password: boolean;
|
||||||
|
load_stage: LoadStage;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fetchUserStatus(): Promise<UserStatus>;
|
fetchUserStatus(): Promise<UserStatus>;
|
||||||
@@ -100,7 +111,8 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
return false;
|
return false;
|
||||||
else return true;
|
else return true;
|
||||||
},
|
},
|
||||||
needs_password: false
|
needs_password: false,
|
||||||
|
load_stage: "fresh" as LoadStage
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -175,7 +187,15 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
throw state.setup_error;
|
throw state.setup_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ wallet_loading: true });
|
setState({
|
||||||
|
wallet_loading: true,
|
||||||
|
load_stage: "checking_double_init"
|
||||||
|
});
|
||||||
|
|
||||||
|
await doubleInitDefense();
|
||||||
|
setState({ load_stage: "downloading" });
|
||||||
|
await initializeWasm();
|
||||||
|
setState({ load_stage: "setup" });
|
||||||
|
|
||||||
const mutinyWallet = await setupMutinyWallet(
|
const mutinyWallet = await setupMutinyWallet(
|
||||||
settings,
|
settings,
|
||||||
@@ -185,9 +205,6 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
// If we get this far then we don't need the password anymore
|
// If we get this far then we don't need the password anymore
|
||||||
setState({ needs_password: false });
|
setState({ needs_password: false });
|
||||||
|
|
||||||
// Get balance optimistically
|
|
||||||
const balance = await mutinyWallet.get_balance();
|
|
||||||
|
|
||||||
// Subscription stuff. Skip if it's not already in localstorage
|
// Subscription stuff. Skip if it's not already in localstorage
|
||||||
let subscription_timestamp = undefined;
|
let subscription_timestamp = undefined;
|
||||||
const stored_subscription_timestamp = localStorage.getItem(
|
const stored_subscription_timestamp = localStorage.getItem(
|
||||||
@@ -214,10 +231,14 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get balance optimistically
|
||||||
|
const balance = await mutinyWallet.get_balance();
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
mutiny_wallet: mutinyWallet,
|
mutiny_wallet: mutinyWallet,
|
||||||
wallet_loading: false,
|
wallet_loading: false,
|
||||||
subscription_timestamp: subscription_timestamp,
|
subscription_timestamp: subscription_timestamp,
|
||||||
|
load_stage: "done",
|
||||||
balance
|
balance
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -318,6 +339,7 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
|
|
||||||
// Fetch status from remote on load
|
// Fetch status from remote on load
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
setState({ load_stage: "checking_user" });
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
actions.fetchUserStatus().then((status) => {
|
actions.fetchUserStatus().then((status) => {
|
||||||
setState({ user_status: status });
|
setState({ user_status: status });
|
||||||
@@ -341,6 +363,7 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
console.log("stopping mutiny_wallet");
|
console.log("stopping mutiny_wallet");
|
||||||
await state.mutiny_wallet?.stop();
|
await state.mutiny_wallet?.stop();
|
||||||
console.log("mutiny_wallet stopped");
|
console.log("mutiny_wallet stopped");
|
||||||
|
sessionStorage.removeItem("MUTINY_WALLET_INITIALIZED");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user