settings reorg

This commit is contained in:
Paul Miller
2023-06-29 10:19:22 -05:00
parent 3348db1eeb
commit 534a1ba00f
18 changed files with 430 additions and 304 deletions

View File

@@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icons">
<path id="Vector" d="M10 17L15 12L10 7" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 244 B

View File

@@ -1,5 +1,5 @@
import { Show } from "solid-js";
import { Button, FancyCard, Hr, Indicator } from "~/components/layout";
import { Button, FancyCard, Indicator } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { Amount } from "./Amount";
import { A, useNavigate } from "solid-start";

View File

@@ -1,6 +1,5 @@
import { useMegaStore } from "~/state/megaStore";
import { Hr, Button, InnerCard, VStack } from "~/components/layout";
import NostrWalletConnectModal from "~/components/NostrWalletConnectModal";
import {
For,
Match,
@@ -16,10 +15,8 @@ import mempoolTxUrl from "~/utils/mempoolTxUrl";
import eify from "~/utils/eify";
import { ConfirmDialog } from "~/components/Dialog";
import { showToast } from "~/components/Toaster";
import { ImportExport } from "~/components/ImportExport";
import { Network } from "~/logic/mutinyWalletSetup";
import { ExternalLink } from "./layout/ExternalLink";
import { Logs } from "./Logs";
import { Restart } from "./Restart";
import { ResyncOnchain } from "./ResyncOnchain";
import { MiniStringShower } from "./DetailsModal";
@@ -399,53 +396,6 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) {
);
}
function LnUrlAuth() {
const [state, _] = useMegaStore();
const [value, setValue] = createSignal("");
const onSubmit = async (e: SubmitEvent) => {
e.preventDefault();
const lnurl = value().trim();
await state.mutiny_wallet?.lnurl_auth(0, lnurl);
setValue("");
};
return (
<InnerCard>
<form class="flex flex-col gap-4" onSubmit={onSubmit}>
<TextField.Root
value={value()}
onChange={setValue}
validationState={
value() == "" ||
value().toLowerCase().startsWith("lnurl")
? "valid"
: "invalid"
}
class="flex flex-col gap-4"
>
<TextField.Label class="text-sm font-semibold uppercase">
LNURL Auth
</TextField.Label>
<TextField.Input
class="w-full p-2 rounded-lg text-black"
placeholder="LNURL..."
/>
<TextField.ErrorMessage class="text-red-500">
Expecting something like LNURL...
</TextField.ErrorMessage>
</TextField.Root>
<Button layout="small" type="submit">
Auth
</Button>
</form>
</InnerCard>
);
}
function ListNodes() {
const [state, _] = useMegaStore();
@@ -470,24 +420,16 @@ function ListNodes() {
export default function KitchenSink() {
return (
<>
<Logs />
<Hr />
<ListNodes />
<Hr />
<NostrWalletConnectModal />
<Hr />
<PeersList />
<Hr />
<ChannelsList />
<Hr />
<LnUrlAuth />
<Hr />
<ResyncOnchain />
<Hr />
<Restart />
<Hr />
<ImportExport />
<Hr />
</>
);
}

View File

@@ -1,77 +0,0 @@
import { Show, createResource } from "solid-js";
import { useMegaStore } from "~/state/megaStore";
import { Card, NiceP, SmallHeader, TinyText, VStack } from "./layout";
import { AmountSmall } from "./Amount";
function BalanceBar(props: { inbound: number; outbound: number }) {
return (
<VStack smallgap>
<div class="flex justify-between">
<SmallHeader>Outbound</SmallHeader>
<SmallHeader>Inbound</SmallHeader>
</div>
<div class="flex gap-1 w-full">
<div
class="bg-m-green p-2 rounded-l-xl min-w-fit"
style={{
"flex-grow": props.outbound || 1
}}
>
<AmountSmall amountSats={props.outbound} />
</div>
<div
class="bg-m-blue p-2 rounded-r-xl min-w-fit"
style={{
"flex-grow": props.inbound || 1
}}
>
<AmountSmall amountSats={props.inbound} />
</div>
</div>
</VStack>
);
}
export function LiquidityMonitor() {
const [state, _actions] = useMegaStore();
const [channelInfo] = createResource(async () => {
try {
const channels = await state.mutiny_wallet?.list_channels();
let inbound = 0n;
for (const channel of channels) {
inbound =
inbound +
BigInt(channel.size) -
BigInt(channel.balance + channel.reserve);
}
return { inbound, channelCount: channels?.length };
} catch (e) {
console.error(e);
return { inbound: 0, channelCount: 0 };
}
});
return (
<Show when={channelInfo()?.channelCount}>
<Card>
<NiceP>
You have {channelInfo()?.channelCount} lightning{" "}
{channelInfo()?.channelCount === 1 ? "channel" : "channels"}
.
</NiceP>{" "}
<BalanceBar
inbound={Number(channelInfo()?.inbound) || 0}
outbound={Number(state.balance?.lightning) || 0}
/>
<TinyText>
Outbound is the amount of money you can spend on lightning.
Inbound is the amount you can receive without incurring a
lightning service fee.
</TinyText>
</Card>
</Show>
);
}

View File

@@ -1,83 +0,0 @@
import { QRCodeSVG } from "solid-qr-code";
import { As, Dialog } from "@kobalte/core";
import { Button, Card, InnerCard, NiceP } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { createResource, Show } from "solid-js";
const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm";
const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center";
const DIALOG_CONTENT =
"w-[80vw] max-w-[400px] max-h-[100dvh] overflow-y-auto disable-scrollbars p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10";
const SMALL_HEADER = "text-sm font-semibold uppercase";
export default function NostrWalletConnectModal() {
const [state, actions] = useMegaStore();
const getConnectionURI = () => {
if (state.mutiny_wallet) {
return state.mutiny_wallet.get_nwc_uri();
} else {
return undefined;
}
};
const [connectionURI] = createResource(getConnectionURI);
const toggleNwc = async () => {
if (state.nwc_enabled) {
actions.setNwc(false);
window.location.reload();
} else {
actions.setNwc(true);
const nodes = await state.mutiny_wallet?.list_nodes();
const firstNode = (nodes[0] as string) || "";
await state.mutiny_wallet?.start_nostr_wallet_connect(firstNode);
}
};
// TODO: a lot of this markup is probably reusable as a "Modal" component
return (
<InnerCard title="Nostr Wallet Connect">
<NiceP>Test out some nostr stuff.</NiceP>
<div />
<Dialog.Root>
<Dialog.Trigger asChild>
<As component={Button}>Show Nostr Wallet Connect URI</As>
</Dialog.Trigger>
<Dialog.Portal>
<Dialog.Overlay class={OVERLAY} />
<div class={DIALOG_POSITIONER}>
<Dialog.Content class={DIALOG_CONTENT}>
<div class="flex justify-between mb-2">
<Dialog.Title class={SMALL_HEADER}>
Nostr Wallet Connect
</Dialog.Title>
<Dialog.CloseButton class="dialog__close-button">
<code>X</code>
</Dialog.CloseButton>
</div>
<Dialog.Description class="flex flex-col gap-4">
<Show when={connectionURI()}>
<div class="w-full bg-white rounded-xl">
<QRCodeSVG
value={connectionURI() || ""}
class="w-full h-full p-8 max-h-[400px]"
/>
</div>
<Card>
<code class="break-all">
{connectionURI() || ""}
</code>
</Card>
</Show>
<Button onClick={toggleNwc}>
{state.nwc_enabled ? "Disable" : "Enable"}
</Button>
</Dialog.Description>
</Dialog.Content>
</div>
</Dialog.Portal>
</Dialog.Root>
</InnerCard>
);
}

View File

@@ -83,7 +83,7 @@ export function OnboardWarning() {
intent="blue"
layout="xs"
class="self-auto"
href="/backup"
href="/settings/backup"
>
Backup
</ButtonLink>

View File

@@ -60,6 +60,22 @@ export const FancyCard: ParentComponent<{
);
};
export const SettingsCard: ParentComponent<{
title?: string;
}> = (props) => {
return (
<VStack smallgap>
<div class="mt-2 pl-4">
<SmallHeader>{props.title}</SmallHeader>
</div>
<div class="rounded-xl py-4 flex flex-col gap-2 bg-m-grey-800 w-full">
{props.children}
</div>
</VStack>
);
};
export const SafeArea: ParentComponent = (props) => {
return (
<div class="h-[100dvh] safe-left safe-right">

View File

@@ -12,8 +12,6 @@ import {
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import { CombinedActivity } from "~/components/Activity";
import { A } from "solid-start";
import settings from "~/assets/icons/settings.svg";
import { Tabs } from "@kobalte/core";
import { gradientsPerContact } from "~/utils/gradientHash";
import { ContactEditor } from "~/components/ContactEditor";

View File

@@ -1,72 +0,0 @@
import {
ButtonLink,
Card,
DefaultMain,
LargeHeader,
MutinyWalletGuard,
NiceP,
SafeArea,
VStack
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import NavBar from "~/components/NavBar";
import { SeedWords } from "~/components/SeedWords";
import { SettingsStringsEditor } from "~/components/SettingsStringsEditor";
import { useMegaStore } from "~/state/megaStore";
import { LiquidityMonitor } from "~/components/LiquidityMonitor";
import { A } from "solid-start";
import { Suspense } from "solid-js";
export default function Settings() {
const [store, _actions] = useMegaStore();
return (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink />
<LargeHeader>Settings</LargeHeader>
<VStack biggap>
<LiquidityMonitor />
<Card title="Backup your seed words">
<VStack>
<NiceP>
These 12 words allow you to recover your
on-chain funds in case you lose your device
or clear your browser storage.
</NiceP>
<SeedWords
words={
store.mutiny_wallet?.show_seed() || ""
}
/>
</VStack>
</Card>
<SettingsStringsEditor />
<Card title="Emergency Kit">
<NiceP>
Having some serious problems with your wallet?
Check out the{" "}
<A href="/emergencykit">emergency kit.</A>
</NiceP>
</Card>
<Card title="If you know what you're doing">
<VStack>
<NiceP>
We have some not-very-pretty debug tools we
use to test the wallet. Use wisely!
</NiceP>
<div class="flex justify-center">
<ButtonLink href="/admin" layout="xs">
Secret Debug Tools
</ButtonLink>
</div>
</VStack>
</Card>
</VStack>
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}

View File

@@ -35,7 +35,7 @@ export default function Admin() {
</div>
</VStack>
</DefaultMain>
<NavBar activeTab="none" />
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);

View File

@@ -49,7 +49,7 @@ function Quiz(props: { setHasCheckedAll: (hasChecked: boolean) => void }) {
);
}
export default function App() {
export default function Backup() {
const [store, actions] = useMegaStore();
const navigate = useNavigate();
@@ -65,7 +65,7 @@ export default function App() {
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink />
<BackLink href="/settings" title="Settings" />
<LargeHeader>Backup</LargeHeader>
<VStack>
@@ -98,7 +98,7 @@ export default function App() {
</Button>
</VStack>
</DefaultMain>
<NavBar activeTab="none" />
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);

View File

@@ -0,0 +1,115 @@
import { Match, Switch, createResource } from "solid-js";
import { useMegaStore } from "~/state/megaStore";
import {
Card,
DefaultMain,
LargeHeader,
MutinyWalletGuard,
NiceP,
SafeArea,
SmallHeader,
TinyText,
VStack
} from "~/components/layout";
import { AmountSmall } from "~/components/Amount";
import { BackLink } from "~/components/layout/BackLink";
import NavBar from "~/components/NavBar";
function BalanceBar(props: { inbound: number; outbound: number }) {
return (
<VStack smallgap>
<div class="flex justify-between">
<SmallHeader>Outbound</SmallHeader>
<SmallHeader>Inbound</SmallHeader>
</div>
<div class="flex gap-1 w-full">
<div
class="bg-m-green p-2 rounded-l-xl min-w-fit"
style={{
"flex-grow": props.outbound || 1
}}
>
<AmountSmall amountSats={props.outbound} />
</div>
<div
class="bg-m-blue p-2 rounded-r-xl min-w-fit"
style={{
"flex-grow": props.inbound || 1
}}
>
<AmountSmall amountSats={props.inbound} />
</div>
</div>
</VStack>
);
}
export function LiquidityMonitor() {
const [state, _actions] = useMegaStore();
const [channelInfo] = createResource(async () => {
try {
const channels = await state.mutiny_wallet?.list_channels();
let inbound = 0n;
for (const channel of channels) {
inbound =
inbound +
BigInt(channel.size) -
BigInt(channel.balance + channel.reserve);
}
return { inbound, channelCount: channels?.length };
} catch (e) {
console.error(e);
return { inbound: 0, channelCount: 0 };
}
});
return (
<Switch>
<Match when={channelInfo()?.channelCount}>
<Card>
<NiceP>
You have {channelInfo()?.channelCount} lightning{" "}
{channelInfo()?.channelCount === 1
? "channel"
: "channels"}
.
</NiceP>{" "}
<BalanceBar
inbound={Number(channelInfo()?.inbound) || 0}
outbound={Number(state.balance?.lightning) || 0}
/>
<TinyText>
Outbound is the amount of money you can spend on
lightning. Inbound is the amount you can receive without
incurring a lightning service fee.
</TinyText>
</Card>
</Match>
<Match when={true}>
<NiceP>
It looks like you don't have any channels yet. To get
started, receive some sats over lightning, or swap some
on-chain funds into a channel. Get your hands dirty!
</NiceP>
</Match>
</Switch>
);
}
export default function Channels() {
return (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink href="/settings" title="Settings" />
<LargeHeader>Lightning Channels</LargeHeader>
<LiquidityMonitor />
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}

View File

@@ -0,0 +1,68 @@
import { Show, createMemo } from "solid-js";
import { QRCodeSVG } from "solid-qr-code";
import NavBar from "~/components/NavBar";
import { ShareCard } from "~/components/ShareCard";
import {
Button,
DefaultMain,
LargeHeader,
MutinyWalletGuard,
NiceP,
SafeArea
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import { useMegaStore } from "~/state/megaStore";
export default function Connections() {
const [state, actions] = useMegaStore();
const connectionURI = createMemo(() => {
if (state.nwc_enabled) {
return state.mutiny_wallet?.get_nwc_uri();
}
});
const toggleNwc = async () => {
if (state.nwc_enabled) {
actions.setNwc(false);
window.location.reload();
} else {
actions.setNwc(true);
const nodes = await state.mutiny_wallet?.list_nodes();
const firstNode = (nodes[0] as string) || "";
await state.mutiny_wallet?.start_nostr_wallet_connect(firstNode);
}
};
return (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink href="/settings" title="Settings" />
<LargeHeader>Wallet Connections</LargeHeader>
<NiceP>
Authorize Mutiny Wallet with external services like
Nostr clients.
</NiceP>
<Button onClick={toggleNwc}>
{state.nwc_enabled
? "Disable Nostr Wallet Connect"
: "Enable Nostr Wallet Connect"}
</Button>
<Show when={connectionURI() && state.nwc_enabled}>
<div class="w-full bg-white rounded-xl">
<QRCodeSVG
value={connectionURI() || ""}
class="w-full h-full p-8 max-h-[400px]"
/>
</div>
<ShareCard text={connectionURI() || ""} />
</Show>
<div class="h-full" />
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}

View File

@@ -17,7 +17,7 @@ export default function EmergencyKit() {
return (
<SafeArea>
<DefaultMain>
<BackLink />
<BackLink href="/settings" title="Settings" />
<LargeHeader>Emergency Kit</LargeHeader>
<VStack>
<NiceP>
@@ -39,7 +39,7 @@ export default function EmergencyKit() {
</div>
</VStack>
</DefaultMain>
<NavBar activeTab="none" />
<NavBar activeTab="settings" />
</SafeArea>
);
}

View File

@@ -0,0 +1,69 @@
import { TextField } from "@kobalte/core";
import { createSignal } from "solid-js";
import NavBar from "~/components/NavBar";
import {
Button,
DefaultMain,
InnerCard,
LargeHeader,
MutinyWalletGuard,
SafeArea
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import { useMegaStore } from "~/state/megaStore";
export default function LnUrlAuth() {
const [state, _] = useMegaStore();
const [value, setValue] = createSignal("");
const onSubmit = async (e: SubmitEvent) => {
e.preventDefault();
const lnurl = value().trim();
await state.mutiny_wallet?.lnurl_auth(0, lnurl);
setValue("");
};
return (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink href="/settings" title="Settings" />
<LargeHeader>LNURL Auth</LargeHeader>
<InnerCard>
<form class="flex flex-col gap-4" onSubmit={onSubmit}>
<TextField.Root
value={value()}
onChange={setValue}
validationState={
value() == "" ||
value().toLowerCase().startsWith("lnurl")
? "valid"
: "invalid"
}
class="flex flex-col gap-4"
>
<TextField.Label class="text-sm font-semibold uppercase">
LNURL Auth
</TextField.Label>
<TextField.Input
class="w-full p-2 rounded-lg text-black"
placeholder="LNURL..."
/>
<TextField.ErrorMessage class="text-red-500">
Expecting something like LNURL...
</TextField.ErrorMessage>
</TextField.Root>
<Button layout="small" type="submit">
Auth
</Button>
</form>
</InnerCard>
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}

View File

@@ -5,10 +5,20 @@ import {
getExistingSettings,
setAndGetMutinySettings
} from "~/logic/mutinyWalletSetup";
import { Button, Card, NiceP } from "~/components/layout";
import { showToast } from "./Toaster";
import {
Button,
Card,
DefaultMain,
LargeHeader,
MutinyWalletGuard,
NiceP,
SafeArea
} from "~/components/layout";
import { showToast } from "~/components/Toaster";
import eify from "~/utils/eify";
import { ExternalLink } from "./layout/ExternalLink";
import { ExternalLink } from "~/components/layout/ExternalLink";
import { BackLink } from "~/components/layout/BackLink";
import NavBar from "~/components/NavBar";
export function SettingsStringsEditor() {
const existingSettings = getExistingSettings();
@@ -107,3 +117,18 @@ export function SettingsStringsEditor() {
</Card>
);
}
export default function Servers() {
return (
<MutinyWalletGuard>
<SafeArea>
<DefaultMain>
<BackLink href="/settings" title="Settings" />
<LargeHeader>Backup</LargeHeader>
<SettingsStringsEditor />
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
</MutinyWalletGuard>
);
}

View File

@@ -0,0 +1,118 @@
import {
DefaultMain,
LargeHeader,
MutinyWalletGuard,
SafeArea,
SettingsCard,
VStack
} from "~/components/layout";
import { BackLink } from "~/components/layout/BackLink";
import NavBar from "~/components/NavBar";
import { A } from "solid-start";
import { For, Show } from "solid-js";
import forward from "~/assets/icons/forward.svg";
function SettingsLinkList(props: {
header: string;
links: {
href: string;
text: string;
caption?: string;
accent?: "red" | "green";
}[];
}) {
return (
<SettingsCard title={props.header}>
<For each={props.links}>
{(link) => (
<A
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 "
>
<div class="flex justify-between">
<span
classList={{
"text-m-red": link.accent === "red",
"text-m-green": link.accent === "green"
}}
>
{link.text}
</span>
<img src={forward} alt="go" />
</div>
<Show when={link.caption}>
<div class="text-sm text-m-grey-400">
{link.caption}
</div>
</Show>
</A>
)}
</For>
</SettingsCard>
);
}
export default function Settings() {
return (
<SafeArea>
<DefaultMain>
<BackLink />
<LargeHeader>Settings</LargeHeader>
<VStack biggap>
<SettingsLinkList
header="General"
links={[
{
href: "/settings/channels",
text: "Lightning Channels"
},
{
href: "/settings/backup",
text: "Backup",
accent: "green"
},
{
href: "/settings/servers",
text: "Servers",
caption:
"Don't trust us! Use your own servers to back Mutiny."
}
]}
/>
<SettingsLinkList
header="Beta Features"
links={[
{
href: "/settings/connections",
text: "Wallet Connections"
},
{
href: "/settings/lnurlauth",
text: "LNURL Auth"
}
]}
/>
<SettingsLinkList
header="Debug Tools"
links={[
{
href: "/settings/emergencykit",
text: "Emergency Kit",
caption:
"Diagnose and solve problems with your wallet."
},
{
href: "/settings/admin",
text: "Admin Page",
caption:
"Our internal debug tools. Use wisely!",
accent: "red"
}
]}
/>
</VStack>
</DefaultMain>
<NavBar activeTab="settings" />
</SafeArea>
);
}