mirror of
https://github.com/aljazceru/rabbit.git
synced 2025-12-17 22:14:26 +01:00
feat: send zaps to user
This commit is contained in:
@@ -460,7 +460,7 @@ const Actions: Component<ActionProps> = (props) => {
|
||||
<RepostsModal event={props.event} onClose={closeModal} />
|
||||
</Match>
|
||||
<Match when={modal() === 'ZapRequest'}>
|
||||
<ZapRequestModal event={props.event} onClose={closeModal} />
|
||||
<ZapRequestModal zapTo={{ event: props.event }} onClose={closeModal} />
|
||||
</Match>
|
||||
</Switch>
|
||||
</>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, createSignal, createMemo, Show, Switch, Match, createEffect
|
||||
|
||||
import { createMutation } from '@tanstack/solid-query';
|
||||
import ArrowPath from 'heroicons/24/outline/arrow-path.svg';
|
||||
import Bolt from 'heroicons/24/outline/bolt.svg';
|
||||
import EllipsisHorizontal from 'heroicons/24/outline/ellipsis-horizontal.svg';
|
||||
import GlobeAlt from 'heroicons/24/outline/globe-alt.svg';
|
||||
import CheckCircle from 'heroicons/24/solid/check-circle.svg';
|
||||
@@ -12,6 +13,7 @@ import TextNoteContentDisplay from '@/components/event/textNote/TextNoteContentD
|
||||
import BasicModal from '@/components/modal/BasicModal';
|
||||
import EventDebugModal from '@/components/modal/EventDebugModal';
|
||||
import UserList from '@/components/modal/UserList';
|
||||
import ZapRequestModal from '@/components/modal/ZapRequestModal';
|
||||
import Timeline from '@/components/timeline/Timeline';
|
||||
import SafeLink from '@/components/utils/SafeLink';
|
||||
import useContextMenu from '@/components/utils/useContextMenu';
|
||||
@@ -34,6 +36,7 @@ import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import npubEncodeFallback from '@/utils/npubEncodeFallback';
|
||||
import timeout from '@/utils/timeout';
|
||||
|
||||
|
||||
export type ProfileDisplayProps = {
|
||||
pubkey: string;
|
||||
onClose?: () => void;
|
||||
@@ -60,7 +63,9 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
const [updatingContacts, setUpdatingContacts] = createSignal(false);
|
||||
const [hoverFollowButton, setHoverFollowButton] = createSignal(false);
|
||||
const [showFollowers, setShowFollowers] = createSignal(false);
|
||||
const [modal, setModal] = createSignal<'Following' | 'EventDebugModal' | null>(null);
|
||||
const [modal, setModal] = createSignal<'Following' | 'EventDebugModal' | 'ZapRequest' | null>(
|
||||
null,
|
||||
);
|
||||
const closeModal = () => setModal(null);
|
||||
|
||||
const {
|
||||
@@ -354,6 +359,12 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
</button>
|
||||
</Match>
|
||||
</Switch>
|
||||
<button
|
||||
class="w-10 rounded-full border border-primary p-2 text-primary hover:border-primary-hover hover:text-primary-hover"
|
||||
onClick={() => setModal('ZapRequest')}
|
||||
>
|
||||
<Bolt />
|
||||
</button>
|
||||
<button
|
||||
ref={otherActionsPopup.targetRef}
|
||||
type="button"
|
||||
@@ -482,6 +493,9 @@ const ProfileDisplay: Component<ProfileDisplayProps> = (props) => {
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
<Match when={modal() === 'ZapRequest'}>
|
||||
<ZapRequestModal zapTo={{ pubkey: props.pubkey }} onClose={closeModal} />
|
||||
</Match>
|
||||
</Switch>
|
||||
<ul class="border-t border-border p-1">
|
||||
<LoadMore loadMore={loadMore} eose={eose()}>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { requestProvider, type WebLNProvider } from 'webln';
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import EventDisplay from '@/components/event/EventDisplay';
|
||||
import BasicModal from '@/components/modal/BasicModal';
|
||||
import UserNameDisplay from '@/components/UserDisplayName';
|
||||
import Copy from '@/components/utils/Copy';
|
||||
import useConfig from '@/core/useConfig';
|
||||
import { useTranslation } from '@/i18n/useTranslation';
|
||||
@@ -39,17 +40,33 @@ import verifyInvoice from '@/nostr/zap/verifyInvoice';
|
||||
import ensureNonNull from '@/utils/ensureNonNull';
|
||||
import epoch from '@/utils/epoch';
|
||||
|
||||
|
||||
type ZapTarget = { event: NostrEvent } | { pubkey: string };
|
||||
|
||||
export type ZapRequestModalProps = {
|
||||
event: NostrEvent;
|
||||
zapTo: ZapTarget;
|
||||
// TODO zap to profile
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
type ZapDialogProps = {
|
||||
zapTo: ZapTarget;
|
||||
lnurlPayUrl?: string | null;
|
||||
event: NostrEvent;
|
||||
};
|
||||
|
||||
const getPubkey = (zapTo: ZapTarget): string => {
|
||||
if ('event' in zapTo) {
|
||||
return zapTo.event.pubkey;
|
||||
}
|
||||
if ('pubkey' in zapTo) {
|
||||
return zapTo.pubkey;
|
||||
}
|
||||
throw new Error('unexpected ZapTarget');
|
||||
};
|
||||
|
||||
const getEvent = (zapTo: ZapTarget): NostrEvent | undefined =>
|
||||
'event' in zapTo ? zapTo.event : undefined;
|
||||
|
||||
const useWebLN = () => {
|
||||
const [provider, setProvider] = createSignal<WebLNProvider | undefined>();
|
||||
const [status, setStatus] = createSignal<'available' | 'unavailable' | 'checking'>('checking');
|
||||
@@ -96,9 +113,11 @@ const QRCodeDisplay: Component<{ text: string }> = (props) => {
|
||||
return <canvas width="256" height="256" ref={canvasRef} />;
|
||||
};
|
||||
|
||||
const InvoiceDisplay: Component<{ invoice: string; event: NostrEvent; nostrPubkey?: string }> = (
|
||||
props,
|
||||
) => {
|
||||
const InvoiceDisplay: Component<{
|
||||
invoice: string;
|
||||
recipientPubkey: string;
|
||||
lnurlPubkey?: string;
|
||||
}> = (props) => {
|
||||
const i18n = useTranslation();
|
||||
const { config } = useConfig();
|
||||
const webln = useWebLN();
|
||||
@@ -106,14 +125,13 @@ const InvoiceDisplay: Component<{ invoice: string; event: NostrEvent; nostrPubke
|
||||
const lightingInvoiceWithSchema = () => `lightning:${props.invoice}`;
|
||||
|
||||
const { events } = useSubscription(() =>
|
||||
ensureNonNull([props.nostrPubkey] as const)(([nostrPubkey]) => ({
|
||||
ensureNonNull([props.lnurlPubkey] as const)(([lnurlPubkey]) => ({
|
||||
relayUrls: config().relayUrls,
|
||||
filters: [
|
||||
{
|
||||
kinds: [Kind.Zap],
|
||||
authors: nostrPubkey != null ? [nostrPubkey] : undefined,
|
||||
'#p': [props.event.pubkey],
|
||||
'#e': [props.event.id],
|
||||
authors: lnurlPubkey != null ? [lnurlPubkey] : undefined,
|
||||
'#p': [props.recipientPubkey],
|
||||
since: epoch(),
|
||||
},
|
||||
],
|
||||
@@ -197,8 +215,17 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
})),
|
||||
);
|
||||
|
||||
const event = () => genericEvent(props.event);
|
||||
const hasZapTag = () => event().findTagsByName('zap').length > 0;
|
||||
const event = () => {
|
||||
const rawEvent = getEvent(props.zapTo);
|
||||
if (rawEvent == null) return undefined;
|
||||
return genericEvent(rawEvent);
|
||||
};
|
||||
|
||||
const hasZapTag = () => {
|
||||
const ev = event();
|
||||
if (ev == null) return false;
|
||||
return ev.findTagsByName('zap').length > 0;
|
||||
};
|
||||
|
||||
const lnurlPayUrlDomain = () => {
|
||||
if (props.lnurlPayUrl == null) return null;
|
||||
@@ -240,8 +267,8 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
amountMilliSats,
|
||||
content: comment(),
|
||||
pubkey: p,
|
||||
recipientPubkey: props.event.pubkey,
|
||||
eventId: props.event.id,
|
||||
recipientPubkey: getPubkey(props.zapTo),
|
||||
eventId: event()?.id,
|
||||
relays: config().relayUrls,
|
||||
lnurlPayUrl: props.lnurlPayUrl,
|
||||
});
|
||||
@@ -263,7 +290,7 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
};
|
||||
|
||||
const getInvoiceMutation = createMutation(() => ({
|
||||
mutationKey: ['getInvoiceMutation', props.event.id],
|
||||
mutationKey: ['getInvoiceMutation', props.zapTo],
|
||||
mutationFn: () => getInvoice(),
|
||||
}));
|
||||
|
||||
@@ -293,8 +320,8 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
{(invoice) => (
|
||||
<InvoiceDisplay
|
||||
invoice={invoice}
|
||||
event={props.event}
|
||||
nostrPubkey={endpoint()?.nostrPubkey}
|
||||
recipientPubkey={getPubkey(props.zapTo)}
|
||||
lnurlPubkey={endpoint()?.nostrPubkey}
|
||||
/>
|
||||
)}
|
||||
</Match>
|
||||
@@ -321,7 +348,14 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
<div>{endpoint()?.decodedMetadata?.textLongDesc}</div>
|
||||
</div>
|
||||
<div class="w-96 rounded-lg border border-border p-2">
|
||||
<EventDisplay event={props.event} actions={false} embedding={false} />
|
||||
<Switch>
|
||||
<Match when={'event' in props.zapTo && props.zapTo} keyed>
|
||||
{(zapTo) => <EventDisplay event={zapTo.event} actions={false} embedding={false} />}
|
||||
</Match>
|
||||
<Match when={'pubkey' in props.zapTo && props.zapTo} keyed>
|
||||
{(zapTo) => <UserNameDisplay pubkey={zapTo.pubkey} />}
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
<form class="mt-4 flex w-64 flex-col items-center gap-1" onSubmit={handleSubmit}>
|
||||
<label class="flex w-full items-center gap-2">
|
||||
@@ -366,8 +400,11 @@ const ZapDialog: Component<ZapDialogProps> = (props) => {
|
||||
|
||||
const ZapRequestModal: Component<ZapRequestModalProps> = (props) => {
|
||||
const i18n = useTranslation();
|
||||
|
||||
const recipientPubkey = () => getPubkey(props.zapTo);
|
||||
|
||||
const { lud06, lud16, isZapConfigured } = useProfile(() => ({
|
||||
pubkey: props.event.pubkey,
|
||||
pubkey: recipientPubkey(),
|
||||
}));
|
||||
|
||||
const [lnurlSource, setLnurlSource] = createSignal<'lud06' | 'lud16' | undefined>();
|
||||
@@ -412,10 +449,10 @@ const ZapRequestModal: Component<ZapRequestModalProps> = (props) => {
|
||||
</div>
|
||||
</Show>
|
||||
<Show when={lnurlSource() === 'lud06' && lud06()} keyed>
|
||||
{(value) => <ZapDialog lnurlPayUrl={lud06ToLnurlPayUrl(value)} event={props.event} />}
|
||||
{(value) => <ZapDialog lnurlPayUrl={lud06ToLnurlPayUrl(value)} zapTo={props.zapTo} />}
|
||||
</Show>
|
||||
<Show when={lnurlSource() === 'lud16' && lud16()} keyed>
|
||||
{(value) => <ZapDialog lnurlPayUrl={lud16ToLnurlPayUrl(value)} event={props.event} />}
|
||||
{(value) => <ZapDialog lnurlPayUrl={lud16ToLnurlPayUrl(value)} zapTo={props.zapTo} />}
|
||||
</Show>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user