Bring back tabs in groups (#923)

This commit is contained in:
FRC 2022-09-22 12:12:53 -04:00 committed by GitHub
parent 4412d0195c
commit a5e293c010
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 78 additions and 269 deletions

View File

@ -49,6 +49,7 @@ export function ContractTabs(props: { contract: Contract; bets: Bet[] }) {
return ( return (
<Tabs <Tabs
className="mb-4"
currentPageForAnalytics={'contract'} currentPageForAnalytics={'contract'}
tabs={[ tabs={[
{ {

View File

@ -115,6 +115,7 @@ function FollowsDialog(props: {
<div className="p-2 pb-1 text-xl">{user.name}</div> <div className="p-2 pb-1 text-xl">{user.name}</div>
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div> <div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
<Tabs <Tabs
className="mb-4"
tabs={[ tabs={[
{ {
title: 'Following', title: 'Following',

View File

@ -31,7 +31,7 @@ export function ControlledTabs(props: TabProps & { activeIndex: number }) {
return ( return (
<> <>
<nav <nav
className={clsx('mb-4 space-x-8 border-b border-gray-200', className)} className={clsx(' space-x-8 border-b border-gray-200', className)}
aria-label="Tabs" aria-label="Tabs"
> >
{tabs.map((tab, i) => ( {tabs.map((tab, i) => (

View File

@ -1,94 +0,0 @@
import { ClipboardIcon, HomeIcon } from '@heroicons/react/outline'
import { Item } from './sidebar-item'
import clsx from 'clsx'
import { trackCallback } from 'web/lib/service/analytics'
import TrophyIcon from 'web/lib/icons/trophy-icon'
import { useUser } from 'web/hooks/use-user'
import NotificationsIcon from '../notifications-icon'
import router from 'next/router'
import { userProfileItem } from './bottom-nav-bar'
const mobileGroupNavigation = [
{ name: 'Markets', key: 'markets', icon: HomeIcon },
{ name: 'Leaderboard', key: 'leaderboards', icon: TrophyIcon },
{ name: 'About', key: 'about', icon: ClipboardIcon },
]
const mobileGeneralNavigation = [
{
name: 'Notifications',
key: 'notifications',
icon: NotificationsIcon,
href: '/notifications',
},
]
export function GroupNavBar(props: {
currentPage: string
onClick: (key: string) => void
}) {
const { currentPage } = props
const user = useUser()
return (
<nav className="z-20 flex justify-between border-t-2 bg-white text-xs text-gray-700 lg:hidden">
{mobileGroupNavigation.map((item) => (
<NavBarItem
key={item.name}
item={item}
currentPage={currentPage}
onClick={props.onClick}
/>
))}
{mobileGeneralNavigation.map((item) => (
<NavBarItem
key={item.name}
item={item}
currentPage={currentPage}
onClick={() => {
router.push(item.href)
}}
/>
))}
{user && (
<NavBarItem
key={'profile'}
currentPage={currentPage}
onClick={() => {
router.push(`/${user.username}?tab=trades`)
}}
item={userProfileItem(user)}
/>
)}
</nav>
)
}
function NavBarItem(props: {
item: Item
currentPage: string
onClick: (key: string) => void
}) {
const { item, currentPage } = props
const track = trackCallback(
`group navbar: ${item.trackingEventName ?? item.name}`
)
return (
<button onClick={() => props.onClick(item.key ?? '#')}>
<a
className={clsx(
'block w-full py-1 px-3 text-center hover:bg-indigo-200 hover:text-indigo-700',
currentPage === item.key && 'bg-gray-200 text-indigo-700'
)}
onClick={track}
>
{item.icon && <item.icon className="my-1 mx-auto h-6 w-6" />}
{item.name}
</a>
</button>
)
}

View File

@ -1,82 +0,0 @@
import { ClipboardIcon, HomeIcon } from '@heroicons/react/outline'
import clsx from 'clsx'
import { useUser } from 'web/hooks/use-user'
import { ManifoldLogo } from './manifold-logo'
import { ProfileSummary } from './profile-menu'
import React from 'react'
import TrophyIcon from 'web/lib/icons/trophy-icon'
import { SignInButton } from '../sign-in-button'
import NotificationsIcon from '../notifications-icon'
import { SidebarItem } from './sidebar-item'
import { buildArray } from 'common/util/array'
import { User } from 'common/user'
import { Row } from '../layout/row'
import { Spacer } from '../layout/spacer'
const groupNavigation = [
{ name: 'Markets', key: 'markets', icon: HomeIcon },
{ name: 'About', key: 'about', icon: ClipboardIcon },
{ name: 'Leaderboard', key: 'leaderboards', icon: TrophyIcon },
]
const generalNavigation = (user?: User | null) =>
buildArray(
user && {
name: 'Notifications',
href: `/notifications`,
key: 'notifications',
icon: NotificationsIcon,
}
)
export function GroupSidebar(props: {
groupName: string
className?: string
onClick: (key: string) => void
joinOrAddQuestionsButton: React.ReactNode
currentKey: string
}) {
const { className, groupName, currentKey } = props
const user = useUser()
return (
<nav
aria-label="Group Sidebar"
className={clsx('flex max-h-[100vh] flex-col', className)}
>
<ManifoldLogo className="pt-6" twoLine />
<Row className="pl-2 text-xl text-indigo-700 sm:mt-3">{groupName}</Row>
<div className=" min-h-0 shrink flex-col items-stretch gap-1 pt-6 lg:flex ">
{user ? (
<ProfileSummary user={user} />
) : (
<SignInButton className="mb-4" />
)}
</div>
{/* Desktop navigation */}
{groupNavigation.map((item) => (
<SidebarItem
key={item.key}
item={item}
currentPage={currentKey}
onClick={props.onClick}
/>
))}
{generalNavigation(user).map((item) => (
<SidebarItem
key={item.key}
item={item}
currentPage={currentKey}
onClick={props.onClick}
/>
))}
<Spacer h={2} />
{props.joinOrAddQuestionsButton}
</nav>
)
}

View File

@ -26,9 +26,14 @@ import TrophyIcon from 'web/lib/icons/trophy-icon'
import { SignInButton } from '../sign-in-button' import { SignInButton } from '../sign-in-button'
import { SidebarItem } from './sidebar-item' import { SidebarItem } from './sidebar-item'
import { MoreButton } from './more-button' import { MoreButton } from './more-button'
import { Row } from '../layout/row'
import { Spacer } from '../layout/spacer'
export default function Sidebar(props: { className?: string }) { export default function Sidebar(props: {
const { className } = props className?: string
logoSubheading?: string
}) {
const { className, logoSubheading } = props
const router = useRouter() const router = useRouter()
const currentPage = router.pathname const currentPage = router.pathname
@ -51,7 +56,13 @@ export default function Sidebar(props: { className?: string }) {
aria-label="Sidebar" aria-label="Sidebar"
className={clsx('flex max-h-[100vh] flex-col', className)} className={clsx('flex max-h-[100vh] flex-col', className)}
> >
<ManifoldLogo className="py-6" twoLine /> <ManifoldLogo className="pt-6" twoLine />
{logoSubheading && (
<Row className="pl-2 text-2xl text-indigo-700 sm:mt-3">
{logoSubheading}
</Row>
)}
<Spacer h={6} />
{!user && <SignInButton className="mb-4" />} {!user && <SignInButton className="mb-4" />}

View File

@ -9,8 +9,15 @@ export function Page(props: {
className?: string className?: string
rightSidebarClassName?: string rightSidebarClassName?: string
children?: ReactNode children?: ReactNode
logoSubheading?: string
}) { }) {
const { children, rightSidebar, className, rightSidebarClassName } = props const {
children,
rightSidebar,
className,
rightSidebarClassName,
logoSubheading,
} = props
const bottomBarPadding = 'pb-[58px] lg:pb-0 ' const bottomBarPadding = 'pb-[58px] lg:pb-0 '
return ( return (
@ -23,7 +30,10 @@ export function Page(props: {
)} )}
> >
<Toaster /> <Toaster />
<Sidebar className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex" /> <Sidebar
logoSubheading={logoSubheading}
className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex"
/>
<main <main
className={clsx( className={clsx(
'lg:col-span-8 lg:pt-6', 'lg:col-span-8 lg:pt-6',

View File

@ -64,6 +64,7 @@ function ReferralsDialog(props: {
<div className="p-2 pb-1 text-xl">{user.name}</div> <div className="p-2 pb-1 text-xl">{user.name}</div>
<div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div> <div className="p-2 pt-0 text-sm text-gray-500">@{user.username}</div>
<Tabs <Tabs
className="mb-4"
tabs={[ tabs={[
{ {
title: 'Referrals', title: 'Referrals',

View File

@ -92,7 +92,7 @@ export default function ChallengesListPage() {
tap the button above to create a new market & challenge in one. tap the button above to create a new market & challenge in one.
</p> </p>
<Tabs tabs={[...userTab, ...publicTab]} /> <Tabs className="mb-4" tabs={[...userTab, ...publicTab]} />
</Col> </Col>
</Page> </Page>
) )

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import Link from 'next/link' import Link from 'next/link'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { toast, Toaster } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { Group, GROUP_CHAT_SLUG } from 'common/group' import { Group, GROUP_CHAT_SLUG } from 'common/group'
import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts' import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts'
@ -48,11 +48,11 @@ import { Spacer } from 'web/components/layout/spacer'
import { usePost } from 'web/hooks/use-post' import { usePost } from 'web/hooks/use-post'
import { useAdmin } from 'web/hooks/use-admin' import { useAdmin } from 'web/hooks/use-admin'
import { track } from '@amplitude/analytics-browser' import { track } from '@amplitude/analytics-browser'
import { GroupNavBar } from 'web/components/nav/group-nav-bar'
import { ArrowLeftIcon } from '@heroicons/react/solid' import { ArrowLeftIcon } from '@heroicons/react/solid'
import { GroupSidebar } from 'web/components/nav/group-sidebar'
import { SelectMarketsModal } from 'web/components/contract-select-modal' import { SelectMarketsModal } from 'web/components/contract-select-modal'
import { BETTORS } from 'common/user' import { BETTORS } from 'common/user'
import { Page } from 'web/components/page'
import { Tabs } from 'web/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[] } }) {
@ -140,10 +140,6 @@ export default function GroupPage(props: {
const user = useUser() const user = useUser()
const isAdmin = useAdmin() const isAdmin = useAdmin()
const memberIds = useMemberIds(group?.id ?? null) ?? props.memberIds const memberIds = useMemberIds(group?.id ?? null) ?? props.memberIds
// Note: Keep in sync with sidebarPages
const [sidebarIndex, setSidebarIndex] = useState(
['markets', 'leaderboards', 'about'].indexOf(page ?? 'markets')
)
useSaveReferral(user, { useSaveReferral(user, {
defaultReferrerUsername: creator.username, defaultReferrerUsername: creator.username,
@ -157,7 +153,7 @@ export default function GroupPage(props: {
const isMember = user && memberIds.includes(user.id) const isMember = user && memberIds.includes(user.id)
const maxLeaderboardSize = 50 const maxLeaderboardSize = 50
const leaderboardPage = ( const leaderboardTab = (
<Col> <Col>
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row"> <div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
<GroupLeaderboard <GroupLeaderboard
@ -176,7 +172,7 @@ export default function GroupPage(props: {
</Col> </Col>
) )
const aboutPage = ( const aboutTab = (
<Col> <Col>
{(group.aboutPostId != null || isCreator || isAdmin) && ( {(group.aboutPostId != null || isCreator || isAdmin) && (
<GroupAboutPost <GroupAboutPost
@ -196,16 +192,21 @@ export default function GroupPage(props: {
</Col> </Col>
) )
const questionsPage = ( const questionsTab = (
<> <>
{/* align the divs to the right */} <div className={'flex justify-end '}>
<div className={' flex justify-end px-2 pb-2 sm:hidden'}> <div
<div> className={
<JoinOrAddQuestionsButtons 'flex items-end justify-self-end px-2 md:absolute md:top-0 md:pb-2'
group={group} }
user={user} >
isMember={!!isMember} <div>
/> <JoinOrAddQuestionsButtons
group={group}
user={user}
isMember={!!isMember}
/>
</div>
</div> </div>
</div> </div>
<ContractSearch <ContractSearch
@ -220,88 +221,37 @@ export default function GroupPage(props: {
</> </>
) )
const sidebarPages = [ const tabs = [
{ {
title: 'Markets', title: 'Markets',
content: questionsPage, content: questionsTab,
href: groupPath(group.slug, 'markets'),
key: 'markets',
}, },
{ {
title: 'Leaderboards', title: 'Leaderboards',
content: leaderboardPage, content: leaderboardTab,
href: groupPath(group.slug, 'leaderboards'),
key: 'leaderboards',
}, },
{ {
title: 'About', title: 'About',
content: aboutPage, content: aboutTab,
href: groupPath(group.slug, 'about'),
key: 'about',
}, },
] ]
const pageContent = sidebarPages[sidebarIndex].content
const onSidebarClick = (key: string) => {
const index = sidebarPages.findIndex((t) => t.key === key)
setSidebarIndex(index)
// Append the page to the URL, e.g. /group/mexifold/markets
router.replace(
{ query: { ...router.query, slugs: [group.slug, key] } },
undefined,
{ shallow: true }
)
}
const joinOrAddQuestionsButton = (
<JoinOrAddQuestionsButtons
group={group}
user={user}
isMember={!!isMember}
/>
)
return ( return (
<> <Page logoSubheading={group.name}>
<TopGroupNavBar <SEO
group={group} title={group.name}
currentPage={sidebarPages[sidebarIndex].key} description={`Created by ${creator.name}. ${group.about}`}
onClick={onSidebarClick} url={groupPath(group.slug)}
/> />
<div> <TopGroupNavBar group={group} />
<div <div className={'relative p-2 pt-0 md:pt-2'}>
className={ <Tabs className={'mb-2'} tabs={tabs} />
'mx-auto w-full pb-[58px] lg:grid lg:grid-cols-12 lg:gap-x-2 lg:pb-0 xl:max-w-7xl xl:gap-x-8'
}
>
<Toaster />
<GroupSidebar
groupName={group.name}
className="sticky top-0 hidden divide-gray-300 self-start pl-2 lg:col-span-2 lg:flex"
onClick={onSidebarClick}
joinOrAddQuestionsButton={joinOrAddQuestionsButton}
currentKey={sidebarPages[sidebarIndex].key}
/>
<SEO
title={group.name}
description={`Created by ${creator.name}. ${group.about}`}
url={groupPath(group.slug)}
/>
<main className={'px-2 pt-1 lg:col-span-8 lg:pt-6 xl:col-span-8'}>
{pageContent}
</main>
</div>
</div> </div>
</> </Page>
) )
} }
export function TopGroupNavBar(props: { export function TopGroupNavBar(props: { group: Group }) {
group: Group
currentPage: string
onClick: (key: string) => void
}) {
return ( return (
<header className="sticky top-0 z-50 w-full border-b border-gray-200 md:hidden lg:col-span-12"> <header className="sticky top-0 z-50 w-full border-b border-gray-200 md:hidden lg:col-span-12">
<div className="flex items-center bg-white px-4"> <div className="flex items-center bg-white px-4">
@ -318,7 +268,6 @@ export function TopGroupNavBar(props: {
</h1> </h1>
</div> </div>
</div> </div>
<GroupNavBar currentPage={props.currentPage} onClick={props.onClick} />
</header> </header>
) )
} }
@ -331,11 +280,13 @@ function JoinOrAddQuestionsButtons(props: {
}) { }) {
const { group, user, isMember } = props const { group, user, isMember } = props
return user && isMember ? ( return user && isMember ? (
<Row className={'w-full self-start pt-4'}> <Row className={'mb-2 w-full self-start md:mt-2 '}>
<AddContractButton group={group} user={user} /> <AddContractButton group={group} user={user} />
</Row> </Row>
) : group.anyoneCanJoin ? ( ) : group.anyoneCanJoin ? (
<JoinGroupButton group={group} user={user} /> <div className="mb-2 md:mb-0">
<JoinGroupButton group={group} user={user} />
</div>
) : null ) : null
} }

View File

@ -99,6 +99,7 @@ export default function Groups(props: {
</div> </div>
<Tabs <Tabs
className="mb-4"
currentPageForAnalytics={'groups'} currentPageForAnalytics={'groups'}
tabs={[ tabs={[
...(user ...(user

View File

@ -132,6 +132,7 @@ export default function Leaderboards(_props: {
/> />
<Title text={'Leaderboards'} className={'hidden md:block'} /> <Title text={'Leaderboards'} className={'hidden md:block'} />
<Tabs <Tabs
className="mb-4"
currentPageForAnalytics={'leaderboards'} currentPageForAnalytics={'leaderboards'}
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[

View File

@ -26,6 +26,7 @@ export default function Analytics() {
return ( return (
<Page> <Page>
<Tabs <Tabs
className="mb-4"
currentPageForAnalytics={'stats'} currentPageForAnalytics={'stats'}
tabs={[ tabs={[
{ {
@ -89,6 +90,7 @@ export function CustomAnalytics(props: Stats) {
<Spacer h={4} /> <Spacer h={4} />
<Tabs <Tabs
className="mb-4"
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
{ {
@ -141,6 +143,7 @@ export function CustomAnalytics(props: Stats) {
period? period?
</p> </p>
<Tabs <Tabs
className="mb-4"
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
{ {
@ -198,6 +201,7 @@ export function CustomAnalytics(props: Stats) {
<Spacer h={4} /> <Spacer h={4} />
<Tabs <Tabs
className="mb-4"
defaultIndex={2} defaultIndex={2}
tabs={[ tabs={[
{ {
@ -239,6 +243,7 @@ export function CustomAnalytics(props: Stats) {
<Title text="Daily activity" /> <Title text="Daily activity" />
<Tabs <Tabs
className="mb-4"
defaultIndex={0} defaultIndex={0}
tabs={[ tabs={[
{ {
@ -293,6 +298,7 @@ export function CustomAnalytics(props: Stats) {
<Spacer h={4} /> <Spacer h={4} />
<Tabs <Tabs
className="mb-4"
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
{ {
@ -323,6 +329,7 @@ export function CustomAnalytics(props: Stats) {
<Title text="Ratio of Active Users" /> <Title text="Ratio of Active Users" />
<Tabs <Tabs
className="mb-4"
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
{ {
@ -367,6 +374,7 @@ export function CustomAnalytics(props: Stats) {
Sum of bet amounts. (Divided by 100 to be more readable.) Sum of bet amounts. (Divided by 100 to be more readable.)
</p> </p>
<Tabs <Tabs
className="mb-4"
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
{ {