mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-09-21 22:13:11 +02:00
rebuild zap modal
This commit is contained in:
@@ -15,7 +15,6 @@ export const CopyIconButton = ({ text, ...props }: { text?: string } & Omit<Icon
|
|||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
size="xs"
|
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@@ -1,22 +1,17 @@
|
|||||||
import { Button, ButtonProps, useToast } from "@chakra-ui/react";
|
import { Button, ButtonProps, useDisclosure } from "@chakra-ui/react";
|
||||||
import { makeZapRequest } from "nostr-tools/nip57";
|
import { useMemo } from "react";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import { readableAmountInSats } from "../../helpers/bolt11";
|
||||||
import { random } from "../../helpers/array";
|
|
||||||
import { parsePaymentRequest, readableAmountInSats } from "../../helpers/bolt11";
|
|
||||||
import { parseZapNote, totalZaps } from "../../helpers/nip-57";
|
import { parseZapNote, totalZaps } from "../../helpers/nip-57";
|
||||||
import { useCurrentAccount } from "../../hooks/use-current-account";
|
import { useCurrentAccount } from "../../hooks/use-current-account";
|
||||||
import useEventZaps from "../../hooks/use-event-zaps";
|
import useEventZaps from "../../hooks/use-event-zaps";
|
||||||
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
import { useUserMetadata } from "../../hooks/use-user-metadata";
|
||||||
import { useSigningContext } from "../../providers/signing-provider";
|
|
||||||
import clientRelaysService from "../../services/client-relays";
|
import clientRelaysService from "../../services/client-relays";
|
||||||
import { getEventRelays } from "../../services/event-relays";
|
|
||||||
import eventZapsService from "../../services/event-zaps";
|
import eventZapsService from "../../services/event-zaps";
|
||||||
import lnurlMetadataService from "../../services/lnurl-metadata";
|
|
||||||
import { NostrEvent } from "../../types/nostr-event";
|
import { NostrEvent } from "../../types/nostr-event";
|
||||||
import { LightningIcon } from "../icons";
|
import { LightningIcon } from "../icons";
|
||||||
|
import ZapModal from "../zap-modal";
|
||||||
|
|
||||||
export default function NoteZapButton({ note, ...props }: { note: NostrEvent } & Omit<ButtonProps, "children">) {
|
export default function NoteZapButton({ note, ...props }: { note: NostrEvent } & Omit<ButtonProps, "children">) {
|
||||||
const { requestSignature } = useSigningContext();
|
|
||||||
const account = useCurrentAccount();
|
const account = useCurrentAccount();
|
||||||
const metadata = useUserMetadata(note.pubkey);
|
const metadata = useUserMetadata(note.pubkey);
|
||||||
const zaps = useEventZaps(note.id) ?? [];
|
const zaps = useEventZaps(note.id) ?? [];
|
||||||
@@ -29,101 +24,27 @@ export default function NoteZapButton({ note, ...props }: { note: NostrEvent } &
|
|||||||
}
|
}
|
||||||
return parsed;
|
return parsed;
|
||||||
}, [zaps]);
|
}, [zaps]);
|
||||||
const toast = useToast();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const timeout = useRef(0);
|
|
||||||
const zapAmount = useRef(0);
|
|
||||||
|
|
||||||
const hasZapped = parsedZaps.some((zapRequest) => zapRequest.request.pubkey === account.pubkey);
|
const hasZapped = parsedZaps.some((zapRequest) => zapRequest.request.pubkey === account.pubkey);
|
||||||
const tipAddress = metadata?.lud06 || metadata?.lud16;
|
const tipAddress = metadata?.lud06 || metadata?.lud16;
|
||||||
|
|
||||||
const handleClick = () => {
|
const invoicePaid = () => eventZapsService.requestZaps(note.id, clientRelaysService.getReadUrls(), true);
|
||||||
if (!tipAddress) return;
|
|
||||||
if (timeout.current) {
|
|
||||||
window.clearTimeout(timeout.current);
|
|
||||||
}
|
|
||||||
zapAmount.current += 21;
|
|
||||||
timeout.current = window.setTimeout(async () => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const eventRelays = getEventRelays(note.id).value;
|
|
||||||
const readRelays = clientRelaysService.getReadUrls();
|
|
||||||
const lnurlMetadata = await lnurlMetadataService.requestMetadata(tipAddress);
|
|
||||||
const amount = zapAmount.current * 1000;
|
|
||||||
|
|
||||||
if (lnurlMetadata && lnurlMetadata.allowsNostr && lnurlMetadata.nostrPubkey) {
|
|
||||||
const zapRequest = makeZapRequest({
|
|
||||||
profile: note.pubkey,
|
|
||||||
event: note.id,
|
|
||||||
// pick a random relay from the event and one of our read relays
|
|
||||||
relays: [random(eventRelays), random(readRelays)],
|
|
||||||
amount,
|
|
||||||
comment: "",
|
|
||||||
});
|
|
||||||
|
|
||||||
const signed = await requestSignature(zapRequest);
|
|
||||||
if (signed) {
|
|
||||||
if (amount > lnurlMetadata.maxSendable) throw new Error("amount to large");
|
|
||||||
if (amount < lnurlMetadata.minSendable) throw new Error("amount to small");
|
|
||||||
|
|
||||||
const url = new URL(lnurlMetadata.callback);
|
|
||||||
url.searchParams.append("amount", String(zapAmount.current * 1000));
|
|
||||||
url.searchParams.append("nostr", JSON.stringify(signed));
|
|
||||||
|
|
||||||
const { pr: payRequest } = await fetch(url).then((res) => res.json());
|
|
||||||
if (payRequest as string) {
|
|
||||||
const parsed = parsePaymentRequest(payRequest);
|
|
||||||
if (parsed.amount !== amount) throw new Error("incorrect amount");
|
|
||||||
|
|
||||||
if (window.webln) {
|
|
||||||
await window.webln.enable();
|
|
||||||
await window.webln.sendPayment(payRequest);
|
|
||||||
|
|
||||||
// fetch the zaps again
|
|
||||||
eventZapsService.requestZaps(note.id, readRelays, true);
|
|
||||||
} else {
|
|
||||||
window.addEventListener(
|
|
||||||
"focus",
|
|
||||||
() => {
|
|
||||||
// when the window regains focus, fetch the zaps again
|
|
||||||
eventZapsService.requestZaps(note.id, readRelays, true);
|
|
||||||
},
|
|
||||||
{ once: true }
|
|
||||||
);
|
|
||||||
window.open("lightning:" + payRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// show standard tipping
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof Error) {
|
|
||||||
console.log(e);
|
|
||||||
toast({
|
|
||||||
status: "error",
|
|
||||||
description: e.message,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zapAmount.current = 0;
|
|
||||||
setLoading(false);
|
|
||||||
}, 1500);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<LightningIcon color="yellow.500" />}
|
leftIcon={<LightningIcon color="yellow.500" />}
|
||||||
aria-label="Zap Note"
|
aria-label="Zap Note"
|
||||||
title="Zap Note"
|
title="Zap Note"
|
||||||
colorScheme={hasZapped ? "brand" : undefined}
|
colorScheme={hasZapped ? "brand" : undefined}
|
||||||
{...props}
|
{...props}
|
||||||
isLoading={loading}
|
onClick={onOpen}
|
||||||
onClick={handleClick}
|
|
||||||
isDisabled={!tipAddress}
|
isDisabled={!tipAddress}
|
||||||
>
|
>
|
||||||
{readableAmountInSats(totalZaps(zaps), false)}
|
{readableAmountInSats(totalZaps(zaps), false)}
|
||||||
</Button>
|
</Button>
|
||||||
|
{isOpen && <ZapModal isOpen={isOpen} onClose={onClose} event={note} onPaid={invoicePaid} pubkey={note.pubkey} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,12 @@ export default function QrCodeSvg({
|
|||||||
content,
|
content,
|
||||||
lightColor = "white",
|
lightColor = "white",
|
||||||
darkColor = "black",
|
darkColor = "black",
|
||||||
border,
|
border = 2,
|
||||||
}: {
|
}: {
|
||||||
content: string;
|
content: string;
|
||||||
lightColor?: string;
|
lightColor?: string;
|
||||||
darkColor?: string;
|
darkColor?: string;
|
||||||
border: number;
|
border?: number;
|
||||||
}) {
|
}) {
|
||||||
const qrCode = useMemo(() => QrCode.encodeText(content, Ecc.LOW), [content]);
|
const qrCode = useMemo(() => QrCode.encodeText(content, Ecc.LOW), [content]);
|
||||||
|
|
||||||
|
@@ -1,66 +1,31 @@
|
|||||||
import { IconButton, IconButtonProps, useToast } from "@chakra-ui/react";
|
import { IconButton, IconButtonProps, useDisclosure, useToast } from "@chakra-ui/react";
|
||||||
import { useUserMetadata } from "../hooks/use-user-metadata";
|
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||||
import { LightningIcon } from "./icons";
|
import { LightningIcon } from "./icons";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { encodeText } from "../helpers/bech32";
|
import { encodeText } from "../helpers/bech32";
|
||||||
|
import ZapModal from "./zap-modal";
|
||||||
|
|
||||||
export const UserTipButton = ({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "aria-label">) => {
|
export const UserTipButton = ({ pubkey, ...props }: { pubkey: string } & Omit<IconButtonProps, "aria-label">) => {
|
||||||
const metadata = useUserMetadata(pubkey);
|
const metadata = useUserMetadata(pubkey);
|
||||||
const [loading, setLoading] = useState(false);
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const toast = useToast();
|
|
||||||
if (!metadata) return null;
|
if (!metadata) return null;
|
||||||
|
|
||||||
// use lud06 and lud16 fields interchangeably
|
// use lud06 and lud16 fields interchangeably
|
||||||
let lnurl = metadata.lud06 || metadata.lud16;
|
let tipAddress = metadata.lud06 || metadata.lud16;
|
||||||
if (lnurl && lnurl.includes("@")) {
|
|
||||||
//if its a lightning address convert it to a lnurl
|
|
||||||
const parts = lnurl.split("@");
|
|
||||||
if (parts[0] && parts[1]) {
|
|
||||||
lnurl = encodeText("lnurl", `https://${parts[1]}/.well-known/lnurlp/${parts[0]}`);
|
|
||||||
} else {
|
|
||||||
// failed to parse it. something is wrong...
|
|
||||||
lnurl = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lnurl) return null;
|
if (!tipAddress) return null;
|
||||||
|
|
||||||
const handleClick = async () => {
|
|
||||||
if (!lnurl) return;
|
|
||||||
setLoading(true);
|
|
||||||
|
|
||||||
if (window.webln && window.webln.lnurl) {
|
|
||||||
try {
|
|
||||||
if (!window.webln.enabled) await window.webln.enable();
|
|
||||||
await window.webln.lnurl(lnurl);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "Tip sent",
|
|
||||||
status: "success",
|
|
||||||
duration: 1000,
|
|
||||||
});
|
|
||||||
} catch (e: any) {
|
|
||||||
toast({
|
|
||||||
status: "error",
|
|
||||||
description: e?.message,
|
|
||||||
isClosable: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
window.open(`lnurl:${lnurl}`);
|
|
||||||
}
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={handleClick}
|
onClick={onOpen}
|
||||||
aria-label="Send Tip"
|
aria-label="Send Tip"
|
||||||
title="Send Tip"
|
title="Send Tip"
|
||||||
icon={<LightningIcon />}
|
icon={<LightningIcon />}
|
||||||
isLoading={loading}
|
|
||||||
color="yellow.400"
|
color="yellow.400"
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
{isOpen && <ZapModal isOpen={isOpen} onClose={onClose} pubkey={pubkey} />}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
237
src/components/zap-modal.tsx
Normal file
237
src/components/zap-modal.tsx
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
ButtonGroup,
|
||||||
|
Flex,
|
||||||
|
IconButton,
|
||||||
|
Input,
|
||||||
|
InputGroup,
|
||||||
|
InputLeftElement,
|
||||||
|
Modal,
|
||||||
|
ModalBody,
|
||||||
|
ModalContent,
|
||||||
|
ModalOverlay,
|
||||||
|
ModalProps,
|
||||||
|
Text,
|
||||||
|
useDisclosure,
|
||||||
|
useToast,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { getUserDisplayName } from "../helpers/user-metadata";
|
||||||
|
import { NostrEvent } from "../types/nostr-event";
|
||||||
|
import { SubmitHandler, useForm } from "react-hook-form";
|
||||||
|
import { UserAvatar } from "./user-avatar";
|
||||||
|
import { useUserMetadata } from "../hooks/use-user-metadata";
|
||||||
|
import { UserLink } from "./user-link";
|
||||||
|
import { parsePaymentRequest, readableAmountInSats } from "../helpers/bolt11";
|
||||||
|
import { ExternalLinkIcon, LightningIcon, QrCodeIcon } from "./icons";
|
||||||
|
import lnurlMetadataService from "../services/lnurl-metadata";
|
||||||
|
import { useAsync } from "react-use";
|
||||||
|
import { makeZapRequest } from "nostr-tools/nip57";
|
||||||
|
import clientRelaysService from "../services/client-relays";
|
||||||
|
import { getEventRelays } from "../services/event-relays";
|
||||||
|
import { useSigningContext } from "../providers/signing-provider";
|
||||||
|
import QrCodeSvg from "./qr-code-svg";
|
||||||
|
import { CopyIconButton } from "./copy-icon-button";
|
||||||
|
import { useIsMobile } from "../hooks/use-is-mobile";
|
||||||
|
|
||||||
|
type FormValues = {
|
||||||
|
amount: number;
|
||||||
|
comment: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function ZapModal({
|
||||||
|
event,
|
||||||
|
pubkey,
|
||||||
|
onClose,
|
||||||
|
onPaid,
|
||||||
|
...props
|
||||||
|
}: { event?: NostrEvent; pubkey: string; onPaid?: () => void } & Omit<ModalProps, "children">) {
|
||||||
|
const metadata = useUserMetadata(pubkey);
|
||||||
|
const { requestSignature } = useSigningContext();
|
||||||
|
const toast = useToast();
|
||||||
|
const [invoice, setInvoice] = useState<string>();
|
||||||
|
const { isOpen: showQr, onToggle: toggleQr } = useDisclosure();
|
||||||
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const {
|
||||||
|
register,
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
formState: { errors, isSubmitting },
|
||||||
|
} = useForm<FormValues>({
|
||||||
|
mode: "onBlur",
|
||||||
|
defaultValues: {
|
||||||
|
amount: 10,
|
||||||
|
comment: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tipAddress = metadata?.lud06 || metadata?.lud16;
|
||||||
|
const { value: lnurlMetadata } = useAsync(
|
||||||
|
async () => (tipAddress ? lnurlMetadataService.requestMetadata(tipAddress) : undefined),
|
||||||
|
[tipAddress]
|
||||||
|
);
|
||||||
|
|
||||||
|
const canZap = lnurlMetadata?.allowsNostr && lnurlMetadata?.nostrPubkey;
|
||||||
|
const actionName = canZap ? "Zap" : "Tip";
|
||||||
|
|
||||||
|
const onSubmitZap: SubmitHandler<FormValues> = async (values) => {
|
||||||
|
try {
|
||||||
|
if (lnurlMetadata) {
|
||||||
|
const amountInMilisat = values.amount * 1000;
|
||||||
|
|
||||||
|
if (amountInMilisat > lnurlMetadata.maxSendable) throw new Error("amount to large");
|
||||||
|
if (amountInMilisat < lnurlMetadata.minSendable) throw new Error("amount to small");
|
||||||
|
if (canZap) {
|
||||||
|
const otherRelays = event ? getEventRelays(event.id).value : [];
|
||||||
|
const readRelays = clientRelaysService.getReadUrls();
|
||||||
|
|
||||||
|
const zapRequest = makeZapRequest({
|
||||||
|
profile: pubkey,
|
||||||
|
event: event?.id ?? null,
|
||||||
|
relays: [...otherRelays, ...readRelays],
|
||||||
|
amount: amountInMilisat,
|
||||||
|
comment: values.comment,
|
||||||
|
});
|
||||||
|
|
||||||
|
const signed = await requestSignature(zapRequest);
|
||||||
|
if (signed) {
|
||||||
|
const callbackUrl = new URL(lnurlMetadata.callback);
|
||||||
|
callbackUrl.searchParams.append("amount", String(amountInMilisat));
|
||||||
|
callbackUrl.searchParams.append("nostr", JSON.stringify(signed));
|
||||||
|
|
||||||
|
const { pr: payRequest } = await fetch(callbackUrl).then((res) => res.json());
|
||||||
|
|
||||||
|
if (payRequest as string) {
|
||||||
|
const parsed = parsePaymentRequest(payRequest);
|
||||||
|
if (parsed.amount !== amountInMilisat) throw new Error("incorrect amount");
|
||||||
|
|
||||||
|
setInvoice(payRequest);
|
||||||
|
} else throw new Error("Failed to get invoice");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const callbackUrl = new URL(lnurlMetadata.callback);
|
||||||
|
callbackUrl.searchParams.append("amount", String(amountInMilisat));
|
||||||
|
if (values.comment) callbackUrl.searchParams.append("comment", values.comment);
|
||||||
|
|
||||||
|
const { pr: payRequest } = await fetch(callbackUrl).then((res) => res.json());
|
||||||
|
if (payRequest as string) {
|
||||||
|
const parsed = parsePaymentRequest(payRequest);
|
||||||
|
if (parsed.amount !== amountInMilisat) throw new Error("incorrect amount");
|
||||||
|
|
||||||
|
setInvoice(payRequest);
|
||||||
|
} else throw new Error("Failed to get invoice");
|
||||||
|
}
|
||||||
|
} else throw new Error("No lightning address");
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) toast({ status: "error", description: e.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const payWithWebLn = async () => {
|
||||||
|
if (window.webln && invoice) {
|
||||||
|
if (!window.webln.enabled) await window.webln.enable();
|
||||||
|
await window.webln.sendPayment(invoice);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: actionName + " sent",
|
||||||
|
status: "success",
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onPaid) onPaid();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const payWithApp = async () => {
|
||||||
|
window.open("lightning:" + invoice);
|
||||||
|
|
||||||
|
window.addEventListener(
|
||||||
|
"focus",
|
||||||
|
() => {
|
||||||
|
if (onPaid) onPaid();
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal onClose={onClose} {...props}>
|
||||||
|
<ModalOverlay />
|
||||||
|
<ModalContent>
|
||||||
|
<ModalBody padding="4">
|
||||||
|
{invoice ? (
|
||||||
|
<Flex gap="4" direction="column">
|
||||||
|
{showQr && <QrCodeSvg content={invoice} />}
|
||||||
|
<Flex gap="2">
|
||||||
|
<Input value={invoice} readOnly />
|
||||||
|
<IconButton
|
||||||
|
icon={<QrCodeIcon />}
|
||||||
|
aria-label="Show QrCode"
|
||||||
|
onClick={toggleQr}
|
||||||
|
variant="solid"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
<CopyIconButton text={invoice} aria-label="Copy Invoice" variant="solid" size="md" />
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="2">
|
||||||
|
{window.webln && (
|
||||||
|
<Button onClick={payWithWebLn} flex={1} variant="solid" size="md">
|
||||||
|
Pay with WebLN
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button leftIcon={<ExternalLinkIcon />} onClick={payWithApp} flex={1} variant="solid" size="md">
|
||||||
|
Open App
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<form onSubmit={handleSubmit(onSubmitZap)}>
|
||||||
|
<Flex gap="4" direction="column">
|
||||||
|
<Flex gap="2" alignItems="center">
|
||||||
|
<UserAvatar pubkey={pubkey} size="sm" />
|
||||||
|
<Text>{actionName}</Text>
|
||||||
|
<UserLink pubkey={pubkey} />
|
||||||
|
</Flex>
|
||||||
|
<Flex gap="2" alignItems="center">
|
||||||
|
<ButtonGroup>
|
||||||
|
<Button onClick={() => setValue("amount", 10)}>10</Button>
|
||||||
|
<Button onClick={() => setValue("amount", 100)}>100</Button>
|
||||||
|
<Button onClick={() => setValue("amount", 500)}>500</Button>
|
||||||
|
<Button onClick={() => setValue("amount", 1000)}>1K</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
<InputGroup maxWidth={32}>
|
||||||
|
{!isMobile && (
|
||||||
|
<InputLeftElement pointerEvents="none" color="gray.300" fontSize="1.2em">
|
||||||
|
<LightningIcon fontSize="1rem" />
|
||||||
|
</InputLeftElement>
|
||||||
|
)}
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
placeholder="amount"
|
||||||
|
isInvalid={!!errors.amount}
|
||||||
|
step={1}
|
||||||
|
{...register("amount", { valueAsNumber: true, min: 1, required: true })}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</Flex>
|
||||||
|
{(canZap || lnurlMetadata?.commentAllowed) && (
|
||||||
|
<Input
|
||||||
|
placeholder="Comment"
|
||||||
|
{...register("comment", { maxLength: lnurlMetadata?.commentAllowed ?? 150 })}
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Button leftIcon={<LightningIcon />} type="submit" isLoading={isSubmitting} variant="solid" size="md">
|
||||||
|
{actionName} {getUserDisplayName(metadata, pubkey)} {readableAmountInSats(watch("amount") * 1000)}
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</form>
|
||||||
|
)}
|
||||||
|
</ModalBody>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@@ -1,5 +1,5 @@
|
|||||||
|
import { useDisclosure } from "@chakra-ui/react";
|
||||||
import React, { useCallback, useMemo, useState } from "react";
|
import React, { useCallback, useMemo, useState } from "react";
|
||||||
import { ErrorBoundary } from "../components/error-boundary";
|
|
||||||
import { PostModal } from "../components/post-modal";
|
import { PostModal } from "../components/post-modal";
|
||||||
import { DraftNostrEvent } from "../types/nostr-event";
|
import { DraftNostrEvent } from "../types/nostr-event";
|
||||||
|
|
||||||
@@ -12,20 +12,20 @@ export const PostModalContext = React.createContext<PostModalContextType>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const PostModalProvider = ({ children }: { children: React.ReactNode }) => {
|
export const PostModalProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
const [open, setOpen] = useState(false);
|
const {isOpen, onOpen, onClose} = useDisclosure();
|
||||||
const [draft, setDraft] = useState<Partial<DraftNostrEvent> | undefined>(undefined);
|
const [draft, setDraft] = useState<Partial<DraftNostrEvent> | undefined>(undefined);
|
||||||
const openModal = useCallback(
|
const openModal = useCallback(
|
||||||
(draft?: Partial<DraftNostrEvent>) => {
|
(draft?: Partial<DraftNostrEvent>) => {
|
||||||
setDraft(draft);
|
setDraft(draft);
|
||||||
setOpen(true);
|
onOpen();
|
||||||
},
|
},
|
||||||
[setDraft, setOpen]
|
[setDraft, onOpen]
|
||||||
);
|
);
|
||||||
const context = useMemo(() => ({ openModal }), [openModal]);
|
const context = useMemo(() => ({ openModal }), [openModal]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PostModalContext.Provider value={context}>
|
<PostModalContext.Provider value={context}>
|
||||||
{open && <PostModal isOpen initialDraft={draft} onClose={() => setOpen(false)} />}
|
{isOpen && <PostModal isOpen initialDraft={draft} onClose={onClose} />}
|
||||||
{children}
|
{children}
|
||||||
</PostModalContext.Provider>
|
</PostModalContext.Provider>
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user