Show every user's bets on their profile (#170)

* Show user bets on their profile

* Add an alert for current users

* Replace `/portfolio` with `/Austin?tab=Bets`

* Replace `/Austin?tab=Bets` with `/Austin/bets`

* Use replaceState for better browser history

* Remove two console.logs

* Note a bug

* Fix path

* Write in description of why we're doing this
This commit is contained in:
Austin Chen 2022-05-18 10:36:17 -04:00 committed by GitHub
parent 42c981a54d
commit d50cc39c27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 94 additions and 26 deletions

View File

@ -19,7 +19,6 @@ export function ContractsGrid(props: {
const isBottomVisible = useIsVisible(elem) const isBottomVisible = useIsVisible(elem)
useEffect(() => { useEffect(() => {
console.log({ isBottomVisible, hasMore })
if (isBottomVisible) { if (isBottomVisible) {
loadMore() loadMore()
} }

View File

@ -47,7 +47,7 @@ export function BottomNavBar() {
)} )}
{user !== null && ( {user !== null && (
<Link href="/portfolio"> <Link href={`${user}/bets`}>
<a className="block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700"> <a className="block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700">
<PresentationChartLineIcon <PresentationChartLineIcon
className="my-1 mx-auto h-6 w-6" className="my-1 mx-auto h-6 w-6"

View File

@ -28,12 +28,18 @@ function IconFromUrl(url: string): React.ComponentType<{ className?: string }> {
} }
} }
const navigation = [ function getNavigation(userName: string) {
return [
{ name: 'Home', href: '/home', icon: HomeIcon }, { name: 'Home', href: '/home', icon: HomeIcon },
{ name: 'Activity', href: '/activity', icon: ChatAltIcon }, { name: 'Activity', href: '/activity', icon: ChatAltIcon },
{ name: 'Portfolio', href: '/portfolio', icon: PresentationChartLineIcon }, {
name: 'Portfolio',
href: `/${userName}/bets`,
icon: PresentationChartLineIcon,
},
{ name: 'Charity', href: '/charity', icon: HeartIcon }, { name: 'Charity', href: '/charity', icon: HeartIcon },
] ]
}
const signedOutNavigation = [ const signedOutNavigation = [
{ name: 'Home', href: '/home', icon: HomeIcon }, { name: 'Home', href: '/home', icon: HomeIcon },
@ -119,7 +125,8 @@ export default function Sidebar(props: { className?: string }) {
folds = _.sortBy(folds, 'followCount').reverse() folds = _.sortBy(folds, 'followCount').reverse()
const deservesDailyFreeMarket = !useHasCreatedContractToday(user) const deservesDailyFreeMarket = !useHasCreatedContractToday(user)
const navigationOptions = user === null ? signedOutNavigation : navigation const navigationOptions =
user === null ? signedOutNavigation : getNavigation(user?.name || 'error')
const mobileNavigationOptions = const mobileNavigationOptions =
user === null ? signedOutMobileNavigation : mobileNavigation user === null ? signedOutMobileNavigation : mobileNavigation

View File

@ -21,6 +21,9 @@ import { getContractFromId, listContracts } from 'web/lib/firebase/contracts'
import { LoadingIndicator } from './loading-indicator' import { LoadingIndicator } from './loading-indicator'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import _ from 'lodash' import _ from 'lodash'
import { BetsList } from './bets-list'
import { Bet } from 'common/bet'
import { getUserBets } from 'web/lib/firebase/bets'
export function UserLink(props: { export function UserLink(props: {
name: string name: string
@ -38,12 +41,13 @@ export function UserLink(props: {
) )
} }
export const TAB_IDS = ['markets', 'comments', 'bets']
export function UserPage(props: { export function UserPage(props: {
user: User user: User
currentUser?: User currentUser?: User
defaultTabTitle?: string defaultTabTitle?: 'markets' | 'comments' | 'bets'
}) { }) {
const router = useRouter()
const { user, currentUser, defaultTabTitle } = props const { user, currentUser, defaultTabTitle } = props
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)
@ -51,6 +55,7 @@ export function UserPage(props: {
const [usersContracts, setUsersContracts] = useState<Contract[] | 'loading'>( const [usersContracts, setUsersContracts] = useState<Contract[] | 'loading'>(
'loading' 'loading'
) )
const [usersBets, setUsersBets] = useState<Bet[] | 'loading'>('loading')
const [commentsByContract, setCommentsByContract] = useState< const [commentsByContract, setCommentsByContract] = useState<
Map<Contract, Comment[]> | 'loading' Map<Contract, Comment[]> | 'loading'
>('loading') >('loading')
@ -59,6 +64,7 @@ export function UserPage(props: {
if (!user) return if (!user) return
getUsersComments(user.id).then(setUsersComments) getUsersComments(user.id).then(setUsersComments)
listContracts(user.id).then(setUsersContracts) listContracts(user.id).then(setUsersContracts)
getUserBets(user.id).then(setUsersBets)
}, [user]) }, [user])
useEffect(() => { useEffect(() => {
@ -187,17 +193,14 @@ export function UserPage(props: {
{usersContracts !== 'loading' && commentsByContract != 'loading' ? ( {usersContracts !== 'loading' && commentsByContract != 'loading' ? (
<Tabs <Tabs
className={'pb-2 pt-1 '} className={'pb-2 pt-1 '}
defaultIndex={defaultTabTitle === 'Comments' ? 1 : 0} defaultIndex={TAB_IDS.indexOf(defaultTabTitle || 'markets')}
onClick={(tabName) => onClick={(tabName) => {
router.push( const tabId = tabName.toLowerCase()
{ const subpath = tabId === 'markets' ? '' : '/' + tabId
pathname: `/${user.username}`, // BUG: if you start on `/Bob/bets`, then click on Markets, use-query-and-sort-params
query: { tab: tabName }, // rewrites the url incorrectly to `/Bob/bets` instead of `/Bob`
}, window.history.replaceState('', '', `/${user.username}${subpath}`)
undefined, }}
{ shallow: true }
)
}
tabs={[ tabs={[
{ {
title: 'Markets', title: 'Markets',
@ -220,6 +223,24 @@ export function UserPage(props: {
<div className="px-0.5 font-bold">{usersComments.length}</div> <div className="px-0.5 font-bold">{usersComments.length}</div>
), ),
}, },
{
title: 'Bets',
content: (
<div>
<AlertBox
title="Bets are becoming publicly visible on 2022-06-01"
text="Bettor identities have always been traceable through the Manifold API.
However, our interface implied that they were private.
As we develop new features such as leaderboards and bet history, it won't be technically feasible to keep this info private.
For more context, or if you'd like to wipe your bet history, see: https://manifold.markets/Austin/will-all-bets-on-manifold-be-public"
/>
{isCurrentUser && <BetsList user={user} />}
</div>
),
tabIcon: (
<div className="px-0.5 font-bold">{usersBets.length}</div>
),
},
]} ]}
/> />
) : ( ) : (
@ -242,3 +263,27 @@ export function defaultBannerUrl(userId: string) {
] ]
return defaultBanner[genHash(userId)() % defaultBanner.length] return defaultBanner[genHash(userId)() % defaultBanner.length]
} }
import { ExclamationIcon } from '@heroicons/react/solid'
function AlertBox(props: { title: string; text: string }) {
const { title, text } = props
return (
<div className="rounded-md bg-yellow-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<ExclamationIcon
className="h-5 w-5 text-yellow-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">{title}</h3>
<div className="mt-2 text-sm text-yellow-700">
<Linkify text={text} />
</div>
</div>
</div>
</div>
)
}

View File

@ -47,7 +47,6 @@ export function useInitialQueryAndSort(options?: {
} }
setInitialSort(localSort ?? defaultSort) setInitialSort(localSort ?? defaultSort)
} else { } else {
console.log('ready setting to ', sort ?? defaultSort)
setInitialSort(sort ?? defaultSort) setInitialSort(sort ?? defaultSort)
} }
} }

View File

@ -60,6 +60,12 @@ export function listenForBets(
}) })
} }
export async function getUserBets(userId: string) {
return getValues<Bet>(
query(collectionGroup(db, 'bets'), where('userId', '==', userId))
)
}
export function listenForUserBets( export function listenForUserBets(
userId: string, userId: string,
setBets: (bets: Bet[]) => void setBets: (bets: Bet[]) => void

View File

@ -0,0 +1,5 @@
import UserProfile from '.'
export default function UserBets() {
return <UserProfile tab="bets" />
}

View File

@ -0,0 +1,5 @@
import UserProfile from '.'
export default function UserBets() {
return <UserProfile tab="comments" />
}

View File

@ -6,10 +6,12 @@ import { UserPage } from 'web/components/user-page'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import Custom404 from '../404' import Custom404 from '../404'
export default function UserProfile() { export default function UserProfile(props: {
tab?: 'markets' | 'comments' | 'bets'
}) {
const router = useRouter() const router = useRouter()
const [user, setUser] = useState<User | null | 'loading'>('loading') const [user, setUser] = useState<User | null | 'loading'>('loading')
const { username, tab } = router.query as { username: string; tab: string } const { username } = router.query as { username: string }
useEffect(() => { useEffect(() => {
if (username) { if (username) {
getUserByUsername(username).then(setUser) getUserByUsername(username).then(setUser)
@ -24,7 +26,7 @@ export default function UserProfile() {
<UserPage <UserPage
user={user} user={user}
currentUser={currentUser || undefined} currentUser={currentUser || undefined}
defaultTabTitle={tab} defaultTabTitle={props.tab}
/> />
) : ( ) : (
<Custom404 /> <Custom404 />