diff --git a/src/assets/hands/handshake.png b/src/assets/hands/handshake.png new file mode 100644 index 0000000..6fccb36 Binary files /dev/null and b/src/assets/hands/handshake.png differ diff --git a/src/assets/hands/handsup.png b/src/assets/hands/handsup.png new file mode 100644 index 0000000..8e1cfa9 Binary files /dev/null and b/src/assets/hands/handsup.png differ diff --git a/src/assets/hands/nostr.png b/src/assets/hands/nostr.png new file mode 100644 index 0000000..792e552 Binary files /dev/null and b/src/assets/hands/nostr.png differ diff --git a/src/assets/hands/thumbsdown.png b/src/assets/hands/thumbsdown.png new file mode 100644 index 0000000..7510060 Binary files /dev/null and b/src/assets/hands/thumbsdown.png differ diff --git a/src/assets/handshake.png b/src/assets/handshake.png deleted file mode 100644 index 0f88f82..0000000 Binary files a/src/assets/handshake.png and /dev/null differ diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 5e21f5b..9171864 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -19,7 +19,7 @@ type OnChainTx = { sent: number fee?: number confirmation_time?: { - "Confirmed": { + "Confirmed"?: { height: number time: number } @@ -63,7 +63,7 @@ function OnChainItem(props: { item: OnChainTx }) { {isReceive() ? "RECEIVE" : "SEND"} - {props.item.confirmation_time ? prettyPrintTime(props.item.confirmation_time.Confirmed.time) : "Unconfirmed"} + {props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"} diff --git a/src/components/ErrorDisplay.tsx b/src/components/ErrorDisplay.tsx new file mode 100644 index 0000000..bbd7878 --- /dev/null +++ b/src/components/ErrorDisplay.tsx @@ -0,0 +1,20 @@ +import { Title } from "solid-start"; +import { ButtonLink, DefaultMain, LargeHeader, SafeArea, SmallHeader } from "~/components/layout"; + +export default function ErrorDisplay(props: { error: Error }) { + return ( + + Oh no! + + Error + This never should've happened +

+ + {props.error.name}: {props.error.message} +

+
+ Dangit + + + ); +} diff --git a/src/components/Toaster.tsx b/src/components/Toaster.tsx index 6a1116a..3383d69 100644 --- a/src/components/Toaster.tsx +++ b/src/components/Toaster.tsx @@ -30,7 +30,7 @@ export function showToast(arg: ToastArg) { export function ToastItem(props: { toastId: number, title: string, description: string, isError?: boolean }) { return ( -
+
@@ -43,7 +43,7 @@ export function ToastItem(props: { toastId: number, title: string, description:

- + Close
diff --git a/src/components/layout/FullscreenModal.tsx b/src/components/layout/FullscreenModal.tsx index cbffcb6..56a0f18 100644 --- a/src/components/layout/FullscreenModal.tsx +++ b/src/components/layout/FullscreenModal.tsx @@ -5,7 +5,7 @@ import { Button, LargeHeader, SmallHeader } 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 p-4 bg-gray/50 backdrop-blur-md bg-black/80" +const DIALOG_CONTENT = "h-full flex flex-col justify-between p-4 bg-gray/50 backdrop-blur-md bg-black/80" type FullscreenModalProps = { title: string, @@ -34,8 +34,10 @@ export function FullscreenModal(props: FullscreenModalProps) {
{props.children} - +
+ +
diff --git a/src/root.tsx b/src/root.tsx index 09f594e..1a7393d 100644 --- a/src/root.tsx +++ b/src/root.tsx @@ -15,6 +15,7 @@ import { import "./root.css"; import { Provider as MegaStoreProvider } from "~/state/megaStore"; import { Toaster } from "~/components/Toaster"; +import ErrorDisplay from "./components/ErrorDisplay"; export default function Root() { return ( @@ -35,7 +36,7 @@ export default function Root() { - + }> diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index a6df2fe..9705e65 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -12,7 +12,8 @@ import { ParsedParams, toParsedParams } from "./Scanner"; import { showToast } from "~/components/Toaster"; import eify from "~/utils/eify"; import { FullscreenModal } from "~/components/layout/FullscreenModal"; -import handshake from "~/assets/handshake.png"; +import handshake from "~/assets/hands/handshake.png"; +import thumbsdown from "~/assets/hands/thumbsdown.png"; import mempoolTxUrl from "~/utils/mempoolTxUrl"; import { BackButton } from "~/components/layout/BackButton"; @@ -23,7 +24,8 @@ const PAYMENT_METHODS = [{ value: "lightning", label: "Lightning", caption: "Fas // const TEST_DEST = "bitcoin:tb1pdh43en28jmhnsrhxkusja46aufdlae5qnfrhucw5jvefw9flce3sdxfcwe?amount=0.00001&label=heyo&lightning=lntbs10u1pjrwrdedq8dpjhjmcnp4qd60w268ve0jencwzhz048ruprkxefhj0va2uspgj4q42azdg89uupp5gngy2pqte5q5uvnwcxwl2t8fsdlla5s6xl8aar4xcsvxeus2w2pqsp5n5jp3pz3vpu92p3uswttxmw79a5lc566herwh3f2amwz2sp6f9tq9qyysgqcqpcxqrpwugv5m534ww5ukcf6sdw2m75f2ntjfh3gzeqay649256yvtecgnhjyugf74zakaf56sdh66ec9fqep2kvu6xv09gcwkv36rrkm38ylqsgpw3yfjl" // const TEST_DEST_ADDRESS = "tb1pdh43en28jmhnsrhxkusja46aufdlae5qnfrhucw5jvefw9flce3sdxfcwe" -type SentDetails = { amount: bigint, destination: string, txid?: string } +// TODO: better success / fail type +type SentDetails = { amount?: bigint, destination?: string, txid?: string, failure_reason?: string } export default function Send() { const [state, actions] = useMegaStore(); @@ -68,6 +70,7 @@ export default function Send() { onMount(() => { if (state.scan_result) { setDestination(state.scan_result); + actions.setScanResult(undefined); } }) @@ -155,10 +158,16 @@ export default function Send() { } else if (source() === "lightning" && nodePubkey()) { const nodes = await state.node_manager?.list_nodes(); const firstNode = nodes[0] as string || "" - const invoice = await state.node_manager?.keysend(firstNode, nodePubkey()!, amountSats()); - console.log(invoice?.value) - sentDetails.amount = amountSats(); - } else { + const payment = await state.node_manager?.keysend(firstNode, nodePubkey()!, amountSats()); + console.log(payment?.value) + + // TODO: handle timeouts + if (!payment?.paid) { + throw new Error("Keysend failed") + } else { + sentDetails.amount = amountSats(); + } + } else if (source() === "onchain" && address()) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const txid = await state.node_manager?.send_to_address(address()!, amountSats()); sentDetails.amount = amountSats(); @@ -170,7 +179,10 @@ export default function Send() { setSentDetails(sentDetails as SentDetails); clearAll(); } catch (e) { - showToast(eify(e)) + const error = eify(e) + setSentDetails({ failure_reason: error.message }); + // TODO: figure out ux of when we want to show toast vs error screen + // showToast(eify(e)) console.error(e); } finally { setSending(false); @@ -187,15 +199,29 @@ export default function Send() { Send Bitcoin - { if (!open) setSentDetails(undefined) }} onConfirm={() => setSentDetails(undefined)}> -
- party - - - - Mempool Link - - + { if (!open) setSentDetails(undefined) }} + onConfirm={() => setSentDetails(undefined)} + > +
+ + + thumbs down +

{sentDetails()?.failure_reason}

+
+ + handshake + + + + Mempool Link + + + +
@@ -229,7 +255,7 @@ export default function Send() {
-
+
{/* if the amount came with the invoice we can't allow setting it */} }> diff --git a/src/routes/[...404].tsx b/src/routes/[...404].tsx index 92f1dc0..1c69657 100644 --- a/src/routes/[...404].tsx +++ b/src/routes/[...404].tsx @@ -5,7 +5,6 @@ import { ButtonLink, DefaultMain, LargeHeader, SafeArea } from "~/components/lay export default function NotFound() { return ( - Not Found