Standardize on a single Tabs component (#72)

* Extract out Tabs component

* Use tabs component on /home

* Use Tabs on Fold pages too
This commit is contained in:
Austin Chen 2022-03-30 23:24:35 -07:00 committed by GitHub
parent ce76a9754b
commit 34e7a27125
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 181 additions and 209 deletions

View File

@ -0,0 +1,49 @@
import clsx from 'clsx'
import Link from 'next/link'
import { useState } from 'react'
type Tab = {
title: string
tabIcon?: JSX.Element
content: JSX.Element
// If set, change the url to this href when the tab is selected
href?: string
}
export function Tabs(props: { tabs: Tab[]; defaultIndex?: number }) {
const { tabs, defaultIndex } = props
const [activeIndex, setActiveIndex] = useState(defaultIndex ?? 0)
const activeTab = tabs[activeIndex]
return (
<div>
<nav className="flex space-x-4" aria-label="Tabs">
{tabs.map((tab, i) => (
<Link href={tab.href ?? '#'} key={tab.title} shallow={!!tab.href}>
<a
key={tab.title}
className={clsx(
tab.title === activeTab.title
? 'bg-gray-200 text-gray-700'
: 'text-gray-500 hover:bg-gray-100 hover:text-gray-700',
'rounded-md px-3 py-2 text-sm font-medium'
)}
aria-current={tab.title === activeTab.title ? 'page' : undefined}
onClick={(e) => {
if (!tab.href) {
e.preventDefault()
}
setActiveIndex(i)
}}
>
{tab.tabIcon ? <span className="mr-2">{tab.tabIcon}</span> : null}
{tab.title}
</a>
</Link>
))}
</nav>
<div className="mt-4">{activeTab.content}</div>
</div>
)
}

View File

@ -9,6 +9,7 @@ import {
} from '../components/analytics/charts'
import { Col } from '../components/layout/col'
import { Spacer } from '../components/layout/spacer'
import { Tabs } from '../components/layout/tabs'
import { Page } from '../components/page'
import { Title } from '../components/title'
import { fromPropz, usePropz } from '../hooks/use-propz'
@ -250,45 +251,6 @@ export function CustomAnalytics(props: {
)
}
type Tab = {
title: string
content: JSX.Element
}
function Tabs(props: { tabs: Tab[]; defaultIndex: number }) {
const { tabs, defaultIndex } = props
const [activeTab, setActiveTab] = useState(tabs[defaultIndex])
return (
<div>
<nav className="flex space-x-4" aria-label="Tabs">
{tabs.map((tab) => (
<a
key={tab.title}
href="#"
className={clsx(
tab.title === activeTab.title
? 'bg-gray-100 text-gray-700'
: 'text-gray-500 hover:text-gray-700',
'rounded-md px-3 py-2 text-sm font-medium'
)}
aria-current={tab.title === activeTab.title ? 'page' : undefined}
onClick={(e) => {
console.log('clicked')
e.preventDefault()
setActiveTab(tab)
}}
>
{tab.title}
</a>
))}
</nav>
<div className="mt-4">{activeTab.content}</div>
</div>
)
}
export function FirebaseAnalytics() {
// Edit dashboard at https://datastudio.google.com/u/0/reporting/faeaf3a4-c8da-4275-b157-98dad017d305/page/Gg3/edit

View File

@ -40,6 +40,7 @@ import { useRecentBets } from '../../../hooks/use-bets'
import { useRecentComments } from '../../../hooks/use-comments'
import { LoadingIndicator } from '../../../components/loading-indicator'
import { findActiveContracts } from '../../../components/feed/find-active-contracts'
import { Tabs } from '../../../components/layout/tabs'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
@ -169,6 +170,47 @@ export default function FoldPage(props: {
)}
</Row>
<FoldOverview fold={fold} curator={curator} />
<YourPerformance
traderScores={traderScores}
creatorScores={creatorScores}
user={user}
/>
</Col>
)
const activityTab = (
<Col className="flex-1">
{user !== null && !fold.disallowMarketCreation && (
<FeedCreate
className={clsx('border-b-2')}
user={user}
tag={toCamelCase(fold.name)}
placeholder={`Type your question about ${fold.name}`}
/>
)}
{recentBets && recentComments ? (
<>
<ActivityFeed
contracts={activeContracts}
recentBets={recentBets ?? []}
recentComments={recentComments ?? []}
mode="abbreviated"
/>
{activeContracts.length === 0 && (
<div className="mx-2 mt-4 text-gray-500 lg:mx-0">
No activity from matching markets.{' '}
{isCurator && 'Try editing to add more tags!'}
</div>
)}
</>
) : (
<LoadingIndicator className="mt-4" />
)}
</Col>
)
const leaderboardsTab = (
<Col className="gap-8 px-4 lg:flex-row">
<FoldLeaderboards
traderScores={traderScores}
creatorScores={creatorScores}
@ -205,90 +247,25 @@ export default function FoldPage(props: {
</Col>
</div>
<div className="tabs mb-2">
<Link href={foldPath(fold)} shallow>
<a
className={clsx(
'tab tab-bordered',
page === 'activity' && 'tab-active'
)}
>
Activity
</a>
</Link>
<Link href={foldPath(fold, 'markets')} shallow>
<a
className={clsx(
'tab tab-bordered',
page === 'markets' && 'tab-active'
)}
>
Markets
</a>
</Link>
<Link href={foldPath(fold, 'leaderboards')} shallow>
<a
className={clsx(
'tab tab-bordered',
page === 'leaderboards' && 'tab-active',
page !== 'leaderboards' && 'md:hidden'
)}
>
Leaderboards
</a>
</Link>
</div>
{(page === 'activity' || page === 'markets') && (
<Row className={clsx(page === 'activity' ? 'gap-16' : 'gap-8')}>
<Col className="flex-1">
{user !== null && !fold.disallowMarketCreation && (
<FeedCreate
className={clsx('border-b-2', page !== 'activity' && 'hidden')}
user={user}
tag={toCamelCase(fold.name)}
placeholder={`Type your question about ${fold.name}`}
/>
)}
{page === 'activity' ? (
recentBets && recentComments ? (
<>
<ActivityFeed
contracts={activeContracts}
recentBets={recentBets ?? []}
recentComments={recentComments ?? []}
mode="abbreviated"
/>
{activeContracts.length === 0 && (
<div className="mx-2 mt-4 text-gray-500 lg:mx-0">
No activity from matching markets.{' '}
{isCurator && 'Try editing to add more tags!'}
</div>
)}
</>
) : (
<LoadingIndicator className="mt-4" />
)
) : (
<SearchableGrid contracts={contracts} />
)}
</Col>
</Row>
)}
{page === 'leaderboards' && (
<Col className="gap-8 px-4 lg:flex-row">
<FoldLeaderboards
traderScores={traderScores}
creatorScores={creatorScores}
topTraders={topTraders}
topCreators={topCreators}
user={user}
yourPerformanceClassName="lg:hidden"
/>
</Col>
)}
<Tabs
tabs={[
{
title: 'Activity',
content: activityTab,
href: foldPath(fold),
},
{
title: 'Markets',
content: <SearchableGrid contracts={contracts} />,
href: foldPath(fold, 'markets'),
},
{
title: 'Leaderboards',
content: leaderboardsTab,
href: foldPath(fold, 'leaderboards'),
},
]}
/>
</Page>
)
}
@ -333,55 +310,56 @@ function FoldOverview(props: { fold: Fold; curator: User }) {
)
}
function YourPerformance(props: {
traderScores: { [userId: string]: number }
creatorScores: { [userId: string]: number }
user: User | null | undefined
}) {
const { traderScores, creatorScores, user } = props
const yourTraderScore = user ? traderScores[user.id] : undefined
const yourCreatorScore = user ? creatorScores[user.id] : undefined
return user ? (
<Col>
<div className="rounded bg-indigo-500 px-4 py-3 text-sm text-white">
Your performance
</div>
<div className="bg-white p-2">
<table className="table-compact table w-full text-gray-500">
<tbody>
<tr>
<td>Trading profit</td>
<td>{formatMoney(yourTraderScore ?? 0)}</td>
</tr>
{yourCreatorScore && (
<tr>
<td>Created market vol</td>
<td>{formatMoney(yourCreatorScore)}</td>
</tr>
)}
</tbody>
</table>
</div>
</Col>
) : null
}
function FoldLeaderboards(props: {
traderScores: { [userId: string]: number }
creatorScores: { [userId: string]: number }
topTraders: User[]
topCreators: User[]
user: User | null | undefined
yourPerformanceClassName?: string
}) {
const {
traderScores,
creatorScores,
topTraders,
topCreators,
user,
yourPerformanceClassName,
} = props
const yourTraderScore = user ? traderScores[user.id] : undefined
const yourCreatorScore = user ? creatorScores[user.id] : undefined
const { traderScores, creatorScores, topTraders, topCreators } = props
const topTraderScores = topTraders.map((user) => traderScores[user.id])
const topCreatorScores = topCreators.map((user) => creatorScores[user.id])
return (
<>
{user && (
<Col className={yourPerformanceClassName}>
<div className="rounded bg-indigo-500 px-4 py-3 text-sm text-white">
Your performance
</div>
<div className="bg-white p-2">
<table className="table-compact table w-full text-gray-500">
<tbody>
<tr>
<td>Trading profit</td>
<td>{formatMoney(yourTraderScore ?? 0)}</td>
</tr>
{yourCreatorScore && (
<tr>
<td>Created market vol</td>
<td>{formatMoney(yourCreatorScore)}</td>
</tr>
)}
</tbody>
</table>
</div>
</Col>
)}
<Leaderboard
className="max-w-xl"
title="🏅 Top traders"

View File

@ -1,7 +1,6 @@
import React, { useState } from 'react'
import React from 'react'
import Router from 'next/router'
import { SparklesIcon, GlobeAltIcon } from '@heroicons/react/solid'
import clsx from 'clsx'
import _ from 'lodash'
import { Contract } from '../lib/firebase/contracts'
@ -17,8 +16,6 @@ import { Col } from '../components/layout/col'
import { useUser } from '../hooks/use-user'
import { Fold } from '../../common/fold'
import { LoadingIndicator } from '../components/loading-indicator'
import { Row } from '../components/layout/row'
import { FastFoldFollowing } from '../components/folds/fast-fold-following'
import {
getAllContractInfo,
useExploreContracts,
@ -29,7 +26,7 @@ import { fromPropz, usePropz } from '../hooks/use-propz'
import { useGetRecentBets, useRecentBets } from '../hooks/use-bets'
import { useActiveContracts } from '../hooks/use-contracts'
import { useRecentComments } from '../hooks/use-comments'
import { IS_PRIVATE_MANIFOLD } from '../../common/envs/constants'
import { Tabs } from '../components/layout/tabs'
export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz() {
@ -73,13 +70,22 @@ const Home = (props: {
const exploreContracts = useExploreContracts()
const [feedMode, setFeedMode] = useState<'activity' | 'explore'>('activity')
if (user === null) {
Router.replace('/')
return <></>
}
const activityContent = recentBets ? (
<ActivityFeed
contracts={activeContracts}
recentBets={recentBets}
recentComments={recentComments}
mode="only-recent"
/>
) : (
<LoadingIndicator className="mt-4" />
)
return (
<Page assertUser="signed-in">
<Col className="items-center">
@ -98,51 +104,28 @@ const Home = (props: {
<Spacer h={5} />
<Col className="mb-3 gap-2 text-sm text-gray-800 sm:flex-row">
<Row className="gap-2">
<div className="tabs">
<div
className={clsx(
'tab gap-2',
feedMode === 'activity' && 'tab-active'
)}
onClick={() => setFeedMode('activity')}
>
<Tabs
tabs={[
{
title: 'Recent activity',
tabIcon: (
<SparklesIcon className="inline h-5 w-5" aria-hidden="true" />
Recent activity
</div>
<div
className={clsx(
'tab gap-2',
feedMode === 'explore' && 'tab-active'
)}
onClick={() => setFeedMode('explore')}
>
),
content: activityContent,
},
{
title: 'Explore',
tabIcon: (
<GlobeAltIcon className="inline h-5 w-5" aria-hidden="true" />
Explore
</div>
</div>
</Row>
</Col>
{feedMode === 'activity' &&
(recentBets ? (
<ActivityFeed
contracts={activeContracts}
recentBets={recentBets}
recentComments={recentComments}
mode="only-recent"
/>
) : (
<LoadingIndicator className="mt-4" />
))}
{feedMode === 'explore' &&
(exploreContracts ? (
<SummaryActivityFeed contracts={exploreContracts} />
) : (
<LoadingIndicator className="mt-4" />
))}
),
content: exploreContracts ? (
<SummaryActivityFeed contracts={exploreContracts} />
) : (
<LoadingIndicator className="mt-4" />
),
},
]}
/>
</Col>
</Col>
</Page>