better boost hints (#1441)

* better boost hints

* refine
This commit is contained in:
Keyan 2024-10-02 19:24:01 -05:00 committed by GitHub
parent 5f1d3dbde4
commit f4382ad73e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 107 additions and 41 deletions

View File

@ -685,11 +685,7 @@ export default {
return await models.item.count({ where }) + 1 return await models.item.count({ where }) + 1
}, },
boostPosition: async (parent, { id, sub, boost }, { models, me }) => { boostPosition: async (parent, { id, sub, boost = 0 }, { models, me }) => {
if (boost <= 0) {
throw new GqlInputError('boost must be greater than 0')
}
const where = { const where = {
boost: { gte: boost }, boost: { gte: boost },
status: 'ACTIVE', status: 'ACTIVE',
@ -701,9 +697,29 @@ export default {
where.id = { not: Number(id) } where.id = { not: Number(id) }
} }
const homeAgg = await models.item.aggregate({
_count: { id: true },
_max: { boost: true },
where
})
let subAgg
if (sub) {
subAgg = await models.item.aggregate({
_count: { id: true },
_max: { boost: true },
where: {
...where,
subName: sub
}
})
}
return { return {
home: await models.item.count({ where }) === 0, home: homeAgg._count.id === 0 && boost >= BOOST_MULT,
sub: sub ? await models.item.count({ where: { ...where, subName: sub } }) === 0 : false sub: subAgg?._count.id === 0 && boost >= BOOST_MULT,
homeMaxBoost: homeAgg._max.boost || 0,
subMaxBoost: subAgg?._max.boost || 0
} }
} }
}, },

View File

@ -16,6 +16,8 @@ export default gql`
type BoostPositions { type BoostPositions {
home: Boolean! home: Boolean!
sub: Boolean! sub: Boolean!
homeMaxBoost: Int!
subMaxBoost: Int!
} }
type TitleUnshorted { type TitleUnshorted {

View File

@ -1,18 +1,20 @@
import { useState, useEffect, useMemo } from 'react' import { useState, useEffect, useMemo, useCallback } from 'react'
import AccordianItem from './accordian-item' import AccordianItem from './accordian-item'
import { Input, InputUserSuggest, VariableInput, Checkbox } from './form' import { Input, InputUserSuggest, VariableInput, Checkbox } from './form'
import InputGroup from 'react-bootstrap/InputGroup' import InputGroup from 'react-bootstrap/InputGroup'
import { BOOST_MIN, BOOST_MULT, MAX_FORWARDS } from '@/lib/constants' import { BOOST_MIN, BOOST_MULT, MAX_FORWARDS, SSR } from '@/lib/constants'
import { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr' import { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr'
import Info from './info' import Info from './info'
import { numWithUnits } from '@/lib/format' import { abbrNum, numWithUnits } from '@/lib/format'
import styles from './adv-post-form.module.css' import styles from './adv-post-form.module.css'
import { useMe } from './me' import { useMe } from './me'
import { useFeeButton } from './fee-button' import { useFeeButton } from './fee-button'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { gql, useLazyQuery } from '@apollo/client' import { gql, useQuery } from '@apollo/client'
import useDebounceCallback from './use-debounce-callback' import useDebounceCallback from './use-debounce-callback'
import { Button } from 'react-bootstrap'
import classNames from 'classnames'
const EMPTY_FORWARD = { nym: '', pct: '' } const EMPTY_FORWARD = { nym: '', pct: '' }
@ -85,56 +87,96 @@ export function BoostInput ({ onChange, ...props }) {
) )
} }
const BoostMaxes = ({ subName, homeMax, subMax, boost, updateBoost }) => {
return (
<div className='d-flex flex-row mb-2'>
<Button
className={classNames(styles.boostMax, 'me-2', homeMax + BOOST_MULT <= (boost || 0) && 'invisible')}
size='sm'
onClick={() => updateBoost(homeMax + BOOST_MULT)}
>
{abbrNum(homeMax + BOOST_MULT)} <small>top of homepage</small>
</Button>
{subName &&
<Button
className={classNames(styles.boostMax, subMax + BOOST_MULT <= (boost || 0) && 'invisible')}
size='sm'
onClick={() => updateBoost(subMax + BOOST_MULT)}
>
{abbrNum(subMax + BOOST_MULT)} <small>top of ~{subName}</small>
</Button>}
</div>
)
}
// act means we are adding to existing boost // act means we are adding to existing boost
export function BoostItemInput ({ item, sub, act = false, ...props }) { export function BoostItemInput ({ item, sub, act = false, ...props }) {
const [boost, setBoost] = useState(Number(item?.boost) + (act ? BOOST_MULT : 0)) // act adds boost to existing boost
const existingBoost = act ? Number(item?.boost || 0) : 0
const [boost, setBoost] = useState(act ? 0 : Number(item?.boost || 0))
const [getBoostPosition, { data }] = useLazyQuery(gql` const { data, previousData, refetch } = useQuery(gql`
query BoostPosition($sub: String, $id: ID, $boost: Int) { query BoostPosition($sub: String, $id: ID, $boost: Int) {
boostPosition(sub: $sub, id: $id, boost: $boost) { boostPosition(sub: $sub, id: $id, boost: $boost) {
home home
sub sub
homeMaxBoost
subMaxBoost
} }
}`, }`,
{ fetchPolicy: 'cache-and-network' }) {
variables: { sub: item?.subName || sub?.name, boost: existingBoost + boost, id: item?.id },
fetchPolicy: 'cache-and-network',
skip: !!item?.parentId || SSR
})
const getPositionDebounce = useDebounceCallback((...args) => getBoostPosition(...args), 1000, [getBoostPosition]) const getPositionDebounce = useDebounceCallback((...args) => refetch(...args), 1000, [refetch])
const updateBoost = useCallback((boost) => {
const boostToUse = Number(boost || 0)
setBoost(boostToUse)
getPositionDebounce({ sub: item?.subName || sub?.name, boost: Number(existingBoost + boostToUse), id: item?.id })
}, [getPositionDebounce, item?.id, item?.subName, sub?.name, existingBoost])
useEffect(() => { const dat = data || previousData
if (boost >= 0 && !item?.parentId) {
getPositionDebounce({ variables: { sub: item?.subName || sub?.name, boost: Number(boost), id: item?.id } })
}
}, [boost, item?.id, !item?.parentId, item?.subName || sub?.name])
const boostMessage = useMemo(() => { const boostMessage = useMemo(() => {
if (!item?.parentId) { if (!item?.parentId && boost >= BOOST_MULT) {
if (data?.boostPosition?.home || data?.boostPosition?.sub) { if (dat?.boostPosition?.home || dat?.boostPosition?.sub || boost > dat?.boostPosition?.homeMaxBoost || boost > dat?.boostPosition?.subMaxBoost) {
const boostPinning = [] const boostPinning = []
if (data?.boostPosition?.home) { if (dat?.boostPosition?.home || boost > dat?.boostPosition?.homeMaxBoost) {
boostPinning.push('homepage') boostPinning.push('homepage')
} }
if (data?.boostPosition?.sub) { if ((item?.subName || sub?.name) && (dat?.boostPosition?.sub || boost > dat?.boostPosition?.subMaxBoost)) {
boostPinning.push(`~${item?.subName || sub?.name}`) boostPinning.push(`~${item?.subName || sub?.name}`)
} }
return `pins to the top of ${boostPinning.join(' and ')}` return `pins to the top of ${boostPinning.join(' and ')}`
} }
} }
if (boost >= 0 && boost % BOOST_MULT === 0) {
return `${act ? 'brings to' : 'equivalent to'} ${numWithUnits(boost / BOOST_MULT, { unitPlural: 'zapvotes', unitSingular: 'zapvote' })}`
}
return 'ranks posts higher based on the amount' return 'ranks posts higher based on the amount'
}, [boost, data?.boostPosition?.home, data?.boostPosition?.sub, item?.subName, sub?.name]) }, [boost, dat?.boostPosition?.home, dat?.boostPosition?.sub, item?.subName, sub?.name])
return ( return (
<BoostInput <>
hint={<span className='text-muted'>{boostMessage}</span>} <BoostInput
onChange={(_, e) => { hint={<span className='text-muted'>{boostMessage}</span>}
if (e.target.value >= 0) { onChange={(_, e) => {
setBoost(Number(e.target.value) + (act ? Number(item?.boost) : 0)) if (e.target.value >= 0) {
} updateBoost(Number(e.target.value))
}} }
{...props} }}
/> overrideValue={boost}
{...props}
groupClassName='mb-1'
/>
{!item?.parentId &&
<BoostMaxes
subName={item?.subName || sub?.name}
homeMax={(dat?.boostPosition?.homeMaxBoost || 0) - existingBoost}
subMax={(dat?.boostPosition?.subMaxBoost || 0) - existingBoost}
boost={existingBoost + boost}
updateBoost={updateBoost}
/>}
</>
) )
} }

View File

@ -9,4 +9,11 @@
display: flex; display: flex;
flex: 0 1 fit-content; flex: 0 1 fit-content;
height: fit-content; height: fit-content;
}
.boostMax small {
font-weight: 400;
margin-left: 0.25rem;
margin-right: 0.25rem;
opacity: 0.5;
} }

View File

@ -70,19 +70,18 @@ function BoostForm ({ step, onSubmit, children, item, oValue, inputRef, act = 'B
name='amount' name='amount'
type='number' type='number'
innerRef={inputRef} innerRef={inputRef}
overrideValue={oValue}
sub={item.sub} sub={item.sub}
step={step} step={step}
required required
autoFocus autoFocus
item={item} item={item}
/> />
{children}
<div className='d-flex mt-3'> <div className='d-flex mt-3'>
<SubmitButton variant='success' className='ms-auto mt-1 px-4' value={act}> <SubmitButton variant='success' className='ms-auto mt-1 px-4' value={act}>
boost boost
</SubmitButton> </SubmitButton>
</div> </div>
{children}
</Form> </Form>
) )
} }
@ -147,7 +146,7 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
}, [me, actor, !!wallet, act, item.id, onClose, abortSignal, strike]) }, [me, actor, !!wallet, act, item.id, onClose, abortSignal, strike])
return act === 'BOOST' return act === 'BOOST'
? <BoostForm step={step} onSubmit={onSubmit} item={item} oValue={oValue} inputRef={inputRef} act={act}>{children}</BoostForm> ? <BoostForm step={step} onSubmit={onSubmit} item={item} inputRef={inputRef} act={act}>{children}</BoostForm>
: ( : (
<Form <Form
initial={{ initial={{
@ -171,12 +170,12 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
<div> <div>
<Tips setOValue={setOValue} /> <Tips setOValue={setOValue} />
</div> </div>
{children}
<div className='d-flex mt-3'> <div className='d-flex mt-3'>
<SubmitButton variant={act === 'DONT_LIKE_THIS' ? 'danger' : 'success'} className='ms-auto mt-1 px-4' value={act}> <SubmitButton variant={act === 'DONT_LIKE_THIS' ? 'danger' : 'success'} className='ms-auto mt-1 px-4' value={act}>
{act === 'DONT_LIKE_THIS' ? 'downzap' : 'zap'} {act === 'DONT_LIKE_THIS' ? 'downzap' : 'zap'}
</SubmitButton> </SubmitButton>
</div> </div>
{children}
</Form>) </Form>)
} }