diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index 7259ceeb..07915631 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -290,7 +290,7 @@ export default { if (payer) { payer = { ...payer, - identifier: payer.identifier ? me.name : undefined + identifier: payer.identifier ? `${me.name}@stacker.news` : undefined } payer = Object.fromEntries( Object.entries(payer).filter(([, value]) => !!value) @@ -305,10 +305,10 @@ export default { callback.searchParams.append('comment', comment) } - let encodedPayerData = '' + let stringifiedPayerData = '' if (payer && Object.entries(payer).length) { - encodedPayerData = encodeURIComponent(JSON.stringify(payer)) - callback.searchParams.append('payerdata', encodedPayerData) + stringifiedPayerData = JSON.stringify(payer) + callback.searchParams.append('payerdata', stringifiedPayerData) } // call callback with amount and conditionally comment @@ -320,7 +320,7 @@ export default { // decode invoice try { const decoded = await decodePaymentRequest({ lnd, request: res.pr }) - if (decoded.description_hash !== lnurlPayDescriptionHash(`${options.metadata}${encodedPayerData}`)) { + if (decoded.description_hash !== lnurlPayDescriptionHash(`${options.metadata}${stringifiedPayerData}`)) { throw new Error('description hash does not match') } if (!decoded.mtokens || BigInt(decoded.mtokens) !== BigInt(milliamount)) { diff --git a/components/dont-link-this.js b/components/dont-link-this.js index 90464e32..ae3c11d8 100644 --- a/components/dont-link-this.js +++ b/components/dont-link-this.js @@ -2,8 +2,6 @@ import { gql, useMutation } from '@apollo/client' import Dropdown from 'react-bootstrap/Dropdown' import { useShowModal } from './modal' import { useToast } from './toast' -import { InvoiceModal, payOrLoginError } from './invoice' -import { DONT_LIKE_THIS_COST } from '../lib/constants' import ItemAct from './item-act' export default function DontLikeThisDropdownItem ({ id }) { @@ -40,22 +38,7 @@ export default function DontLikeThisDropdownItem ({ id }) { }} itemId={id} act={dontLikeThis} down />) } catch (error) { - console.error(error) - if (payOrLoginError(error)) { - showModal(onClose => { - return ( - { - await dontLikeThis({ variables: { id, hash, hmac } }) - toaster.success('item flagged') - }} - /> - ) - }) - } else { - toaster.danger('failed to flag item') - } + toaster.danger('failed to flag item') } }} > diff --git a/components/form.js b/components/form.js index bdbbcf19..5406d5c8 100644 --- a/components/form.js +++ b/components/form.js @@ -24,6 +24,7 @@ import { numWithUnits } from '../lib/format' import textAreaCaret from 'textarea-caret' import ReactDatePicker from 'react-datepicker' import 'react-datepicker/dist/react-datepicker.css' +import { debounce } from './use-debounce-callback' export function SubmitButton ({ children, variant, value, onClick, disabled, cost, ...props @@ -290,7 +291,7 @@ function FormGroup ({ className, label, children }) { function InputInner ({ prepend, append, hint, showValid, onChange, onBlur, overrideValue, - innerRef, noForm, clear, onKeyDown, inputGroupClassName, debounce, maxLength, + innerRef, noForm, clear, onKeyDown, inputGroupClassName, debounce: debounceTime, maxLength, ...props }) { const [field, meta, helpers] = noForm ? [{}, {}, {}] : useField(props) @@ -317,17 +318,11 @@ function InputInner ({ const invalid = (!formik || formik.submitCount > 0) && meta.touched && meta.error - const debounceRef = useRef(-1) - - useEffect(() => { - if (debounceRef.current !== -1) { - clearTimeout(debounceRef.current) + useEffect(debounce(() => { + if (!noForm && !isNaN(debounceTime) && debounceTime > 0) { + formik.validateForm() } - if (!noForm && !isNaN(debounce) && debounce > 0) { - debounceRef.current = setTimeout(() => formik.validateForm(), debounce) - } - return () => clearTimeout(debounceRef.current) - }, [noForm, formik, field.value]) + }, debounceTime), [noForm, formik, field.value]) const remaining = maxLength && maxLength - (field.value || '').length @@ -601,7 +596,7 @@ export function Form ({ const toaster = useToast() const initialErrorToasted = useRef(false) useEffect(() => { - if (initialError && !initialErrorToasted) { + if (initialError && !initialErrorToasted.current) { toaster.danger(initialError.message || initialError.toString?.()) initialErrorToasted.current = true } diff --git a/components/invoice.js b/components/invoice.js index cb9e7218..04c2283e 100644 --- a/components/invoice.js +++ b/components/invoice.js @@ -149,6 +149,9 @@ const defaultOptions = { callback: null, // (formValues) => void replaceModal: false } +// TODO: refactor this so it can be easily understood +// there's lots of state cascading paired with logic +// independent of the state, and it's hard to follow export const useInvoiceable = (onSubmit, options = defaultOptions) => { const me = useMe() const [createInvoice, { data }] = useMutation(gql` @@ -247,17 +250,14 @@ export const useInvoiceable = (onSubmit, options = defaultOptions) => { // tell onSubmit handler that we want to keep local storage // even though the submit handler was "successful" return { keepLocalStorage: true } - }, [onSubmit, setFormValues, setSubmitArgs, createInvoice]) + }, [onSubmit, setFormValues, setSubmitArgs, createInvoice, !!me]) return onSubmitWrapper } -export const InvoiceModal = ({ onPayment, amount }) => { - const createInvoice = useInvoiceable(onPayment, { replaceModal: true }) - - useEffect(() => { - createInvoice({ amount }) - }, []) +export const useInvoiceModal = (onPayment, deps) => { + const onPaymentMemo = useCallback(onPayment, deps) + return useInvoiceable(onPaymentMemo, { replaceModal: true }) } export const payOrLoginError = (error) => { diff --git a/components/item-act.js b/components/item-act.js index ef50241b..d249074d 100644 --- a/components/item-act.js +++ b/components/item-act.js @@ -67,7 +67,7 @@ export default function ItemAct ({ onClose, itemId, act, down, strike }) { return (
{ + await act({ variables: { ...variables, hash, hmac } }) + }, [act]) const handlePayBounty = async onComplete => { try { @@ -74,16 +77,7 @@ export default function PayBounty ({ children, item }) { onComplete() } catch (error) { if (payOrLoginError(error)) { - showModal(onClose => { - return ( - { - await act({ variables: { id: item.id, sats: root.bounty, hash, hmac } }) - }} - /> - ) - }) + showInvoiceModal({ amount: root.bounty }, { variables: { id: item.id, sats: root.bounty } }) return } throw new Error({ message: error.toString() }) diff --git a/components/poll.js b/components/poll.js index 1a0b8fc6..987f96ee 100644 --- a/components/poll.js +++ b/components/poll.js @@ -7,13 +7,11 @@ import styles from './poll.module.css' import Check from '../svgs/checkbox-circle-fill.svg' import { signIn } from 'next-auth/react' import ActionTooltip from './action-tooltip' -import { useShowModal } from './modal' import { POLL_COST } from '../lib/constants' -import { InvoiceModal } from './invoice' +import { payOrLoginError, useInvoiceModal } from './invoice' export default function Poll ({ item }) { const me = useMe() - const showModal = useShowModal() const [pollVote] = useMutation( gql` mutation pollVote($id: ID!, $hash: String, $hmac: String) { @@ -47,6 +45,12 @@ export default function Poll ({ item }) { ) const PollButton = ({ v }) => { + const showInvoiceModal = useInvoiceModal(async ({ hash, hmac }, { variables }) => { + await pollVote({ variables: { ...variables, hash, hmac } }) + }, [pollVote]) + + const variables = { id: v.id } + return (