Show users position or recent bet with comments
This commit is contained in:
		
							parent
							
								
									3e600f45b5
								
							
						
					
					
						commit
						d0700c1221
					
				|  | @ -31,6 +31,8 @@ type BaseActivityItem = { | |||
| 
 | ||||
| export type CommentInputItem = BaseActivityItem & { | ||||
|   type: 'commentInput' | ||||
|   bets: Bet[] | ||||
|   comments: Comment[] | ||||
| } | ||||
| 
 | ||||
| export type DescriptionItem = BaseActivityItem & { | ||||
|  | @ -48,12 +50,13 @@ export type BetItem = BaseActivityItem & { | |||
|   bet: Bet | ||||
|   hideOutcome: boolean | ||||
|   smallAvatar: boolean | ||||
|   hideComment?: boolean | ||||
| } | ||||
| 
 | ||||
| export type CommentItem = BaseActivityItem & { | ||||
|   type: 'comment' | ||||
|   comment: Comment | ||||
|   bet: Bet | undefined | ||||
|   betsBySameUser: Bet[] | ||||
|   hideOutcome: boolean | ||||
|   truncate: boolean | ||||
|   smallAvatar: boolean | ||||
|  | @ -129,7 +132,7 @@ function groupBets( | |||
|           type: 'comment' as const, | ||||
|           id: bet.id, | ||||
|           comment, | ||||
|           bet, | ||||
|           betsBySameUser: [bet], | ||||
|           contract, | ||||
|           hideOutcome, | ||||
|           truncate: abbreviated, | ||||
|  | @ -280,7 +283,7 @@ function groupBetsAndComments( | |||
|       id: comment.id, | ||||
|       contract: contract, | ||||
|       comment, | ||||
|       bet: undefined, | ||||
|       betsBySameUser: [], | ||||
|       truncate: abbreviated, | ||||
|       hideOutcome: true, | ||||
|       smallAvatar, | ||||
|  | @ -308,6 +311,37 @@ function groupBetsAndComments( | |||
|   return abbrItems | ||||
| } | ||||
| 
 | ||||
| function getCommentsWithPositions( | ||||
|   bets: Bet[], | ||||
|   comments: Comment[], | ||||
|   contract: Contract | ||||
| ) { | ||||
|   function mapBetsByUserId(bets: Bet[]) { | ||||
|     return bets.reduce((acc, bet) => { | ||||
|       const userId = bet.userId | ||||
|       if (!acc[userId]) { | ||||
|         acc[userId] = [] | ||||
|       } | ||||
|       acc[userId].push(bet) | ||||
|       return acc | ||||
|     }, {} as { [userId: string]: Bet[] }) | ||||
|   } | ||||
|   const betsByUserId = mapBetsByUserId(bets) | ||||
| 
 | ||||
|   const items = comments.map((comment) => ({ | ||||
|     type: 'comment' as const, | ||||
|     id: comment.id, | ||||
|     contract: contract, | ||||
|     comment, | ||||
|     betsBySameUser: bets.length === 0 ? [] : betsByUserId[comment.userId], | ||||
|     truncate: true, | ||||
|     hideOutcome: false, | ||||
|     smallAvatar: false, | ||||
|   })) | ||||
| 
 | ||||
|   return items | ||||
| } | ||||
| 
 | ||||
| export function getAllContractActivityItems( | ||||
|   contract: Contract, | ||||
|   bets: Bet[], | ||||
|  | @ -361,6 +395,8 @@ export function getAllContractActivityItems( | |||
|       type: 'commentInput', | ||||
|       id: 'commentInput', | ||||
|       contract, | ||||
|       bets: [], | ||||
|       comments: [], | ||||
|     }) | ||||
|   } else { | ||||
|     items.push( | ||||
|  | @ -385,6 +421,8 @@ export function getAllContractActivityItems( | |||
|       type: 'commentInput', | ||||
|       id: 'commentInput', | ||||
|       contract, | ||||
|       bets: [], | ||||
|       comments: [], | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|  | @ -467,32 +505,20 @@ export function getSpecificContractActivityItems( | |||
|           contract, | ||||
|           hideOutcome: false, | ||||
|           smallAvatar: false, | ||||
|           hideComment: true, | ||||
|         })) | ||||
|       ) | ||||
|       break | ||||
| 
 | ||||
|     case 'comments': | ||||
|       const onlyBetsWithComments = bets.filter((bet) => | ||||
|         comments.some((comment) => comment.betId === bet.id) | ||||
|       ) | ||||
|       items.push( | ||||
|         ...groupBetsAndComments( | ||||
|           onlyBetsWithComments, | ||||
|           comments, | ||||
|           contract, | ||||
|           user?.id, | ||||
|           { | ||||
|             hideOutcome: false, | ||||
|             abbreviated: false, | ||||
|             smallAvatar: false, | ||||
|             reversed: false, | ||||
|           } | ||||
|         ) | ||||
|       ) | ||||
|       items.push(...getCommentsWithPositions(bets, comments, contract)) | ||||
| 
 | ||||
|       items.push({ | ||||
|         type: 'commentInput', | ||||
|         id: 'commentInput', | ||||
|         contract, | ||||
|         bets: bets, | ||||
|         comments: comments, | ||||
|       }) | ||||
|       break | ||||
|   } | ||||
|  |  | |||
|  | @ -56,8 +56,7 @@ import { trackClick } from '../../lib/firebase/tracking' | |||
| import { firebaseLogin } from '../../lib/firebase/users' | ||||
| import { DAY_MS } from '../../../common/util/time' | ||||
| import NewContractBadge from '../new-contract-badge' | ||||
| import { useUserContractBets } from '../../hooks/use-user-bets' | ||||
| import { useSaveShares } from '../use-save-shares' | ||||
| import { calculateCpmmSale } from '../../../common/calculate-cpmm' | ||||
| 
 | ||||
| export function FeedItems(props: { | ||||
|   contract: Contract | ||||
|  | @ -131,29 +130,38 @@ function FeedItem(props: { item: ActivityItem }) { | |||
| export function FeedComment(props: { | ||||
|   contract: Contract | ||||
|   comment: Comment | ||||
|   bet: Bet | undefined | ||||
|   betsBySameUser: Bet[] | ||||
|   hideOutcome: boolean | ||||
|   truncate: boolean | ||||
|   smallAvatar: boolean | ||||
| }) { | ||||
|   const { contract, comment, bet, hideOutcome, truncate, smallAvatar } = props | ||||
|   let money: string | undefined | ||||
|   let outcome: string | undefined | ||||
|   let bought: string | undefined | ||||
|   if (bet) { | ||||
|     outcome = bet.outcome | ||||
|     bought = bet.amount >= 0 ? 'bought' : 'sold' | ||||
|     money = formatMoney(Math.abs(bet.amount)) | ||||
|   } | ||||
|   const { text, userUsername, userName, userAvatarUrl, createdTime, userId } = | ||||
|     comment | ||||
|   const { | ||||
|     contract, | ||||
|     comment, | ||||
|     betsBySameUser, | ||||
|     hideOutcome, | ||||
|     truncate, | ||||
|     smallAvatar, | ||||
|   } = props | ||||
|   const { text, userUsername, userName, userAvatarUrl, createdTime } = comment | ||||
|   let outcome: string | undefined, | ||||
|     bought: string | undefined, | ||||
|     money: string | undefined | ||||
| 
 | ||||
|   const userBets = useUserContractBets(userId, contract.id) | ||||
|   const { yesFloorShares, noFloorShares } = useSaveShares( | ||||
|     contract as FullContract<CPMM | DPM, Binary>, | ||||
|     userBets | ||||
|   const matchedBet = betsBySameUser.find((bet) => bet.id === comment.betId) | ||||
|   if (matchedBet) { | ||||
|     outcome = matchedBet.outcome | ||||
|     bought = matchedBet.amount >= 0 ? 'bought' : 'sold' | ||||
|     money = formatMoney(Math.abs(matchedBet.amount)) | ||||
|   } | ||||
| 
 | ||||
|   // Only calculated if they don't have a matching bet
 | ||||
|   const [userPosition, userPositionMoney, yesFloorShares, noFloorShares] = | ||||
|     getBettorsPosition( | ||||
|       contract, | ||||
|       comment.createdTime, | ||||
|       matchedBet ? [] : betsBySameUser | ||||
|     ) | ||||
|   const userPosition = yesFloorShares || noFloorShares | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|  | @ -171,10 +179,9 @@ export function FeedComment(props: { | |||
|               username={userUsername} | ||||
|               name={userName} | ||||
|             />{' '} | ||||
|             {contract.outcomeType === 'BINARY' ? ( | ||||
|               userPosition > 0 && ( | ||||
|             {!matchedBet && userPosition > 0 && ( | ||||
|               <> | ||||
|                   {'owns ' + userPosition + ' shares '} | ||||
|                 {'with ' + userPositionMoney + ' '} | ||||
|                 <> | ||||
|                   {' of '} | ||||
|                   <OutcomeLabel | ||||
|  | @ -184,11 +191,10 @@ export function FeedComment(props: { | |||
|                   /> | ||||
|                 </> | ||||
|               </> | ||||
|               ) | ||||
|             ) : ( | ||||
|             )} | ||||
|             <> | ||||
|               {bought} {money} | ||||
|                 {!hideOutcome && ( | ||||
|               {outcome && !hideOutcome && ( | ||||
|                 <> | ||||
|                   {' '} | ||||
|                   of{' '} | ||||
|  | @ -200,7 +206,6 @@ export function FeedComment(props: { | |||
|                 </> | ||||
|               )} | ||||
|             </> | ||||
|             )} | ||||
|             <RelativeTimestamp time={createdTime} /> | ||||
|           </p> | ||||
|         </div> | ||||
|  | @ -214,20 +219,12 @@ export function FeedComment(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| function RelativeTimestamp(props: { time: number }) { | ||||
|   const { time } = props | ||||
|   return ( | ||||
|     <DateTimeTooltip time={time}> | ||||
|       <span className="ml-1 whitespace-nowrap text-gray-400"> | ||||
|         {fromNow(time)} | ||||
|       </span> | ||||
|     </DateTimeTooltip> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function CommentInput(props: { contract: Contract }) { | ||||
|   // see if we can comment input on any bet:
 | ||||
|   const { contract } = props | ||||
| export function CommentInput(props: { | ||||
|   contract: Contract | ||||
|   bets: Bet[] | ||||
|   comments: Comment[] | ||||
| }) { | ||||
|   const { contract, bets, comments } = props | ||||
|   const user = useUser() | ||||
|   const [comment, setComment] = useState('') | ||||
| 
 | ||||
|  | @ -240,14 +237,50 @@ export function CommentInput(props: { contract: Contract }) { | |||
|     setComment('') | ||||
|   } | ||||
| 
 | ||||
|   // Should this be oldest bet or most recent bet?
 | ||||
|   const mostRecentCommentableBet = bets | ||||
|     .filter( | ||||
|       (bet) => | ||||
|         canCommentOnBet(bet.userId, bet.createdTime, user) && | ||||
|         !comments.some((comment) => comment.betId == bet.id) | ||||
|     ) | ||||
|     .sort((b1, b2) => b1.createdTime - b2.createdTime) | ||||
|     .pop() | ||||
| 
 | ||||
|   if (mostRecentCommentableBet) { | ||||
|     return ( | ||||
|       <FeedBet | ||||
|         contract={contract} | ||||
|         bet={mostRecentCommentableBet} | ||||
|         hideOutcome={false} | ||||
|         smallAvatar={false} | ||||
|       /> | ||||
|     ) | ||||
|   } | ||||
|   const [userPosition, userPositionMoney, yesFloorShares, noFloorShares] = | ||||
|     getBettorsPosition(contract, Date.now(), bets) | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <Row className={'flex w-full gap-2 pt-5'}> | ||||
|       <Row className={'flex w-full gap-2 pt-3'}> | ||||
|         <div> | ||||
|           <Avatar avatarUrl={user?.avatarUrl} username={user?.username} /> | ||||
|         </div> | ||||
|         <div className={'min-w-0 flex-1 py-1.5'}> | ||||
|           <div className="text-sm text-gray-500"> | ||||
|             {user && userPosition > 0 && ( | ||||
|               <> | ||||
|                 {'You with ' + userPositionMoney + ' '} | ||||
|                 <> | ||||
|                   {' of '} | ||||
|                   <OutcomeLabel | ||||
|                     outcome={yesFloorShares > noFloorShares ? 'YES' : 'NO'} | ||||
|                     contract={contract} | ||||
|                     truncate="short" | ||||
|                   /> | ||||
|                 </> | ||||
|               </> | ||||
|             )} | ||||
|             <div className="mt-2"> | ||||
|               <Textarea | ||||
|                 value={comment} | ||||
|  | @ -278,20 +311,68 @@ export function CommentInput(props: { contract: Contract }) { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| function RelativeTimestamp(props: { time: number }) { | ||||
|   const { time } = props | ||||
|   return ( | ||||
|     <DateTimeTooltip time={time}> | ||||
|       <span className="ml-1 whitespace-nowrap text-gray-400"> | ||||
|         {fromNow(time)} | ||||
|       </span> | ||||
|     </DateTimeTooltip> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function getBettorsPosition( | ||||
|   contract: Contract, | ||||
|   createdTime: number, | ||||
|   bets: Bet[] | ||||
| ) { | ||||
|   let yesFloorShares = 0, | ||||
|     yesShares = 0, | ||||
|     noShares = 0, | ||||
|     noFloorShares = 0 | ||||
|   // TODO: show which of the answers was their majority stake at time of comment for FR?
 | ||||
|   if (contract.outcomeType != 'BINARY') { | ||||
|     return [0, 0, 0, 0] | ||||
|   } | ||||
|   if (bets.length === 0) { | ||||
|     return [0, 0, 0, 0] | ||||
|   } | ||||
| 
 | ||||
|   // Calculate the majority shares they had when they made the comment
 | ||||
|   const betsBefore = bets.filter((prevBet) => prevBet.createdTime < createdTime) | ||||
|   const [yesBets, noBets] = _.partition( | ||||
|     betsBefore ?? [], | ||||
|     (bet) => bet.outcome === 'YES' | ||||
|   ) | ||||
|   yesShares = _.sumBy(yesBets, (bet) => bet.shares) | ||||
|   noShares = _.sumBy(noBets, (bet) => bet.shares) | ||||
|   yesFloorShares = Math.floor(yesShares) | ||||
|   noFloorShares = Math.floor(noShares) | ||||
| 
 | ||||
|   const userPosition = yesFloorShares || noFloorShares | ||||
|   const { saleValue } = calculateCpmmSale( | ||||
|     contract as FullContract<CPMM, Binary>, | ||||
|     yesShares || noShares, | ||||
|     yesFloorShares > noFloorShares ? 'YES' : 'NO' | ||||
|   ) | ||||
|   const userPositionMoney = formatMoney(Math.abs(saleValue)) | ||||
|   return [userPosition, userPositionMoney, yesFloorShares, noFloorShares] | ||||
| } | ||||
| 
 | ||||
| export function FeedBet(props: { | ||||
|   contract: Contract | ||||
|   bet: Bet | ||||
|   hideOutcome: boolean | ||||
|   smallAvatar: boolean | ||||
|   hideComment?: boolean | ||||
|   bettor?: User // If set: reveal bettor identity
 | ||||
| }) { | ||||
|   const { contract, bet, hideOutcome, smallAvatar, bettor } = props | ||||
|   const { contract, bet, hideOutcome, smallAvatar, bettor, hideComment } = props | ||||
|   const { id, amount, outcome, createdTime, userId } = bet | ||||
|   const user = useUser() | ||||
|   const isSelf = user?.id === userId | ||||
|   const canComment = | ||||
|     canCommentOnBet(userId, createdTime, user) && | ||||
|     contract.outcomeType !== 'BINARY' | ||||
|   const canComment = canCommentOnBet(userId, createdTime, user) && !hideComment | ||||
| 
 | ||||
|   const [comment, setComment] = useState('') | ||||
|   async function submitComment() { | ||||
|  | @ -304,6 +385,7 @@ export function FeedBet(props: { | |||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       <Row className={'flex w-full gap-2 pt-3'}> | ||||
|         <div> | ||||
|           {isSelf ? ( | ||||
|             <Avatar | ||||
|  | @ -322,7 +404,10 @@ export function FeedBet(props: { | |||
|           ) : ( | ||||
|             <div className="relative px-1"> | ||||
|               <div className="flex h-8 w-8 items-center justify-center rounded-full bg-gray-200"> | ||||
|               <UserIcon className="h-5 w-5 text-gray-500" aria-hidden="true" /> | ||||
|                 <UserIcon | ||||
|                   className="h-5 w-5 text-gray-500" | ||||
|                   aria-hidden="true" | ||||
|                 /> | ||||
|               </div> | ||||
|             </div> | ||||
|           )} | ||||
|  | @ -369,6 +454,7 @@ export function FeedBet(props: { | |||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|       </Row> | ||||
|     </> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -287,7 +287,7 @@ function ContractTopTrades(props: { | |||
|             <FeedComment | ||||
|               contract={contract} | ||||
|               comment={commentsById[topCommentId]} | ||||
|               bet={betsById[topCommentId]} | ||||
|               betsBySameUser={[betsById[topCommentId]]} | ||||
|               hideOutcome={false} | ||||
|               truncate={false} | ||||
|               smallAvatar={false} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user