stacker.news/pages/withdrawals/[id].js

179 lines
5.0 KiB
JavaScript
Raw Normal View History

import { useQuery, useMutation } from '@apollo/client'
import { CenterLayout } from '@/components/layout'
import { CopyInput, Input, InputSkeleton } from '@/components/form'
2021-05-13 13:28:38 +00:00
import InputGroup from 'react-bootstrap/InputGroup'
import InvoiceStatus from '@/components/invoice-status'
2021-10-26 20:49:37 +00:00
import { useRouter } from 'next/router'
import { WITHDRAWL } from '@/fragments/wallet'
import Link from 'next/link'
import { SSR, INVOICE_RETENTION_DAYS } from '@/lib/constants'
import { numWithUnits } from '@/lib/format'
import Bolt11Info from '@/components/bolt11-info'
import { datePivot, timeLeft } from '@/lib/time'
import { useMe } from '@/components/me'
import { useToast } from '@/components/toast'
import { gql } from 'graphql-tag'
import { useShowModal } from '@/components/modal'
import { DeleteConfirm } from '@/components/delete'
import { getGetServerSideProps } from '@/api/ssrApollo'
// force SSR to include CSP nonces
export const getServerSideProps = getGetServerSideProps({ query: null })
2021-05-13 01:51:37 +00:00
2021-10-26 20:49:37 +00:00
export default function Withdrawl () {
2021-05-13 01:51:37 +00:00
return (
<CenterLayout>
2021-10-26 20:49:37 +00:00
<LoadWithdrawl />
</CenterLayout>
2021-05-13 01:51:37 +00:00
)
}
2021-05-13 21:19:51 +00:00
export function WithdrawlSkeleton ({ status }) {
return (
<>
<div className='w-100'>
<InputSkeleton label='invoice' />
</div>
<div className='w-100'>
<InputSkeleton label='max fee' />
</div>
<InvoiceStatus status={status} />
</>
)
}
2021-10-26 20:49:37 +00:00
function LoadWithdrawl () {
const router = useRouter()
const { loading, error, data } = useQuery(WITHDRAWL, SSR
? {}
: {
variables: { id: router.query.id },
pollInterval: 1000,
nextFetchPolicy: 'cache-and-network'
})
2021-05-13 01:51:37 +00:00
if (error) return <div>error</div>
if (!data || loading) {
2021-05-13 21:19:51 +00:00
return <WithdrawlSkeleton status='loading' />
}
const TryMaxFee = () =>
<Link href='/wallet?type=withdraw' className='text-reset text-underline'>
2023-07-24 18:35:05 +00:00
<small className='ms-3'>try increasing max fee</small>
</Link>
2021-05-13 21:19:51 +00:00
let status = 'pending'
let variant = 'default'
switch (data.withdrawl.status) {
case 'CONFIRMED':
status = `sent ${numWithUnits(data.withdrawl.satsPaid, { abbreviate: false })} with ${numWithUnits(data.withdrawl.satsFeePaid, { abbreviate: false })} in routing fees`
2021-05-13 21:19:51 +00:00
variant = 'confirmed'
break
case 'INSUFFICIENT_BALANCE':
2023-07-24 18:35:05 +00:00
status = <>insufficient balance <small className='ms-3'>contact keyan!</small></>
2021-05-13 21:19:51 +00:00
variant = 'failed'
break
case 'INVALID_PAYMENT':
status = 'invalid invoice'
variant = 'failed'
break
case 'PATHFINDING_TIMEOUT':
status = <>timed out finding route <TryMaxFee /></>
2021-05-13 21:19:51 +00:00
variant = 'failed'
break
case 'ROUTE_NOT_FOUND':
status = <>no route <TryMaxFee /></>
2021-05-13 21:19:51 +00:00
variant = 'failed'
break
2022-01-05 20:37:34 +00:00
case 'UNKNOWN_FAILURE':
status = <>unknown error</>
variant = 'failed'
break
2021-05-13 21:19:51 +00:00
default:
break
2021-05-13 01:51:37 +00:00
}
2021-05-13 13:28:38 +00:00
return (
<>
<div className='w-100'>
<CopyInput
label='invoice' type='text'
placeholder={data.withdrawl.bolt11 || 'deleted'} readOnly noForm
2021-05-13 13:28:38 +00:00
/>
</div>
<div className='w-100'>
<Input
label='max fee' type='text'
2022-08-18 18:15:24 +00:00
placeholder={data.withdrawl.satsFeePaying} readOnly noForm
2021-05-13 21:19:51 +00:00
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
2021-05-13 13:28:38 +00:00
/>
</div>
2021-05-13 21:19:51 +00:00
<InvoiceStatus variant={variant} status={status} />
<Bolt11Info bolt11={data.withdrawl.bolt11}>
<PrivacyOption wd={data.withdrawl} />
</Bolt11Info>
2021-05-13 13:28:38 +00:00
</>
)
2021-05-13 01:51:37 +00:00
}
function PrivacyOption ({ wd }) {
if (!wd.bolt11) return
const me = useMe()
const keepUntil = datePivot(new Date(wd.createdAt), { days: INVOICE_RETENTION_DAYS })
const oldEnough = new Date() >= keepUntil
if (!oldEnough) {
return (
<span className='text-muted fst-italic'>
{`this invoice ${me.privates?.autoDropBolt11s ? 'will be auto-deleted' : 'can be deleted'} in ${timeLeft(keepUntil)}`}
</span>
)
}
const showModal = useShowModal()
const toaster = useToast()
const [dropBolt11] = useMutation(
gql`
mutation dropBolt11($id: ID!) {
dropBolt11(id: $id) {
id
}
}`, {
update (cache) {
cache.modify({
id: `Withdrawl:${wd.id}`,
fields: {
bolt11: () => null,
hash: () => null
}
})
}
})
return (
<span
className='btn btn-md btn-danger' onClick={() => {
showModal(onClose => {
return (
<DeleteConfirm
type='invoice'
onConfirm={async () => {
if (me) {
try {
await dropBolt11({ variables: { id: wd.id } })
} catch (err) {
toaster.danger('unable to delete invoice: ' + err.message || err.toString?.())
throw err
} finally {
onClose()
}
}
}}
/>
)
})
}}
>delete invoice
</span>
)
}