diff --git a/web/pages/group/[...slugs]/index.tsx b/web/pages/group/[...slugs]/index.tsx index fc76df48..0d38580c 100644 --- a/web/pages/group/[...slugs]/index.tsx +++ b/web/pages/group/[...slugs]/index.tsx @@ -1,15 +1,15 @@ import { take, sortBy, debounce } from 'lodash' +import PlusSmIcon from '@heroicons/react/solid/PlusSmIcon' -import { Group } from 'common/group' +import { Group, GROUP_CHAT_SLUG } from 'common/group' import { Page } from 'web/components/page' import { listAllBets } from 'web/lib/firebase/bets' -import { Contract } from 'web/lib/firebase/contracts' +import { Contract, listContractsByGroupSlug } from 'web/lib/firebase/contracts' import { groupPath, getGroupBySlug, - getGroupContracts, updateGroup, - addUserToGroup, + joinGroup, addContractToGroup, } from 'web/lib/firebase/groups' import { Row } from 'web/components/layout/row' @@ -33,38 +33,37 @@ import { SEO } from 'web/components/SEO' import { Linkify } from 'web/components/linkify' import { fromPropz, usePropz } from 'web/hooks/use-propz' import { Tabs } from 'web/components/layout/tabs' -import { ContractsGrid } from 'web/components/contract/contracts-list' -import { - createButtonStyle, - CreateQuestionButton, -} from 'web/components/create-question-button' +import { CreateQuestionButton } from 'web/components/create-question-button' import React, { useEffect, useState } from 'react' import { GroupChat } from 'web/components/groups/group-chat' import { LoadingIndicator } from 'web/components/loading-indicator' import { Modal } from 'web/components/layout/modal' -import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params' +import { getSavedSort } from 'web/hooks/use-sort-and-query-params' import { ChoicesToggleGroup } from 'web/components/choices-toggle-group' import { toast } from 'react-hot-toast' import { useCommentsOnGroup } from 'web/hooks/use-comments' -import { ShareIconButton } from 'web/components/share-icon-button' import { REFERRAL_AMOUNT } from 'common/user' -import { SiteLink } from 'web/components/site-link' import { ContractSearch } from 'web/components/contract-search' import clsx from 'clsx' import { FollowList } from 'web/components/follow-list' import { SearchIcon } from '@heroicons/react/outline' import { useTipTxns } from 'web/hooks/use-tip-txns' import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button' +import { searchInAny } from 'common/util/parse' +import { useWindowSize } from 'web/hooks/use-window-size' +import { CopyLinkButton } from 'web/components/copy-link-button' +import { ENV_CONFIG } from 'common/envs/constants' export const getStaticProps = fromPropz(getStaticPropz) export async function getStaticPropz(props: { params: { slugs: string[] } }) { const { slugs } = props.params const group = await getGroupBySlug(slugs[0]) - const members = group ? await listMembers(group) : [] + const members = group && (await listMembers(group)) const creatorPromise = group ? getUser(group.creatorId) : null - const contracts = group ? await getGroupContracts(group).catch((_) => []) : [] + const contracts = + (group && (await listContractsByGroupSlug(group.slug))) ?? [] const bets = await Promise.all( contracts.map((contract: Contract) => listAllBets(contract.id)) @@ -72,10 +71,12 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) { const creatorScores = scoreCreators(contracts) const traderScores = scoreTraders(contracts, bets) - const [topCreators, topTraders] = await Promise.all([ - toTopUsers(creatorScores), - toTopUsers(traderScores), - ]) + const [topCreators, topTraders] = + (members && [ + toTopUsers(creatorScores, members), + toTopUsers(traderScores, members), + ]) ?? + [] const creator = await creatorPromise @@ -94,14 +95,14 @@ export async function getStaticPropz(props: { params: { slugs: string[] } }) { } } -async function toTopUsers(userScores: { [userId: string]: number }) { +function toTopUsers(userScores: { [userId: string]: number }, users: User[]) { const topUserPairs = take( sortBy(Object.entries(userScores), ([_, score]) => -1 * score), 10 ).filter(([_, score]) => score >= 0.5) - const topUsers = await Promise.all( - topUserPairs.map(([userId]) => getUser(userId)) + const topUsers = topUserPairs.map( + ([userId]) => users.filter((user) => user.id === userId)[0] ) return topUsers.filter((user) => user) } @@ -111,9 +112,9 @@ export async function getStaticPaths() { } const groupSubpages = [ undefined, - 'chat', + GROUP_CHAT_SLUG, 'questions', - 'rankings', + 'leaderboards', 'about', ] as const @@ -149,26 +150,9 @@ export default function GroupPage(props: { const page = slugs?.[1] as typeof groupSubpages[number] const group = useGroup(props.group?.id) ?? props.group - const [contracts, setContracts] = useState(undefined) - const [query, setQuery] = useState('') const tips = useTipTxns({ groupId: group?.id }) const messages = useCommentsOnGroup(group?.id) - const debouncedQuery = debounce(setQuery, 50) - const filteredContracts = - query != '' && contracts - ? contracts.filter( - (c) => - checkAgainstQuery(query, c.question) || - checkAgainstQuery(query, c.creatorName) || - checkAgainstQuery(query, c.creatorUsername) - ) - : [] - - useEffect(() => { - if (group) - getGroupContracts(group).then((contracts) => setContracts(contracts)) - }, [group]) const user = useUser() useEffect(() => { @@ -176,9 +160,14 @@ export default function GroupPage(props: { referrer?: string } if (!user && router.isReady) - writeReferralInfo(creator.username, undefined, referrer, group?.slug) + writeReferralInfo(creator.username, undefined, referrer, group?.id) }, [user, creator, group, router]) + const { width } = useWindowSize() + const chatDisabled = !group || group.chatDisabled + const showChatSidebar = !chatDisabled && (width ?? 1280) >= 1280 + const showChatTab = !chatDisabled && !showChatSidebar + if (group === null || !groupSubpages.includes(page) || slugs[2]) { return } @@ -186,11 +175,6 @@ export default function GroupPage(props: { const isCreator = user && group && user.id === group.creatorId const isMember = user && memberIds.includes(user.id) - const rightSidebar = ( - - - - ) const leaderboard = ( ) + const chatTab = ( + + {messages ? ( + + ) : ( + + )} + + ) + + const questionsTab = ( + + ) + const tabs = [ - ...(group.chatDisabled + ...(!showChatTab ? [] : [ { title: 'Chat', - content: messages ? ( - - ) : ( - - ), - href: groupPath(group.slug, 'chat'), + content: chatTab, + href: groupPath(group.slug, GROUP_CHAT_SLUG), }, ]), { title: 'Questions', - content: ( -
- {contracts ? ( - contracts.length > 0 ? ( - <> - debouncedQuery(e.target.value)} - placeholder="Search the group's questions" - className="input input-bordered mb-4 w-full" - /> - {}} - /> - - ) : ( -
- No questions yet. Why not{' '} - - add one? - -
- ) - ) : ( - - )} -
- ), + content: questionsTab, href: groupPath(group.slug, 'questions'), }, { - title: 'Rankings', + title: 'Leaderboards', content: leaderboard, - href: groupPath(group.slug, 'rankings'), + href: groupPath(group.slug, 'leaderboards'), }, { title: 'About', @@ -282,22 +247,24 @@ export default function GroupPage(props: { href: groupPath(group.slug, 'about'), }, ] - const tabIndex = tabs.map((t) => t.title).indexOf(page ?? 'chat') + const tabIndex = tabs.map((t) => t.title).indexOf(page ?? GROUP_CHAT_SLUG) + return ( - + -
{group.name}
@@ -305,19 +272,15 @@ export default function GroupPage(props: {
-
- +
-
- -
- - - + ) : group.anyoneCanJoin ? ( @@ -362,8 +311,9 @@ function GroupOverview(props: { creator: User user: User | null | undefined isCreator: boolean + members: User[] }) { - const { group, creator, isCreator, user } = props + const { group, creator, isCreator, user, members } = props const anyoneCanJoinChoices: { [key: string]: string } = { Closed: 'false', Open: 'true', @@ -379,6 +329,11 @@ function GroupOverview(props: { }) } + const postFix = user ? '?referrer=' + user.username : '' + const shareUrl = `https://${ENV_CONFIG.domain}${groupPath( + group.slug + )}${postFix}` + return ( <> @@ -423,22 +378,27 @@ function GroupOverview(props: { )} + {anyoneCanJoin && user && ( - - Share - - - Invite a friend and get M${REFERRAL_AMOUNT} if they sign up! - - - + +
Invite
+
+ Invite a friend to this group and get M${REFERRAL_AMOUNT} if they + sign up! +
+ + + )} + - +
Members
+ @@ -461,14 +421,20 @@ function SearchBar(props: { setQuery: (query: string) => void }) { ) } -function GroupMemberSearch(props: { group: Group }) { +function GroupMemberSearch(props: { members: User[]; group: Group }) { const [query, setQuery] = useState('') - const members = useMembers(props.group) + const { group } = props + let { members } = props + + // Use static members on load, but also listen to member changes: + const listenToMembers = useMembers(group) + if (listenToMembers) { + members = listenToMembers + } // TODO use find-active-contracts to sort by? - const matches = sortBy(members, [(member) => member.name]).filter( - (m) => - checkAgainstQuery(query, m.name) || checkAgainstQuery(query, m.username) + const matches = sortBy(members, [(member) => member.name]).filter((m) => + searchInAny(query, m.name, m.username) ) const matchLimit = 25 @@ -489,27 +455,6 @@ function GroupMemberSearch(props: { group: Group }) { ) } -export function GroupMembersList(props: { group: Group }) { - const { group } = props - const members = useMembers(group).filter((m) => m.id !== group.creatorId) - const maxMembersToShow = 3 - if (group.memberIds.length === 1) return
- return ( -
- Other members - {members.slice(0, maxMembersToShow).map((member, i) => ( -
- - {members.length > 1 && i !== members.length - 1 && ,} -
- ))} - {members.length > maxMembersToShow && ( - & {members.length - maxMembersToShow} more - )} -
- ) -} - function SortedLeaderboard(props: { users: User[] scoreFunction: (user: User) => number @@ -553,14 +498,14 @@ function GroupLeaderboards(props: { traderScores[user.id] ?? 0} - title="🏅 Bettor rankings" + title="🏅 Top bettors" header="Profit" maxToShow={maxToShow} /> creatorScores[user.id] ?? 0} - title="🏅 Creator rankings" + title="🏅 Top creators" header="Market volume" maxToShow={maxToShow} /> @@ -600,7 +545,7 @@ function GroupLeaderboards(props: { } function AddContractButton(props: { group: Group; user: User }) { - const { group } = props + const { group, user } = props const [open, setOpen] = useState(false) async function addContractToCurrentGroup(contract: Contract) { @@ -610,16 +555,39 @@ function AddContractButton(props: { group: Group; user: User }) { return ( <> +
+ +
+ -
- Add a question to your group -
-
+ +
+ Add a question to your group +
+ + + + +
or
+ + + +
-
- - -
) } @@ -660,19 +608,19 @@ function JoinGroupButton(props: { user: User | null | undefined }) { const { group, user } = props - function joinGroup() { + function addUserToGroup() { if (user && !group.memberIds.includes(user.id)) { - toast.promise(addUserToGroup(group, user.id), { + toast.promise(joinGroup(group, user.id), { loading: 'Joining group...', success: 'Joined group!', - error: "Couldn't join group", + error: "Couldn't join group, try again?", }) } } return (