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
},
boostPosition: async (parent, { id, sub, boost }, { models, me }) => {
if (boost <= 0) {
throw new GqlInputError('boost must be greater than 0')
}
boostPosition: async (parent, { id, sub, boost = 0 }, { models, me }) => {
const where = {
boost: { gte: boost },
status: 'ACTIVE',
@ -701,9 +697,29 @@ export default {
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 {
home: await models.item.count({ where }) === 0,
sub: sub ? await models.item.count({ where: { ...where, subName: sub } }) === 0 : false
home: homeAgg._count.id === 0 && boost >= BOOST_MULT,
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 {
home: Boolean!
sub: Boolean!
homeMaxBoost: Int!
subMaxBoost: Int!
}
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 { Input, InputUserSuggest, VariableInput, Checkbox } from './form'
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 Info from './info'
import { numWithUnits } from '@/lib/format'
import { abbrNum, numWithUnits } from '@/lib/format'
import styles from './adv-post-form.module.css'
import { useMe } from './me'
import { useFeeButton } from './fee-button'
import { useRouter } from 'next/router'
import { useFormikContext } from 'formik'
import { gql, useLazyQuery } from '@apollo/client'
import { gql, useQuery } from '@apollo/client'
import useDebounceCallback from './use-debounce-callback'
import { Button } from 'react-bootstrap'
import classNames from 'classnames'
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
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) {
boostPosition(sub: $sub, id: $id, boost: $boost) {
home
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(() => {
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 dat = data || previousData
const boostMessage = useMemo(() => {
if (!item?.parentId) {
if (data?.boostPosition?.home || data?.boostPosition?.sub) {
if (!item?.parentId && boost >= BOOST_MULT) {
if (dat?.boostPosition?.home || dat?.boostPosition?.sub || boost > dat?.boostPosition?.homeMaxBoost || boost > dat?.boostPosition?.subMaxBoost) {
const boostPinning = []
if (data?.boostPosition?.home) {
if (dat?.boostPosition?.home || boost > dat?.boostPosition?.homeMaxBoost) {
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}`)
}
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'
}, [boost, data?.boostPosition?.home, data?.boostPosition?.sub, item?.subName, sub?.name])
}, [boost, dat?.boostPosition?.home, dat?.boostPosition?.sub, item?.subName, sub?.name])
return (
<BoostInput
hint={<span className='text-muted'>{boostMessage}</span>}
onChange={(_, e) => {
if (e.target.value >= 0) {
setBoost(Number(e.target.value) + (act ? Number(item?.boost) : 0))
}
}}
{...props}
/>
<>
<BoostInput
hint={<span className='text-muted'>{boostMessage}</span>}
onChange={(_, e) => {
if (e.target.value >= 0) {
updateBoost(Number(e.target.value))
}
}}
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;
flex: 0 1 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'
type='number'
innerRef={inputRef}
overrideValue={oValue}
sub={item.sub}
step={step}
required
autoFocus
item={item}
/>
{children}
<div className='d-flex mt-3'>
<SubmitButton variant='success' className='ms-auto mt-1 px-4' value={act}>
boost
</SubmitButton>
</div>
{children}
</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])
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
initial={{
@ -171,12 +170,12 @@ export default function ItemAct ({ onClose, item, act = 'TIP', step, children, a
<div>
<Tips setOValue={setOValue} />
</div>
{children}
<div className='d-flex mt-3'>
<SubmitButton variant={act === 'DONT_LIKE_THIS' ? 'danger' : 'success'} className='ms-auto mt-1 px-4' value={act}>
{act === 'DONT_LIKE_THIS' ? 'downzap' : 'zap'}
</SubmitButton>
</div>
{children}
</Form>)
}