Allow pay per invoice for stackers

The modal which pops up if the stacker does not have enough sats now has two options: "fund wallet" and "pay invoice"
This commit is contained in:
ekzyis 2023-07-21 00:34:39 +02:00
parent fd8510d59f
commit 853a389b65
8 changed files with 74 additions and 17 deletions

View File

@ -14,7 +14,6 @@ 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 { useAnonymous } from '../lib/anonymous'
import { ANON_POST_FEE } from '../lib/constants'
export function DiscussionForm ({ export function DiscussionForm ({
item, sub, editThreshold, titleLabel = 'title', item, sub, editThreshold, titleLabel = 'title',
@ -79,8 +78,8 @@ export function DiscussionForm ({
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}
onSubmit={handleSubmit || (async ({ boost, ...values }) => { onSubmit={handleSubmit || (async ({ boost, cost, ...values }) => {
await anonUpsertDiscussion(ANON_POST_FEE, boost, values) await anonUpsertDiscussion(cost, boost, values)
})} })}
storageKeyPrefix={item ? undefined : 'discussion'} storageKeyPrefix={item ? undefined : 'discussion'}
> >

View File

@ -6,6 +6,7 @@ import { gql, useQuery } from '@apollo/client'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { useMe } from './me' import { useMe } from './me'
import { ANON_COMMENT_FEE, ANON_POST_FEE } from '../lib/constants' import { ANON_COMMENT_FEE, ANON_POST_FEE } from '../lib/constants'
import { useEffect } from 'react'
function Receipt ({ cost, repetition, hasImgLink, baseFee, parentId, boost }) { function Receipt ({ cost, repetition, hasImgLink, baseFee, parentId, boost }) {
return ( return (
@ -53,6 +54,10 @@ export default function FeeButton ({ parentId, hasImgLink, baseFee, ChildButton,
const boost = Number(formik?.values?.boost) || 0 const boost = Number(formik?.values?.boost) || 0
const cost = baseFee * (hasImgLink ? 10 : 1) * Math.pow(10, repetition) + Number(boost) const cost = baseFee * (hasImgLink ? 10 : 1) * Math.pow(10, repetition) + Number(boost)
useEffect(() => {
formik.setFieldValue('cost', cost)
}, [cost])
const show = alwaysShow || !formik?.isSubmitting const show = alwaysShow || !formik?.isSubmitting
return ( return (
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>

View File

@ -1,14 +1,18 @@
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'
export default function FundError ({ onClose }) { export default function FundError ({ onClose, amount, onPayment }) {
const anonPayment = useAnonymous(onPayment, { forceInvoice: true })
return ( return (
<> <>
<p className='fw-bolder'>you need more sats</p> <p className='fw-bolder'>you need more sats</p>
<div className='d-flex justify-content-end'> <div className='d-flex justify-content-end'>
<Link href='/wallet?type=fund'> <Link href='/wallet?type=fund'>
<Button variant='success' onClick={onClose}>fund</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>
<Button variant='success' onClick={() => anonPayment(amount)}>pay invoice</Button>
</div> </div>
</> </>
) )

View File

@ -6,6 +6,8 @@ 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 { useAnonymous } from '../lib/anonymous'
import { useShowModal } from './modal'
import FundError from './fund-error'
const defaultTips = [100, 1000, 10000, 100000] const defaultTips = [100, 1000, 10000, 100000]
@ -41,6 +43,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
const inputRef = useRef(null) const inputRef = useRef(null)
const me = useMe() const me = useMe()
const [oValue, setOValue] = useState() const [oValue, setOValue] = useState()
const showModal = useShowModal()
useEffect(() => { useEffect(() => {
inputRef.current?.focus() inputRef.current?.focus()
@ -75,7 +78,23 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
}} }}
schema={amountSchema} schema={amountSchema}
onSubmit={async ({ amount }) => { onSubmit={async ({ amount }) => {
await anonAct(amount) try {
await anonAct(amount)
} catch (error) {
if (error.toString().includes('insufficient funds')) {
showModal(onClose => {
return (
<FundError
onClose={onClose}
amount={amount}
onPayment={submitAct}
/>
)
})
return
}
throw new Error({ message: error.toString() })
}
}} }}
> >
<Input <Input

View File

@ -15,7 +15,6 @@ 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 { useAnonymous } from '../lib/anonymous'
import { ANON_POST_FEE } from '../lib/constants'
export function LinkForm ({ item, sub, editThreshold, children }) { export function LinkForm ({ item, sub, editThreshold, children }) {
const router = useRouter() const router = useRouter()
@ -119,8 +118,8 @@ export function LinkForm ({ item, sub, editThreshold, children }) {
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}
onSubmit={async ({ boost, title, ...values }) => { onSubmit={async ({ boost, title, cost, ...values }) => {
await anonUpsertLink(ANON_POST_FEE, boost, title, values) await anonUpsertLink(cost, boost, title, values)
}} }}
storageKeyPrefix={item ? undefined : 'link'} storageKeyPrefix={item ? undefined : 'link'}
> >

View File

@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
import { gql, useApolloClient, useMutation } from '@apollo/client' import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown' import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form' import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import { ANON_POST_FEE, MAX_POLL_NUM_CHOICES } from '../lib/constants' import { MAX_POLL_NUM_CHOICES } from '../lib/constants'
import FeeButton, { EditFeeButton } from './fee-button' import FeeButton, { EditFeeButton } from './fee-button'
import Delete from './delete' import Delete from './delete'
import Button from 'react-bootstrap/Button' import Button from 'react-bootstrap/Button'
@ -68,8 +68,8 @@ export function PollForm ({ item, sub, editThreshold, children }) {
...SubSelectInitial({ sub: item?.subName || sub?.name }) ...SubSelectInitial({ sub: item?.subName || sub?.name })
}} }}
schema={schema} schema={schema}
onSubmit={async ({ boost, title, options, ...values }) => { onSubmit={async ({ boost, title, options, cost, ...values }) => {
await anonUpsertPoll(ANON_POST_FEE, boost, title, options, values) await anonUpsertPoll(cost, boost, title, options, values)
}} }}
storageKeyPrefix={item ? undefined : 'poll'} storageKeyPrefix={item ? undefined : 'poll'}
> >

View File

@ -163,10 +163,11 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
if (pendingSats > 0) { if (pendingSats > 0) {
timerRef.current = setTimeout(async (sats) => { timerRef.current = setTimeout(async (sats) => {
const variables = { id: item.id, sats: pendingSats }
try { try {
timerRef.current && setPendingSats(0) timerRef.current && setPendingSats(0)
await act({ await act({
variables: { id: item.id, sats }, variables,
optimisticResponse: { optimisticResponse: {
act: { act: {
sats sats
@ -178,7 +179,15 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
if (error.toString().includes('insufficient funds')) { if (error.toString().includes('insufficient funds')) {
showModal(onClose => { showModal(onClose => {
return <FundError onClose={onClose} /> return (
<FundError
onClose={onClose}
amount={pendingSats}
onPayment={async (_, invoiceHash) => {
await act({ variables: { ...variables, invoiceHash } })
}}
/>
)
}) })
return return
} }

View File

@ -7,6 +7,7 @@ import { Invoice as QrInvoice } from '../components/invoice'
import { QrSkeleton } from '../components/qr' import { QrSkeleton } from '../components/qr'
import { useMe } from '../components/me' import { useMe } from '../components/me'
import { msatsToSats } from './format' import { msatsToSats } from './format'
import FundError from '../components/fund-error'
import { INVOICE } from '../fragments/wallet' import { INVOICE } from '../fragments/wallet'
const Invoice = ({ id, ...props }) => { const Invoice = ({ id, ...props }) => {
@ -24,7 +25,10 @@ const Invoice = ({ id, ...props }) => {
return <QrInvoice invoice={data.invoice} {...props} /> return <QrInvoice invoice={data.invoice} {...props} />
} }
export const useAnonymous = (fn) => { const defaultOptions = {
forceInvoice: false
}
export const useAnonymous = (fn, options = defaultOptions) => {
const me = useMe() const me = useMe()
const [createInvoice, { data }] = useMutation(gql` const [createInvoice, { data }] = useMutation(gql`
mutation createInvoice($amount: Int!) { mutation createInvoice($amount: Int!) {
@ -55,8 +59,26 @@ export const useAnonymous = (fn) => {
} }
}, [invoice?.id]) }, [invoice?.id])
const anonFn = useCallback((amount, ...args) => { const anonFn = useCallback(async (amount, ...args) => {
if (me) return fn(amount, ...args) if (me && !options.forceInvoice) {
try {
return await fn(amount, ...args)
} catch (error) {
if (error.toString().includes('insufficient funds')) {
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) setFnArgs(args)
return createInvoice({ variables: { amount } }) return createInvoice({ variables: { amount } })
}, [fn, setFnArgs, createInvoice]) }, [fn, setFnArgs, createInvoice])