betting streaks (#777)
* Parse notif, show betting streaks modal, schedule function * Ignore streaks of 0 * Pass notifyFills the contract * Turn 9am into a constant * Lint * Up streak reward, fix timing logic * Change wording
This commit is contained in:
parent
4f3202f90b
commit
00c9fa61c3
|
@ -38,6 +38,7 @@ export type notification_source_types =
|
|||
| 'user'
|
||||
| 'bonus'
|
||||
| 'challenge'
|
||||
| 'betting_streak_bonus'
|
||||
|
||||
export type notification_source_update_types =
|
||||
| 'created'
|
||||
|
@ -66,3 +67,4 @@ export type notification_reason_types =
|
|||
| 'bet_fill'
|
||||
| 'user_joined_from_your_group_invite'
|
||||
| 'challenge_accepted'
|
||||
| 'betting_streak_incremented'
|
||||
|
|
|
@ -4,3 +4,5 @@ export const NUMERIC_FIXED_VAR = 0.005
|
|||
export const NUMERIC_GRAPH_COLOR = '#5fa5f9'
|
||||
export const NUMERIC_TEXT_COLOR = 'text-blue-500'
|
||||
export const UNIQUE_BETTOR_BONUS_AMOUNT = 10
|
||||
export const BETTING_STREAK_BONUS_AMOUNT = 5
|
||||
export const BETTING_STREAK_RESET_HOUR = 9
|
||||
|
|
|
@ -16,7 +16,13 @@ export type Txn<T extends AnyTxnType = AnyTxnType> = {
|
|||
amount: number
|
||||
token: 'M$' // | 'USD' | MarketOutcome
|
||||
|
||||
category: 'CHARITY' | 'MANALINK' | 'TIP' | 'REFERRAL' | 'UNIQUE_BETTOR_BONUS'
|
||||
category:
|
||||
| 'CHARITY'
|
||||
| 'MANALINK'
|
||||
| 'TIP'
|
||||
| 'REFERRAL'
|
||||
| 'UNIQUE_BETTOR_BONUS'
|
||||
| 'BETTING_STREAK_BONUS'
|
||||
|
||||
// Any extra data
|
||||
data?: { [key: string]: any }
|
||||
|
@ -57,7 +63,7 @@ type Referral = {
|
|||
type Bonus = {
|
||||
fromType: 'BANK'
|
||||
toType: 'USER'
|
||||
category: 'UNIQUE_BETTOR_BONUS'
|
||||
category: 'UNIQUE_BETTOR_BONUS' | 'BETTING_STREAK_BONUS'
|
||||
}
|
||||
|
||||
export type DonationTxn = Txn & Donation
|
||||
|
|
|
@ -41,6 +41,8 @@ export type User = {
|
|||
referredByGroupId?: string
|
||||
lastPingTime?: number
|
||||
shouldShowWelcome?: boolean
|
||||
lastBetTime?: number
|
||||
currentBettingStreak?: number
|
||||
}
|
||||
|
||||
export const STARTING_BALANCE = ENV_CONFIG.startingBalance ?? 1000
|
||||
|
|
|
@ -504,3 +504,38 @@ export const createChallengeAcceptedNotification = async (
|
|||
}
|
||||
return await notificationRef.set(removeUndefinedProps(notification))
|
||||
}
|
||||
|
||||
export const createBettingStreakBonusNotification = async (
|
||||
user: User,
|
||||
txnId: string,
|
||||
bet: Bet,
|
||||
contract: Contract,
|
||||
amount: number,
|
||||
idempotencyKey: string
|
||||
) => {
|
||||
const notificationRef = firestore
|
||||
.collection(`/users/${user.id}/notifications`)
|
||||
.doc(idempotencyKey)
|
||||
const notification: Notification = {
|
||||
id: idempotencyKey,
|
||||
userId: user.id,
|
||||
reason: 'betting_streak_incremented',
|
||||
createdTime: Date.now(),
|
||||
isSeen: false,
|
||||
sourceId: txnId,
|
||||
sourceType: 'betting_streak_bonus',
|
||||
sourceUpdateType: 'created',
|
||||
sourceUserName: user.name,
|
||||
sourceUserUsername: user.username,
|
||||
sourceUserAvatarUrl: user.avatarUrl,
|
||||
sourceText: amount.toString(),
|
||||
sourceSlug: `/${contract.creatorUsername}/${contract.slug}/bets/${bet.id}`,
|
||||
sourceTitle: 'Betting Streak Bonus',
|
||||
// Perhaps not necessary, but just in case
|
||||
sourceContractSlug: contract.slug,
|
||||
sourceContractId: contract.id,
|
||||
sourceContractTitle: contract.question,
|
||||
sourceContractCreatorUsername: contract.creatorUsername,
|
||||
}
|
||||
return await notificationRef.set(removeUndefinedProps(notification))
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ export * from './on-create-comment-on-group'
|
|||
export * from './on-create-txn'
|
||||
export * from './on-delete-group'
|
||||
export * from './score-contracts'
|
||||
export * from './reset-betting-streaks'
|
||||
|
||||
// v2
|
||||
export * from './health'
|
||||
|
|
|
@ -3,15 +3,20 @@ import * as admin from 'firebase-admin'
|
|||
import { keyBy, uniq } from 'lodash'
|
||||
|
||||
import { Bet, LimitBet } from '../../common/bet'
|
||||
import { getContract, getUser, getValues, isProd, log } from './utils'
|
||||
import { getUser, getValues, isProd, log } from './utils'
|
||||
import {
|
||||
createBetFillNotification,
|
||||
createBettingStreakBonusNotification,
|
||||
createNotification,
|
||||
} from './create-notification'
|
||||
import { filterDefined } from '../../common/util/array'
|
||||
import { Contract } from '../../common/contract'
|
||||
import { runTxn, TxnData } from './transact'
|
||||
import { UNIQUE_BETTOR_BONUS_AMOUNT } from '../../common/numeric-constants'
|
||||
import {
|
||||
BETTING_STREAK_BONUS_AMOUNT,
|
||||
BETTING_STREAK_RESET_HOUR,
|
||||
UNIQUE_BETTOR_BONUS_AMOUNT,
|
||||
} from '../../common/numeric-constants'
|
||||
import {
|
||||
DEV_HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||
HOUSE_LIQUIDITY_PROVIDER_ID,
|
||||
|
@ -38,37 +43,99 @@ export const onCreateBet = functions.firestore
|
|||
.doc(contractId)
|
||||
.update({ lastBetTime, lastUpdatedTime: Date.now() })
|
||||
|
||||
await notifyFills(bet, contractId, eventId)
|
||||
await updateUniqueBettorsAndGiveCreatorBonus(
|
||||
contractId,
|
||||
eventId,
|
||||
bet.userId
|
||||
)
|
||||
})
|
||||
|
||||
const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||
contractId: string,
|
||||
eventId: string,
|
||||
bettorId: string
|
||||
) => {
|
||||
const userContractSnap = await firestore
|
||||
.collection(`contracts`)
|
||||
.doc(contractId)
|
||||
.get()
|
||||
const contract = userContractSnap.data() as Contract
|
||||
|
||||
if (!contract) {
|
||||
log(`Could not find contract ${contractId}`)
|
||||
return
|
||||
}
|
||||
await updateUniqueBettorsAndGiveCreatorBonus(contract, eventId, bet.userId)
|
||||
|
||||
const bettor = await getUser(bet.userId)
|
||||
if (!bettor) return
|
||||
|
||||
await notifyFills(bet, contract, eventId, bettor)
|
||||
await updateBettingStreak(bettor, bet, contract, eventId)
|
||||
|
||||
await firestore.collection('users').doc(bettor.id).update({ lastBetTime })
|
||||
})
|
||||
|
||||
const updateBettingStreak = async (
|
||||
user: User,
|
||||
bet: Bet,
|
||||
contract: Contract,
|
||||
eventId: string
|
||||
) => {
|
||||
const betStreakResetTime = getTodaysBettingStreakResetTime()
|
||||
const lastBetTime = user?.lastBetTime ?? 0
|
||||
|
||||
// If they've already bet after the reset time, or if we haven't hit the reset time yet
|
||||
if (lastBetTime > betStreakResetTime || bet.createdTime < betStreakResetTime)
|
||||
return
|
||||
|
||||
const newBettingStreak = (user?.currentBettingStreak ?? 0) + 1
|
||||
// Otherwise, add 1 to their betting streak
|
||||
await firestore.collection('users').doc(user.id).update({
|
||||
currentBettingStreak: newBettingStreak,
|
||||
})
|
||||
|
||||
// Send them the bonus times their streak
|
||||
const bonusAmount = Math.min(
|
||||
BETTING_STREAK_BONUS_AMOUNT * newBettingStreak,
|
||||
100
|
||||
)
|
||||
const fromUserId = isProd()
|
||||
? HOUSE_LIQUIDITY_PROVIDER_ID
|
||||
: DEV_HOUSE_LIQUIDITY_PROVIDER_ID
|
||||
const bonusTxnDetails = {
|
||||
currentBettingStreak: newBettingStreak,
|
||||
}
|
||||
const result = await firestore.runTransaction(async (trans) => {
|
||||
const bonusTxn: TxnData = {
|
||||
fromId: fromUserId,
|
||||
fromType: 'BANK',
|
||||
toId: user.id,
|
||||
toType: 'USER',
|
||||
amount: bonusAmount,
|
||||
token: 'M$',
|
||||
category: 'BETTING_STREAK_BONUS',
|
||||
description: JSON.stringify(bonusTxnDetails),
|
||||
}
|
||||
return await runTxn(trans, bonusTxn)
|
||||
})
|
||||
if (!result.txn) {
|
||||
log("betting streak bonus txn couldn't be made")
|
||||
return
|
||||
}
|
||||
|
||||
await createBettingStreakBonusNotification(
|
||||
user,
|
||||
result.txn.id,
|
||||
bet,
|
||||
contract,
|
||||
bonusAmount,
|
||||
eventId
|
||||
)
|
||||
}
|
||||
|
||||
const updateUniqueBettorsAndGiveCreatorBonus = async (
|
||||
contract: Contract,
|
||||
eventId: string,
|
||||
bettorId: string
|
||||
) => {
|
||||
let previousUniqueBettorIds = contract.uniqueBettorIds
|
||||
|
||||
if (!previousUniqueBettorIds) {
|
||||
const contractBets = (
|
||||
await firestore.collection(`contracts/${contractId}/bets`).get()
|
||||
await firestore.collection(`contracts/${contract.id}/bets`).get()
|
||||
).docs.map((doc) => doc.data() as Bet)
|
||||
|
||||
if (contractBets.length === 0) {
|
||||
log(`No bets for contract ${contractId}`)
|
||||
log(`No bets for contract ${contract.id}`)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -86,7 +153,7 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
|||
if (!contract.uniqueBettorIds || isNewUniqueBettor) {
|
||||
log(`Got ${previousUniqueBettorIds} unique bettors`)
|
||||
isNewUniqueBettor && log(`And a new unique bettor ${bettorId}`)
|
||||
await firestore.collection(`contracts`).doc(contractId).update({
|
||||
await firestore.collection(`contracts`).doc(contract.id).update({
|
||||
uniqueBettorIds: newUniqueBettorIds,
|
||||
uniqueBettorCount: newUniqueBettorIds.length,
|
||||
})
|
||||
|
@ -97,7 +164,7 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
|||
|
||||
// Create combined txn for all new unique bettors
|
||||
const bonusTxnDetails = {
|
||||
contractId: contractId,
|
||||
contractId: contract.id,
|
||||
uniqueBettorIds: newUniqueBettorIds,
|
||||
}
|
||||
const fromUserId = isProd()
|
||||
|
@ -140,14 +207,14 @@ const updateUniqueBettorsAndGiveCreatorBonus = async (
|
|||
}
|
||||
}
|
||||
|
||||
const notifyFills = async (bet: Bet, contractId: string, eventId: string) => {
|
||||
const notifyFills = async (
|
||||
bet: Bet,
|
||||
contract: Contract,
|
||||
eventId: string,
|
||||
user: User
|
||||
) => {
|
||||
if (!bet.fills) return
|
||||
|
||||
const user = await getUser(bet.userId)
|
||||
if (!user) return
|
||||
const contract = await getContract(contractId)
|
||||
if (!contract) return
|
||||
|
||||
const matchedFills = bet.fills.filter((fill) => fill.matchedBetId !== null)
|
||||
const matchedBets = (
|
||||
await Promise.all(
|
||||
|
@ -180,3 +247,7 @@ const notifyFills = async (bet: Bet, contractId: string, eventId: string) => {
|
|||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getTodaysBettingStreakResetTime = () => {
|
||||
return new Date().setUTCHours(BETTING_STREAK_RESET_HOUR, 0, 0, 0)
|
||||
}
|
||||
|
|
38
functions/src/reset-betting-streaks.ts
Normal file
38
functions/src/reset-betting-streaks.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
// check every day if the user has created a bet since 4pm UTC, and if not, reset their streak
|
||||
|
||||
import * as functions from 'firebase-functions'
|
||||
import * as admin from 'firebase-admin'
|
||||
import { User } from '../../common/user'
|
||||
import { DAY_MS } from '../../common/util/time'
|
||||
import { BETTING_STREAK_RESET_HOUR } from '../../common/numeric-constants'
|
||||
const firestore = admin.firestore()
|
||||
|
||||
export const resetBettingStreaksForUsers = functions.pubsub
|
||||
.schedule(`0 ${BETTING_STREAK_RESET_HOUR} * * *`)
|
||||
.onRun(async () => {
|
||||
await resetBettingStreaksInternal()
|
||||
})
|
||||
|
||||
const resetBettingStreaksInternal = async () => {
|
||||
const usersSnap = await firestore.collection('users').get()
|
||||
|
||||
const users = usersSnap.docs.map((doc) => doc.data() as User)
|
||||
|
||||
for (const user of users) {
|
||||
await resetBettingStreakForUser(user)
|
||||
}
|
||||
}
|
||||
|
||||
const resetBettingStreakForUser = async (user: User) => {
|
||||
const betStreakResetTime = Date.now() - DAY_MS
|
||||
// if they made a bet within the last day, don't reset their streak
|
||||
if (
|
||||
(user.lastBetTime ?? 0 > betStreakResetTime) ||
|
||||
!user.currentBettingStreak ||
|
||||
user.currentBettingStreak === 0
|
||||
)
|
||||
return
|
||||
await firestore.collection('users').doc(user.id).update({
|
||||
currentBettingStreak: 0,
|
||||
})
|
||||
}
|
32
web/components/profile/betting-streak-modal.tsx
Normal file
32
web/components/profile/betting-streak-modal.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { Modal } from 'web/components/layout/modal'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
|
||||
export function BettingStreakModal(props: {
|
||||
isOpen: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
const { isOpen, setOpen } = props
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} setOpen={setOpen}>
|
||||
<Col className="items-center gap-4 rounded-md bg-white px-8 py-6">
|
||||
<span className={'text-8xl'}>🔥</span>
|
||||
<span>Betting streaks are here!</span>
|
||||
<Col className={'gap-2'}>
|
||||
<span className={'text-indigo-700'}>• What are they?</span>
|
||||
<span className={'ml-2'}>
|
||||
You get a reward for every consecutive day that you place a bet. The
|
||||
more days you bet in a row, the more you earn!
|
||||
</span>
|
||||
<span className={'text-indigo-700'}>
|
||||
• Where can I check my streak?
|
||||
</span>
|
||||
<span className={'ml-2'}>
|
||||
You can see your current streak on the top right of your profile
|
||||
page.
|
||||
</span>
|
||||
</Col>
|
||||
</Col>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -28,6 +28,7 @@ import { ReferralsButton } from 'web/components/referrals-button'
|
|||
import { formatMoney } from 'common/util/format'
|
||||
import { ShareIconButton } from 'web/components/share-icon-button'
|
||||
import { ENV_CONFIG } from 'common/envs/constants'
|
||||
import { BettingStreakModal } from 'web/components/profile/betting-streak-modal'
|
||||
|
||||
export function UserLink(props: {
|
||||
name: string
|
||||
|
@ -65,10 +66,13 @@ export function UserPage(props: { user: User }) {
|
|||
const isCurrentUser = user.id === currentUser?.id
|
||||
const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id)
|
||||
const [showConfetti, setShowConfetti] = useState(false)
|
||||
const [showBettingStreakModal, setShowBettingStreakModal] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const claimedMana = router.query['claimed-mana'] === 'yes'
|
||||
setShowConfetti(claimedMana)
|
||||
const showBettingStreak = router.query['show'] === 'betting-streak'
|
||||
setShowBettingStreakModal(showBettingStreak)
|
||||
}, [router])
|
||||
|
||||
const profit = user.profitCached.allTime
|
||||
|
@ -80,9 +84,14 @@ export function UserPage(props: { user: User }) {
|
|||
description={user.bio ?? ''}
|
||||
url={`/${user.username}`}
|
||||
/>
|
||||
{showConfetti && (
|
||||
{showConfetti ||
|
||||
(showBettingStreakModal && (
|
||||
<FullscreenConfetti recycle={false} numberOfPieces={300} />
|
||||
)}
|
||||
))}
|
||||
<BettingStreakModal
|
||||
isOpen={showBettingStreakModal}
|
||||
setOpen={setShowBettingStreakModal}
|
||||
/>
|
||||
{/* Banner image up top, with an circle avatar overlaid */}
|
||||
<div
|
||||
className="h-32 w-full bg-cover bg-center sm:h-40"
|
||||
|
@ -114,9 +123,14 @@ export function UserPage(props: { user: User }) {
|
|||
|
||||
{/* Profile details: name, username, bio, and link to twitter/discord */}
|
||||
<Col className="mx-4 -mt-6">
|
||||
<Row className={'items-center gap-2'}>
|
||||
<Row className={'justify-between'}>
|
||||
<Col>
|
||||
<span className="text-2xl font-bold">{user.name}</span>
|
||||
<span className="mt-1 text-gray-500">
|
||||
<span className="text-gray-500">@{user.username}</span>
|
||||
</Col>
|
||||
<Col className={'justify-center gap-4'}>
|
||||
<Row>
|
||||
<Col className={'items-center text-gray-500'}>
|
||||
<span
|
||||
className={clsx(
|
||||
'text-md',
|
||||
|
@ -124,12 +138,19 @@ export function UserPage(props: { user: User }) {
|
|||
)}
|
||||
>
|
||||
{formatMoney(profit)}
|
||||
</span>{' '}
|
||||
profit
|
||||
</span>
|
||||
<span>profit</span>
|
||||
</Col>
|
||||
<Col
|
||||
className={'cursor-pointer items-center text-gray-500'}
|
||||
onClick={() => setShowBettingStreakModal(true)}
|
||||
>
|
||||
<span>🔥{user.currentBettingStreak ?? 0}</span>
|
||||
<span>streak</span>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<span className="text-gray-500">@{user.username}</span>
|
||||
|
||||
<Spacer h={4} />
|
||||
{user.bio && (
|
||||
<>
|
||||
|
|
|
@ -71,11 +71,15 @@ export function groupNotifications(notifications: Notification[]) {
|
|||
const notificationsGroupedByDay = notificationGroupsByDay[day]
|
||||
const incomeNotifications = notificationsGroupedByDay.filter(
|
||||
(notification) =>
|
||||
notification.sourceType === 'bonus' || notification.sourceType === 'tip'
|
||||
notification.sourceType === 'bonus' ||
|
||||
notification.sourceType === 'tip' ||
|
||||
notification.sourceType === 'betting_streak_bonus'
|
||||
)
|
||||
const normalNotificationsGroupedByDay = notificationsGroupedByDay.filter(
|
||||
(notification) =>
|
||||
notification.sourceType !== 'bonus' && notification.sourceType !== 'tip'
|
||||
notification.sourceType !== 'bonus' &&
|
||||
notification.sourceType !== 'tip' &&
|
||||
notification.sourceType !== 'betting_streak_bonus'
|
||||
)
|
||||
if (incomeNotifications.length > 0) {
|
||||
notificationGroups = notificationGroups.concat({
|
||||
|
|
|
@ -31,7 +31,10 @@ import {
|
|||
import { TrendingUpIcon } from '@heroicons/react/outline'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { groupPath } from 'web/lib/firebase/groups'
|
||||
import { UNIQUE_BETTOR_BONUS_AMOUNT } from 'common/numeric-constants'
|
||||
import {
|
||||
BETTING_STREAK_BONUS_AMOUNT,
|
||||
UNIQUE_BETTOR_BONUS_AMOUNT,
|
||||
} from 'common/numeric-constants'
|
||||
import { groupBy, sum, uniq } from 'lodash'
|
||||
import { track } from '@amplitude/analytics-browser'
|
||||
import { Pagination } from 'web/components/pagination'
|
||||
|
@ -229,39 +232,39 @@ function IncomeNotificationGroupItem(props: {
|
|||
(n) => n.sourceType
|
||||
)
|
||||
for (const sourceType in groupedNotificationsBySourceType) {
|
||||
// Source title splits by contracts and groups
|
||||
// Source title splits by contracts, groups, betting streak bonus
|
||||
const groupedNotificationsBySourceTitle = groupBy(
|
||||
groupedNotificationsBySourceType[sourceType],
|
||||
(notification) => {
|
||||
return notification.sourceTitle ?? notification.sourceContractTitle
|
||||
}
|
||||
)
|
||||
for (const contractId in groupedNotificationsBySourceTitle) {
|
||||
const notificationsForContractId =
|
||||
groupedNotificationsBySourceTitle[contractId]
|
||||
if (notificationsForContractId.length === 1) {
|
||||
newNotifications.push(notificationsForContractId[0])
|
||||
for (const sourceTitle in groupedNotificationsBySourceTitle) {
|
||||
const notificationsForSourceTitle =
|
||||
groupedNotificationsBySourceTitle[sourceTitle]
|
||||
if (notificationsForSourceTitle.length === 1) {
|
||||
newNotifications.push(notificationsForSourceTitle[0])
|
||||
continue
|
||||
}
|
||||
let sum = 0
|
||||
notificationsForContractId.forEach(
|
||||
notificationsForSourceTitle.forEach(
|
||||
(notification) =>
|
||||
notification.sourceText &&
|
||||
(sum = parseInt(notification.sourceText) + sum)
|
||||
)
|
||||
const uniqueUsers = uniq(
|
||||
notificationsForContractId.map((notification) => {
|
||||
notificationsForSourceTitle.map((notification) => {
|
||||
return notification.sourceUserUsername
|
||||
})
|
||||
)
|
||||
|
||||
const newNotification = {
|
||||
...notificationsForContractId[0],
|
||||
...notificationsForSourceTitle[0],
|
||||
sourceText: sum.toString(),
|
||||
sourceUserUsername:
|
||||
uniqueUsers.length > 1
|
||||
? MULTIPLE_USERS_KEY
|
||||
: notificationsForContractId[0].sourceType,
|
||||
: notificationsForSourceTitle[0].sourceType,
|
||||
}
|
||||
newNotifications.push(newNotification)
|
||||
}
|
||||
|
@ -362,7 +365,8 @@ function IncomeNotificationItem(props: {
|
|||
justSummary?: boolean
|
||||
}) {
|
||||
const { notification, justSummary } = props
|
||||
const { sourceType, sourceUserName, sourceUserUsername } = notification
|
||||
const { sourceType, sourceUserName, sourceUserUsername, sourceText } =
|
||||
notification
|
||||
const [highlighted] = useState(!notification.isSeen)
|
||||
const { width } = useWindowSize()
|
||||
const isMobile = (width && width < 768) || false
|
||||
|
@ -370,19 +374,74 @@ function IncomeNotificationItem(props: {
|
|||
setNotificationsAsSeen([notification])
|
||||
}, [notification])
|
||||
|
||||
function getReasonForShowingIncomeNotification(simple: boolean) {
|
||||
function reasonAndLink(simple: boolean) {
|
||||
const { sourceText } = notification
|
||||
let reasonText = ''
|
||||
if (sourceType === 'bonus' && sourceText) {
|
||||
reasonText = !simple
|
||||
? `Bonus for ${
|
||||
parseInt(sourceText) / UNIQUE_BETTOR_BONUS_AMOUNT
|
||||
} unique traders`
|
||||
} unique traders on`
|
||||
: 'bonus on'
|
||||
} else if (sourceType === 'tip') {
|
||||
reasonText = !simple ? `tipped you` : `in tips on`
|
||||
reasonText = !simple ? `tipped you on` : `in tips on`
|
||||
} else if (sourceType === 'betting_streak_bonus' && sourceText) {
|
||||
reasonText = `for your ${
|
||||
parseInt(sourceText) / BETTING_STREAK_BONUS_AMOUNT
|
||||
}-day`
|
||||
}
|
||||
return reasonText
|
||||
return (
|
||||
<>
|
||||
{reasonText}
|
||||
{sourceType === 'betting_streak_bonus' ? (
|
||||
simple ? (
|
||||
<span className={'ml-1 font-bold'}>Betting Streak</span>
|
||||
) : (
|
||||
<SiteLink
|
||||
className={'ml-1 font-bold'}
|
||||
href={'/betting-streak-bonus'}
|
||||
>
|
||||
Betting Streak
|
||||
</SiteLink>
|
||||
)
|
||||
) : (
|
||||
<QuestionOrGroupLink
|
||||
notification={notification}
|
||||
ignoreClick={isMobile}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const incomeNotificationLabel = () => {
|
||||
return sourceText ? (
|
||||
<span className="text-primary">
|
||||
{'+' + formatMoney(parseInt(sourceText))}
|
||||
</span>
|
||||
) : (
|
||||
<div />
|
||||
)
|
||||
}
|
||||
|
||||
const getIncomeSourceUrl = () => {
|
||||
const {
|
||||
sourceId,
|
||||
sourceContractCreatorUsername,
|
||||
sourceContractSlug,
|
||||
sourceSlug,
|
||||
} = notification
|
||||
if (sourceType === 'tip' && sourceContractSlug)
|
||||
return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${sourceSlug}`
|
||||
if (sourceType === 'tip' && sourceSlug) return `${groupPath(sourceSlug)}`
|
||||
if (sourceType === 'challenge') return `${sourceSlug}`
|
||||
if (sourceType === 'betting_streak_bonus')
|
||||
return `/${sourceUserUsername}/?show=betting-streak`
|
||||
if (sourceContractCreatorUsername && sourceContractSlug)
|
||||
return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${getSourceIdForLinkComponent(
|
||||
sourceId ?? '',
|
||||
sourceType
|
||||
)}`
|
||||
}
|
||||
|
||||
if (justSummary) {
|
||||
|
@ -392,19 +451,9 @@ function IncomeNotificationItem(props: {
|
|||
<div className={'flex pl-1 sm:pl-0'}>
|
||||
<div className={'inline-flex overflow-hidden text-ellipsis pl-1'}>
|
||||
<div className={'mr-1 text-black'}>
|
||||
<NotificationTextLabel
|
||||
className={'line-clamp-1'}
|
||||
notification={notification}
|
||||
justSummary={true}
|
||||
/>
|
||||
{incomeNotificationLabel()}
|
||||
</div>
|
||||
<span className={'flex truncate'}>
|
||||
{getReasonForShowingIncomeNotification(true)}
|
||||
<QuestionOrGroupLink
|
||||
notification={notification}
|
||||
ignoreClick={isMobile}
|
||||
/>
|
||||
</span>
|
||||
<span className={'flex truncate'}>{reasonAndLink(true)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -421,18 +470,16 @@ function IncomeNotificationItem(props: {
|
|||
>
|
||||
<div className={'relative'}>
|
||||
<SiteLink
|
||||
href={getSourceUrl(notification) ?? ''}
|
||||
href={getIncomeSourceUrl() ?? ''}
|
||||
className={'absolute left-0 right-0 top-0 bottom-0 z-0'}
|
||||
/>
|
||||
<Row className={'items-center text-gray-500 sm:justify-start'}>
|
||||
<div className={'line-clamp-2 flex max-w-xl shrink '}>
|
||||
<div className={'inline'}>
|
||||
<span className={'mr-1'}>
|
||||
<NotificationTextLabel notification={notification} />
|
||||
</span>
|
||||
<span className={'mr-1'}>{incomeNotificationLabel()}</span>
|
||||
</div>
|
||||
<span>
|
||||
{sourceType != 'bonus' &&
|
||||
{sourceType === 'tip' &&
|
||||
(sourceUserUsername === MULTIPLE_USERS_KEY ? (
|
||||
<span className={'mr-1 truncate'}>Multiple users</span>
|
||||
) : (
|
||||
|
@ -443,8 +490,7 @@ function IncomeNotificationItem(props: {
|
|||
short={true}
|
||||
/>
|
||||
))}
|
||||
{getReasonForShowingIncomeNotification(false)} {' on'}
|
||||
<QuestionOrGroupLink notification={notification} />
|
||||
{reasonAndLink(false)}
|
||||
</span>
|
||||
</div>
|
||||
</Row>
|
||||
|
@ -794,9 +840,6 @@ function getSourceUrl(notification: Notification) {
|
|||
// User referral:
|
||||
if (sourceType === 'user' && !sourceContractSlug)
|
||||
return `/${sourceUserUsername}`
|
||||
if (sourceType === 'tip' && sourceContractSlug)
|
||||
return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${sourceSlug}`
|
||||
if (sourceType === 'tip' && sourceSlug) return `${groupPath(sourceSlug)}`
|
||||
if (sourceType === 'challenge') return `${sourceSlug}`
|
||||
if (sourceContractCreatorUsername && sourceContractSlug)
|
||||
return `/${sourceContractCreatorUsername}/${sourceContractSlug}#${getSourceIdForLinkComponent(
|
||||
|
@ -885,12 +928,6 @@ function NotificationTextLabel(props: {
|
|||
return (
|
||||
<span className="text-blue-400">{formatMoney(parseInt(sourceText))}</span>
|
||||
)
|
||||
} else if ((sourceType === 'bonus' || sourceType === 'tip') && sourceText) {
|
||||
return (
|
||||
<span className="text-primary">
|
||||
{'+' + formatMoney(parseInt(sourceText))}
|
||||
</span>
|
||||
)
|
||||
} else if (sourceType === 'bet' && sourceText) {
|
||||
return (
|
||||
<>
|
||||
|
|
Loading…
Reference in New Issue
Block a user