Daily profit 💰 (#1023)
* Daily profit client side * Filter out those where profit rounds to 0 * Tabs to spaces
This commit is contained in:
		
							parent
							
								
									0ec15ff2f8
								
							
						
					
					
						commit
						70b2b14f80
					
				|  | @ -266,6 +266,8 @@ export const calculateMetricsByContract = ( | |||
|   }) | ||||
| } | ||||
| 
 | ||||
| export type ContractMetrics = ReturnType<typeof calculateMetricsByContract>[number] | ||||
| 
 | ||||
| const calculatePeriodProfit = ( | ||||
|   contract: CPMMBinaryContract, | ||||
|   bets: Bet[], | ||||
|  |  | |||
|  | @ -178,6 +178,8 @@ function getDpmInvested(yourBets: Bet[]) { | |||
|   }) | ||||
| } | ||||
| 
 | ||||
| export type ContractBetMetrics = ReturnType<typeof getContractBetMetrics> | ||||
| 
 | ||||
| export function getContractBetMetrics(contract: Contract, yourBets: Bet[]) { | ||||
|   const { resolution } = contract | ||||
|   const isCpmm = contract.mechanism === 'cpmm-1' | ||||
|  |  | |||
|  | @ -44,6 +44,10 @@ service cloud.firestore { | |||
|       allow read; | ||||
|     } | ||||
| 
 | ||||
|     match /{somePath=**}/contract-metrics/{contractId} { | ||||
|       allow read; | ||||
|     } | ||||
| 
 | ||||
|     match /{somePath=**}/challenges/{challengeId}{ | ||||
|       allow read; | ||||
|     } | ||||
|  |  | |||
|  | @ -1,7 +1,11 @@ | |||
| import clsx from 'clsx' | ||||
| import Link from 'next/link' | ||||
| import { Row } from '../layout/row' | ||||
| import { formatLargeNumber, formatPercent } from 'common/util/format' | ||||
| import { | ||||
|   formatLargeNumber, | ||||
|   formatMoney, | ||||
|   formatPercent, | ||||
| } from 'common/util/format' | ||||
| import { contractPath, getBinaryProbPercent } from 'web/lib/firebase/contracts' | ||||
| import { Col } from '../layout/col' | ||||
| import { | ||||
|  | @ -17,6 +21,7 @@ import { | |||
| import { | ||||
|   AnswerLabel, | ||||
|   BinaryContractOutcomeLabel, | ||||
|   BinaryOutcomeLabel, | ||||
|   CancelLabel, | ||||
|   FreeResponseOutcomeLabel, | ||||
| } from '../outcome-label' | ||||
|  | @ -29,7 +34,7 @@ import { AvatarDetails, MiscDetails, ShowTime } from './contract-details' | |||
| import { getExpectedValue, getValueFromBucket } from 'common/calculate-dpm' | ||||
| import { getColor, ProbBar, QuickBet } from './quick-bet' | ||||
| import { useContractWithPreload } from 'web/hooks/use-contract' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
| import { useUser, useUserContractMetrics } from 'web/hooks/use-user' | ||||
| import { track } from '@amplitude/analytics-browser' | ||||
| import { trackCallback } from 'web/lib/service/analytics' | ||||
| import { getMappedValue } from 'common/pseudo-numeric' | ||||
|  | @ -37,6 +42,7 @@ import { Tooltip } from '../tooltip' | |||
| import { SiteLink } from '../site-link' | ||||
| import { ProbChange } from './prob-change-table' | ||||
| import { Card } from '../card' | ||||
| import { ProfitBadgeMana } from '../profit-badge' | ||||
| 
 | ||||
| export function ContractCard(props: { | ||||
|   contract: Contract | ||||
|  | @ -390,11 +396,18 @@ export function PseudoNumericResolutionOrExpectation(props: { | |||
| export function ContractCardProbChange(props: { | ||||
|   contract: CPMMContract | ||||
|   noLinkAvatar?: boolean | ||||
|   showPosition?: boolean | ||||
|   className?: string | ||||
| }) { | ||||
|   const { noLinkAvatar, className } = props | ||||
|   const { noLinkAvatar, showPosition, className } = props | ||||
|   const contract = useContractWithPreload(props.contract) as CPMMBinaryContract | ||||
| 
 | ||||
|   const user = useUser() | ||||
|   const metrics = useUserContractMetrics(user?.id, contract.id) | ||||
|   const dayMetrics = metrics && metrics.from && metrics.from.day | ||||
|   const outcome = | ||||
|     metrics && metrics.hasShares && metrics.totalShares.YES ? 'YES' : 'NO' | ||||
| 
 | ||||
|   return ( | ||||
|     <Card className={clsx(className, 'mb-4')}> | ||||
|       <AvatarDetails | ||||
|  | @ -411,6 +424,28 @@ export function ContractCardProbChange(props: { | |||
|         </SiteLink> | ||||
|         <ProbChange className="py-2 pr-4" contract={contract} /> | ||||
|       </Row> | ||||
|       {showPosition && metrics && ( | ||||
|         <Row | ||||
|           className={clsx( | ||||
|             'items-center justify-between gap-4 pl-6 pr-4 pb-2 text-sm' | ||||
|           )} | ||||
|         > | ||||
|           <Row className="gap-1"> | ||||
|             <div className="text-gray-500">Position</div> | ||||
|             {formatMoney(metrics.payout)} | ||||
|             <BinaryOutcomeLabel outcome={outcome} /> | ||||
|           </Row> | ||||
| 
 | ||||
|           {dayMetrics && ( | ||||
|             <> | ||||
|               <Row className="items-center"> | ||||
|                 <div className="mr-1 text-gray-500">Daily profit</div> | ||||
|                 <ProfitBadgeMana amount={dayMetrics.profit} /> | ||||
|               </Row> | ||||
|             </> | ||||
|           )} | ||||
|         </Row> | ||||
|       )} | ||||
|     </Card> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ export function ContractsGrid(props: { | |||
|             <ContractCardProbChange | ||||
|               key={contract.id} | ||||
|               contract={contract as CPMMBinaryContract} | ||||
|               showPosition | ||||
|             /> | ||||
|           ) : ( | ||||
|             <ContractCard | ||||
|  |  | |||
|  | @ -1,11 +1,70 @@ | |||
| import clsx from 'clsx' | ||||
| import { CPMMContract } from 'common/contract' | ||||
| import { formatPercent } from 'common/util/format' | ||||
| import { sortBy } from 'lodash' | ||||
| import { filterDefined } from 'common/util/array' | ||||
| import { ContractMetrics } from 'common/calculate-metrics' | ||||
| import { CPMMBinaryContract, CPMMContract } from 'common/contract' | ||||
| import { formatPercent } from 'common/util/format' | ||||
| import { Col } from '../layout/col' | ||||
| import { LoadingIndicator } from '../loading-indicator' | ||||
| import { ContractCardProbChange } from './contract-card' | ||||
| 
 | ||||
| export function ProfitChangeTable(props: { | ||||
|   contracts: CPMMBinaryContract[] | ||||
|   metrics: ContractMetrics[] | ||||
| }) { | ||||
|   const { contracts, metrics } = props | ||||
| 
 | ||||
|   const contractProfit = metrics.map( | ||||
|     (m) => [m.contractId, m.from?.day.profit ?? 0] as const | ||||
|   ) | ||||
| 
 | ||||
|   const positiveProfit = sortBy( | ||||
|     contractProfit.filter(([, profit]) => profit > 0), | ||||
|     ([, profit]) => profit | ||||
|   ).reverse() | ||||
|   const positive = filterDefined( | ||||
|     positiveProfit.map(([contractId]) => | ||||
|       contracts.find((c) => c.id === contractId) | ||||
|     ) | ||||
|   ) | ||||
| 
 | ||||
|   const negativeProfit = sortBy( | ||||
|     contractProfit.filter(([, profit]) => profit < 0), | ||||
|     ([, profit]) => profit | ||||
|   ) | ||||
|   const negative = filterDefined( | ||||
|     negativeProfit.map(([contractId]) => | ||||
|       contracts.find((c) => c.id === contractId) | ||||
|     ) | ||||
|   ) | ||||
| 
 | ||||
|   if (positive.length === 0 && negative.length === 0) | ||||
|     return <div className="px-4 text-gray-500">None</div> | ||||
| 
 | ||||
|   return ( | ||||
|     <Col className="mb-4 w-full gap-4 rounded-lg md:flex-row"> | ||||
|       <Col className="flex-1"> | ||||
|         {positive.map((contract) => ( | ||||
|           <ContractCardProbChange | ||||
|             key={contract.id} | ||||
|             contract={contract} | ||||
|             showPosition | ||||
|           /> | ||||
|         ))} | ||||
|       </Col> | ||||
|       <Col className="flex-1"> | ||||
|         {negative.map((contract) => ( | ||||
|           <ContractCardProbChange | ||||
|             key={contract.id} | ||||
|             contract={contract} | ||||
|             showPosition | ||||
|           /> | ||||
|         ))} | ||||
|       </Col> | ||||
|     </Col> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function ProbChangeTable(props: { | ||||
|   changes: CPMMContract[] | undefined | ||||
|   full?: boolean | ||||
|  | @ -39,12 +98,20 @@ export function ProbChangeTable(props: { | |||
|     <Col className="mb-4 w-full gap-4 rounded-lg md:flex-row"> | ||||
|       <Col className="flex-1"> | ||||
|         {filteredPositiveChanges.map((contract) => ( | ||||
|           <ContractCardProbChange key={contract.id} contract={contract} /> | ||||
|           <ContractCardProbChange | ||||
|             key={contract.id} | ||||
|             contract={contract} | ||||
|             showPosition | ||||
|           /> | ||||
|         ))} | ||||
|       </Col> | ||||
|       <Col className="flex-1"> | ||||
|         {filteredNegativeChanges.map((contract) => ( | ||||
|           <ContractCardProbChange key={contract.id} contract={contract} /> | ||||
|           <ContractCardProbChange | ||||
|             key={contract.id} | ||||
|             contract={contract} | ||||
|             showPosition | ||||
|           /> | ||||
|         ))} | ||||
|       </Col> | ||||
|     </Col> | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import clsx from 'clsx' | ||||
| import { ENV_CONFIG } from 'common/envs/constants' | ||||
| 
 | ||||
| export function ProfitBadge(props: { | ||||
|   profitPercent: number | ||||
|  | @ -26,3 +27,24 @@ export function ProfitBadge(props: { | |||
|     </span> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function ProfitBadgeMana(props: { amount: number; className?: string }) { | ||||
|   const { amount, className } = props | ||||
|   const colors = | ||||
|     amount > 0 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' | ||||
| 
 | ||||
|   const formatted = | ||||
|     ENV_CONFIG.moneyMoniker + (amount > 0 ? '+' : '') + amount.toFixed(0) | ||||
| 
 | ||||
|   return ( | ||||
|     <span | ||||
|       className={clsx( | ||||
|         'ml-1 inline-flex items-center rounded-full px-3 py-0.5 text-sm font-medium', | ||||
|         colors, | ||||
|         className | ||||
|       )} | ||||
|     > | ||||
|       {formatted} | ||||
|     </span> | ||||
|   ) | ||||
| } | ||||
|  |  | |||
|  | @ -1,10 +1,18 @@ | |||
| import { useContext } from 'react' | ||||
| import { useFirestoreDocumentData } from '@react-query-firebase/firestore' | ||||
| import { useQueryClient } from 'react-query' | ||||
| import { | ||||
|   useFirestoreDocumentData, | ||||
|   useFirestoreQueryData, | ||||
| } from '@react-query-firebase/firestore' | ||||
| import { useQuery, useQueryClient } from 'react-query' | ||||
| 
 | ||||
| import { doc, DocumentData } from 'firebase/firestore' | ||||
| import { getUser, User, users } from 'web/lib/firebase/users' | ||||
| import { AuthContext } from 'web/components/auth-context' | ||||
| import { ContractMetrics } from 'common/calculate-metrics' | ||||
| import { getUserContractMetricsQuery } from 'web/lib/firebase/contract-metrics' | ||||
| import { getContractFromId } from 'web/lib/firebase/contracts' | ||||
| import { buildArray, filterDefined } from 'common/util/array' | ||||
| import { CPMMBinaryContract } from 'common/contract' | ||||
| 
 | ||||
| export const useUser = () => { | ||||
|   const authUser = useContext(AuthContext) | ||||
|  | @ -38,3 +46,45 @@ export const usePrefetchUsers = (userIds: string[]) => { | |||
|     queryClient.prefetchQuery(['users', userId], () => getUser(userId)) | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export const useUserContractMetricsByProfit = ( | ||||
|   userId: string, | ||||
|   count: number | ||||
| ) => { | ||||
|   const positiveResult = useFirestoreQueryData<ContractMetrics>( | ||||
|     ['contract-metrics-descending', userId, count], | ||||
|     getUserContractMetricsQuery(userId, count, 'desc') | ||||
|   ) | ||||
|   const negativeResult = useFirestoreQueryData<ContractMetrics>( | ||||
|     ['contract-metrics-ascending', userId, count], | ||||
|     getUserContractMetricsQuery(userId, count, 'asc') | ||||
|   ) | ||||
| 
 | ||||
|   const metrics = buildArray(positiveResult.data, negativeResult.data) | ||||
|   const contractIds = metrics.map((m) => m.contractId) | ||||
| 
 | ||||
|   const contractResult = useQuery(['contracts', contractIds], () => | ||||
|     Promise.all(contractIds.map(getContractFromId)) | ||||
|   ) | ||||
|   const contracts = contractResult.data | ||||
| 
 | ||||
|   if (!positiveResult.data || !negativeResult.data || !contracts) | ||||
|     return undefined | ||||
| 
 | ||||
|   const filteredContracts = filterDefined(contracts) as CPMMBinaryContract[] | ||||
|   const filteredMetrics = metrics.filter( | ||||
|     (m) => m.from && Math.abs(m.from.day.profit) >= 0.5 | ||||
|   ) | ||||
|   return { contracts: filteredContracts, metrics: filteredMetrics } | ||||
| } | ||||
| 
 | ||||
| export const useUserContractMetrics = (userId = '_', contractId: string) => { | ||||
|   const result = useFirestoreDocumentData<DocumentData, ContractMetrics>( | ||||
|     ['user-contract-metrics', userId, contractId], | ||||
|     doc(users, userId, 'contract-metrics', contractId) | ||||
|   ) | ||||
| 
 | ||||
|   if (userId === '_') return undefined | ||||
| 
 | ||||
|   return result.data | ||||
| } | ||||
|  |  | |||
							
								
								
									
										23
									
								
								web/lib/firebase/contract-metrics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/lib/firebase/contract-metrics.ts
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,23 @@ | |||
| import { ContractMetrics } from 'common/calculate-metrics' | ||||
| import { | ||||
|   query, | ||||
|   limit, | ||||
|   Query, | ||||
|   collection, | ||||
|   orderBy, | ||||
|   where, | ||||
| } from 'firebase/firestore' | ||||
| import { db } from './init' | ||||
| 
 | ||||
| export function getUserContractMetricsQuery( | ||||
|   userId: string, | ||||
|   count: number, | ||||
|   sort: 'asc' | 'desc' | ||||
| ) { | ||||
|   return query( | ||||
|     collection(db, 'users', userId, 'contract-metrics'), | ||||
|     where('from.day.profit', sort === 'desc' ? '>' : '<', 0), | ||||
|     orderBy('from.day.profit', sort), | ||||
|     limit(count) | ||||
|   ) as Query<ContractMetrics> | ||||
| } | ||||
|  | @ -1,10 +1,12 @@ | |||
| import { ProbChangeTable } from 'web/components/contract/prob-change-table' | ||||
| import { ProfitChangeTable } from 'web/components/contract/prob-change-table' | ||||
| import { Col } from 'web/components/layout/col' | ||||
| import { Row } from 'web/components/layout/row' | ||||
| import { LoadingIndicator } from 'web/components/loading-indicator' | ||||
| import { Page } from 'web/components/page' | ||||
| import { Title } from 'web/components/title' | ||||
| import { useProbChanges } from 'web/hooks/use-prob-changes' | ||||
| import { useTracking } from 'web/hooks/use-tracking' | ||||
| import { useUser } from 'web/hooks/use-user' | ||||
| import { useUser, useUserContractMetricsByProfit } from 'web/hooks/use-user' | ||||
| import { DailyProfit } from './home' | ||||
| 
 | ||||
| export default function DailyMovers() { | ||||
|   const user = useUser() | ||||
|  | @ -13,7 +15,10 @@ export default function DailyMovers() { | |||
|   return ( | ||||
|     <Page> | ||||
|       <Col className="pm:mx-10 gap-4 sm:px-4 sm:pb-4"> | ||||
|         <Title className="mx-4 !mb-0 sm:mx-0" text="Daily movers" /> | ||||
|         <Row className="mt-4 items-start justify-between sm:mt-0"> | ||||
|           <Title className="mx-4 !mb-0 !mt-0 sm:mx-0" text="Daily movers" /> | ||||
|           <DailyProfit user={user} /> | ||||
|         </Row> | ||||
|         {user && <ProbChangesWrapper userId={user.id} />} | ||||
|       </Col> | ||||
|     </Page> | ||||
|  | @ -23,9 +28,9 @@ export default function DailyMovers() { | |||
| function ProbChangesWrapper(props: { userId: string }) { | ||||
|   const { userId } = props | ||||
| 
 | ||||
|   const changes = useProbChanges({ bettorId: userId })?.filter( | ||||
|     (c) => Math.abs(c.probChanges.day) >= 0.01 | ||||
|   ) | ||||
|   const data = useUserContractMetricsByProfit(userId, 50) | ||||
| 
 | ||||
|   return <ProbChangeTable changes={changes} full /> | ||||
|   if (!data) return <LoadingIndicator /> | ||||
| 
 | ||||
|   return <ProfitChangeTable contracts={data.contracts} metrics={data.metrics} /> | ||||
| } | ||||
|  |  | |||
|  | @ -19,19 +19,22 @@ import { useSaveReferral } from 'web/hooks/use-save-referral' | |||
| import { Sort } from 'web/components/contract-search' | ||||
| import { Group } from 'common/group' | ||||
| import { SiteLink } from 'web/components/site-link' | ||||
| import { usePrivateUser, useUser } from 'web/hooks/use-user' | ||||
| import { | ||||
|   usePrivateUser, | ||||
|   useUser, | ||||
|   useUserContractMetricsByProfit, | ||||
| } from 'web/hooks/use-user' | ||||
| import { | ||||
|   useMemberGroupsSubscription, | ||||
|   useTrendingGroups, | ||||
| } from 'web/hooks/use-group' | ||||
| import { Button } from 'web/components/button' | ||||
| import { Row } from 'web/components/layout/row' | ||||
| import { ProbChangeTable } from 'web/components/contract/prob-change-table' | ||||
| import { ProfitChangeTable } from 'web/components/contract/prob-change-table' | ||||
| import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups' | ||||
| import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' | ||||
| import { formatMoney } from 'common/util/format' | ||||
| import { useProbChanges } from 'web/hooks/use-prob-changes' | ||||
| import { calculatePortfolioProfit } from 'common/calculate-metrics' | ||||
| import { ContractMetrics } from 'common/calculate-metrics' | ||||
| import { hasCompletedStreakToday } from 'web/components/profile/betting-streak-modal' | ||||
| import { ContractsGrid } from 'web/components/contract/contracts-grid' | ||||
| import { PillButton } from 'web/components/buttons/pill-button' | ||||
|  | @ -74,7 +77,11 @@ export default function Home() { | |||
|     } | ||||
|   }, [user, sections]) | ||||
| 
 | ||||
|   const dailyMovers = useProbChanges({ bettorId: user?.id }) | ||||
|   const contractMetricsByProfit = useUserContractMetricsByProfit( | ||||
|     user?.id ?? '_', | ||||
|     3 | ||||
|   ) | ||||
| 
 | ||||
|   const trendingContracts = useTrendingContracts(6) | ||||
|   const newContracts = useNewContracts(6) | ||||
|   const dailyTrendingContracts = useContractsByDailyScoreNotBetOn(user?.id, 6) | ||||
|  | @ -87,7 +94,7 @@ export default function Home() { | |||
| 
 | ||||
|   const isLoading = | ||||
|     !user || | ||||
|     !dailyMovers || | ||||
|     !contractMetricsByProfit || | ||||
|     !trendingContracts || | ||||
|     !newContracts || | ||||
|     !dailyTrendingContracts | ||||
|  | @ -118,7 +125,7 @@ export default function Home() { | |||
|               score: trendingContracts, | ||||
|               newest: newContracts, | ||||
|               'daily-trending': dailyTrendingContracts, | ||||
|               'daily-movers': dailyMovers, | ||||
|               'daily-movers': contractMetricsByProfit, | ||||
|             })} | ||||
| 
 | ||||
|             {groups && groupContracts && trendingGroups.length > 0 ? ( | ||||
|  | @ -184,7 +191,10 @@ export const getHomeItems = (sections: string[]) => { | |||
| function renderSections( | ||||
|   sections: { id: string; label: string }[], | ||||
|   sectionContracts: { | ||||
|     'daily-movers': CPMMBinaryContract[] | ||||
|     'daily-movers': { | ||||
|       contracts: CPMMBinaryContract[] | ||||
|       metrics: ContractMetrics[] | ||||
|     } | ||||
|     'daily-trending': CPMMBinaryContract[] | ||||
|     newest: CPMMBinaryContract[] | ||||
|     score: CPMMBinaryContract[] | ||||
|  | @ -193,13 +203,16 @@ function renderSections( | |||
|   return ( | ||||
|     <> | ||||
|       {sections.map((s) => { | ||||
|         const { id, label } = s | ||||
|         const contracts = | ||||
|           sectionContracts[s.id as keyof typeof sectionContracts] | ||||
| 
 | ||||
|         if (id === 'daily-movers') { | ||||
|           return <DailyMoversSection key={id} contracts={contracts} /> | ||||
|         const { id, label } = s as { | ||||
|           id: keyof typeof sectionContracts | ||||
|           label: string | ||||
|         } | ||||
|         if (id === 'daily-movers') { | ||||
|           return <DailyMoversSection key={id} {...sectionContracts[id]} /> | ||||
|         } | ||||
| 
 | ||||
|         const contracts = sectionContracts[id] | ||||
| 
 | ||||
|         if (id === 'daily-trending') { | ||||
|           return ( | ||||
|             <SearchSection | ||||
|  | @ -347,58 +360,39 @@ function GroupSection(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| function DailyMoversSection(props: { contracts: CPMMBinaryContract[] }) { | ||||
|   const { contracts } = props | ||||
| function DailyMoversSection(props: { | ||||
|   contracts: CPMMBinaryContract[] | ||||
|   metrics: ContractMetrics[] | ||||
| }) { | ||||
|   const { contracts, metrics } = props | ||||
| 
 | ||||
|   const changes = contracts.filter((c) => Math.abs(c.probChanges.day) >= 0.01) | ||||
| 
 | ||||
|   if (changes.length === 0) { | ||||
|   if (contracts.length === 0) { | ||||
|     return null | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <Col className="gap-2"> | ||||
|       <SectionHeader label="Daily movers" href="/daily-movers" /> | ||||
|       <ProbChangeTable changes={changes} /> | ||||
|       <ProfitChangeTable contracts={contracts} metrics={metrics} /> | ||||
|     </Col> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| function DailyStats(props: { | ||||
|   user: User | null | undefined | ||||
|   className?: string | ||||
| }) { | ||||
|   const { user, className } = props | ||||
| 
 | ||||
|   const metrics = usePortfolioHistory(user?.id ?? '', 'daily') ?? [] | ||||
|   const [first, last] = [metrics[0], metrics[metrics.length - 1]] | ||||
| function DailyStats(props: { user: User | null | undefined }) { | ||||
|   const { user } = props | ||||
| 
 | ||||
|   const privateUser = usePrivateUser() | ||||
|   const streaks = privateUser?.notificationPreferences?.betting_streaks ?? [] | ||||
|   const streaksHidden = streaks.length === 0 | ||||
| 
 | ||||
|   let profit = 0 | ||||
|   let profitPercent = 0 | ||||
|   if (first && last) { | ||||
|     profit = calculatePortfolioProfit(last) - calculatePortfolioProfit(first) | ||||
|     profitPercent = profit / first.investmentValue | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <Row className={'flex-shrink-0 gap-4'}> | ||||
|       <Col> | ||||
|         <div className="text-gray-500">Daily profit</div> | ||||
|         <Row className={clsx(className, 'items-center text-lg')}> | ||||
|           <span>{formatMoney(profit)}</span>{' '} | ||||
|           <ProfitBadge profitPercent={profitPercent * 100} /> | ||||
|         </Row> | ||||
|       </Col> | ||||
|       <DailyProfit user={user} /> | ||||
|       {!streaksHidden && ( | ||||
|         <Col> | ||||
|           <div className="text-gray-500">Streak</div> | ||||
|           <Row | ||||
|             className={clsx( | ||||
|               className, | ||||
|               'items-center text-lg', | ||||
|               user && !hasCompletedStreakToday(user) && 'grayscale' | ||||
|             )} | ||||
|  | @ -411,6 +405,39 @@ function DailyStats(props: { | |||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function DailyProfit(props: { user: User | null | undefined }) { | ||||
|   const { user } = props | ||||
| 
 | ||||
|   const contractMetricsByProfit = useUserContractMetricsByProfit( | ||||
|     user?.id ?? '_', | ||||
|     100 | ||||
|   ) | ||||
|   const profit = sum( | ||||
|     contractMetricsByProfit?.metrics.map((m) => | ||||
|       m.from ? m.from.day.profit : 0 | ||||
|     ) ?? [] | ||||
|   ) | ||||
| 
 | ||||
|   const metrics = usePortfolioHistory(user?.id ?? '', 'daily') ?? [] | ||||
|   const [first, last] = [metrics[0], metrics[metrics.length - 1]] | ||||
| 
 | ||||
|   let profitPercent = 0 | ||||
|   if (first && last) { | ||||
|     // profit = calculatePortfolioProfit(last) - calculatePortfolioProfit(first)
 | ||||
|     profitPercent = profit / first.investmentValue | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <SiteLink className="flex flex-col" href="/daily-movers"> | ||||
|       <div className="text-gray-500">Daily profit</div> | ||||
|       <Row className="items-center text-lg"> | ||||
|         <span>{formatMoney(profit)}</span>{' '} | ||||
|         <ProfitBadge profitPercent={profitPercent * 100} /> | ||||
|       </Row> | ||||
|     </SiteLink> | ||||
|   ) | ||||
| } | ||||
| 
 | ||||
| export function TrendingGroupsSection(props: { | ||||
|   user: User | ||||
|   myGroups: Group[] | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user