The drawCardToCanvas function had an old version of the card rendering logic that leaked outside the function body (lines 505-731 were orphaned code). This code referenced local variables (ctx, p, data, qrX, qrSize, PAD, etc.) that only existed inside the function, causing ReferenceError at module load time and crashing the entire app with a blank screen. Also added missing lucide-react imports (Printer, Loader2, Share2, Download, Check, Copy, Award, Zap, Info, Palette, Shield, ChevronUp, ChevronDown) that were used in the JSX but not imported, causing the secondary Printer is not defined ReferenceError. Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
701 lines
23 KiB
TypeScript
701 lines
23 KiB
TypeScript
import { useState, useEffect, useRef, forwardRef } from 'react';
|
||
import { Zap, Copy, Check, ExternalLink, Sparkle, Sparkles, Star, Rocket, ArrowLeft, X, Wallet } from 'lucide-react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { cn } from '@/lib/utils';
|
||
import {
|
||
Dialog,
|
||
DialogContent,
|
||
DialogDescription,
|
||
DialogHeader,
|
||
DialogTitle,
|
||
DialogTrigger,
|
||
} from '@/components/ui/dialog';
|
||
import {
|
||
Drawer,
|
||
DrawerContent,
|
||
DrawerDescription,
|
||
DrawerHeader,
|
||
DrawerTitle,
|
||
DrawerTrigger,
|
||
DrawerClose,
|
||
} from '@/components/ui/drawer';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { Textarea } from '@/components/ui/textarea';
|
||
import { Card, CardContent } from '@/components/ui/card';
|
||
import { Separator } from '@/components/ui/separator';
|
||
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||
import { useAuthor } from '@/hooks/useAuthor';
|
||
import { useToast } from '@/hooks/useToast';
|
||
import { useZaps } from '@/hooks/useZaps';
|
||
import { useWallet } from '@/hooks/useWallet';
|
||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||
import { useCashu } from '@/hooks/wallet/useCashu';
|
||
import { useLightningOperations } from '@/hooks/wallet/useLightningOperations';
|
||
import { WalletModal } from '@/components/WalletModal';
|
||
import type { Event } from 'nostr-tools';
|
||
// qrcode imported lazily in async function to avoid canvas crash at module load
|
||
import type { WebLNProvider } from "@webbtc/webln-types";
|
||
|
||
interface ZapDialogProps {
|
||
target: Event;
|
||
children?: React.ReactNode;
|
||
className?: string;
|
||
}
|
||
|
||
const presetAmounts = [
|
||
{ amount: 1, icon: Sparkle },
|
||
{ amount: 50, icon: Sparkles },
|
||
{ amount: 100, icon: Zap },
|
||
{ amount: 250, icon: Star },
|
||
{ amount: 1000, icon: Rocket },
|
||
];
|
||
|
||
interface ZapContentProps {
|
||
invoice: string | null;
|
||
amount: number | string;
|
||
comment: string;
|
||
isZapping: boolean;
|
||
qrCodeUrl: string;
|
||
copied: boolean;
|
||
webln: WebLNProvider | null;
|
||
handleZap: () => void;
|
||
handleCopy: () => void;
|
||
openInWallet: () => void;
|
||
setAmount: (amount: number | string) => void;
|
||
setComment: (comment: string) => void;
|
||
inputRef: React.RefObject<HTMLInputElement>;
|
||
zap: (amount: number, comment: string) => void;
|
||
paymentMethod?: 'lightning' | 'cashu';
|
||
setPaymentMethod?: (method: 'lightning' | 'cashu') => void;
|
||
cashuBalance?: number;
|
||
hasCashu?: boolean;
|
||
}
|
||
|
||
// Wallet Setup Screen - shown when user has no wallet connected
|
||
const WalletSetupContent = forwardRef<HTMLDivElement, {
|
||
onContinue: () => void;
|
||
}>(({ onContinue }, ref) => (
|
||
<div className="px-4 py-6 space-y-6" ref={ref}>
|
||
<div className="text-center space-y-3">
|
||
<div className="mx-auto w-16 h-16 rounded-full bg-orange-500/20 flex items-center justify-center">
|
||
<Wallet className="h-8 w-8 text-orange-400" />
|
||
</div>
|
||
<div>
|
||
<h3 className="font-bold text-lg mb-2">Connect Your Wallet First</h3>
|
||
<p className="text-sm text-muted-foreground">
|
||
To send zaps (Bitcoin tips), you need to connect a Lightning wallet.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<Card className="bg-blue-950/30 border-blue-500/50">
|
||
<CardContent className="p-4 space-y-3">
|
||
<div className="flex items-start gap-3">
|
||
<div className="text-2xl">💡</div>
|
||
<div className="flex-1">
|
||
<p className="text-sm font-medium mb-1">Quick Setup (30 seconds)</p>
|
||
<p className="text-xs text-muted-foreground">
|
||
Connect your existing Lightning wallet with Nostr Wallet Connect (NWC). No extra accounts needed.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<div className="space-y-3">
|
||
<WalletModal>
|
||
<Button className="w-full gap-2 bg-orange-500 hover:bg-orange-600" size="lg">
|
||
<Wallet className="h-4 w-4" />
|
||
Connect Wallet with NWC
|
||
</Button>
|
||
</WalletModal>
|
||
|
||
<a
|
||
href="https://planb.academy/en/tutorials/wallet"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
className="block"
|
||
>
|
||
<Button variant="outline" className="w-full gap-2" size="lg">
|
||
<ExternalLink className="h-4 w-4" />
|
||
Don't have a wallet? Learn how
|
||
</Button>
|
||
</a>
|
||
|
||
<Button
|
||
variant="ghost"
|
||
onClick={onContinue}
|
||
className="w-full text-muted-foreground hover:text-foreground"
|
||
size="sm"
|
||
>
|
||
Skip for now (manual invoice)
|
||
</Button>
|
||
</div>
|
||
|
||
<Card className="bg-yellow-950/30 border-yellow-500/30">
|
||
<CardContent className="p-3">
|
||
<p className="text-xs text-gray-300">
|
||
<strong className="text-yellow-400">ℹ️ Note:</strong> You can still create an invoice without connecting a wallet, but you'll need to copy it manually to pay.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
));
|
||
WalletSetupContent.displayName = 'WalletSetupContent';
|
||
|
||
// Moved ZapContent outside of ZapDialog to prevent re-renders causing focus loss
|
||
const ZapContent = forwardRef<HTMLDivElement, ZapContentProps>(({
|
||
invoice,
|
||
amount,
|
||
comment,
|
||
isZapping,
|
||
qrCodeUrl,
|
||
copied,
|
||
webln,
|
||
handleZap,
|
||
handleCopy,
|
||
openInWallet,
|
||
setAmount,
|
||
setComment,
|
||
inputRef,
|
||
zap,
|
||
paymentMethod,
|
||
setPaymentMethod,
|
||
cashuBalance,
|
||
hasCashu,
|
||
}, ref) => (
|
||
<div ref={ref}>
|
||
{invoice ? (
|
||
<div className="flex flex-col h-full min-h-0">
|
||
{/* Payment amount display */}
|
||
<div className="text-center pt-4">
|
||
<div className="text-2xl font-bold">{amount} sats</div>
|
||
</div>
|
||
|
||
<Separator className="my-4" />
|
||
|
||
<div className="flex flex-col justify-center min-h-0 flex-1 px-2">
|
||
{/* QR Code */}
|
||
<div className="flex justify-center">
|
||
<Card className="p-3 [@media(max-height:680px)]:max-w-[65vw] max-w-[95vw] mx-auto">
|
||
<CardContent className="p-0 flex justify-center">
|
||
{qrCodeUrl ? (
|
||
<img
|
||
src={qrCodeUrl}
|
||
alt="Lightning Invoice QR Code"
|
||
className="w-full h-auto aspect-square max-w-full object-contain"
|
||
/>
|
||
) : (
|
||
<div className="w-full aspect-square bg-muted animate-pulse rounded" />
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Invoice input */}
|
||
<div className="space-y-2 mt-4">
|
||
<Label htmlFor="invoice">Lightning Invoice</Label>
|
||
<div className="flex gap-2 min-w-0">
|
||
<Input
|
||
id="invoice"
|
||
value={invoice}
|
||
readOnly
|
||
className="font-mono text-xs min-w-0 flex-1 overflow-hidden text-ellipsis"
|
||
onClick={(e) => e.currentTarget.select()}
|
||
/>
|
||
<Button
|
||
variant="outline"
|
||
size="icon"
|
||
onClick={handleCopy}
|
||
className="shrink-0"
|
||
>
|
||
{copied ? (
|
||
<Check className="h-4 w-4 text-green-600" />
|
||
) : (
|
||
<Copy className="h-4 w-4" />
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Payment buttons */}
|
||
<div className="space-y-3 mt-4">
|
||
{webln && (
|
||
<Button
|
||
onClick={() => {
|
||
const finalAmount = typeof amount === 'string' ? parseInt(amount, 10) : amount;
|
||
zap(finalAmount, comment);
|
||
}}
|
||
disabled={isZapping}
|
||
className="w-full"
|
||
size="lg"
|
||
>
|
||
<Zap className="h-4 w-4 mr-2" />
|
||
{isZapping ? "Processing..." : "Pay with WebLN"}
|
||
</Button>
|
||
)}
|
||
|
||
<Button
|
||
variant="outline"
|
||
onClick={openInWallet}
|
||
className="w-full"
|
||
size="lg"
|
||
>
|
||
<ExternalLink className="h-4 w-4 mr-2" />
|
||
Open in Lightning Wallet
|
||
</Button>
|
||
|
||
<div className="text-xs sm:text-[.65rem] text-muted-foreground text-center">
|
||
Scan the QR code or copy the invoice to pay with any Lightning wallet.
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
) : (
|
||
<>
|
||
<div className="grid gap-3 px-4 py-4 w-full overflow-hidden">
|
||
{/* Cashu Balance Banner - Show prominently if user has Cashu funds */}
|
||
{hasCashu && cashuBalance !== undefined && cashuBalance > 0 && setPaymentMethod && (
|
||
<Card className={cn(
|
||
"border-2 transition-all cursor-pointer",
|
||
paymentMethod === 'cashu'
|
||
? "bg-orange-500/20 border-orange-500/50"
|
||
: "bg-orange-500/10 border-orange-500/30 hover:bg-orange-500/15"
|
||
)}
|
||
onClick={() => setPaymentMethod('cashu')}
|
||
>
|
||
<CardContent className="p-3">
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-10 h-10 rounded-lg bg-orange-500/20 flex items-center justify-center">
|
||
<span className="text-xl">🥜</span>
|
||
</div>
|
||
<div>
|
||
<p className="text-sm font-bold text-orange-400">Cashu Wallet</p>
|
||
<p className="text-xs text-muted-foreground">Pay instantly with your built-in wallet</p>
|
||
</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<p className="text-lg font-bold text-orange-400">{cashuBalance.toLocaleString()}</p>
|
||
<p className="text-[10px] text-muted-foreground uppercase">sats available</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
)}
|
||
|
||
{/* Payment Method Selector - show if user has Cashu but might want Lightning too */}
|
||
{hasCashu && cashuBalance !== undefined && setPaymentMethod && (
|
||
<>
|
||
<div className="flex items-center gap-2">
|
||
<Button
|
||
type="button"
|
||
variant={paymentMethod === 'cashu' ? 'default' : 'ghost'}
|
||
size="sm"
|
||
onClick={() => setPaymentMethod('cashu')}
|
||
className={cn(
|
||
"flex-1 gap-2 h-9",
|
||
paymentMethod === 'cashu' && "bg-orange-500 hover:bg-orange-600"
|
||
)}
|
||
>
|
||
<span>🥜</span>
|
||
<span className="text-xs">Cashu</span>
|
||
</Button>
|
||
<Button
|
||
type="button"
|
||
variant={paymentMethod === 'lightning' ? 'default' : 'ghost'}
|
||
size="sm"
|
||
onClick={() => setPaymentMethod('lightning')}
|
||
className="flex-1 gap-2 h-9"
|
||
>
|
||
<Zap className="h-4 w-4" />
|
||
<span className="text-xs">Lightning</span>
|
||
</Button>
|
||
</div>
|
||
<Separator />
|
||
</>
|
||
)}
|
||
|
||
<ToggleGroup
|
||
type="single"
|
||
value={String(amount)}
|
||
onValueChange={(value) => {
|
||
if (value) {
|
||
setAmount(parseInt(value, 10));
|
||
}
|
||
}}
|
||
className="grid grid-cols-5 gap-1 w-full"
|
||
>
|
||
{presetAmounts.map(({ amount: presetAmount, icon: Icon }) => (
|
||
<ToggleGroupItem
|
||
key={presetAmount}
|
||
value={String(presetAmount)}
|
||
className="flex flex-col h-auto min-w-0 text-xs px-1 py-2"
|
||
>
|
||
<Icon className="h-4 w-4 mb-1" />
|
||
<span className="truncate">{presetAmount}</span>
|
||
</ToggleGroupItem>
|
||
))}
|
||
</ToggleGroup>
|
||
<div className="flex items-center gap-2">
|
||
<div className="h-px flex-1 bg-muted" />
|
||
<span className="text-xs text-muted-foreground">OR</span>
|
||
<div className="h-px flex-1 bg-muted" />
|
||
</div>
|
||
<Input
|
||
ref={inputRef}
|
||
id="custom-amount"
|
||
type="number"
|
||
placeholder="Custom amount"
|
||
value={amount}
|
||
onChange={(e) => setAmount(e.target.value)}
|
||
className="w-full text-sm"
|
||
/>
|
||
<Textarea
|
||
id="custom-comment"
|
||
placeholder="Add a comment (optional)"
|
||
value={comment}
|
||
onChange={(e) => setComment(e.target.value)}
|
||
className="w-full resize-none text-sm"
|
||
rows={2}
|
||
/>
|
||
</div>
|
||
<div className="px-4 pb-4 space-y-2">
|
||
{/* Show insufficient balance warning for Cashu */}
|
||
{paymentMethod === 'cashu' && cashuBalance !== undefined && (
|
||
typeof amount === 'number' && amount > cashuBalance ? (
|
||
<Card className="bg-yellow-950/30 border-yellow-500/30">
|
||
<CardContent className="p-3">
|
||
<p className="text-xs text-yellow-200">
|
||
⚠️ Insufficient Cashu balance. You need {(amount - cashuBalance).toLocaleString()} more sats.
|
||
Switch to Lightning or top up your Cashu wallet.
|
||
</p>
|
||
</CardContent>
|
||
</Card>
|
||
) : null
|
||
)}
|
||
|
||
<Button
|
||
onClick={handleZap}
|
||
className="w-full"
|
||
disabled={
|
||
isZapping ||
|
||
(paymentMethod === 'cashu' && typeof amount === 'number' && cashuBalance !== undefined && amount > cashuBalance)
|
||
}
|
||
size="default"
|
||
>
|
||
{isZapping ? (
|
||
'Creating invoice...'
|
||
) : (
|
||
<>
|
||
{paymentMethod === 'cashu' ? '🥜' : <Zap className="h-4 w-4 mr-2" />}
|
||
Zap {amount} sats {paymentMethod === 'cashu' ? 'with Cashu' : ''}
|
||
</>
|
||
)}
|
||
</Button>
|
||
</div>
|
||
</>
|
||
)}
|
||
</div>
|
||
));
|
||
ZapContent.displayName = 'ZapContent';
|
||
|
||
export function ZapDialog({ target, children, className }: ZapDialogProps) {
|
||
const [open, setOpen] = useState(false);
|
||
const { user } = useCurrentUser();
|
||
const { data: author } = useAuthor(target.pubkey);
|
||
const { toast } = useToast();
|
||
const { webln, activeNWC } = useWallet();
|
||
const { zap, isZapping, invoice, setInvoice } = useZaps(target, webln, activeNWC, () => setOpen(false));
|
||
const [amount, setAmount] = useState<number | string>(100);
|
||
const [comment, setComment] = useState<string>('');
|
||
const [copied, setCopied] = useState(false);
|
||
const [qrCodeUrl, setQrCodeUrl] = useState<string>('');
|
||
const [showWalletSetup, setShowWalletSetup] = useState(false);
|
||
const [hasShownSetup, setHasShownSetup] = useState(false);
|
||
const [paymentMethod, setPaymentMethod] = useState<'lightning' | 'cashu'>('lightning');
|
||
const inputRef = useRef<HTMLInputElement>(null);
|
||
const isMobile = useIsMobile();
|
||
|
||
// Cashu wallet integration
|
||
const { totalBalance: cashuBalance, isInitialized: hasCashu, coco, mints } = useCashu();
|
||
|
||
// Check if user has any wallet connected
|
||
const hasWallet = !!webln || !!activeNWC;
|
||
const hasAnyCashuBalance = hasCashu && cashuBalance > 0;
|
||
|
||
useEffect(() => {
|
||
if (target) {
|
||
setComment('Zapped with Engorile!');
|
||
}
|
||
}, [target]);
|
||
|
||
// Generate QR code
|
||
useEffect(() => {
|
||
let isCancelled = false;
|
||
|
||
const generateQR = async () => {
|
||
if (!invoice) {
|
||
setQrCodeUrl('');
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const { default: QRCode } = await import('qrcode');
|
||
const url = await QRCode.toDataURL(invoice.toUpperCase(), {
|
||
width: 512,
|
||
margin: 2,
|
||
color: {
|
||
dark: '#000000',
|
||
light: '#FFFFFF',
|
||
},
|
||
});
|
||
|
||
if (!isCancelled) {
|
||
setQrCodeUrl(url);
|
||
}
|
||
} catch (err) {
|
||
if (!isCancelled) {
|
||
console.error('Failed to generate QR code:', err);
|
||
}
|
||
}
|
||
};
|
||
|
||
generateQR();
|
||
|
||
return () => {
|
||
isCancelled = true;
|
||
};
|
||
}, [invoice]);
|
||
|
||
const handleCopy = async () => {
|
||
if (invoice) {
|
||
await navigator.clipboard.writeText(invoice);
|
||
setCopied(true);
|
||
toast({
|
||
title: 'Invoice copied',
|
||
description: 'Lightning invoice copied to clipboard',
|
||
});
|
||
setTimeout(() => setCopied(false), 2000);
|
||
}
|
||
};
|
||
|
||
const openInWallet = () => {
|
||
if (invoice) {
|
||
const lightningUrl = `lightning:${invoice}`;
|
||
window.open(lightningUrl, '_blank');
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (open) {
|
||
setAmount(100);
|
||
setInvoice(null);
|
||
setCopied(false);
|
||
setQrCodeUrl('');
|
||
|
||
// Show wallet setup screen if no wallet connected and haven't shown it yet
|
||
if (!hasWallet && !hasShownSetup) {
|
||
setShowWalletSetup(true);
|
||
} else {
|
||
setShowWalletSetup(false);
|
||
}
|
||
} else {
|
||
// Clean up state when dialog closes
|
||
setAmount(100);
|
||
setInvoice(null);
|
||
setCopied(false);
|
||
setQrCodeUrl('');
|
||
setShowWalletSetup(false);
|
||
}
|
||
}, [open, setInvoice, hasWallet, hasShownSetup]);
|
||
|
||
const handleZap = async () => {
|
||
const finalAmount = typeof amount === 'string' ? parseInt(amount, 10) : amount;
|
||
|
||
// If Cashu payment method is selected and user has invoice to pay
|
||
if (paymentMethod === 'cashu' && hasCashu && coco && invoice) {
|
||
try {
|
||
// Get first active mint
|
||
const activeMint = mints.find(m => m.url);
|
||
if (!activeMint) {
|
||
throw new Error('No active mint found');
|
||
}
|
||
|
||
// Pay the Lightning invoice using Cashu (create and pay melt quote)
|
||
const meltQuote = await coco.quotes.createMeltQuote(activeMint.url, invoice);
|
||
await coco.quotes.payMeltQuote(activeMint.url, meltQuote.quote);
|
||
|
||
toast({
|
||
title: 'Zap sent!',
|
||
description: `Successfully zapped ${finalAmount} sats from your Cashu wallet`,
|
||
});
|
||
setOpen(false);
|
||
setInvoice(null);
|
||
} catch (error) {
|
||
console.error('Cashu payment error:', error);
|
||
toast({
|
||
variant: 'destructive',
|
||
title: 'Payment failed',
|
||
description: error instanceof Error ? error.message : 'Failed to pay with Cashu wallet',
|
||
});
|
||
}
|
||
} else {
|
||
// Use regular Lightning/NWC zap flow
|
||
zap(finalAmount, comment);
|
||
}
|
||
};
|
||
|
||
const handleContinueWithoutWallet = () => {
|
||
setShowWalletSetup(false);
|
||
setHasShownSetup(true);
|
||
};
|
||
|
||
const contentProps = {
|
||
invoice,
|
||
amount,
|
||
comment,
|
||
isZapping,
|
||
qrCodeUrl,
|
||
copied,
|
||
webln,
|
||
handleZap,
|
||
handleCopy,
|
||
openInWallet,
|
||
setAmount,
|
||
setComment,
|
||
inputRef,
|
||
zap,
|
||
paymentMethod,
|
||
setPaymentMethod,
|
||
cashuBalance,
|
||
hasCashu,
|
||
};
|
||
|
||
if (!user || user.pubkey === target.pubkey || !author?.metadata?.lud06 && !author?.metadata?.lud16) {
|
||
return null;
|
||
}
|
||
|
||
if (isMobile) {
|
||
// Use drawer for entire mobile flow, make it full-screen when showing invoice
|
||
return (
|
||
<Drawer
|
||
open={open}
|
||
onOpenChange={(newOpen) => {
|
||
// Reset invoice when closing
|
||
if (!newOpen) {
|
||
setInvoice(null);
|
||
setQrCodeUrl('');
|
||
}
|
||
setOpen(newOpen);
|
||
}}
|
||
dismissible={true} // Always allow dismissal via drag
|
||
snapPoints={invoice ? [0.5, 0.75, 0.98] : [0.98]}
|
||
activeSnapPoint={invoice ? 0.98 : 0.98}
|
||
modal={true}
|
||
shouldScaleBackground={false}
|
||
fadeFromIndex={0}
|
||
>
|
||
<DrawerTrigger asChild>
|
||
<div className={`cursor-pointer ${className || ''}`}>
|
||
{children}
|
||
</div>
|
||
</DrawerTrigger>
|
||
<DrawerContent
|
||
key={invoice ? 'payment' : 'form'}
|
||
className={cn(
|
||
"transition-all duration-300",
|
||
invoice ? "h-full max-h-screen" : "max-h-[98vh]"
|
||
)}
|
||
data-testid="zap-modal"
|
||
>
|
||
<DrawerHeader className="text-center relative">
|
||
{/* Back button when showing invoice */}
|
||
{invoice && (
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={() => {
|
||
setInvoice(null);
|
||
setQrCodeUrl('');
|
||
}}
|
||
className="absolute left-4 top-4 flex items-center gap-2"
|
||
>
|
||
<ArrowLeft className="h-4 w-4" />
|
||
</Button>
|
||
)}
|
||
|
||
{/* Close button */}
|
||
<DrawerClose asChild>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="absolute right-4 top-4"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
<span className="sr-only">Close</span>
|
||
</Button>
|
||
</DrawerClose>
|
||
|
||
<DrawerTitle className="text-lg break-words pt-2">
|
||
{showWalletSetup ? 'Connect Wallet' : invoice ? 'Lightning Payment' : 'Send a Zap'}
|
||
</DrawerTitle>
|
||
<DrawerDescription className="text-sm break-words text-center">
|
||
{showWalletSetup ? (
|
||
'Connect your Lightning wallet to send zaps'
|
||
) : invoice ? (
|
||
'Pay with Bitcoin Lightning Network'
|
||
) : (
|
||
'Zaps are small Bitcoin payments that support the creator of this item. If you enjoyed this, consider sending a zap!'
|
||
)}
|
||
</DrawerDescription>
|
||
</DrawerHeader>
|
||
<div className="flex-1 overflow-y-auto px-4 pb-4">
|
||
{showWalletSetup ? (
|
||
<WalletSetupContent onContinue={handleContinueWithoutWallet} />
|
||
) : (
|
||
<ZapContent {...contentProps} />
|
||
)}
|
||
</div>
|
||
</DrawerContent>
|
||
</Drawer>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={setOpen}>
|
||
<DialogTrigger asChild>
|
||
<div className={`cursor-pointer ${className || ''}`}>
|
||
{children}
|
||
</div>
|
||
</DialogTrigger>
|
||
<DialogContent className="w-[calc(100%-1rem)] sm:max-w-[425px] max-h-[90vh] max-h-[90dvh] overflow-hidden p-4 sm:p-6" data-testid="zap-modal">
|
||
<DialogHeader>
|
||
<DialogTitle className="text-base sm:text-lg break-words">
|
||
{showWalletSetup ? 'Connect Wallet' : invoice ? 'Lightning Payment' : 'Send a Zap'}
|
||
</DialogTitle>
|
||
<DialogDescription className="text-xs sm:text-sm text-center break-words">
|
||
{showWalletSetup ? (
|
||
'Connect your Lightning wallet to send zaps'
|
||
) : invoice ? (
|
||
'Pay with Bitcoin Lightning Network'
|
||
) : (
|
||
<>
|
||
Zaps are small Bitcoin payments that support creators. Consider sending a zap!
|
||
</>
|
||
)}
|
||
</DialogDescription>
|
||
</DialogHeader>
|
||
<div className="overflow-y-auto -mx-4 sm:-mx-6 px-4 sm:px-6">
|
||
{showWalletSetup ? (
|
||
<WalletSetupContent onContinue={handleContinueWithoutWallet} />
|
||
) : (
|
||
<ZapContent {...contentProps} />
|
||
)}
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
}
|