styling pass

This commit is contained in:
Paul Miller
2023-04-15 11:01:40 -05:00
parent d36adad34c
commit 3c94312a9f
17 changed files with 230 additions and 179 deletions

View File

@@ -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)

View File

@@ -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();
}
}
}

View File

@@ -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")

View File

@@ -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>

View File

@@ -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>
</>
)

View File

@@ -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" />

View File

@@ -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: {

View 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>
)
}

View File

@@ -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 }

View File

@@ -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%;
}

View File

@@ -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>

View File

@@ -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 >
)

View File

@@ -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>
)

View File

@@ -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>
);
}