Add anon zaps
This commit is contained in:
parent
42bdd40f91
commit
5415c6b0f6
@ -16,6 +16,7 @@ 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}`
|
||||||
@ -711,24 +712,37 @@ export default {
|
|||||||
|
|
||||||
return id
|
return id
|
||||||
},
|
},
|
||||||
act: async (parent, { id, sats }, { me, models }) => {
|
act: async (parent, { id, sats, invoiceId }, { me, models }) => {
|
||||||
// need to make sure we are logged in
|
// need to make sure we are logged in
|
||||||
if (!me) {
|
if (!me && !invoiceId) {
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
await ssValidate(amountSchema, { amount: sats })
|
await ssValidate(amountSchema, { amount: sats })
|
||||||
|
|
||||||
|
let user = me
|
||||||
|
if (!me && invoiceId) {
|
||||||
|
const invoice = await checkInvoice(models, invoiceId, sats)
|
||||||
|
user = invoice.user
|
||||||
|
}
|
||||||
|
|
||||||
// disallow self tips
|
// disallow self tips
|
||||||
const [item] = await models.$queryRawUnsafe(`
|
const [item] = await models.$queryRawUnsafe(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE id = $1 AND "userId" = $2`, Number(id), me.id)
|
WHERE id = $1 AND "userId" = $2`, Number(id), user.id)
|
||||||
if (item) {
|
if (item) {
|
||||||
throw new GraphQLError('cannot zap your self', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('cannot zap your self', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const [{ item_act: vote }] = await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}::INTEGER, ${me.id}::INTEGER, 'TIP', ${Number(sats)}::INTEGER)`)
|
const calls = [
|
||||||
|
models.$queryRaw`SELECT item_act(${Number(id)}::INTEGER, ${user.id}::INTEGER, 'TIP', ${Number(sats)}::INTEGER)`
|
||||||
|
]
|
||||||
|
if (!me && invoiceId) {
|
||||||
|
calls.push(models.invoice.delete({ where: { id: Number(invoiceId) } }))
|
||||||
|
}
|
||||||
|
|
||||||
|
const [{ item_act: vote }] = await serialize(models, ...calls)
|
||||||
|
|
||||||
const updatedItem = await models.item.findUnique({ where: { id: Number(id) } })
|
const updatedItem = await models.item.findUnique({ where: { id: Number(id) } })
|
||||||
const title = `your ${updatedItem.title ? 'post' : 'reply'} ${updatedItem.fwdUser ? 'forwarded' : 'stacked'} ${Math.floor(Number(updatedItem.msats) / 1000)} sats${updatedItem.fwdUser ? ` to @${updatedItem.fwdUser.name}` : ''}`
|
const title = `your ${updatedItem.title ? 'post' : 'reply'} ${updatedItem.fwdUser ? 'forwarded' : 'stacked'} ${Math.floor(Number(updatedItem.msats) / 1000)} sats${updatedItem.fwdUser ? ` to @${updatedItem.fwdUser.name}` : ''}`
|
||||||
|
@ -2,13 +2,13 @@ const { GraphQLError } = require('graphql')
|
|||||||
const retry = require('async-retry')
|
const retry = require('async-retry')
|
||||||
const Prisma = require('@prisma/client')
|
const Prisma = require('@prisma/client')
|
||||||
|
|
||||||
async function serialize (models, call) {
|
async function serialize (models, ...calls) {
|
||||||
return await retry(async bail => {
|
return await retry(async bail => {
|
||||||
try {
|
try {
|
||||||
const [, result] = await models.$transaction(
|
const [, ...result] = await models.$transaction(
|
||||||
[models.$executeRaw`SELECT ASSERT_SERIALIZED()`, call],
|
[models.$executeRaw`SELECT ASSERT_SERIALIZED()`, ...calls],
|
||||||
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
|
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
|
||||||
return result
|
return calls.length > 1 ? result : result[0]
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
if (error.message.includes('SN_INSUFFICIENT_FUNDS')) {
|
if (error.message.includes('SN_INSUFFICIENT_FUNDS')) {
|
||||||
|
@ -7,12 +7,9 @@ import { SELECT } from './item'
|
|||||||
import { lnurlPayDescriptionHash } from '../../lib/lnurl'
|
import { lnurlPayDescriptionHash } from '../../lib/lnurl'
|
||||||
import { msatsToSats, msatsToSatsDecimal } from '../../lib/format'
|
import { msatsToSats, msatsToSatsDecimal } from '../../lib/format'
|
||||||
import { amountSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
|
import { amountSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
|
||||||
|
import { ANON_USER_ID } from '../../lib/constants'
|
||||||
|
|
||||||
export async function getInvoice (parent, { id }, { me, models }) {
|
export async function getInvoice (parent, { id }, { me, models }) {
|
||||||
if (!me) {
|
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
|
||||||
}
|
|
||||||
|
|
||||||
const inv = await models.invoice.findUnique({
|
const inv = await models.invoice.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: Number(id)
|
id: Number(id)
|
||||||
@ -22,6 +19,15 @@ export async function getInvoice (parent, { id }, { me, models }) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!inv) {
|
||||||
|
throw new GraphQLError('invoice not found', { extensions: { code: 'BAD_INPUT' } })
|
||||||
|
}
|
||||||
|
if (inv.user.id === ANON_USER_ID) {
|
||||||
|
return inv
|
||||||
|
}
|
||||||
|
if (!me) {
|
||||||
|
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
||||||
|
}
|
||||||
if (inv.user.id !== me.id) {
|
if (inv.user.id !== me.id) {
|
||||||
throw new GraphQLError('not ur invoice', { extensions: { code: 'FORBIDDEN' } })
|
throw new GraphQLError('not ur invoice', { extensions: { code: 'FORBIDDEN' } })
|
||||||
}
|
}
|
||||||
@ -190,13 +196,9 @@ export default {
|
|||||||
|
|
||||||
Mutation: {
|
Mutation: {
|
||||||
createInvoice: async (parent, { amount }, { me, models, lnd }) => {
|
createInvoice: async (parent, { amount }, { me, models, lnd }) => {
|
||||||
if (!me) {
|
|
||||||
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
|
|
||||||
}
|
|
||||||
|
|
||||||
await ssValidate(amountSchema, { amount })
|
await ssValidate(amountSchema, { amount })
|
||||||
|
|
||||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
const user = await models.user.findUnique({ where: { id: me ? me.id : ANON_USER_ID } })
|
||||||
|
|
||||||
// set expires at to 3 hours into future
|
// set expires at to 3 hours into future
|
||||||
const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
|
const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
|
||||||
@ -211,7 +213,7 @@ export default {
|
|||||||
|
|
||||||
const [inv] = await serialize(models,
|
const [inv] = await serialize(models,
|
||||||
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
|
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
|
||||||
${expiresAt}, ${amount * 1000}, ${me.id}::INTEGER, ${description})`)
|
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description})`)
|
||||||
|
|
||||||
return inv
|
return inv
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -35,7 +35,7 @@ export default gql`
|
|||||||
createComment(text: String!, parentId: ID!): Item!
|
createComment(text: String!, parentId: ID!): Item!
|
||||||
updateComment(id: ID!, text: String!): Item!
|
updateComment(id: ID!, text: String!): Item!
|
||||||
dontLikeThis(id: ID!): Boolean!
|
dontLikeThis(id: ID!): Boolean!
|
||||||
act(id: ID!, sats: Int): ItemActResult!
|
act(id: ID!, sats: Int, invoiceId: ID): ItemActResult!
|
||||||
pollVote(id: ID!): ID!
|
pollVote(id: ID!): ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import Qr from './qr'
|
import Qr from './qr'
|
||||||
|
|
||||||
export function Invoice ({ invoice }) {
|
export function Invoice ({ invoice, onConfirmation, successVerb }) {
|
||||||
let variant = 'default'
|
let variant = 'default'
|
||||||
let status = 'waiting for you'
|
let status = 'waiting for you'
|
||||||
if (invoice.confirmedAt) {
|
if (invoice.confirmedAt) {
|
||||||
variant = 'confirmed'
|
variant = 'confirmed'
|
||||||
status = `${invoice.satsReceived} sats deposited`
|
status = `${invoice.satsReceived} sats ${successVerb || 'deposited'}`
|
||||||
|
onConfirmation?.(invoice)
|
||||||
} else if (invoice.cancelled) {
|
} else if (invoice.cancelled) {
|
||||||
variant = 'failed'
|
variant = 'failed'
|
||||||
status = 'cancelled'
|
status = 'cancelled'
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import Button from 'react-bootstrap/Button'
|
import Button from 'react-bootstrap/Button'
|
||||||
import InputGroup from 'react-bootstrap/InputGroup'
|
import InputGroup from 'react-bootstrap/InputGroup'
|
||||||
import React, { useState, useRef, useEffect } from 'react'
|
import React, { useState, useRef, useEffect, useCallback } from 'react'
|
||||||
import { Form, Input, SubmitButton } from './form'
|
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'
|
||||||
|
|
||||||
const defaultTips = [100, 1000, 10000, 100000]
|
const defaultTips = [100, 1000, 10000, 100000]
|
||||||
|
|
||||||
@ -45,6 +46,27 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
|
|||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
}, [onClose, itemId])
|
}, [onClose, itemId])
|
||||||
|
|
||||||
|
const submitAct = useCallback(
|
||||||
|
async (amount, invoiceId) => {
|
||||||
|
if (!me) {
|
||||||
|
const storageKey = `TIP-item:${itemId}`
|
||||||
|
const existingAmount = Number(window.localStorage.getItem(storageKey) || '0')
|
||||||
|
window.localStorage.setItem(storageKey, existingAmount + amount)
|
||||||
|
}
|
||||||
|
await act({
|
||||||
|
variables: {
|
||||||
|
id: itemId,
|
||||||
|
sats: Number(amount),
|
||||||
|
invoiceId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
await strike()
|
||||||
|
addCustomTip(Number(amount))
|
||||||
|
onClose()
|
||||||
|
}, [act, onClose, strike, itemId])
|
||||||
|
|
||||||
|
const anonAct = useAnonymous(submitAct)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
@ -53,15 +75,7 @@ export default function ItemAct ({ onClose, itemId, act, strike }) {
|
|||||||
}}
|
}}
|
||||||
schema={amountSchema}
|
schema={amountSchema}
|
||||||
onSubmit={async ({ amount }) => {
|
onSubmit={async ({ amount }) => {
|
||||||
await act({
|
await anonAct(amount)
|
||||||
variables: {
|
|
||||||
id: itemId,
|
|
||||||
sats: Number(amount)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
await strike()
|
|
||||||
addCustomTip(Number(amount))
|
|
||||||
onClose()
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
|
@ -24,17 +24,23 @@ export default function ItemInfo ({ item, pendingSats, full, commentsText, class
|
|||||||
const [canEdit, setCanEdit] =
|
const [canEdit, setCanEdit] =
|
||||||
useState(item.mine && (Date.now() < editThreshold))
|
useState(item.mine && (Date.now() < editThreshold))
|
||||||
const [hasNewComments, setHasNewComments] = useState(false)
|
const [hasNewComments, setHasNewComments] = useState(false)
|
||||||
|
const [meTotalSats, setMeTotalSats] = useState(0)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!full) {
|
if (!full) {
|
||||||
setHasNewComments(newComments(item))
|
setHasNewComments(newComments(item))
|
||||||
}
|
}
|
||||||
}, [item])
|
}, [item])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (item) setMeTotalSats(item.meSats + item.meAnonSats + pendingSats)
|
||||||
|
}, [item?.meSats, item?.meAnonSats, pendingSats])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className || `${styles.other}`}>
|
<div className={className || `${styles.other}`}>
|
||||||
{!item.position &&
|
{!item.position &&
|
||||||
<>
|
<>
|
||||||
<span title={`from ${item.upvotes} stackers ${item.mine ? `\\ ${item.meSats} sats to post` : `(${item.meSats + pendingSats} sats from me)`} `}>{abbrNum(item.sats + pendingSats)} sats</span>
|
<span title={`from ${item.upvotes} stackers ${item.mine ? `\\ ${item.meSats} sats to post` : `(${meTotalSats} sats from me)`} `}>{abbrNum(item.sats + pendingSats)} sats</span>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
</>}
|
</>}
|
||||||
{item.boost > 0 &&
|
{item.boost > 0 &&
|
||||||
|
@ -11,7 +11,6 @@ import LongPressable from 'react-longpressable'
|
|||||||
import Overlay from 'react-bootstrap/Overlay'
|
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 { useRouter } from 'next/router'
|
|
||||||
import { LightningConsumer } from './lightning'
|
import { LightningConsumer } from './lightning'
|
||||||
|
|
||||||
const getColor = (meSats) => {
|
const getColor = (meSats) => {
|
||||||
@ -66,7 +65,6 @@ const TipPopover = ({ target, show, handleClose }) => (
|
|||||||
|
|
||||||
export default function UpVote ({ item, className, pendingSats, setPendingSats }) {
|
export default function UpVote ({ item, className, pendingSats, setPendingSats }) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
const router = useRouter()
|
|
||||||
const [voteShow, _setVoteShow] = useState(false)
|
const [voteShow, _setVoteShow] = useState(false)
|
||||||
const [tipShow, _setTipShow] = useState(false)
|
const [tipShow, _setTipShow] = useState(false)
|
||||||
const ref = useRef()
|
const ref = useRef()
|
||||||
@ -110,8 +108,8 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
|
|
||||||
const [act] = useMutation(
|
const [act] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation act($id: ID!, $sats: Int!) {
|
mutation act($id: ID!, $sats: Int!, $invoiceId: ID) {
|
||||||
act(id: $id, sats: $sats) {
|
act(id: $id, sats: $sats, invoiceId: $invoiceId) {
|
||||||
sats
|
sats
|
||||||
}
|
}
|
||||||
}`, {
|
}`, {
|
||||||
@ -122,17 +120,19 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
sats (existingSats = 0) {
|
sats (existingSats = 0) {
|
||||||
return existingSats + sats
|
return existingSats + sats
|
||||||
},
|
},
|
||||||
meSats (existingSats = 0) {
|
meSats: me
|
||||||
if (sats <= me.sats) {
|
? (existingSats = 0) => {
|
||||||
if (existingSats === 0) {
|
if (sats <= me.sats) {
|
||||||
setVoteShow(true)
|
if (existingSats === 0) {
|
||||||
} else {
|
setVoteShow(true)
|
||||||
setTipShow(true)
|
} else {
|
||||||
}
|
setTipShow(true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return existingSats + sats
|
return existingSats + sats
|
||||||
}
|
}
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -197,8 +197,9 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
return item?.mine || (me && Number(me.id) === item?.fwdUserId) || item?.deletedAt
|
return item?.mine || (me && Number(me.id) === item?.fwdUserId) || item?.deletedAt
|
||||||
}, [me?.id, item?.fwdUserId, item?.mine, item?.deletedAt])
|
}, [me?.id, item?.fwdUserId, item?.mine, item?.deletedAt])
|
||||||
|
|
||||||
const [meSats, sats, overlayText, color] = useMemo(() => {
|
const [meSats, meTotalSats, sats, overlayText, color] = useMemo(() => {
|
||||||
const meSats = (item?.meSats || 0) + pendingSats
|
const meSats = (item?.meSats || 0) + pendingSats
|
||||||
|
const meTotalSats = meSats + (item?.meAnonSats || 0)
|
||||||
|
|
||||||
// what should our next tip be?
|
// what should our next tip be?
|
||||||
let sats = me?.tipDefault || 1
|
let sats = me?.tipDefault || 1
|
||||||
@ -211,8 +212,8 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
sats = raiseTip - meSats
|
sats = raiseTip - meSats
|
||||||
}
|
}
|
||||||
|
|
||||||
return [meSats, sats, `${sats} sat${sats > 1 ? 's' : ''}`, getColor(meSats)]
|
return [meSats, meTotalSats, sats, `${sats} sat${sats > 1 ? 's' : ''}`, getColor(meTotalSats)]
|
||||||
}, [item?.meSats, pendingSats, me?.tipDefault, me?.turboDefault])
|
}, [item?.meSats, item?.meAnonSats, pendingSats, me?.tipDefault, me?.turboDefault])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LightningConsumer>
|
<LightningConsumer>
|
||||||
@ -251,10 +252,7 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
|
|
||||||
setPendingSats(pendingSats + sats)
|
setPendingSats(pendingSats + sats)
|
||||||
}
|
}
|
||||||
: async () => await router.push({
|
: () => showModal(onClose => <ItemAct onClose={onClose} itemId={item.id} act={act} strike={strike} />)
|
||||||
pathname: '/signup',
|
|
||||||
query: { callbackUrl: window.location.origin + router.asPath }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ActionTooltip notForm disable={disabled} overlayText={overlayText}>
|
<ActionTooltip notForm disable={disabled} overlayText={overlayText}>
|
||||||
@ -268,9 +266,9 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
`${styles.upvote}
|
`${styles.upvote}
|
||||||
${className || ''}
|
${className || ''}
|
||||||
${disabled ? styles.noSelfTips : ''}
|
${disabled ? styles.noSelfTips : ''}
|
||||||
${meSats ? styles.voted : ''}`
|
${meTotalSats ? styles.voted : ''}`
|
||||||
}
|
}
|
||||||
style={meSats
|
style={meTotalSats
|
||||||
? {
|
? {
|
||||||
fill: color,
|
fill: color,
|
||||||
filter: `drop-shadow(0 0 6px ${color}90)`
|
filter: `drop-shadow(0 0 6px ${color}90)`
|
||||||
|
@ -14,6 +14,7 @@ export const COMMENT_FIELDS = gql`
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
sats
|
sats
|
||||||
|
meAnonSats @client
|
||||||
upvotes
|
upvotes
|
||||||
wvotes
|
wvotes
|
||||||
boost
|
boost
|
||||||
|
@ -19,6 +19,7 @@ export const ITEM_FIELDS = gql`
|
|||||||
otsHash
|
otsHash
|
||||||
position
|
position
|
||||||
sats
|
sats
|
||||||
|
meAnonSats @client
|
||||||
boost
|
boost
|
||||||
bounty
|
bounty
|
||||||
bountyPaidTo
|
bountyPaidTo
|
||||||
|
83
lib/anonymous.js
Normal file
83
lib/anonymous.js
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
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 { INVOICE } from '../fragments/wallet'
|
||||||
|
|
||||||
|
const Invoice = ({ id, ...props }) => {
|
||||||
|
const { data, loading, error } = useQuery(INVOICE, {
|
||||||
|
pollInterval: 1000,
|
||||||
|
variables: { id }
|
||||||
|
})
|
||||||
|
if (error) {
|
||||||
|
console.log(error)
|
||||||
|
return <div>error</div>
|
||||||
|
}
|
||||||
|
if (!data || loading) {
|
||||||
|
return <QrSkeleton status='loading' />
|
||||||
|
}
|
||||||
|
return <QrInvoice invoice={data.invoice} {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAnonymous = (fn) => {
|
||||||
|
const me = useMe()
|
||||||
|
const [createInvoice, { data }] = useMutation(gql`
|
||||||
|
mutation createInvoice($amount: Int!) {
|
||||||
|
createInvoice(amount: $amount) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
const showModal = useShowModal()
|
||||||
|
const [fnArgs, setFnArgs] = useState()
|
||||||
|
|
||||||
|
const invoice = data?.createInvoice
|
||||||
|
useEffect(() => {
|
||||||
|
if (invoice) {
|
||||||
|
showModal(onClose =>
|
||||||
|
<Invoice
|
||||||
|
id={invoice.id}
|
||||||
|
onConfirmation={
|
||||||
|
async ({ id, satsReceived }) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
await fn(satsReceived, ...fnArgs, id)
|
||||||
|
onClose()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
} successVerb='received'
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, [invoice?.id])
|
||||||
|
|
||||||
|
const anonFn = useCallback((amount, ...args) => {
|
||||||
|
if (me) return fn(amount, ...args)
|
||||||
|
setFnArgs(args)
|
||||||
|
return createInvoice({ variables: { amount } })
|
||||||
|
})
|
||||||
|
|
||||||
|
return anonFn
|
||||||
|
}
|
||||||
|
|
||||||
|
export const checkInvoice = async (models, invoiceId, fee) => {
|
||||||
|
const invoice = await models.invoice.findUnique({
|
||||||
|
where: { id: Number(invoiceId) },
|
||||||
|
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
|
||||||
|
}
|
@ -141,6 +141,17 @@ function getClient (uri) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Item: {
|
||||||
|
fields: {
|
||||||
|
meAnonSats: {
|
||||||
|
read (meAnonSats, { readField }) {
|
||||||
|
if (typeof window === 'undefined') return null
|
||||||
|
const itemId = readField('id')
|
||||||
|
return meAnonSats ?? Number(localStorage.getItem(`TIP-item:${itemId}`) || '0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
@ -44,3 +44,5 @@ export const ITEM_TYPES = context => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const OLD_ITEM_DAYS = 3
|
export const OLD_ITEM_DAYS = 3
|
||||||
|
|
||||||
|
export const ANON_USER_ID = 27
|
||||||
|
@ -5,7 +5,7 @@ import { CenterLayout } from '../../components/layout'
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { INVOICE } from '../../fragments/wallet'
|
import { INVOICE } from '../../fragments/wallet'
|
||||||
|
|
||||||
export default function FullInvoice () {
|
export default function FullInvoice ({ id }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { data, error } = useQuery(INVOICE, {
|
const { data, error } = useQuery(INVOICE, {
|
||||||
pollInterval: 1000,
|
pollInterval: 1000,
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
UPDATE users SET "hideInvoiceDesc" = 't' WHERE id = 27;
|
Loading…
x
Reference in New Issue
Block a user