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' } from '../components/analytics/charts'
import { Col } from '../components/layout/col' import { Col } from '../components/layout/col'
import { Spacer } from '../components/layout/spacer' import { Spacer } from '../components/layout/spacer'
import { Tabs } from '../components/layout/tabs'
import { Page } from '../components/page' import { Page } from '../components/page'
import { Title } from '../components/title' import { Title } from '../components/title'
import { fromPropz, usePropz } from '../hooks/use-propz' 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() { export function FirebaseAnalytics() {
// Edit dashboard at https://datastudio.google.com/u/0/reporting/faeaf3a4-c8da-4275-b157-98dad017d305/page/Gg3/edit // 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 { useRecentComments } from '../../../hooks/use-comments'
import { LoadingIndicator } from '../../../components/loading-indicator' import { LoadingIndicator } from '../../../components/loading-indicator'
import { findActiveContracts } from '../../../components/feed/find-active-contracts' import { findActiveContracts } from '../../../components/feed/find-active-contracts'
import { Tabs } from '../../../components/layout/tabs'
export const getStaticProps = fromPropz(getStaticPropz) export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) { export async function getStaticPropz(props: { params: { slugs: string[] } }) {
@ -169,6 +170,47 @@ export default function FoldPage(props: {
)} )}
</Row> </Row>
<FoldOverview fold={fold} curator={curator} /> <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 <FoldLeaderboards
traderScores={traderScores} traderScores={traderScores}
creatorScores={creatorScores} creatorScores={creatorScores}
@ -205,90 +247,25 @@ export default function FoldPage(props: {
</Col> </Col>
</div> </div>
<div className="tabs mb-2"> <Tabs
<Link href={foldPath(fold)} shallow> tabs={[
<a {
className={clsx( title: 'Activity',
'tab tab-bordered', content: activityTab,
page === 'activity' && 'tab-active' href: foldPath(fold),
)} },
> {
Activity title: 'Markets',
</a> content: <SearchableGrid contracts={contracts} />,
</Link> href: foldPath(fold, 'markets'),
},
<Link href={foldPath(fold, 'markets')} shallow> {
<a title: 'Leaderboards',
className={clsx( content: leaderboardsTab,
'tab tab-bordered', href: foldPath(fold, 'leaderboards'),
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>
)}
</Page> </Page>
) )
} }
@ -333,33 +310,19 @@ function FoldOverview(props: { fold: Fold; curator: User }) {
) )
} }
function FoldLeaderboards(props: { function YourPerformance(props: {
traderScores: { [userId: string]: number } traderScores: { [userId: string]: number }
creatorScores: { [userId: string]: number } creatorScores: { [userId: string]: number }
topTraders: User[]
topCreators: User[]
user: User | null | undefined user: User | null | undefined
yourPerformanceClassName?: string
}) { }) {
const { const { traderScores, creatorScores, user } = props
traderScores,
creatorScores,
topTraders,
topCreators,
user,
yourPerformanceClassName,
} = props
const yourTraderScore = user ? traderScores[user.id] : undefined const yourTraderScore = user ? traderScores[user.id] : undefined
const yourCreatorScore = user ? creatorScores[user.id] : undefined const yourCreatorScore = user ? creatorScores[user.id] : undefined
const topTraderScores = topTraders.map((user) => traderScores[user.id]) return user ? (
const topCreatorScores = topCreators.map((user) => creatorScores[user.id]) <Col>
return (
<>
{user && (
<Col className={yourPerformanceClassName}>
<div className="rounded bg-indigo-500 px-4 py-3 text-sm text-white"> <div className="rounded bg-indigo-500 px-4 py-3 text-sm text-white">
Your performance Your performance
</div> </div>
@ -380,8 +343,23 @@ function FoldLeaderboards(props: {
</table> </table>
</div> </div>
</Col> </Col>
)} ) : null
}
function FoldLeaderboards(props: {
traderScores: { [userId: string]: number }
creatorScores: { [userId: string]: number }
topTraders: User[]
topCreators: User[]
user: User | null | undefined
}) {
const { traderScores, creatorScores, topTraders, topCreators } = props
const topTraderScores = topTraders.map((user) => traderScores[user.id])
const topCreatorScores = topCreators.map((user) => creatorScores[user.id])
return (
<>
<Leaderboard <Leaderboard
className="max-w-xl" className="max-w-xl"
title="🏅 Top traders" title="🏅 Top traders"

View File

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