mirror of
https://gitlab.com/soapbox-pub/mkstack.git
synced 2025-08-27 13:09:22 +00:00
cleanup + icons
This commit is contained in:
parent
ec1a9777de
commit
55bf545646
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect, useRef } from 'react';
|
import { useState, useEffect, useRef } from 'react';
|
||||||
import { Zap, Copy } from 'lucide-react';
|
import { Zap, Copy, Sparkle, Sparkles, Star, Rocket } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -18,6 +18,7 @@ import { useAuthor } from '@/hooks/useAuthor';
|
|||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useZaps } from '@/hooks/useZaps';
|
import { useZaps } from '@/hooks/useZaps';
|
||||||
import type { WebLNProvider } from 'webln';
|
import type { WebLNProvider } from 'webln';
|
||||||
|
import { requestProvider } from 'webln';
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
import type { Event } from 'nostr-tools';
|
import type { Event } from 'nostr-tools';
|
||||||
|
|
||||||
@ -27,11 +28,17 @@ interface ZapDialogProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const presetAmounts = [1, 50, 100, 250, 1000];
|
const presetAmounts = [
|
||||||
|
{ amount: 1, icon: Sparkle },
|
||||||
|
{ amount: 50, icon: Sparkles },
|
||||||
|
{ amount: 100, icon: Zap },
|
||||||
|
{ amount: 250, icon: Star },
|
||||||
|
{ amount: 1000, icon: Rocket },
|
||||||
|
];
|
||||||
|
|
||||||
export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [webln, _setWebln] = useState<WebLNProvider | null>(null);
|
const [webln, setWebln] = useState<WebLNProvider | null>(null);
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const { data: author } = useAuthor(target.pubkey);
|
const { data: author } = useAuthor(target.pubkey);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
@ -47,6 +54,23 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
}
|
}
|
||||||
}, [target]);
|
}, [target]);
|
||||||
|
|
||||||
|
// Detect WebLN when dialog opens
|
||||||
|
useEffect(() => {
|
||||||
|
const detectWebLN = async () => {
|
||||||
|
if (open && !webln) {
|
||||||
|
try {
|
||||||
|
const provider = await requestProvider();
|
||||||
|
setWebln(provider);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('WebLN requestProvider failed:', error);
|
||||||
|
setWebln(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
detectWebLN();
|
||||||
|
}, [open, webln]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (invoice && qrCodeRef.current) {
|
if (invoice && qrCodeRef.current) {
|
||||||
QRCode.toCanvas(qrCodeRef.current, invoice, { width: 256 });
|
QRCode.toCanvas(qrCodeRef.current, invoice, { width: 256 });
|
||||||
@ -81,8 +105,8 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={setOpen}>
|
<Dialog open={open} onOpenChange={setOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button size="sm" className={className}>
|
<Button variant="ghost" size="sm" className={`text-muted-foreground hover:text-yellow-600 ${className || ''}`}>
|
||||||
<Zap className={`h-4 w-4 ${children ? 'mr-2' : ''}`} />
|
<Zap className={`h-4 w-4 ${children ? 'mr-1' : ''}`} />
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
@ -123,12 +147,13 @@ export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
|||||||
}}
|
}}
|
||||||
className="grid grid-cols-5 gap-2"
|
className="grid grid-cols-5 gap-2"
|
||||||
>
|
>
|
||||||
{presetAmounts.map((presetAmount) => (
|
{presetAmounts.map(({ amount: presetAmount, icon: Icon }) => (
|
||||||
<ToggleGroupItem
|
<ToggleGroupItem
|
||||||
key={presetAmount}
|
key={presetAmount}
|
||||||
value={String(presetAmount)}
|
value={String(presetAmount)}
|
||||||
className="flex flex-col h-auto"
|
className="flex flex-col h-auto"
|
||||||
>
|
>
|
||||||
|
<Icon className="h-5 w-5 mb-1.5" />
|
||||||
{presetAmount}
|
{presetAmount}
|
||||||
</ToggleGroupItem>
|
</ToggleGroupItem>
|
||||||
))}
|
))}
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNostrPublish } from '@/hooks/useNostrPublish';
|
|
||||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useAuthor } from '@/hooks/useAuthor';
|
import { useAuthor } from '@/hooks/useAuthor';
|
||||||
import { useAppContext } from '@/hooks/useAppContext';
|
import { useAppContext } from '@/hooks/useAppContext';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { nip57, nip19, Event } from 'nostr-tools';
|
import { nip57, nip19 } from 'nostr-tools';
|
||||||
|
import type { Event } from 'nostr-tools';
|
||||||
import type { WebLNProvider } from 'webln';
|
import type { WebLNProvider } from 'webln';
|
||||||
|
import { LNURL } from '@nostrify/nostrify/ln';
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useNostr } from '@nostrify/react';
|
import { useNostr } from '@nostrify/react';
|
||||||
@ -16,7 +17,6 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const { config } = useAppContext();
|
const { config } = useAppContext();
|
||||||
const { mutate: publishEvent } = useNostrPublish();
|
|
||||||
const author = useAuthor(target?.pubkey);
|
const author = useAuthor(target?.pubkey);
|
||||||
const [isZapping, setIsZapping] = useState(false);
|
const [isZapping, setIsZapping] = useState(false);
|
||||||
const [invoice, setInvoice] = useState<string | null>(null);
|
const [invoice, setInvoice] = useState<string | null>(null);
|
||||||
@ -86,18 +86,8 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const lud16 = author.data?.metadata?.lud16;
|
|
||||||
if (!lud16) {
|
|
||||||
toast({
|
|
||||||
title: 'Lightning address not found',
|
|
||||||
description: 'The author does not have a lightning address configured.',
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
setIsZapping(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!author.data) {
|
if (!author.data || !author.data?.metadata) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Author not found',
|
title: 'Author not found',
|
||||||
description: 'Could not find the author of this item.',
|
description: 'Could not find the author of this item.',
|
||||||
@ -107,67 +97,50 @@ export function useZaps(target: Event, webln: WebLNProvider | null, onZapSuccess
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const zapEndpoint = await nip57.getZapEndpoint(author.data.event as Event);
|
const { lud06, lud16 } = author.data.metadata;
|
||||||
if (!zapEndpoint) {
|
if (!lud16 && !lud06) {
|
||||||
toast({
|
toast({
|
||||||
title: 'Zap endpoint not found',
|
title: 'Lightning address not found',
|
||||||
description: 'Could not find a zap endpoint for the author.',
|
description: 'The author does not have a lightning address (lud16 or lud06) configured.',
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
setIsZapping(false);
|
setIsZapping(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lnurl = lud06 ? LNURL.fromString(lud06) : LNURL.fromLightningAddress(lud16!);
|
||||||
const zapAmount = amount * 1000; // convert to millisats
|
const zapAmount = amount * 1000; // convert to millisats
|
||||||
const relays = [config.relayUrl];
|
const zapRequest = await user.signer.signEvent(nip57.makeZapRequest({
|
||||||
const zapRequest = nip57.makeZapRequest({
|
|
||||||
profile: target.pubkey,
|
profile: target.pubkey,
|
||||||
event: target.id,
|
event: target,
|
||||||
amount: zapAmount,
|
amount: zapAmount,
|
||||||
relays,
|
relays: [config.relayUrl],
|
||||||
comment: comment,
|
comment: comment,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { pr: newInvoice } = await lnurl.getInvoice({
|
||||||
|
amount: zapAmount,
|
||||||
|
nostr: zapRequest,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (naddr) {
|
if (webln) {
|
||||||
const decoded = nip19.decode(naddr).data as nip19.AddressPointer;
|
await webln.sendPayment(newInvoice);
|
||||||
zapRequest.tags.push(["a", `${decoded.kind}:${decoded.pubkey}:${decoded.identifier}`]);
|
toast({
|
||||||
zapRequest.tags = zapRequest.tags.filter(t => t[0] !== 'e');
|
title: 'Zap successful!',
|
||||||
|
description: `You sent ${amount} sats to the author.`,
|
||||||
|
});
|
||||||
|
onZapSuccess?.();
|
||||||
|
} else {
|
||||||
|
setInvoice(newInvoice);
|
||||||
}
|
}
|
||||||
|
|
||||||
publishEvent(zapRequest, {
|
|
||||||
onSuccess: async (event) => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(`${zapEndpoint}?amount=${zapAmount}&nostr=${encodeURI(JSON.stringify(event))}`);
|
|
||||||
const { pr: newInvoice } = await res.json();
|
|
||||||
|
|
||||||
if (webln) {
|
|
||||||
await webln.sendPayment(newInvoice);
|
|
||||||
toast({
|
|
||||||
title: 'Zap successful!',
|
|
||||||
description: `You sent ${amount} sats to the author.`,
|
|
||||||
});
|
|
||||||
onZapSuccess?.();
|
|
||||||
} else {
|
|
||||||
setInvoice(newInvoice);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Zap error:', err);
|
|
||||||
toast({
|
|
||||||
title: 'Zap failed',
|
|
||||||
description: (err as Error).message,
|
|
||||||
variant: 'destructive',
|
|
||||||
});
|
|
||||||
} finally {
|
|
||||||
setIsZapping(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Zap error:', err);
|
||||||
toast({
|
toast({
|
||||||
title: 'Zap failed',
|
title: 'Zap failed',
|
||||||
description: (err as Error).message,
|
description: (err as Error).message,
|
||||||
variant: 'destructive',
|
variant: 'destructive',
|
||||||
});
|
});
|
||||||
|
} finally {
|
||||||
setIsZapping(false);
|
setIsZapping(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user