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'
|
} 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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user