lint extravaganza

This commit is contained in:
Paul Miller
2023-05-23 19:03:52 -05:00
parent 6ee7bd068b
commit c937bcbf9e
36 changed files with 1206 additions and 1216 deletions

View File

@@ -1,8 +1,7 @@
import { LoadingSpinner, NiceP, SmallAmount, SmallHeader } from "./layout"
import { LoadingSpinner, NiceP } from "./layout"
import {
For,
Match,
Show,
Switch,
createEffect,
createMemo,
@@ -11,9 +10,6 @@ import {
} from "solid-js"
import { useMegaStore } from "~/state/megaStore"
import { MutinyInvoice } from "@mutinywallet/mutiny-wasm"
import { JsonModal } from "~/components/JsonModal"
import utxoIcon from "~/assets/icons/coin.svg"
import { getRedshifted } from "~/utils/fakeLabels"
import { ActivityItem } from "./ActivityItem"
import { MutinyTagItem } from "~/utils/tags"
import { Network } from "~/logic/mutinyWalletSetup"
@@ -76,7 +72,7 @@ function OnChainItem(props: {
}
date={props.item.confirmation_time?.Confirmed?.time}
positive={isReceive()}
onClick={() => setOpen(!open())}
onClick={() => setOpen(o => !o)}
/>
</>
)
@@ -96,52 +92,12 @@ function InvoiceItem(props: { item: MutinyInvoice; labels: MutinyTagItem[] }) {
amount={props.item.amount_sats || 0n}
date={props.item.last_updated}
positive={!isSend()}
onClick={() => setOpen(!open())}
onClick={() => setOpen(o => !o)}
/>
</>
)
}
function Utxo(props: { item: UtxoItem }) {
const spent = createMemo(() => props.item.is_spent)
const [open, setOpen] = createSignal(false)
const redshifted = createMemo(() => getRedshifted(props.item.outpoint))
return (
<>
<JsonModal
open={open()}
data={props.item}
title="Unspent Transaction Output"
setOpen={setOpen}
/>
<div class={THREE_COLUMNS} onClick={() => setOpen(!open())}>
<div class="flex items-center">
<img src={utxoIcon} alt="coin" />
</div>
<div class={CENTER_COLUMN}>
<div class="flex gap-2">
<Show
when={redshifted()}
fallback={<h2 class={MISSING_LABEL}>Unknown</h2>}
>
<h2 class={REDSHIFT_LABEL}>Redshift</h2>
</Show>
</div>
<SmallAmount amount={props.item.txout.value} />
</div>
<div class={RIGHT_COLUMN}>
<SmallHeader class={spent() ? "text-m-red" : "text-m-green"}>
{/* {spent() ? "SPENT" : "UNSPENT"} */}
</SmallHeader>
</div>
</div>
</>
)
}
type ActivityItem = {
type: "onchain" | "lightning"
item: OnChainTx | MutinyInvoice

View File

@@ -41,8 +41,8 @@ export const ActivityAmount: ParentComponent<{ amount: string, price: number, po
function LabelCircle(props: { name?: string, contact: boolean }) {
// TODO: don't need to run this if it's not a contact
const [gradient] = createResource(props.name, async (name: string) => {
return generateGradient(name || "?")
const [gradient] = createResource(async () => {
return generateGradient(props.name || "?")
})
const text = () => (props.contact && props.name && props.name.length) ? props.name[0] : (props.name && props.name.length) ? "≡" : "?"

View File

@@ -57,10 +57,6 @@ function add(a: string, b?: string) {
return Number(a || 0) + Number(b || 0);
}
function subtract(a: string, b?: string) {
return Number(a || 0) - Number(b || 0);
}
export function AmountCard(props: {
amountSats: string;
fee?: string;

View File

@@ -18,7 +18,7 @@ function SingleDigitButton(props: { character: string, onClick: (c: string) => v
<Show when={props.fiat || !(props.character === ".")} fallback={<div />}>
<button
disabled={props.character === "."}
class="disabled:opacity-50 p-2 rounded-lg hover:bg-white/10 active:bg-m-blue text-white text-4xl font-semi font-mono"
class="disabled:opacity-50 p-2 rounded-lg md:hover:bg-white/10 active:bg-m-blue text-white text-4xl font-semi font-mono"
onClick={() => props.onClick(props.character)}
>
{props.character}
@@ -148,7 +148,7 @@ export const AmountEditable: ParentComponent<{ initialAmountSats: string, initia
<Dialog.Content class={DIALOG_CONTENT} onEscapeKeyDown={() => setIsOpen(false)}>
{/* TODO: figure out how to submit on enter */}
<div class="w-full flex justify-end">
<button tabindex="-1" onClick={() => setIsOpen(false)} class="hover:bg-white/10 rounded-lg active:bg-m-blue">
<button onClick={() => setIsOpen(false)} class="hover:bg-white/10 rounded-lg active:bg-m-blue w-8 h-8">
<img src={close} alt="Close" />
</button>
</div>

View File

@@ -1,5 +1,5 @@
import logo from '~/assets/icons/mutiny-logo.svg';
import { DefaultMain, SafeArea, VStack, Card, LoadingSpinner } from "~/components/layout";
import { DefaultMain, SafeArea, VStack, Card } from "~/components/layout";
import BalanceBox, { LoadingShimmer } from "~/components/BalanceBox";
import NavBar from "~/components/NavBar";
import ReloadPrompt from "~/components/Reload";
@@ -18,7 +18,7 @@ export default function App() {
<DefaultMain>
<header class="w-full flex justify-between items-center mt-4 mb-2">
<img src={logo} class="h-10" alt="logo" />
<A class="md:hidden p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="/activity"><img src={userClock} alt="Activity" /></A>
<A class="md:hidden p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="/activity"><img src={userClock} alt="Activity" class="h-8 w-8" /></A>
</header>
<Show when={!state.wallet_loading}>
<OnboardWarning />

View File

@@ -1,5 +1,5 @@
import { Show, Suspense } from "solid-js";
import { Button, ButtonLink, FancyCard, 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";
@@ -15,10 +15,10 @@ function prettyPrintAmount(n?: number | bigint): string {
export function LoadingShimmer() {
return (<div class="flex flex-col gap-2 animate-pulse">
<h1 class="text-4xl font-light">
<div class="w-[12rem] rounded bg-neutral-700 h-[2.5rem]"></div>
<div class="w-[12rem] rounded bg-neutral-700 h-[2.5rem]" />
</h1>
<h2 class="text-xl font-light text-white/70" >
<div class="w-[8rem] rounded bg-neutral-700 h-[1.75rem]"></div>
<div class="w-[8rem] rounded bg-neutral-700 h-[1.75rem]" />
</h2>
</div>)
}
@@ -26,7 +26,7 @@ export function LoadingShimmer() {
const STYLE = "px-2 py-1 rounded-xl border border-neutral-400 text-sm flex gap-2 items-center font-semibold"
export default function BalanceBox(props: { loading?: boolean }) {
const [state, actions] = useMegaStore();
const [state, _actions] = useMegaStore();
const emptyBalance = () => (state.balance?.confirmed || 0n) === 0n && (state.balance?.lightning || 0n) === 0n
@@ -46,7 +46,7 @@ export default function BalanceBox(props: { loading?: boolean }) {
<Amount amountSats={state.balance?.confirmed} showFiat />
<div class="self-end justify-self-end">
<A href="/swap" class={STYLE}>
<img src={shuffle} alt="swap" />
<img src={shuffle} alt="swap" class="h-8 w-8" />
</A>
</div>
</div>

View File

@@ -8,11 +8,10 @@ import {
Switch,
createMemo,
} from "solid-js"
import { Hr, TinyButton, VStack } from "~/components/layout"
import { Hr, ModalCloseButton, TinyButton, VStack } from "~/components/layout"
import { MutinyInvoice } from "@mutinywallet/mutiny-wasm"
import { OnChainTx } from "./Activity"
import close from "~/assets/icons/close.svg"
import bolt from "~/assets/icons/bolt-black.svg"
import chain from "~/assets/icons/chain-black.svg"
import copyIcon from "~/assets/icons/copy.svg"
@@ -27,9 +26,9 @@ import mempoolTxUrl from "~/utils/mempoolTxUrl"
import { Network } from "~/logic/mutinyWalletSetup"
import { AmountSmall } from "./Amount"
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 =
export const OVERLAY = "fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
export const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center"
export const DIALOG_CONTENT =
"max-w-[500px] w-[90vw] max-h-[100dvh] overflow-y-scroll disable-scrollbars mx-4 p-4 bg-neutral-800/80 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
function LightningHeader(props: { info: MutinyInvoice }) {
@@ -37,7 +36,7 @@ function LightningHeader(props: { info: MutinyInvoice }) {
const tags = createMemo(() => {
if (props.info.labels.length) {
let contact = state.mutiny_wallet?.get_contact(props.info.labels[0])
const contact = state.mutiny_wallet?.get_contact(props.info.labels[0])
if (contact) {
return [tagToMutinyTag(contact)]
} else {
@@ -64,7 +63,9 @@ function LightningHeader(props: { info: MutinyInvoice }) {
/>
<For each={tags()}>
{(tag) => (
<TinyButton tag={tag} onClick={() => {}}>
<TinyButton tag={tag} onClick={() => {
// noop
}}>
{tag.name}
</TinyButton>
)}
@@ -78,7 +79,7 @@ function OnchainHeader(props: { info: OnChainTx }) {
const tags = createMemo(() => {
if (props.info.labels.length) {
let contact = state.mutiny_wallet?.get_contact(props.info.labels[0])
const contact = state.mutiny_wallet?.get_contact(props.info.labels[0])
if (contact) {
return [tagToMutinyTag(contact)]
} else {
@@ -117,7 +118,9 @@ function OnchainHeader(props: { info: OnChainTx }) {
/>
<For each={tags()}>
{(tag) => (
<TinyButton tag={tag} onClick={() => {}}>
<TinyButton tag={tag} onClick={() => {
// noop
}}>
{tag.name}
</TinyButton>
)}
@@ -250,7 +253,7 @@ export function DetailsModal(props: {
return (
<Dialog.Root
open={props.open}
onOpenChange={(isOpen) => props.setOpen(isOpen)}
onOpenChange={props.setOpen}
>
<Dialog.Portal>
<Dialog.Overlay class={OVERLAY} />
@@ -259,12 +262,7 @@ export function DetailsModal(props: {
<div class="flex justify-between mb-2">
<div />
<Dialog.CloseButton>
<button
tabindex="-1"
class="self-center hover:bg-white/10 rounded-lg active:bg-m-blue "
>
<img src={close} alt="Close" class="w-8 h-8" />
</button>
<ModalCloseButton />
</Dialog.CloseButton>
</div>
<Dialog.Title>

View File

@@ -1,5 +1,5 @@
import { Title } from "solid-start";
import { Button, ButtonLink, DefaultMain, LargeHeader, SafeArea, SmallHeader } from "~/components/layout";
import { Button, DefaultMain, LargeHeader, SafeArea, SmallHeader } from "~/components/layout";
export default function ErrorDisplay(props: { error: Error }) {
return (

View File

@@ -34,14 +34,16 @@ export function ImportExport() {
reject(new Error("No text found in file"));
}
};
fileReader.onerror = e => reject(new Error("File read error"));
fileReader.onerror = _e => reject(new Error("File read error"));
fileReader.readAsText(file, "UTF-8");
});
// This should throw if there's a parse error, so we won't end up clearing
JSON.parse(text);
if (text) {
JSON.parse(text);
MutinyWallet.import_json(text);
}
MutinyWallet.import_json(text);
if (state.mutiny_wallet) {
await state.mutiny_wallet.stop();
}

View File

@@ -1,5 +1,4 @@
import { ParentComponent } from "solid-js";
import { ButtonLink, SmallHeader } from "~/components/layout"
import info from "~/assets/icons/info.svg"
export const InfoBox: ParentComponent<{ accent: "red" | "blue" | "green" | "white" }> = (props) => {

View File

@@ -1,42 +1,36 @@
import { Dialog } from "@kobalte/core";
import { JSX, createMemo } from "solid-js";
import { Button, SmallHeader } from "~/components/layout";
import { useCopy } from "~/utils/useCopy";
import { ModalCloseButton, SmallHeader } from "~/components/layout";
import { DIALOG_CONTENT, DIALOG_POSITIONER, OVERLAY } from "~/components/DetailsModal";
import { CopyButton } from "./ShareCard";
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 = "max-w-[600px] max-h-full mx-4 p-4 bg-neutral-900/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
export function JsonModal(props: { title: string, open: boolean, data?: unknown, setOpen: (open: boolean) => void, children?: JSX.Element }) {
const json = createMemo(() => JSON.stringify(props.data, null, 2));
const [copy, copied] = useCopy({ copiedTimeout: 1000 });
export function JsonModal(props: { title: string, open: boolean, plaintext?: string, data?: unknown, setOpen: (open: boolean) => void, children?: JSX.Element }) {
const json = createMemo(() => props.plaintext ? props.plaintext : JSON.stringify(props.data, null, 2));
return (
<Dialog.Root open={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
<Dialog.Root open={props.open} onOpenChange={props.setOpen}>
<Dialog.Portal>
<Dialog.Overlay class={OVERLAY} />
<div class={DIALOG_POSITIONER}>
<Dialog.Content class={DIALOG_CONTENT}>
<div class="flex justify-between mb-2">
<div class="flex justify-between mb-2 items-center">
<Dialog.Title>
<SmallHeader>
{props.title}
</SmallHeader>
</Dialog.Title>
<Dialog.CloseButton>
<code>X</code>
<ModalCloseButton />
</Dialog.CloseButton>
</div>
<Dialog.Description class="flex flex-col gap-4">
<div class="bg-white/10 rounded-xl max-h-[50vh] overflow-y-scroll disable-scrollbars p-4">
<Dialog.Description class="flex flex-col gap-4 items-center">
<div class="bg-white/5 rounded-xl max-h-[50vh] overflow-y-scroll disable-scrollbars p-4">
<pre class="whitespace-pre-wrap break-all">
{json()}
</pre>
</div>
{props.children}
<Button onClick={(_) => copy(json() ?? "")}>{copied() ? "Copied" : "Copy"}</Button>
<Button onClick={(_) => props.setOpen(false)}>Close</Button>
<CopyButton title="Copy" text={json()} />
</Dialog.Description>
</Dialog.Content>
</div>

View File

@@ -7,13 +7,15 @@ export default function Scanner(props: { onResult: (result: string) => void }) {
// TODO: not sure it's appropriate to use a signal for this but it works!
const [scanner, setScanner] = createSignal<QrScanner | null>(null);
const handleResult = (result: { data: string }) => {
props.onResult(result.data);
}
onMount(() => {
if (container) {
const newScanner = new QrScanner(
container,
(result: { data: string }) => {
props.onResult(result.data);
},
handleResult,
{
returnDetailedScanResult: true,
}

View File

@@ -2,26 +2,26 @@ import type { Component } from 'solid-js'
import { Show } from 'solid-js'
// eslint-disable-next-line import/no-unresolved
import { useRegisterSW } from 'virtual:pwa-register/solid'
import { Button, Card } from '~/components/layout'
const ReloadPrompt: Component = () => {
const {
offlineReady: [offlineReady, setOfflineReady],
needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker,
offlineReady: [offlineReady, _setOfflineReady],
needRefresh: [needRefresh, _setNeedRefresh],
updateServiceWorker: _update,
} = useRegisterSW({
onRegistered(r: ServiceWorkerRegistration) {
console.log('SW Registered: ' + r.scope)
immediate: true,
onRegisteredSW(swUrl, r) {
console.log('SW Registered: ' + r?.scope)
},
onRegisterError(error: Error) {
console.log('SW registration error', error)
},
})
const close = () => {
setOfflineReady(false)
setNeedRefresh(false)
}
// const close = () => {
// setOfflineReady(false)
// setNeedRefresh(false)
// }
return (
<Show when={offlineReady() || needRefresh()}>

View File

@@ -1,12 +1,11 @@
import { Button, Card, NiceP, VStack } from "~/components/layout";
import { useMegaStore } from "~/state/megaStore";
import { downloadTextFile } from "~/utils/download";
export function Restart() {
const [state, _] = useMegaStore()
async function handleStop() {
const result = await state.mutiny_wallet?.stop()
await state.mutiny_wallet?.stop()
}
return (

View File

@@ -6,7 +6,7 @@ import eyeIcon from "~/assets/icons/eye.svg"
import { Show, createSignal } from "solid-js";
import { JsonModal } from "./JsonModal";
const STYLE = "px-4 py-2 rounded-xl border-2 border-white flex gap-2 items-center font-semibold"
const STYLE = "px-4 py-2 rounded-xl border-2 border-white flex gap-2 items-center font-semibold hover:text-m-blue transition-colors"
export function ShareButton(props: { receiveString: string }) {
async function share(receiveString: string) {
@@ -34,7 +34,7 @@ export function StringShower(props: { text: string }) {
const [open, setOpen] = createSignal(false);
return (
<>
<JsonModal open={open()} data={props.text} title="Details" setOpen={setOpen} />
<JsonModal open={open()} plaintext={props.text} title="Details" setOpen={setOpen} />
<div class="w-full grid grid-cols-[minmax(0,_1fr)_auto]">
<pre class="truncate text-neutral-400">{props.text}</pre>
<button class="w-[2rem]" onClick={() => setOpen(true)}>

View File

@@ -44,6 +44,11 @@ export function TagEditor(props: {
}
};
// FIXME: eslint is mad about reactivity
const onTagTap = (tag: MutinyTagItem) => {
props.setSelectedValues([...props.selectedValues!, tag]);
};
return (
<div class="flex flex-col gap-2 flex-shrink flex-1" >
<Select
@@ -57,7 +62,8 @@ export function TagEditor(props: {
<Show when={availableTags() && availableTags()!.length > 0}>
<For each={availableTags()!.slice(0, 3)}>
{(tag) => (
<TinyButton tag={tag} onClick={() => props.setSelectedValues([...props.selectedValues!, tag])}>
// eslint-disable-next-line solid/reactivity
<TinyButton tag={tag} onClick={() => onTagTap(tag)}>
{tag.name}
</TinyButton>
)}

View File

@@ -15,8 +15,13 @@ type FullscreenModalProps = {
}
export function FullscreenModal(props: FullscreenModalProps) {
const onNice = () => {
props.onConfirm ? props.onConfirm() : props.setOpen(false)
}
return (
<Dialog.Root open={props.open} onOpenChange={(isOpen) => props.setOpen(isOpen)}>
<Dialog.Root open={props.open} onOpenChange={props.setOpen}>
<Dialog.Portal>
<div class={DIALOG_POSITIONER}>
<Dialog.Content class={DIALOG_CONTENT}>
@@ -34,7 +39,7 @@ export function FullscreenModal(props: FullscreenModalProps) {
{props.children}
</Dialog.Description>
<div class="w-full flex">
<Button onClick={(_) => props.onConfirm ? props.onConfirm() : props.setOpen(false)}>{props.confirmText ?? "Nice"}</Button>
<Button onClick={onNice}>{props.confirmText ?? "Nice"}</Button>
</div>
</Dialog.Content>
</div>

View File

@@ -23,7 +23,7 @@ export default function Linkify(props: LinkifyProps): JSX.Element {
links.push(beforeLink);
}
links.push(<a href={href} target="_blank" rel="noopener noreferrer">{link}</a>);
links.push(<a href={href} class="break-all" target="_blank" rel="noopener noreferrer">{link}</a>);
}
const remainingText = text.slice(lastIndex);

View File

@@ -7,7 +7,7 @@ type Choices = { value: string, label: string, caption: string }[]
export function StyledRadioGroup(props: { value: string, choices: Choices, onValueChange: (value: string) => void, small?: boolean, accent?: "red" | "white" }) {
return (
// TODO: rewrite this with CVA, props are bad for tailwind
<RadioGroup.Root value={props.value} onChange={(e) => props.onValueChange(e)}
<RadioGroup.Root value={props.value} onChange={props.onValueChange}
class={"grid w-full gap-4"}
classList={{ "grid-cols-2": props.choices.length === 2, "grid-cols-3": props.choices.length === 3, "gap-2": props.small }}
>

View File

@@ -1,4 +1,4 @@
import { JSX, ParentComponent, Show, Suspense, createResource, createSignal } from "solid-js"
import { JSX, ParentComponent, Show, Suspense, createResource } from "solid-js"
import Linkify from "./Linkify"
import { Button, ButtonLink } from "./Button"
import { Checkbox as KCheckbox, Separator } from "@kobalte/core"
@@ -6,6 +6,7 @@ import { useMegaStore } from "~/state/megaStore"
import check from "~/assets/icons/check.svg"
import { MutinyTagItem } from "~/utils/tags"
import { generateGradient } from "~/utils/gradientHash"
import close from "~/assets/icons/close.svg"
export {
Button,
@@ -128,8 +129,8 @@ export const NiceP: ParentComponent = (props) => {
export const TinyButton: ParentComponent<{ onClick: () => void, tag?: MutinyTagItem }> = (props) => {
// TODO: don't need to run this if it's not a contact
const [gradient] = createResource(props.tag?.name, async (name: string) => {
return generateGradient(name || "?")
const [gradient] = createResource(async () => {
return generateGradient(props.tag?.name || "?")
})
const bg = () => (props.tag?.name && props.tag?.kind === "Contact") ? gradient() : "rgb(255 255 255 / 0.1)"
@@ -161,4 +162,12 @@ export function Checkbox(props: { label: string, checked: boolean, onChange: (ch
<KCheckbox.Label class="flex-1 text-xl font-light">{props.label}</KCheckbox.Label>
</KCheckbox.Root>
)
}
export function ModalCloseButton() {
return (<button
class="self-center justify-self-center hover:bg-white/10 rounded-lg active:bg-m-blue"
>
<img src={close} alt="Close" class="w-8 h-8" />
</button>)
}

View File

@@ -35,12 +35,12 @@ export function WaitlistAlreadyIn() {
const [posts] = createResource("", postsFetcher);
return (
<main class='flex flex-col gap-2 sm:gap-4 py-8 px-4 max-w-xl mx-auto items-start drop-shadow-blue-glow'>
<main class='flex flex-col gap-4 sm:gap-4 py-8 px-4 max-w-xl mx-auto items-start drop-shadow-blue-glow'>
<a href="https://mutinywallet.com">
<img src={logo} class="h-10" alt="logo" />
</a>
<h1 class="text-4xl font-bold">You're on a list!</h1>
<h2 class="text-xl">
<h2 class="text-xl pr-4">
We'll message you when Mutiny Wallet is ready.
</h2>
<div class="px-4 sm:px-8 py-8 rounded-xl bg-half-black w-full">