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:
parent
ce76a9754b
commit
34e7a27125
49
web/components/layout/tabs.tsx
Normal file
49
web/components/layout/tabs.tsx
Normal 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>
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue
Block a user