mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2025-12-19 07:14:22 +01:00
encrypt / decrypt
This commit is contained in:
@@ -6,7 +6,7 @@ import { A } from "solid-start";
|
|||||||
import { OnboardWarning } from "~/components/OnboardWarning";
|
import { OnboardWarning } from "~/components/OnboardWarning";
|
||||||
import { CombinedActivity } from "./Activity";
|
import { CombinedActivity } from "./Activity";
|
||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { Match, Show, Switch, createMemo } from "solid-js";
|
import { Match, Show, Switch } from "solid-js";
|
||||||
import { ExternalLink } from "./layout/ExternalLink";
|
import { ExternalLink } from "./layout/ExternalLink";
|
||||||
import { BetaWarningModal } from "~/components/BetaWarningModal";
|
import { BetaWarningModal } from "~/components/BetaWarningModal";
|
||||||
import settings from "~/assets/icons/settings.svg";
|
import settings from "~/assets/icons/settings.svg";
|
||||||
@@ -14,6 +14,7 @@ import pixelLogo from "~/assets/mutiny-pixel-logo.png";
|
|||||||
import plusLogo from "~/assets/mutiny-plus-logo.png";
|
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";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [state, _actions] = useMegaStore();
|
const [state, _actions] = useMegaStore();
|
||||||
@@ -96,6 +97,7 @@ export default function App() {
|
|||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
|
<DecryptDialog />
|
||||||
<BetaWarningModal />
|
<BetaWarningModal />
|
||||||
<NavBar activeTab="home" />
|
<NavBar activeTab="home" />
|
||||||
</SafeArea>
|
</SafeArea>
|
||||||
|
|||||||
74
src/components/DecryptDialog.tsx
Normal file
74
src/components/DecryptDialog.tsx
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { Show, createSignal } from "solid-js";
|
||||||
|
import { Button, SimpleDialog } from "~/components/layout";
|
||||||
|
import { TextField } from "~/components/layout/TextField";
|
||||||
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import eify from "~/utils/eify";
|
||||||
|
import { A } from "solid-start";
|
||||||
|
|
||||||
|
export function DecryptDialog() {
|
||||||
|
const [state, actions] = useMegaStore();
|
||||||
|
|
||||||
|
const [password, setPassword] = createSignal("");
|
||||||
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
const [error, setError] = createSignal("");
|
||||||
|
|
||||||
|
async function decrypt(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await actions.setupMutinyWallet(undefined, password());
|
||||||
|
|
||||||
|
// If we get this far and the state stills wants a password that means the password was wrong
|
||||||
|
if (state.needs_password) {
|
||||||
|
throw new Error("wrong");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const err = eify(e);
|
||||||
|
console.error(e);
|
||||||
|
if (err.message === "wrong") {
|
||||||
|
setError("Invalid password");
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop() {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SimpleDialog
|
||||||
|
title="Enter your password"
|
||||||
|
// Only show the dialog if we need a password and there's no setup error
|
||||||
|
open={state.needs_password && !state.setup_error}
|
||||||
|
>
|
||||||
|
<form onSubmit={decrypt}>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<TextField
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
ref={noop}
|
||||||
|
value={password()}
|
||||||
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
error={""}
|
||||||
|
onBlur={noop}
|
||||||
|
onChange={noop}
|
||||||
|
/>
|
||||||
|
<Show when={error()}>
|
||||||
|
<InfoBox accent="red">{error()}</InfoBox>
|
||||||
|
</Show>
|
||||||
|
<Button intent="blue" loading={loading()} onClick={decrypt}>
|
||||||
|
Decrypt Wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<A class="self-end text-m-grey-400" href="/settings/restore">
|
||||||
|
Forgot Password?
|
||||||
|
</A>
|
||||||
|
</SimpleDialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,19 +1,64 @@
|
|||||||
import { useMegaStore } from "~/state/megaStore";
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
import { Button, InnerCard, NiceP, VStack } from "~/components/layout";
|
import {
|
||||||
import { createSignal } from "solid-js";
|
Button,
|
||||||
|
InnerCard,
|
||||||
|
NiceP,
|
||||||
|
SimpleDialog,
|
||||||
|
VStack
|
||||||
|
} from "~/components/layout";
|
||||||
|
import { Show, createSignal } from "solid-js";
|
||||||
import eify from "~/utils/eify";
|
import eify from "~/utils/eify";
|
||||||
import { showToast } from "./Toaster";
|
import { showToast } from "./Toaster";
|
||||||
import { downloadTextFile } from "~/utils/download";
|
import { downloadTextFile } from "~/utils/download";
|
||||||
import { createFileUploader } from "@solid-primitives/upload";
|
import { createFileUploader } from "@solid-primitives/upload";
|
||||||
import { ConfirmDialog } from "./Dialog";
|
import { ConfirmDialog } from "./Dialog";
|
||||||
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
import initMutinyWallet, { MutinyWallet } from "@mutinywallet/mutiny-wasm";
|
||||||
|
import { InfoBox } from "./InfoBox";
|
||||||
|
import { TextField } from "./layout/TextField";
|
||||||
|
|
||||||
export function ImportExport(props: { emergency?: boolean }) {
|
export function ImportExport(props: { emergency?: boolean }) {
|
||||||
const [state, _] = useMegaStore();
|
const [state, _] = useMegaStore();
|
||||||
|
|
||||||
|
const [error, setError] = createSignal<Error>();
|
||||||
|
const [exportDecrypt, setExportDecrypt] = createSignal(false);
|
||||||
|
const [password, setPassword] = createSignal("");
|
||||||
|
|
||||||
async function handleSave() {
|
async function handleSave() {
|
||||||
|
try {
|
||||||
|
setError(undefined);
|
||||||
const json = await MutinyWallet.export_json();
|
const json = await MutinyWallet.export_json();
|
||||||
downloadTextFile(json || "", "mutiny-state.json");
|
downloadTextFile(json || "", "mutiny-state.json");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
const err = eify(e);
|
||||||
|
if (err.message === "Incorrect password entered.") {
|
||||||
|
setExportDecrypt(true);
|
||||||
|
} else {
|
||||||
|
setError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function savePassword(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
setError(undefined);
|
||||||
|
if (!password()) {
|
||||||
|
throw new Error("Password is required");
|
||||||
|
}
|
||||||
|
const json = await MutinyWallet.export_json(password());
|
||||||
|
downloadTextFile(json || "", "mutiny-state.json");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError(eify(e));
|
||||||
|
} finally {
|
||||||
|
setExportDecrypt(false);
|
||||||
|
setPassword("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function noop() {
|
||||||
|
// noop
|
||||||
}
|
}
|
||||||
|
|
||||||
const { files, selectFiles } = createFileUploader();
|
const { files, selectFiles } = createFileUploader();
|
||||||
@@ -23,6 +68,7 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
const fileReader = new FileReader();
|
const fileReader = new FileReader();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
setError(undefined);
|
||||||
const file: File = files()[0].file;
|
const file: File = files()[0].file;
|
||||||
|
|
||||||
const text = await new Promise<string | null>((resolve, reject) => {
|
const text = await new Promise<string | null>((resolve, reject) => {
|
||||||
@@ -45,6 +91,7 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
await state.mutiny_wallet.stop();
|
await state.mutiny_wallet.stop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
setError(eify(e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there's no mutiny wallet loaded we need to initialize WASM
|
// If there's no mutiny wallet loaded we need to initialize WASM
|
||||||
@@ -96,6 +143,9 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
to make sure you don't create conflicts.
|
to make sure you don't create conflicts.
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<div />
|
<div />
|
||||||
|
<Show when={error()}>
|
||||||
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||||
|
</Show>
|
||||||
<VStack>
|
<VStack>
|
||||||
<Button onClick={handleSave}>Save State As File</Button>
|
<Button onClick={handleSave}>Save State As File</Button>
|
||||||
<Button onClick={uploadFile}>Import State From File</Button>
|
<Button onClick={uploadFile}>Import State From File</Button>
|
||||||
@@ -109,6 +159,32 @@ export function ImportExport(props: { emergency?: boolean }) {
|
|||||||
>
|
>
|
||||||
Do you want to replace your state with {files()[0].name}?
|
Do you want to replace your state with {files()[0].name}?
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
{/* TODO: this is pretty redundant with the DecryptDialog, could make a shared component */}
|
||||||
|
<SimpleDialog
|
||||||
|
title="Enter your password to decrypt"
|
||||||
|
open={exportDecrypt()}
|
||||||
|
>
|
||||||
|
<form onSubmit={savePassword}>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<TextField
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
ref={noop}
|
||||||
|
value={password()}
|
||||||
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
error={""}
|
||||||
|
onBlur={noop}
|
||||||
|
onChange={noop}
|
||||||
|
/>
|
||||||
|
<Show when={error()}>
|
||||||
|
<InfoBox accent="red">{error()?.message}</InfoBox>
|
||||||
|
</Show>
|
||||||
|
<Button intent="blue" onClick={savePassword}>
|
||||||
|
Decrypt Wallet
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</SimpleDialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ export function TextField(props: TextFieldProps) {
|
|||||||
class="w-full p-2 rounded-lg bg-white/10 placeholder-neutral-400"
|
class="w-full p-2 rounded-lg bg-white/10 placeholder-neutral-400"
|
||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<KTextField.ErrorMessage>{props.error}</KTextField.ErrorMessage>
|
<KTextField.ErrorMessage class="text-m-red">
|
||||||
|
{props.error}
|
||||||
|
</KTextField.ErrorMessage>
|
||||||
<Show when={props.caption}>
|
<Show when={props.caption}>
|
||||||
<TinyText>{props.caption}</TinyText>
|
<TinyText>{props.caption}</TinyText>
|
||||||
</Show>
|
</Show>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { generateGradient } from "~/utils/gradientHash";
|
|||||||
import close from "~/assets/icons/close.svg";
|
import close from "~/assets/icons/close.svg";
|
||||||
import { A } from "solid-start";
|
import { A } from "solid-start";
|
||||||
import down from "~/assets/icons/down.svg";
|
import down from "~/assets/icons/down.svg";
|
||||||
|
import { DecryptDialog } from "../DecryptDialog";
|
||||||
|
|
||||||
export { Button, ButtonLink, Linkify };
|
export { Button, ButtonLink, Linkify };
|
||||||
|
|
||||||
@@ -110,7 +111,6 @@ export const Collapser: ParentComponent<{
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const SafeArea: ParentComponent = (props) => {
|
export const SafeArea: ParentComponent = (props) => {
|
||||||
return (
|
return (
|
||||||
<div class="h-[100dvh] safe-left safe-right">
|
<div class="h-[100dvh] safe-left safe-right">
|
||||||
@@ -161,6 +161,7 @@ export const MutinyWalletGuard: ParentComponent = (props) => {
|
|||||||
<Show when={state.mutiny_wallet && !state.wallet_loading}>
|
<Show when={state.mutiny_wallet && !state.wallet_loading}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Show>
|
</Show>
|
||||||
|
<DecryptDialog />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -318,7 +319,7 @@ export function ModalCloseButton() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SIMPLE_OVERLAY = "fixed inset-0 z-50 bg-black/70 backdrop-blur-md";
|
export const SIMPLE_OVERLAY = "fixed inset-0 z-50 bg-black/20 backdrop-blur-md";
|
||||||
export const SIMPLE_DIALOG_POSITIONER =
|
export const SIMPLE_DIALOG_POSITIONER =
|
||||||
"fixed inset-0 z-50 flex items-center justify-center";
|
"fixed inset-0 z-50 flex items-center justify-center";
|
||||||
export const SIMPLE_DIALOG_CONTENT =
|
export const SIMPLE_DIALOG_CONTENT =
|
||||||
@@ -327,10 +328,13 @@ export const SIMPLE_DIALOG_CONTENT =
|
|||||||
export const SimpleDialog: ParentComponent<{
|
export const SimpleDialog: ParentComponent<{
|
||||||
title: string;
|
title: string;
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen?: (open: boolean) => void;
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={props.open} onOpenChange={props.setOpen}>
|
<Dialog.Root
|
||||||
|
open={props.open}
|
||||||
|
onOpenChange={props.setOpen && props.setOpen}
|
||||||
|
>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay class={SIMPLE_OVERLAY} />
|
<Dialog.Overlay class={SIMPLE_OVERLAY} />
|
||||||
<div class={SIMPLE_DIALOG_POSITIONER}>
|
<div class={SIMPLE_DIALOG_POSITIONER}>
|
||||||
@@ -339,9 +343,11 @@ export const SimpleDialog: ParentComponent<{
|
|||||||
<Dialog.Title>
|
<Dialog.Title>
|
||||||
<SmallHeader>{props.title}</SmallHeader>
|
<SmallHeader>{props.title}</SmallHeader>
|
||||||
</Dialog.Title>
|
</Dialog.Title>
|
||||||
|
<Show when={props.setOpen}>
|
||||||
<Dialog.CloseButton>
|
<Dialog.CloseButton>
|
||||||
<ModalCloseButton />
|
<ModalCloseButton />
|
||||||
</Dialog.CloseButton>
|
</Dialog.CloseButton>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
<Dialog.Description class="flex flex-col gap-4">
|
<Dialog.Description class="flex flex-col gap-4">
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@@ -106,7 +106,8 @@ export async function checkForWasm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function setupMutinyWallet(
|
export async function setupMutinyWallet(
|
||||||
settings?: MutinyWalletSettingStrings
|
settings?: MutinyWalletSettingStrings,
|
||||||
|
password?: string
|
||||||
): Promise<MutinyWallet> {
|
): 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.
|
||||||
@@ -138,7 +139,7 @@ export async function setupMutinyWallet(
|
|||||||
console.log("Using subscriptions address", subscriptions);
|
console.log("Using subscriptions address", subscriptions);
|
||||||
const mutinyWallet = await new MutinyWallet(
|
const mutinyWallet = await new MutinyWallet(
|
||||||
// Password
|
// Password
|
||||||
"",
|
password ? password : undefined,
|
||||||
// Mnemonic
|
// Mnemonic
|
||||||
undefined,
|
undefined,
|
||||||
proxy,
|
proxy,
|
||||||
|
|||||||
@@ -55,10 +55,13 @@ export default function Backup() {
|
|||||||
|
|
||||||
const [hasSeenBackup, setHasSeenBackup] = createSignal(false);
|
const [hasSeenBackup, setHasSeenBackup] = createSignal(false);
|
||||||
const [hasCheckedAll, setHasCheckedAll] = createSignal(false);
|
const [hasCheckedAll, setHasCheckedAll] = createSignal(false);
|
||||||
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
function wroteDownTheWords() {
|
function wroteDownTheWords() {
|
||||||
|
setLoading(true);
|
||||||
actions.setHasBackedUp();
|
actions.setHasBackedUp();
|
||||||
navigate("/");
|
navigate("/settings/encrypt");
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -93,6 +96,7 @@ export default function Backup() {
|
|||||||
disabled={!hasSeenBackup() || !hasCheckedAll()}
|
disabled={!hasSeenBackup() || !hasCheckedAll()}
|
||||||
intent="blue"
|
intent="blue"
|
||||||
onClick={wroteDownTheWords}
|
onClick={wroteDownTheWords}
|
||||||
|
loading={loading()}
|
||||||
>
|
>
|
||||||
I wrote down the words
|
I wrote down the words
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
140
src/routes/settings/Encrypt.tsx
Normal file
140
src/routes/settings/Encrypt.tsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
DefaultMain,
|
||||||
|
LargeHeader,
|
||||||
|
NiceP,
|
||||||
|
MutinyWalletGuard,
|
||||||
|
SafeArea,
|
||||||
|
VStack,
|
||||||
|
ButtonLink
|
||||||
|
} from "~/components/layout";
|
||||||
|
import NavBar from "~/components/NavBar";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
import { Show, createSignal } from "solid-js";
|
||||||
|
import { BackLink } from "~/components/layout/BackLink";
|
||||||
|
import { createForm } from "@modular-forms/solid";
|
||||||
|
import { TextField } from "~/components/layout/TextField";
|
||||||
|
import { timeout } from "~/utils/timeout";
|
||||||
|
import eify from "~/utils/eify";
|
||||||
|
import { InfoBox } from "~/components/InfoBox";
|
||||||
|
|
||||||
|
type EncryptPasswordForm = {
|
||||||
|
existingPassword: string;
|
||||||
|
password: string;
|
||||||
|
confirmPassword: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Encrypt() {
|
||||||
|
const [store, _actions] = useMegaStore();
|
||||||
|
const [error, setError] = createSignal<Error>();
|
||||||
|
const [loading, setLoading] = createSignal(false);
|
||||||
|
|
||||||
|
const [_encryptPasswordForm, { Form, Field }] =
|
||||||
|
createForm<EncryptPasswordForm>({
|
||||||
|
initialValues: {
|
||||||
|
existingPassword: "",
|
||||||
|
password: "",
|
||||||
|
confirmPassword: ""
|
||||||
|
},
|
||||||
|
validate: (values) => {
|
||||||
|
const errors: Record<string, string> = {};
|
||||||
|
if (values.password !== values.confirmPassword) {
|
||||||
|
errors.confirmPassword = "Passwords do not match";
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFormSubmit = async (f: EncryptPasswordForm) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
await store.mutiny_wallet?.change_password(
|
||||||
|
f.existingPassword === "" ? undefined : f.existingPassword,
|
||||||
|
f.password === "" ? undefined : f.password
|
||||||
|
);
|
||||||
|
|
||||||
|
await timeout(1000);
|
||||||
|
window.location.href = "/";
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
setError(eify(e));
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MutinyWalletGuard>
|
||||||
|
<SafeArea>
|
||||||
|
<DefaultMain>
|
||||||
|
<BackLink href="/settings" title="Settings" />
|
||||||
|
<LargeHeader>
|
||||||
|
Encrypt your seed words (optional)
|
||||||
|
</LargeHeader>
|
||||||
|
<VStack>
|
||||||
|
<NiceP>
|
||||||
|
Mutiny is a "hot wallet" so it needs your seed word
|
||||||
|
to operate, but you can optionally encrypt those
|
||||||
|
words with a password.
|
||||||
|
</NiceP>
|
||||||
|
<NiceP>
|
||||||
|
That way, if someone gets access to your browser,
|
||||||
|
they still won't have access to your funds.
|
||||||
|
</NiceP>
|
||||||
|
<Form onSubmit={handleFormSubmit}>
|
||||||
|
<VStack>
|
||||||
|
<Field name="existingPassword">
|
||||||
|
{(field, props) => (
|
||||||
|
<TextField
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
type="password"
|
||||||
|
label="Existing Password (optional)"
|
||||||
|
placeholder="Existing password"
|
||||||
|
caption="Leave blank if you haven't set a password yet."
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="password">
|
||||||
|
{(field, props) => (
|
||||||
|
<TextField
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
type="password"
|
||||||
|
label="Password"
|
||||||
|
placeholder="Enter a password"
|
||||||
|
caption="This password will be used to encrypt your seed words. If you forget it, you will need to re-enter your seed words to access your funds. You did write down your seed words, right?"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Field name="confirmPassword">
|
||||||
|
{(field, props) => (
|
||||||
|
<TextField
|
||||||
|
{...props}
|
||||||
|
{...field}
|
||||||
|
type="password"
|
||||||
|
label="Confirm Password"
|
||||||
|
placeholder="Enter the same password"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
<Show when={error()}>
|
||||||
|
<InfoBox accent="red">
|
||||||
|
{error()?.message}
|
||||||
|
</InfoBox>
|
||||||
|
</Show>
|
||||||
|
<div />
|
||||||
|
<Button intent="blue" loading={loading()}>
|
||||||
|
Encrypt
|
||||||
|
</Button>
|
||||||
|
</VStack>
|
||||||
|
</Form>
|
||||||
|
<ButtonLink href="/settings" intent="green">
|
||||||
|
Skip
|
||||||
|
</ButtonLink>
|
||||||
|
</VStack>
|
||||||
|
</DefaultMain>
|
||||||
|
<NavBar activeTab="settings" />
|
||||||
|
</SafeArea>
|
||||||
|
</MutinyWalletGuard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
DefaultMain,
|
DefaultMain,
|
||||||
LargeHeader,
|
LargeHeader,
|
||||||
MutinyWalletGuard,
|
|
||||||
NiceP,
|
NiceP,
|
||||||
SafeArea,
|
SafeArea,
|
||||||
VStack
|
VStack
|
||||||
@@ -235,30 +234,28 @@ function TwelveWordsEntry() {
|
|||||||
|
|
||||||
export default function RestorePage() {
|
export default function RestorePage() {
|
||||||
return (
|
return (
|
||||||
<MutinyWalletGuard>
|
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
<BackLink title="Settings" href="/settings" />
|
<BackLink title="Settings" href="/settings" />
|
||||||
<LargeHeader>Restore</LargeHeader>
|
<LargeHeader>Restore</LargeHeader>
|
||||||
<VStack>
|
<VStack>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
You can restore an existing Mutiny Wallet from your
|
You can restore an existing Mutiny Wallet from your 12
|
||||||
12 word seed phrase. This will replace your existing
|
word seed phrase. This will replace your existing
|
||||||
wallet, so make sure you know what you're doing!
|
wallet, so make sure you know what you're doing!
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<NiceP>
|
<NiceP>
|
||||||
<strong class="font-bold text-m-red">
|
<strong class="font-bold text-m-red">
|
||||||
Beta warning:
|
Beta warning:
|
||||||
</strong>{" "}
|
</strong>{" "}
|
||||||
you can currently only restore on-chain funds.
|
you can currently only restore on-chain funds. Lightning
|
||||||
Lightning backup restore is coming soon.
|
backup restore is coming soon.
|
||||||
</NiceP>
|
</NiceP>
|
||||||
<TwelveWordsEntry />
|
<TwelveWordsEntry />
|
||||||
</VStack>
|
</VStack>
|
||||||
</DefaultMain>
|
</DefaultMain>
|
||||||
<NavBar activeTab="settings" />
|
<NavBar activeTab="settings" />
|
||||||
</SafeArea>
|
</SafeArea>
|
||||||
</MutinyWalletGuard>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import NavBar from "~/components/NavBar";
|
|||||||
import { A } from "solid-start";
|
import { A } from "solid-start";
|
||||||
import { For, Show } from "solid-js";
|
import { For, Show } from "solid-js";
|
||||||
import forward from "~/assets/icons/forward.svg";
|
import forward from "~/assets/icons/forward.svg";
|
||||||
|
import { useMegaStore } from "~/state/megaStore";
|
||||||
|
|
||||||
function SettingsLinkList(props: {
|
function SettingsLinkList(props: {
|
||||||
header: string;
|
header: string;
|
||||||
@@ -18,6 +19,7 @@ function SettingsLinkList(props: {
|
|||||||
text: string;
|
text: string;
|
||||||
caption?: string;
|
caption?: string;
|
||||||
accent?: "red" | "green";
|
accent?: "red" | "green";
|
||||||
|
disabled?: boolean;
|
||||||
}[];
|
}[];
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -26,7 +28,11 @@ function SettingsLinkList(props: {
|
|||||||
{(link) => (
|
{(link) => (
|
||||||
<A
|
<A
|
||||||
href={link.href}
|
href={link.href}
|
||||||
class="no-underline flex w-full flex-col gap-1 py-2 hover:bg-m-grey-750 active:bg-m-grey-900 px-4 "
|
class="no-underline flex w-full flex-col gap-1 py-2 hover:bg-m-grey-750 active:bg-m-grey-900 px-4"
|
||||||
|
classList={{
|
||||||
|
"opacity-50 cursor pointer-events-none grayscale":
|
||||||
|
link.disabled
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<span
|
<span
|
||||||
@@ -52,6 +58,8 @@ function SettingsLinkList(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const [state, _actions] = useMegaStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeArea>
|
<SafeArea>
|
||||||
<DefaultMain>
|
<DefaultMain>
|
||||||
@@ -84,6 +92,14 @@ export default function Settings() {
|
|||||||
text: "Restore",
|
text: "Restore",
|
||||||
accent: "red"
|
accent: "red"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: "/settings/encrypt",
|
||||||
|
text: "Change Password",
|
||||||
|
disabled: !state.has_backed_up,
|
||||||
|
caption: !state.has_backed_up
|
||||||
|
? "Backup first to unlock encryption"
|
||||||
|
: undefined
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: "/settings/servers",
|
href: "/settings/servers",
|
||||||
text: "Servers",
|
text: "Servers",
|
||||||
|
|||||||
@@ -47,10 +47,14 @@ export type MegaStore = [
|
|||||||
existing_tab_detected: boolean;
|
existing_tab_detected: boolean;
|
||||||
subscription_timestamp?: number;
|
subscription_timestamp?: number;
|
||||||
readonly mutiny_plus: boolean;
|
readonly mutiny_plus: boolean;
|
||||||
|
needs_password: boolean;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fetchUserStatus(): Promise<UserStatus>;
|
fetchUserStatus(): Promise<UserStatus>;
|
||||||
setupMutinyWallet(settings?: MutinyWalletSettingStrings): Promise<void>;
|
setupMutinyWallet(
|
||||||
|
settings?: MutinyWalletSettingStrings,
|
||||||
|
password?: string
|
||||||
|
): Promise<void>;
|
||||||
deleteMutinyWallet(): Promise<void>;
|
deleteMutinyWallet(): Promise<void>;
|
||||||
setWaitlistId(waitlist_id: string): void;
|
setWaitlistId(waitlist_id: string): void;
|
||||||
setScanResult(scan_result: ParsedParams | undefined): void;
|
setScanResult(scan_result: ParsedParams | undefined): void;
|
||||||
@@ -95,7 +99,8 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
if (state.subscription_timestamp < Math.ceil(Date.now() / 1000))
|
if (state.subscription_timestamp < Math.ceil(Date.now() / 1000))
|
||||||
return false;
|
return false;
|
||||||
else return true;
|
else return true;
|
||||||
}
|
},
|
||||||
|
needs_password: false
|
||||||
});
|
});
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
@@ -161,17 +166,24 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async setupMutinyWallet(
|
async setupMutinyWallet(
|
||||||
settings?: MutinyWalletSettingStrings
|
settings?: MutinyWalletSettingStrings,
|
||||||
|
password?: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// If we're already in an error state there should be no reason to continue
|
// If we're already in an error state there should be no reason to continue
|
||||||
if (state.setup_error) {
|
if (state.setup_error) {
|
||||||
throw state.setup_error;
|
throw state.setup_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
setState({ wallet_loading: true });
|
setState({ wallet_loading: true });
|
||||||
|
|
||||||
// This is where all the real setup happens
|
const mutinyWallet = await setupMutinyWallet(
|
||||||
const mutinyWallet = await setupMutinyWallet(settings);
|
settings,
|
||||||
|
password
|
||||||
|
);
|
||||||
|
|
||||||
|
// If we get this far then we don't need the password anymore
|
||||||
|
setState({ needs_password: false });
|
||||||
|
|
||||||
// Get balance optimistically
|
// Get balance optimistically
|
||||||
const balance = await mutinyWallet.get_balance();
|
const balance = await mutinyWallet.get_balance();
|
||||||
@@ -210,8 +222,12 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
if (eify(e).message === "Incorrect password entered.") {
|
||||||
|
setState({ needs_password: true });
|
||||||
|
} else {
|
||||||
setState({ setup_error: eify(e) });
|
setState({ setup_error: eify(e) });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async deleteMutinyWallet(): Promise<void> {
|
async deleteMutinyWallet(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -315,13 +331,10 @@ export const Provider: ParentComponent = (props) => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log("running setup node manager...");
|
console.log("running setup node manager...");
|
||||||
|
|
||||||
actions
|
actions
|
||||||
.setupMutinyWallet()
|
.setupMutinyWallet()
|
||||||
.then(() => console.log("node manager setup done"))
|
.then(() => console.log("node manager setup done"));
|
||||||
.catch((e) => {
|
|
||||||
console.error(e);
|
|
||||||
setState({ setup_error: eify(e) });
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup an event listener to stop the mutiny wallet when the page unloads
|
// Setup an event listener to stop the mutiny wallet when the page unloads
|
||||||
window.onunload = async (_e) => {
|
window.onunload = async (_e) => {
|
||||||
|
|||||||
@@ -57,7 +57,9 @@ export default defineConfig({
|
|||||||
"nostr-tools",
|
"nostr-tools",
|
||||||
"class-variance-authority",
|
"class-variance-authority",
|
||||||
"@kobalte/core",
|
"@kobalte/core",
|
||||||
"@solid-primitives/upload"
|
"@solid-primitives/upload",
|
||||||
|
"i18next",
|
||||||
|
"i18next-browser-languagedetector"
|
||||||
],
|
],
|
||||||
// This is necessary because otherwise `vite dev` can't find the wasm
|
// This is necessary because otherwise `vite dev` can't find the wasm
|
||||||
exclude: ["@mutinywallet/mutiny-wasm", "@mutinywallet/waila-wasm"]
|
exclude: ["@mutinywallet/mutiny-wasm", "@mutinywallet/waila-wasm"]
|
||||||
|
|||||||
Reference in New Issue
Block a user