refine tipping experience, removing notion of upvote from UX

This commit is contained in:
keyan 2022-01-20 17:04:12 -06:00
parent b6b5cea1f5
commit 5d49ecc536
11 changed files with 70 additions and 153 deletions

View File

@ -362,7 +362,7 @@ export default {
return await updateItem(parent, { id, data: { text } }, { me, models }) return await updateItem(parent, { id, data: { text } }, { me, models })
}, },
act: async (parent, { id, act, sats, tipDefault }, { me, models }) => { act: async (parent, { id, sats }, { me, models }) => {
// need to make sure we are logged in // need to make sure we are logged in
if (!me) { if (!me) {
throw new AuthenticationError('you must be logged in') throw new AuthenticationError('you must be logged in')
@ -372,26 +372,20 @@ 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 // disallow self tips
if (act === 'TIP') { const [item] = await models.$queryRaw(`
const [item] = await models.$queryRaw(` ${SELECT}
${SELECT} FROM "Item"
FROM "Item" WHERE id = $1 AND "userId" = $2`, Number(id), me.id)
WHERE id = $1 AND "userId" = $2`, Number(id), me.id) if (item) {
if (item) { throw new UserInputError('cannot tip your self')
throw new UserInputError('cannot tip your self')
}
// if tipDefault, set on user
if (tipDefault) {
await models.user.update({ where: { id: me.id }, data: { tipDefault: sats } })
}
} }
await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, ${act}, ${Number(sats)})`) const [{ item_act: vote }] = await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, 'TIP', ${Number(sats)})`)
return { return {
sats, vote,
act sats
} }
} }
}, },
@ -448,6 +442,27 @@ export default {
}, },
where: { where: {
itemId: item.id, itemId: item.id,
userId: {
not: item.userId
},
act: {
not: 'BOOST'
}
}
})
return sats || 0
},
upvotes: async (item, args, { models }) => {
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
userId: {
not: item.userId
},
act: 'VOTE' act: 'VOTE'
} }
}) })
@ -467,35 +482,6 @@ export default {
return sats || 0 return sats || 0
}, },
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
const { sum: { sats } } = await models.itemAct.aggregate({
sum: {
sats: true
},
where: {
itemId: item.id,
userId: me.id,
act: 'VOTE'
}
})
return sats || 0
},
meSats: async (item, args, { me, models }) => { meSats: async (item, args, { me, models }) => {
if (!me) return 0 if (!me) return 0
@ -522,22 +508,6 @@ export default {
mine: async (item, args, { me, models }) => { mine: async (item, args, { me, models }) => {
return me?.id === item.userId return me?.id === item.userId
}, },
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

@ -10,15 +10,9 @@ export default gql`
dupes(url: String!): [Item!] dupes(url: String!): [Item!]
} }
enum ItemAct {
VOTE
BOOST
TIP
}
type ItemActResult { type ItemActResult {
vote: Int!
sats: Int! sats: Int!
act: ItemAct!
} }
extend type Mutation { extend type Mutation {
@ -28,7 +22,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, tipDefault: Boolean): ItemActResult! act(id: ID!, sats: Int): ItemActResult!
} }
type Items { type Items {
@ -53,13 +47,11 @@ export default gql`
root: Item root: Item
user: User! user: User!
depth: Int! depth: Int!
sats: Int!
boost: Int!
tips: Int!
mine: Boolean! mine: Boolean!
meVote: Int! boost: Int!
sats: Int!
upvotes: Int!
meSats: Int! meSats: Int!
meTip: Int!
ncomments: Int! ncomments: Int!
comments: [Item!]! comments: [Item!]!
path: String path: String

View File

@ -79,7 +79,7 @@ export default function Comment ({
<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 title={`${item.sats} upvotes \\ ${item.tips} tipped${item.meSats > 0 ? ` (${item.meSats} from me)` : ''}`}>{item.sats + item.tips} sats</span> <span title={`from ${item.upvotes} users (${item.meSats} from me)`}>{item.sats} sats</span>
<span> \ </span> <span> \ </span>
{item.boost > 0 && {item.boost > 0 &&
<> <>

View File

@ -57,13 +57,11 @@ export function ItemActModal () {
default: false default: false
}} }}
schema={ActSchema} schema={ActSchema}
onSubmit={async ({ amount, tipDefault, submit }) => { onSubmit={async ({ amount }) => {
await item.act({ await item.act({
variables: { variables: {
id: item.itemId, id: item.itemId,
act: submit, sats: Number(amount)
sats: Number(amount),
tipDefault
} }
}) })
await item.strike() await item.strike()

View File

@ -50,7 +50,7 @@ export default function Item ({ item, rank, children }) {
<div className={`${styles.other}`}> <div className={`${styles.other}`}>
{!item.position && {!item.position &&
<> <>
<span title={`${item.sats} upvotes \\ ${item.tips} tipped${item.meSats > 0 ? ` (${item.meSats} from me)` : ''}`}>{item.sats + item.tips} sats</span> <span title={`from ${item.upvotes} users (${item.meSats} sats from me)`}>{item.sats} sats</span>
<span> \ </span> <span> \ </span>
</>} </>}
{item.boost > 0 && {item.boost > 0 &&

View File

@ -20,7 +20,7 @@ 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 > 0 ? item.sats - 1 : 0) + item.tips} sats ${item.url ? `posting ${item.url}` : 'with this discussion'}` desc = `@${item.user.name} stacked ${item.sats} sats ${item.url ? `posting ${item.url}` : 'with this discussion'}`
} }
if (item.ncomments) { if (item.ncomments) {
desc += ` [${item.ncomments} comments` desc += ` [${item.ncomments} comments`

View File

@ -104,60 +104,29 @@ export default function UpVote ({ item, className }) {
const [act] = useMutation( const [act] = useMutation(
gql` gql`
mutation act($id: ID!, $act: ItemAct! $sats: Int!, $tipDefault: Boolean) { mutation act($id: ID!, $sats: Int!) {
act(id: $id, act: $act, sats: $sats, tipDefault: $tipDefault) { act(id: $id, sats: $sats) {
act, vote,
sats sats
} }
}`, { }`, {
update (cache, { data: { act: { act, sats } } }) { update (cache, { data: { act: { vote, sats } } }) {
// read in the cached object so we don't use meSats prop
// which can be stale
if (act === 'VOTE') {
setVoteShow(true)
}
if (act === 'TIP') {
setTipShow(true)
}
cache.modify({ cache.modify({
id: `Item:${item.id}`, id: `Item:${item.id}`,
fields: { fields: {
meVote (existingMeVote = 0) {
if (act === 'VOTE') {
return existingMeVote + sats
}
return existingMeVote
},
meTip (existingMeTip = 0) {
if (act === 'TIP') {
return existingMeTip + sats
}
return existingMeTip
},
sats (existingSats = 0) { sats (existingSats = 0) {
if (act === 'VOTE') { return existingSats + sats
return existingSats + sats
}
return existingSats
}, },
meSats (existingSats = 0) { meSats (existingSats = 0) {
if (act === 'VOTE' || act === 'TIP') { if (existingSats === 0) {
return existingSats + sats setVoteShow(true)
} else {
setTipShow(true)
} }
return existingSats return existingSats + sats
}, },
boost (existingBoost = 0) { upvotes (existingUpvotes = 0) {
if (act === 'BOOST') { return existingUpvotes + vote
return existingBoost + sats
}
return existingBoost
},
tips (existingTips = 0) {
if (act === 'TIP') {
return existingTips + sats
}
return existingTips
} }
} }
}) })
@ -166,15 +135,12 @@ export default function UpVote ({ item, className }) {
) )
const overlayText = () => { const overlayText = () => {
if (item?.meVote) { if (me?.tipDefault) {
if (me?.tipDefault) { return `${me.tipDefault} sat${me.tipDefault > 1 ? 's' : ''}`
return `${me.tipDefault} sat${me.tipDefault > 1 ? 's' : ''}`
}
return '1 sat'
} }
return '1 sat'
} }
const noSelfTips = item?.meVote && item?.mine
const color = getColor(item?.meSats) const color = getColor(item?.meSats)
return ( return (
<LightningConsumer> <LightningConsumer>
@ -186,7 +152,7 @@ export default function UpVote ({ item, className }) {
if (!item || voteLock) return if (!item || voteLock) return
// we can't tip ourselves // we can't tip ourselves
if (noSelfTips) { if (item?.mine) {
return return
} }
@ -200,26 +166,19 @@ export default function UpVote ({ item, className }) {
if (!item || voteLock) return if (!item || voteLock) return
// we can't tip ourselves // we can't tip ourselves
if (noSelfTips) { if (item?.mine) {
return return
} }
if (item?.meVote) { if (item?.meSats) {
setVoteShow(false) setVoteShow(false)
try {
strike()
await act({ variables: { id: item.id, act: 'TIP', sats: me.tipDefault || 1 } })
} catch (e) {
console.log(e)
}
return
} }
strike() strike()
try { try {
setVoteLock(true) setVoteLock(true)
await act({ variables: { id: item.id, act: 'VOTE', sats: 1 } }) await act({ variables: { id: item.id, sats: me.tipDefault || 1 } })
} catch (error) { } catch (error) {
if (error.toString().includes('insufficient funds')) { if (error.toString().includes('insufficient funds')) {
setError(true) setError(true)
@ -233,9 +192,9 @@ export default function UpVote ({ item, className }) {
: signIn : signIn
} }
> >
<ActionTooltip notForm disable={noSelfTips} overlayText={overlayText()}> <ActionTooltip notForm disable={item?.mine} overlayText={overlayText()}>
<div <div
className={`${noSelfTips ? styles.noSelfTips : ''} className={`${item?.mine ? styles.noSelfTips : ''}
${styles.upvoteWrapper}`} ${styles.upvoteWrapper}`}
> >
<UpBolt <UpBolt
@ -244,7 +203,7 @@ export default function UpVote ({ item, className }) {
className={ className={
`${styles.upvote} `${styles.upvote}
${className || ''} ${className || ''}
${noSelfTips ? styles.noSelfTips : ''} ${item?.mine ? styles.noSelfTips : ''}
${item?.meSats ? styles.voted : ''}` ${item?.meSats ? styles.voted : ''}`
} }
style={item?.meSats style={item?.meSats

View File

@ -10,7 +10,7 @@
padding-right: .2rem; padding-right: .2rem;
} }
.noSelfTips .upvote.voted { .noSelfTips {
fill: transparent !important; fill: transparent !important;
filter: none !important; filter: none !important;
} }

View File

@ -11,11 +11,9 @@ export const COMMENT_FIELDS = gql`
id id
} }
sats sats
upvotes
boost boost
tips
meVote
meSats meSats
meTip
mine mine
ncomments ncomments
root { root {

View File

@ -13,11 +13,9 @@ export const ITEM_FIELDS = gql`
id id
} }
sats sats
upvotes
boost boost
tips
meVote
meSats meSats
meTip
ncomments ncomments
mine mine
root { root {

View File

@ -37,9 +37,11 @@ BEGIN
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at) INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
VALUES (act_sats, item_id, user_id, 'TIP', now_utc(), now_utc()); VALUES (act_sats, item_id, user_id, 'TIP', now_utc(), now_utc());
END IF; END IF;
RETURN 1;
END IF; END IF;
END IF; END IF;
RETURN act_sats; RETURN 0;
END; END;
$$; $$;