Group leaderboards show members only by default

This commit is contained in:
Austin Chen 2022-06-24 18:38:39 -05:00
parent 8357361038
commit da81035e58
3 changed files with 148 additions and 61 deletions

View File

@ -0,0 +1,38 @@
/* This example requires Tailwind CSS v2.0+ */
import { Switch } from '@headlessui/react'
import clsx from 'clsx'
export default function ShortToggle(props: {
enabled: boolean
setEnabled: (enabled: boolean) => void
}) {
const { enabled, setEnabled } = props
return (
<Switch
checked={enabled}
onChange={setEnabled}
className="group relative inline-flex h-5 w-10 flex-shrink-0 cursor-pointer items-center justify-center rounded-full focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<span className="sr-only">Use setting</span>
<span
aria-hidden="true"
className="pointer-events-none absolute h-full w-full rounded-md bg-white"
/>
<span
aria-hidden="true"
className={clsx(
enabled ? 'bg-indigo-600' : 'bg-gray-200',
'pointer-events-none absolute mx-auto h-4 w-9 rounded-full transition-colors duration-200 ease-in-out'
)}
/>
<span
aria-hidden="true"
className={clsx(
enabled ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-full border border-gray-200 bg-white shadow ring-0 transition-transform duration-200 ease-in-out'
)}
/>
</Switch>
)
}

View File

@ -65,19 +65,18 @@ export const useMemberGroupIds = (user: User | null | undefined) => {
export function useMembers(group: Group) { export function useMembers(group: Group) {
const [members, setMembers] = useState<User[]>([]) const [members, setMembers] = useState<User[]>([])
useEffect(() => { useEffect(() => {
const { memberIds, creatorId } = group const { memberIds } = group
if (memberIds.length > 1) if (memberIds.length > 0) {
// get users via their user ids: listMembers(group).then((members) => setMembers(members))
Promise.all( }
memberIds.filter((mId) => mId !== creatorId).map(getUser)
).then((users) => {
const members = users.filter((user) => user)
setMembers(members)
})
}, [group]) }, [group])
return members return members
} }
export async function listMembers(group: Group) {
return await Promise.all(group.memberIds.map(getUser))
}
export const useGroupsWithContract = (contractId: string | undefined) => { export const useGroupsWithContract = (contractId: string | undefined) => {
const [groups, setGroups] = useState<Group[] | null | undefined>() const [groups, setGroups] = useState<Group[] | null | undefined>()

View File

@ -17,7 +17,7 @@ import { firebaseLogin, getUser, User } from 'web/lib/firebase/users'
import { Spacer } from 'web/components/layout/spacer' import { Spacer } from 'web/components/layout/spacer'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { useGroup, useMembers } from 'web/hooks/use-group' import { listMembers, useGroup, useMembers } from 'web/hooks/use-group'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { scoreCreators, scoreTraders } from 'common/scoring' import { scoreCreators, scoreTraders } from 'common/scoring'
import { Leaderboard } from 'web/components/leaderboard' import { Leaderboard } from 'web/components/leaderboard'
@ -39,12 +39,14 @@ import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params'
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group' import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { useCommentsOnGroup } from 'web/hooks/use-comments' import { useCommentsOnGroup } from 'web/hooks/use-comments'
import ShortToggle from 'web/components/widgets/short-toggle'
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[] } }) {
const { slugs } = props.params const { slugs } = props.params
const group = await getGroupBySlug(slugs[0]) const group = await getGroupBySlug(slugs[0])
const members = group ? await listMembers(group) : []
const creatorPromise = group ? getUser(group.creatorId) : null const creatorPromise = group ? getUser(group.creatorId) : null
const contracts = group ? await getGroupContracts(group).catch((_) => []) : [] const contracts = group ? await getGroupContracts(group).catch((_) => []) : []
@ -65,6 +67,7 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) {
return { return {
props: { props: {
group, group,
members,
creator, creator,
traderScores, traderScores,
topTraders, topTraders,
@ -95,6 +98,7 @@ const groupSubpages = [undefined, 'chat', 'questions', 'details'] as const
export default function GroupPage(props: { export default function GroupPage(props: {
group: Group | null group: Group | null
members: User[]
creator: User creator: User
traderScores: { [userId: string]: number } traderScores: { [userId: string]: number }
topTraders: User[] topTraders: User[]
@ -103,14 +107,21 @@ export default function GroupPage(props: {
}) { }) {
props = usePropz(props, getStaticPropz) ?? { props = usePropz(props, getStaticPropz) ?? {
group: null, group: null,
members: [],
creator: null, creator: null,
traderScores: {}, traderScores: {},
topTraders: [], topTraders: [],
creatorScores: {}, creatorScores: {},
topCreators: [], topCreators: [],
} }
const { creator, traderScores, topTraders, creatorScores, topCreators } = const {
props creator,
members,
traderScores,
topTraders,
creatorScores,
topCreators,
} = props
const router = useRouter() const router = useRouter()
const { slugs } = router.query as { slugs: string[] } const { slugs } = router.query as { slugs: string[] }
@ -175,15 +186,15 @@ export default function GroupPage(props: {
user={user} user={user}
/> />
<Spacer h={8} /> <Spacer h={8} />
<Col className="mt-4 gap-8 px-4 md:flex-row">
<GroupLeaderboards <GroupLeaderboards
traderScores={traderScores} traderScores={traderScores}
creatorScores={creatorScores} creatorScores={creatorScores}
topTraders={topTraders} topTraders={topTraders}
topCreators={topCreators} topCreators={topCreators}
user={user} members={members}
/> user={user}
</Col> />
</Col> </Col>
) )
return ( return (
@ -312,7 +323,7 @@ function GroupOverview(props: {
return ( return (
<Col> <Col>
<Row className="items-center justify-end rounded-t bg-indigo-500 px-4 py-3 text-sm text-white"> <Row className="items-center justify-end rounded-t bg-indigo-500 px-4 py-3 text-sm text-white">
<Row className="flex-1 justify-start">About group</Row> <Row className="flex-1 justify-start">About {group.name}</Row>
{isCreator && <EditGroupButton className={'ml-1'} group={group} />} {isCreator && <EditGroupButton className={'ml-1'} group={group} />}
</Row> </Row>
<Col className="gap-2 rounded-b bg-white p-4"> <Col className="gap-2 rounded-b bg-white p-4">
@ -324,7 +335,6 @@ function GroupOverview(props: {
username={creator.username} username={creator.username}
/> />
</Row> </Row>
<GroupMembersList group={group} />
<Row className={'items-center gap-1'}> <Row className={'items-center gap-1'}>
<span className={'text-gray-500'}>Membership</span> <span className={'text-gray-500'}>Membership</span>
{user && user.id === creator.id ? ( {user && user.id === creator.id ? (
@ -343,14 +353,6 @@ function GroupOverview(props: {
</span> </span>
)} )}
</Row> </Row>
{about && (
<>
<Spacer h={2} />
<div className="text-gray-500">
<Linkify text={about} />
</div>
</>
)}
</Col> </Col>
</Col> </Col>
) )
@ -381,46 +383,94 @@ export function GroupMembersList(props: { group: Group }) {
) )
} }
function SortedLeaderboard(props: {
users: User[]
scoreFunction: (user: User) => number
title: string
header: string
}) {
const { users, scoreFunction, title, header } = props
const sortedUsers = users.sort((a, b) => scoreFunction(b) - scoreFunction(a))
return (
<Leaderboard
className="max-w-xl"
users={sortedUsers}
title={title}
columns={[
{ header, renderCell: (user) => formatMoney(scoreFunction(user)) },
]}
/>
)
}
function GroupLeaderboards(props: { function GroupLeaderboards(props: {
traderScores: { [userId: string]: number } traderScores: { [userId: string]: number }
creatorScores: { [userId: string]: number } creatorScores: { [userId: string]: number }
topTraders: User[] topTraders: User[]
topCreators: User[] topCreators: User[]
members: User[]
user: User | null | undefined user: User | null | undefined
}) { }) {
const { traderScores, creatorScores, topTraders, topCreators } = props const { traderScores, creatorScores, members, topTraders, topCreators } =
props
const topTraderScores = topTraders.map((user) => traderScores[user.id]) const [includeOutsiders, setIncludeOutsiders] = useState(false)
const topCreatorScores = topCreators.map((user) => creatorScores[user.id])
// Consider hiding M$0
return ( return (
<> <Col>
<Leaderboard <Row className="items-center justify-end gap-4 text-gray-500">
className="max-w-xl" Include all users
title="🏅 Top bettors" <ShortToggle
users={topTraders} enabled={includeOutsiders}
columns={[ setEnabled={setIncludeOutsiders}
{ />
header: 'Profit', </Row>
renderCell: (user) =>
formatMoney(topTraderScores[topTraders.indexOf(user)]),
},
]}
/>
<Leaderboard <div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
className="max-w-xl" {!includeOutsiders ? (
title="🏅 Top creators" <>
users={topCreators} <SortedLeaderboard
columns={[ users={members}
{ scoreFunction={(user) => traderScores[user.id] ?? 0}
header: 'Market volume', title="🏅 Top bettors"
renderCell: (user) => header="Profit"
formatMoney(topCreatorScores[topCreators.indexOf(user)]), />
}, <SortedLeaderboard
]} users={members}
/> scoreFunction={(user) => creatorScores[user.id] ?? 0}
</> title="🏅 Top creators"
header="Market volume"
/>
</>
) : (
<>
<Leaderboard
className="max-w-xl"
title="🏅 Top bettors"
users={topTraders}
columns={[
{
header: 'Profit',
renderCell: (user) => formatMoney(traderScores[user.id] ?? 0),
},
]}
/>
<Leaderboard
className="max-w-xl"
title="🏅 Top creators"
users={topCreators}
columns={[
{
header: 'Market volume',
renderCell: (user) =>
formatMoney(creatorScores[user.id] ?? 0),
},
]}
/>
</>
)}
</div>
</Col>
) )
} }