mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2026-01-08 16:54:21 +01:00
styling pass
This commit is contained in:
@@ -13,7 +13,8 @@ export function Amount(props: { amountSats: bigint | number | undefined, showFia
|
||||
const [state, _] = useMegaStore()
|
||||
|
||||
async function getPrice() {
|
||||
return await state.node_manager?.get_bitcoin_price()
|
||||
// return await state.node_manager?.get_bitcoin_price()
|
||||
return 30000
|
||||
}
|
||||
|
||||
const [price] = createResource(getPrice)
|
||||
|
||||
@@ -18,7 +18,7 @@ function SingleDigitButton(props: { character: string, onClick: (c: string) => v
|
||||
);
|
||||
}
|
||||
|
||||
export function AmountEditable(props: { amountSats: number | bigint, setAmountSats: (s: string) => void }) {
|
||||
export function AmountEditable(props: { amountSats: string, setAmountSats: (s: string) => void, onSave?: () => void }) {
|
||||
const [isFullscreen, setIsFullscreen] = createSignal(false);
|
||||
|
||||
function toggleFullscreen() {
|
||||
@@ -26,7 +26,7 @@ export function AmountEditable(props: { amountSats: number | bigint, setAmountSa
|
||||
}
|
||||
|
||||
// TODO: validate this doesn't need to be reactive and can be "initialAmountSats"
|
||||
const [displayAmount, setDisplayAmount] = createSignal(props.amountSats.toString() || "0");
|
||||
const [displayAmount, setDisplayAmount] = createSignal(props.amountSats || "0");
|
||||
|
||||
let inputRef!: HTMLInputElement;
|
||||
|
||||
@@ -113,7 +113,8 @@ export function AmountEditable(props: { amountSats: number | bigint, setAmountSa
|
||||
const [state, _] = useMegaStore()
|
||||
|
||||
async function getPrice() {
|
||||
return await state.node_manager?.get_bitcoin_price()
|
||||
// return await state.node_manager?.get_bitcoin_price()
|
||||
return 30000
|
||||
}
|
||||
|
||||
const [price] = createResource(getPrice)
|
||||
@@ -130,6 +131,10 @@ export function AmountEditable(props: { amountSats: number | bigint, setAmountSa
|
||||
} else {
|
||||
props.setAmountSats(displayAmount());
|
||||
toggleFullscreen();
|
||||
// This is so the parent can focus the next input if it wants to
|
||||
if (props.onSave) {
|
||||
props.onSave();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,8 @@ export function AmountInput(props: AmountInputProps) {
|
||||
const [state, _] = useMegaStore()
|
||||
|
||||
async function getPrice() {
|
||||
return await state.node_manager?.get_bitcoin_price()
|
||||
// return await state.node_manager?.get_bitcoin_price()
|
||||
return 30000
|
||||
}
|
||||
|
||||
const [activeCurrency, setActiveCurrency] = createSignal<ActiveCurrency>("sats")
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
import logo from '~/assets/icons/mutiny-logo.svg';
|
||||
import { NodeManagerGuard, SafeArea } from "~/components/layout";
|
||||
import { DefaultMain, NodeManagerGuard, SafeArea } from "~/components/layout";
|
||||
import BalanceBox from "~/components/BalanceBox";
|
||||
import NavBar from "~/components/NavBar";
|
||||
|
||||
// TODO: use this reload prompt for real
|
||||
import ReloadPrompt from "~/components/Reload";
|
||||
import KitchenSink from './KitchenSink';
|
||||
import { Scan } from '~/assets/svg/Scan';
|
||||
import { A } from 'solid-start';
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<NodeManagerGuard>
|
||||
<SafeArea>
|
||||
<main class='flex flex-col gap-4 py-8 px-4 max-w-[800px] mx-auto'>
|
||||
<header>
|
||||
<img src={logo} class="App-logo" alt="logo" />
|
||||
<DefaultMain>
|
||||
<header class="w-full flex justify-between items-center mt-4 mb-2">
|
||||
<img src={logo} class="h-10" alt="logo" />
|
||||
<A class="p-2 hover:bg-white/5 rounded-lg active:bg-m-blue" href="scanner"><Scan /></A>
|
||||
</header>
|
||||
<BalanceBox />
|
||||
<ReloadPrompt />
|
||||
<KitchenSink />
|
||||
{/* safety div */}
|
||||
<div class="h-32" />
|
||||
</main>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="home" />
|
||||
</SafeArea>
|
||||
</NodeManagerGuard>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createResource, Show, Suspense } from "solid-js";
|
||||
import { createResource, createSignal, Show, Suspense } from "solid-js";
|
||||
import { Button, ButtonLink, FancyCard } from "~/components/layout";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { Amount } from "./Amount";
|
||||
@@ -10,46 +10,68 @@ function prettyPrintAmount(n?: number | bigint): string {
|
||||
return n.toLocaleString()
|
||||
}
|
||||
|
||||
function SyncingIndicator() {
|
||||
return (
|
||||
<div class="box-border animate-pulse px-2 py-1 -my-1 bg-white/70 rounded text-xs uppercase text-black">Syncing</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default function BalanceBox() {
|
||||
const [state, _] = useMegaStore();
|
||||
|
||||
const fetchBalance = async () => {
|
||||
console.log("Refetching balance");
|
||||
const fetchOnchainBalance = async () => {
|
||||
console.log("Refetching onchain balance");
|
||||
await state.node_manager?.sync();
|
||||
const balance = await state.node_manager?.get_balance();
|
||||
return balance
|
||||
};
|
||||
|
||||
const [balance, { refetch: refetchBalance }] = createResource(fetchBalance);
|
||||
// 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();
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<FancyCard title="Lightning">
|
||||
<Suspense fallback={<Amount amountSats={0} showFiat loading={true} />}>
|
||||
<Amount amountSats={balance()?.lightning} showFiat loading={balance.loading} />
|
||||
<Show when={lnBalance()}>
|
||||
<Amount amountSats={lnBalance()?.lightning} showFiat />
|
||||
</Show>
|
||||
</Suspense>
|
||||
</FancyCard>
|
||||
<div class="flex gap-2 py-4">
|
||||
<ButtonLink href="/send" intent="green">Send</ButtonLink>
|
||||
<ButtonLink href="/receive" intent="blue">Receive</ButtonLink>
|
||||
</div>
|
||||
<FancyCard title="On-Chain">
|
||||
<FancyCard title="On-Chain" tag={onChainBalance.loading && <SyncingIndicator />}>
|
||||
<Suspense fallback={<Amount amountSats={0} showFiat loading={true} />}>
|
||||
<Amount amountSats={balance()?.confirmed} showFiat loading={balance.loading} />
|
||||
<div onClick={refetchBalance}>
|
||||
<Amount amountSats={onChainBalance()?.confirmed} showFiat loading={onChainBalance.loading} />
|
||||
</div>
|
||||
</Suspense>
|
||||
<Suspense>
|
||||
<Show when={balance()?.unconfirmed}>
|
||||
<Show when={onChainBalance()?.unconfirmed}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<header class='text-sm font-semibold uppercase text-white/50'>
|
||||
Unconfirmed Balance
|
||||
</header>
|
||||
<div class="text-white/50">
|
||||
{prettyPrintAmount(balance()?.unconfirmed)} <span class='text-sm'>SATS</span>
|
||||
{prettyPrintAmount(onChainBalance()?.unconfirmed)} <span class='text-sm'>SATS</span>
|
||||
</div>
|
||||
</div>
|
||||
</Show>
|
||||
</Suspense>
|
||||
<Button onClick={() => refetchBalance()}>Sync</Button>
|
||||
</FancyCard>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import mutiny_m from '~/assets/icons/m.svg';
|
||||
import scan from '~/assets/icons/scan.svg';
|
||||
// import scan from '~/assets/icons/scan.svg';
|
||||
import airplane from '~/assets/icons/airplane.svg';
|
||||
import settings from '~/assets/icons/settings.svg';
|
||||
|
||||
@@ -8,21 +8,16 @@ import { A } from "solid-start";
|
||||
type ActiveTab = 'home' | 'scan' | 'send' | 'settings' | 'none';
|
||||
|
||||
export default function NavBar(props: { activeTab: ActiveTab }) {
|
||||
const activeStyle = 'h-full border-t-2 border-b-2 border-b-sidebar-gray flex flex-col justify-center md:border-t-0 md:border-b-0 md:p-2 md:bg-white/20 md:rounded-xl'
|
||||
const inactiveStyle = "md:p-2 md:hover:bg-white/10 md:rounded-xl"
|
||||
const activeStyle = 'h-full border-t-2 border-b-2 border-b-sidebar-gray flex flex-col justify-center md:border-t-0 md:border-b-0 md:p-2 md:bg-black md:rounded-lg'
|
||||
const inactiveStyle = "md:p-2 md:hover:bg-white/5 md:rounded-lg md:active:bg-m-blue"
|
||||
return (
|
||||
<nav class='bg-sidebar-gray fixed bottom-0 shadow-lg z-40 w-full safe-bottom md:top-0 md:bottom-auto md:left-0 md:w-auto md:h-full'>
|
||||
<nav class='backdrop-blur-xl fixed bottom-0 md:shadow-none shadow-above z-40 w-full safe-bottom md:top-0 md:bottom-auto md:left-0 md:w-auto md:h-full'>
|
||||
<ul class='h-16 flex justify-between px-16 items-center md:flex-col md:justify-start md:gap-4 md:px-4 md:mt-4'>
|
||||
<li class={props.activeTab === "home" ? activeStyle : inactiveStyle}>
|
||||
<A href="/">
|
||||
<img src={mutiny_m} alt="home" />
|
||||
</A>
|
||||
</li>
|
||||
<li class={props.activeTab === "scan" ? activeStyle : inactiveStyle}>
|
||||
<A href="/scanner">
|
||||
<img src={scan} alt="scan" />
|
||||
</A>
|
||||
</li>
|
||||
<li class={props.activeTab === "send" ? activeStyle : inactiveStyle}>
|
||||
<A href="/send">
|
||||
<img src={airplane} alt="send" />
|
||||
|
||||
@@ -3,12 +3,13 @@ import { children, JSX, ParentComponent, splitProps } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import { A } from "solid-start";
|
||||
|
||||
const button = cva("p-4 rounded-xl text-xl font-semibold disabled:opacity-50 disabled:grayscale transition", {
|
||||
const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 disabled:grayscale transition", {
|
||||
variants: {
|
||||
// TODO: button hover has to work different than buttonlinks (like disabled state)
|
||||
intent: {
|
||||
active: "bg-white text-black border border-white hover:text-[#3B6CCC]",
|
||||
inactive: "bg-black text-white border border-white hover:text-[#3B6CCC]",
|
||||
glowy: "bg-black/10 shadow-xl text-white border border-m-blue hover:m-blue-dark hover:text-m-blue",
|
||||
blue: "bg-m-blue text-white shadow-inner-button hover:bg-m-blue-dark text-shadow-button",
|
||||
red: "bg-m-red text-white shadow-inner-button hover:bg-m-red-dark text-shadow-button",
|
||||
green: "bg-m-green text-white shadow-inner-button hover:bg-m-green-dark text-shadow-button",
|
||||
@@ -17,6 +18,7 @@ const button = cva("p-4 rounded-xl text-xl font-semibold disabled:opacity-50 dis
|
||||
flex: "flex-1",
|
||||
pad: "px-8",
|
||||
small: "px-4 py-2 w-auto text-lg",
|
||||
xs: "px-2 py-1 w-auto rounded-lg font-normal text-base"
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
30
src/components/layout/Radio.tsx
Normal file
30
src/components/layout/Radio.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { RadioGroup } from "@kobalte/core";
|
||||
import { For } from "solid-js";
|
||||
|
||||
type Choices = { value: string, label: string, caption: string }[]
|
||||
|
||||
// TODO: how could would it be if we could just pass the estimated fees in here?
|
||||
export function StyledRadioGroup(props: { value: string, choices: Choices, onValueChange: (value: string) => void }) {
|
||||
return (
|
||||
<RadioGroup.Root value={props.value} onValueChange={(e) => props.onValueChange(e)} class="grid w-full gap-4 grid-cols-2">
|
||||
<For each={props.choices}>
|
||||
{choice =>
|
||||
<RadioGroup.Item value={choice.value} class="ui-checked:bg-white bg-white/10 rounded outline outline-black/50 ui-checked:outline-m-blue ui-checked:outline-2">
|
||||
<div class="py-3 px-4">
|
||||
<RadioGroup.ItemInput class="radio__input " />
|
||||
<RadioGroup.ItemControl class="radio__control">
|
||||
<RadioGroup.ItemIndicator class="radio__indicator" />
|
||||
</RadioGroup.ItemControl>
|
||||
<RadioGroup.ItemLabel class="ui-checked:text-m-blue text-neutral-400">
|
||||
<div class="block">
|
||||
<div class="text-lg font-semibold">{choice.label}</div>
|
||||
<div class="text-lg font-light">{choice.caption}</div>
|
||||
</div>
|
||||
</RadioGroup.ItemLabel>
|
||||
</div>
|
||||
</RadioGroup.Item>
|
||||
}
|
||||
</For>
|
||||
</RadioGroup.Root>
|
||||
)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ParentComponent, Show, Suspense } from "solid-js"
|
||||
import { JSX, ParentComponent, Show, Suspense } from "solid-js"
|
||||
import Linkify from "./Linkify"
|
||||
import { Button, ButtonLink } from "./Button"
|
||||
import { Separator } from "@kobalte/core"
|
||||
@@ -8,7 +8,7 @@ const SmallHeader: ParentComponent = (props) => <header class='text-sm font-semi
|
||||
|
||||
const Card: ParentComponent<{ title?: string }> = (props) => {
|
||||
return (
|
||||
<div class='rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]'>
|
||||
<div class='rounded-xl p-4 flex flex-col gap-2 bg-neutral-800'>
|
||||
{props.title && <SmallHeader>{props.title}</SmallHeader>}
|
||||
{props.children}
|
||||
</div>
|
||||
@@ -24,29 +24,37 @@ const InnerCard: ParentComponent<{ title?: string }> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
const FancyCard: ParentComponent<{ title?: string }> = (props) => {
|
||||
const FancyCard: ParentComponent<{ title?: string, tag?: JSX.Element }> = (props) => {
|
||||
return (
|
||||
<div class='border border-white rounded-xl border-b-4 p-4 flex flex-col gap-2'>
|
||||
{props.title && <SmallHeader>{props.title}</SmallHeader>}
|
||||
<div class='border border-black/50 rounded-xl border-b-4 p-4 flex flex-col gap-2 bg-neutral-800/50 shadow-fancy-card'>
|
||||
<div class="w-full flex justify-between items-center">
|
||||
{props.title && <SmallHeader>{props.title}</SmallHeader>}
|
||||
{props.tag && props.tag}
|
||||
</div>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const SafeArea: ParentComponent<{ main?: boolean }> = (props) => {
|
||||
const SafeArea: ParentComponent = (props) => {
|
||||
return (
|
||||
<div class="safe-top safe-left safe-right safe-bottom">
|
||||
<div class="disable-scrollbars max-h-screen h-full overflow-y-scroll md:pl-[8rem] md:pr-[6rem]">
|
||||
<Show when={props.main} fallback={props.children}>
|
||||
<main class='flex flex-col py-8 px-4 items-center'>
|
||||
{props.children}
|
||||
</main>
|
||||
</Show>
|
||||
{props.children}
|
||||
<div class="h-32" />
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
|
||||
const DefaultMain: ParentComponent = (props) => {
|
||||
return (
|
||||
<main class="w-full max-w-[600px] flex flex-col gap-4 mx-auto p-4">
|
||||
{props.children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function FullscreenLoader() {
|
||||
return (
|
||||
<div class="w-screen h-screen flex justify-center items-center">
|
||||
@@ -78,4 +86,8 @@ const LoadingSpinner = () => {
|
||||
|
||||
const Hr = () => <Separator.Root class="my-4 border-white/20" />
|
||||
|
||||
export { SmallHeader, Card, SafeArea, LoadingSpinner, Button, ButtonLink, Linkify, Hr, NodeManagerGuard, FullscreenLoader, InnerCard, FancyCard }
|
||||
const LargeHeader: ParentComponent = (props) => {
|
||||
return (<h1 class="text-4xl font-semibold uppercase border-b-2 border-b-white my-4">{props.children}</h1>)
|
||||
}
|
||||
|
||||
export { SmallHeader, Card, SafeArea, LoadingSpinner, Button, ButtonLink, Linkify, Hr, NodeManagerGuard, FullscreenLoader, InnerCard, FancyCard, DefaultMain, LargeHeader }
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply text-white bg-black;
|
||||
@apply bg-fixed bg-no-repeat bg-fade-to-blue;
|
||||
@apply text-white bg-neutral-900;
|
||||
overscroll-behavior-y: none;
|
||||
min-height: 100.3%;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { TextField } from "@kobalte/core";
|
||||
import { createMemo, createResource, createSignal, Match, Switch } from "solid-js";
|
||||
import { QRCodeSVG } from "solid-qr-code";
|
||||
import { AmountEditable } from "~/components/AmountEditable";
|
||||
import { AmountInput } from "~/components/AmountInput";
|
||||
import { Button, Card, NodeManagerGuard, SafeArea, SmallHeader } from "~/components/layout";
|
||||
import { Button, Card, DefaultMain, LargeHeader, NodeManagerGuard, SafeArea, SmallHeader } from "~/components/layout";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
import { satsToUsd } from "~/utils/conversions";
|
||||
@@ -83,36 +84,44 @@ export default function Receive() {
|
||||
}
|
||||
|
||||
async function getPrice() {
|
||||
return await state.node_manager?.get_bitcoin_price()
|
||||
// return await state.node_manager?.get_bitcoin_price()
|
||||
return 30000
|
||||
}
|
||||
|
||||
const [price] = createResource(getPrice)
|
||||
|
||||
const amountInUsd = createMemo(() => satsToUsd(price(), parseInt(amount()) || 0, true))
|
||||
|
||||
function handleAmountSave() {
|
||||
console.error("focusing label input...")
|
||||
console.error(labelInput)
|
||||
labelInput.focus();
|
||||
}
|
||||
|
||||
return (
|
||||
<NodeManagerGuard>
|
||||
|
||||
<SafeArea main>
|
||||
<div class="w-full max-w-[400px] flex flex-col gap-4">
|
||||
<h1 class="text-2xl font-semibold uppercase border-b-2 border-b-white">Receive Bitcoin</h1>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<LargeHeader>Receive Bitcoin</LargeHeader>
|
||||
<Switch>
|
||||
<Match when={!unified() || receiveState() === "edit"}>
|
||||
<form class="border border-white/20 rounded-xl p-2 flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
{/* TODO this initial amount is not reactive, hope that's okay? */}
|
||||
<AmountInput initialAmountSats={amount()} setAmountSats={setAmount} refSetter={el => amountInput = el} />
|
||||
<AmountEditable amountSats={amount() || "0"} setAmountSats={setAmount} onSave={handleAmountSave} />
|
||||
<div>
|
||||
<Button intent="glowy" layout="xs">Tag the sender</Button>
|
||||
</div>
|
||||
<form class="flex flex-col gap-4" onSubmit={onSubmit} >
|
||||
<TextField.Root
|
||||
value={label()}
|
||||
onValueChange={setLabel}
|
||||
class="flex flex-col gap-2"
|
||||
>
|
||||
<TextField.Label class="text-sm font-semibold uppercase" >Label (private)</TextField.Label>
|
||||
<TextField.Label><SmallHeader>Label (private)</SmallHeader></TextField.Label>
|
||||
<TextField.Input
|
||||
autofocus
|
||||
ref={el => labelInput = el}
|
||||
class="w-full p-2 rounded-lg text-black" />
|
||||
</TextField.Root>
|
||||
<Button disabled={!amount() || !label()} layout="small" type="submit">Create Invoice</Button>
|
||||
<Button disabled={!amount() || !label()} intent="green" type="submit">Create Invoice</Button>
|
||||
</form >
|
||||
</Match>
|
||||
<Match when={unified() && receiveState() === "show"}>
|
||||
@@ -139,7 +148,7 @@ export default function Receive() {
|
||||
</Card>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="none" />
|
||||
</SafeArea >
|
||||
</NodeManagerGuard>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { TextField } from "@kobalte/core";
|
||||
import { Show, createResource, createSignal, onMount } from "solid-js";
|
||||
import { RadioGroup, TextField } from "@kobalte/core";
|
||||
import { For, Show, createResource, createSignal, onMount } from "solid-js";
|
||||
import { Amount } from "~/components/Amount";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import { Button, ButtonLink, InnerCard, SafeArea, SmallHeader } from "~/components/layout";
|
||||
import { Button, ButtonLink, DefaultMain, FancyCard, InnerCard, LargeHeader, SafeArea, SmallHeader } from "~/components/layout";
|
||||
import { Paste } from "~/assets/svg/Paste";
|
||||
import { Scan } from "~/assets/svg/Scan";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
@@ -10,9 +10,12 @@ import { MutinyInvoice, NodeManager } from "@mutinywallet/node-manager";
|
||||
import { bip21decode } from "~/utils/TEMPbip21";
|
||||
import { AmountEditable } from "~/components/AmountEditable";
|
||||
import { useLocation } from "solid-start";
|
||||
import { StyledRadioGroup } from "~/components/layout/Radio";
|
||||
|
||||
type SendSource = "lightning" | "onchain";
|
||||
|
||||
const PAYMENT_METHODS = [{ value: "lightning", label: "Lightning", caption: "Fast and cool" }, { value: "onchain", label: "On-chain", caption: "Just like Satoshi did it" }]
|
||||
|
||||
export default function Send() {
|
||||
const [state, _] = useMegaStore();
|
||||
|
||||
@@ -118,46 +121,55 @@ export default function Send() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SafeArea main>
|
||||
<div class="w-full max-w-[400px] flex flex-col gap-4">
|
||||
<h1 class="text-2xl font-semibold uppercase border-b-2 border-b-white">Send Bitcoin</h1>
|
||||
<SafeArea>
|
||||
<DefaultMain>
|
||||
<LargeHeader>Send Bitcoin</LargeHeader>
|
||||
|
||||
<dl>
|
||||
<dt>
|
||||
<SmallHeader>Destination</SmallHeader>
|
||||
</dt>
|
||||
<dd>
|
||||
<InnerCard>
|
||||
<Show when={destination()} fallback={<div class="flex flex-row gap-4">
|
||||
<Button onClick={handlePaste}>
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<Paste />
|
||||
<span>Paste</span>
|
||||
</div>
|
||||
</Button>
|
||||
<ButtonLink href="/scanner">
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<Scan />
|
||||
<span>Scan QR</span>
|
||||
</div>
|
||||
</ButtonLink>
|
||||
</div>}>
|
||||
<div class="flex flex-col gap-2">
|
||||
<Show when={address()}>
|
||||
<code class="line-clamp-3 text-sm break-all">{source() === "onchain" && "→"} {address()}</code>
|
||||
|
||||
</Show>
|
||||
<Show when={invoice()}>
|
||||
<code class="line-clamp-3 text-sm break-all">{source() === "lightning" && "→"} {invoice()?.bolt11}</code>
|
||||
</Show>
|
||||
|
||||
<Show when={destination()} fallback={<div class="flex flex-row gap-4">
|
||||
<Button onClick={handlePaste}>
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<Paste />
|
||||
<span>Paste</span>
|
||||
</div>
|
||||
{/* <code class="line-clamp-3 text-sm break-all mb-2">{destination()}</code> */}
|
||||
<Show when={destination()}>
|
||||
<Button intent="inactive" onClick={clearAll}>Clear</Button>
|
||||
</Button>
|
||||
<ButtonLink href="/scanner">
|
||||
<div class="flex flex-col gap-2 items-center">
|
||||
<Scan />
|
||||
<span>Scan QR</span>
|
||||
</div>
|
||||
</ButtonLink>
|
||||
</div>}>
|
||||
<div class="flex gap-2 items-center">
|
||||
<Show when={address() && source() === "onchain"}>
|
||||
<code class="truncate text-sm break-all">{"Address: "} {address()}
|
||||
<Show when={description()}>
|
||||
<br />
|
||||
{"Description:"} {description()}
|
||||
</Show>
|
||||
</code>
|
||||
</Show>
|
||||
|
||||
</Show>
|
||||
</InnerCard>
|
||||
<Show when={invoice() && source() === "lightning"}>
|
||||
<code class="truncate text-sm break-all">{"Invoice: "} {invoice()?.bolt11}
|
||||
<Show when={description()}>
|
||||
<br />
|
||||
{"Description:"} {description()}
|
||||
</Show>
|
||||
</code>
|
||||
</Show>
|
||||
<Button class="flex-0" intent="glowy" layout="xs" onClick={clearAll}>Clear</Button>
|
||||
</div>
|
||||
<div class="my-4">
|
||||
{/* if the amount came with the invoice we can't allow setting it */}
|
||||
<Show when={!(invoice()?.amount_sats)} fallback={<Amount amountSats={amountSats() || 0} showFiat />}>
|
||||
<AmountEditable amountSats={amountSats().toString() || "0"} setAmountSats={setAmountSats} />
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
</dd>
|
||||
|
||||
<Show when={address() && invoice()}>
|
||||
@@ -167,38 +179,9 @@ export default function Send() {
|
||||
</SmallHeader>
|
||||
</dt>
|
||||
<dd>
|
||||
<div class="flex gap-4 items-start">
|
||||
<Button onClick={() => setSource("lightning")} intent={source() === "lightning" ? "active" : "inactive"} layout="small">Lightning</Button>
|
||||
<Button onClick={() => setSource("onchain")} intent={source() === "onchain" ? "active" : "inactive"} layout="small">On-Chain</Button>
|
||||
</div>
|
||||
<StyledRadioGroup value={source()} onValueChange={setSource} choices={PAYMENT_METHODS} />
|
||||
</dd>
|
||||
</Show>
|
||||
<Show when={destination()}>
|
||||
<dt>
|
||||
<SmallHeader>
|
||||
Amount
|
||||
</SmallHeader>
|
||||
</dt>
|
||||
<dd>
|
||||
{/* if the amount came with the invoice we can't allow setting it */}
|
||||
<Show when={!(invoice()?.amount_sats)} fallback={<Amount amountSats={amountSats() || 0} showFiat />}>
|
||||
<AmountEditable amountSats={amountSats() || 0} setAmountSats={setAmountSats} />
|
||||
</Show>
|
||||
</dd>
|
||||
</Show>
|
||||
|
||||
|
||||
<Show when={description()}>
|
||||
<dt>
|
||||
|
||||
<SmallHeader>Description</SmallHeader>
|
||||
</dt>
|
||||
<dd>
|
||||
|
||||
<code class="line-clamp-3 text-sm break-all">{description()}</code>
|
||||
</dd>
|
||||
</Show>
|
||||
|
||||
<Show when={destination()}>
|
||||
<TextField.Root
|
||||
value={privateLabel()}
|
||||
@@ -214,7 +197,7 @@ export default function Send() {
|
||||
<TextField.Input
|
||||
autofocus
|
||||
ref={el => labelInput = el}
|
||||
class="w-full p-2 rounded-lg text-black"
|
||||
class="w-full p-2 rounded-lg bg-white/10"
|
||||
placeholder="A helpful reminder of why you spent bitcoin"
|
||||
/>
|
||||
</dd>
|
||||
@@ -222,9 +205,7 @@ export default function Send() {
|
||||
</Show>
|
||||
</dl>
|
||||
<Button disabled={!destination()} intent="blue" onClick={handleSend}>Confirm Send</Button>
|
||||
</div>
|
||||
{/* safety div */}
|
||||
<div class="h-32" />
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="send" />
|
||||
</SafeArea >
|
||||
)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useNavigate } from "solid-start";
|
||||
import { Button, SafeArea } from "~/components/layout";
|
||||
import KitchenSink from "~/components/KitchenSink";
|
||||
import { Button, Card, DefaultMain, Hr, LargeHeader, SafeArea } from "~/components/layout";
|
||||
import NavBar from "~/components/NavBar";
|
||||
import { useMegaStore } from "~/state/megaStore";
|
||||
|
||||
@@ -31,11 +32,16 @@ export default function Settings() {
|
||||
|
||||
return (
|
||||
<SafeArea>
|
||||
<main class='flex flex-col gap-4 py-8 px-4 max-w-[800px] mx-auto'>
|
||||
<Button onClick={clearWaitlistId}>Clear waitlist_id</Button>
|
||||
<Button onClick={setTestWaitlistId}>Use test waitlist_id</Button>
|
||||
<Button onClick={resetNode}>Reset node</Button>
|
||||
</main>
|
||||
<DefaultMain>
|
||||
<LargeHeader>Settings</LargeHeader>
|
||||
<Card title="Random utilities">
|
||||
<Button onClick={clearWaitlistId}>Clear waitlist_id</Button>
|
||||
<Button onClick={setTestWaitlistId}>Use test waitlist_id</Button>
|
||||
<Button onClick={resetNode}>Reset node</Button>
|
||||
</Card>
|
||||
<Hr />
|
||||
<KitchenSink />
|
||||
</DefaultMain>
|
||||
<NavBar activeTab="settings" />
|
||||
</SafeArea>
|
||||
)
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import { Title } from "solid-start";
|
||||
import { HttpStatusCode } from "solid-start/server";
|
||||
import { ButtonLink, DefaultMain, LargeHeader, SafeArea } from "~/components/layout";
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<main>
|
||||
<SafeArea>
|
||||
|
||||
<Title>Not Found</Title>
|
||||
<HttpStatusCode code={404} />
|
||||
<h1>Page Not Found</h1>
|
||||
<p>
|
||||
Visit{" "}
|
||||
<a href="https://start.solidjs.com" target="_blank">
|
||||
start.solidjs.com
|
||||
</a>{" "}
|
||||
to learn how to build SolidStart apps.
|
||||
</p>
|
||||
</main>
|
||||
<DefaultMain>
|
||||
<LargeHeader>Not Found</LargeHeader>
|
||||
<p>
|
||||
This is probably Paul's fault.
|
||||
</p>
|
||||
<div class="h-full">
|
||||
|
||||
</div>
|
||||
<ButtonLink href="/" intent="red">Dangit</ButtonLink>
|
||||
</DefaultMain>
|
||||
</SafeArea>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user