Files
mutiny-web/src/components/Activity.tsx
2023-05-23 19:03:52 -05:00

203 lines
5.2 KiB
TypeScript

import { LoadingSpinner, NiceP } from "./layout"
import {
For,
Match,
Switch,
createEffect,
createMemo,
createResource,
createSignal,
} from "solid-js"
import { useMegaStore } from "~/state/megaStore"
import { MutinyInvoice } from "@mutinywallet/mutiny-wasm"
import { ActivityItem } from "./ActivityItem"
import { MutinyTagItem } from "~/utils/tags"
import { Network } from "~/logic/mutinyWalletSetup"
import { DetailsModal } from "./DetailsModal"
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"
export const MISSING_LABEL =
"py-1 px-2 bg-white/10 rounded inline-block text-sm"
export const REDSHIFT_LABEL =
"py-1 px-2 bg-white text-m-red rounded inline-block text-sm"
export const RIGHT_COLUMN = "flex flex-col items-right text-right max-w-[8rem]"
export type OnChainTx = {
txid: string
received: number
sent: number
fee?: number
confirmation_time?: {
Confirmed?: {
height: number
time: number
}
}
labels: string[]
}
export type UtxoItem = {
outpoint: string
txout: {
value: number
script_pubkey: string
}
keychain: string
is_spent: boolean
redshifted?: boolean
}
function OnChainItem(props: {
item: OnChainTx
labels: MutinyTagItem[]
network: Network
}) {
const isReceive = () => props.item.received > props.item.sent
const [open, setOpen] = createSignal(false)
return (
<>
<DetailsModal open={open()} data={props.item} setOpen={setOpen} />
<ActivityItem
kind={"onchain"}
labels={props.labels}
// FIXME: is this something we can put into node logic?
amount={
isReceive()
? props.item.received - props.item.sent
: props.item.sent - props.item.received
}
date={props.item.confirmation_time?.Confirmed?.time}
positive={isReceive()}
onClick={() => setOpen(o => !o)}
/>
</>
)
}
function InvoiceItem(props: { item: MutinyInvoice; labels: MutinyTagItem[] }) {
const isSend = createMemo(() => props.item.is_send)
const [open, setOpen] = createSignal(false)
return (
<>
<DetailsModal open={open()} data={props.item} setOpen={setOpen} />
<ActivityItem
kind={"lightning"}
labels={props.labels}
amount={props.item.amount_sats || 0n}
date={props.item.last_updated}
positive={!isSend()}
onClick={() => setOpen(o => !o)}
/>
</>
)
}
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, 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()
let activity: ActivityItem[] = []
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: [],
})
}
for (let i = 0; i < invoices.length; i++) {
if (invoices[i].paid) {
activity.push({
type: "lightning",
item: invoices[i],
time: Number(invoices[i].last_updated),
labels: [],
})
}
}
if (props.limit) {
activity = activity.sort(sortByTime).slice(0, props.limit)
} else {
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, { refetch }] = createResource(getAllActivity)
const network = state.mutiny_wallet?.get_network() as Network
createEffect(() => {
// After every sync we should refetch the activity
if (!state.is_syncing) {
refetch()
}
})
return (
<Switch>
<Match when={activity.loading}>
<LoadingSpinner wide />
</Match>
<Match when={activity.state === "ready" && activity().length === 0}>
<NiceP>No activity to show</NiceP>
</Match>
<Match when={activity.state === "ready" && activity().length >= 0}>
<For each={activity.latest}>
{(activityItem) => (
<Switch>
<Match when={activityItem.type === "onchain"}>
<OnChainItem
item={activityItem.item as OnChainTx}
labels={activityItem.labels}
network={network}
/>
</Match>
<Match when={activityItem.type === "lightning"}>
<InvoiceItem
item={activityItem.item as MutinyInvoice}
labels={activityItem.labels}
/>
</Match>
</Switch>
)}
</For>
</Match>
</Switch>
)
}