feat: improve zap
This commit is contained in:
@@ -1,19 +1,18 @@
|
|||||||
import { webln } from "@getalby/sdk";
|
import { webln } from "@getalby/sdk";
|
||||||
import { SendPaymentResponse } from "@getalby/sdk/dist/types";
|
import { SendPaymentResponse } from "@getalby/sdk/dist/types";
|
||||||
import { CancelIcon, ZapIcon } from "@lume/icons";
|
import { CancelIcon, ZapIcon } from "@lume/icons";
|
||||||
import { useStorage } from "@lume/storage";
|
|
||||||
import {
|
import {
|
||||||
compactNumber,
|
compactNumber,
|
||||||
displayNpub,
|
displayNpub,
|
||||||
sendNativeNotification,
|
sendNativeNotification,
|
||||||
} from "@lume/utils";
|
} from "@lume/utils";
|
||||||
import * as Dialog from "@radix-ui/react-dialog";
|
import * as Dialog from "@radix-ui/react-dialog";
|
||||||
|
import * as Tooltip from "@radix-ui/react-tooltip";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { message } from "@tauri-apps/plugin-dialog";
|
|
||||||
import { QRCodeSVG } from "qrcode.react";
|
import { QRCodeSVG } from "qrcode.react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import CurrencyInput from "react-currency-input-field";
|
import CurrencyInput from "react-currency-input-field";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { toast } from "sonner";
|
||||||
import { useArk } from "../../../hooks/useArk";
|
import { useArk } from "../../../hooks/useArk";
|
||||||
import { useProfile } from "../../../hooks/useProfile";
|
import { useProfile } from "../../../hooks/useProfile";
|
||||||
import { useNoteContext } from "../provider";
|
import { useNoteContext } from "../provider";
|
||||||
@@ -29,59 +28,53 @@ export function NoteZap() {
|
|||||||
|
|
||||||
const ark = useArk();
|
const ark = useArk();
|
||||||
const event = useNoteContext();
|
const event = useNoteContext();
|
||||||
const storage = useStorage();
|
|
||||||
const nwc = useRef(null);
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const { user } = useProfile(event.pubkey);
|
const { user } = useProfile(event.pubkey);
|
||||||
|
|
||||||
const createZapRequest = async () => {
|
const createZapRequest = async () => {
|
||||||
try {
|
let nwc: webln.NostrWebLNProvider = undefined;
|
||||||
if (!ark.ndk.signer) return navigate("/new/privkey");
|
|
||||||
|
|
||||||
|
try {
|
||||||
const zapAmount = parseInt(amount) * 1000;
|
const zapAmount = parseInt(amount) * 1000;
|
||||||
const res = await event.zap(zapAmount, zapMessage);
|
const res = await event.zap(zapAmount, zapMessage);
|
||||||
|
|
||||||
if (!res)
|
if (!res) return toast.error("Cannot create zap request");
|
||||||
return await message("Cannot create zap request", {
|
|
||||||
title: "Zap",
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
|
|
||||||
// user don't connect nwc, create QR Code for invoice
|
// user don't connect nwc, create QR Code for invoice
|
||||||
if (!walletConnectURL) return setInvoice(res);
|
if (!walletConnectURL) return setInvoice(res);
|
||||||
|
|
||||||
// user connect nwc
|
// user connect nwc
|
||||||
nwc.current = new webln.NostrWebLNProvider({
|
nwc = new webln.NostrWebLNProvider({
|
||||||
nostrWalletConnectUrl: walletConnectURL,
|
nostrWalletConnectUrl: walletConnectURL,
|
||||||
});
|
});
|
||||||
await nwc.current.enable();
|
await nwc.enable();
|
||||||
|
|
||||||
// start loading
|
// start loading
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
// send payment via nwc
|
// send payment via nwc
|
||||||
const send: SendPaymentResponse = await nwc.current.sendPayment(res);
|
const send: SendPaymentResponse = await nwc.sendPayment(res);
|
||||||
|
|
||||||
if (send) {
|
if (send) {
|
||||||
await sendNativeNotification(
|
await sendNativeNotification(
|
||||||
`You've tipped ${compactNumber.format(send.amount)} sats to ${
|
`You've zapped ${compactNumber.format(send.amount)} sats to ${
|
||||||
user?.name || user?.display_name || user?.displayName
|
user?.name || user?.displayName || "anon"
|
||||||
}`,
|
}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
// eose
|
// eose
|
||||||
nwc.current.close();
|
nwc.close();
|
||||||
setIsCompleted(true);
|
setIsCompleted(true);
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
|
||||||
// reset after 3 secs
|
// reset after 1.5 secs
|
||||||
const timeout = setTimeout(() => setIsCompleted(false), 3000);
|
const timeout = setTimeout(() => setIsCompleted(false), 1500);
|
||||||
clearTimeout(timeout);
|
clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
nwc.current.close();
|
nwc.close();
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
await message(JSON.stringify(e), { title: "Zap", type: "error" });
|
toast.error(String(e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,14 +98,26 @@ export function NoteZap() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog.Root open={isOpen} onOpenChange={setIsOpen}>
|
||||||
<Dialog.Trigger asChild>
|
<Tooltip.Provider>
|
||||||
<button
|
<Tooltip.Root delayDuration={150}>
|
||||||
type="button"
|
<Dialog.Trigger asChild>
|
||||||
className="inline-flex items-center justify-center group size-7 text-neutral-600 dark:text-neutral-400"
|
<Tooltip.Trigger asChild>
|
||||||
>
|
<button
|
||||||
<ZapIcon className="size-5 group-hover:text-blue-500" />
|
type="button"
|
||||||
</button>
|
className="inline-flex items-center justify-center group size-7 text-neutral-600 dark:text-neutral-400"
|
||||||
</Dialog.Trigger>
|
>
|
||||||
|
<ZapIcon className="size-5 group-hover:text-blue-500" />
|
||||||
|
</button>
|
||||||
|
</Tooltip.Trigger>
|
||||||
|
</Dialog.Trigger>
|
||||||
|
<Tooltip.Portal>
|
||||||
|
<Tooltip.Content className="inline-flex h-7 select-none text-neutral-50 dark:text-neutral-950 items-center justify-center rounded-md bg-neutral-950 dark:bg-neutral-50 px-3.5 text-sm will-change-[transform,opacity] data-[state=delayed-open]:data-[side=bottom]:animate-slideUpAndFade data-[state=delayed-open]:data-[side=left]:animate-slideRightAndFade data-[state=delayed-open]:data-[side=right]:animate-slideLeftAndFade data-[state=delayed-open]:data-[side=top]:animate-slideDownAndFade">
|
||||||
|
Zap
|
||||||
|
<Tooltip.Arrow className="fill-neutral-950 dark:fill-neutral-50" />
|
||||||
|
</Tooltip.Content>
|
||||||
|
</Tooltip.Portal>
|
||||||
|
</Tooltip.Root>
|
||||||
|
</Tooltip.Provider>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/10 backdrop-blur-sm dark:bg-white/10" />
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/10 backdrop-blur-sm dark:bg-white/10" />
|
||||||
<Dialog.Content className="fixed inset-0 z-50 flex items-center justify-center min-h-full">
|
<Dialog.Content className="fixed inset-0 z-50 flex items-center justify-center min-h-full">
|
||||||
@@ -120,7 +125,7 @@ export function NoteZap() {
|
|||||||
<div className="inline-flex items-center justify-between w-full px-5 py-3 shrink-0">
|
<div className="inline-flex items-center justify-between w-full px-5 py-3 shrink-0">
|
||||||
<div className="w-6" />
|
<div className="w-6" />
|
||||||
<Dialog.Title className="font-semibold text-center">
|
<Dialog.Title className="font-semibold text-center">
|
||||||
Send tip to{" "}
|
Send zap to{" "}
|
||||||
{user?.name ||
|
{user?.name ||
|
||||||
user?.displayName ||
|
user?.displayName ||
|
||||||
displayNpub(event.pubkey, 16)}
|
displayNpub(event.pubkey, 16)}
|
||||||
@@ -145,7 +150,7 @@ export function NoteZap() {
|
|||||||
onValueChange={(value) => setAmount(value)}
|
onValueChange={(value) => setAmount(value)}
|
||||||
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
className="flex-1 w-full text-4xl font-semibold text-right bg-transparent border-none placeholder:text-neutral-600 focus:outline-none focus:ring-0 dark:text-neutral-400"
|
||||||
/>
|
/>
|
||||||
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-600 dark:text-neutral-400">
|
<span className="flex-1 w-full text-4xl font-semibold text-left text-neutral-500 dark:text-neutral-400">
|
||||||
sats
|
sats
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -153,35 +158,35 @@ export function NoteZap() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAmount("69")}
|
onClick={() => setAmount("69")}
|
||||||
className="w-max rounded-full border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
69 sats
|
69 sats
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAmount("100")}
|
onClick={() => setAmount("100")}
|
||||||
className="w-max rounded-full border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
100 sats
|
100 sats
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAmount("200")}
|
onClick={() => setAmount("200")}
|
||||||
className="w-max rounded-full border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
200 sats
|
200 sats
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAmount("500")}
|
onClick={() => setAmount("500")}
|
||||||
className="w-max rounded-full border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
500 sats
|
500 sats
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setAmount("1000")}
|
onClick={() => setAmount("1000")}
|
||||||
className="w-max rounded-full border border-neutral-200 bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:border-neutral-800 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
className="w-max rounded-full bg-neutral-100 px-2.5 py-1 text-sm font-medium hover:bg-neutral-200 dark:bg-neutral-900 dark:hover:bg-neutral-800"
|
||||||
>
|
>
|
||||||
1K sats
|
1K sats
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
await ark.getUserContacts();
|
await ark.getUserContacts();
|
||||||
|
|
||||||
// subscribe for new activity
|
// subscribe for new activity
|
||||||
const sub = ndk.subscribe(
|
const notifySub = ndk.subscribe(
|
||||||
{
|
{
|
||||||
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
kinds: [NDKKind.Text, NDKKind.Repost, NDKKind.Zap],
|
||||||
since: Math.floor(Date.now() / 1000),
|
since: Math.floor(Date.now() / 1000),
|
||||||
@@ -156,6 +156,32 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
{ closeOnEose: false, groupable: false },
|
{ closeOnEose: false, groupable: false },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
notifySub.addListener("event", async (event: NDKEvent) => {
|
||||||
|
const profile = await ark.getUserProfile(event.pubkey);
|
||||||
|
switch (event.kind) {
|
||||||
|
case NDKKind.Text:
|
||||||
|
return await sendNativeNotification(
|
||||||
|
`${
|
||||||
|
profile.displayName || profile.name || "anon"
|
||||||
|
} has replied to your note`,
|
||||||
|
);
|
||||||
|
case NDKKind.Repost:
|
||||||
|
return await sendNativeNotification(
|
||||||
|
`${
|
||||||
|
profile.displayName || profile.name || "anon"
|
||||||
|
} has reposted to your note`,
|
||||||
|
);
|
||||||
|
case NDKKind.Zap:
|
||||||
|
return await sendNativeNotification(
|
||||||
|
`${
|
||||||
|
profile.displayName || profile.name || "anon"
|
||||||
|
} has zapped to your note`,
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// prefetch activty
|
// prefetch activty
|
||||||
await queryClient.prefetchInfiniteQuery({
|
await queryClient.prefetchInfiniteQuery({
|
||||||
queryKey: ["activity"],
|
queryKey: ["activity"],
|
||||||
@@ -205,32 +231,6 @@ export const LumeProvider = ({ children }: PropsWithChildren<object>) => {
|
|||||||
return events;
|
return events;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
sub.addListener("event", async (event: NDKEvent) => {
|
|
||||||
const profile = await ark.getUserProfile(event.pubkey);
|
|
||||||
switch (event.kind) {
|
|
||||||
case NDKKind.Text:
|
|
||||||
return await sendNativeNotification(
|
|
||||||
`${
|
|
||||||
profile.displayName || profile.name || "anon"
|
|
||||||
} has replied to your note`,
|
|
||||||
);
|
|
||||||
case NDKKind.Repost:
|
|
||||||
return await sendNativeNotification(
|
|
||||||
`${
|
|
||||||
profile.displayName || profile.name || "anon"
|
|
||||||
} has reposted to your note`,
|
|
||||||
);
|
|
||||||
case NDKKind.Zap:
|
|
||||||
return await sendNativeNotification(
|
|
||||||
`${
|
|
||||||
profile.displayName || profile.name || "anon"
|
|
||||||
} has zapped to your note`,
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setArk(ark);
|
setArk(ark);
|
||||||
|
|||||||
Reference in New Issue
Block a user