Add a left sidebar on desktop (#70)
* Copy in nav from TailwindUI * Split up nav files * Hook up sidebar options to the current page * Tweak padding * Insert a right sidebar on folds & contracts * Keep column always centered * Remove markets and folds from top navbar * Extract out sidebaricon; link to /about * Rename to "useFollowedFoldIds" * Cache followed folds in localstorage * Remove unused mobile sidebar (for now) * Remove unused code * Remove sidebar from landing page * Tweak resolution panel styling * Remove the top navbar entirely * Completely remove the old navbar * Add "more" and profile link * Rearrange sidebar ordering * Remove unused component * Add Sign In button for logged-out users * Remove extra options for signed-out users
This commit is contained in:
parent
cfbbbc4367
commit
5c12da140d
|
@ -85,7 +85,7 @@ export default function FeedCreate(props: {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'mt-2 w-full cursor-text rounded bg-white p-4 shadow-md',
|
'w-full cursor-text rounded bg-white p-4 shadow-md',
|
||||||
isExpanded ? 'ring-2 ring-indigo-300' : '',
|
isExpanded ? 'ring-2 ring-indigo-300' : '',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { Fold } from '../../../common/fold'
|
import { Fold } from '../../../common/fold'
|
||||||
import { useFollowedFolds } from '../../hooks/use-fold'
|
import { useFollowedFoldIds } from '../../hooks/use-fold'
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import { followFold, unfollowFold } from '../../lib/firebase/folds'
|
import { followFold, unfollowFold } from '../../lib/firebase/folds'
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ export function FollowFoldButton(props: { fold: Fold; className?: string }) {
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
const followedFoldIds = useFollowedFolds(user)
|
const followedFoldIds = useFollowedFoldIds(user)
|
||||||
const following = followedFoldIds
|
const following = followedFoldIds
|
||||||
? followedFoldIds.includes(fold.id)
|
? followedFoldIds.includes(fold.id)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
|
@ -7,21 +7,24 @@ import { ENV_CONFIG } from '../../../common/envs/constants'
|
||||||
export function ManifoldLogo(props: {
|
export function ManifoldLogo(props: {
|
||||||
className?: string
|
className?: string
|
||||||
darkBackground?: boolean
|
darkBackground?: boolean
|
||||||
|
hideText?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { darkBackground, className } = props
|
const { darkBackground, className, hideText } = props
|
||||||
|
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={user ? '/home' : '/'}>
|
<Link href={user ? '/home' : '/'}>
|
||||||
<a className={clsx('flex flex-shrink-0 flex-row gap-4', className)}>
|
<a className={clsx('group flex flex-shrink-0 flex-row gap-4', className)}>
|
||||||
<img
|
<img
|
||||||
className="transition-all hover:rotate-12"
|
className="transition-all group-hover:rotate-12"
|
||||||
src={darkBackground ? '/logo-white.svg' : '/logo.svg'}
|
src={darkBackground ? '/logo-white.svg' : '/logo.svg'}
|
||||||
width={45}
|
width={45}
|
||||||
height={45}
|
height={45}
|
||||||
/>
|
/>
|
||||||
{ENV_CONFIG.navbarLogoPath ? (
|
|
||||||
|
{!hideText &&
|
||||||
|
(ENV_CONFIG.navbarLogoPath ? (
|
||||||
<img src={ENV_CONFIG.navbarLogoPath} width={245} height={45} />
|
<img src={ENV_CONFIG.navbarLogoPath} width={245} height={45} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -44,7 +47,7 @@ export function ManifoldLogo(props: {
|
||||||
Manifold Markets
|
Manifold Markets
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
))}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
|
|
|
@ -2,10 +2,7 @@ import clsx from 'clsx'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
||||||
import { useUser } from '../../hooks/use-user'
|
import { useUser } from '../../hooks/use-user'
|
||||||
import { Row } from '../layout/row'
|
|
||||||
import { firebaseLogin, User } from '../../lib/firebase/users'
|
import { firebaseLogin, User } from '../../lib/firebase/users'
|
||||||
import { ManifoldLogo } from './manifold-logo'
|
|
||||||
import { ProfileMenu } from './profile-menu'
|
|
||||||
import {
|
import {
|
||||||
CollectionIcon,
|
CollectionIcon,
|
||||||
HomeIcon,
|
HomeIcon,
|
||||||
|
@ -13,50 +10,12 @@ import {
|
||||||
UserGroupIcon,
|
UserGroupIcon,
|
||||||
} from '@heroicons/react/outline'
|
} from '@heroicons/react/outline'
|
||||||
|
|
||||||
export function NavBar(props: {
|
|
||||||
darkBackground?: boolean
|
|
||||||
wide?: boolean
|
|
||||||
assertUser?: 'signed-in' | 'signed-out'
|
|
||||||
className?: string
|
|
||||||
}) {
|
|
||||||
const { darkBackground, wide, assertUser, className } = props
|
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
const hoverClasses =
|
|
||||||
'hover:underline hover:decoration-indigo-400 hover:decoration-2'
|
|
||||||
const themeClasses = clsx(darkBackground && 'text-white', hoverClasses)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<nav className={clsx('mb-4 w-full p-4', className)} aria-label="Global">
|
|
||||||
<Row
|
|
||||||
className={clsx(
|
|
||||||
'mx-auto items-center justify-between sm:px-4',
|
|
||||||
wide ? 'max-w-6xl' : 'max-w-4xl'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<ManifoldLogo className="my-1" darkBackground={darkBackground} />
|
|
||||||
|
|
||||||
<Row className="ml-6 items-center gap-6 sm:gap-8">
|
|
||||||
{(user || user === null || assertUser) && (
|
|
||||||
<NavOptions
|
|
||||||
user={user}
|
|
||||||
assertUser={assertUser}
|
|
||||||
themeClasses={themeClasses}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Row>
|
|
||||||
</Row>
|
|
||||||
</nav>
|
|
||||||
{user && <BottomNavBar user={user} />}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// From https://codepen.io/chris__sev/pen/QWGvYbL
|
// From https://codepen.io/chris__sev/pen/QWGvYbL
|
||||||
function BottomNavBar(props: { user: User }) {
|
export function BottomNavBar() {
|
||||||
const { user } = props
|
const user = useUser()
|
||||||
|
if (!user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<nav className="fixed inset-x-0 bottom-0 z-20 flex justify-between border-t-2 bg-white text-xs text-gray-700 md:hidden">
|
<nav className="fixed inset-x-0 bottom-0 z-20 flex justify-between border-t-2 bg-white text-xs text-gray-700 md:hidden">
|
||||||
<Link href="/home">
|
<Link href="/home">
|
||||||
|
@ -89,6 +48,7 @@ function BottomNavBar(props: { user: User }) {
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
{/* TODO: replace with a link to your own profile */}
|
||||||
<Link href="/trades">
|
<Link href="/trades">
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
|
@ -101,65 +61,3 @@ function BottomNavBar(props: { user: User }) {
|
||||||
</nav>
|
</nav>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function NavOptions(props: {
|
|
||||||
user: User | null | undefined
|
|
||||||
assertUser: 'signed-in' | 'signed-out' | undefined
|
|
||||||
themeClasses: string
|
|
||||||
}) {
|
|
||||||
const { user, assertUser, themeClasses } = props
|
|
||||||
const showSignedIn = assertUser === 'signed-in' || !!user
|
|
||||||
const showSignedOut =
|
|
||||||
!showSignedIn && (assertUser === 'signed-out' || user === null)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{showSignedOut && (
|
|
||||||
<Link href="/about">
|
|
||||||
<a
|
|
||||||
className={clsx(
|
|
||||||
'hidden whitespace-nowrap text-base md:block',
|
|
||||||
themeClasses
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
About
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Link href="/folds">
|
|
||||||
<a
|
|
||||||
className={clsx(
|
|
||||||
'hidden whitespace-nowrap text-base md:block',
|
|
||||||
themeClasses
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Communities
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link href="/markets">
|
|
||||||
<a
|
|
||||||
className={clsx(
|
|
||||||
'hidden whitespace-nowrap text-base md:block',
|
|
||||||
themeClasses
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
Markets
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{showSignedOut && (
|
|
||||||
<>
|
|
||||||
<button
|
|
||||||
className="btn btn-sm btn-outline bg-gradient-to-r px-6 text-base font-medium normal-case"
|
|
||||||
onClick={firebaseLogin}
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{showSignedIn && <ProfileMenu user={user ?? undefined} />}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,106 +1,42 @@
|
||||||
import { firebaseLogout, User } from '../../lib/firebase/users'
|
import { firebaseLogout, User } from '../../lib/firebase/users'
|
||||||
import { formatMoney } from '../../../common/util/format'
|
import { formatMoney } from '../../../common/util/format'
|
||||||
import { Avatar } from '../avatar'
|
import { Avatar } from '../avatar'
|
||||||
import { Col } from '../layout/col'
|
|
||||||
import { MenuButton } from './menu'
|
|
||||||
import { IS_PRIVATE_MANIFOLD } from '../../../common/envs/constants'
|
import { IS_PRIVATE_MANIFOLD } from '../../../common/envs/constants'
|
||||||
|
import { Row } from '../layout/row'
|
||||||
|
|
||||||
export function ProfileMenu(props: { user: User | undefined }) {
|
export function getNavigationOptions(user?: User | null) {
|
||||||
const { user } = props
|
if (IS_PRIVATE_MANIFOLD) {
|
||||||
|
return [{ name: 'Leaderboards', href: '/leaderboards' }]
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<MenuButton
|
|
||||||
className="hidden md:block"
|
|
||||||
menuItems={getNavigationOptions(user, { mobile: false })}
|
|
||||||
buttonContent={<ProfileSummary user={user} />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MenuButton
|
|
||||||
className="mr-2 md:hidden"
|
|
||||||
menuItems={getNavigationOptions(user, { mobile: true })}
|
|
||||||
buttonContent={<ProfileSummary user={user} />}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNavigationOptions(
|
if (!user) {
|
||||||
user: User | undefined,
|
|
||||||
options: { mobile: boolean }
|
|
||||||
) {
|
|
||||||
const { mobile } = options
|
|
||||||
return [
|
return [
|
||||||
{
|
{ name: 'Leaderboards', href: '/leaderboards' },
|
||||||
name: 'Home',
|
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
||||||
href: user ? '/home' : '/',
|
|
||||||
},
|
|
||||||
...(mobile
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: 'Markets',
|
|
||||||
href: '/markets',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Communities',
|
|
||||||
href: '/folds',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
name: `Your profile`,
|
|
||||||
href: `/${user?.username}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Your trades',
|
|
||||||
href: '/trades',
|
|
||||||
},
|
|
||||||
// Disable irrelevant menu options for teams.
|
|
||||||
...(IS_PRIVATE_MANIFOLD
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
name: 'Leaderboards',
|
|
||||||
href: '/leaderboards',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
name: 'Add funds',
|
|
||||||
href: '/add-funds',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Leaderboards',
|
|
||||||
href: '/leaderboards',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Discord',
|
|
||||||
href: 'https://discord.gg/eHQBNBqXuh',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'About',
|
|
||||||
href: '/about',
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
{
|
|
||||||
name: 'Sign out',
|
|
||||||
href: '#',
|
|
||||||
onClick: () => firebaseLogout(),
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProfileSummary(props: { user: User | undefined }) {
|
return [
|
||||||
|
{ name: 'Your trades', href: '/trades' },
|
||||||
|
{ name: 'Add funds', href: '/add-funds' },
|
||||||
|
{ name: 'Leaderboards', href: '/leaderboards' },
|
||||||
|
{ name: 'Discord', href: 'https://discord.gg/eHQBNBqXuh' },
|
||||||
|
{ name: 'Sign out', href: '#', onClick: () => firebaseLogout() },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ProfileSummary(props: { user: User | undefined }) {
|
||||||
const { user } = props
|
const { user } = props
|
||||||
return (
|
return (
|
||||||
<Col className="avatar items-center gap-2 sm:flex-row sm:gap-4">
|
<Row className="group avatar items-center gap-4 py-6 text-gray-600 group-hover:text-gray-900">
|
||||||
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} noLink />
|
<Avatar avatarUrl={user?.avatarUrl} username={user?.username} noLink />
|
||||||
|
|
||||||
<div className="truncate text-left sm:w-32">
|
<div className="truncate text-left sm:w-32">
|
||||||
<div className="hidden sm:flex">{user?.name}</div>
|
<div className="hidden sm:flex">{user?.name}</div>
|
||||||
<div className="text-sm text-gray-700">
|
<div className="text-sm">
|
||||||
{user ? formatMoney(Math.floor(user.balance)) : ' '}
|
{user ? formatMoney(Math.floor(user.balance)) : ' '}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
135
web/components/nav/sidebar.tsx
Normal file
135
web/components/nav/sidebar.tsx
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
import {
|
||||||
|
HomeIcon,
|
||||||
|
UserGroupIcon,
|
||||||
|
SearchIcon,
|
||||||
|
BookOpenIcon,
|
||||||
|
DotsHorizontalIcon,
|
||||||
|
} from '@heroicons/react/outline'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import _ from 'lodash'
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useFollowedFolds } from '../../hooks/use-fold'
|
||||||
|
import { useUser } from '../../hooks/use-user'
|
||||||
|
import { firebaseLogin } from '../../lib/firebase/users'
|
||||||
|
import { ManifoldLogo } from './manifold-logo'
|
||||||
|
import { MenuButton } from './menu'
|
||||||
|
import { getNavigationOptions, ProfileSummary } from './profile-menu'
|
||||||
|
|
||||||
|
const navigation = [
|
||||||
|
{ name: 'Home', href: '/home', icon: HomeIcon },
|
||||||
|
{ name: 'Markets', href: '/markets', icon: SearchIcon },
|
||||||
|
{ name: 'About', href: 'https://docs.manifold.markets', icon: BookOpenIcon },
|
||||||
|
]
|
||||||
|
|
||||||
|
type Item = {
|
||||||
|
name: string
|
||||||
|
href: string
|
||||||
|
icon: React.ComponentType<{ className?: string }>
|
||||||
|
}
|
||||||
|
|
||||||
|
function SidebarItem(props: { item: Item; currentPage: string }) {
|
||||||
|
const { item, currentPage } = props
|
||||||
|
return (
|
||||||
|
<Link href={item.href} key={item.name}>
|
||||||
|
<a
|
||||||
|
className={clsx(
|
||||||
|
item.href == currentPage
|
||||||
|
? 'bg-gray-200 text-gray-900'
|
||||||
|
: 'text-gray-600 hover:bg-gray-50',
|
||||||
|
'group flex items-center rounded-md px-3 py-2 text-sm font-medium'
|
||||||
|
)}
|
||||||
|
aria-current={item.href == currentPage ? 'page' : undefined}
|
||||||
|
>
|
||||||
|
<item.icon
|
||||||
|
className={clsx(
|
||||||
|
item.href == currentPage
|
||||||
|
? 'text-gray-500'
|
||||||
|
: 'text-gray-400 group-hover:text-gray-500',
|
||||||
|
'-ml-1 mr-3 h-6 w-6 flex-shrink-0'
|
||||||
|
)}
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span className="truncate">{item.name}</span>
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MoreButton() {
|
||||||
|
return (
|
||||||
|
<a className="group flex items-center rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:cursor-pointer hover:bg-gray-50">
|
||||||
|
<DotsHorizontalIcon
|
||||||
|
className="-ml-1 mr-3 h-6 w-6 flex-shrink-0 text-gray-400 group-hover:text-gray-500"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<span className="truncate">More</span>
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Sidebar() {
|
||||||
|
const router = useRouter()
|
||||||
|
const currentPage = router.pathname
|
||||||
|
|
||||||
|
const user = useUser()
|
||||||
|
let folds = useFollowedFolds(user) || []
|
||||||
|
folds = _.sortBy(folds, 'followCount').reverse()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<nav aria-label="Sidebar" className="sticky top-4 divide-y divide-gray-300">
|
||||||
|
<div className="space-y-1 pb-6">
|
||||||
|
<ManifoldLogo hideText />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{user ? (
|
||||||
|
<Link href={`/${user.username}`}>
|
||||||
|
<a className="group">
|
||||||
|
<ProfileSummary user={user} />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className="py-6 text-center">
|
||||||
|
<button
|
||||||
|
className="btn border-none bg-gradient-to-r from-teal-500 to-green-500 px-10 text-lg font-medium normal-case hover:from-teal-600 hover:to-green-600"
|
||||||
|
onClick={firebaseLogin}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-1 py-6">
|
||||||
|
{navigation.map((item) => (
|
||||||
|
<SidebarItem item={item} currentPage={currentPage} />
|
||||||
|
))}
|
||||||
|
|
||||||
|
<MenuButton
|
||||||
|
menuItems={getNavigationOptions(user)}
|
||||||
|
buttonContent={<MoreButton />}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-6">
|
||||||
|
<SidebarItem
|
||||||
|
item={{ name: 'Communities', href: '/folds', icon: UserGroupIcon }}
|
||||||
|
currentPage={currentPage}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-3 space-y-2">
|
||||||
|
{folds.map((fold) => (
|
||||||
|
<a
|
||||||
|
key={fold.name}
|
||||||
|
href={`/fold/${fold.slug}`}
|
||||||
|
className="group flex items-center rounded-md px-3 py-2 text-sm font-medium text-gray-600 hover:bg-gray-50 hover:text-gray-900"
|
||||||
|
>
|
||||||
|
<span className="truncate"> {fold.name}</span>
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,27 +1,42 @@
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
import { NavBar } from './nav/nav-bar'
|
import { BottomNavBar } from './nav/nav-bar'
|
||||||
|
import Sidebar from './nav/sidebar'
|
||||||
|
|
||||||
export function Page(props: {
|
export function Page(props: {
|
||||||
wide?: boolean
|
wide?: boolean
|
||||||
margin?: boolean
|
margin?: boolean
|
||||||
assertUser?: 'signed-in' | 'signed-out'
|
assertUser?: 'signed-in' | 'signed-out'
|
||||||
|
rightSidebar?: React.ReactNode
|
||||||
children?: any
|
children?: any
|
||||||
}) {
|
}) {
|
||||||
const { wide, margin, assertUser, children } = props
|
const { wide, margin, assertUser, children, rightSidebar } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<NavBar wide={wide} assertUser={assertUser} />
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'mx-auto w-full pb-16',
|
'mx-auto w-full pb-16 lg:grid lg:grid-cols-12 lg:gap-8 xl:max-w-7xl',
|
||||||
wide ? 'max-w-6xl' : 'max-w-4xl',
|
wide ? 'max-w-6xl' : 'max-w-4xl',
|
||||||
margin && 'px-4'
|
margin && 'px-4'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{children}
|
<div className="hidden lg:col-span-3 lg:block xl:col-span-2">
|
||||||
|
{assertUser !== 'signed-out' && <Sidebar />}
|
||||||
</div>
|
</div>
|
||||||
|
<main
|
||||||
|
className={clsx(
|
||||||
|
'mt-6 lg:col-span-9',
|
||||||
|
rightSidebar ? 'xl:col-span-7' : 'xl:col-span-8'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<aside className="hidden xl:col-span-3 xl:block">
|
||||||
|
<div className="sticky top-4 space-y-4">{rightSidebar}</div>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<BottomNavBar />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ export function ResolutionPanel(props: {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
<Col className={clsx('rounded-md bg-white px-8 py-6', className)}>
|
||||||
<Title className="mt-0 whitespace-nowrap" text="Resolve market" />
|
<Title className="!mt-0 whitespace-nowrap" text="Resolve market" />
|
||||||
|
|
||||||
<div className="mb-2 text-sm text-gray-500">Outcome</div>
|
<div className="mb-2 text-sm text-gray-500">Outcome</div>
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { Comment, getRecentComments } from '../lib/firebase/comments'
|
||||||
import { Contract, getActiveContracts } from '../lib/firebase/contracts'
|
import { Contract, getActiveContracts } from '../lib/firebase/contracts'
|
||||||
import { listAllFolds } from '../lib/firebase/folds'
|
import { listAllFolds } from '../lib/firebase/folds'
|
||||||
import { useInactiveContracts } from './use-contracts'
|
import { useInactiveContracts } from './use-contracts'
|
||||||
import { useFollowedFolds } from './use-fold'
|
import { useFollowedFoldIds } from './use-fold'
|
||||||
import { useSeenContracts } from './use-seen-contracts'
|
import { useSeenContracts } from './use-seen-contracts'
|
||||||
import { useUserBetContracts } from './use-user-bets'
|
import { useUserBetContracts } from './use-user-bets'
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ export const useFilterYourContracts = (
|
||||||
folds: Fold[],
|
folds: Fold[],
|
||||||
contracts: Contract[]
|
contracts: Contract[]
|
||||||
) => {
|
) => {
|
||||||
const followedFoldIds = useFollowedFolds(user)
|
const followedFoldIds = useFollowedFoldIds(user)
|
||||||
|
|
||||||
const followedFolds = filterDefined(
|
const followedFolds = filterDefined(
|
||||||
(followedFoldIds ?? []).map((id) => folds.find((fold) => fold.id === id))
|
(followedFoldIds ?? []).map((id) => folds.find((fold) => fold.id === id))
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
import _ from 'lodash'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Fold } from '../../common/fold'
|
import { Fold } from '../../common/fold'
|
||||||
import { User } from '../../common/user'
|
import { User } from '../../common/user'
|
||||||
import {
|
import {
|
||||||
|
listAllFolds,
|
||||||
listenForFold,
|
listenForFold,
|
||||||
listenForFolds,
|
listenForFolds,
|
||||||
listenForFoldsWithTags,
|
listenForFoldsWithTags,
|
||||||
|
@ -49,8 +51,8 @@ export const useFollowingFold = (fold: Fold, user: User | null | undefined) => {
|
||||||
return following
|
return following
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: We cache FollowedFolds in localstorage to speed up the initial load
|
// Note: We cache followedFoldIds in localstorage to speed up the initial load
|
||||||
export const useFollowedFolds = (user: User | null | undefined) => {
|
export const useFollowedFoldIds = (user: User | null | undefined) => {
|
||||||
const [followedFoldIds, setFollowedFoldIds] = useState<string[] | undefined>(
|
const [followedFoldIds, setFollowedFoldIds] = useState<string[] | undefined>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
|
@ -72,3 +74,38 @@ export const useFollowedFolds = (user: User | null | undefined) => {
|
||||||
|
|
||||||
return followedFoldIds
|
return followedFoldIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We also cache followedFolds directly in JSON.
|
||||||
|
// TODO: Extract out localStorage caches to a utility
|
||||||
|
export const useFollowedFolds = (user: User | null | undefined) => {
|
||||||
|
const [followedFolds, setFollowedFolds] = useState<Fold[] | undefined>()
|
||||||
|
const ids = useFollowedFoldIds(user)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (user && ids) {
|
||||||
|
const key = `followed-full-folds-${user.id}`
|
||||||
|
const followedFoldJson = localStorage.getItem(key)
|
||||||
|
if (followedFoldJson) {
|
||||||
|
setFollowedFolds(JSON.parse(followedFoldJson))
|
||||||
|
// Exit early if ids and followedFoldIds have all the same elements.
|
||||||
|
if (
|
||||||
|
_.isEqual(
|
||||||
|
_.sortBy(ids),
|
||||||
|
_.sortBy(JSON.parse(followedFoldJson).map((f: Fold) => f.id))
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, fetch the full contents of all folds
|
||||||
|
listAllFolds().then((folds) => {
|
||||||
|
const followedFolds = folds.filter((fold) => ids.includes(fold.id))
|
||||||
|
setFollowedFolds(followedFolds)
|
||||||
|
localStorage.setItem(key, JSON.stringify(followedFolds))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [user, ids])
|
||||||
|
|
||||||
|
return followedFolds
|
||||||
|
}
|
||||||
|
|
|
@ -121,8 +121,17 @@ export default function ContractPage(props: {
|
||||||
|
|
||||||
const ogCardProps = getOpenGraphProps(contract)
|
const ogCardProps = getOpenGraphProps(contract)
|
||||||
|
|
||||||
|
const rightSidebar = hasSidePanel ? (
|
||||||
|
<Col className="gap-4">
|
||||||
|
{allowTrade && (
|
||||||
|
<BetPanel className="hidden lg:flex" contract={contract} />
|
||||||
|
)}
|
||||||
|
{allowResolve && <ResolutionPanel creator={user} contract={contract} />}
|
||||||
|
</Col>
|
||||||
|
) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page wide={hasSidePanel}>
|
<Page wide={hasSidePanel} rightSidebar={rightSidebar}>
|
||||||
{ogCardProps && (
|
{ogCardProps && (
|
||||||
<SEO
|
<SEO
|
||||||
title={question}
|
title={question}
|
||||||
|
@ -168,21 +177,6 @@ export default function ContractPage(props: {
|
||||||
)}
|
)}
|
||||||
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
<BetsSection contract={contract} user={user ?? null} bets={bets} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{hasSidePanel && (
|
|
||||||
<>
|
|
||||||
<div className="md:ml-6" />
|
|
||||||
|
|
||||||
<Col className="flex-shrink-0 md:w-[310px]">
|
|
||||||
{allowTrade && (
|
|
||||||
<BetPanel className="hidden lg:flex" contract={contract} />
|
|
||||||
)}
|
|
||||||
{allowResolve && (
|
|
||||||
<ResolutionPanel creator={user} contract={contract} />
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Col>
|
</Col>
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
|
|
|
@ -159,8 +159,28 @@ export default function FoldPage(props: {
|
||||||
return <Custom404 />
|
return <Custom404 />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rightSidebar = (
|
||||||
|
<Col className="mt-6 gap-12">
|
||||||
|
<Row className="justify-end">
|
||||||
|
{isCurator ? (
|
||||||
|
<EditFoldButton className="ml-1" fold={fold} />
|
||||||
|
) : (
|
||||||
|
<FollowFoldButton className="ml-1" fold={fold} />
|
||||||
|
)}
|
||||||
|
</Row>
|
||||||
|
<FoldOverview fold={fold} curator={curator} />
|
||||||
|
<FoldLeaderboards
|
||||||
|
traderScores={traderScores}
|
||||||
|
creatorScores={creatorScores}
|
||||||
|
topTraders={topTraders}
|
||||||
|
topCreators={topCreators}
|
||||||
|
user={user}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page wide>
|
<Page rightSidebar={rightSidebar}>
|
||||||
<SEO
|
<SEO
|
||||||
title={fold.name}
|
title={fold.name}
|
||||||
description={`Curated by ${curator.name}. ${fold.about}`}
|
description={`Curated by ${curator.name}. ${fold.about}`}
|
||||||
|
@ -170,11 +190,6 @@ export default function FoldPage(props: {
|
||||||
<div className="px-3 lg:px-1">
|
<div className="px-3 lg:px-1">
|
||||||
<Row className="mb-6 justify-between">
|
<Row className="mb-6 justify-between">
|
||||||
<Title className="!m-0" text={fold.name} />
|
<Title className="!m-0" text={fold.name} />
|
||||||
{isCurator ? (
|
|
||||||
<EditFoldButton className="ml-1" fold={fold} />
|
|
||||||
) : (
|
|
||||||
<FollowFoldButton className="ml-1" fold={fold} />
|
|
||||||
)}
|
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<Col className="mb-6 gap-2 text-gray-500 md:hidden">
|
<Col className="mb-6 gap-2 text-gray-500 md:hidden">
|
||||||
|
@ -259,16 +274,6 @@ export default function FoldPage(props: {
|
||||||
<SearchableGrid contracts={contracts} />
|
<SearchableGrid contracts={contracts} />
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
<Col className="hidden w-full max-w-xs gap-12 md:flex">
|
|
||||||
<FoldOverview fold={fold} curator={curator} />
|
|
||||||
<FoldLeaderboards
|
|
||||||
traderScores={traderScores}
|
|
||||||
creatorScores={creatorScores}
|
|
||||||
topTraders={topTraders}
|
|
||||||
topCreators={topCreators}
|
|
||||||
user={user}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
</Row>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,10 @@ import { FollowFoldButton } from '../components/folds/follow-fold-button'
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { Row } from '../components/layout/row'
|
import { Row } from '../components/layout/row'
|
||||||
import { Page } from '../components/page'
|
import { Page } from '../components/page'
|
||||||
import { SiteLink } from '../components/site-link'
|
|
||||||
import { TagsList } from '../components/tags-list'
|
import { TagsList } from '../components/tags-list'
|
||||||
import { Title } from '../components/title'
|
import { Title } from '../components/title'
|
||||||
import { UserLink } from '../components/user-page'
|
import { UserLink } from '../components/user-page'
|
||||||
import { useFolds, useFollowedFolds } from '../hooks/use-fold'
|
import { useFolds, useFollowedFoldIds } from '../hooks/use-fold'
|
||||||
import { useUser } from '../hooks/use-user'
|
import { useUser } from '../hooks/use-user'
|
||||||
import { foldPath, listAllFolds } from '../lib/firebase/folds'
|
import { foldPath, listAllFolds } from '../lib/firebase/folds'
|
||||||
import { getUser, User } from '../lib/firebase/users'
|
import { getUser, User } from '../lib/firebase/users'
|
||||||
|
@ -44,7 +43,7 @@ export default function Folds(props: {
|
||||||
|
|
||||||
let folds = useFolds() ?? props.folds
|
let folds = useFolds() ?? props.folds
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
const followedFoldIds = useFollowedFolds(user) || []
|
const followedFoldIds = useFollowedFoldIds(user) || []
|
||||||
// First sort by follower count, then list followed folds first
|
// First sort by follower count, then list followed folds first
|
||||||
folds = _.sortBy(folds, (fold) => -1 * fold.followCount)
|
folds = _.sortBy(folds, (fold) => -1 * fold.followCount)
|
||||||
folds = _.sortBy(folds, (fold) => !followedFoldIds.includes(fold.id))
|
folds = _.sortBy(folds, (fold) => !followedFoldIds.includes(fold.id))
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
import { firebaseLogin } from '../lib/firebase/users'
|
import { firebaseLogin } from '../lib/firebase/users'
|
||||||
import { ContractsGrid } from '../components/contracts-list'
|
import { ContractsGrid } from '../components/contracts-list'
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
import { NavBar } from '../components/nav/nav-bar'
|
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { Contract } from '../lib/firebase/contracts'
|
import { Contract } from '../lib/firebase/contracts'
|
||||||
|
|
||||||
|
@ -34,7 +33,6 @@ const scrollToAbout = () => {
|
||||||
function Hero() {
|
function Hero() {
|
||||||
return (
|
return (
|
||||||
<div className="bg-world-trading h-screen overflow-hidden bg-gray-900 bg-cover bg-center lg:bg-left">
|
<div className="bg-world-trading h-screen overflow-hidden bg-gray-900 bg-cover bg-center lg:bg-left">
|
||||||
<NavBar darkBackground />
|
|
||||||
<main>
|
<main>
|
||||||
<div className="pt-32 sm:pt-8 lg:overflow-hidden lg:pt-0 lg:pb-14">
|
<div className="pt-32 sm:pt-8 lg:overflow-hidden lg:pt-0 lg:pb-14">
|
||||||
<div className="mx-auto max-w-7xl lg:px-8 xl:px-0">
|
<div className="mx-auto max-w-7xl lg:px-8 xl:px-0">
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { DatumValue } from '@nivo/core'
|
||||||
import { ResponsiveLine } from '@nivo/line'
|
import { ResponsiveLine } from '@nivo/line'
|
||||||
|
|
||||||
import { Entry, makeEntries } from '../lib/simulator/entries'
|
import { Entry, makeEntries } from '../lib/simulator/entries'
|
||||||
import { NavBar } from '../components/nav/nav-bar'
|
|
||||||
import { Col } from '../components/layout/col'
|
import { Col } from '../components/layout/col'
|
||||||
|
|
||||||
function TableBody(props: { entries: Entry[] }) {
|
function TableBody(props: { entries: Entry[] }) {
|
||||||
|
@ -254,7 +253,6 @@ export default function Simulator() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Col>
|
<Col>
|
||||||
<NavBar />
|
|
||||||
<div className="mx-auto mt-8 grid w-full grid-cols-1 gap-4 p-2 text-center xl:grid-cols-2">
|
<div className="mx-auto mt-8 grid w-full grid-cols-1 gap-4 p-2 text-center xl:grid-cols-2">
|
||||||
{/* Left column */}
|
{/* Left column */}
|
||||||
<div>
|
<div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user