full powered editing
This commit is contained in:
parent
9b8b6078d6
commit
388c7d0240
@ -4,7 +4,7 @@ import serialize from './serial'
|
|||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
||||||
import domino from 'domino'
|
import domino from 'domino'
|
||||||
import { BOOST_MIN, ITEM_SPAM_INTERVAL } from '../../lib/constants'
|
import { BOOST_MIN, ITEM_SPAM_INTERVAL, MAX_POLL_NUM_CHOICES } from '../../lib/constants'
|
||||||
import { mdHas } from '../../lib/md'
|
import { mdHas } from '../../lib/md'
|
||||||
|
|
||||||
async function comments (models, id, sort) {
|
async function comments (models, id, sort) {
|
||||||
@ -450,8 +450,7 @@ export default {
|
|||||||
data.url = ensureProtocol(data.url)
|
data.url = ensureProtocol(data.url)
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
const { forward, boost, ...remaining } = data
|
return await updateItem(parent, { id, data }, { me, models })
|
||||||
return await updateItem(parent, { id, data: remaining }, { me, models })
|
|
||||||
} else {
|
} else {
|
||||||
return await createItem(parent, data, { me, models })
|
return await createItem(parent, data, { me, models })
|
||||||
}
|
}
|
||||||
@ -460,8 +459,7 @@ export default {
|
|||||||
const { id, ...data } = args
|
const { id, ...data } = args
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
const { forward, boost, ...remaining } = data
|
return await updateItem(parent, { id, data }, { me, models })
|
||||||
return await updateItem(parent, { id, data: remaining }, { me, models })
|
|
||||||
} else {
|
} else {
|
||||||
return await createItem(parent, data, { me, models })
|
return await createItem(parent, data, { me, models })
|
||||||
}
|
}
|
||||||
@ -475,37 +473,43 @@ export default {
|
|||||||
throw new UserInputError(`boost must be at least ${BOOST_MIN}`, { argumentName: 'boost' })
|
throw new UserInputError(`boost must be at least ${BOOST_MIN}`, { argumentName: 'boost' })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id) {
|
let fwdUser
|
||||||
// TODO: this isn't ever called clientside, we edit like it's a discussion
|
if (forward) {
|
||||||
|
fwdUser = await models.user.findUnique({ where: { name: forward } })
|
||||||
|
if (!fwdUser) {
|
||||||
|
throw new UserInputError('forward user does not exist', { argumentName: 'forward' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const item = await models.item.update({
|
const hasImgLink = !!(text && mdHas(text, ['link', 'image']))
|
||||||
where: { id: Number(id) },
|
|
||||||
data: { title: title }
|
if (id) {
|
||||||
|
const optionCount = await models.pollOption.count({
|
||||||
|
where: {
|
||||||
|
itemId: Number(id)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return item
|
if (options.length + optionCount > MAX_POLL_NUM_CHOICES) {
|
||||||
} else {
|
throw new UserInputError(`total choices must be <${MAX_POLL_NUM_CHOICES}`, { argumentName: 'options' })
|
||||||
let fwdUser
|
|
||||||
if (forward) {
|
|
||||||
fwdUser = await models.user.findUnique({ where: { name: forward } })
|
|
||||||
if (!fwdUser) {
|
|
||||||
throw new UserInputError('forward user does not exist', { argumentName: 'forward' })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [item] = await serialize(models,
|
const [item] = await serialize(models,
|
||||||
models.$queryRaw(`${SELECT} FROM create_poll($1, $2, $3, $4, $5, $6) AS "Item"`,
|
models.$queryRaw(`${SELECT} FROM update_poll($1, $2, $3, $4, $5, $6, $7) AS "Item"`,
|
||||||
title, text, 1, Number(boost || 0), Number(me.id), options))
|
Number(id), title, text, Number(boost || 0), options, Number(fwdUser?.id), hasImgLink))
|
||||||
|
|
||||||
if (fwdUser) {
|
return item
|
||||||
await models.item.update({
|
} else {
|
||||||
where: { id: item.id },
|
if (options.length < 2 || options.length > MAX_POLL_NUM_CHOICES) {
|
||||||
data: {
|
throw new UserInputError(`choices must be >2 and <${MAX_POLL_NUM_CHOICES}`, { argumentName: 'options' })
|
||||||
fwdUserId: fwdUser.id
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [item] = await serialize(models,
|
||||||
|
models.$queryRaw(`${SELECT} FROM create_poll($1, $2, $3, $4, $5, $6, $7, $8, '${ITEM_SPAM_INTERVAL}') AS "Item"`,
|
||||||
|
title, text, 1, Number(boost || 0), Number(me.id), options, Number(fwdUser?.id), hasImgLink))
|
||||||
|
|
||||||
|
await createMentions(item, models)
|
||||||
|
|
||||||
item.comments = []
|
item.comments = []
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
@ -847,26 +851,37 @@ export const createMentions = async (item, models) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateItem = async (parent, { id, data }, { me, models }) => {
|
export const updateItem = async (parent, { id, data: { title, url, text, boost, forward, parentId } }, { me, models }) => {
|
||||||
// update iff this item belongs to me
|
// update iff this item belongs to me
|
||||||
const old = await models.item.findUnique({ where: { id: Number(id) } })
|
const old = await models.item.findUnique({ where: { id: Number(id) } })
|
||||||
if (Number(old.userId) !== Number(me?.id)) {
|
if (Number(old.userId) !== Number(me?.id)) {
|
||||||
throw new AuthenticationError('item does not belong to you')
|
throw new AuthenticationError('item does not belong to you')
|
||||||
}
|
}
|
||||||
|
|
||||||
// if it's not the FAQ and older than 10 minutes
|
// if it's not the FAQ, not their bio, and older than 10 minutes
|
||||||
if (old.id !== 349 && Date.now() > new Date(old.createdAt).getTime() + 10 * 60000) {
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
if (old.id !== 349 && user.bioId !== id && Date.now() > new Date(old.createdAt).getTime() + 10 * 60000) {
|
||||||
throw new UserInputError('item can no longer be editted')
|
throw new UserInputError('item can no longer be editted')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data?.text && !old.paidImgLink && mdHas(data.text, ['link', 'image'])) {
|
if (boost && boost < BOOST_MIN) {
|
||||||
throw new UserInputError('adding links or images on edit is not allowed yet')
|
throw new UserInputError(`boost must be at least ${BOOST_MIN}`, { argumentName: 'boost' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = await models.item.update({
|
let fwdUser
|
||||||
where: { id: Number(id) },
|
if (forward) {
|
||||||
data
|
fwdUser = await models.user.findUnique({ where: { name: forward } })
|
||||||
})
|
if (!fwdUser) {
|
||||||
|
throw new UserInputError('forward user does not exist', { argumentName: 'forward' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasImgLink = !!(text && mdHas(text, ['link', 'image']))
|
||||||
|
|
||||||
|
const [item] = await serialize(models,
|
||||||
|
models.$queryRaw(
|
||||||
|
`${SELECT} FROM update_item($1, $2, $3, $4, $5, $6, $7) AS "Item"`,
|
||||||
|
Number(id), title, url, text, Number(boost || 0), Number(fwdUser?.id), hasImgLink))
|
||||||
|
|
||||||
await createMentions(item, models)
|
await createMentions(item, models)
|
||||||
|
|
||||||
@ -934,7 +949,7 @@ export const SELECT =
|
|||||||
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
||||||
"Item".text, "Item".url, "Item"."userId", "Item"."fwdUserId", "Item"."parentId", "Item"."pinId", "Item"."maxBid",
|
"Item".text, "Item".url, "Item"."userId", "Item"."fwdUserId", "Item"."parentId", "Item"."pinId", "Item"."maxBid",
|
||||||
"Item".company, "Item".location, "Item".remote,
|
"Item".company, "Item".location, "Item".remote,
|
||||||
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", ltree2text("Item"."path") AS "path"`
|
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item"."paidImgLink", ltree2text("Item"."path") AS "path"`
|
||||||
|
|
||||||
function newTimedOrderByWeightedSats (num) {
|
function newTimedOrderByWeightedSats (num) {
|
||||||
return `
|
return `
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import { createMentions, getItem, SELECT } from './item'
|
import { mdHas } from '../../lib/md'
|
||||||
|
import { createMentions, getItem, SELECT, updateItem } from './item'
|
||||||
import serialize from './serial'
|
import serialize from './serial'
|
||||||
|
|
||||||
export function topClause (within) {
|
export function topClause (within) {
|
||||||
@ -188,21 +189,16 @@ export default {
|
|||||||
|
|
||||||
const user = await models.user.findUnique({ where: { id: me.id } })
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
|
||||||
let item
|
|
||||||
if (user.bioId) {
|
if (user.bioId) {
|
||||||
item = await models.item.update({
|
await updateItem(parent, { id: user.bioId, data: { text: bio, title: `@${user.name}'s bio` } }, { me, models })
|
||||||
where: { id: Number(user.bioId) },
|
|
||||||
data: {
|
|
||||||
text: bio
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
([item] = await serialize(models,
|
const hasImgLink = !!(bio && mdHas(bio, ['link', 'image']))
|
||||||
models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3) AS "Item"`,
|
|
||||||
`@${user.name}'s bio`, bio, Number(me.id))))
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMentions(item, models)
|
const [item] = await serialize(models,
|
||||||
|
models.$queryRaw(`${SELECT} FROM create_bio($1, $2, $3, $4) AS "Item"`,
|
||||||
|
`@${user.name}'s bio`, bio, Number(me.id), hasImgLink))
|
||||||
|
await createMentions(item, models)
|
||||||
|
}
|
||||||
|
|
||||||
return await models.user.findUnique({ where: { id: me.id } })
|
return await models.user.findUnique({ where: { id: me.id } })
|
||||||
},
|
},
|
||||||
|
@ -76,6 +76,7 @@ export default gql`
|
|||||||
sats: Int!
|
sats: Int!
|
||||||
upvotes: Int!
|
upvotes: Int!
|
||||||
meSats: Int!
|
meSats: Int!
|
||||||
|
paidImgLink: Boolean
|
||||||
meComments: Int!
|
meComments: Int!
|
||||||
ncomments: Int!
|
ncomments: Int!
|
||||||
comments: [Item!]!
|
comments: [Item!]!
|
||||||
|
@ -9,7 +9,14 @@ import Info from './info'
|
|||||||
export function AdvPostSchema (client) {
|
export function AdvPostSchema (client) {
|
||||||
return {
|
return {
|
||||||
boost: Yup.number().typeError('must be a number')
|
boost: Yup.number().typeError('must be a number')
|
||||||
.min(BOOST_MIN, `must be blank or at least ${BOOST_MIN}`).integer('must be whole'),
|
.min(BOOST_MIN, `must be blank or at least ${BOOST_MIN}`).integer('must be whole').test({
|
||||||
|
name: 'boost',
|
||||||
|
test: async boost => {
|
||||||
|
if (!boost || boost % BOOST_MIN === 0) return true
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
message: `must be divisble be ${BOOST_MIN}`
|
||||||
|
}),
|
||||||
forward: Yup.string()
|
forward: Yup.string()
|
||||||
.test({
|
.test({
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@ -23,12 +30,14 @@ export function AdvPostSchema (client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AdvPostInitial = {
|
export function AdvPostInitial ({ forward }) {
|
||||||
boost: '',
|
return {
|
||||||
forward: ''
|
boost: '',
|
||||||
|
forward: forward || ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AdvPostForm () {
|
export default function AdvPostForm ({ edit }) {
|
||||||
return (
|
return (
|
||||||
<AccordianItem
|
<AccordianItem
|
||||||
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
|
header={<div style={{ fontWeight: 'bold', fontSize: '92%' }}>options</div>}
|
||||||
@ -36,7 +45,7 @@ export default function AdvPostForm () {
|
|||||||
<>
|
<>
|
||||||
<Input
|
<Input
|
||||||
label={
|
label={
|
||||||
<div className='d-flex align-items-center'>boost
|
<div className='d-flex align-items-center'>{edit ? 'add boost' : 'boost'}
|
||||||
<Info>
|
<Info>
|
||||||
<ol className='font-weight-bold'>
|
<ol className='font-weight-bold'>
|
||||||
<li>Boost ranks posts higher temporarily based on the amount</li>
|
<li>Boost ranks posts higher temporarily based on the amount</li>
|
||||||
|
@ -3,17 +3,22 @@ import * as Yup from 'yup'
|
|||||||
import { gql, useMutation } from '@apollo/client'
|
import { gql, useMutation } from '@apollo/client'
|
||||||
import styles from './reply.module.css'
|
import styles from './reply.module.css'
|
||||||
import TextareaAutosize from 'react-textarea-autosize'
|
import TextareaAutosize from 'react-textarea-autosize'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { EditFeeButton } from './fee-button'
|
||||||
|
|
||||||
export const CommentSchema = Yup.object({
|
export const CommentSchema = Yup.object({
|
||||||
text: Yup.string().required('required').trim()
|
text: Yup.string().required('required').trim()
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function CommentEdit ({ comment, editThreshold, onSuccess, onCancel }) {
|
export default function CommentEdit ({ comment, editThreshold, onSuccess, onCancel }) {
|
||||||
|
const [hasImgLink, setHasImgLink] = useState()
|
||||||
|
|
||||||
const [updateComment] = useMutation(
|
const [updateComment] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
mutation updateComment($id: ID! $text: String!) {
|
mutation updateComment($id: ID! $text: String!) {
|
||||||
updateComment(id: $id, text: $text) {
|
updateComment(id: $id, text: $text) {
|
||||||
text
|
text
|
||||||
|
paidImgLink
|
||||||
}
|
}
|
||||||
}`, {
|
}`, {
|
||||||
update (cache, { data: { updateComment } }) {
|
update (cache, { data: { updateComment } }) {
|
||||||
@ -22,6 +27,9 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
|
|||||||
fields: {
|
fields: {
|
||||||
text () {
|
text () {
|
||||||
return updateComment.text
|
return updateComment.text
|
||||||
|
},
|
||||||
|
paidImgLink () {
|
||||||
|
return updateComment.paidImgLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -51,9 +59,13 @@ export default function CommentEdit ({ comment, editThreshold, onSuccess, onCanc
|
|||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
minRows={6}
|
minRows={6}
|
||||||
autoFocus
|
autoFocus
|
||||||
|
setHasImgLink={setHasImgLink}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<SubmitButton variant='secondary' className='mt-1'>save</SubmitButton>
|
<EditFeeButton
|
||||||
|
paidSats={comment.meSats} hadImgLink={comment.paidImgLink} hasImgLink={hasImgLink}
|
||||||
|
parentId={comment.parentId} text='save' ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -7,7 +7,7 @@ import Countdown from './countdown'
|
|||||||
import AdvPostForm, { AdvPostInitial, AdvPostSchema } from './adv-post-form'
|
import AdvPostForm, { AdvPostInitial, AdvPostSchema } from './adv-post-form'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import FeeButton from './fee-button'
|
import FeeButton, { EditFeeButton } from './fee-button'
|
||||||
|
|
||||||
export function DiscussionForm ({
|
export function DiscussionForm ({
|
||||||
item, editThreshold, titleLabel = 'title',
|
item, editThreshold, titleLabel = 'title',
|
||||||
@ -41,7 +41,7 @@ export function DiscussionForm ({
|
|||||||
initial={{
|
initial={{
|
||||||
title: item?.title || '',
|
title: item?.title || '',
|
||||||
text: item?.text || '',
|
text: item?.text || '',
|
||||||
...AdvPostInitial
|
...AdvPostInitial({ forward: item?.fwdUser?.name })
|
||||||
}}
|
}}
|
||||||
schema={DiscussionSchema}
|
schema={DiscussionSchema}
|
||||||
onSubmit={handleSubmit || (async ({ boost, ...values }) => {
|
onSubmit={handleSubmit || (async ({ boost, ...values }) => {
|
||||||
@ -77,10 +77,13 @@ export function DiscussionForm ({
|
|||||||
: null}
|
: null}
|
||||||
setHasImgLink={setHasImgLink}
|
setHasImgLink={setHasImgLink}
|
||||||
/>
|
/>
|
||||||
{!item && adv && <AdvPostForm />}
|
{adv && <AdvPostForm edit={!!item} />}
|
||||||
<div className='mt-3'>
|
<div className='mt-3'>
|
||||||
{item
|
{item
|
||||||
? <SubmitButton variant='secondary'>save</SubmitButton>
|
? <EditFeeButton
|
||||||
|
paidSats={item.meSats} hadImgLink={item.paidImgLink} hasImgLink={hasImgLink}
|
||||||
|
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>
|
||||||
: <FeeButton
|
: <FeeButton
|
||||||
baseFee={1} hasImgLink={hasImgLink} parentId={null} text={buttonText}
|
baseFee={1} hasImgLink={hasImgLink} parentId={null} text={buttonText}
|
||||||
ChildButton={SubmitButton} variant='secondary'
|
ChildButton={SubmitButton} variant='secondary'
|
||||||
|
@ -62,3 +62,58 @@ export default function FeeButton ({ parentId, hasImgLink, baseFee, ChildButton,
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function EditReceipt ({ cost, paidSats, addImgLink, boost, parentId }) {
|
||||||
|
return (
|
||||||
|
<Table className={styles.receipt} borderless size='sm'>
|
||||||
|
<tbody>
|
||||||
|
{addImgLink &&
|
||||||
|
<>
|
||||||
|
<tr>
|
||||||
|
<td>{paidSats} sats</td>
|
||||||
|
<td align='right' className='font-weight-light'>{parentId ? 'reply' : 'post'} fee</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>x 10</td>
|
||||||
|
<td align='right' className='font-weight-light'>image/link fee</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>- {paidSats} sats</td>
|
||||||
|
<td align='right' className='font-weight-light'>already paid</td>
|
||||||
|
</tr>
|
||||||
|
</>}
|
||||||
|
{boost > 0 &&
|
||||||
|
<tr>
|
||||||
|
<td>+ {boost} sats</td>
|
||||||
|
<td className='font-weight-light' align='right'>boost</td>
|
||||||
|
</tr>}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td className='font-weight-bold'>{cost} sats</td>
|
||||||
|
<td align='right' className='font-weight-light'>total fee</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</Table>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EditFeeButton ({ paidSats, hadImgLink, hasImgLink, ChildButton, variant, text, alwaysShow, parentId }) {
|
||||||
|
const formik = useFormikContext()
|
||||||
|
const boost = formik?.values?.boost || 0
|
||||||
|
const addImgLink = hasImgLink && !hadImgLink
|
||||||
|
const cost = (addImgLink ? paidSats * 9 : 0) + Number(boost)
|
||||||
|
|
||||||
|
const show = alwaysShow || !formik?.isSubmitting
|
||||||
|
return (
|
||||||
|
<div className='d-flex align-items-center'>
|
||||||
|
<ActionTooltip overlayText={`${cost} sats`}>
|
||||||
|
<ChildButton variant={variant}>{text}{cost > 0 && show && <small> {cost} sats</small>}</ChildButton>
|
||||||
|
</ActionTooltip>
|
||||||
|
{cost > 0 && show &&
|
||||||
|
<Info>
|
||||||
|
<EditReceipt paidSats={paidSats} addImgLink={addImgLink} cost={cost} parentId={parentId} boost={boost} />
|
||||||
|
</Info>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -173,6 +173,7 @@ export default function Footer ({ noLinks }) {
|
|||||||
size='sm'
|
size='sm'
|
||||||
groupClassName='mb-0 w-100'
|
groupClassName='mb-0 w-100'
|
||||||
readOnly
|
readOnly
|
||||||
|
noForm
|
||||||
placeholder={data.connectAddress}
|
placeholder={data.connectAddress}
|
||||||
/>
|
/>
|
||||||
</div>}
|
</div>}
|
||||||
|
@ -129,10 +129,10 @@ function FormGroup ({ className, label, children }) {
|
|||||||
|
|
||||||
function InputInner ({
|
function InputInner ({
|
||||||
prepend, append, hint, showValid, onChange, overrideValue,
|
prepend, append, hint, showValid, onChange, overrideValue,
|
||||||
innerRef, storageKeyPrefix, ...props
|
innerRef, storageKeyPrefix, noForm, ...props
|
||||||
}) {
|
}) {
|
||||||
const [field, meta, helpers] = props.readOnly ? [{}, {}, {}] : useField(props)
|
const [field, meta, helpers] = noForm ? [{}, {}, {}] : useField(props)
|
||||||
const formik = props.readOnly ? null : useFormikContext()
|
const formik = noForm ? null : useFormikContext()
|
||||||
|
|
||||||
const storageKey = storageKeyPrefix ? storageKeyPrefix + '-' + props.name : undefined
|
const storageKey = storageKeyPrefix ? storageKeyPrefix + '-' + props.name : undefined
|
||||||
|
|
||||||
@ -208,19 +208,19 @@ export function Input ({ label, groupClassName, ...props }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function VariableInput ({ label, groupClassName, name, hint, max, ...props }) {
|
export function VariableInput ({ label, groupClassName, name, hint, max, readOnlyLen, ...props }) {
|
||||||
return (
|
return (
|
||||||
<FormGroup label={label} className={groupClassName}>
|
<FormGroup label={label} className={groupClassName}>
|
||||||
<FieldArray name={name}>
|
<FieldArray name={name}>
|
||||||
{({ form, ...fieldArrayHelpers }) => {
|
{({ form, ...fieldArrayHelpers }) => {
|
||||||
const options = form.values.options
|
const options = form.values[name]
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{options.map((_, i) => (
|
{options.map((_, i) => (
|
||||||
<div key={i}>
|
<div key={i}>
|
||||||
<BootstrapForm.Row className='mb-2'>
|
<BootstrapForm.Row className='mb-2'>
|
||||||
<Col>
|
<Col>
|
||||||
<InputInner name={`${name}[${i}]`} {...props} placeholder={i > 1 ? 'optional' : undefined} />
|
<InputInner name={`${name}[${i}]`} {...props} readOnly={i < readOnlyLen} placeholder={i > 1 ? 'optional' : undefined} />
|
||||||
</Col>
|
</Col>
|
||||||
{options.length - 1 === i && options.length !== max
|
{options.length - 1 === i && options.length !== max
|
||||||
? <AddIcon className='fill-grey align-self-center pointer mx-2' onClick={() => fieldArrayHelpers.push('')} />
|
? <AddIcon className='fill-grey align-self-center pointer mx-2' onClick={() => fieldArrayHelpers.push('')} />
|
||||||
|
@ -21,7 +21,7 @@ export default function Invite ({ invite, active }) {
|
|||||||
<CopyInput
|
<CopyInput
|
||||||
groupClassName='mb-1'
|
groupClassName='mb-1'
|
||||||
size='sm' type='text'
|
size='sm' type='text'
|
||||||
placeholder={`https://stacker.news/invites/${invite.id}`} readOnly
|
placeholder={`https://stacker.news/invites/${invite.id}`} readOnly noForm
|
||||||
/>
|
/>
|
||||||
<div className={styles.other}>
|
<div className={styles.other}>
|
||||||
<span>{invite.gift} sat gift</span>
|
<span>{invite.gift} sat gift</span>
|
||||||
|
@ -8,7 +8,7 @@ import { ITEM_FIELDS } from '../fragments/items'
|
|||||||
import Item from './item'
|
import Item from './item'
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH } from '../lib/constants'
|
||||||
import FeeButton from './fee-button'
|
import FeeButton, { EditFeeButton } from './fee-button'
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
const URL = /^((https?|ftp):\/\/)?(www.)?(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
|
const URL = /^((https?|ftp):\/\/)?(www.)?(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
|
||||||
@ -55,7 +55,7 @@ export function LinkForm ({ item, editThreshold }) {
|
|||||||
initial={{
|
initial={{
|
||||||
title: item?.title || '',
|
title: item?.title || '',
|
||||||
url: item?.url || '',
|
url: item?.url || '',
|
||||||
...AdvPostInitial
|
...AdvPostInitial({ forward: item?.fwdUser?.name })
|
||||||
}}
|
}}
|
||||||
schema={LinkSchema}
|
schema={LinkSchema}
|
||||||
onSubmit={async ({ boost, title, ...values }) => {
|
onSubmit={async ({ boost, title, ...values }) => {
|
||||||
@ -98,10 +98,13 @@ export function LinkForm ({ item, editThreshold }) {
|
|||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{!item && <AdvPostForm />}
|
<AdvPostForm edit={!!item} />
|
||||||
<div className='mt-3'>
|
<div className='mt-3'>
|
||||||
{item
|
{item
|
||||||
? <SubmitButton variant='secondary'>save</SubmitButton>
|
? <EditFeeButton
|
||||||
|
paidSats={item.meSats}
|
||||||
|
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>
|
||||||
: <FeeButton
|
: <FeeButton
|
||||||
baseFee={1} parentId={null} text='post'
|
baseFee={1} parentId={null} text='post'
|
||||||
ChildButton={SubmitButton} variant='secondary'
|
ChildButton={SubmitButton} variant='secondary'
|
||||||
|
@ -26,7 +26,7 @@ export default function LnQR ({ value, webLn, statusVariant, status }) {
|
|||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<div className='mt-3 w-100'>
|
<div className='mt-3 w-100'>
|
||||||
<CopyInput type='text' placeholder={value} readOnly />
|
<CopyInput type='text' placeholder={value} readOnly noForm />
|
||||||
</div>
|
</div>
|
||||||
<InvoiceStatus variant={statusVariant} status={status} />
|
<InvoiceStatus variant={statusVariant} status={status} />
|
||||||
</>
|
</>
|
||||||
|
@ -2,15 +2,17 @@ import { Form, Input, MarkdownInput, SubmitButton, VariableInput } from '../comp
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
import { gql, useApolloClient, useMutation } from '@apollo/client'
|
||||||
import ActionTooltip from '../components/action-tooltip'
|
|
||||||
import Countdown from './countdown'
|
import Countdown from './countdown'
|
||||||
import AdvPostForm, { AdvPostInitial, AdvPostSchema } from './adv-post-form'
|
import AdvPostForm, { AdvPostInitial, AdvPostSchema } from './adv-post-form'
|
||||||
import { MAX_TITLE_LENGTH, MAX_POLL_CHOICE_LENGTH } from '../lib/constants'
|
import { MAX_TITLE_LENGTH, MAX_POLL_CHOICE_LENGTH, MAX_POLL_NUM_CHOICES } from '../lib/constants'
|
||||||
import TextareaAutosize from 'react-textarea-autosize'
|
import TextareaAutosize from 'react-textarea-autosize'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import FeeButton, { EditFeeButton } from './fee-button'
|
||||||
|
|
||||||
export function PollForm ({ item, editThreshold }) {
|
export function PollForm ({ item, editThreshold }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const client = useApolloClient()
|
const client = useApolloClient()
|
||||||
|
const [hasImgLink, setHasImgLink] = useState()
|
||||||
|
|
||||||
const [upsertPoll] = useMutation(
|
const [upsertPoll] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
@ -36,16 +38,19 @@ export function PollForm ({ item, editThreshold }) {
|
|||||||
...AdvPostSchema(client)
|
...AdvPostSchema(client)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const initialOptions = item?.poll?.options.map(i => i.option)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
title: item?.title || '',
|
title: item?.title || '',
|
||||||
options: item?.options || ['', ''],
|
text: item?.text || '',
|
||||||
...AdvPostInitial
|
options: initialOptions || ['', ''],
|
||||||
|
...AdvPostInitial({ forward: item?.fwdUser?.name })
|
||||||
}}
|
}}
|
||||||
schema={PollSchema}
|
schema={PollSchema}
|
||||||
onSubmit={async ({ boost, title, options, ...values }) => {
|
onSubmit={async ({ boost, title, options, ...values }) => {
|
||||||
const optionsFiltered = options.filter(word => word.trim().length > 0)
|
const optionsFiltered = options.slice(initialOptions?.length).filter(word => word.trim().length > 0)
|
||||||
const { error } = await upsertPoll({
|
const { error } = await upsertPoll({
|
||||||
variables: {
|
variables: {
|
||||||
id: item?.id,
|
id: item?.id,
|
||||||
@ -77,20 +82,29 @@ export function PollForm ({ item, editThreshold }) {
|
|||||||
name='text'
|
name='text'
|
||||||
as={TextareaAutosize}
|
as={TextareaAutosize}
|
||||||
minRows={2}
|
minRows={2}
|
||||||
|
setHasImgLink={setHasImgLink}
|
||||||
/>
|
/>
|
||||||
<VariableInput
|
<VariableInput
|
||||||
label='choices'
|
label='choices'
|
||||||
name='options'
|
name='options'
|
||||||
max={5}
|
readOnlyLen={initialOptions?.length}
|
||||||
|
max={MAX_POLL_NUM_CHOICES}
|
||||||
hint={editThreshold
|
hint={editThreshold
|
||||||
? <div className='text-muted font-weight-bold'><Countdown date={editThreshold} /></div>
|
? <div className='text-muted font-weight-bold'><Countdown date={editThreshold} /></div>
|
||||||
: null}
|
: null}
|
||||||
/>
|
/>
|
||||||
{!item && <AdvPostForm />}
|
<AdvPostForm edit={!!item} />
|
||||||
<ActionTooltip>
|
<div className='mt-3'>
|
||||||
<SubmitButton variant='secondary' className='mt-3'>{item ? 'save' : 'post'}</SubmitButton>
|
{item
|
||||||
</ActionTooltip>
|
? <EditFeeButton
|
||||||
|
paidSats={item.meSats} hadImgLink={item.paidImgLink} hasImgLink={hasImgLink}
|
||||||
|
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>
|
||||||
|
: <FeeButton
|
||||||
|
baseFee={1} hasImgLink={hasImgLink} parentId={null} text='post'
|
||||||
|
ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ export const COMMENT_FIELDS = gql`
|
|||||||
meComments
|
meComments
|
||||||
path
|
path
|
||||||
mine
|
mine
|
||||||
|
paidImgLink
|
||||||
ncomments
|
ncomments
|
||||||
root {
|
root {
|
||||||
id
|
id
|
||||||
|
@ -34,6 +34,7 @@ export const ITEM_FIELDS = gql`
|
|||||||
status
|
status
|
||||||
uploadId
|
uploadId
|
||||||
mine
|
mine
|
||||||
|
paidImgLink
|
||||||
root {
|
root {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
@ -64,12 +65,28 @@ export const ITEMS = gql`
|
|||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
export const POLL_FIELDS = gql`
|
||||||
|
fragment PollFields on Item {
|
||||||
|
poll {
|
||||||
|
meVoted
|
||||||
|
count
|
||||||
|
options {
|
||||||
|
id
|
||||||
|
option
|
||||||
|
count
|
||||||
|
meVoted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
export const ITEM = gql`
|
export const ITEM = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
|
${POLL_FIELDS}
|
||||||
|
|
||||||
query Item($id: ID!) {
|
query Item($id: ID!) {
|
||||||
item(id: $id) {
|
item(id: $id) {
|
||||||
...ItemFields
|
...ItemFields
|
||||||
|
...PollFields
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -86,6 +103,7 @@ export const COMMENTS_QUERY = gql`
|
|||||||
|
|
||||||
export const ITEM_FULL = gql`
|
export const ITEM_FULL = gql`
|
||||||
${ITEM_FIELDS}
|
${ITEM_FIELDS}
|
||||||
|
${POLL_FIELDS}
|
||||||
${COMMENTS}
|
${COMMENTS}
|
||||||
query Item($id: ID!) {
|
query Item($id: ID!) {
|
||||||
item(id: $id) {
|
item(id: $id) {
|
||||||
@ -94,16 +112,7 @@ export const ITEM_FULL = gql`
|
|||||||
meComments
|
meComments
|
||||||
position
|
position
|
||||||
text
|
text
|
||||||
poll {
|
...PollFields
|
||||||
meVoted
|
|
||||||
count
|
|
||||||
options {
|
|
||||||
id
|
|
||||||
option
|
|
||||||
count
|
|
||||||
meVoted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
comments {
|
comments {
|
||||||
...CommentsRecursive
|
...CommentsRecursive
|
||||||
}
|
}
|
||||||
|
@ -13,3 +13,4 @@ export const COMMENT_DEPTH_LIMIT = 10
|
|||||||
export const MAX_TITLE_LENGTH = 80
|
export const MAX_TITLE_LENGTH = 80
|
||||||
export const MAX_POLL_CHOICE_LENGTH = 30
|
export const MAX_POLL_CHOICE_LENGTH = 30
|
||||||
export const ITEM_SPAM_INTERVAL = '10m'
|
export const ITEM_SPAM_INTERVAL = '10m'
|
||||||
|
export const MAX_POLL_NUM_CHOICES = 10
|
||||||
|
@ -13,7 +13,7 @@ import { useMe } from '../../components/me'
|
|||||||
import { USER_FULL } from '../../fragments/users'
|
import { USER_FULL } from '../../fragments/users'
|
||||||
import { ITEM_FIELDS } from '../../fragments/items'
|
import { ITEM_FIELDS } from '../../fragments/items'
|
||||||
import { getGetServerSideProps } from '../../api/ssrApollo'
|
import { getGetServerSideProps } from '../../api/ssrApollo'
|
||||||
import FeeButton from '../../components/fee-button'
|
import FeeButton, { EditFeeButton } from '../../components/fee-button'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(USER_FULL, null,
|
export const getServerSideProps = getGetServerSideProps(USER_FULL, null,
|
||||||
data => !data.user)
|
data => !data.user)
|
||||||
@ -74,7 +74,10 @@ export function BioForm ({ handleSuccess, bio }) {
|
|||||||
/>
|
/>
|
||||||
<div className='mt-3'>
|
<div className='mt-3'>
|
||||||
{bio?.text
|
{bio?.text
|
||||||
? <SubmitButton variant='secondary'>save</SubmitButton>
|
? <EditFeeButton
|
||||||
|
paidSats={bio?.meSats} hadImgLink={bio?.paidImgLink} hasImgLink={hasImgLink}
|
||||||
|
parentId={null} text='save' ChildButton={SubmitButton} variant='secondary'
|
||||||
|
/>
|
||||||
: <FeeButton
|
: <FeeButton
|
||||||
baseFee={1} hasImgLink={hasImgLink} parentId={null} text='create'
|
baseFee={1} hasImgLink={hasImgLink} parentId={null} text='create'
|
||||||
ChildButton={SubmitButton} variant='secondary'
|
ChildButton={SubmitButton} variant='secondary'
|
||||||
|
@ -4,6 +4,7 @@ import { DiscussionForm } from '../../../components/discussion-form'
|
|||||||
import { LinkForm } from '../../../components/link-form'
|
import { LinkForm } from '../../../components/link-form'
|
||||||
import LayoutCenter from '../../../components/layout-center'
|
import LayoutCenter from '../../../components/layout-center'
|
||||||
import JobForm from '../../../components/job-form'
|
import JobForm from '../../../components/job-form'
|
||||||
|
import { PollForm } from '../../../components/poll-form'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(ITEM, null,
|
export const getServerSideProps = getGetServerSideProps(ITEM, null,
|
||||||
data => !data.item)
|
data => !data.item)
|
||||||
@ -16,8 +17,10 @@ export default function PostEdit ({ data: { item } }) {
|
|||||||
{item.maxBid
|
{item.maxBid
|
||||||
? <JobForm item={item} sub={item.sub} />
|
? <JobForm item={item} sub={item.sub} />
|
||||||
: (item.url
|
: (item.url
|
||||||
? <LinkForm item={item} editThreshold={editThreshold} />
|
? <LinkForm item={item} editThreshold={editThreshold} adv />
|
||||||
: <DiscussionForm item={item} editThreshold={editThreshold} />)}
|
: (item.pollCost
|
||||||
|
? <PollForm item={item} editThreshold={editThreshold} />
|
||||||
|
: <DiscussionForm item={item} editThreshold={editThreshold} adv />))}
|
||||||
</LayoutCenter>
|
</LayoutCenter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -241,6 +241,7 @@ function AuthMethods ({ methods }) {
|
|||||||
placeholder={methods.email}
|
placeholder={methods.email}
|
||||||
groupClassName='mb-0'
|
groupClassName='mb-0'
|
||||||
readOnly
|
readOnly
|
||||||
|
noForm
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className='ml-2' variant='secondary' onClick={
|
className='ml-2' variant='secondary' onClick={
|
||||||
|
@ -81,13 +81,13 @@ function LoadWithdrawl () {
|
|||||||
<div className='w-100'>
|
<div className='w-100'>
|
||||||
<CopyInput
|
<CopyInput
|
||||||
label='invoice' type='text'
|
label='invoice' type='text'
|
||||||
placeholder={data.withdrawl.bolt11} readOnly
|
placeholder={data.withdrawl.bolt11} readOnly noForm
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='w-100'>
|
<div className='w-100'>
|
||||||
<Input
|
<Input
|
||||||
label='max fee' type='text'
|
label='max fee' type='text'
|
||||||
placeholder={data.withdrawl.satsFeePaying} readOnly
|
placeholder={data.withdrawl.satsFeePaying} readOnly noForm
|
||||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
107
prisma/migrations/20220815195309_edit_funcs/migration.sql
Normal file
107
prisma/migrations/20220815195309_edit_funcs/migration.sql
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION update_item(item_id INTEGER,
|
||||||
|
item_title TEXT, item_url TEXT, item_text TEXT, boost INTEGER,
|
||||||
|
fwd_user_id INTEGER, has_img_link BOOLEAN)
|
||||||
|
RETURNS "Item"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
user_msats INTEGER;
|
||||||
|
prior_cost INTEGER;
|
||||||
|
prior_act_id INTEGER;
|
||||||
|
cost INTEGER;
|
||||||
|
item "Item";
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
SELECT * INTO item FROM "Item" WHERE id = item_id;
|
||||||
|
|
||||||
|
-- if has_img_link we need to figure out new costs, which is their prior_cost * 9
|
||||||
|
IF has_img_link AND NOT item."paidImgLink" THEN
|
||||||
|
SELECT sats * 1000, id INTO prior_cost, prior_act_id
|
||||||
|
FROM "ItemAct"
|
||||||
|
WHERE act = 'VOTE' AND "itemId" = item.id AND "userId" = item."userId";
|
||||||
|
|
||||||
|
cost := prior_cost * 9;
|
||||||
|
|
||||||
|
IF cost > user_msats THEN
|
||||||
|
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE users SET msats = msats - cost WHERE id = item."userId";
|
||||||
|
|
||||||
|
UPDATE "ItemAct" SET sats = (prior_cost + cost) / 1000 WHERE id = prior_act_id;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
UPDATE "Item" set title = item_title, url = item_url, text = item_text, "fwdUserId" = fwd_user_id, "paidImgLink" = has_img_link
|
||||||
|
WHERE id = item_id
|
||||||
|
RETURNING * INTO item;
|
||||||
|
|
||||||
|
IF boost > 0 THEN
|
||||||
|
PERFORM item_act(item.id, item."userId", 'BOOST', boost);
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN item;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION create_poll(
|
||||||
|
title TEXT, text TEXT, poll_cost INTEGER, boost INTEGER, user_id INTEGER,
|
||||||
|
options TEXT[], fwd_user_id INTEGER, has_img_link BOOLEAN, spam_within INTERVAL)
|
||||||
|
RETURNS "Item"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
item "Item";
|
||||||
|
option TEXT;
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
item := create_item(title, null, text, boost, null, user_id, fwd_user_id, has_img_link, spam_within);
|
||||||
|
|
||||||
|
UPDATE "Item" set "pollCost" = poll_cost where id = item.id;
|
||||||
|
FOREACH option IN ARRAY options LOOP
|
||||||
|
INSERT INTO "PollOption" (created_at, updated_at, "itemId", "option") values (now_utc(), now_utc(), item.id, option);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN item;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION update_poll(
|
||||||
|
id INTEGER, title TEXT, text TEXT, boost INTEGER,
|
||||||
|
options TEXT[], fwd_user_id INTEGER, has_img_link BOOLEAN)
|
||||||
|
RETURNS "Item"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
item "Item";
|
||||||
|
option TEXT;
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
item := update_item(id, title, null, text, boost, fwd_user_id, has_img_link);
|
||||||
|
|
||||||
|
FOREACH option IN ARRAY options LOOP
|
||||||
|
INSERT INTO "PollOption" (created_at, updated_at, "itemId", "option") values (now_utc(), now_utc(), item.id, option);
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
RETURN item;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION create_bio(title TEXT, text TEXT, user_id INTEGER, has_img_link BOOLEAN)
|
||||||
|
RETURNS "Item"
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
item "Item";
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
SELECT * INTO item FROM create_item(title, NULL, text, 0, NULL, user_id, NULL, has_img_link, '0');
|
||||||
|
|
||||||
|
UPDATE users SET "bioId" = item.id WHERE id = user_id;
|
||||||
|
|
||||||
|
RETURN item;
|
||||||
|
END;
|
||||||
|
$$;
|
Loading…
x
Reference in New Issue
Block a user