stacker.news/components/poll-form.js
ekzyis bb2212d51e Add invoice HMAC
This prevents entities which know the invoice hash (like all LN nodes on the payment path) from using the invoice hash on SN.

Only the user which created the invoice knows the HMAC and thus can use the invoice hash.
2023-08-10 07:10:07 +02:00

123 lines
4.1 KiB
JavaScript

import { Form, Input, MarkdownInput, SubmitButton, VariableInput } from '../components/form'
import { useRouter } from 'next/router'
import { gql, useApolloClient, useMutation } from '@apollo/client'
import Countdown from './countdown'
import AdvPostForm, { AdvPostInitial } from './adv-post-form'
import { MAX_POLL_NUM_CHOICES } from '../lib/constants'
import FeeButton, { EditFeeButton } from './fee-button'
import Delete from './delete'
import Button from 'react-bootstrap/Button'
import { pollSchema } from '../lib/validate'
import { SubSelectInitial } from './sub-select-form'
import CancelButton from './cancel-button'
import { useCallback } from 'react'
import { useInvoiceable } from './invoice'
export function PollForm ({ item, sub, editThreshold, children }) {
const router = useRouter()
const client = useApolloClient()
const schema = pollSchema(client)
const [upsertPoll] = useMutation(
gql`
mutation upsertPoll($sub: String, $id: ID, $title: String!, $text: String,
$options: [String!]!, $boost: Int, $forward: String, $invoiceHash: String, $invoiceHmac: String) {
upsertPoll(sub: $sub, id: $id, title: $title, text: $text,
options: $options, boost: $boost, forward: $forward, invoiceHash: $invoiceHash, invoiceHmac: $invoiceHmac) {
id
}
}`
)
const submitUpsertPoll = useCallback(
async (_, boost, title, options, values, invoiceHash, invoiceHmac) => {
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
const { error } = await upsertPoll({
variables: {
id: item?.id,
sub: item?.subName || sub?.name,
boost: boost ? Number(boost) : undefined,
title: title.trim(),
options: optionsFiltered,
...values,
invoiceHash,
invoiceHmac
}
})
if (error) {
throw new Error({ message: error.toString() })
}
if (item) {
await router.push(`/items/${item.id}`)
} else {
const prefix = sub?.name ? `/~${sub.name}` : ''
await router.push(prefix + '/recent')
}
}, [upsertPoll, router])
const invoiceableUpsertPoll = useInvoiceable(submitUpsertPoll)
const initialOptions = item?.poll?.options.map(i => i.option)
return (
<Form
initial={{
title: item?.title || '',
text: item?.text || '',
options: initialOptions || ['', ''],
...AdvPostInitial({ forward: item?.fwdUser?.name }),
...SubSelectInitial({ sub: item?.subName || sub?.name })
}}
schema={schema}
onSubmit={async ({ boost, title, options, cost, ...values }) => {
await invoiceableUpsertPoll(cost, boost, title, options, values)
}}
storageKeyPrefix={item ? undefined : 'poll'}
>
{children}
<Input
label='title'
name='title'
required
/>
<MarkdownInput
topLevel
label={<>text <small className='text-muted ms-2'>optional</small></>}
name='text'
minRows={2}
/>
<VariableInput
label='choices'
name='options'
readOnlyLen={initialOptions?.length}
max={MAX_POLL_NUM_CHOICES}
min={2}
hint={editThreshold
? <div className='text-muted fw-bold'><Countdown date={editThreshold} /></div>
: null}
/>
<AdvPostForm edit={!!item} />
<div className='mt-3'>
{item
? (
<div className='d-flex justify-content-between'>
<Delete itemId={item.id} onDelete={() => router.push(`/items/${item.id}`)}>
<Button variant='grey-medium'>delete</Button>
</Delete>
<div className='d-flex'>
<CancelButton />
<EditFeeButton
paidSats={item.meSats}
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
/>
</div>
</div>)
: <FeeButton
baseFee={1} parentId={null} text='post'
ChildButton={SubmitButton} variant='secondary'
/>}
</div>
</Form>
)
}