2025-12-22 09:48:57 +01:00

150 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useState } from 'react'
import QRCode from 'react-qr-code'
import type { AlbyInvoice } from '@/types/alby'
import { getAlbyService, isWebLNAvailable } from '@/lib/alby'
import { AlbyInstaller } from './AlbyInstaller'
interface PaymentModalProps {
invoice: AlbyInvoice
onClose: () => void
onPaymentComplete: () => void
}
export function PaymentModal({ invoice, onClose, onPaymentComplete }: PaymentModalProps) {
const [copied, setCopied] = useState(false)
const [timeRemaining, setTimeRemaining] = useState<number | null>(null)
const paymentUrl = `lightning:${invoice.invoice}`
// Calculate time remaining until invoice expiry
useEffect(() => {
if (invoice.expiresAt) {
const updateTimeRemaining = () => {
const now = Math.floor(Date.now() / 1000)
const remaining = invoice.expiresAt - now
setTimeRemaining(remaining > 0 ? remaining : 0)
}
updateTimeRemaining()
const interval = setInterval(updateTimeRemaining, 1000)
return () => clearInterval(interval)
}
}, [invoice.expiresAt])
const formatTimeRemaining = (seconds: number): string => {
if (seconds <= 0) return 'Expired'
const minutes = Math.floor(seconds / 60)
const secs = seconds % 60
return `${minutes}:${secs.toString().padStart(2, '0')}`
}
const handleCopy = async () => {
try {
await navigator.clipboard.writeText(invoice.invoice)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch (e) {
console.error('Failed to copy:', e)
}
}
const handleOpenWallet = async () => {
try {
const alby = getAlbyService()
if (!isWebLNAvailable()) {
throw new Error('WebLN is not available. Please install Alby or another Lightning wallet extension.')
}
await alby.enable()
await alby.sendPayment(invoice.invoice)
onPaymentComplete()
} catch (e) {
const error = e instanceof Error ? e : new Error(String(e))
console.error('Payment failed:', error)
if (error.message.includes('user rejected') || error.message.includes('cancelled')) {
return
}
alert(`Payment failed: ${error.message}`)
}
}
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-6 max-w-md w-full mx-4 max-h-[90vh] overflow-y-auto">
<AlbyInstaller />
<div className="flex justify-between items-center mb-4">
<div>
<h2 className="text-xl font-bold">Pay {invoice.amount} sats</h2>
{timeRemaining !== null && (
<p className={`text-sm ${timeRemaining <= 60 ? 'text-red-600 font-semibold' : 'text-gray-600'}`}>
Time remaining: {formatTimeRemaining(timeRemaining)}
</p>
)}
</div>
<button
onClick={onClose}
className="text-gray-500 hover:text-gray-700 text-2xl"
>
×
</button>
</div>
<div className="mb-4">
<p className="text-sm text-gray-600 mb-2">Lightning Invoice:</p>
<div className="bg-gray-100 p-3 rounded break-all text-sm font-mono mb-4">
{invoice.invoice}
</div>
{/* QR Code */}
<div className="flex justify-center mb-4">
<div className="bg-white p-4 rounded-lg border-2 border-gray-200">
<QRCode
value={paymentUrl}
size={200}
style={{ height: 'auto', maxWidth: '100%', width: '100%' }}
viewBox="0 0 256 256"
/>
</div>
</div>
<p className="text-xs text-gray-500 text-center mb-2">
Scan with your Lightning wallet to pay
</p>
</div>
<div className="flex gap-2">
<button
onClick={handleCopy}
className="flex-1 px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg font-medium transition-colors"
>
{copied ? 'Copied!' : 'Copy Invoice'}
</button>
<button
onClick={handleOpenWallet}
className="flex-1 px-4 py-2 bg-orange-500 hover:bg-orange-600 text-white rounded-lg font-medium transition-colors"
>
Pay with Alby
</button>
</div>
{timeRemaining !== null && timeRemaining <= 0 && (
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg">
<p className="text-sm text-red-700 font-semibold mb-2">
This invoice has expired
</p>
<p className="text-xs text-red-600">
Please close this modal and try again to generate a new invoice.
</p>
</div>
)}
<p className="text-xs text-gray-500 mt-4 text-center">
Payment will be automatically verified once completed
</p>
</div>
</div>
)
}