making portfolio

This commit is contained in:
ingawei 2022-09-22 14:54:31 -07:00
parent 70cccee469
commit c15d5e9e91
7 changed files with 240 additions and 143 deletions

View File

@ -13,16 +13,18 @@ import { useDiscoverUsers } from 'web/hooks/use-users'
import { TextButton } from './text-button' import { TextButton } from './text-button'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
export function FollowingButton(props: { user: User }) { export function FollowingButton(props: { user: User; className?: string }) {
const { user } = props const { user, className } = props
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const followingIds = useFollows(user.id) const followingIds = useFollows(user.id)
const followerIds = useFollowers(user.id) const followerIds = useFollowers(user.id)
return ( return (
<> <>
<TextButton onClick={() => setIsOpen(true)}> <TextButton onClick={() => setIsOpen(true)} className={className}>
<span className="font-semibold">{followingIds?.length ?? ''}</span>{' '} <span className={clsx('font-semibold')}>
{followingIds?.length ?? ''}
</span>{' '}
Following Following
</TextButton> </TextButton>
@ -69,15 +71,15 @@ export function EditFollowingButton(props: { user: User; className?: string }) {
) )
} }
export function FollowersButton(props: { user: User }) { export function FollowersButton(props: { user: User; className?: string }) {
const { user } = props const { user, className } = props
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const followingIds = useFollows(user.id) const followingIds = useFollows(user.id)
const followerIds = useFollowers(user.id) const followerIds = useFollowers(user.id)
return ( return (
<> <>
<TextButton onClick={() => setIsOpen(true)}> <TextButton onClick={() => setIsOpen(true)} className={className}>
<span className="font-semibold">{followerIds?.length ?? ''}</span>{' '} <span className="font-semibold">{followerIds?.length ?? ''}</span>{' '}
Followers Followers
</TextButton> </TextButton>

View File

@ -14,14 +14,14 @@ import { firebaseLogin } from 'web/lib/firebase/users'
import { GroupLinkItem } from 'web/pages/groups' import { GroupLinkItem } from 'web/pages/groups'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
export function GroupsButton(props: { user: User }) { export function GroupsButton(props: { user: User; className?: string }) {
const { user } = props const { user, className } = props
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const groups = useMemberGroups(user.id) const groups = useMemberGroups(user.id)
return ( return (
<> <>
<TextButton onClick={() => setIsOpen(true)}> <TextButton onClick={() => setIsOpen(true)} className={className}>
<span className="font-semibold">{groups?.length ?? ''}</span> Groups <span className="font-semibold">{groups?.length ?? ''}</span> Groups
</TextButton> </TextButton>

View File

@ -2,6 +2,7 @@ import clsx from 'clsx'
import { useRouter, NextRouter } from 'next/router' import { useRouter, NextRouter } from 'next/router'
import { ReactNode, useState } from 'react' import { ReactNode, useState } from 'react'
import { track } from '@amplitude/analytics-browser' import { track } from '@amplitude/analytics-browser'
import { Col } from './col'
type Tab = { type Tab = {
title: string title: string
@ -55,11 +56,13 @@ export function ControlledTabs(props: TabProps & { activeIndex: number }) {
)} )}
aria-current={activeIndex === i ? 'page' : undefined} aria-current={activeIndex === i ? 'page' : undefined}
> >
{tab.tabIcon && <span>{tab.tabIcon}</span>}
{tab.badge ? ( {tab.badge ? (
<span className="px-0.5 font-bold">{tab.badge}</span> <span className="px-0.5 font-bold">{tab.badge}</span>
) : null} ) : null}
{tab.title} <Col>
{tab.tabIcon && <div className="mx-auto">{tab.tabIcon}</div>}
{tab.title}
</Col>
</a> </a>
))} ))}
</nav> </nav>

View File

@ -1,3 +1,4 @@
import clsx from 'clsx'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { last } from 'lodash' import { last } from 'lodash'
import { memo, useRef, useState } from 'react' import { memo, useRef, useState } from 'react'
@ -36,7 +37,7 @@ export const PortfolioValueSection = memo(
<div className="text-sm text-gray-500">Profit</div> <div className="text-sm text-gray-500">Profit</div>
<div className="text-lg">{formatMoney(totalProfit)}</div> <div className="text-lg">{formatMoney(totalProfit)}</div>
</Col> </Col>
<select {/* <select
className="select select-bordered self-start" className="select select-bordered self-start"
value={portfolioPeriod} value={portfolioPeriod}
onChange={(e) => { onChange={(e) => {
@ -47,7 +48,7 @@ export const PortfolioValueSection = memo(
<option value="monthly">Last Month</option> <option value="monthly">Last Month</option>
<option value="weekly">Last 7d</option> <option value="weekly">Last 7d</option>
<option value="daily">Last 24h</option> <option value="daily">Last 24h</option>
</select> </select> */}
</Row> </Row>
<PortfolioValueGraph <PortfolioValueGraph
portfolioHistory={currPortfolioHistory} portfolioHistory={currPortfolioHistory}
@ -64,7 +65,51 @@ export const PortfolioValueSection = memo(
includeTime={portfolioPeriod == 'daily'} includeTime={portfolioPeriod == 'daily'}
mode="value" mode="value"
/> />
<PortfolioPeriodSelection
portfolioPeriod={portfolioPeriod}
setPortfolioPeriod={setPortfolioPeriod}
className="mt-2 gap-4"
selectClassName="text-indigo-600 text-bold underline"
/>
</> </>
) )
} }
) )
export function PortfolioPeriodSelection(props: {
setPortfolioPeriod: (string: any) => void
portfolioPeriod: string
className?: string
selectClassName?: string
}) {
const { setPortfolioPeriod, portfolioPeriod, className, selectClassName } =
props
return (
<Row className={className}>
<button
className={clsx(portfolioPeriod === 'daily' ? selectClassName : '')}
onClick={() => setPortfolioPeriod('daily' as Period)}
>
1D
</button>
<button
className={clsx(portfolioPeriod === 'weekly' ? selectClassName : '')}
onClick={() => setPortfolioPeriod('weekly' as Period)}
>
1W
</button>
<button
className={clsx(portfolioPeriod === 'monthly' ? selectClassName : '')}
onClick={() => setPortfolioPeriod('monthly' as Period)}
>
1M
</button>
<button
className={clsx(portfolioPeriod === 'allTime' ? selectClassName : '')}
onClick={() => setPortfolioPeriod('allTime' as Period)}
>
ALL
</button>
</Row>
)
}

View File

@ -10,15 +10,15 @@ import { XIcon } from '@heroicons/react/outline'
import { unLikeContract } from 'web/lib/firebase/likes' import { unLikeContract } from 'web/lib/firebase/likes'
import { contractPath } from 'web/lib/firebase/contracts' import { contractPath } from 'web/lib/firebase/contracts'
export function UserLikesButton(props: { user: User }) { export function UserLikesButton(props: { user: User; className?: string }) {
const { user } = props const { user, className } = props
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const likedContracts = useUserLikedContracts(user.id) const likedContracts = useUserLikedContracts(user.id)
return ( return (
<> <>
<TextButton onClick={() => setIsOpen(true)}> <TextButton onClick={() => setIsOpen(true)} className={className}>
<span className="font-semibold">{likedContracts?.length ?? ''}</span>{' '} <span className="font-semibold">{likedContracts?.length ?? ''}</span>{' '}
Likes Likes
</TextButton> </TextButton>

View File

@ -13,14 +13,18 @@ import { getUser, updateUser } from 'web/lib/firebase/users'
import { TextButton } from 'web/components/text-button' import { TextButton } from 'web/components/text-button'
import { UserLink } from 'web/components/user-link' import { UserLink } from 'web/components/user-link'
export function ReferralsButton(props: { user: User; currentUser?: User }) { export function ReferralsButton(props: {
const { user, currentUser } = props user: User
currentUser?: User
className?: string
}) {
const { user, currentUser, className } = props
const [isOpen, setIsOpen] = useState(false) const [isOpen, setIsOpen] = useState(false)
const referralIds = useReferrals(user.id) const referralIds = useReferrals(user.id)
return ( return (
<> <>
<TextButton onClick={() => setIsOpen(true)}> <TextButton onClick={() => setIsOpen(true)} className={className}>
<span className="font-semibold">{referralIds?.length ?? ''}</span>{' '} <span className="font-semibold">{referralIds?.length ?? ''}</span>{' '}
Referrals Referrals
</TextButton> </TextButton>

View File

@ -1,8 +1,13 @@
import clsx from 'clsx' import clsx from 'clsx'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { NextRouter, useRouter } from 'next/router'
import { LinkIcon } from '@heroicons/react/solid' import { LinkIcon } from '@heroicons/react/solid'
import { PencilIcon } from '@heroicons/react/outline' import {
ChatIcon,
FolderIcon,
PencilIcon,
ScaleIcon,
} from '@heroicons/react/outline'
import { User } from 'web/lib/firebase/users' import { User } from 'web/lib/firebase/users'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
@ -37,6 +42,7 @@ import { LoansModal } from './profile/loans-modal'
import { UserLikesButton } from 'web/components/profile/user-likes-button' import { UserLikesButton } from 'web/components/profile/user-likes-button'
import { PAST_BETS } from 'common/user' import { PAST_BETS } from 'common/user'
import { capitalize } from 'lodash' import { capitalize } from 'lodash'
import { useIsMobile } from 'web/hooks/use-is-mobile'
export function UserPage(props: { user: User }) { export function UserPage(props: { user: User }) {
const { user } = props const { user } = props
@ -45,17 +51,18 @@ export function UserPage(props: { user: User }) {
const isCurrentUser = user.id === currentUser?.id const isCurrentUser = user.id === currentUser?.id
const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id) const bannerUrl = user.bannerUrl ?? defaultBannerUrl(user.id)
const [showConfetti, setShowConfetti] = useState(false) const [showConfetti, setShowConfetti] = useState(false)
const [showBettingStreakModal, setShowBettingStreakModal] = useState(false) // const [showBettingStreakModal, setShowBettingStreakModal] = useState(false)
const [showLoansModal, setShowLoansModal] = useState(false) // const [showLoansModal, setShowLoansModal] = useState(false)
const isMobile = useIsMobile()
useEffect(() => { useEffect(() => {
const claimedMana = router.query['claimed-mana'] === 'yes' const claimedMana = router.query['claimed-mana'] === 'yes'
const showBettingStreak = router.query['show'] === 'betting-streak' // const showBettingStreak = router.query['show'] === 'betting-streak'
setShowBettingStreakModal(showBettingStreak) // setShowBettingStreakModal(showBettingStreak)
setShowConfetti(claimedMana || showBettingStreak) // setShowConfetti(claimedMana || showBettingStreak)
const showLoansModel = router.query['show'] === 'loans' // const showLoansModel = router.query['show'] === 'loans'
setShowLoansModal(showLoansModel) // setShowLoansModal(showLoansModel)
const query = { ...router.query } const query = { ...router.query }
if (query.claimedMana || query.show) { if (query.claimedMana || query.show) {
@ -85,102 +92,57 @@ export function UserPage(props: { user: User }) {
{showConfetti && ( {showConfetti && (
<FullscreenConfetti recycle={false} numberOfPieces={300} /> <FullscreenConfetti recycle={false} numberOfPieces={300} />
)} )}
<BettingStreakModal <Col className="relative">
isOpen={showBettingStreakModal} <Row className="relative px-4 pt-4">
setOpen={setShowBettingStreakModal}
currentUser={currentUser}
/>
{showLoansModal && (
<LoansModal isOpen={showLoansModal} setOpen={setShowLoansModal} />
)}
{/* Banner image up top, with an circle avatar overlaid */}
{/* <div
className="h-32 w-full bg-cover bg-center sm:h-40"
style={{
backgroundImage: `url(${bannerUrl})`,
}}
></div> */}
<Col>
<Row className="px-4 py-4">
<Avatar <Avatar
username={user.username} username={user.username}
avatarUrl={user.avatarUrl} avatarUrl={user.avatarUrl}
size={24} size={24}
className="bg-white ring-4 ring-white" className="bg-white shadow-sm shadow-indigo-300"
/> />
<Col> {isCurrentUser && (
<span className="break-anywhere text-2xl font-bold"> <div className="absolute z-50 ml-16 mt-16 rounded-full bg-indigo-600 p-2 text-white shadow-sm shadow-indigo-300">
{user.name} <SiteLink href="/profile">
</span> <PencilIcon className="h-5" />{' '}
<span className="text-gray-500">@{user.username}</span> </SiteLink>
</div>
)}
<Col className="w-full gap-4 pl-5">
<div className="flex flex-col gap-2 sm:flex-row sm:justify-between">
<Col>
<span className="break-anywhere text-lg font-bold sm:text-2xl">
{user.name}
</span>
<span className="sm:text-md text-greyscale-4 text-sm">
@{user.username}
</span>
</Col>
{isCurrentUser && (
<ProfilePrivateStats
currentUser={currentUser}
profit={profit}
user={user}
router={router}
/>
)}
{!isCurrentUser && <UserFollowButton userId={user.id} />}
</div>
{!isMobile && (
<ProfilePublicStats className="text-md" user={user} />
)}
</Col> </Col>
</Row> </Row>
{/* Top right buttons (e.g. edit, follow) */} <Col className="mx-4 mt-2">
<div className="absolute right-0 top-0 mt-2 mr-4"> <Spacer h={1} />
{!isCurrentUser && <UserFollowButton userId={user.id} />} {isMobile && <ProfilePublicStats className="text-sm" user={user} />}
{isCurrentUser && ( <Spacer h={1} />
<SiteLink className="btn-sm btn" href="/profile">
<PencilIcon className="h-5 w-5" />{' '}
<div className="ml-2">Edit</div>
</SiteLink>
)}
</div>
{/* Profile details: name, username, bio, and link to twitter/discord */}
<Col className="mx-4 -mt-6">
<Row className={'flex-wrap justify-between gap-y-2'}>
{/* <Col>
<span className="break-anywhere text-2xl font-bold">
{user.name}
</span>
<span className="text-gray-500">@{user.username}</span>
</Col> */}
<Col className={'justify-center'}>
<Row className={'gap-3'}>
<Col className={'items-center text-gray-500'}>
<span
className={clsx(
'text-md',
profit >= 0 ? 'text-green-600' : 'text-red-400'
)}
>
{formatMoney(profit)}
</span>
<span>profit</span>
</Col>
<Col
className={clsx(
'cursor-pointer items-center text-gray-500',
isCurrentUser && !hasCompletedStreakToday(user)
? 'grayscale'
: 'grayscale-0'
)}
onClick={() => setShowBettingStreakModal(true)}
>
<span>🔥 {user.currentBettingStreak ?? 0}</span>
<span>streak</span>
</Col>
<Col
className={
'flex-shrink-0 cursor-pointer items-center text-gray-500'
}
onClick={() => setShowLoansModal(true)}
>
<span className="text-green-600">
🏦 {formatMoney(user.nextLoanCached ?? 0)}
</span>
<span>next loan</span>
</Col>
</Row>
</Col>
</Row>
<Spacer h={4} />
{user.bio && ( {user.bio && (
<> <>
<div> <div className="sm:text-md text-greyscale-6 mt-2 text-sm sm:mt-0">
<Linkify text={user.bio}></Linkify> <Linkify text={user.bio}></Linkify>
</div> </div>
<Spacer h={4} /> <Spacer h={2} />
</> </>
)} )}
{(user.website || user.twitterHandle || user.discordHandle) && ( {(user.website || user.twitterHandle || user.discordHandle) && (
@ -194,7 +156,7 @@ export function UserPage(props: { user: User }) {
> >
<Row className="items-center gap-1"> <Row className="items-center gap-1">
<LinkIcon className="h-4 w-4" /> <LinkIcon className="h-4 w-4" />
<span className="text-sm text-gray-500"> <span className="text-greyscale-4 text-sm">
{user.website} {user.website}
</span> </span>
</Row> </Row>
@ -215,7 +177,7 @@ export function UserPage(props: { user: User }) {
className="h-4 w-4" className="h-4 w-4"
alt="Twitter" alt="Twitter"
/> />
<span className="text-sm text-gray-500"> <span className="text-greyscale-4 text-sm">
{user.twitterHandle} {user.twitterHandle}
</span> </span>
</Row> </Row>
@ -230,7 +192,7 @@ export function UserPage(props: { user: User }) {
className="h-4 w-4" className="h-4 w-4"
alt="Discord" alt="Discord"
/> />
<span className="text-sm text-gray-500"> <span className="text-greyscale-4 text-sm">
{user.discordHandle} {user.discordHandle}
</span> </span>
</Row> </Row>
@ -238,7 +200,7 @@ export function UserPage(props: { user: User }) {
)} )}
</Row> </Row>
)} )}
{currentUser?.id === user.id && REFERRAL_AMOUNT > 0 && ( {/* {currentUser?.id === user.id && REFERRAL_AMOUNT > 0 && (
<Row <Row
className={ className={
'mb-5 w-full items-center justify-center gap-2 rounded-md border-2 border-indigo-100 bg-indigo-50 p-2 text-indigo-600' 'mb-5 w-full items-center justify-center gap-2 rounded-md border-2 border-indigo-100 bg-indigo-50 p-2 text-indigo-600'
@ -258,48 +220,45 @@ export function UserPage(props: { user: User }) {
iconClassName={'h-8 w-8 text-indigo-700'} iconClassName={'h-8 w-8 text-indigo-700'}
/> />
</Row> </Row>
)} )} */}
<QueryUncontrolledTabs <QueryUncontrolledTabs
currentPageForAnalytics={'profile'} currentPageForAnalytics={'profile'}
labelClassName={'pb-2 pt-1 '} labelClassName={'pb-2 pt-1 sm:pt-4 '}
tabs={[ tabs={[
{
title: 'Portfolio',
tabIcon: <FolderIcon className="h-5" />,
content: (
<>
<PortfolioValueSection userId={user.id} />
<BetsList user={user} />
</>
),
},
{ {
title: 'Markets', title: 'Markets',
tabIcon: <ScaleIcon className="h-5" />,
content: ( content: (
<CreatorContractsList user={currentUser} creator={user} /> <CreatorContractsList user={currentUser} creator={user} />
), ),
}, },
{ {
title: 'Comments', title: 'Comments',
tabIcon: <ChatIcon className="h-5" />,
content: ( content: (
<Col> <Col>
<UserCommentsList user={user} /> <UserCommentsList user={user} />
</Col> </Col>
), ),
}, },
{ // {
title: capitalize(PAST_BETS), // title: 'Stats',
content: ( // content: (
<> // <Col className="mb-8">
<BetsList user={user} /> // <PortfolioValueSection userId={user.id} />
</> // </Col>
), // ),
}, // },
{
title: 'Stats',
content: (
<Col className="mb-8">
<Row className={'mb-8 flex-wrap items-center gap-6'}>
<FollowingButton user={user} />
<FollowersButton user={user} />
<ReferralsButton user={user} />
<GroupsButton user={user} />
<UserLikesButton user={user} />
</Row>
<PortfolioValueSection userId={user.id} />
</Col>
),
},
]} ]}
/> />
</Col> </Col>
@ -320,3 +279,87 @@ export function defaultBannerUrl(userId: string) {
] ]
return defaultBanner[genHash(userId)() % defaultBanner.length] return defaultBanner[genHash(userId)() % defaultBanner.length]
} }
export function ProfilePrivateStats(props: {
currentUser: User | null | undefined
profit: number
user: User
router: NextRouter
}) {
const { currentUser, profit, user, router } = props
const [showBettingStreakModal, setShowBettingStreakModal] = useState(false)
const [showLoansModal, setShowLoansModal] = useState(false)
useEffect(() => {
const showBettingStreak = router.query['show'] === 'betting-streak'
setShowBettingStreakModal(showBettingStreak)
const showLoansModel = router.query['show'] === 'loans'
setShowLoansModal(showLoansModel)
}, [])
return (
<>
<Row className={'justify-between gap-4 sm:justify-end'}>
<Col className={'text-greyscale-4 text-md sm:text-lg'}>
<span
className={clsx(profit >= 0 ? 'text-green-600' : 'text-red-400')}
>
{formatMoney(profit)}
</span>
<span className="mx-auto text-xs sm:text-sm">profit</span>
</Col>
<Col
className={clsx('text-,d cursor-pointer sm:text-lg ')}
onClick={() => setShowBettingStreakModal(true)}
>
<span
className={clsx(
!hasCompletedStreakToday(user)
? 'opacity-50 grayscale'
: 'grayscale-0'
)}
>
🔥 {user.currentBettingStreak ?? 0}
</span>
<span className="text-greyscale-4 mx-auto text-xs sm:text-sm">
streak
</span>
</Col>
<Col
className={
'text-greyscale-4 text-md flex-shrink-0 cursor-pointer sm:text-lg'
}
onClick={() => setShowLoansModal(true)}
>
<span className="text-green-600">
🏦 {formatMoney(user.nextLoanCached ?? 0)}
</span>
<span className="mx-auto text-xs sm:text-sm">next loan</span>
</Col>
</Row>
{BettingStreakModal && (
<BettingStreakModal
isOpen={showBettingStreakModal}
setOpen={setShowBettingStreakModal}
currentUser={currentUser}
/>
)}
{showLoansModal && (
<LoansModal isOpen={showLoansModal} setOpen={setShowLoansModal} />
)}
</>
)
}
export function ProfilePublicStats(props: { user: User; className?: string }) {
const { user, className } = props
return (
<Row className={'flex-wrap items-center gap-3'}>
<FollowingButton user={user} className={className} />
<FollowersButton user={user} className={className} />
{/* <ReferralsButton user={user} className={className} /> */}
<GroupsButton user={user} className={className} />
{/* <UserLikesButton user={user} className={className} /> */}
</Row>
)
}