Small groups UX changes
This commit is contained in:
parent
2591655269
commit
a23c744c3e
|
@ -3,6 +3,8 @@ import clsx from 'clsx'
|
|||
import { firebaseLogin, User } from 'web/lib/firebase/users'
|
||||
import React from 'react'
|
||||
|
||||
export const createButtonStyle =
|
||||
'border-w-0 mx-auto mt-4 -ml-1 w-full rounded-md bg-gradient-to-r py-2.5 text-base font-semibold text-white shadow-sm lg:-ml-0 h-11'
|
||||
export const CreateQuestionButton = (props: {
|
||||
user: User | null | undefined
|
||||
overrideText?: string
|
||||
|
@ -12,20 +14,20 @@ export const CreateQuestionButton = (props: {
|
|||
const gradient =
|
||||
'from-indigo-500 to-blue-500 hover:from-indigo-700 hover:to-blue-700'
|
||||
|
||||
const buttonStyle =
|
||||
'border-w-0 mx-auto mt-4 -ml-1 w-full rounded-md bg-gradient-to-r py-2.5 text-base font-semibold text-white shadow-sm lg:-ml-0'
|
||||
|
||||
const { user, overrideText, className, query } = props
|
||||
return (
|
||||
<div className={clsx('aligncenter flex justify-center', className)}>
|
||||
<div className={clsx('flex justify-center', className)}>
|
||||
{user ? (
|
||||
<Link href={`/create${query ? query : ''}`} passHref>
|
||||
<button className={clsx(gradient, buttonStyle)}>
|
||||
<button className={clsx(gradient, createButtonStyle)}>
|
||||
{overrideText ? overrideText : 'Create a question'}
|
||||
</button>
|
||||
</Link>
|
||||
) : (
|
||||
<button onClick={firebaseLogin} className={clsx(gradient, buttonStyle)}>
|
||||
<button
|
||||
onClick={firebaseLogin}
|
||||
className={clsx(gradient, createButtonStyle)}
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
)}
|
||||
|
|
|
@ -36,6 +36,7 @@ export function GroupChat(props: {
|
|||
const [inputRef, setInputRef] = useState<HTMLTextAreaElement | null>(null)
|
||||
const [groupedMessages, setGroupedMessages] = useState<Comment[]>([])
|
||||
const router = useRouter()
|
||||
const isMember = user && group.memberIds.includes(user?.id)
|
||||
|
||||
useMemo(() => {
|
||||
// Group messages with createdTime within 2 minutes of each other.
|
||||
|
@ -120,7 +121,7 @@ export function GroupChat(props: {
|
|||
))}
|
||||
{messages.length === 0 && (
|
||||
<div className="p-2 text-gray-500">
|
||||
No messages yet. Why not{' '}
|
||||
No messages yet. Why not{isMember ? ` ` : ' join and '}
|
||||
<button
|
||||
className={'cursor-pointer font-bold text-gray-700'}
|
||||
onClick={() => focusInput()}
|
||||
|
|
|
@ -22,7 +22,7 @@ export const groups = coll<Group>('groups')
|
|||
|
||||
export function groupPath(
|
||||
groupSlug: string,
|
||||
subpath?: 'edit' | 'questions' | 'about' | 'chat'
|
||||
subpath?: 'edit' | 'questions' | 'about' | 'chat' | 'rankings'
|
||||
) {
|
||||
return `/group/${groupSlug}${subpath ? `/${subpath}` : ''}`
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import {
|
|||
User,
|
||||
writeReferralInfo,
|
||||
} from 'web/lib/firebase/users'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { listMembers, useGroup, useMembers } from 'web/hooks/use-group'
|
||||
|
@ -36,7 +35,10 @@ 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 { CreateQuestionButton } from 'web/components/create-question-button'
|
||||
import {
|
||||
createButtonStyle,
|
||||
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'
|
||||
|
@ -45,11 +47,13 @@ import { checkAgainstQuery } 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 ShortToggle from 'web/components/widgets/short-toggle'
|
||||
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'
|
||||
|
||||
export const getStaticProps = fromPropz(getStaticPropz)
|
||||
export async function getStaticPropz(props: { params: { slugs: string[] } }) {
|
||||
|
@ -104,7 +108,13 @@ async function toTopUsers(userScores: { [userId: string]: number }) {
|
|||
export async function getStaticPaths() {
|
||||
return { paths: [], fallback: 'blocking' }
|
||||
}
|
||||
const groupSubpages = [undefined, 'chat', 'questions', 'about'] as const
|
||||
const groupSubpages = [
|
||||
undefined,
|
||||
'chat',
|
||||
'questions',
|
||||
'rankings',
|
||||
'about',
|
||||
] as const
|
||||
|
||||
export default function GroupPage(props: {
|
||||
group: Group | null
|
||||
|
@ -178,20 +188,18 @@ export default function GroupPage(props: {
|
|||
const rightSidebar = (
|
||||
<Col className="mt-6 hidden xl:block">
|
||||
<JoinOrCreateButton group={group} user={user} isMember={!!isMember} />
|
||||
<Spacer h={6} />
|
||||
{contracts && (
|
||||
<div className={'mt-2'}>
|
||||
<div className={'my-2 text-gray-500'}>Recent Questions</div>
|
||||
<ContractsGrid
|
||||
contracts={contracts
|
||||
.sort((a, b) => b.createdTime - a.createdTime)
|
||||
.slice(0, 3)}
|
||||
hasMore={false}
|
||||
loadMore={() => {}}
|
||||
overrideGridClassName={'grid w-full grid-cols-1 gap-4'}
|
||||
</Col>
|
||||
)
|
||||
const leaderboard = (
|
||||
<Col>
|
||||
<GroupLeaderboards
|
||||
traderScores={traderScores}
|
||||
creatorScores={creatorScores}
|
||||
topTraders={topTraders}
|
||||
topCreators={topCreators}
|
||||
members={members}
|
||||
user={user}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Col>
|
||||
)
|
||||
|
||||
|
@ -203,16 +211,6 @@ export default function GroupPage(props: {
|
|||
isCreator={!!isCreator}
|
||||
user={user}
|
||||
/>
|
||||
<Spacer h={8} />
|
||||
|
||||
<GroupLeaderboards
|
||||
traderScores={traderScores}
|
||||
creatorScores={creatorScores}
|
||||
topTraders={topTraders}
|
||||
topCreators={topCreators}
|
||||
members={members}
|
||||
user={user}
|
||||
/>
|
||||
</Col>
|
||||
)
|
||||
return (
|
||||
|
@ -243,7 +241,15 @@ export default function GroupPage(props: {
|
|||
</Col>
|
||||
|
||||
<Tabs
|
||||
defaultIndex={page === 'about' ? 2 : page === 'questions' ? 1 : 0}
|
||||
defaultIndex={
|
||||
page === 'rankings'
|
||||
? 2
|
||||
: page === 'about'
|
||||
? 3
|
||||
: page === 'questions'
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
tabs={[
|
||||
{
|
||||
title: 'Chat',
|
||||
|
@ -287,11 +293,15 @@ export default function GroupPage(props: {
|
|||
) : (
|
||||
<LoadingIndicator />
|
||||
)}
|
||||
{isMember && <AddContractButton group={group} user={user} />}
|
||||
</div>
|
||||
),
|
||||
href: groupPath(group.slug, 'questions'),
|
||||
},
|
||||
{
|
||||
title: 'Leaderboards',
|
||||
content: leaderboard,
|
||||
href: groupPath(group.slug, 'rankings'),
|
||||
},
|
||||
{
|
||||
title: 'About',
|
||||
content: aboutTab,
|
||||
|
@ -309,13 +319,16 @@ function JoinOrCreateButton(props: {
|
|||
isMember: boolean
|
||||
}) {
|
||||
const { group, user, isMember } = props
|
||||
return isMember ? (
|
||||
return user && isMember ? (
|
||||
<Row className={'justify-between sm:flex-col sm:justify-center'}>
|
||||
<CreateQuestionButton
|
||||
user={user}
|
||||
overrideText={'Add a new question'}
|
||||
className={'w-48 flex-shrink-0'}
|
||||
query={`?groupId=${group.id}`}
|
||||
/>
|
||||
<AddContractButton group={group} user={user} />
|
||||
</Row>
|
||||
) : group.anyoneCanJoin ? (
|
||||
<JoinGroupButton group={group} user={user} />
|
||||
) : null
|
||||
|
@ -389,8 +402,48 @@ function GroupOverview(props: {
|
|||
</ShareIconButton>
|
||||
</Row>
|
||||
)}
|
||||
<Col className={'mt-2'}>
|
||||
<GroupMemberSearch group={group} />
|
||||
</Col>
|
||||
</Col>
|
||||
</Col>
|
||||
)
|
||||
}
|
||||
|
||||
function SearchBar(props: { setQuery: (query: string) => void }) {
|
||||
const { setQuery } = props
|
||||
const debouncedQuery = debounce(setQuery, 50)
|
||||
return (
|
||||
<div className={'relative'}>
|
||||
<SearchIcon className={'absolute left-5 top-3.5 h-5 w-5 text-gray-500'} />
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => debouncedQuery(e.target.value)}
|
||||
placeholder="Find a member"
|
||||
className="input input-bordered mb-4 w-full pl-12"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function GroupMemberSearch(props: { group: Group }) {
|
||||
const [query, setQuery] = useState('')
|
||||
const members = useMembers(props.group)
|
||||
|
||||
// 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)
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<SearchBar setQuery={setQuery} />
|
||||
<Col className={'gap-2'}>
|
||||
{matches.length > 0 && (
|
||||
<FollowList userIds={matches.map((m) => m.id)} />
|
||||
)}
|
||||
</Col>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -449,32 +502,24 @@ function GroupLeaderboards(props: {
|
|||
}) {
|
||||
const { traderScores, creatorScores, members, topTraders, topCreators } =
|
||||
props
|
||||
const [includeOutsiders, setIncludeOutsiders] = useState(false)
|
||||
|
||||
// Consider hiding M$0
|
||||
// If it's just one member (curator), show all bettors, otherwise just show members
|
||||
return (
|
||||
<Col>
|
||||
<Row className="items-center justify-end gap-4 text-gray-500">
|
||||
Include all users
|
||||
<ShortToggle
|
||||
enabled={includeOutsiders}
|
||||
setEnabled={setIncludeOutsiders}
|
||||
/>
|
||||
</Row>
|
||||
|
||||
<div className="mt-4 flex flex-col gap-8 px-4 md:flex-row">
|
||||
{!includeOutsiders ? (
|
||||
{members.length > 1 ? (
|
||||
<>
|
||||
<SortedLeaderboard
|
||||
users={members}
|
||||
scoreFunction={(user) => traderScores[user.id] ?? 0}
|
||||
title="🏅 Top bettors"
|
||||
title="🏅 Bettor rankings"
|
||||
header="Profit"
|
||||
/>
|
||||
<SortedLeaderboard
|
||||
users={members}
|
||||
scoreFunction={(user) => creatorScores[user.id] ?? 0}
|
||||
title="🏅 Top creators"
|
||||
title="🏅 Creator rankings"
|
||||
header="Market volume"
|
||||
/>
|
||||
</>
|
||||
|
@ -543,16 +588,17 @@ function AddContractButton(props: { group: Group; user: User }) {
|
|||
</div>
|
||||
</Col>
|
||||
</Modal>
|
||||
<Row className={'items-center justify-center'}>
|
||||
<div className={'flex w-48 justify-center'}>
|
||||
<button
|
||||
className={
|
||||
'btn btn-md btn-outline cursor-pointer gap-2 whitespace-nowrap text-sm normal-case'
|
||||
}
|
||||
className={clsx(
|
||||
createButtonStyle,
|
||||
'w-48 whitespace-nowrap border border-black text-black hover:bg-black hover:text-white'
|
||||
)}
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
Add an old question
|
||||
</button>
|
||||
</Row>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -347,10 +347,10 @@ function IncomeNotificationItem(props: {
|
|||
let reasonText = ''
|
||||
if (sourceType === 'bonus' && sourceText) {
|
||||
reasonText = !simple
|
||||
? `bonus for ${
|
||||
? `Bonus for ${
|
||||
parseInt(sourceText) / UNIQUE_BETTOR_BONUS_AMOUNT
|
||||
} unique bettors`
|
||||
: ' bonus for unique bettors on'
|
||||
: 'bonus on'
|
||||
} else if (sourceType === 'tip') {
|
||||
reasonText = !simple ? `tipped you` : `in tips on`
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user