From de83eef68a5e85d90b9acf7ad717ef9f295b7932 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 9 May 2023 16:44:59 -0500 Subject: [PATCH 01/17] new activity design --- src/assets/icons/bolt.svg | 3 ++ src/assets/icons/chain.svg | 4 ++ src/components/Activity.tsx | 22 +++++++--- src/components/ActivityItem.tsx | 72 +++++++++++++++++++++++++++++++++ src/components/App.tsx | 3 +- src/routes/Activity.tsx | 5 ++- src/routes/Storybook.tsx | 12 ++++-- src/utils/prettyPrintTime.ts | 29 ++++++++++++- 8 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 src/assets/icons/bolt.svg create mode 100644 src/assets/icons/chain.svg create mode 100644 src/components/ActivityItem.tsx diff --git a/src/assets/icons/bolt.svg b/src/assets/icons/bolt.svg new file mode 100644 index 0000000..92080ef --- /dev/null +++ b/src/assets/icons/bolt.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/chain.svg b/src/assets/icons/chain.svg new file mode 100644 index 0000000..7946243 --- /dev/null +++ b/src/assets/icons/chain.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 50d6702..438c08c 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -10,6 +10,7 @@ import mempoolTxUrl from '~/utils/mempoolTxUrl'; import wave from "~/assets/wave.gif" import utxoIcon from '~/assets/icons/coin.svg'; import { getRedshifted } from '~/utils/fakeLabels'; +import { ActivityItem } from './ActivityItem'; export const THREE_COLUMNS = 'grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0' export const CENTER_COLUMN = 'min-w-0 overflow-hidden max-w-full' @@ -57,7 +58,15 @@ function OnChainItem(props: { item: OnChainTx }) { Mempool Link -
setOpen(!open())}> + setOpen(!open())} + /> + {/*
setOpen(!open())}>
{isReceive() ? receive arrow : send arrow}
@@ -71,7 +80,7 @@ function OnChainItem(props: { item: OnChainTx }) { {props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"}
-
+ */} ) } @@ -84,7 +93,8 @@ function InvoiceItem(props: { item: MutinyInvoice }) { return ( <> -
setOpen(!open())}> + setOpen(!open())} /> + {/*
setOpen(!open())}>
{isSend() ? send arrow : receive arrow}
@@ -98,7 +108,7 @@ function InvoiceItem(props: { item: MutinyInvoice }) { {prettyPrintTime(Number(props.item.expire))}
-
+ */} ) } @@ -243,7 +253,9 @@ export function CombinedActivity(props: { limit?: number }) { }) invoices.forEach((invoice) => { - activity.push({ type: "lightning", item: invoice, time: Number(invoice.expire) }) + if (invoice.paid) { + activity.push({ type: "lightning", item: invoice, time: Number(invoice.expire) }) + } }) if (props.limit) { diff --git a/src/components/ActivityItem.tsx b/src/components/ActivityItem.tsx new file mode 100644 index 0000000..915d034 --- /dev/null +++ b/src/components/ActivityItem.tsx @@ -0,0 +1,72 @@ +import { ParentComponent, createMemo } from "solid-js"; +import { InlineAmount } from "./AmountCard"; +import { satsToUsd } from "~/utils/conversions"; +import bolt from "~/assets/icons/bolt.svg" +import chain from "~/assets/icons/chain.svg" +import { timeAgo } from "~/utils/prettyPrintTime"; + +export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean }> = (props) => { + const amountInUsd = createMemo(() => { + const parsed = Number(props.amount); + if (isNaN(parsed)) { + return props.amount; + } else { + return satsToUsd(props.price, parsed, true); + } + }) + + const prettyPrint = createMemo(() => { + const parsed = Number(props.amount); + if (isNaN(parsed)) { + return props.amount; + } else { + return parsed.toLocaleString(); + } + }) + + return ( +
+
{props.positive && "+ "}{prettyPrint()} SATS +
+
≈ {amountInUsd()} USD
+
+ ) +} + +function LabelCircle(props: { name: string }) { + return ( +
+ {props.name[0] || "?"} +
+ ) +} + +export function ActivityItem(props: { kind: "lightning" | "onchain", labels: string[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) { + return ( +
props.onClick && props.onClick()} + class="grid grid-cols-[auto_minmax(0,_1fr)_minmax(0,_max-content)] pb-4 gap-4 border-b border-neutral-800 last:border-b-0" + classList={{ "cursor-pointer": !!props.onClick }} + > +
+
+ {props.kind === "lightning" ? lightning : onchain} +
+
+ +
+
+
+ {props.labels.length ? props.labels[0] : "Unknown"} + +
+
+ +
+
+ ) +} \ No newline at end of file diff --git a/src/components/App.tsx b/src/components/App.tsx index 927960c..b96a600 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -21,11 +21,12 @@ export default function App() { +
{/* View All */} - View All + View All diff --git a/src/routes/Activity.tsx b/src/routes/Activity.tsx index 6234989..76ce361 100644 --- a/src/routes/Activity.tsx +++ b/src/routes/Activity.tsx @@ -58,7 +58,10 @@ export default function Activity() { {/* */} - +
+ + + diff --git a/src/routes/Storybook.tsx b/src/routes/Storybook.tsx index 1ac4995..9a6ce43 100644 --- a/src/routes/Storybook.tsx +++ b/src/routes/Storybook.tsx @@ -1,7 +1,8 @@ +import { ActivityItem } from "~/components/ActivityItem"; import { AmountCard } from "~/components/AmountCard"; import NavBar from "~/components/NavBar"; import { ShareCard } from "~/components/ShareCard"; -import { DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout"; +import { Card, DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout"; const SAMPLE = "bitcoin:tb1prqm8xtlgme0vmw5s30lgf0a4f5g4mkgsqundwmpu6thrg8zr6uvq2qrhzq?amount=0.001&lightning=lntbs1m1pj9n9xjsp5xgdrmvprtm67p7nq4neparalexlhlmtxx87zx6xeqthsplu842zspp546d6zd2seyaxpapaxx62m88yz3xueqtjmn9v6wj8y56np8weqsxqdqqnp4qdn2hj8tfknpuvdg6tz9yrf3e27ltrx9y58c24jh89lnm43yjwfc5xqrpwjcqpj9qrsgq5sdgh0m3ur5mu5hrmmag4mx9yvy86f83pd0x9ww80kgck6tac3thuzkj0mrtltaxwnlfea95h2re7tj4qsnwzxlvrdmyq2h9mgapnycpppz6k6" export default function Admin() { @@ -11,10 +12,13 @@ export default function Admin() { Storybook - - - + + + + + + diff --git a/src/utils/prettyPrintTime.ts b/src/utils/prettyPrintTime.ts index 254c2db..de82a2a 100644 --- a/src/utils/prettyPrintTime.ts +++ b/src/utils/prettyPrintTime.ts @@ -9,4 +9,31 @@ export function prettyPrintTime(ts: number) { }; return new Date(ts * 1000).toLocaleString('en-US', options); -} \ No newline at end of file +} + +export function timeAgo(ts?: number | bigint): string { + if (!ts || ts === 0) return "Pending"; + const timestamp = Number(ts) * 1000; + const now = Date.now(); + const elapsedMilliseconds = now - timestamp; + const elapsedSeconds = Math.floor(elapsedMilliseconds / 1000); + const elapsedMinutes = Math.floor(elapsedSeconds / 60); + const elapsedHours = Math.floor(elapsedMinutes / 60); + const elapsedDays = Math.floor(elapsedHours / 24); + + if (elapsedSeconds < 60) { + return "Just now"; + } else if (elapsedMinutes < 60) { + return `${elapsedMinutes} minute${elapsedMinutes > 1 ? 's' : ''} ago`; + } else if (elapsedHours < 24) { + return `${elapsedHours} hour${elapsedHours > 1 ? 's' : ''} ago`; + } else if (elapsedDays < 7) { + return `${elapsedDays} day${elapsedDays > 1 ? 's' : ''} ago`; + } else { + const date = new Date(timestamp); + const day = String(date.getDate()).padStart(2, '0'); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const year = date.getFullYear(); + return `${month}/${day}/${year}`; + } +} From b670da8bcc49f75ce4dd299ed366597ef5e99c86 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 9 May 2023 17:40:20 -0500 Subject: [PATCH 02/17] half-started on labels --- src/components/Activity.tsx | 126 ++++++++++-------------------------- src/routes/Receive.tsx | 8 ++- 2 files changed, 42 insertions(+), 92 deletions(-) diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 438c08c..42941bf 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -46,11 +46,14 @@ const SubtleText: ParentComponent = (props) => { return

{props.children}

} -function OnChainItem(props: { item: OnChainTx }) { +function OnChainItem(props: { item: OnChainTx, labels: string[] }) { + const [store, actions] = useMegaStore(); const isReceive = createMemo(() => props.item.received > 0); const [open, setOpen] = createSignal(false) + + return ( <> @@ -58,9 +61,10 @@ function OnChainItem(props: { item: OnChainTx }) { Mempool Link + {JSON.stringify(props.labels)} props.item.is_send); const [open, setOpen] = createSignal(false) + const labels = createMemo(() => { + const labels = store.mutiny_wallet?.get_address_labels(); + console.log(labels); + if (!labels) return ["abcdefg"]; + return labels; + // return labels.filter((label) => label.address === props.item.txid) + }) + return ( <> @@ -145,92 +158,6 @@ function Utxo(props: { item: UtxoItem }) { ) } -export function Activity() { - const [state, _] = useMegaStore(); - - const getTransactions = async () => { - console.log("Getting onchain txs"); - const txs = await state.mutiny_wallet?.list_onchain() as OnChainTx[]; - return txs.reverse(); - } - - const getInvoices = async () => { - console.log("Getting invoices"); - const invoices = await state.mutiny_wallet?.list_invoices() as MutinyInvoice[]; - return invoices.filter((inv) => inv.paid).reverse(); - } - - const getUtXos = async () => { - console.log("Getting utxos"); - const utxos = await state.mutiny_wallet?.list_utxos() as UtxoItem[]; - return utxos; - } - - const [transactions, { refetch: _refetchTransactions }] = createResource(getTransactions); - const [invoices, { refetch: _refetchInvoices }] = createResource(getInvoices); - const [utxos, { refetch: _refetchUtxos }] = createResource(getUtXos); - - return ( - - - - - - - - - No transactions (empty state) - - = 0}> - - {(tx) => - - } - - - - - - - - - - - No invoices (empty state) - - = 0}> - - {(invoice) => - - } - - - - - - - - - - - No utxos (empty state) - - = 0}> - - {(utxo) => - - } - - - - Redshift redshift - - - - ) - -} - type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number } function sortByTime(a: ActivityItem, b: ActivityItem) { @@ -267,6 +194,21 @@ export function CombinedActivity(props: { limit?: number }) { const [activity] = createResource(getAllActivity); + const addressLabels = createMemo(() => { + const labels = state.mutiny_wallet?.get_address_labels(); + console.log(labels); + return labels || []; + // return labels.filter((label) => label.address === props.item.txid) + }) + + const invoiceLabels = createMemo(() => { + const labels = state.mutiny_wallet?.get_address_labels(); + console.log(labels); + if (!labels) return ["abcdefg"]; + return labels; + // return labels.filter((label) => label.address === props.item.txid) + }) + return ( @@ -280,10 +222,12 @@ export function CombinedActivity(props: { limit?: number }) { {(activityItem) => - + {/* FIXME */} + - + {/* FIXME */} + } diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 00031f5..130a710 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -51,6 +51,10 @@ type ReceiveFlavor = "unified" | "lightning" | "onchain" type ReceiveState = "edit" | "show" | "paid" type PaidState = "lightning_paid" | "onchain_paid"; +function tagItemsToLabels(items: TagItem[]) { + return items.map(item => item.kind === "contact" ? item.id : item.name) +} + export default function Receive() { const [state, _] = useMegaStore() const navigate = useNavigate(); @@ -103,9 +107,11 @@ export default function Receive() { async function getUnifiedQr(amount: string) { const bigAmount = BigInt(amount); + console.log(selectedValues()); + console.log(tagItemsToLabels(selectedValues())) try { // FIXME: actual labels - const raw = await state.mutiny_wallet?.create_bip21(bigAmount, []); + const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagItemsToLabels(selectedValues())); // Save the raw info so we can watch the address and invoice setBip21Raw(raw); From 22a49d327d8fa96d110343176fb29cdcdae784b2 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 9 May 2023 18:16:07 -0500 Subject: [PATCH 03/17] better onboard warnings --- src/assets/icons/save.svg | 3 ++ src/assets/icons/upload.svg | 3 ++ src/components/OnboardWarning.tsx | 57 ++++++++++++++++++++----------- src/components/layout/Button.tsx | 8 ++--- src/routes/Storybook.tsx | 10 +++--- 5 files changed, 53 insertions(+), 28 deletions(-) create mode 100644 src/assets/icons/save.svg create mode 100644 src/assets/icons/upload.svg diff --git a/src/assets/icons/save.svg b/src/assets/icons/save.svg new file mode 100644 index 0000000..c63233f --- /dev/null +++ b/src/assets/icons/save.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/upload.svg b/src/assets/icons/upload.svg new file mode 100644 index 0000000..79cafb5 --- /dev/null +++ b/src/assets/icons/upload.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/OnboardWarning.tsx b/src/components/OnboardWarning.tsx index a8b45ed..1137278 100644 --- a/src/components/OnboardWarning.tsx +++ b/src/components/OnboardWarning.tsx @@ -1,7 +1,10 @@ import { Show, createSignal, onMount } from "solid-js"; -import { Button, ButtonLink, SmallHeader, VStack } from "./layout"; +import { Button, ButtonLink, SmallHeader } from "./layout"; import { useMegaStore } from "~/state/megaStore"; import { showToast } from "./Toaster"; +import save from "~/assets/icons/save.svg" +import close from "~/assets/icons/close.svg"; +import restore from "~/assets/icons/upload.svg"; export function OnboardWarning() { const [state, actions] = useMegaStore(); @@ -18,30 +21,44 @@ export function OnboardWarning() { return ( <> {/* TODO: show this once we have a restore flow */} - -
- Welcome! - -

- Do you want to restore an existing Mutiny Wallet? -

-
- - + +
+
+ backup +
+
+
+ Welcome! +

+ If you've used Mutiny before you can restore from a backup. Otherwise you can skip this and enjoy your new wallet! +

- + + +
+
-
- Secure your funds -

- You have money stored in this browser. Let's make sure you have a backup. -

-
- Backup - +
+
+ backup
+
+
+ Secure your funds +

+ You have money stored in this browser. Let's make sure you have a backup. +

+
+ Backup + +
+
diff --git a/src/components/layout/Button.tsx b/src/components/layout/Button.tsx index 32744c5..3bbc2cb 100644 --- a/src/components/layout/Button.tsx +++ b/src/components/layout/Button.tsx @@ -4,7 +4,7 @@ import { Dynamic } from "solid-js/web"; import { A } from "solid-start"; import { LoadingSpinner } from "."; -const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 disabled:grayscale transition", { +const button = cva("p-3 rounded-xl font-semibold disabled:opacity-50 disabled:grayscale transition", { variants: { // TODO: button hover has to work different than buttonlinks (like disabled state) intent: { @@ -16,10 +16,10 @@ const button = cva("p-3 rounded-xl text-xl font-semibold disabled:opacity-50 dis green: "bg-m-green text-white shadow-inner-button hover:bg-m-green-dark text-shadow-button", }, layout: { - flex: "flex-1", - pad: "px-8", + flex: "flex-1 text-xl", + pad: "px-8 text-xl", small: "px-4 py-2 w-auto text-lg", - xs: "px-2 py-1 w-auto rounded-lg font-normal text-base" + xs: "px-4 py-2 w-auto rounded-lg text-base" }, }, defaultVariants: { diff --git a/src/routes/Storybook.tsx b/src/routes/Storybook.tsx index 9a6ce43..f33f0e9 100644 --- a/src/routes/Storybook.tsx +++ b/src/routes/Storybook.tsx @@ -1,6 +1,7 @@ import { ActivityItem } from "~/components/ActivityItem"; import { AmountCard } from "~/components/AmountCard"; import NavBar from "~/components/NavBar"; +import { OnboardWarning } from "~/components/OnboardWarning"; import { ShareCard } from "~/components/ShareCard"; import { Card, DefaultMain, LargeHeader, SafeArea, VStack } from "~/components/layout"; @@ -10,14 +11,15 @@ export default function Admin() { Storybook + - - - - + + + + From cda1fcac945fcc068f4c5b33dd0537d4aaa901cd Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Tue, 9 May 2023 19:12:30 -0500 Subject: [PATCH 04/17] fix onboard warning on mobile --- src/components/OnboardWarning.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/components/OnboardWarning.tsx b/src/components/OnboardWarning.tsx index 1137278..2b71418 100644 --- a/src/components/OnboardWarning.tsx +++ b/src/components/OnboardWarning.tsx @@ -26,15 +26,14 @@ export function OnboardWarning() {
backup
-
+
Welcome!

If you've used Mutiny before you can restore from a backup. Otherwise you can skip this and enjoy your new wallet!

- - +
+ From 4407474f421ebe685f5de79c50f67d49d77567b6 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 11:19:47 -0500 Subject: [PATCH 06/17] kobalte breaking change --- src/components/AmountEditable.tsx | 2 +- src/components/ContactEditor.tsx | 2 +- src/components/ContactViewer.tsx | 2 +- src/components/DeleteEverything.tsx | 2 +- src/components/Dialog.tsx | 4 ++-- src/components/ImportExport.tsx | 2 +- src/components/JsonModal.tsx | 2 +- src/components/KitchenSink.tsx | 2 +- src/components/layout/FullscreenModal.tsx | 2 +- src/components/layout/Radio.tsx | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/AmountEditable.tsx b/src/components/AmountEditable.tsx index 8b6f00e..a6161c2 100644 --- a/src/components/AmountEditable.tsx +++ b/src/components/AmountEditable.tsx @@ -135,7 +135,7 @@ export const AmountEditable: ParentComponent<{ initialAmountSats: string, initia const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70" return ( - +
}> diff --git a/src/components/ContactEditor.tsx b/src/components/ContactEditor.tsx index 748fa80..9cf4e0f 100644 --- a/src/components/ContactEditor.tsx +++ b/src/components/ContactEditor.tsx @@ -26,7 +26,7 @@ export function ContactEditor(props: { createContact: (contact: ContactItem) => const DIALOG_CONTENT = "h-full safe-bottom flex flex-col justify-between p-4 backdrop-blur-xl bg-neutral-800/70" return ( - + - setConfirmOpen(false)}> + setConfirmOpen(false)}> This will delete your node's state. This can't be undone! diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx index 4ec624a..73197c6 100644 --- a/src/components/Dialog.tsx +++ b/src/components/Dialog.tsx @@ -7,9 +7,9 @@ const DIALOG_POSITIONER = "fixed inset-0 z-50 flex items-center justify-center" const DIALOG_CONTENT = "w-[80vw] max-w-[400px] p-4 bg-gray/50 backdrop-blur-md shadow-xl rounded-xl border border-white/10" // TODO: implement this like toast so it's just one global confirm and I can call it with `confirm({ title: "Are you sure?", description: "This will delete your node" })` -export const ConfirmDialog: ParentComponent<{ isOpen: boolean; loading: boolean; onCancel: () => void, onConfirm: () => void }> = (props) => { +export const ConfirmDialog: ParentComponent<{ open: boolean; loading: boolean; onCancel: () => void, onConfirm: () => void }> = (props) => { return ( - +
diff --git a/src/components/ImportExport.tsx b/src/components/ImportExport.tsx index e22a48a..983b569 100644 --- a/src/components/ImportExport.tsx +++ b/src/components/ImportExport.tsx @@ -67,7 +67,7 @@ export function ImportExport() { - setConfirmOpen(false)}> + setConfirmOpen(false)}> Do you want to replace your state with {files()[0].name}? diff --git a/src/components/JsonModal.tsx b/src/components/JsonModal.tsx index 29ec256..5cafb10 100644 --- a/src/components/JsonModal.tsx +++ b/src/components/JsonModal.tsx @@ -13,7 +13,7 @@ export function JsonModal(props: { title: string, open: boolean, data?: unknown, const [copy, copied] = useCopy({ copiedTimeout: 1000 }); return ( - props.setOpen(isOpen)}> + props.setOpen(isOpen)}>
diff --git a/src/components/KitchenSink.tsx b/src/components/KitchenSink.tsx index a37c0b7..5f8e353 100644 --- a/src/components/KitchenSink.tsx +++ b/src/components/KitchenSink.tsx @@ -167,7 +167,7 @@ function ChannelItem(props: { channel: MutinyChannel, network?: string }) { - setConfirmOpen(false)} loading={confirmLoading()}> + setConfirmOpen(false)} loading={confirmLoading()}>

Are you sure you want to close this channel?

diff --git a/src/components/layout/FullscreenModal.tsx b/src/components/layout/FullscreenModal.tsx index 12202fd..c87bb8f 100644 --- a/src/components/layout/FullscreenModal.tsx +++ b/src/components/layout/FullscreenModal.tsx @@ -18,7 +18,7 @@ type FullscreenModalProps = { export function FullscreenModal(props: FullscreenModalProps) { return ( - props.setOpen(isOpen)}> + props.setOpen(isOpen)}>
diff --git a/src/components/layout/Radio.tsx b/src/components/layout/Radio.tsx index 6c1d421..7de314c 100644 --- a/src/components/layout/Radio.tsx +++ b/src/components/layout/Radio.tsx @@ -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 - props.onValueChange(e)} + 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 }} > From f1dcfb16abd5a8080225dd3eae400c67400aec8a Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 11:20:09 -0500 Subject: [PATCH 07/17] simple text labels for lightning --- package.json | 4 ++-- pnpm-lock.yaml | 46 ++++++++++++++++++------------------ src/components/Activity.tsx | 47 +++++++++++++++---------------------- src/routes/Receive.tsx | 4 +++- 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 0a7740c..b7e1e3c 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "type": "module", "devDependencies": { - "@types/node": "^18.16.6", + "@types/node": "^18.16.7", "@typescript-eslint/eslint-plugin": "^5.59.5", "@typescript-eslint/parser": "^5.59.5", "autoprefixer": "^10.4.14", @@ -33,7 +33,7 @@ "@kobalte/core": "^0.9.6", "@kobalte/tailwindcss": "^0.5.0", "@modular-forms/solid": "^0.13.2", - "@mutinywallet/mutiny-wasm": "^0.3.0", + "@mutinywallet/mutiny-wasm": "^0.3.1", "@mutinywallet/waila-wasm": "^0.1.5", "@solid-primitives/upload": "^0.0.111", "@solidjs/meta": "^0.28.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1d26606..cbe7648 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,8 +11,8 @@ dependencies: specifier: ^0.13.2 version: 0.13.2(solid-js@1.7.5) '@mutinywallet/mutiny-wasm': - specifier: ^0.3.0 - version: 0.3.0 + specifier: ^0.3.1 + version: 0.3.1 '@mutinywallet/waila-wasm': specifier: ^0.1.5 version: 0.1.5 @@ -52,8 +52,8 @@ dependencies: devDependencies: '@types/node': - specifier: ^18.16.6 - version: 18.16.6 + specifier: ^18.16.7 + version: 18.16.7 '@typescript-eslint/eslint-plugin': specifier: ^5.59.5 version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5) @@ -95,7 +95,7 @@ devDependencies: version: 4.9.5 vite: specifier: ^4.3.5 - version: 4.3.5(@types/node@18.16.6) + version: 4.3.5(@types/node@18.16.7) vite-plugin-pwa: specifier: ^0.14.7 version: 0.14.7(vite@4.3.5)(workbox-build@6.5.4)(workbox-window@6.5.4) @@ -1625,8 +1625,8 @@ packages: solid-js: 1.7.5 dev: false - /@mutinywallet/mutiny-wasm@0.3.0: - resolution: {integrity: sha512-K+u2u/XMX1269U8af3T/ZvS+SzzrQcVYrdMi420dWCa14gke0vPWbGp+01zN7SCqBL4jp929emHTUZ4YBEpkzQ==} + /@mutinywallet/mutiny-wasm@0.3.1: + resolution: {integrity: sha512-TSOU0ttNZuAmiP7ZymyW6bgWpUk/6F/++YOEDfymkcIKsEWhhITNGcAVFPHzZQP/dLU7leoR3puHauKjoDa8lQ==} dev: false /@mutinywallet/waila-wasm@0.1.5: @@ -1982,8 +1982,8 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/node@18.16.6: - resolution: {integrity: sha512-N7KINmeB8IN3vRR8dhgHEp+YpWvGFcpDoh5XZ8jB5a00AdFKCKEyyGTOPTddUf4JqU1ZKTVxkOxakDvchNVI2Q==} + /@types/node@18.16.7: + resolution: {integrity: sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ==} /@types/offscreencanvas@2019.7.0: resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==} @@ -1992,7 +1992,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 18.16.6 + '@types/node': 18.16.7 dev: true /@types/resolve@1.20.2: @@ -2370,7 +2370,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001486 - electron-to-chromium: 1.4.388 + electron-to-chromium: 1.4.389 node-releases: 2.0.10 update-browserslist-db: 1.0.11(browserslist@4.21.5) @@ -2653,8 +2653,8 @@ packages: jake: 10.8.5 dev: true - /electron-to-chromium@1.4.388: - resolution: {integrity: sha512-xZ0y4zjWZgp65okzwwt00f2rYibkFPHUv9qBz+Vzn8cB9UXIo9Zc6Dw81LJYhhNt0G/vR1OJEfStZ49NKl0YxQ==} + /electron-to-chromium@1.4.389: + resolution: {integrity: sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg==} /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3803,7 +3803,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.16.6 + '@types/node': 18.16.7 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -4613,7 +4613,7 @@ packages: solid-start: 0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5) terser: 5.17.3 undici: 5.22.0 - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) transitivePeerDependencies: - supports-color @@ -4683,7 +4683,7 @@ packages: solid-start-node: 0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5) terser: 5.17.3 undici: 5.22.0 - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) vite-plugin-inspect: 0.7.26(rollup@3.21.6)(vite@4.3.5) vite-plugin-solid: 2.7.0(solid-js@1.7.5)(vite@4.3.5) wait-on: 6.0.1(debug@4.3.4) @@ -5092,7 +5092,7 @@ packages: fs-extra: 11.1.1 picocolors: 1.0.0 sirv: 2.0.3 - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) transitivePeerDependencies: - rollup - supports-color @@ -5109,7 +5109,7 @@ packages: fast-glob: 3.2.12 pretty-bytes: 6.1.0 rollup: 3.21.6 - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) workbox-build: 6.5.4 workbox-window: 6.5.4 transitivePeerDependencies: @@ -5129,7 +5129,7 @@ packages: merge-anything: 5.1.6 solid-js: 1.7.5 solid-refresh: 0.5.2(solid-js@1.7.5) - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) vitefu: 0.2.4(vite@4.3.5) transitivePeerDependencies: - supports-color @@ -5139,10 +5139,10 @@ packages: peerDependencies: vite: ^2 || ^3 || ^4 dependencies: - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) dev: true - /vite@4.3.5(@types/node@18.16.6): + /vite@4.3.5(@types/node@18.16.7): resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -5167,7 +5167,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.16.6 + '@types/node': 18.16.7 esbuild: 0.17.18 postcss: 8.4.23 rollup: 3.21.6 @@ -5182,7 +5182,7 @@ packages: vite: optional: true dependencies: - vite: 4.3.5(@types/node@18.16.6) + vite: 4.3.5(@types/node@18.16.7) /wait-on@6.0.1(debug@4.3.4): resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 42941bf..8c36de2 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -28,7 +28,8 @@ export type OnChainTx = { height: number time: number } - } + }, + labels: string[] } export type UtxoItem = { @@ -52,8 +53,6 @@ function OnChainItem(props: { item: OnChainTx, labels: string[] }) { const [open, setOpen] = createSignal(false) - - return ( <> @@ -61,7 +60,7 @@ function OnChainItem(props: { item: OnChainTx, labels: string[] }) { Mempool Link - {JSON.stringify(props.labels)} + {/* {JSON.stringify(props.labels)} */} { - const labels = store.mutiny_wallet?.get_address_labels(); - console.log(labels); - if (!labels) return ["abcdefg"]; - return labels; - // return labels.filter((label) => label.address === props.item.txid) - }) - return ( <> - setOpen(!open())} /> + setOpen(!open())} /> {/*
setOpen(!open())}>
{isSend() ? send arrow : receive arrow} @@ -194,20 +185,20 @@ export function CombinedActivity(props: { limit?: number }) { const [activity] = createResource(getAllActivity); - const addressLabels = createMemo(() => { - const labels = state.mutiny_wallet?.get_address_labels(); - console.log(labels); - return labels || []; - // return labels.filter((label) => label.address === props.item.txid) - }) + // const addressLabels = createMemo(() => { + // const labels = state.mutiny_wallet?.get_address_labels(); + // console.log(labels); + // return labels || []; + // // return labels.filter((label) => label.address === props.item.txid) + // }) - const invoiceLabels = createMemo(() => { - const labels = state.mutiny_wallet?.get_address_labels(); - console.log(labels); - if (!labels) return ["abcdefg"]; - return labels; - // return labels.filter((label) => label.address === props.item.txid) - }) + // const invoiceLabels = createMemo(() => { + // const labels = state.mutiny_wallet?.get_address_labels(); + // console.log(labels); + // if (!labels) return ["abcdefg"]; + // return labels; + // // return labels.filter((label) => label.address === props.item.txid) + // }) return ( @@ -223,11 +214,11 @@ export function CombinedActivity(props: { limit?: number }) { {/* FIXME */} - + {/* FIXME */} - + } diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 130a710..0acbeac 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -52,7 +52,9 @@ type ReceiveState = "edit" | "show" | "paid" type PaidState = "lightning_paid" | "onchain_paid"; function tagItemsToLabels(items: TagItem[]) { - return items.map(item => item.kind === "contact" ? item.id : item.name) + const labels = items.map(item => item.kind === "contact" ? item.id : item.name) + console.log("Labels", labels) + return labels; } export default function Receive() { From c6fcca4592b03530126a5056392cce6bfb9e2ba7 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 13:37:15 -0500 Subject: [PATCH 08/17] fix sends for now --- src/routes/Send.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index 8ef1911..880a685 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -235,16 +235,19 @@ export default function Send() { sentDetails.destination = bolt11; // If the invoice has sats use that, otherwise we pass the user-defined amount if (invoice()?.amount_sats) { - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11); + // FIXME: labels + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, []); sentDetails.amount = invoice()?.amount_sats; } else { - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats()); + // FIXME: labels + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), []); sentDetails.amount = amountSats(); } } else if (source() === "lightning" && nodePubkey()) { const nodes = await state.mutiny_wallet?.list_nodes(); const firstNode = nodes[0] as string || "" - const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats()); + // FIXME: labels + const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), []); console.log(payment?.value) // TODO: handle timeouts From 8c0da8ceedbdd0f6a2c67a86a7e2d353487addcf Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 13:37:25 -0500 Subject: [PATCH 09/17] don't show pwa prompt for now --- src/components/Reload.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Reload.tsx b/src/components/Reload.tsx index 18cf0dd..3311c29 100644 --- a/src/components/Reload.tsx +++ b/src/components/Reload.tsx @@ -25,7 +25,7 @@ const ReloadPrompt: Component = () => { return ( - + {/*
New content available, click on reload button to update.} @@ -38,7 +38,7 @@ const ReloadPrompt: Component = () => { - + */} ) } From dd892f352cc473e9b65c608c88688a9520baa0eb Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 15:33:54 -0500 Subject: [PATCH 10/17] skeleton loader for home screen --- src/components/App.tsx | 46 ++++++++++++++++++-------------- src/components/BalanceBox.tsx | 36 +++++++++++++++++++------ src/components/layout/Button.tsx | 6 +++-- src/state/megaStore.tsx | 9 ++++--- 4 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index b96a600..282f33f 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,5 +1,5 @@ import logo from '~/assets/icons/mutiny-logo.svg'; -import { DefaultMain, MutinyWalletGuard, SafeArea, VStack, Card } from "~/components/layout"; +import { DefaultMain, SafeArea, VStack, Card, FullscreenLoader } from "~/components/layout"; import BalanceBox from "~/components/BalanceBox"; import NavBar from "~/components/NavBar"; import ReloadPrompt from "~/components/Reload"; @@ -7,30 +7,36 @@ import { A } from 'solid-start'; import { OnboardWarning } from '~/components/OnboardWarning'; import { CombinedActivity } from './Activity'; import userClock from '~/assets/icons/user-clock.svg'; +import { useMegaStore } from '~/state/megaStore'; +import { Show } from 'solid-js'; export default function App() { + const [state, _actions] = useMegaStore(); + return ( - - - -
- logo - Activity -
+ + +
+ logo + Activity +
+ - - -
- + + + +
+ + }> - {/* View All */} - - View All - - - - - + + {/* View All */} + + View All + + + + ); } diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index cae3044..708df39 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -1,7 +1,8 @@ import { Show, Suspense } from "solid-js"; -import { ButtonLink, FancyCard, Indicator } from "~/components/layout"; +import { Button, ButtonLink, FancyCard, Indicator } from "~/components/layout"; import { useMegaStore } from "~/state/megaStore"; import { Amount } from "./Amount"; +import { useNavigate } from "solid-start"; function prettyPrintAmount(n?: number | bigint): string { if (!n || n.valueOf() === 0) { @@ -10,19 +11,38 @@ function prettyPrintAmount(n?: number | bigint): string { return n.toLocaleString() } -export default function BalanceBox() { +function LoadingShimmer() { + return (
+

+
+

+

+
+

+
) +} + +export default function BalanceBox(props: { loading?: boolean }) { const [state, actions] = useMegaStore(); + const emptyBalance = () => (state.balance?.confirmed || 0n) === 0n && (state.balance?.lightning || 0n) === 0n + + const navigate = useNavigate() + return ( <> - + }> + + Syncing}> -
- -
+ }> +
+ +
+
@@ -37,8 +57,8 @@ export default function BalanceBox() {
- Send - Receive + +
) diff --git a/src/components/layout/Button.tsx b/src/components/layout/Button.tsx index 3bbc2cb..30b60d3 100644 --- a/src/components/layout/Button.tsx +++ b/src/components/layout/Button.tsx @@ -4,7 +4,7 @@ import { Dynamic } from "solid-js/web"; import { A } from "solid-start"; import { LoadingSpinner } from "."; -const button = cva("p-3 rounded-xl font-semibold disabled:opacity-50 disabled:grayscale transition", { +const button = cva("p-3 rounded-xl font-semibold disabled:opacity-50 disabled:grayscale transition", { variants: { // TODO: button hover has to work different than buttonlinks (like disabled state) intent: { @@ -32,7 +32,8 @@ const button = cva("p-3 rounded-xl font-semibold disabled:opacity-50 disabled:g type StyleProps = VariantProps interface ButtonProps extends JSX.ButtonHTMLAttributes, StyleProps { - loading?: boolean + loading?: boolean, + disabled?: boolean, } export const Button: ParentComponent = props => { @@ -42,6 +43,7 @@ export const Button: ParentComponent = props => { return (
- + Close
- {/* - - */} ) } \ No newline at end of file From eab0346dcd62b9c95411354f9a73c5679beaec57 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 19:10:05 -0500 Subject: [PATCH 12/17] fix string spacing --- src/components/ShareCard.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ShareCard.tsx b/src/components/ShareCard.tsx index 8221f56..32ab3b3 100644 --- a/src/components/ShareCard.tsx +++ b/src/components/ShareCard.tsx @@ -35,13 +35,12 @@ export function StringShower(props: { text: string }) { return ( <> -
+
{props.text}
-
- ) } From 80c5af14899ab26363db99cb80d3f983645ee99b Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Wed, 10 May 2023 23:42:14 -0500 Subject: [PATCH 13/17] lots of tagging works but not sends rn --- src/components/Activity.tsx | 97 +++++++++--------------------- src/components/ActivityItem.tsx | 44 +++++++++++--- src/components/App.tsx | 6 +- src/components/BalanceBox.tsx | 2 +- src/components/ColorRadioGroup.tsx | 59 ------------------ src/components/ContactEditor.tsx | 19 ++---- src/components/ContactForm.tsx | 14 +---- src/components/ContactViewer.tsx | 15 +++-- src/components/TagEditor.tsx | 45 ++++++++++---- src/components/layout/index.tsx | 19 +++++- src/routes/Activity.tsx | 34 ++++++++--- src/routes/Receive.tsx | 25 +++----- src/routes/Send.tsx | 42 +++++-------- src/state/contacts.ts | 58 ------------------ src/state/megaStore.tsx | 5 ++ src/utils/gradientHash.ts | 11 ++-- src/utils/tags.ts | 27 +++++++++ 17 files changed, 222 insertions(+), 300 deletions(-) delete mode 100644 src/components/ColorRadioGroup.tsx delete mode 100644 src/state/contacts.ts create mode 100644 src/utils/tags.ts diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 8c36de2..856f07b 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -1,16 +1,13 @@ -import send from '~/assets/icons/send.svg'; -import receive from '~/assets/icons/receive.svg'; -import { ButtonLink, Card, LoadingSpinner, NiceP, SmallAmount, SmallHeader, VStack } from './layout'; -import { For, Match, ParentComponent, Show, Suspense, Switch, createMemo, createResource, createSignal } from 'solid-js'; +import { LoadingSpinner, NiceP, SmallAmount, SmallHeader } from './layout'; +import { For, Match, ParentComponent, Show, Switch, createMemo, createResource, createSignal } from 'solid-js'; import { useMegaStore } from '~/state/megaStore'; import { MutinyInvoice } from '@mutinywallet/mutiny-wasm'; -import { prettyPrintTime } from '~/utils/prettyPrintTime'; import { JsonModal } from '~/components/JsonModal'; import mempoolTxUrl from '~/utils/mempoolTxUrl'; -import wave from "~/assets/wave.gif" import utxoIcon from '~/assets/icons/coin.svg'; import { getRedshifted } from '~/utils/fakeLabels'; import { ActivityItem } from './ActivityItem'; +import { MutinyTagItem } from '~/utils/tags'; export const THREE_COLUMNS = 'grid grid-cols-[auto,1fr,auto] gap-4 py-2 px-2 border-b border-neutral-800 last:border-b-0' export const CENTER_COLUMN = 'min-w-0 overflow-hidden max-w-full' @@ -40,14 +37,14 @@ export type UtxoItem = { } keychain: string is_spent: boolean, - redshifted?: boolean + redshifted?: boolean, } const SubtleText: ParentComponent = (props) => { return

{props.children}

} -function OnChainItem(props: { item: OnChainTx, labels: string[] }) { +function OnChainItem(props: { item: OnChainTx, labels: MutinyTagItem[] }) { const [store, actions] = useMegaStore(); const isReceive = createMemo(() => props.item.received > 0); @@ -69,26 +66,11 @@ function OnChainItem(props: { item: OnChainTx, labels: string[] }) { positive={isReceive()} onClick={() => setOpen(!open())} /> - {/*
setOpen(!open())}> -
- {isReceive() ? receive arrow : send arrow} -
-
-

Unknown

- {isReceive() ? : } -
-
- - On-chain {isReceive() ? Receive : Send} - - {props.item.confirmation_time?.Confirmed ? prettyPrintTime(props.item.confirmation_time?.Confirmed?.time) : "Unconfirmed"} -
-
*/} ) } -function InvoiceItem(props: { item: MutinyInvoice, labels: string[] }) { +function InvoiceItem(props: { item: MutinyInvoice, labels: MutinyTagItem[] }) { const [store, actions] = useMegaStore(); const isSend = createMemo(() => props.item.is_send); @@ -98,21 +80,6 @@ function InvoiceItem(props: { item: MutinyInvoice, labels: string[] }) { <> setOpen(!open())} /> - {/*
setOpen(!open())}> -
- {isSend() ? send arrow : receive arrow} -
-
-

Unknown

- -
-
- - Lightning {!isSend() ? Receive : Send} - - {prettyPrintTime(Number(props.item.expire))} -
-
*/} ) } @@ -149,57 +116,49 @@ function Utxo(props: { item: UtxoItem }) { ) } -type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number } +type ActivityItem = { type: "onchain" | "lightning", item: OnChainTx | MutinyInvoice, time: number, labels: MutinyTagItem[] } function sortByTime(a: ActivityItem, b: ActivityItem) { return b.time - a.time; } export function CombinedActivity(props: { limit?: number }) { - - const [state, _] = useMegaStore(); + const [state, actions] = useMegaStore(); const getAllActivity = async () => { console.log("Getting all activity"); const txs = await state.mutiny_wallet?.list_onchain() as OnChainTx[]; const invoices = await state.mutiny_wallet?.list_invoices() as MutinyInvoice[]; + const tags = await actions.listTags(); - const activity: ActivityItem[] = []; + let activity: ActivityItem[] = []; - txs.forEach((tx) => { - activity.push({ type: "onchain", item: tx, time: tx.confirmation_time?.Confirmed?.time || Date.now() }) - }) + for (let i = 0; i < txs.length; i++) { + activity.push({ type: "onchain", item: txs[i], time: txs[i].confirmation_time?.Confirmed?.time || Date.now(), labels: [] }) + } - invoices.forEach((invoice) => { - if (invoice.paid) { - activity.push({ type: "lightning", item: invoice, time: Number(invoice.expire) }) + for (let i = 0; i < invoices.length; i++) { + if (invoices[i].paid) { + activity.push({ type: "lightning", item: invoices[i], time: Number(invoices[i].expire), labels: [] }) } - }) + } if (props.limit) { - return activity.sort(sortByTime).slice(0, props.limit); + activity = activity.sort(sortByTime).slice(0, props.limit); } else { - return activity.sort(sortByTime); + activity.sort(sortByTime); } + + for (let i = 0; i < activity.length; i++) { + // filter the tags to only include the ones that have an id matching one of the labels + activity[i].labels = tags.filter((tag) => activity[i].item.labels.includes(tag.id)); + } + + return activity; } const [activity] = createResource(getAllActivity); - // const addressLabels = createMemo(() => { - // const labels = state.mutiny_wallet?.get_address_labels(); - // console.log(labels); - // return labels || []; - // // return labels.filter((label) => label.address === props.item.txid) - // }) - - // const invoiceLabels = createMemo(() => { - // const labels = state.mutiny_wallet?.get_address_labels(); - // console.log(labels); - // if (!labels) return ["abcdefg"]; - // return labels; - // // return labels.filter((label) => label.address === props.item.txid) - // }) - return ( @@ -214,11 +173,11 @@ export function CombinedActivity(props: { limit?: number }) { {/* FIXME */} - + {/* FIXME */} - + } diff --git a/src/components/ActivityItem.tsx b/src/components/ActivityItem.tsx index 915d034..604bc68 100644 --- a/src/components/ActivityItem.tsx +++ b/src/components/ActivityItem.tsx @@ -1,9 +1,11 @@ -import { ParentComponent, createMemo } from "solid-js"; +import { ParentComponent, createMemo, createResource } from "solid-js"; import { InlineAmount } from "./AmountCard"; import { satsToUsd } from "~/utils/conversions"; import bolt from "~/assets/icons/bolt.svg" import chain from "~/assets/icons/chain.svg" import { timeAgo } from "~/utils/prettyPrintTime"; +import { MutinyTagItem } from "~/utils/tags"; +import { generateGradient } from "~/utils/gradientHash"; export const ActivityAmount: ParentComponent<{ amount: string, price: number, positive?: boolean }> = (props) => { const amountInUsd = createMemo(() => { @@ -35,17 +37,43 @@ export const ActivityAmount: ParentComponent<{ amount: string, price: number, po ) } -function LabelCircle(props: { name: string }) { +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 text = () => (props.contact && props.name && props.name.length) ? props.name[0] : (props.name && props.name.length) ? "≡" : "?" + const bg = () => (props.name && props.contact) ? gradient() : "gray" + return (
- {props.name[0] || "?"} + {text()}
) } -export function ActivityItem(props: { kind: "lightning" | "onchain", labels: string[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) { +// function that takes a list of MutinyTagItems and returns bool if one of those items is of kind Contact +function includesContact(labels: MutinyTagItem[]) { + return labels.some((label) => label.kind === "Contact") +} + +// sort the labels so that the contact is always first +function sortLabels(labels: MutinyTagItem[]) { + const contact = labels.find(label => label.kind === "Contact"); + return contact ? [contact, ...labels.filter(label => label !== contact)] : labels; +} + +// return a string of each label name separated by a comma and a space. if the array is empty return "Unknown" +function labelString(labels: MutinyTagItem[]) { + return labels.length ? labels.map(label => label.name).join(", ") : "Unknown" +} + +export function ActivityItem(props: { kind: "lightning" | "onchain", labels: MutinyTagItem[], amount: number | bigint, date?: number | bigint, positive?: boolean, onClick?: () => void }) { + const labels = () => sortLabels(props.labels) return (
props.onClick && props.onClick()} @@ -54,14 +82,14 @@ export function ActivityItem(props: { kind: "lightning" | "onchain", labels: str >
- {props.kind === "lightning" ? lightning : onchain} + {props.kind === "lightning" ? lightning : onchain}
- +
- {props.labels.length ? props.labels[0] : "Unknown"} + {labelString(labels())}
diff --git a/src/components/App.tsx b/src/components/App.tsx index 282f33f..53ca269 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1,6 +1,6 @@ import logo from '~/assets/icons/mutiny-logo.svg'; -import { DefaultMain, SafeArea, VStack, Card, FullscreenLoader } from "~/components/layout"; -import BalanceBox from "~/components/BalanceBox"; +import { DefaultMain, SafeArea, VStack, Card, LoadingSpinner } from "~/components/layout"; +import BalanceBox, { LoadingShimmer } from "~/components/BalanceBox"; import NavBar from "~/components/NavBar"; import ReloadPrompt from "~/components/Reload"; import { A } from 'solid-start'; @@ -28,7 +28,7 @@ export default function App() {
- }> + }> {/* View All */} diff --git a/src/components/BalanceBox.tsx b/src/components/BalanceBox.tsx index 708df39..f0f6c15 100644 --- a/src/components/BalanceBox.tsx +++ b/src/components/BalanceBox.tsx @@ -11,7 +11,7 @@ function prettyPrintAmount(n?: number | bigint): string { return n.toLocaleString() } -function LoadingShimmer() { +export function LoadingShimmer() { return (

diff --git a/src/components/ColorRadioGroup.tsx b/src/components/ColorRadioGroup.tsx deleted file mode 100644 index d32e701..0000000 --- a/src/components/ColorRadioGroup.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { RadioGroup as Kobalte } from '@kobalte/core'; -import { type JSX, Show, splitProps, For } from 'solid-js'; - -type RadioGroupProps = { - name: string; - label?: string | undefined; - options: { label: string; value: string }[]; - value: string | undefined; - error: string; - required?: boolean | undefined; - disabled?: boolean | undefined; - ref: (element: HTMLInputElement | HTMLTextAreaElement) => void; - onInput: JSX.EventHandler; - onChange: JSX.EventHandler; - onBlur: JSX.EventHandler; -}; -type Color = "blue" | "green" | "red" | "gray" - -export const colorVariants = { - blue: "bg-m-blue", - green: "bg-m-green", - red: "bg-m-red", - gray: "bg-[#898989]", -} - -export function ColorRadioGroup(props: RadioGroupProps) { - const [rootProps, inputProps] = splitProps( - props, - ['name', 'value', 'required', 'disabled'], - ['ref', 'onInput', 'onChange', 'onBlur'] - ); - return ( - - - - {props.label} - - -
- - {(option) => ( - - - - - - {/* {option.label} */} - - )} - -
- {props.error} -
- ); -} \ No newline at end of file diff --git a/src/components/ContactEditor.tsx b/src/components/ContactEditor.tsx index 9cf4e0f..e724f94 100644 --- a/src/components/ContactEditor.tsx +++ b/src/components/ContactEditor.tsx @@ -1,24 +1,17 @@ -import { Match, Switch, createSignal, createUniqueId } from 'solid-js'; +import { Match, Switch, createSignal } from 'solid-js'; import { SmallHeader, TinyButton } from '~/components/layout'; import { Dialog } from '@kobalte/core'; import close from "~/assets/icons/close.svg"; import { SubmitHandler } from '@modular-forms/solid'; -import { ContactItem } from '~/state/contacts'; import { ContactForm } from './ContactForm'; +import { ContactFormValues } from './ContactViewer'; -const INITIAL: ContactItem = { id: createUniqueId(), kind: "contact", name: "", color: "gray" } - -export function ContactEditor(props: { createContact: (contact: ContactItem) => void, list?: boolean }) { +export function ContactEditor(props: { createContact: (contact: ContactFormValues) => void, list?: boolean }) { const [isOpen, setIsOpen] = createSignal(false); // What we're all here for in the first place: returning a value - const handleSubmit: SubmitHandler = (c: ContactItem) => { - // TODO: why do the id and color disappear? - - const odd = { id: createUniqueId(), kind: "contact" } - - props.createContact({ ...odd, ...c }) - + const handleSubmit: SubmitHandler = (c: ContactFormValues) => { + props.createContact(c) setIsOpen(false); } @@ -50,7 +43,7 @@ export function ContactEditor(props: { createContact: (contact: ContactItem) => Close

- +
diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index e94c139..b90330b 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -1,13 +1,10 @@ import { SubmitHandler, createForm, required } from "@modular-forms/solid"; -import { ContactItem } from "~/state/contacts"; import { Button, LargeHeader, VStack } from "~/components/layout"; import { TextField } from "~/components/layout/TextField"; -import { ColorRadioGroup } from "~/components/ColorRadioGroup"; +import { ContactFormValues } from "./ContactViewer"; -const colorOptions = [{ label: "blue", value: "blue" }, { label: "green", value: "green" }, { label: "red", value: "red" }, { label: "gray", value: "gray" }] - -export function ContactForm(props: { handleSubmit: SubmitHandler, initialValues?: ContactItem, title: string, cta: string }) { - const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); +export function ContactForm(props: { handleSubmit: SubmitHandler, initialValues?: ContactFormValues, title: string, cta: string }) { + const [_contactForm, { Form, Field }] = createForm({ initialValues: props.initialValues }); return (
@@ -24,11 +21,6 @@ export function ContactForm(props: { handleSubmit: SubmitHandler, i )} - - {(field, props) => ( - - )} -
) diff --git a/src/components/layout/index.tsx b/src/components/layout/index.tsx index 14ffe3a..168f056 100644 --- a/src/components/layout/index.tsx +++ b/src/components/layout/index.tsx @@ -1,9 +1,11 @@ -import { JSX, ParentComponent, Show, Suspense, createSignal } from "solid-js" +import { JSX, ParentComponent, Show, Suspense, createResource, createSignal } from "solid-js" import Linkify from "./Linkify" import { Button, ButtonLink } from "./Button" import { Checkbox as KCheckbox, Separator } from "@kobalte/core" import { useMegaStore } from "~/state/megaStore" import check from "~/assets/icons/check.svg" +import { MutinyTagItem } from "~/utils/tags" +import { generateGradient } from "~/utils/gradientHash" export { Button, @@ -122,9 +124,20 @@ export const NiceP: ParentComponent = (props) => { return (

{props.children}

) } -export const TinyButton: ParentComponent<{ onClick: () => void }> = (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 bg = () => (props.tag?.name && props.tag?.kind === "Contact") ? gradient() : "rgb(255 255 255 / 0.1)" + + console.log("tiny tag", props.tag?.name, gradient()) + return ( - ) diff --git a/src/routes/Activity.tsx b/src/routes/Activity.tsx index 76ce361..a2d1c8c 100644 --- a/src/routes/Activity.tsx +++ b/src/routes/Activity.tsx @@ -5,23 +5,40 @@ import { BackLink } from "~/components/layout/BackLink"; import { CombinedActivity } from "~/components/Activity"; import { A } from "solid-start"; import settings from '~/assets/icons/settings.svg'; -import { ContactItem, addContact, editContact, listContacts } from "~/state/contacts"; import { Tabs } from "@kobalte/core"; import { gradientsPerContact } from "~/utils/gradientHash"; import { ContactEditor } from "~/components/ContactEditor"; -import { ContactViewer } from "~/components/ContactViewer"; +import { ContactFormValues, ContactViewer } from "~/components/ContactViewer"; +import { useMegaStore } from "~/state/megaStore"; +import { Contact } from "@mutinywallet/mutiny-wasm"; +import { showToast } from "~/components/Toaster"; function ContactRow() { - const [contacts, { refetch }] = createResource(listContacts) + const [state, actions] = useMegaStore(); + const [contacts, { refetch }] = createResource(async () => { + const contacts = state.mutiny_wallet?.get_contacts(); + console.log(contacts) + + let c: Contact[] = [] + if (contacts) { + for (let contact in contacts) { + c.push(contacts[contact]) + } + } + return c || [] + }) const [gradients] = createResource(contacts, gradientsPerContact); - async function createContact(contact: ContactItem) { - await addContact(contact) + async function createContact(contact: ContactFormValues) { + const c = new Contact(contact.name, contact.npub ?? undefined, undefined, undefined); + await state.mutiny_wallet?.create_new_contact(c) refetch(); } - async function saveContact(contact: ContactItem) { - await editContact(contact) + // + async function saveContact(contact: ContactFormValues) { + showToast(new Error("Unimplemented")) + // await editContact(contact) refetch(); } @@ -31,7 +48,7 @@ function ContactRow() { {(contact) => ( - + )} @@ -49,6 +66,7 @@ export default function Activity() { Settings}>Activity + Mutiny diff --git a/src/routes/Receive.tsx b/src/routes/Receive.tsx index 0acbeac..f8c6270 100644 --- a/src/routes/Receive.tsx +++ b/src/routes/Receive.tsx @@ -14,10 +14,10 @@ import { StyledRadioGroup } from "~/components/layout/Radio"; 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"; +import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags"; type OnChainTx = { transaction: { @@ -43,22 +43,14 @@ type OnChainTx = { } } -const createUniqueId = () => Math.random().toString(36).substr(2, 9); - 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" type ReceiveState = "edit" | "show" | "paid" type PaidState = "lightning_paid" | "onchain_paid"; -function tagItemsToLabels(items: TagItem[]) { - const labels = items.map(item => item.kind === "contact" ? item.id : item.name) - console.log("Labels", labels) - return labels; -} - export default function Receive() { - const [state, _] = useMegaStore() + const [state, actions] = useMegaStore() const navigate = useNavigate(); const [amount, setAmount] = createSignal("") @@ -68,8 +60,8 @@ export default function Receive() { const [shouldShowAmountEditor, setShouldShowAmountEditor] = createSignal(true) // Tagging stuff - const [selectedValues, setSelectedValues] = createSignal([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([UNKNOWN_TAG]); // The data we get after a payment const [paymentTx, setPaymentTx] = createSignal(); @@ -92,8 +84,8 @@ export default function Receive() { }) onMount(() => { - listTags().then((tags) => { - setValues(prev => [...prev, ...tags || []]) + actions.listTags().then((tags) => { + setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []]) }); }) @@ -109,11 +101,8 @@ export default function Receive() { async function getUnifiedQr(amount: string) { const bigAmount = BigInt(amount); - console.log(selectedValues()); - console.log(tagItemsToLabels(selectedValues())) try { - // FIXME: actual labels - const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagItemsToLabels(selectedValues())); + const raw = await state.mutiny_wallet?.create_bip21(bigAmount, tagsToIds(selectedValues())); // Save the raw info so we can watch the address and invoice setBip21Raw(raw); diff --git a/src/routes/Send.tsx b/src/routes/Send.tsx index 880a685..c6a306d 100644 --- a/src/routes/Send.tsx +++ b/src/routes/Send.tsx @@ -17,9 +17,9 @@ import mempoolTxUrl from "~/utils/mempoolTxUrl"; import { BackLink } from "~/components/layout/BackLink"; import { useNavigate } from "solid-start"; import { TagEditor } from "~/components/TagEditor"; -import { TagItem, createUniqueId, listTags } from "~/state/contacts"; import { StringShower } from "~/components/ShareCard"; import { AmountCard } from "~/components/AmountCard"; +import { MutinyTagItem, UNKNOWN_TAG, sortByLastUsed, tagsToIds } from "~/utils/tags"; type SendSource = "lightning" | "onchain"; @@ -97,23 +97,6 @@ function DestinationShower(props: { ) } -function SendTags() { - // Tagging stuff - const [selectedValues, setSelectedValues] = createSignal([]); - const [values, setValues] = createSignal([{ id: createUniqueId(), name: "Unknown", kind: "text" }]); - - onMount(() => { - listTags().then((tags) => { - setValues(prev => [...prev, ...tags || []]) - }); - }) - - return ( - - ) -} - - export default function Send() { const [state, actions] = useMegaStore(); const navigate = useNavigate() @@ -136,6 +119,10 @@ export default function Send() { const [sending, setSending] = createSignal(false); const [sentDetails, setSentDetails] = createSignal(); + // Tagging stuff + const [selectedValues, setSelectedValues] = createSignal([]); + const [values, setValues] = createSignal([UNKNOWN_TAG]); + function clearAll() { setDestination(undefined); setAmountSats(0n); @@ -158,6 +145,10 @@ export default function Send() { setDestination(state.scan_result); actions.setScanResult(undefined); } + + actions.listTags().then((tags) => { + setValues(prev => [...prev, ...tags.sort(sortByLastUsed) || []]) + }); }) // Rerun every time the destination changes @@ -235,20 +226,16 @@ export default function Send() { sentDetails.destination = bolt11; // If the invoice has sats use that, otherwise we pass the user-defined amount if (invoice()?.amount_sats) { - // FIXME: labels - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, []); + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, undefined, tagsToIds(selectedValues())); sentDetails.amount = invoice()?.amount_sats; } else { - // FIXME: labels - await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), []); + await state.mutiny_wallet?.pay_invoice(firstNode, bolt11, amountSats(), tagsToIds(selectedValues())); sentDetails.amount = amountSats(); } } else if (source() === "lightning" && nodePubkey()) { const nodes = await state.mutiny_wallet?.list_nodes(); const firstNode = nodes[0] as string || "" - // FIXME: labels - const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), []); - console.log(payment?.value) + const payment = await state.mutiny_wallet?.keysend(firstNode, nodePubkey()!, amountSats(), tagsToIds(selectedValues())); // TODO: handle timeouts if (!payment?.paid) { @@ -257,9 +244,8 @@ export default function Send() { sentDetails.amount = amountSats(); } } else if (source() === "onchain" && address()) { - // FIXME: actual labels // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), []); + const txid = await state.mutiny_wallet?.send_to_address(address()!, amountSats(), tagsToIds(selectedValues())); sentDetails.amount = amountSats(); sentDetails.destination = address(); // TODO: figure out if this is necessary, it takes forever @@ -323,7 +309,7 @@ export default function Send() { - + diff --git a/src/state/contacts.ts b/src/state/contacts.ts deleted file mode 100644 index 7637faa..0000000 --- a/src/state/contacts.ts +++ /dev/null @@ -1,58 +0,0 @@ -export type TagItem = TextItem | ContactItem; - -export type TextItem = { - id: string; - kind: "text"; - name: string; -} - -export type ContactItem = { - id: string; - kind: "contact"; - name: string; - npub?: string; - color: Color; -} - -export type Color = "blue" | "green" | "red" | "gray" - -export const createUniqueId = () => Math.random().toString(36).substr(2, 9); - -export async function listContacts(): Promise { - // get contacts from localstorage - const contacts: ContactItem[] = JSON.parse(localStorage.getItem("contacts") || "[]"); - return contacts; -} - -export async function listTexts(): Promise { - // get texts from localstorage - const texts: TextItem[] = JSON.parse(localStorage.getItem("texts") || "[]"); - return texts; -} - -export async function listTags(): Promise { - const contacts = await listContacts(); - const texts = await listTexts(); - return [...contacts, ...texts]; -} - -export async function addContact(contact: ContactItem): Promise { - const contacts = await listContacts(); - contacts.push(contact); - localStorage.setItem("contacts", JSON.stringify(contacts)); -} - -export async function editContact(contact: ContactItem): Promise { - const contacts = await listContacts(); - const index = contacts.findIndex(c => c.id === contact.id); - contacts[index] = contact; - localStorage.setItem("contacts", JSON.stringify(contacts)); -} - -export async function addTextTag(text: TextItem): Promise { - const texts = await listTexts(); - texts.push(text); - localStorage.setItem("texts", JSON.stringify(texts)); -} - - diff --git a/src/state/megaStore.tsx b/src/state/megaStore.tsx index 74a2870..7038444 100644 --- a/src/state/megaStore.tsx +++ b/src/state/megaStore.tsx @@ -6,6 +6,7 @@ import { createStore } from "solid-js/store"; import { MutinyWalletSettingStrings, setupMutinyWallet } from "~/logic/mutinyWalletSetup"; import { MutinyBalance, MutinyWallet } from "@mutinywallet/mutiny-wasm"; import { ParsedParams } from "~/routes/Scanner"; +import { MutinyTagItem } from "~/utils/tags"; const MegaStoreContext = createContext(); @@ -33,6 +34,7 @@ export type MegaStore = [{ sync(): Promise; dismissRestorePrompt(): void; setHasBackedUp(): void; + listTags(): Promise; }]; export const Provider: ParentComponent = (props) => { @@ -119,6 +121,9 @@ export const Provider: ParentComponent = (props) => { dismissRestorePrompt() { localStorage.setItem("dismissed_restore_prompt", "true") setState({ dismissed_restore_prompt: true }) + }, + async listTags(): Promise { + return state.mutiny_wallet?.get_tag_items() as MutinyTagItem[] } }; diff --git a/src/utils/gradientHash.ts b/src/utils/gradientHash.ts index 4d85234..22d65d8 100644 --- a/src/utils/gradientHash.ts +++ b/src/utils/gradientHash.ts @@ -1,6 +1,6 @@ -import { ContactItem } from "~/state/contacts"; +import { Contact } from "@mutinywallet/mutiny-wasm"; -async function generateGradientFromHashedString(str: string) { +export async function generateGradient(str: string) { const encoder = new TextEncoder(); const data = encoder.encode(str); const digestBuffer = await crypto.subtle.digest('SHA-256', data); @@ -13,11 +13,12 @@ async function generateGradientFromHashedString(str: string) { return gradient; } -export async function gradientsPerContact(contacts: ContactItem[]) { +export async function gradientsPerContact(contacts: Contact[]) { + console.log(contacts); const gradients = new Map(); for (const contact of contacts) { - const gradient = await generateGradientFromHashedString(contact.name); - gradients.set(contact.id, gradient); + const gradient = await generateGradient(contact.name); + gradients.set(contact.name, gradient); } return gradients; diff --git a/src/utils/tags.ts b/src/utils/tags.ts new file mode 100644 index 0000000..a76d835 --- /dev/null +++ b/src/utils/tags.ts @@ -0,0 +1,27 @@ +import { TagItem } from "@mutinywallet/mutiny-wasm" + +export type MutinyTagItem = { + id: string, + kind: "Label" | "Contact" + name: string, + last_used_time: bigint, + npub?: string, + ln_address?: string, + lnurl?: string, +} + +export const UNKNOWN_TAG: MutinyTagItem = { id: "Unknown", kind: "Label", name: "Unknown", last_used_time: 0n } + +export function tagsToIds(tags: MutinyTagItem[]): string[] { + return tags.filter((tag) => tag.id !== "Unknown").map((tag) => tag.id) +} + +export function tagToMutinyTag(tag: TagItem): MutinyTagItem { + // @ts-ignore + // FIXME: make typescript less mad about this + return tag as MutinyTagItem +} + +export function sortByLastUsed(a: MutinyTagItem, b: MutinyTagItem) { + return Number(b.last_used_time - a.last_used_time); +} \ No newline at end of file From cbd990d8206977dc24c35bba2becebec6d5d285c Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 11 May 2023 10:54:48 -0500 Subject: [PATCH 14/17] add tags list to kitchensink --- src/components/KitchenSink.tsx | 37 ++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/components/KitchenSink.tsx b/src/components/KitchenSink.tsx index 5f8e353..8e2f119 100644 --- a/src/components/KitchenSink.tsx +++ b/src/components/KitchenSink.tsx @@ -110,7 +110,7 @@ function ConnectPeer(props: { refetchPeers: RefetchPeersType }) { @@ -259,7 +259,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) { Pubkey @@ -267,7 +267,7 @@ function OpenChannel(props: { refetchChannels: RefetchChannelsListType }) { Amount @@ -313,7 +313,7 @@ function LnUrlAuth() { @@ -327,6 +327,32 @@ function LnUrlAuth() { ) } +function ListTags() { + const [_state, actions] = useMegaStore() + + const [tags] = createResource(actions.listTags) + + return ( + + + +

+ {">"} Tags +

+
+ + +
+                        {JSON.stringify(tags(), null, 2)}
+                    
+
+
+
+ + ) +} + + export default function KitchenSink() { @@ -339,6 +365,9 @@ export default function KitchenSink() {
+
+ +
From 7c802fde542de91b76a0afb2b8704e828659f997 Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 11 May 2023 10:56:09 -0500 Subject: [PATCH 15/17] npub doesn't work so hide from contact form --- src/components/Activity.tsx | 2 +- src/components/ContactForm.tsx | 4 ++-- src/components/ContactViewer.tsx | 3 ++- src/components/TagEditor.tsx | 1 + src/routes/Activity.tsx | 3 ++- src/routes/Redshift.tsx | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/components/Activity.tsx b/src/components/Activity.tsx index 856f07b..e567efb 100644 --- a/src/components/Activity.tsx +++ b/src/components/Activity.tsx @@ -46,7 +46,7 @@ const SubtleText: ParentComponent = (props) => { function OnChainItem(props: { item: OnChainTx, labels: MutinyTagItem[] }) { const [store, actions] = useMegaStore(); - const isReceive = createMemo(() => props.item.received > 0); + const isReceive = () => props.item.received > props.item.sent const [open, setOpen] = createSignal(false) diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index b90330b..b6a10b1 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -16,11 +16,11 @@ export function ContactForm(props: { handleSubmit: SubmitHandler )} - + {/* {(field, props) => ( )} - + */}
+
+ + + ) +} \ No newline at end of file diff --git a/src/components/SettingsStringsEditor.tsx b/src/components/SettingsStringsEditor.tsx index ed31dcb..aa98f2d 100644 --- a/src/components/SettingsStringsEditor.tsx +++ b/src/components/SettingsStringsEditor.tsx @@ -1,7 +1,7 @@ import { createForm, url } from '@modular-forms/solid'; import { TextField } from '~/components/layout/TextField'; import { MutinyWalletSettingStrings, getExistingSettings } from '~/logic/mutinyWalletSetup'; -import { Button, SmallHeader } from '~/components/layout'; +import { Button, Card, SmallHeader } from '~/components/layout'; import { showToast } from './Toaster'; import eify from '~/utils/eify'; import { useMegaStore } from '~/state/megaStore'; @@ -24,36 +24,38 @@ export function SettingsStringsEditor() { console.log(values) } - return -

Don't trust us! Use your own servers to back Mutiny.

-
- Network -
-                {existingSettings.network}
-            
-
+ return + +

Don't trust us! Use your own servers to back Mutiny.

+
+ Network +
+                    {existingSettings.network}
+                
+
- - {(field, props) => ( - - )} - - - {(field, props) => ( - - )} - - - {(field, props) => ( - - )} - - - {(field, props) => ( - - )} - - - + + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + {(field, props) => ( + + )} + + + +
} \ No newline at end of file diff --git a/src/routes/Settings.tsx b/src/routes/Settings.tsx index 1ab8639..57db0fd 100644 --- a/src/routes/Settings.tsx +++ b/src/routes/Settings.tsx @@ -1,5 +1,6 @@ import { ButtonLink, DefaultMain, LargeHeader, MutinyWalletGuard, SafeArea, VStack } from "~/components/layout"; import { BackLink } from "~/components/layout/BackLink"; +import { Logs } from "~/components/Logs"; import NavBar from "~/components/NavBar"; import { SeedWords } from "~/components/SeedWords"; import { SettingsStringsEditor } from "~/components/SettingsStringsEditor"; @@ -20,6 +21,7 @@ export default function Settings() { + "I know what I'm doing" diff --git a/src/utils/download.ts b/src/utils/download.ts index 4fa9f5b..90c3a0a 100644 --- a/src/utils/download.ts +++ b/src/utils/download.ts @@ -1,7 +1,7 @@ // https://stackoverflow.com/questions/34156282/how-do-i-save-json-to-local-text-file -export function downloadTextFile(content: string, fileName: string) { - const contentType = "application/json"; +export function downloadTextFile(content: string, fileName: string, type: string) { + const contentType = type ? type : "application/json"; const a = document.createElement("a"); const file = new Blob([content], { type: contentType }); a.href = URL.createObjectURL(file); From f90011df0d75c6dad14e904d1b084d76e237466c Mon Sep 17 00:00:00 2001 From: Paul Miller Date: Thu, 11 May 2023 17:54:40 -0500 Subject: [PATCH 17/17] update to mutiny 0.3.2 --- package.json | 8 +++---- pnpm-lock.yaml | 64 +++++++++++++++++++++++++------------------------- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index b7e1e3c..677f28c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mws", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "scripts": { "dev": "solid-start dev", @@ -10,7 +10,7 @@ }, "type": "module", "devDependencies": { - "@types/node": "^18.16.7", + "@types/node": "^18.16.8", "@typescript-eslint/eslint-plugin": "^5.59.5", "@typescript-eslint/parser": "^5.59.5", "autoprefixer": "^10.4.14", @@ -33,7 +33,7 @@ "@kobalte/core": "^0.9.6", "@kobalte/tailwindcss": "^0.5.0", "@modular-forms/solid": "^0.13.2", - "@mutinywallet/mutiny-wasm": "^0.3.1", + "@mutinywallet/mutiny-wasm": "^0.3.2", "@mutinywallet/waila-wasm": "^0.1.5", "@solid-primitives/upload": "^0.0.111", "@solidjs/meta": "^0.28.5", @@ -45,7 +45,7 @@ "solid-js": "^1.7.5", "solid-qr-code": "^0.0.8", "solid-start": "^0.2.26", - "undici": "^5.22.0" + "undici": "^5.22.1" }, "engines": { "node": ">=16.8" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cbe7648..1f9c6a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,8 +11,8 @@ dependencies: specifier: ^0.13.2 version: 0.13.2(solid-js@1.7.5) '@mutinywallet/mutiny-wasm': - specifier: ^0.3.1 - version: 0.3.1 + specifier: ^0.3.2 + version: 0.3.2 '@mutinywallet/waila-wasm': specifier: ^0.1.5 version: 0.1.5 @@ -47,13 +47,13 @@ dependencies: specifier: ^0.2.26 version: 0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5) undici: - specifier: ^5.22.0 - version: 5.22.0 + specifier: ^5.22.1 + version: 5.22.1 devDependencies: '@types/node': - specifier: ^18.16.7 - version: 18.16.7 + specifier: ^18.16.8 + version: 18.16.8 '@typescript-eslint/eslint-plugin': specifier: ^5.59.5 version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5) @@ -86,7 +86,7 @@ devDependencies: version: 8.4.23 solid-start-node: specifier: ^0.2.26 - version: 0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5) + version: 0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5) tailwindcss: specifier: ^3.3.2 version: 3.3.2 @@ -95,7 +95,7 @@ devDependencies: version: 4.9.5 vite: specifier: ^4.3.5 - version: 4.3.5(@types/node@18.16.7) + version: 4.3.5(@types/node@18.16.8) vite-plugin-pwa: specifier: ^0.14.7 version: 0.14.7(vite@4.3.5)(workbox-build@6.5.4)(workbox-window@6.5.4) @@ -1625,8 +1625,8 @@ packages: solid-js: 1.7.5 dev: false - /@mutinywallet/mutiny-wasm@0.3.1: - resolution: {integrity: sha512-TSOU0ttNZuAmiP7ZymyW6bgWpUk/6F/++YOEDfymkcIKsEWhhITNGcAVFPHzZQP/dLU7leoR3puHauKjoDa8lQ==} + /@mutinywallet/mutiny-wasm@0.3.2: + resolution: {integrity: sha512-m0VyEmVJ6Gl3YiTYYZLegeHFFVW21S2khtFljRyKKtcm0T8FZwJi0w2gNBaLQTakl5mpXwBgjTQwLqFnKSuhuQ==} dev: false /@mutinywallet/waila-wasm@0.1.5: @@ -1982,8 +1982,8 @@ packages: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true - /@types/node@18.16.7: - resolution: {integrity: sha512-MFg7ua/bRtnA1hYE3pVyWxGd/r7aMqjNOdHvlSsXV3n8iaeGKkOaPzpJh6/ovf4bEXWcojkeMJpTsq3mzXW4IQ==} + /@types/node@18.16.8: + resolution: {integrity: sha512-p0iAXcfWCOTCBbsExHIDFCfwsqFwBTgETJveKMT+Ci3LY9YqQCI91F5S+TB20+aRCXpcWfvx5Qr5EccnwCm2NA==} /@types/offscreencanvas@2019.7.0: resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==} @@ -1992,7 +1992,7 @@ packages: /@types/resolve@1.17.1: resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} dependencies: - '@types/node': 18.16.7 + '@types/node': 18.16.8 dev: true /@types/resolve@1.20.2: @@ -2370,7 +2370,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001486 - electron-to-chromium: 1.4.389 + electron-to-chromium: 1.4.392 node-releases: 2.0.10 update-browserslist-db: 1.0.11(browserslist@4.21.5) @@ -2653,8 +2653,8 @@ packages: jake: 10.8.5 dev: true - /electron-to-chromium@1.4.389: - resolution: {integrity: sha512-WDgWUOK8ROR7sDFyYmxCUOoDc50lPgYAHAHwnnD1iN3SKO/mpqftb9iIPiEkMKmqYdkrR0j3N/O+YB/U7lSxwg==} + /electron-to-chromium@1.4.392: + resolution: {integrity: sha512-TXQOMW9tnhIms3jGy/lJctLjICOgyueZFJ1KUtm6DTQ+QpxX3p7ZBwB6syuZ9KBuT5S4XX7bgY1ECPgfxKUdOg==} /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -3803,7 +3803,7 @@ packages: resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.16.7 + '@types/node': 18.16.8 merge-stream: 2.0.0 supports-color: 7.2.0 dev: true @@ -4596,7 +4596,7 @@ packages: '@babel/types': 7.21.5 solid-js: 1.7.5 - /solid-start-node@0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5): + /solid-start-node@0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5): resolution: {integrity: sha512-8vciTGoQV+lIlCUSVHJPazlaoKDRfBowDkPeBr/EZdmtbcMOKoJYf/APPQWFspylF+nhzunMf0+zJP90VtMEYg==} peerDependencies: solid-start: '*' @@ -4612,8 +4612,8 @@ packages: sirv: 2.0.3 solid-start: 0.2.26(@solidjs/meta@0.28.5)(@solidjs/router@0.8.2)(solid-js@1.7.5)(solid-start-node@0.2.26)(vite@4.3.5) terser: 5.17.3 - undici: 5.22.0 - vite: 4.3.5(@types/node@18.16.7) + undici: 5.22.1 + vite: 4.3.5(@types/node@18.16.8) transitivePeerDependencies: - supports-color @@ -4680,10 +4680,10 @@ packages: set-cookie-parser: 2.6.0 sirv: 2.0.3 solid-js: 1.7.5 - solid-start-node: 0.2.26(solid-start@0.2.26)(undici@5.22.0)(vite@4.3.5) + solid-start-node: 0.2.26(solid-start@0.2.26)(undici@5.22.1)(vite@4.3.5) terser: 5.17.3 - undici: 5.22.0 - vite: 4.3.5(@types/node@18.16.7) + undici: 5.22.1 + vite: 4.3.5(@types/node@18.16.8) vite-plugin-inspect: 0.7.26(rollup@3.21.6)(vite@4.3.5) vite-plugin-solid: 2.7.0(solid-js@1.7.5)(vite@4.3.5) wait-on: 6.0.1(debug@4.3.4) @@ -5005,8 +5005,8 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /undici@5.22.0: - resolution: {integrity: sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==} + /undici@5.22.1: + resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} engines: {node: '>=14.0'} dependencies: busboy: 1.6.0 @@ -5092,7 +5092,7 @@ packages: fs-extra: 11.1.1 picocolors: 1.0.0 sirv: 2.0.3 - vite: 4.3.5(@types/node@18.16.7) + vite: 4.3.5(@types/node@18.16.8) transitivePeerDependencies: - rollup - supports-color @@ -5109,7 +5109,7 @@ packages: fast-glob: 3.2.12 pretty-bytes: 6.1.0 rollup: 3.21.6 - vite: 4.3.5(@types/node@18.16.7) + vite: 4.3.5(@types/node@18.16.8) workbox-build: 6.5.4 workbox-window: 6.5.4 transitivePeerDependencies: @@ -5129,7 +5129,7 @@ packages: merge-anything: 5.1.6 solid-js: 1.7.5 solid-refresh: 0.5.2(solid-js@1.7.5) - vite: 4.3.5(@types/node@18.16.7) + vite: 4.3.5(@types/node@18.16.8) vitefu: 0.2.4(vite@4.3.5) transitivePeerDependencies: - supports-color @@ -5139,10 +5139,10 @@ packages: peerDependencies: vite: ^2 || ^3 || ^4 dependencies: - vite: 4.3.5(@types/node@18.16.7) + vite: 4.3.5(@types/node@18.16.8) dev: true - /vite@4.3.5(@types/node@18.16.7): + /vite@4.3.5(@types/node@18.16.8): resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -5167,7 +5167,7 @@ packages: terser: optional: true dependencies: - '@types/node': 18.16.7 + '@types/node': 18.16.8 esbuild: 0.17.18 postcss: 8.4.23 rollup: 3.21.6 @@ -5182,7 +5182,7 @@ packages: vite: optional: true dependencies: - vite: 4.3.5(@types/node@18.16.7) + vite: 4.3.5(@types/node@18.16.8) /wait-on@6.0.1(debug@4.3.4): resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==}