stacker.news/components/invoice.js

125 lines
3.7 KiB
JavaScript

import { useState, useEffect } from 'react'
import { numWithUnits } from '@/lib/format'
import AccordianItem from './accordian-item'
import Qr from './qr'
import Countdown from './countdown'
import PayerData from './payer-data'
import Bolt11Info from './bolt11-info'
import { useQuery } from '@apollo/client'
import { INVOICE } from '@/fragments/wallet'
import { FAST_POLL_INTERVAL, SSR } from '@/lib/constants'
import { WebLnNotEnabledError } from './payment'
export default function Invoice ({ invoice, modal, onPayment, info, successVerb, webLn, webLnError, poll }) {
const [expired, setExpired] = useState(new Date(invoice.expiredAt) <= new Date())
const { data, error } = useQuery(INVOICE, SSR
? {}
: {
pollInterval: FAST_POLL_INTERVAL,
variables: { id: invoice.id },
nextFetchPolicy: 'cache-and-network',
skip: !poll
})
if (data) {
invoice = data.invoice
}
if (error) {
return <div>{error.toString()}</div>
}
// if webLn was not passed, use true by default
if (webLn === undefined) webLn = true
let variant = 'default'
let status = 'waiting for you'
if (invoice.cancelled) {
variant = 'failed'
status = 'cancelled'
webLn = false
} else if (invoice.confirmedAt || (invoice.isHeld && invoice.satsReceived && !expired)) {
variant = 'confirmed'
status = `${numWithUnits(invoice.satsReceived, { abbreviate: false })} ${successVerb || 'deposited'}`
webLn = false
} else if (expired) {
variant = 'failed'
status = 'expired'
webLn = false
}
useEffect(() => {
if (invoice.confirmedAt || (invoice.isHeld && invoice.satsReceived)) {
onPayment?.(invoice)
}
}, [invoice.confirmedAt, invoice.isHeld, invoice.satsReceived])
const { nostr, comment, lud18Data, bolt11, confirmedPreimage } = invoice
return (
<>
{webLnError && !(webLnError instanceof WebLnNotEnabledError) &&
<div className='text-center text-danger mb-3'>
Payment from attached wallet failed:
<div>{webLnError.toString()}</div>
</div>}
<Qr
webLn={webLn} value={invoice.bolt11}
description={numWithUnits(invoice.satsRequested, { abbreviate: false })}
statusVariant={variant} status={status}
/>
{invoice.confirmedAt
? (
<div className='text-muted text-center invisible'>
<Countdown date={Date.now()} />
</div>
)
: (
<div className='text-muted text-center'>
<Countdown
date={invoice.expiresAt} onComplete={() => {
setExpired(true)
}}
/>
</div>
)}
{!modal &&
<>
{info && <div className='text-muted fst-italic text-center'>{info}</div>}
<div className='w-100'>
{nostr
? <AccordianItem
header='Nostr Zap Request'
body={
<pre>
<code>
{JSON.stringify(nostr, null, 2)}
</code>
</pre>
}
/>
: null}
</div>
{lud18Data &&
<div className='w-100'>
<AccordianItem
header='sender information'
body={<PayerData data={lud18Data} className='text-muted ms-3 mb-3' />}
/>
</div>}
{comment &&
<div className='w-100'>
<AccordianItem
header='sender comments'
body={<span className='text-muted ms-3'>{comment}</span>}
/>
</div>}
<Bolt11Info bolt11={bolt11} preimage={confirmedPreimage} />
</>}
</>
)
}