Rename to useInvoiceable
This commit is contained in:
		
							parent
							
								
									38dbbd5a4f
								
							
						
					
					
						commit
						318088179a
					
				@ -17,7 +17,6 @@ import { amountSchema, bountySchema, commentSchema, discussionSchema, jobSchema,
 | 
				
			|||||||
import { sendUserNotification } from '../webPush'
 | 
					import { sendUserNotification } from '../webPush'
 | 
				
			||||||
import { proxyImages } from './imgproxy'
 | 
					import { proxyImages } from './imgproxy'
 | 
				
			||||||
import { defaultCommentSort } from '../../lib/item'
 | 
					import { defaultCommentSort } from '../../lib/item'
 | 
				
			||||||
import { checkInvoice } from '../../lib/anonymous'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function commentFilterClause (me, models) {
 | 
					export async function commentFilterClause (me, models) {
 | 
				
			||||||
  let clause = ` AND ("Item"."weightedVotes" - "Item"."weightedDownVotes" > -${ITEM_FILTER_THRESHOLD}`
 | 
					  let clause = ` AND ("Item"."weightedVotes" - "Item"."weightedDownVotes" > -${ITEM_FILTER_THRESHOLD}`
 | 
				
			||||||
@ -38,6 +37,25 @@ export async function commentFilterClause (me, models) {
 | 
				
			|||||||
  return clause
 | 
					  return clause
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function checkInvoice (models, invoiceHash, fee) {
 | 
				
			||||||
 | 
					  const invoice = await models.invoice.findUnique({
 | 
				
			||||||
 | 
					    where: { hash: invoiceHash },
 | 
				
			||||||
 | 
					    include: {
 | 
				
			||||||
 | 
					      user: true
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  if (!invoice) {
 | 
				
			||||||
 | 
					    throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!invoice.msatsReceived) {
 | 
				
			||||||
 | 
					    throw new GraphQLError('invoice was not paid', { extensions: { code: 'BAD_INPUT' } })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (msatsToSats(invoice.msatsReceived) < fee) {
 | 
				
			||||||
 | 
					    throw new GraphQLError('invoice amount too low', { extensions: { code: 'BAD_INPUT' } })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return invoice
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function comments (me, models, id, sort) {
 | 
					async function comments (me, models, id, sort) {
 | 
				
			||||||
  let orderBy
 | 
					  let orderBy
 | 
				
			||||||
  switch (sort) {
 | 
					  switch (sort) {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ import { bountySchema } from '../lib/validate'
 | 
				
			|||||||
import { SubSelectInitial } from './sub-select-form'
 | 
					import { SubSelectInitial } from './sub-select-form'
 | 
				
			||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function BountyForm ({
 | 
					export function BountyForm ({
 | 
				
			||||||
  item,
 | 
					  item,
 | 
				
			||||||
@ -75,7 +75,7 @@ export function BountyForm ({
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }, [upsertBounty, router])
 | 
					    }, [upsertBounty, router])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonUpsertBounty = useAnonymous(submitUpsertBounty, { requireSession: true })
 | 
					  const invoiceableUpsertBounty = useInvoiceable(submitUpsertBounty, { requireSession: true })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Form
 | 
					    <Form
 | 
				
			||||||
@ -90,7 +90,7 @@ export function BountyForm ({
 | 
				
			|||||||
      onSubmit={
 | 
					      onSubmit={
 | 
				
			||||||
        handleSubmit ||
 | 
					        handleSubmit ||
 | 
				
			||||||
        (async ({ boost, bounty, cost, ...values }) => {
 | 
					        (async ({ boost, bounty, cost, ...values }) => {
 | 
				
			||||||
          await anonUpsertBounty(cost, boost, bounty, values)
 | 
					          await invoiceableUpsertBounty(cost, boost, bounty, values)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      storageKeyPrefix={item ? undefined : 'bounty'}
 | 
					      storageKeyPrefix={item ? undefined : 'bounty'}
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ import { discussionSchema } from '../lib/validate'
 | 
				
			|||||||
import { SubSelectInitial } from './sub-select-form'
 | 
					import { SubSelectInitial } from './sub-select-form'
 | 
				
			||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DiscussionForm ({
 | 
					export function DiscussionForm ({
 | 
				
			||||||
  item, sub, editThreshold, titleLabel = 'title',
 | 
					  item, sub, editThreshold, titleLabel = 'title',
 | 
				
			||||||
@ -53,7 +53,7 @@ export function DiscussionForm ({
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }, [upsertDiscussion, router])
 | 
					    }, [upsertDiscussion, router])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonUpsertDiscussion = useAnonymous(submitUpsertDiscussion)
 | 
					  const invoiceableUpsertDiscussion = useInvoiceable(submitUpsertDiscussion)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const [getRelated, { data: relatedData }] = useLazyQuery(gql`
 | 
					  const [getRelated, { data: relatedData }] = useLazyQuery(gql`
 | 
				
			||||||
    ${ITEM_FIELDS}
 | 
					    ${ITEM_FIELDS}
 | 
				
			||||||
@ -79,7 +79,7 @@ export function DiscussionForm ({
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      schema={schema}
 | 
					      schema={schema}
 | 
				
			||||||
      onSubmit={handleSubmit || (async ({ boost, cost, ...values }) => {
 | 
					      onSubmit={handleSubmit || (async ({ boost, cost, ...values }) => {
 | 
				
			||||||
        await anonUpsertDiscussion(cost, boost, values)
 | 
					        await invoiceableUpsertDiscussion(cost, boost, values)
 | 
				
			||||||
      })}
 | 
					      })}
 | 
				
			||||||
      storageKeyPrefix={item ? undefined : 'discussion'}
 | 
					      storageKeyPrefix={item ? undefined : 'discussion'}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
				
			|||||||
@ -1,9 +1,9 @@
 | 
				
			|||||||
import Link from 'next/link'
 | 
					import Link from 'next/link'
 | 
				
			||||||
import Button from 'react-bootstrap/Button'
 | 
					import Button from 'react-bootstrap/Button'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default function FundError ({ onClose, amount, onPayment }) {
 | 
					export default function FundError ({ onClose, amount, onPayment }) {
 | 
				
			||||||
  const anonPayment = useAnonymous(onPayment, { forceInvoice: true })
 | 
					  const createInvoice = useInvoiceable(onPayment, { forceInvoice: true })
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
      <p className='fw-bolder'>you need more sats</p>
 | 
					      <p className='fw-bolder'>you need more sats</p>
 | 
				
			||||||
@ -12,8 +12,15 @@ export default function FundError ({ onClose, amount, onPayment }) {
 | 
				
			|||||||
          <Button variant='success' onClick={onClose}>fund wallet</Button>
 | 
					          <Button variant='success' onClick={onClose}>fund wallet</Button>
 | 
				
			||||||
        </Link>
 | 
					        </Link>
 | 
				
			||||||
        <span className='d-flex mx-3 font-weight-bold text-muted align-items-center'>or</span>
 | 
					        <span className='d-flex mx-3 font-weight-bold text-muted align-items-center'>or</span>
 | 
				
			||||||
        <Button variant='success' onClick={() => anonPayment(amount)}>pay invoice</Button>
 | 
					        <Button variant='success' onClick={() => createInvoice(amount)}>pay invoice</Button>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </>
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const isInsufficientFundsError = (error) => {
 | 
				
			||||||
 | 
					  if (Array.isArray(error)) {
 | 
				
			||||||
 | 
					    return error.some(({ message }) => message.includes('insufficient funds'))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return error.toString().includes('insufficient funds')
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,17 @@
 | 
				
			|||||||
import AccordianItem from './accordian-item'
 | 
					import { useState, useCallback, useEffect } from 'react'
 | 
				
			||||||
import Qr from './qr'
 | 
					import { useMutation, useQuery } from '@apollo/client'
 | 
				
			||||||
 | 
					import { Button } from 'react-bootstrap'
 | 
				
			||||||
 | 
					import { gql } from 'graphql-tag'
 | 
				
			||||||
import { numWithUnits } from '../lib/format'
 | 
					import { numWithUnits } from '../lib/format'
 | 
				
			||||||
 | 
					import AccordianItem from './accordian-item'
 | 
				
			||||||
 | 
					import Qr, { QrSkeleton } from './qr'
 | 
				
			||||||
 | 
					import { CopyInput } from './form'
 | 
				
			||||||
 | 
					import { INVOICE } from '../fragments/wallet'
 | 
				
			||||||
 | 
					import InvoiceStatus from './invoice-status'
 | 
				
			||||||
 | 
					import { useMe } from './me'
 | 
				
			||||||
 | 
					import { useShowModal } from './modal'
 | 
				
			||||||
 | 
					import { sleep } from '../lib/time'
 | 
				
			||||||
 | 
					import FundError, { isInsufficientFundsError } from './fund-error'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Invoice ({ invoice, onConfirmation, successVerb }) {
 | 
					export function Invoice ({ invoice, onConfirmation, successVerb }) {
 | 
				
			||||||
  let variant = 'default'
 | 
					  let variant = 'default'
 | 
				
			||||||
@ -43,3 +54,176 @@ export function Invoice ({ invoice, onConfirmation, successVerb }) {
 | 
				
			|||||||
    </>
 | 
					    </>
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Contacts = ({ invoiceHash }) => {
 | 
				
			||||||
 | 
					  const subject = `Support request for payment hash: ${invoiceHash}`
 | 
				
			||||||
 | 
					  const body = 'Hi, I successfully paid for <insert action> but the action did not work.'
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className='d-flex flex-column justify-content-center'>
 | 
				
			||||||
 | 
					      <span>Payment hash</span>
 | 
				
			||||||
 | 
					      <div className='w-100'>
 | 
				
			||||||
 | 
					        <CopyInput type='text' placeholder={invoiceHash} readOnly noForm />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className='d-flex flex-row justify-content-center'>
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href={`mailto:kk@stacker.news?subject=${subject}&body=${body}`} className='nav-link p-0 d-inline-flex'
 | 
				
			||||||
 | 
					          target='_blank' rel='noreferrer'
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          e-mail
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <span className='mx-2 text-muted'> \ </span>
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href='https://tribes.sphinx.chat/t/stackerzchat' className='nav-link p-0 d-inline-flex'
 | 
				
			||||||
 | 
					          target='_blank' rel='noreferrer'
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          sphinx
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <span className='mx-2 text-muted'> \ </span>
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href='https://t.me/k00bideh' className='nav-link p-0 d-inline-flex'
 | 
				
			||||||
 | 
					          target='_blank' rel='noreferrer'
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          telegram
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					        <span className='mx-2 text-muted'> \ </span>
 | 
				
			||||||
 | 
					        <a
 | 
				
			||||||
 | 
					          href='https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FebLYaEFGjsD3uK4fpE326c5QI1RZSxau%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAV086Oj5yCsavWzIbRMCVuF6jq793Tt__rWvCec__viI%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22cZwSGoQhyOUulzp7rwCdWQ%3D%3D%22%7D' className='nav-link p-0 d-inline-flex'
 | 
				
			||||||
 | 
					          target='_blank' rel='noreferrer'
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          simplex
 | 
				
			||||||
 | 
					        </a>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ActionInvoice = ({ id, hash, errorCount, repeat, ...props }) => {
 | 
				
			||||||
 | 
					  const { data, loading, error } = useQuery(INVOICE, {
 | 
				
			||||||
 | 
					    pollInterval: 1000,
 | 
				
			||||||
 | 
					    variables: { id }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  if (error) {
 | 
				
			||||||
 | 
					    if (error.message?.includes('invoice not found')) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return <div>error</div>
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  if (!data || loading) {
 | 
				
			||||||
 | 
					    return <QrSkeleton status='loading' />
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let errorStatus = 'Something went wrong. Please try again.'
 | 
				
			||||||
 | 
					  if (errorCount > 1) {
 | 
				
			||||||
 | 
					    errorStatus = 'Something still went wrong.\nPlease contact admins for support or to request a refund.'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <>
 | 
				
			||||||
 | 
					      <Invoice invoice={data.invoice} {...props} />
 | 
				
			||||||
 | 
					      {errorCount > 0
 | 
				
			||||||
 | 
					        ? (
 | 
				
			||||||
 | 
					          <>
 | 
				
			||||||
 | 
					            <InvoiceStatus variant='failed' status={errorStatus} />
 | 
				
			||||||
 | 
					            {errorCount === 1
 | 
				
			||||||
 | 
					              ? <div className='d-flex flex-row mt-3 justify-content-center'><Button variant='info' onClick={repeat}>Retry</Button></div>
 | 
				
			||||||
 | 
					              : <Contacts invoiceHash={hash} />}
 | 
				
			||||||
 | 
					          </>
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					        : null}
 | 
				
			||||||
 | 
					    </>
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const defaultOptions = {
 | 
				
			||||||
 | 
					  forceInvoice: false,
 | 
				
			||||||
 | 
					  requireSession: false
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export const useInvoiceable = (fn, options = defaultOptions) => {
 | 
				
			||||||
 | 
					  const me = useMe()
 | 
				
			||||||
 | 
					  const [createInvoice, { data }] = useMutation(gql`
 | 
				
			||||||
 | 
					    mutation createInvoice($amount: Int!) {
 | 
				
			||||||
 | 
					      createInvoice(amount: $amount) {
 | 
				
			||||||
 | 
					        id
 | 
				
			||||||
 | 
					        hash
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }`)
 | 
				
			||||||
 | 
					  const showModal = useShowModal()
 | 
				
			||||||
 | 
					  const [fnArgs, setFnArgs] = useState()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice
 | 
				
			||||||
 | 
					  let called = false
 | 
				
			||||||
 | 
					  let errorCount = 0
 | 
				
			||||||
 | 
					  const onConfirmation = useCallback(
 | 
				
			||||||
 | 
					    onClose => {
 | 
				
			||||||
 | 
					      called = false
 | 
				
			||||||
 | 
					      return async ({ id, satsReceived, hash }) => {
 | 
				
			||||||
 | 
					        if (called) return
 | 
				
			||||||
 | 
					        called = true
 | 
				
			||||||
 | 
					        await sleep(2000)
 | 
				
			||||||
 | 
					        const repeat = () =>
 | 
				
			||||||
 | 
					          fn(satsReceived, ...fnArgs, hash)
 | 
				
			||||||
 | 
					            .then(onClose)
 | 
				
			||||||
 | 
					            .catch((error) => {
 | 
				
			||||||
 | 
					              console.error(error)
 | 
				
			||||||
 | 
					              errorCount++
 | 
				
			||||||
 | 
					              onClose()
 | 
				
			||||||
 | 
					              showModal(onClose => (
 | 
				
			||||||
 | 
					                <ActionInvoice
 | 
				
			||||||
 | 
					                  id={id}
 | 
				
			||||||
 | 
					                  hash={hash}
 | 
				
			||||||
 | 
					                  onConfirmation={onConfirmation(onClose)}
 | 
				
			||||||
 | 
					                  successVerb='received'
 | 
				
			||||||
 | 
					                  errorCount={errorCount}
 | 
				
			||||||
 | 
					                  repeat={repeat}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              ), { keepOpen: true })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        // prevents infinite loop of calling `onConfirmation`
 | 
				
			||||||
 | 
					        if (errorCount === 0) await repeat()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }, [fn, fnArgs]
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const invoice = data?.createInvoice
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    if (invoice) {
 | 
				
			||||||
 | 
					      showModal(onClose => (
 | 
				
			||||||
 | 
					        <ActionInvoice
 | 
				
			||||||
 | 
					          id={invoice.id}
 | 
				
			||||||
 | 
					          hash={invoice.hash}
 | 
				
			||||||
 | 
					          onConfirmation={onConfirmation(onClose)}
 | 
				
			||||||
 | 
					          successVerb='received'
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      ), { keepOpen: true }
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }, [invoice?.id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const actionFn = useCallback(async (amount, ...args) => {
 | 
				
			||||||
 | 
					    if (!me && options.requireSession) {
 | 
				
			||||||
 | 
					      throw new Error('you must be logged in')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (me && !options.forceInvoice) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        return await fn(amount, ...args)
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        if (isInsufficientFundsError(error)) {
 | 
				
			||||||
 | 
					          showModal(onClose => {
 | 
				
			||||||
 | 
					            return (
 | 
				
			||||||
 | 
					              <FundError
 | 
				
			||||||
 | 
					                onClose={onClose}
 | 
				
			||||||
 | 
					                amount={amount}
 | 
				
			||||||
 | 
					                onPayment={async (_, invoiceHash) => { await fn(amount, ...args, invoiceHash) }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					          return
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        throw new Error({ message: error.toString() })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    setFnArgs(args)
 | 
				
			||||||
 | 
					    return createInvoice({ variables: { amount } })
 | 
				
			||||||
 | 
					  }, [fn, setFnArgs, createInvoice])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return actionFn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -5,7 +5,7 @@ import { Form, Input, SubmitButton } from './form'
 | 
				
			|||||||
import { useMe } from './me'
 | 
					import { useMe } from './me'
 | 
				
			||||||
import UpBolt from '../svgs/bolt.svg'
 | 
					import UpBolt from '../svgs/bolt.svg'
 | 
				
			||||||
import { amountSchema } from '../lib/validate'
 | 
					import { amountSchema } from '../lib/validate'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const defaultTips = [100, 1000, 10000, 100000]
 | 
					const defaultTips = [100, 1000, 10000, 100000]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -65,7 +65,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
 | 
				
			|||||||
      onClose()
 | 
					      onClose()
 | 
				
			||||||
    }, [act, onClose, strike, itemId])
 | 
					    }, [act, onClose, strike, itemId])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonAct = useAnonymous(submitAct)
 | 
					  const invoiceableAct = useInvoiceable(submitAct)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <Form
 | 
					    <Form
 | 
				
			||||||
@ -75,7 +75,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      schema={amountSchema}
 | 
					      schema={amountSchema}
 | 
				
			||||||
      onSubmit={async ({ amount }) => {
 | 
					      onSubmit={async ({ amount }) => {
 | 
				
			||||||
        await anonAct(amount)
 | 
					        await invoiceableAct(amount)
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <Input
 | 
					      <Input
 | 
				
			||||||
 | 
				
			|||||||
@ -17,7 +17,7 @@ import Avatar from './avatar'
 | 
				
			|||||||
import ActionTooltip from './action-tooltip'
 | 
					import ActionTooltip from './action-tooltip'
 | 
				
			||||||
import { jobSchema } from '../lib/validate'
 | 
					import { jobSchema } from '../lib/validate'
 | 
				
			||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function satsMin2Mo (minute) {
 | 
					function satsMin2Mo (minute) {
 | 
				
			||||||
  return minute * 30 * 24 * 60
 | 
					  return minute * 30 * 24 * 60
 | 
				
			||||||
@ -82,7 +82,7 @@ export default function JobForm ({ item, sub }) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }, [upsertJob, router])
 | 
					    }, [upsertJob, router])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonUpsertJob = useAnonymous(submitUpsertJob, { requireSession: true })
 | 
					  const invoiceableUpsertJob = useInvoiceable(submitUpsertJob, { requireSession: true })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
@ -102,7 +102,7 @@ export default function JobForm ({ item, sub }) {
 | 
				
			|||||||
        schema={jobSchema}
 | 
					        schema={jobSchema}
 | 
				
			||||||
        storageKeyPrefix={storageKeyPrefix}
 | 
					        storageKeyPrefix={storageKeyPrefix}
 | 
				
			||||||
        onSubmit={(async ({ maxBid, stop, start, ...values }) => {
 | 
					        onSubmit={(async ({ maxBid, stop, start, ...values }) => {
 | 
				
			||||||
          await anonUpsertJob(1000, maxBid, stop, start, values)
 | 
					          await invoiceableUpsertJob(1000, maxBid, stop, start, values)
 | 
				
			||||||
        })}
 | 
					        })}
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
        <div className='form-group'>
 | 
					        <div className='form-group'>
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ import { linkSchema } from '../lib/validate'
 | 
				
			|||||||
import Moon from '../svgs/moon-fill.svg'
 | 
					import Moon from '../svgs/moon-fill.svg'
 | 
				
			||||||
import { SubSelectInitial } from './sub-select-form'
 | 
					import { SubSelectInitial } from './sub-select-form'
 | 
				
			||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
					export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
@ -90,7 +90,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }, [upsertLink, router])
 | 
					    }, [upsertLink, router])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonUpsertLink = useAnonymous(submitUpsertLink)
 | 
					  const invoiceableUpsertLink = useInvoiceable(submitUpsertLink)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    if (data?.pageTitleAndUnshorted?.title) {
 | 
					    if (data?.pageTitleAndUnshorted?.title) {
 | 
				
			||||||
@ -119,7 +119,7 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      schema={schema}
 | 
					      schema={schema}
 | 
				
			||||||
      onSubmit={async ({ boost, title, cost, ...values }) => {
 | 
					      onSubmit={async ({ boost, title, cost, ...values }) => {
 | 
				
			||||||
        await anonUpsertLink(cost, boost, title, values)
 | 
					        await invoiceableUpsertLink(cost, boost, title, values)
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      storageKeyPrefix={item ? undefined : 'link'}
 | 
					      storageKeyPrefix={item ? undefined : 'link'}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ import { pollSchema } from '../lib/validate'
 | 
				
			|||||||
import { SubSelectInitial } from './sub-select-form'
 | 
					import { SubSelectInitial } from './sub-select-form'
 | 
				
			||||||
import CancelButton from './cancel-button'
 | 
					import CancelButton from './cancel-button'
 | 
				
			||||||
import { useCallback } from 'react'
 | 
					import { useCallback } from 'react'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function PollForm ({ item, sub, editThreshold, children }) {
 | 
					export function PollForm ({ item, sub, editThreshold, children }) {
 | 
				
			||||||
  const router = useRouter()
 | 
					  const router = useRouter()
 | 
				
			||||||
@ -54,7 +54,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }, [upsertPoll, router])
 | 
					    }, [upsertPoll, router])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonUpsertPoll = useAnonymous(submitUpsertPoll)
 | 
					  const invoiceableUpsertPoll = useInvoiceable(submitUpsertPoll)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const initialOptions = item?.poll?.options.map(i => i.option)
 | 
					  const initialOptions = item?.poll?.options.map(i => i.option)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,7 +69,7 @@ export function PollForm ({ item, sub, editThreshold, children }) {
 | 
				
			|||||||
      }}
 | 
					      }}
 | 
				
			||||||
      schema={schema}
 | 
					      schema={schema}
 | 
				
			||||||
      onSubmit={async ({ boost, title, options, cost, ...values }) => {
 | 
					      onSubmit={async ({ boost, title, options, cost, ...values }) => {
 | 
				
			||||||
        await anonUpsertPoll(cost, boost, title, options, values)
 | 
					        await invoiceableUpsertPoll(cost, boost, title, options, values)
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      storageKeyPrefix={item ? undefined : 'poll'}
 | 
					      storageKeyPrefix={item ? undefined : 'poll'}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
				
			|||||||
@ -9,7 +9,7 @@ import FeeButton from './fee-button'
 | 
				
			|||||||
import { commentsViewedAfterComment } from '../lib/new-comments'
 | 
					import { commentsViewedAfterComment } from '../lib/new-comments'
 | 
				
			||||||
import { commentSchema } from '../lib/validate'
 | 
					import { commentSchema } from '../lib/validate'
 | 
				
			||||||
import Info from './info'
 | 
					import Info from './info'
 | 
				
			||||||
import { useAnonymous } from '../lib/anonymous'
 | 
					import { useInvoiceable } from './invoice'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ReplyOnAnotherPage ({ parentId }) {
 | 
					export function ReplyOnAnotherPage ({ parentId }) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@ -101,7 +101,7 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
 | 
				
			|||||||
      setReply(replyOpen || false)
 | 
					      setReply(replyOpen || false)
 | 
				
			||||||
    }, [createComment, setReply])
 | 
					    }, [createComment, setReply])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const anonCreateComment = useAnonymous(submitComment)
 | 
					  const invoiceableCreateComment = useInvoiceable(submitComment)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const replyInput = useRef(null)
 | 
					  const replyInput = useRef(null)
 | 
				
			||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
@ -130,7 +130,7 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
 | 
				
			|||||||
            }}
 | 
					            }}
 | 
				
			||||||
            schema={commentSchema}
 | 
					            schema={commentSchema}
 | 
				
			||||||
            onSubmit={async ({ cost, ...values }, { resetForm }) => {
 | 
					            onSubmit={async ({ cost, ...values }, { resetForm }) => {
 | 
				
			||||||
              await anonCreateComment(cost, values, parentId, resetForm)
 | 
					              await invoiceableCreateComment(cost, values, parentId, resetForm)
 | 
				
			||||||
            }}
 | 
					            }}
 | 
				
			||||||
            storageKeyPrefix={'reply-' + parentId}
 | 
					            storageKeyPrefix={'reply-' + parentId}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import UpBolt from '../svgs/bolt.svg'
 | 
					import UpBolt from '../svgs/bolt.svg'
 | 
				
			||||||
import styles from './upvote.module.css'
 | 
					import styles from './upvote.module.css'
 | 
				
			||||||
import { gql, useMutation } from '@apollo/client'
 | 
					import { gql, useMutation } from '@apollo/client'
 | 
				
			||||||
import FundError from './fund-error'
 | 
					import FundError, { isInsufficientFundsError } from './fund-error'
 | 
				
			||||||
import ActionTooltip from './action-tooltip'
 | 
					import ActionTooltip from './action-tooltip'
 | 
				
			||||||
import ItemAct from './item-act'
 | 
					import ItemAct from './item-act'
 | 
				
			||||||
import { useMe } from './me'
 | 
					import { useMe } from './me'
 | 
				
			||||||
@ -12,7 +12,6 @@ import Overlay from 'react-bootstrap/Overlay'
 | 
				
			|||||||
import Popover from 'react-bootstrap/Popover'
 | 
					import Popover from 'react-bootstrap/Popover'
 | 
				
			||||||
import { useShowModal } from './modal'
 | 
					import { useShowModal } from './modal'
 | 
				
			||||||
import { LightningConsumer, useLightning } from './lightning'
 | 
					import { LightningConsumer, useLightning } from './lightning'
 | 
				
			||||||
import { isInsufficientFundsError } from '../lib/anonymous'
 | 
					 | 
				
			||||||
import { numWithUnits } from '../lib/format'
 | 
					import { numWithUnits } from '../lib/format'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getColor = (meSats) => {
 | 
					const getColor = (meSats) => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										214
									
								
								lib/anonymous.js
									
									
									
									
									
								
							
							
						
						
									
										214
									
								
								lib/anonymous.js
									
									
									
									
									
								
							@ -1,214 +0,0 @@
 | 
				
			|||||||
import { useMutation, useQuery } from '@apollo/client'
 | 
					 | 
				
			||||||
import { GraphQLError } from 'graphql'
 | 
					 | 
				
			||||||
import { gql } from 'graphql-tag'
 | 
					 | 
				
			||||||
import { useCallback, useEffect, useState } from 'react'
 | 
					 | 
				
			||||||
import { useShowModal } from '../components/modal'
 | 
					 | 
				
			||||||
import { Invoice as QrInvoice } from '../components/invoice'
 | 
					 | 
				
			||||||
import { QrSkeleton } from '../components/qr'
 | 
					 | 
				
			||||||
import { useMe } from '../components/me'
 | 
					 | 
				
			||||||
import { msatsToSats } from './format'
 | 
					 | 
				
			||||||
import FundError from '../components/fund-error'
 | 
					 | 
				
			||||||
import { INVOICE } from '../fragments/wallet'
 | 
					 | 
				
			||||||
import InvoiceStatus from '../components/invoice-status'
 | 
					 | 
				
			||||||
import { sleep } from './time'
 | 
					 | 
				
			||||||
import { Button } from 'react-bootstrap'
 | 
					 | 
				
			||||||
import { CopyInput } from '../components/form'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Contacts = ({ invoiceHash }) => {
 | 
					 | 
				
			||||||
  const subject = `Support request for payment hash: ${invoiceHash}`
 | 
					 | 
				
			||||||
  const body = 'Hi, I successfully paid for <insert action> but the action did not work.'
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <div className='d-flex flex-column justify-content-center'>
 | 
					 | 
				
			||||||
      <span>Payment hash</span>
 | 
					 | 
				
			||||||
      <div className='w-100'>
 | 
					 | 
				
			||||||
        <CopyInput type='text' placeholder={invoiceHash} readOnly noForm />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
      <div className='d-flex flex-row justify-content-center'>
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href={`mailto:kk@stacker.news?subject=${subject}&body=${body}`} className='nav-link p-0 d-inline-flex'
 | 
					 | 
				
			||||||
          target='_blank' rel='noreferrer'
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          e-mail
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
        <span className='mx-2 text-muted'> \ </span>
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href='https://tribes.sphinx.chat/t/stackerzchat' className='nav-link p-0 d-inline-flex'
 | 
					 | 
				
			||||||
          target='_blank' rel='noreferrer'
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          sphinx
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
        <span className='mx-2 text-muted'> \ </span>
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href='https://t.me/k00bideh' className='nav-link p-0 d-inline-flex'
 | 
					 | 
				
			||||||
          target='_blank' rel='noreferrer'
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          telegram
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
        <span className='mx-2 text-muted'> \ </span>
 | 
					 | 
				
			||||||
        <a
 | 
					 | 
				
			||||||
          href='https://simplex.chat/contact#/?v=1-2&smp=smp%3A%2F%2F6iIcWT_dF2zN_w5xzZEY7HI2Prbh3ldP07YTyDexPjE%3D%40smp10.simplex.im%2FebLYaEFGjsD3uK4fpE326c5QI1RZSxau%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAV086Oj5yCsavWzIbRMCVuF6jq793Tt__rWvCec__viI%253D%26srv%3Drb2pbttocvnbrngnwziclp2f4ckjq65kebafws6g4hy22cdaiv5dwjqd.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22cZwSGoQhyOUulzp7rwCdWQ%3D%3D%22%7D' className='nav-link p-0 d-inline-flex'
 | 
					 | 
				
			||||||
          target='_blank' rel='noreferrer'
 | 
					 | 
				
			||||||
        >
 | 
					 | 
				
			||||||
          simplex
 | 
					 | 
				
			||||||
        </a>
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Invoice = ({ id, hash, errorCount, repeat, ...props }) => {
 | 
					 | 
				
			||||||
  const { data, loading, error } = useQuery(INVOICE, {
 | 
					 | 
				
			||||||
    pollInterval: 1000,
 | 
					 | 
				
			||||||
    variables: { id }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  if (error) {
 | 
					 | 
				
			||||||
    if (error.message?.includes('invoice not found')) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return <div>error</div>
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!data || loading) {
 | 
					 | 
				
			||||||
    return <QrSkeleton status='loading' />
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  let errorStatus = 'Something went wrong. Please try again.'
 | 
					 | 
				
			||||||
  if (errorCount > 1) {
 | 
					 | 
				
			||||||
    errorStatus = 'Something still went wrong.\nPlease contact admins for support or to request a refund.'
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return (
 | 
					 | 
				
			||||||
    <>
 | 
					 | 
				
			||||||
      <QrInvoice invoice={data.invoice} {...props} />
 | 
					 | 
				
			||||||
      {errorCount > 0
 | 
					 | 
				
			||||||
        ? (
 | 
					 | 
				
			||||||
          <>
 | 
					 | 
				
			||||||
            <InvoiceStatus variant='failed' status={errorStatus} />
 | 
					 | 
				
			||||||
            {errorCount === 1
 | 
					 | 
				
			||||||
              ? <div className='d-flex flex-row mt-3 justify-content-center'><Button variant='info' onClick={repeat}>Retry</Button></div>
 | 
					 | 
				
			||||||
              : <Contacts invoiceHash={hash} />}
 | 
					 | 
				
			||||||
          </>
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        : null}
 | 
					 | 
				
			||||||
    </>
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const isInsufficientFundsError = (error) => {
 | 
					 | 
				
			||||||
  if (Array.isArray(error)) {
 | 
					 | 
				
			||||||
    return error.some(({ message }) => message.includes('insufficient funds'))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return error.toString().includes('insufficient funds')
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const defaultOptions = {
 | 
					 | 
				
			||||||
  forceInvoice: false,
 | 
					 | 
				
			||||||
  requireSession: false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
export const useAnonymous = (fn, options = defaultOptions) => {
 | 
					 | 
				
			||||||
  const me = useMe()
 | 
					 | 
				
			||||||
  const [createInvoice, { data }] = useMutation(gql`
 | 
					 | 
				
			||||||
    mutation createInvoice($amount: Int!) {
 | 
					 | 
				
			||||||
      createInvoice(amount: $amount) {
 | 
					 | 
				
			||||||
        id
 | 
					 | 
				
			||||||
        hash
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }`)
 | 
					 | 
				
			||||||
  const showModal = useShowModal()
 | 
					 | 
				
			||||||
  const [fnArgs, setFnArgs] = useState()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // fix for bug where `showModal` runs the code for two modals and thus executes `onConfirmation` twice
 | 
					 | 
				
			||||||
  let called = false
 | 
					 | 
				
			||||||
  let errorCount = 0
 | 
					 | 
				
			||||||
  const onConfirmation = useCallback(
 | 
					 | 
				
			||||||
    onClose => {
 | 
					 | 
				
			||||||
      called = false
 | 
					 | 
				
			||||||
      return async ({ id, satsReceived, hash }) => {
 | 
					 | 
				
			||||||
        if (called) return
 | 
					 | 
				
			||||||
        called = true
 | 
					 | 
				
			||||||
        await sleep(2000)
 | 
					 | 
				
			||||||
        const repeat = () =>
 | 
					 | 
				
			||||||
          fn(satsReceived, ...fnArgs, hash)
 | 
					 | 
				
			||||||
            .then(onClose)
 | 
					 | 
				
			||||||
            .catch((error) => {
 | 
					 | 
				
			||||||
              console.error(error)
 | 
					 | 
				
			||||||
              errorCount++
 | 
					 | 
				
			||||||
              onClose()
 | 
					 | 
				
			||||||
              showModal(onClose => (
 | 
					 | 
				
			||||||
                <Invoice
 | 
					 | 
				
			||||||
                  id={id}
 | 
					 | 
				
			||||||
                  hash={hash}
 | 
					 | 
				
			||||||
                  onConfirmation={onConfirmation(onClose)}
 | 
					 | 
				
			||||||
                  successVerb='received'
 | 
					 | 
				
			||||||
                  errorCount={errorCount}
 | 
					 | 
				
			||||||
                  repeat={repeat}
 | 
					 | 
				
			||||||
                />
 | 
					 | 
				
			||||||
              ), { keepOpen: true })
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
        // prevents infinite loop of calling `onConfirmation`
 | 
					 | 
				
			||||||
        if (errorCount === 0) await repeat()
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }, [fn, fnArgs]
 | 
					 | 
				
			||||||
  )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const invoice = data?.createInvoice
 | 
					 | 
				
			||||||
  useEffect(() => {
 | 
					 | 
				
			||||||
    if (invoice) {
 | 
					 | 
				
			||||||
      showModal(onClose => (
 | 
					 | 
				
			||||||
        <Invoice
 | 
					 | 
				
			||||||
          id={invoice.id}
 | 
					 | 
				
			||||||
          hash={invoice.hash}
 | 
					 | 
				
			||||||
          onConfirmation={onConfirmation(onClose)}
 | 
					 | 
				
			||||||
          successVerb='received'
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      ), { keepOpen: true }
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }, [invoice?.id])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const anonFn = useCallback(async (amount, ...args) => {
 | 
					 | 
				
			||||||
    if (!me && options.requireSession) {
 | 
					 | 
				
			||||||
      throw new Error('you must be logged in')
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (me && !options.forceInvoice) {
 | 
					 | 
				
			||||||
      try {
 | 
					 | 
				
			||||||
        return await fn(amount, ...args)
 | 
					 | 
				
			||||||
      } catch (error) {
 | 
					 | 
				
			||||||
        if (isInsufficientFundsError(error)) {
 | 
					 | 
				
			||||||
          showModal(onClose => {
 | 
					 | 
				
			||||||
            return (
 | 
					 | 
				
			||||||
              <FundError
 | 
					 | 
				
			||||||
                onClose={onClose}
 | 
					 | 
				
			||||||
                amount={amount}
 | 
					 | 
				
			||||||
                onPayment={async (_, invoiceHash) => { await fn(amount, ...args, invoiceHash) }}
 | 
					 | 
				
			||||||
              />
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          })
 | 
					 | 
				
			||||||
          return
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw new Error({ message: error.toString() })
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    setFnArgs(args)
 | 
					 | 
				
			||||||
    return createInvoice({ variables: { amount } })
 | 
					 | 
				
			||||||
  }, [fn, setFnArgs, createInvoice])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return anonFn
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const checkInvoice = async (models, invoiceHash, fee) => {
 | 
					 | 
				
			||||||
  const invoice = await models.invoice.findUnique({
 | 
					 | 
				
			||||||
    where: { hash: invoiceHash },
 | 
					 | 
				
			||||||
    include: {
 | 
					 | 
				
			||||||
      user: true
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  })
 | 
					 | 
				
			||||||
  if (!invoice) {
 | 
					 | 
				
			||||||
    throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (!invoice.msatsReceived) {
 | 
					 | 
				
			||||||
    throw new GraphQLError('invoice was not paid', { extensions: { code: 'BAD_INPUT' } })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (msatsToSats(invoice.msatsReceived) < fee) {
 | 
					 | 
				
			||||||
    throw new GraphQLError('invoice amount too low', { extensions: { code: 'BAD_INPUT' } })
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return invoice
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user