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() ?
:
}
-
-
-
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() ?
:
}
-
-
-
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" ?
:
}
+ {props.kind === "lightning" ?
:
}
-
+
- {props.labels.length ? props.labels[0] : "Unknown"}
+ {labelString(labels())}
{timeAgo(props.date)}
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) =>
-
+
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 (
diff --git a/src/components/ContactViewer.tsx b/src/components/ContactViewer.tsx
index 83514f2..89814f7 100644
--- a/src/components/ContactViewer.tsx
+++ b/src/components/ContactViewer.tsx
@@ -3,16 +3,23 @@ import { Button, Card, NiceP, SmallHeader } 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 { showToast } from './Toaster';
+import { Contact } from '@mutinywallet/mutiny-wasm';
-export function ContactViewer(props: { contact: ContactItem, gradient: string, saveContact: (contact: ContactItem) => void }) {
+export type ContactFormValues = {
+ name: string,
+ npub?: string,
+}
+
+export function ContactViewer(props: { contact: Contact, gradient: string, saveContact: (contact: Contact) => void }) {
const [isOpen, setIsOpen] = createSignal(false);
const [isEditing, setIsEditing] = createSignal(false);
- const handleSubmit: SubmitHandler = (c: ContactItem) => {
- props.saveContact({ ...props.contact, ...c })
+ const handleSubmit: SubmitHandler = (c: ContactFormValues) => {
+ // FIXME: merge with existing contact if saving (need edit contact method)
+ const contact = new Contact(c.name, c.npub ?? undefined, undefined, undefined)
+ props.saveContact(contact)
setIsEditing(false)
}
diff --git a/src/components/TagEditor.tsx b/src/components/TagEditor.tsx
index 09e01c7..8d94de8 100644
--- a/src/components/TagEditor.tsx
+++ b/src/components/TagEditor.tsx
@@ -1,9 +1,12 @@
import { Select, createOptions } from "@thisbeyond/solid-select";
import "~/styles/solid-select.css"
-import { For, createUniqueId } from "solid-js";
+import { For } from "solid-js";
import { ContactEditor } from "./ContactEditor";
-import { ContactItem, TagItem, TextItem, addContact } from "~/state/contacts";
import { TinyButton } from "./layout";
+import { ContactFormValues } from "./ContactViewer";
+import { MutinyTagItem } from "~/utils/tags";
+import { Contact } from "@mutinywallet/mutiny-wasm";
+import { useMegaStore } from "~/state/megaStore";
// take two arrays, subtract the second from the first, then return the first
function subtract(a: T[], b: T[]) {
@@ -11,12 +14,20 @@ function subtract(a: T[], b: T[]) {
return a.filter(x => !set.has(x));
}
-const createValue = (name: string): TextItem => {
- return { id: createUniqueId(), name, kind: "text" };
+const createLabelValue = (label: string): Partial => {
+ return { id: label, name: label, kind: "Label" };
};
-export function TagEditor(props: { values: TagItem[], setValues: (values: TagItem[]) => void, selectedValues: TagItem[], setSelectedValues: (values: TagItem[]) => void, placeholder: string }) {
- const onChange = (selected: TagItem[]) => {
+export function TagEditor(props: {
+ values: MutinyTagItem[],
+ setValues: (values: MutinyTagItem[]) => void,
+ selectedValues: MutinyTagItem[],
+ setSelectedValues: (values: MutinyTagItem[]) => void,
+ placeholder: string
+}) {
+ const [state, actions] = useMegaStore();
+
+ const onChange = (selected: MutinyTagItem[]) => {
props.setSelectedValues(selected);
console.log(selected)
@@ -31,12 +42,22 @@ export function TagEditor(props: { values: TagItem[], setValues: (values: TagIte
key: "name",
disable: (value) => props.selectedValues.includes(value),
filterable: true, // Default
- createable: createValue,
+ createable: createLabelValue,
});
- const newContact = async (contact: ContactItem) => {
- await addContact(contact)
- onChange([...props.selectedValues, contact])
+ async function createContact(contact: ContactFormValues) {
+ // FIXME: undefineds
+ const c = new Contact(contact.name, undefined, undefined, undefined);
+ const newContactId = await state.mutiny_wallet?.create_new_contact(c);
+ const contactItem = await state.mutiny_wallet?.get_contact(newContactId ?? "");
+ const mutinyContactItem: MutinyTagItem = { id: contactItem?.id || "", name: contactItem?.name || "", kind: "Contact", last_used_time: 0n };
+ if (contactItem) {
+ // @ts-ignore
+ // FIXME: make typescript less mad about this
+ onChange([...props.selectedValues, mutinyContactItem])
+ } else {
+ console.error("Failed to create contact")
+ }
}
return (
@@ -52,13 +73,13 @@ export function TagEditor(props: { values: TagItem[], setValues: (values: TagIte
{(tag) => (
- onChange([...props.selectedValues, tag])}
+ onChange([...props.selectedValues, tag])}
>
{tag.name}
)}
-
+
)
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 (
- props.onClick()}>
+ props.onClick()}
+ style={{ background: bg() }}
+ >
{props.children}
)
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() {
}>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