From 853a389b6543817e7f857bcaed404b9e3c195baf Mon Sep 17 00:00:00 2001 From: ekzyis Date: Fri, 21 Jul 2023 00:34:39 +0200 Subject: [PATCH] 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" --- components/discussion-form.js | 5 ++--- components/fee-button.js | 5 +++++ components/fund-error.js | 8 ++++++-- components/item-act.js | 21 ++++++++++++++++++++- components/link-form.js | 5 ++--- components/poll-form.js | 6 +++--- components/upvote.js | 13 +++++++++++-- lib/anonymous.js | 28 +++++++++++++++++++++++++--- 8 files changed, 74 insertions(+), 17 deletions(-) diff --git a/components/discussion-form.js b/components/discussion-form.js index 600b50bd..14271f73 100644 --- a/components/discussion-form.js +++ b/components/discussion-form.js @@ -14,7 +14,6 @@ import { SubSelectInitial } from './sub-select-form' import CancelButton from './cancel-button' import { useCallback } from 'react' import { useAnonymous } from '../lib/anonymous' -import { ANON_POST_FEE } from '../lib/constants' export function DiscussionForm ({ item, sub, editThreshold, titleLabel = 'title', @@ -79,8 +78,8 @@ export function DiscussionForm ({ ...SubSelectInitial({ sub: item?.subName || sub?.name }) }} schema={schema} - onSubmit={handleSubmit || (async ({ boost, ...values }) => { - await anonUpsertDiscussion(ANON_POST_FEE, boost, values) + onSubmit={handleSubmit || (async ({ boost, cost, ...values }) => { + await anonUpsertDiscussion(cost, boost, values) })} storageKeyPrefix={item ? undefined : 'discussion'} > diff --git a/components/fee-button.js b/components/fee-button.js index 1eae2506..f6daeac1 100644 --- a/components/fee-button.js +++ b/components/fee-button.js @@ -6,6 +6,7 @@ import { gql, useQuery } from '@apollo/client' import { useFormikContext } from 'formik' import { useMe } from './me' import { ANON_COMMENT_FEE, ANON_POST_FEE } from '../lib/constants' +import { useEffect } from 'react' function Receipt ({ cost, repetition, hasImgLink, baseFee, parentId, boost }) { return ( @@ -53,6 +54,10 @@ export default function FeeButton ({ parentId, hasImgLink, baseFee, ChildButton, const boost = Number(formik?.values?.boost) || 0 const cost = baseFee * (hasImgLink ? 10 : 1) * Math.pow(10, repetition) + Number(boost) + useEffect(() => { + formik.setFieldValue('cost', cost) + }, [cost]) + const show = alwaysShow || !formik?.isSubmitting return (
diff --git a/components/fund-error.js b/components/fund-error.js index e25cddfe..01297e8d 100644 --- a/components/fund-error.js +++ b/components/fund-error.js @@ -1,14 +1,18 @@ import Link from 'next/link' 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 ( <>

you need more sats

- + + or +
) diff --git a/components/item-act.js b/components/item-act.js index e5323fdf..84884bd3 100644 --- a/components/item-act.js +++ b/components/item-act.js @@ -6,6 +6,8 @@ import { useMe } from './me' import UpBolt from '../svgs/bolt.svg' import { amountSchema } from '../lib/validate' import { useAnonymous } from '../lib/anonymous' +import { useShowModal } from './modal' +import FundError from './fund-error' const defaultTips = [100, 1000, 10000, 100000] @@ -41,6 +43,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) { const inputRef = useRef(null) const me = useMe() const [oValue, setOValue] = useState() + const showModal = useShowModal() useEffect(() => { inputRef.current?.focus() @@ -75,7 +78,23 @@ export default function ItemAct ({ onClose, itemId, act, strike }) { }} schema={amountSchema} onSubmit={async ({ amount }) => { - await anonAct(amount) + try { + await anonAct(amount) + } catch (error) { + if (error.toString().includes('insufficient funds')) { + showModal(onClose => { + return ( + + ) + }) + return + } + throw new Error({ message: error.toString() }) + } }} > { - await anonUpsertLink(ANON_POST_FEE, boost, title, values) + onSubmit={async ({ boost, title, cost, ...values }) => { + await anonUpsertLink(cost, boost, title, values) }} storageKeyPrefix={item ? undefined : 'link'} > diff --git a/components/poll-form.js b/components/poll-form.js index d719def0..5cf26156 100644 --- a/components/poll-form.js +++ b/components/poll-form.js @@ -3,7 +3,7 @@ import { useRouter } from 'next/router' import { gql, useApolloClient, useMutation } from '@apollo/client' import Countdown from './countdown' 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 Delete from './delete' import Button from 'react-bootstrap/Button' @@ -68,8 +68,8 @@ export function PollForm ({ item, sub, editThreshold, children }) { ...SubSelectInitial({ sub: item?.subName || sub?.name }) }} schema={schema} - onSubmit={async ({ boost, title, options, ...values }) => { - await anonUpsertPoll(ANON_POST_FEE, boost, title, options, values) + onSubmit={async ({ boost, title, options, cost, ...values }) => { + await anonUpsertPoll(cost, boost, title, options, values) }} storageKeyPrefix={item ? undefined : 'poll'} > diff --git a/components/upvote.js b/components/upvote.js index c19572fc..a04e69a4 100644 --- a/components/upvote.js +++ b/components/upvote.js @@ -163,10 +163,11 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } if (pendingSats > 0) { timerRef.current = setTimeout(async (sats) => { + const variables = { id: item.id, sats: pendingSats } try { timerRef.current && setPendingSats(0) await act({ - variables: { id: item.id, sats }, + variables, optimisticResponse: { act: { sats @@ -178,7 +179,15 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats } if (error.toString().includes('insufficient funds')) { showModal(onClose => { - return + return ( + { + await act({ variables: { ...variables, invoiceHash } }) + }} + /> + ) }) return } diff --git a/lib/anonymous.js b/lib/anonymous.js index b7705869..16d95543 100644 --- a/lib/anonymous.js +++ b/lib/anonymous.js @@ -7,6 +7,7 @@ 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' const Invoice = ({ id, ...props }) => { @@ -24,7 +25,10 @@ const Invoice = ({ id, ...props }) => { return } -export const useAnonymous = (fn) => { +const defaultOptions = { + forceInvoice: false +} +export const useAnonymous = (fn, options = defaultOptions) => { const me = useMe() const [createInvoice, { data }] = useMutation(gql` mutation createInvoice($amount: Int!) { @@ -55,8 +59,26 @@ export const useAnonymous = (fn) => { } }, [invoice?.id]) - const anonFn = useCallback((amount, ...args) => { - if (me) return fn(amount, ...args) + const anonFn = useCallback(async (amount, ...args) => { + if (me && !options.forceInvoice) { + try { + return await fn(amount, ...args) + } catch (error) { + if (error.toString().includes('insufficient funds')) { + showModal(onClose => { + return ( + { await fn(amount, ...args, invoiceHash) }} + /> + ) + }) + return + } + throw new Error({ message: error.toString() }) + } + } setFnArgs(args) return createInvoice({ variables: { amount } }) }, [fn, setFnArgs, createInvoice])