Add notification for bounty award as tip
This commit is contained in:
parent
b8f9f791b9
commit
353648566f
|
@ -1046,3 +1046,47 @@ export const createContractResolvedNotifications = async (
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const createBountyNotification = async (
|
||||||
|
fromUser: User,
|
||||||
|
toUserId: string,
|
||||||
|
amount: number,
|
||||||
|
idempotencyKey: string,
|
||||||
|
contract: Contract,
|
||||||
|
commentId?: string
|
||||||
|
) => {
|
||||||
|
const privateUser = await getPrivateUser(toUserId)
|
||||||
|
if (!privateUser) return
|
||||||
|
const { sendToBrowser } = getNotificationDestinationsForUser(
|
||||||
|
privateUser,
|
||||||
|
'tip_received'
|
||||||
|
)
|
||||||
|
if (!sendToBrowser) return
|
||||||
|
|
||||||
|
const slug = commentId
|
||||||
|
const notificationRef = firestore
|
||||||
|
.collection(`/users/${toUserId}/notifications`)
|
||||||
|
.doc(idempotencyKey)
|
||||||
|
const notification: Notification = {
|
||||||
|
id: idempotencyKey,
|
||||||
|
userId: toUserId,
|
||||||
|
reason: 'tip_received',
|
||||||
|
createdTime: Date.now(),
|
||||||
|
isSeen: false,
|
||||||
|
sourceId: commentId ? commentId : contract.id,
|
||||||
|
sourceType: 'tip',
|
||||||
|
sourceUpdateType: 'created',
|
||||||
|
sourceUserName: fromUser.name,
|
||||||
|
sourceUserUsername: fromUser.username,
|
||||||
|
sourceUserAvatarUrl: fromUser.avatarUrl,
|
||||||
|
sourceText: amount.toString(),
|
||||||
|
sourceContractCreatorUsername: contract.creatorUsername,
|
||||||
|
sourceContractTitle: contract.question,
|
||||||
|
sourceContractSlug: contract.slug,
|
||||||
|
sourceSlug: slug,
|
||||||
|
sourceTitle: contract.question,
|
||||||
|
}
|
||||||
|
return await notificationRef.set(removeUndefinedProps(notification))
|
||||||
|
|
||||||
|
// maybe TODO: send email notification to comment creator
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { isProd } from './utils'
|
||||||
import { CommentBountyDepositTxn, CommentBountyWithdrawalTxn } from 'common/txn'
|
import { CommentBountyDepositTxn, CommentBountyWithdrawalTxn } from 'common/txn'
|
||||||
import { runTxn } from 'functions/src/transact'
|
import { runTxn } from 'functions/src/transact'
|
||||||
import { Comment } from 'common/comment'
|
import { Comment } from 'common/comment'
|
||||||
|
import { createBountyNotification } from 'functions/src/create-notification'
|
||||||
|
|
||||||
const bodySchema = z.object({
|
const bodySchema = z.object({
|
||||||
contractId: z.string(),
|
contractId: z.string(),
|
||||||
|
@ -78,7 +79,7 @@ export const awardcommentbounty = newEndpoint({}, async (req, auth) => {
|
||||||
if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
|
if (!isFinite(amount)) throw new APIError(400, 'Invalid amount')
|
||||||
|
|
||||||
// run as transaction to prevent race conditions
|
// run as transaction to prevent race conditions
|
||||||
return await firestore.runTransaction(async (transaction) => {
|
const res = await firestore.runTransaction(async (transaction) => {
|
||||||
const userDoc = firestore.doc(`users/${auth.uid}`)
|
const userDoc = firestore.doc(`users/${auth.uid}`)
|
||||||
const userSnap = await transaction.get(userDoc)
|
const userSnap = await transaction.get(userDoc)
|
||||||
if (!userSnap.exists) throw new APIError(400, 'User not found')
|
if (!userSnap.exists) throw new APIError(400, 'User not found')
|
||||||
|
@ -138,8 +139,21 @@ export const awardcommentbounty = newEndpoint({}, async (req, auth) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
return result
|
return { ...result, comment, contract, user }
|
||||||
})
|
})
|
||||||
|
if (res.txn?.id) {
|
||||||
|
const { comment, contract, user } = res
|
||||||
|
await createBountyNotification(
|
||||||
|
user,
|
||||||
|
comment.userId,
|
||||||
|
amount,
|
||||||
|
res.txn.id,
|
||||||
|
contract,
|
||||||
|
comment.id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
})
|
})
|
||||||
|
|
||||||
const firestore = admin.firestore()
|
const firestore = admin.firestore()
|
||||||
|
|
|
@ -7,14 +7,13 @@ import { Row } from './layout/row'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { TextButton } from 'web/components/text-button'
|
import { TextButton } from 'web/components/text-button'
|
||||||
import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
|
import { COMMENT_BOUNTY_AMOUNT } from 'common/economy'
|
||||||
|
import { formatMoney } from 'common/util/format'
|
||||||
|
|
||||||
export function AwardBountyButton(prop: {
|
export function AwardBountyButton(prop: {
|
||||||
comment: ContractComment
|
comment: ContractComment
|
||||||
contract: Contract
|
contract: Contract
|
||||||
}) {
|
}) {
|
||||||
const { comment, contract } = prop
|
const { comment, contract } = prop
|
||||||
const { bountiesAwarded } = comment
|
|
||||||
const amountAwarded = bountiesAwarded ?? 0
|
|
||||||
|
|
||||||
const me = useUser()
|
const me = useUser()
|
||||||
|
|
||||||
|
@ -39,7 +38,7 @@ export function AwardBountyButton(prop: {
|
||||||
return (
|
return (
|
||||||
<Row className={clsx('-ml-2 items-center gap-0.5', !canUp ? '-ml-6' : '')}>
|
<Row className={clsx('-ml-2 items-center gap-0.5', !canUp ? '-ml-6' : '')}>
|
||||||
<TextButton className={'font-bold'} onClick={submit}>
|
<TextButton className={'font-bold'} onClick={submit}>
|
||||||
Award
|
Award {formatMoney(COMMENT_BOUNTY_AMOUNT)}
|
||||||
</TextButton>
|
</TextButton>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
|
|
|
@ -24,9 +24,9 @@ import {
|
||||||
HOUSE_LIQUIDITY_PROVIDER_ID,
|
HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||||
} from 'common/antes'
|
} from 'common/antes'
|
||||||
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
import { useIsMobile } from 'web/hooks/use-is-mobile'
|
||||||
import { formatMoney } from 'common/lib/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { Button } from 'web/components/button'
|
import { Button } from 'web/components/button'
|
||||||
import { MINUTE_MS } from 'common/lib/util/time'
|
import { MINUTE_MS } from 'common/util/time'
|
||||||
|
|
||||||
export function ContractTabs(props: { contract: Contract; bets: Bet[] }) {
|
export function ContractTabs(props: { contract: Contract; bets: Bet[] }) {
|
||||||
const { contract, bets } = props
|
const { contract, bets } = props
|
||||||
|
|
Loading…
Reference in New Issue
Block a user