pending nwc approve / deny

This commit is contained in:
Paul Miller
2023-06-30 11:44:07 -05:00
parent 7aea08c36c
commit 50f0bd6aed
5 changed files with 207 additions and 7 deletions

View File

@@ -0,0 +1,3 @@
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m9.9998 13.5998 5.9-5.9c.1833-.18333.4167-.275.7-.275.2833 0 .5167.09167.7.275.1833.18334.275.41667.275.7 0 .28334-.0917.51667-.275.7l-6.6 6.6c-.2.2-.4333.3-.7.3-.26666 0-.5-.1-.7-.3l-2.6-2.6c-.18333-.1833-.275-.4167-.275-.7 0-.2833.09167-.5167.275-.7.18334-.1833.41667-.275.7-.275.28334 0 .51667.0917.7.275l1.9 1.9Z" fill="hsla(163, 70%, 38%, 1)"/>
</svg>

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -0,0 +1,3 @@
<svg width="48" height="48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m14 14 20 20m-20 0 20-20" stroke="hsla(343, 92%, 54%, 1)" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 218 B

View File

@@ -11,6 +11,7 @@ import { ExternalLink } from "./layout/ExternalLink";
import { BetaWarningModal } from "~/components/BetaWarningModal";
import settings from "~/assets/icons/settings.svg";
import pixelLogo from "~/assets/mutiny-pixel-logo.png";
import { PendingNwc } from "./PendingNwc";
export default function App() {
const [state, _actions] = useMegaStore();
@@ -49,6 +50,9 @@ export default function App() {
<ReloadPrompt />
</Show>
<BalanceBox loading={state.wallet_loading} />
<Show when={!state.wallet_loading}>
<PendingNwc />
</Show>
<Card title="Activity">
<div class="p-1" />
<VStack>
@@ -58,12 +62,11 @@ export default function App() {
>
<CombinedActivity limit={3} />
</Show>
{/* <ButtonLink href="/activity">View All</ButtonLink> */}
</VStack>
<Show when={state.activity && state.activity.length > 0}>
<A
href="/activity"
class="text-m-red active:text-m-red/80 text-xl font-semibold no-underline self-center"
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
>
View All
</A>

View File

@@ -0,0 +1,187 @@
import { NwcProfile } from "@mutinywallet/mutiny-wasm";
import { timeAgo } from "~/utils/prettyPrintTime";
import { Card, LoadingSpinner, VStack } from "./layout";
import bolt from "~/assets/icons/bolt.svg";
import {
For,
Match,
Show,
Suspense,
Switch,
createEffect,
createResource,
createSignal
} from "solid-js";
import { useMegaStore } from "~/state/megaStore";
import greenCheck from "~/assets/icons/green-check.svg";
import redClose from "~/assets/icons/red-close.svg";
import { ActivityAmount } from "./ActivityItem";
import { InfoBox } from "./InfoBox";
import eify from "~/utils/eify";
import { A } from "solid-start";
type PendingItem = {
id: string;
name_of_connection: string;
date?: bigint;
amount_sats?: bigint;
};
export function PendingNwc() {
const [state, _actions] = useMegaStore();
const [error, setError] = createSignal<Error>();
const [pendingRequests, { refetch }] = createResource(async () => {
const profiles: NwcProfile[] =
await state.mutiny_wallet?.get_nwc_profiles();
const pending = await state.mutiny_wallet?.get_pending_nwc_invoices();
const pendingItems: PendingItem[] = [];
for (const p of pending) {
const profile = profiles.find((pro) => pro.index === p.index);
if (profile) {
pendingItems.push({
id: p.id,
name_of_connection: profile.name,
date: p.expiry,
amount_sats: p.amount_sats
});
}
}
console.log(pendingItems);
return pendingItems;
});
const [paying, setPaying] = createSignal<string>("");
async function payItem(item: PendingItem) {
try {
setPaying(item.id);
const nodes = await state.mutiny_wallet?.list_nodes();
await state.mutiny_wallet?.approve_invoice(item.id, nodes[0]);
} catch (e) {
setError(eify(e));
console.error(e);
} finally {
setPaying("");
refetch();
}
}
async function rejectItem(item: PendingItem) {
try {
setPaying(item.id);
await state.mutiny_wallet?.deny_invoice(item.id);
} catch (e) {
setError(eify(e));
console.error(e);
} finally {
setPaying("");
refetch();
}
}
createEffect(() => {
// When there's an error wait five seconds and then clear it
if (error()) {
setTimeout(() => {
setError(undefined);
}, 5000);
}
});
return (
<Suspense>
<Show when={pendingRequests() && pendingRequests()!.length > 0}>
<Card title="Pending Requests">
<div class="p-1" />
<VStack>
<Show when={error()}>
<InfoBox accent="red">{error()?.message}</InfoBox>
</Show>
<For each={pendingRequests()}>
{(pendingItem) => (
<div class="grid grid-cols-[auto_minmax(0,_1fr)_minmax(0,_max-content)_auto] items-center pb-4 gap-4 border-b border-neutral-800 last:border-b-0">
<img
class="w-[1rem]"
src={bolt}
alt="onchain"
/>
<div class="flex flex-col">
<span class="text-base font-semibold truncate">
{pendingItem.name_of_connection}
</span>
<time class="text-sm text-neutral-500">
Expires {timeAgo(pendingItem.date)}
</time>
</div>
<div>
<ActivityAmount
amount={
pendingItem.amount_sats?.toString() ||
"0"
}
price={state.price}
/>
</div>
<div class="flex gap-2 w-[5rem]">
<Switch>
<Match
when={
paying() !== pendingItem.id
}
>
<button
onClick={() =>
payItem(pendingItem)
}
>
<img
class="h-[2.5rem] w-[2.5rem]"
src={greenCheck}
alt="Approve"
/>
</button>
<button
onClick={() =>
rejectItem(pendingItem)
}
>
<img
class="h-[2rem] w-[2rem]"
src={redClose}
alt="Reject"
/>
</button>
</Match>
<Match
when={
paying() === pendingItem.id
}
>
<LoadingSpinner wide />
</Match>
</Switch>
</div>
</div>
)}
</For>
</VStack>
<A
href="/settings/connections"
class="text-m-red active:text-m-red/80 font-semibold no-underline self-center"
>
Configure
</A>
</Card>
</Show>
</Suspense>
);
}

View File

@@ -14,20 +14,24 @@ 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 negative = now - timestamp < 0;
const nowOrAgo = negative ? "from now" : "ago";
const elapsedMilliseconds = Math.abs(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";
return negative ? "seconds from now" : "Just now";
} else if (elapsedMinutes < 60) {
return `${elapsedMinutes} minute${elapsedMinutes > 1 ? "s" : ""} ago`;
return `${elapsedMinutes} minute${
elapsedMinutes > 1 ? "s" : ""
} ${nowOrAgo}`;
} else if (elapsedHours < 24) {
return `${elapsedHours} hour${elapsedHours > 1 ? "s" : ""} ago`;
return `${elapsedHours} hour${elapsedHours > 1 ? "s" : ""} ${nowOrAgo}`;
} else if (elapsedDays < 7) {
return `${elapsedDays} day${elapsedDays > 1 ? "s" : ""} ago`;
return `${elapsedDays} day${elapsedDays > 1 ? "s" : ""} ${nowOrAgo}`;
} else {
const date = new Date(timestamp);
const day = String(date.getDate()).padStart(2, "0");