* wip backend optimism * another inch * make action state transitions only happen once * another inch * almost ready for testing * use interactive txs * another inch * ready for basic testing * lint fix * inches * wip item update * get item update to work * donate and downzap * inchy inch * fix territory paid actions * wip usePaidMutation * usePaidMutation error handling * PENDING_HELD and HELD transitions, gql paidAction return types * mostly working pessimism * make sure invoice field is present in optimisticResponse * inches * show optimistic values to current me * first pass at notifications and payment status reporting * fix migration to have withdrawal hash * reverse optimism on payment failure * Revert "Optimistic updates via pending sats in item context (#1229)" This reverts commit 93713b33df9bc3701dc5a692b86a04ff64e8cfb1. * add onCompleted to usePaidMutation * onPaid and onPayError for new comments * use 'IS DISTINCT FROM' for NULL invoiceActionState columns * make usePaidMutation easier to read * enhance invoice qr * prevent actions on unpaid items * allow navigation to action's invoice * retry create item * start edit window after item is paid for * fix ux of retries from notifications * refine retries * fix optimistic downzaps * remember item updates can't be retried * store reference to action item in invoice * remove invoice modal layout shift * fix destructuring * fix zap undos * make sure ItemAct is paid in aggregate queries * dont toast on long press zap undo * fix delete and remindme bots * optimistic poll votes with retries * fix retry notifications and invoice item context * fix pessimisitic typo * item mentions and mention notifications * dont show payment retry on item popover * make bios work * refactor paidAction transitions * remove stray console.log * restore docker compose nwc settings * add new todos * persist qr modal on post submission + unify item form submission * fix post edit threshold * make bounty payments work * make job posting work * remove more store procedure usage ... document serialization concerns * dont use dynamic imports for paid action modules * inline comment denormalization * create item starts with median votes * fix potential of serialization anomalies in zaps * dont trigger notification indicator on successful paid action invoices * ignore invoiceId on territory actions and add optimistic concurrency control * begin docs for paid actions * better error toasts and fix apollo cache warnings * small documentation enhancements * improve paid action docs * optimistic concurrency control for territory updates * use satsToMsats and msatsToSats helpers * explictly type raw query template parameters * improve consistency of nested relation names * complete paid action docs * useEffect for canEdit on payment * make sure invoiceId is provided when required * don't return null when expecting array * remove buy credits * move verifyPayment to paidAction * fix comments invoicePaidAt time zone * close nwc connections once * grouped logs for paid actions * stop invoiceWaitUntilPaid if not attempting to pay * allow actionState to transition directly from HELD to PAID * make paid mutation wait until pessimistic are fully paid * change button text when form submits/pays * pulsing form submit button * ignore me in notification indicator for territory subscription * filter unpaid items from more queries * fix donation stike timing * fix pending poll vote * fix recent item notifcation padding * no default form submitting button text * don't show paying on submit button on free edits * fix territory autorenew with fee credits * reorg readme * allow jobs to be editted forever * fix image uploads * more filter fixes for aggregate views * finalize paid action invoice expirations * remove unnecessary async * keep clientside cache normal/consistent * add more detail to paid action doc * improve paid action table * remove actionType guard * fix top territories * typo api/paidAction/README.md Co-authored-by: ekzyis <ek@stacker.news> * typo components/use-paid-mutation.js Co-authored-by: ekzyis <ek@stacker.news> * Apply suggestions from code review Co-authored-by: ekzyis <ek@stacker.news> * encorporate ek feeback * more ek suggestions * fix 'cost to post' hover on items * Apply suggestions from code review Co-authored-by: ekzyis <ek@stacker.news> --------- Co-authored-by: ekzyis <ek@stacker.news>
170 lines
4.8 KiB
JavaScript
170 lines
4.8 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { numWithUnits } from '@/lib/format'
|
|
import AccordianItem from './accordian-item'
|
|
import Qr, { QrSkeleton } from './qr'
|
|
import { CompactLongCountdown } 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'
|
|
import ItemJob from './item-job'
|
|
import Item from './item'
|
|
import { CommentFlat } from './comment'
|
|
import classNames from 'classnames'
|
|
|
|
export default function Invoice ({ id, query = INVOICE, modal, onPayment, info, successVerb, webLn = true, webLnError, poll, waitFor, ...props }) {
|
|
const [expired, setExpired] = useState(false)
|
|
const { data, error } = useQuery(query, SSR
|
|
? {}
|
|
: {
|
|
pollInterval: FAST_POLL_INTERVAL,
|
|
variables: { id },
|
|
nextFetchPolicy: 'cache-and-network',
|
|
skip: !poll
|
|
})
|
|
|
|
const invoice = data?.invoice
|
|
|
|
useEffect(() => {
|
|
if (!invoice) {
|
|
return
|
|
}
|
|
if (waitFor?.(invoice)) {
|
|
onPayment?.(invoice)
|
|
}
|
|
setExpired(new Date(invoice.expiredAt) <= new Date())
|
|
}, [invoice, onPayment, setExpired])
|
|
|
|
if (error) {
|
|
return <div>{error.message}</div>
|
|
}
|
|
|
|
if (!invoice) {
|
|
return <QrSkeleton {...props} />
|
|
}
|
|
|
|
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
|
|
} else if (invoice.expiresAt) {
|
|
variant = 'pending'
|
|
status = (
|
|
<CompactLongCountdown
|
|
date={invoice.expiresAt} onComplete={() => {
|
|
setExpired(true)
|
|
}}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const { nostr, comment, lud18Data, bolt11, confirmedPreimage } = invoice
|
|
|
|
return (
|
|
<>
|
|
{webLnError && !(webLnError instanceof WebLnNotEnabledError) &&
|
|
<div className='text-center fw-bold text-info mb-3' style={{ lineHeight: 1.25 }}>
|
|
Paying from attached wallet failed:
|
|
<code> {webLnError.message}</code>
|
|
</div>}
|
|
<Qr
|
|
webLn={webLn} value={invoice.bolt11}
|
|
description={numWithUnits(invoice.satsRequested, { abbreviate: false })}
|
|
statusVariant={variant} status={status}
|
|
/>
|
|
{!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} />
|
|
{invoice?.item && <ActionInfo invoice={invoice} />}
|
|
</>}
|
|
</>
|
|
)
|
|
}
|
|
|
|
function ActionInfo ({ invoice }) {
|
|
if (!invoice.actionType) return null
|
|
|
|
let className = 'text-info'
|
|
let actionString = ''
|
|
|
|
switch (invoice.actionState) {
|
|
case 'FAILED':
|
|
case 'RETRYING':
|
|
actionString += 'attempted '
|
|
className = 'text-warning'
|
|
break
|
|
case 'PAID':
|
|
actionString += 'successful '
|
|
className = 'text-success'
|
|
break
|
|
default:
|
|
actionString += 'pending '
|
|
}
|
|
|
|
switch (invoice.actionType) {
|
|
case 'ITEM_CREATE':
|
|
actionString += 'item creation'
|
|
break
|
|
case 'ZAP':
|
|
actionString += 'zap on item'
|
|
break
|
|
case 'DOWN_ZAP':
|
|
actionString += 'downzap on item'
|
|
break
|
|
case 'POLL_VOTE':
|
|
actionString += 'poll vote'
|
|
break
|
|
}
|
|
|
|
return (
|
|
<div className='text-start w-100 my-3'>
|
|
<div className={classNames('fw-bold', 'pb-1', className)}>{actionString}</div>
|
|
{(invoice.item?.isJob && <ItemJob item={invoice?.item} />) ||
|
|
(invoice.item?.title && <Item item={invoice?.item} />) ||
|
|
<CommentFlat item={invoice.item} includeParent noReply truncate />}
|
|
</div>
|
|
)
|
|
}
|