diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 2bb139a..659b953 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -4,7 +4,6 @@ module.exports = {
"es2021": true
},
"extends": [
- "prettier",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:solid/typescript",
diff --git a/package.json b/package.json
index 3694e11..7c827cf 100644
--- a/package.json
+++ b/package.json
@@ -48,5 +48,13 @@
},
"engines": {
"node": ">=16.8"
+ },
+ "prettier": {
+ "semi": false,
+ "singleQuote": false,
+ "trailingComma": "es5",
+ "printWidth": 80,
+ "tabWidth": 2,
+ "useTabs": false
}
}
diff --git a/src/assets/icons/copy.svg b/src/assets/icons/copy.svg
new file mode 100644
index 0000000..95475d3
--- /dev/null
+++ b/src/assets/icons/copy.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/eye.svg b/src/assets/icons/eye.svg
new file mode 100644
index 0000000..5045194
--- /dev/null
+++ b/src/assets/icons/eye.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/pencil.svg b/src/assets/icons/pencil.svg
new file mode 100644
index 0000000..b1f32b4
--- /dev/null
+++ b/src/assets/icons/pencil.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/share.svg b/src/assets/icons/share.svg
new file mode 100644
index 0000000..a672592
--- /dev/null
+++ b/src/assets/icons/share.svg
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/components/AmountCard.tsx b/src/components/AmountCard.tsx
new file mode 100644
index 0000000..7c375a0
--- /dev/null
+++ b/src/components/AmountCard.tsx
@@ -0,0 +1,87 @@
+import { Match, ParentComponent, Show, Switch, createMemo } from "solid-js";
+import { Card, VStack } from "~/components/layout";
+import { useMegaStore } from "~/state/megaStore";
+import { satsToUsd } from "~/utils/conversions";
+import { AmountEditable } from "./AmountEditable";
+
+const KeyValue: ParentComponent<{ key: string, gray?: boolean }> = (props) => {
+ return (
+
+
{props.key}
+
{props.children}
+
+ )
+}
+
+export const InlineAmount: ParentComponent<{ amount: string, sign?: string, fiat?: boolean }> = (props) => {
+ const prettyPrint = createMemo(() => {
+ const parsed = Number(props.amount);
+ if (isNaN(parsed)) {
+ return props.amount;
+ } else {
+ return parsed.toLocaleString();
+ }
+ })
+
+ return ({props.sign ? `${props.sign} ` : ""}{props.fiat ? "$" : ""}{prettyPrint()} {props.fiat ? "USD" : "SATS"}
)
+}
+
+function USDShower(props: { amountSats: string, fee?: string }) {
+ const [state, _] = useMegaStore()
+ const amountInUsd = () => satsToUsd(state.price, add(props.amountSats, props.fee), true)
+
+ return (
+
+
+ ≈ {amountInUsd()} USD
+
+
+ )
+
+}
+
+function add(a: string, b?: string) {
+ return Number(a || 0) + Number(b || 0)
+}
+
+export function AmountCard(props: { amountSats: string, fee?: string, initialOpen?: boolean, isAmountEditable?: boolean, setAmountSats?: (amount: bigint) => void }) {
+ return (
+
+
+
+
+
+
+
+ }>
+ { }} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ }>
+ { }} />
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/components/AmountEditable.tsx b/src/components/AmountEditable.tsx
index 63bfb1a..8b6f00e 100644
--- a/src/components/AmountEditable.tsx
+++ b/src/components/AmountEditable.tsx
@@ -1,10 +1,11 @@
-import { For, Show, createMemo, createSignal } from 'solid-js';
+import { For, ParentComponent, Show, createMemo, createSignal } from 'solid-js';
import { Button } from '~/components/layout';
import { useMegaStore } from '~/state/megaStore';
import { satsToUsd } from '~/utils/conversions';
-import { Amount } from './Amount';
import { Dialog } from '@kobalte/core';
import close from "~/assets/icons/close.svg";
+import pencil from "~/assets/icons/pencil.svg";
+import { InlineAmount } from './AmountCard';
const CHARACTERS = ["1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "0", "DEL"];
@@ -25,8 +26,8 @@ function SingleDigitButton(props: { character: string, onClick: (c: string) => v
);
}
-export function AmountEditable(props: { initialAmountSats: string, setAmountSats: (s: bigint) => void }) {
- const [isOpen, setIsOpen] = createSignal(false);
+export const AmountEditable: ParentComponent<{ initialAmountSats: string, initialOpen: boolean, setAmountSats: (s: bigint) => void }> = (props) => {
+ const [isOpen, setIsOpen] = createSignal(props.initialOpen);
const [displayAmount, setDisplayAmount] = createSignal(props.initialAmountSats || "0");
@@ -135,8 +136,13 @@ export function AmountEditable(props: { initialAmountSats: string, setAmountSats
return (
-
- setIsOpen(true)} class="border border-l-white/50 border-r-white/50 border-t-white/75 border-b-white/25 bg-black px-1 py-[0.5] rounded cursor-pointer hover:outline-white hover:outline-1">+ Add Contact
+ setIsOpen(true)}>+ Add Contact
diff --git a/src/components/JsonModal.tsx b/src/components/JsonModal.tsx
index 813a0ff..29ec256 100644
--- a/src/components/JsonModal.tsx
+++ b/src/components/JsonModal.tsx
@@ -5,7 +5,7 @@ import { useCopy } from "~/utils/useCopy";
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 p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10"
+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));
diff --git a/src/components/ShareCard.tsx b/src/components/ShareCard.tsx
new file mode 100644
index 0000000..8221f56
--- /dev/null
+++ b/src/components/ShareCard.tsx
@@ -0,0 +1,71 @@
+import { Card, VStack } from "~/components/layout";
+import { useCopy } from "~/utils/useCopy";
+import copyIcon from "~/assets/icons/copy.svg"
+import shareIcon from "~/assets/icons/share.svg"
+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"
+
+function ShareButton(props: { receiveString: string }) {
+ async function share(receiveString: string) {
+ // If the browser doesn't support share we can just copy the address
+ if (!navigator.share) {
+ console.error("Share not supported")
+ }
+ const shareData: ShareData = {
+ title: "Mutiny Wallet",
+ text: receiveString,
+ }
+ try {
+ await navigator.share(shareData)
+ } catch (e) {
+ console.error(e)
+ }
+ }
+
+ return (
+ share(props.receiveString)}>Share
+ )
+}
+
+export function StringShower(props: { text: string }) {
+ const [open, setOpen] = createSignal(false);
+ return (
+ <>
+
+
+
{props.text}
+
setOpen(true)}>
+
+
+
+
+ >
+ )
+}
+
+export function ShareCard(props: { text?: string }) {
+ const [copy, copied] = useCopy({ copiedTimeout: 1000 });
+
+ function handleCopy() {
+ copy(props.text ?? "")
+ }
+
+ return (
+
+
+
+
+
+
{copied() ? "Copied" : "Copy"}
+
+
+
+
+
+
+ )
+
+}
\ No newline at end of file
diff --git a/src/components/TagEditor.tsx b/src/components/TagEditor.tsx
index af538c4..09e01c7 100644
--- a/src/components/TagEditor.tsx
+++ b/src/components/TagEditor.tsx
@@ -1,22 +1,21 @@
import { Select, createOptions } from "@thisbeyond/solid-select";
import "~/styles/solid-select.css"
-import { SmallHeader } from "./layout";
import { For, createUniqueId } from "solid-js";
import { ContactEditor } from "./ContactEditor";
import { ContactItem, TagItem, TextItem, addContact } from "~/state/contacts";
+import { TinyButton } from "./layout";
// take two arrays, subtract the second from the first, then return the first
function subtract(a: T[], b: T[]) {
const set = new Set(b);
return a.filter(x => !set.has(x));
-};
+}
const createValue = (name: string): TextItem => {
return { id: createUniqueId(), name, kind: "text" };
};
-export function TagEditor(props: { title: string, values: TagItem[], setValues: (values: TagItem[]) => void, selectedValues: TagItem[], setSelectedValues: (values: TagItem[]) => void, placeholder: string }) {
- console.log(props.values);
+export function TagEditor(props: { values: TagItem[], setValues: (values: TagItem[]) => void, selectedValues: TagItem[], setSelectedValues: (values: TagItem[]) => void, placeholder: string }) {
const onChange = (selected: TagItem[]) => {
props.setSelectedValues(selected);
@@ -42,7 +41,7 @@ export function TagEditor(props: { title: string, values: TagItem[], setValues:
return (
-
{props.title}
+ {/* FIXME this is causing overflow scroll for now good reason */}
diff --git a/src/components/layout/FullscreenModal.tsx b/src/components/layout/FullscreenModal.tsx
index 56a0f18..12202fd 100644
--- a/src/components/layout/FullscreenModal.tsx
+++ b/src/components/layout/FullscreenModal.tsx
@@ -1,11 +1,11 @@
import { Dialog } from "@kobalte/core";
import { JSX } from "solid-js";
-import { Button, LargeHeader, SmallHeader } from "~/components/layout";
+import { Button, LargeHeader } from "~/components/layout";
import close from "~/assets/icons/close.svg";
const DIALOG_POSITIONER = "fixed inset-0 safe-top safe-bottom z-50"
-const DIALOG_CONTENT = "h-full flex flex-col justify-between p-4 bg-gray/50 backdrop-blur-md bg-black/80"
+const DIALOG_CONTENT = "h-full flex flex-col justify-between p-4 backdrop-blur-md bg-neutral-900/50"
type FullscreenModalProps = {
title: string,
diff --git a/src/components/layout/Radio.tsx b/src/components/layout/Radio.tsx
index 7249b4f..6c1d421 100644
--- a/src/components/layout/Radio.tsx
+++ b/src/components/layout/Radio.tsx
@@ -4,13 +4,19 @@ import { For, Show } 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, small?: boolean, red?: boolean }) {
+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
- props.onValueChange(e)} class={`grid w-full gap-${props.small ? "2" : "4"} grid-cols-${props.choices.length.toString()}`}>
+ props.onValueChange(e)}
+ 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 }}
+ >
{choice =>
-
+
diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx
index a3eec05..fb122b2 100644
--- a/src/components/layout/index.tsx
+++ b/src/components/layout/index.tsx
@@ -16,7 +16,7 @@ export const SmallHeader: ParentComponent<{ class?: string }> = (props) => {
export const Card: ParentComponent<{ title?: string, titleElement?: JSX.Element }> = (props) => {
return (
-
+
{props.title &&
{props.title}}
{props.titleElement && props.titleElement}
{props.children}
@@ -97,7 +97,7 @@ export const Hr = () =>
export const LargeHeader: ParentComponent<{ action?: JSX.Element }> = (props) => {
return (
- {props.children}
+ {props.children}
{props.action}
@@ -121,5 +121,16 @@ export const NiceP: ParentComponent = (props) => {
return ({props.children}
)
}
+export const TinyButton: ParentComponent<{ onClick: () => void }> = (props) => {
+ return (
+
+ {props.children}
+
+ )
+}
-
+export const Indicator: ParentComponent = (props) => {
+ return (
+ {props.children}
+ )
+}
\ No newline at end of file
diff --git a/src/root.tsx b/src/root.tsx
index 1a7393d..3c40eae 100644
--- a/src/root.tsx
+++ b/src/root.tsx
@@ -28,7 +28,7 @@ export default function Root() {
content="width=device-width, initial-scale=1.0 height=device-height viewport-fit=cover user-scalable=no"
/>
-
+
diff --git a/src/routes/Backup.tsx b/src/routes/Backup.tsx
index 813a002..7d6b60b 100644
--- a/src/routes/Backup.tsx
+++ b/src/routes/Backup.tsx
@@ -1,10 +1,10 @@
import { Button, DefaultMain, LargeHeader, NiceP, NodeManagerGuard, SafeArea, VStack } from "~/components/layout";
import NavBar from "~/components/NavBar";
import { useNavigate } from 'solid-start';
-import { BackButton } from '~/components/layout/BackButton';
import { SeedWords } from '~/components/SeedWords';
import { useMegaStore } from '~/state/megaStore';
import { Show, createSignal } from 'solid-js';
+import { BackLink } from "~/components/layout/BackLink";
export default function App() {
const [store, actions] = useMegaStore();
@@ -21,7 +21,7 @@ export default function App() {
-
+
Backup
Let's get these funds secured.
diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx
index 1ada186..d45b2fa 100644
--- a/src/routes/Receive.tsx
+++ b/src/routes/Receive.tsx
@@ -1,12 +1,10 @@
import { MutinyBip21RawMaterials, MutinyInvoice } from "@mutinywallet/mutiny-wasm";
-import { createEffect, createResource, createSignal, For, Match, onCleanup, onMount, Switch } from "solid-js";
+import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, onMount, Show, Switch } from "solid-js";
import { QRCodeSVG } from "solid-qr-code";
-import { AmountEditable } from "~/components/AmountEditable";
-import { Button, Card, LargeHeader, NodeManagerGuard, SafeArea, SmallHeader } from "~/components/layout";
+import { Button, Card, Indicator, LargeHeader, NodeManagerGuard, SafeArea, SmallHeader } from "~/components/layout";
import NavBar from "~/components/NavBar";
import { useMegaStore } from "~/state/megaStore";
import { objectToSearchParams } from "~/utils/objectToSearchParams";
-import { useCopy } from "~/utils/useCopy";
import mempoolTxUrl from "~/utils/mempoolTxUrl";
import { Amount } from "~/components/Amount";
import { FullscreenModal } from "~/components/layout/FullscreenModal";
@@ -17,6 +15,9 @@ import { showToast } from "~/components/Toaster";
import { useNavigate } from "solid-start";
import megacheck from "~/assets/icons/megacheck.png";
import { TagItem, listTags } from "~/state/contacts";
+import { AmountCard } from "~/components/AmountCard";
+import { ShareCard } from "~/components/ShareCard";
+import { BackButton } from "~/components/layout/BackButton";
type OnChainTx = {
transaction: {
@@ -44,39 +45,9 @@ type OnChainTx = {
const createUniqueId = () => Math.random().toString(36).substr(2, 9);
-const fakeContacts: TagItem[] = [
- { id: createUniqueId(), name: "Unknown", kind: "text" },
- // { id: createUniqueId(), name: "Alice", kind: "contact" },
- // { id: createUniqueId(), name: "Bob", kind: "contact" },
- // { id: createUniqueId(), name: "Carol", kind: "contact" },
-]
-
const RECEIVE_FLAVORS = [{ value: "unified", label: "Unified", caption: "Sender decides" }, { value: "lightning", label: "Lightning", caption: "Fast and cool" }, { value: "onchain", label: "On-chain", caption: "Just like Satoshi did it" }]
type ReceiveFlavor = "unified" | "lightning" | "onchain"
-
-function ShareButton(props: { receiveString: string }) {
- async function share(receiveString: string) {
- // If the browser doesn't support share we can just copy the address
- if (!navigator.share) {
- console.error("Share not supported")
- }
- const shareData: ShareData = {
- title: "Mutiny Wallet",
- text: receiveString,
- }
- try {
- await navigator.share(shareData)
- } catch (e) {
- console.error(e)
- }
- }
-
- return (
- share(props.receiveString)}>Share
- )
-}
-
type ReceiveState = "edit" | "show" | "paid"
type PaidState = "lightning_paid" | "onchain_paid";
@@ -88,17 +59,12 @@ export default function Receive() {
const [receiveState, setReceiveState] = createSignal("edit")
const [bip21Raw, setBip21Raw] = createSignal();
const [unified, setUnified] = createSignal("")
+ const [shouldShowAmountEditor, setShouldShowAmountEditor] = createSignal(true)
// Tagging stuff
const [selectedValues, setSelectedValues] = createSignal([]);
const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]);
- onMount(() => {
- listTags().then((tags) => {
- setValues(prev => [...prev, ...tags || []])
- });
- })
-
// The data we get after a payment
const [paymentTx, setPaymentTx] = createSignal();
const [paymentInvoice, setPaymentInvoice] = createSignal();
@@ -106,6 +72,25 @@ export default function Receive() {
// The flavor of the receive
const [flavor, setFlavor] = createSignal("unified");
+ const receiveString = createMemo(() => {
+ if (unified() && receiveState() === "show") {
+ if (flavor() === "unified") {
+ return unified();
+ } else if (flavor() === "lightning") {
+ return bip21Raw()?.invoice ?? "";
+ } else if (flavor() === "onchain") {
+ return bip21Raw()?.address ?? "";
+ }
+
+ }
+ })
+
+ onMount(() => {
+ listTags().then((tags) => {
+ setValues(prev => [...prev, ...tags || []])
+ });
+ })
+
function clearAll() {
setAmount("")
setReceiveState("edit")
@@ -116,33 +101,6 @@ export default function Receive() {
setSelectedValues([])
}
- let amountInput!: HTMLInputElement;
- let labelInput!: HTMLInputElement;
-
- function editAmount(e: Event) {
- e.preventDefault();
- setReceiveState("edit")
- amountInput.focus();
- }
-
- function editLabel(e: Event) {
- e.preventDefault();
- setReceiveState("edit")
- labelInput.focus();
- }
-
- const [copy, copied] = useCopy({ copiedTimeout: 1000 });
-
- function handleCopy() {
- if (flavor() === "unified") {
- copy(unified() ?? "")
- } else if (flavor() === "lightning") {
- copy(bip21Raw()?.invoice ?? "")
- } else if (flavor() === "onchain") {
- copy(bip21Raw()?.address ?? "")
- }
- }
-
async function getUnifiedQr(amount: string) {
const bigAmount = BigInt(amount);
try {
@@ -171,6 +129,7 @@ export default function Receive() {
setUnified(unifiedQr || "")
setReceiveState("show")
+ setShouldShowAmountEditor(false)
}
async function checkIfPaid(bip21?: MutinyBip21RawMaterials): Promise {
@@ -211,63 +170,31 @@ export default function Receive() {
return (
-
-
- Receive Bitcoin
+
+ }>
+ setReceiveState("edit")} title="Edit" />
+
+ Checking}>Receive Bitcoin
-
- -
-
-
- -
-
-
-
- Create Invoice
+
+
+
+
+
+
+
+
+
Create Invoice
+
-
+
-
-
-
-
-
-
-
-
-
-
-
+
-
- {copied() ? "Copied" : "Copy"}
-
-
-
- Amount
-
- Tags
-
-
-
- {(tag) => (
-
- {tag.name}
-
)}
-
-
- {/*
{JSON.stringify(selectedValues(), null, 2)} */}
-
✏️
-
-
-
- {unified()}
-
+ Show or share this code with the sender
+
void }) {
- const [store, actions] = useMegaStore();
+ const [store, _actions] = useMegaStore();
+
+ const methods = createMemo(() => {
+ return [
+ { value: "lightning", label: "Lightning", caption: store.balance?.lightning ? `${store.balance?.lightning.toLocaleString()} SATS` : "No balance" },
+ { value: "onchain", label: "On-chain", caption: store.balance?.confirmed ? `${store.balance?.confirmed.toLocaleString()} SATS` : "No balance" }
+ ]
- const amount = createMemo(() => {
- if (props.source === "lightning") {
- return store.balance?.lightning
- } else {
- return store.balance?.confirmed
- }
})
return (
-
-
- Payment Method
-
-
- Current balance
-
+
)
}
@@ -89,62 +82,18 @@ function DestinationShower(props: {
clearAll: () => void,
}) {
return (
-
- Destination
-
-
- {"Address: "} {props.address}
-
-
- {"Description:"} {props.description}
-
-
-
-
- {"Invoice: "} {props.invoice?.bolt11}
-
-
- {"Description:"} {props.description}
-
-
-
-
- {"Node Pubkey: "} {props.nodePubkey}
+
+
+
+
+
+
+
+
+
+
+
-
-
- Clear
-
-
- )
-}
-
-function AmountThing(props: { invoice?: MutinyInvoice, amountSats: bigint, fakeFee: bigint, setAmountSats: (amount: bigint) => void }) {
- return (
-
- Amount
-
-
- {/* if the amount came with the invoice we can't allow setting it */}
-
}>
-
-
-
-
-
+ Fee
-
- {props.fakeFee.toLocaleString()} SATS
-
-
-
-
Total
-
- {(props.amountSats.valueOf() + props.fakeFee.valueOf()).toLocaleString()} SATS
-
-
-
-
-
)
}
@@ -160,7 +109,7 @@ function SendTags() {
})
return (
-
+
)
}
@@ -170,9 +119,8 @@ export default function Send() {
const navigate = useNavigate()
// These can only be set by the user
- const [fieldDestination, setFieldDestination] = createSignal(TEST_DEST);
+ const [fieldDestination, setFieldDestination] = createSignal("");
const [destination, setDestination] = createSignal();
- const [privateLabel, setPrivateLabel] = createSignal("");
// These can be derived from the "destination" signal or set by the user
const [amountSats, setAmountSats] = createSignal(0n);
@@ -190,7 +138,6 @@ export default function Send() {
function clearAll() {
setDestination(undefined);
- setPrivateLabel("");
setAmountSats(0n);
setSource("lightning");
setInvoice(undefined);
@@ -281,7 +228,7 @@ export default function Send() {
try {
setSending(true);
const bolt11 = invoice()?.bolt11;
- let sentDetails: Partial = {};
+ const sentDetails: Partial = {};
if (source() === "lightning" && invoice() && bolt11) {
const nodes = await state.node_manager?.list_nodes();
const firstNode = nodes[0] as string || ""
@@ -366,12 +313,16 @@ export default function Send() {
-
-
-
+
+
+
+
+
+
+
diff --git a/src/routes/Storybook.tsx b/src/routes/Storybook.tsx
new file mode 100644
index 0000000..1ac4995
--- /dev/null
+++ b/src/routes/Storybook.tsx
@@ -0,0 +1,23 @@
+import { AmountCard } from "~/components/AmountCard";
+import NavBar from "~/components/NavBar";
+import { ShareCard } from "~/components/ShareCard";
+import { DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout";
+
+const SAMPLE = "bitcoin:tb1prqm8xtlgme0vmw5s30lgf0a4f5g4mkgsqundwmpu6thrg8zr6uvq2qrhzq?amount=0.001&lightning=lntbs1m1pj9n9xjsp5xgdrmvprtm67p7nq4neparalexlhlmtxx87zx6xeqthsplu842zspp546d6zd2seyaxpapaxx62m88yz3xueqtjmn9v6wj8y56np8weqsxqdqqnp4qdn2hj8tfknpuvdg6tz9yrf3e27ltrx9y58c24jh89lnm43yjwfc5xqrpwjcqpj9qrsgq5sdgh0m3ur5mu5hrmmag4mx9yvy86f83pd0x9ww80kgck6tac3thuzkj0mrtltaxwnlfea95h2re7tj4qsnwzxlvrdmyq2h9mgapnycpppz6k6"
+export default function Admin() {
+ return (
+
+
+ Storybook
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file