complete tips

This commit is contained in:
keyan 2021-09-10 16:13:52 -05:00
parent 0a20f2ea23
commit 2dd49171e2
10 changed files with 155 additions and 36 deletions

View File

@ -219,8 +219,23 @@ export default {
throw new UserInputError('sats must be positive', { argumentName: 'sats' }) throw new UserInputError('sats must be positive', { argumentName: 'sats' })
} }
// if we are tipping disallow self tips
if (act === 'TIP') {
const [item] = await models.$queryRaw(`
${SELECT}
FROM "Item"
WHERE id = $1 AND "userId" = $2`, Number(id), me.id)
if (item) {
throw new UserInputError('cannot tip your self')
}
}
await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, ${act}, ${Number(sats)})`) await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, ${act}, ${Number(sats)})`)
return sats
return {
sats,
act
}
} }
}, },
@ -260,7 +275,20 @@ export default {
return sats || 0 return sats || 0
}, },
meSats: async (item, args, { me, models }) => { tips: async (item, args, { models }) => {
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
act: 'TIP'
}
})
return sats || 0
},
meVote: async (item, args, { me, models }) => {
if (!me) return 0 if (!me) return 0
const { sum: { sats } } = await models.itemAct.aggregate({ const { sum: { sats } } = await models.itemAct.aggregate({
@ -276,6 +304,38 @@ export default {
return sats || 0 return sats || 0
}, },
meBoost: async (item, args, { me, models }) => {
if (!me) return 0
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
userId: me.id,
act: 'BOOST'
}
})
return sats || 0
},
meTip: async (item, args, { me, models }) => {
if (!me) return 0
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
userId: me.id,
act: 'TIP'
}
})
return sats || 0
},
root: async (item, args, { models }) => { root: async (item, args, { models }) => {
if (!item.parentId) { if (!item.parentId) {
return null return null

View File

@ -15,6 +15,11 @@ export default gql`
TIP TIP
} }
type ItemActResult {
sats: Int!
act: ItemAct!
}
extend type Mutation { extend type Mutation {
createLink(title: String!, url: String): Item! createLink(title: String!, url: String): Item!
updateLink(id: ID!, title: String!, url: String): Item! updateLink(id: ID!, title: String!, url: String): Item!
@ -22,7 +27,7 @@ export default gql`
updateDiscussion(id: ID!, title: String!, text: String): Item! updateDiscussion(id: ID!, title: String!, text: String): Item!
createComment(text: String!, parentId: ID!): Item! createComment(text: String!, parentId: ID!): Item!
updateComment(id: ID!, text: String!): Item! updateComment(id: ID!, text: String!): Item!
act(id: ID!, act: ItemAct!, sats: Int): Int! act(id: ID!, act: ItemAct!, sats: Int): ItemActResult!
} }
type Items { type Items {
@ -48,7 +53,10 @@ export default gql`
depth: Int! depth: Int!
sats: Int! sats: Int!
boost: Int! boost: Int!
meSats: Int! tips: Int!
meVote: Int!
meBoost: Int!
meTip: Int!
ncomments: Int! ncomments: Int!
comments: [Item!]! comments: [Item!]!
path: String path: String

View File

@ -1,7 +1,7 @@
import { useFormikContext } from 'formik' import { useFormikContext } from 'formik'
import { OverlayTrigger, Tooltip } from 'react-bootstrap' import { OverlayTrigger, Tooltip } from 'react-bootstrap'
export default function ActionTooltip ({ children, notForm }) { export default function ActionTooltip ({ children, notForm, overlayText }) {
// if we're in a form, we want to hide tooltip on submit // if we're in a form, we want to hide tooltip on submit
let formik let formik
if (!notForm) { if (!notForm) {
@ -12,7 +12,7 @@ export default function ActionTooltip ({ children, notForm }) {
placement='bottom' placement='bottom'
overlay={ overlay={
<Tooltip> <Tooltip>
1 sat {overlayText || '1 sat'}
</Tooltip> </Tooltip>
} }
trigger={['hover', 'focus']} trigger={['hover', 'focus']}

View File

@ -63,14 +63,22 @@ export default function Comment ({ item, children, replyOpen, includeParent, roo
ref={ref} className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`} ref={ref} className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`}
> >
<div className={`${itemStyles.item} ${styles.item}`}> <div className={`${itemStyles.item} ${styles.item}`}>
<UpVote itemId={item.id} meSats={item.meSats} className={styles.upvote} /> <UpVote item={item} className={styles.upvote} />
<div className={`${itemStyles.hunk} ${styles.hunk}`}> <div className={`${itemStyles.hunk} ${styles.hunk}`}>
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>
<div className={`${itemStyles.other} ${styles.other}`}> <div className={`${itemStyles.other} ${styles.other}`}>
<span>{item.sats} sats</span> <span>{item.sats} sats</span>
<span> \ </span> <span> \ </span>
<span>{item.boost} boost</span> {item.boost > 0 &&
<span> \ </span> <>
<span>{item.boost} boost</span>
<span> \ </span>
</>}
{item.tips > 0 &&
<>
<span>{item.tips} tipped</span>
<span> \ </span>
</>}
<Link href={`/items/${item.id}`} passHref> <Link href={`/items/${item.id}`} passHref>
<a onClick={e => e.stopPropagation()} className='text-reset'>{item.ncomments} replies</a> <a onClick={e => e.stopPropagation()} className='text-reset'>{item.ncomments} replies</a>
</Link> </Link>

View File

@ -22,7 +22,7 @@ export default function Item ({ item, rank, children }) {
</div>) </div>)
: <div />} : <div />}
<div className={styles.item}> <div className={styles.item}>
<UpVote itemId={item.id} meSats={item.meSats} className={styles.upvote} /> <UpVote item={item} className={styles.upvote} />
<div className={styles.hunk}> <div className={styles.hunk}>
<div className={`${styles.main} flex-wrap`}> <div className={`${styles.main} flex-wrap`}>
<Link href={`/items/${item.id}`} passHref> <Link href={`/items/${item.id}`} passHref>
@ -44,6 +44,11 @@ export default function Item ({ item, rank, children }) {
<span>{item.boost} boost</span> <span>{item.boost} boost</span>
<span> \ </span> <span> \ </span>
</>} </>}
{item.tips > 0 &&
<>
<span>{item.tips} tipped</span>
<span> \ </span>
</>}
<Link href={`/items/${item.id}`} passHref> <Link href={`/items/${item.id}`} passHref>
<a className='text-reset'>{item.ncomments} comments</a> <a className='text-reset'>{item.ncomments} comments</a>
</Link> </Link>

View File

@ -20,9 +20,17 @@ export default function Seo ({ item, user }) {
desc = desc.replace(/\s+/g, ' ') desc = desc.replace(/\s+/g, ' ')
} }
} else { } else {
desc = `@${item.user.name} stacked ${item.sats} sats ${item.url ? `posting ${item.url}` : ''}` desc = `@${item.user.name} stacked ${(item.sats > 0 ? item.sats - 1 : 0) + item.tips} sats ${item.url ? `posting ${item.url}` : 'with this discussion'}`
}
if (item.ncomments) {
desc += ` [${item.ncomments} comments`
if (item.boost) {
desc += `, ${item.boost} boost`
}
desc += ']'
} else if (item.boost) {
desc += ` [${item.boost} boost]`
} }
desc += ` [${item.ncomments} comments, ${item.boost} boost]`
} }
if (user) { if (user) {
desc = `@${user.name} has [${user.stacked} stacked, ${user.sats} sats, ${user.nitems} posts, ${user.ncomments} comments]` desc = `@${user.name} has [${user.stacked} stacked, ${user.sats} sats, ${user.nitems} posts, ${user.ncomments} comments]`

View File

@ -6,38 +6,61 @@ import { signIn, useSession } from 'next-auth/client'
import { useFundError } from './fund-error' import { useFundError } from './fund-error'
import ActionTooltip from './action-tooltip' import ActionTooltip from './action-tooltip'
import { useItemAct } from './item-act' import { useItemAct } from './item-act'
import Window from '../svgs/window-2-fill.svg'
export default function UpVote ({ itemId, meSats, className }) { export default function UpVote ({ item, className }) {
const [session] = useSession() const [session] = useSession()
const { setError } = useFundError() const { setError } = useFundError()
const { setItem } = useItemAct() const { setItem } = useItemAct()
const [act] = useMutation( const [act] = useMutation(
gql` gql`
mutation act($id: ID!, $act: ItemAct! $sats: Int!) { mutation act($id: ID!, $act: ItemAct! $sats: Int!) {
act(id: $id, act: $act, sats: $sats) act(id: $id, act: $act, sats: $sats) {
act,
sats
}
}`, { }`, {
update (cache, { data: { act } }) { update (cache, { data: { act: { act, sats } } }) {
// read in the cached object so we don't use meSats prop // read in the cached object so we don't use meSats prop
// which can be stale // which can be stale
const item = cache.readFragment({
id: `Item:${itemId}`,
fragment: gql`
fragment actedItem on Item {
meSats
}
`
})
cache.modify({ cache.modify({
id: `Item:${itemId}`, id: `Item:${item.id}`,
fields: { fields: {
meSats (existingMeSats = 0) { meVote (existingMeVote = 0) {
return existingMeSats + act if (act === 'VOTE') {
return existingMeVote + sats
}
return existingMeVote
},
meBoost (existingMeBoost = 0) {
if (act === 'BOOST') {
return existingMeBoost + sats
}
return existingMeBoost
},
meTip (existingMeTip = 0) {
if (act === 'TIP') {
return existingMeTip + sats
}
return existingMeTip
}, },
sats (existingSats = 0) { sats (existingSats = 0) {
return item.meSats === 0 ? existingSats + act : existingSats if (act === 'VOTE') {
return existingSats + sats
}
return existingSats
}, },
boost (existingBoost = 0) { boost (existingBoost = 0) {
return item.meSats >= 1 ? existingBoost + act : existingBoost if (act === 'BOOST') {
return existingBoost + sats
}
return existingBoost
},
tips (existingTips = 0) {
if (act === 'TIP') {
return existingTips + sats
}
return existingTips
} }
} }
}) })
@ -48,29 +71,29 @@ export default function UpVote ({ itemId, meSats, className }) {
return ( return (
<LightningConsumer> <LightningConsumer>
{({ strike }) => {({ strike }) =>
<ActionTooltip notForm> <ActionTooltip notForm overlayText={item?.meVote ? <Window style={{ fill: '#fff' }} /> : '1 sat'}>
<UpArrow <UpArrow
width={24} width={24}
height={24} height={24}
className={ className={
`${styles.upvote} `${styles.upvote}
${className || ''} ${className || ''}
${meSats ? (meSats > 1 ? styles.stimi : styles.voted) : ''}` ${item?.meVote ? styles.voted : ''}`
} }
onClick={ onClick={
session session
? async (e) => { ? async (e) => {
e.stopPropagation() e.stopPropagation()
if (meSats >= 1) { if (item?.meVote) {
setItem({ itemId, act, strike }) setItem({ itemId: item.id, act, strike })
return return
} }
strike() strike()
if (!itemId) return if (!item) return
try { try {
await act({ variables: { id: itemId, act: 'VOTE', sats: 1 } }) await act({ variables: { id: item.id, act: 'VOTE', sats: 1 } })
} catch (error) { } catch (error) {
if (error.toString().includes('insufficient funds')) { if (error.toString().includes('insufficient funds')) {
setError(true) setError(true)

View File

@ -12,7 +12,10 @@ export const COMMENT_FIELDS = gql`
} }
sats sats
boost boost
meSats tips
meVote
meBoost
meTip
ncomments ncomments
root { root {
id id

View File

@ -13,7 +13,10 @@ export const ITEM_FIELDS = gql`
} }
sats sats
boost boost
meSats tips
meVote
meBoost
meTip
ncomments ncomments
root { root {
id id

1
svgs/window-2-fill.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 3h18a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zm17 7H4v9h16v-9zm-5-4v2h4V6h-4z"/></svg>

After

Width:  |  Height:  |  Size: 240 B