Show groups on user page, allow to join/leave (#594)
* Show groups on user page, allow to join/leave * Link to groups * Unused var
This commit is contained in:
parent
63528aa0f3
commit
8c3c30c707
|
@ -22,7 +22,7 @@ export function GroupSelector(props: {
|
||||||
const [isCreatingNewGroup, setIsCreatingNewGroup] = useState(false)
|
const [isCreatingNewGroup, setIsCreatingNewGroup] = useState(false)
|
||||||
|
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const memberGroups = useMemberGroups(creator)
|
const memberGroups = useMemberGroups(creator?.id)
|
||||||
const filteredGroups = memberGroups
|
const filteredGroups = memberGroups
|
||||||
? query === ''
|
? query === ''
|
||||||
? memberGroups
|
? memberGroups
|
||||||
|
|
144
web/components/groups/groups-button.tsx
Normal file
144
web/components/groups/groups-button.tsx
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { User } from 'common/user'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useUser } from 'web/hooks/use-user'
|
||||||
|
import { withTracking } from 'web/lib/service/analytics'
|
||||||
|
import { Row } from 'web/components/layout/row'
|
||||||
|
import { useMemberGroups } from 'web/hooks/use-group'
|
||||||
|
import { TextButton } from 'web/components/text-button'
|
||||||
|
import { Group } from 'common/group'
|
||||||
|
import { Modal } from 'web/components/layout/modal'
|
||||||
|
import { Col } from 'web/components/layout/col'
|
||||||
|
import { joinGroup, leaveGroup } from 'web/lib/firebase/groups'
|
||||||
|
import { firebaseLogin } from 'web/lib/firebase/users'
|
||||||
|
import { GroupLink } from 'web/pages/groups'
|
||||||
|
|
||||||
|
export function GroupsButton(props: { user: User }) {
|
||||||
|
const { user } = props
|
||||||
|
const [isOpen, setIsOpen] = useState(false)
|
||||||
|
const groups = useMemberGroups(user.id)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TextButton onClick={() => setIsOpen(true)}>
|
||||||
|
<span className="font-semibold">{groups?.length ?? ''}</span> Groups
|
||||||
|
</TextButton>
|
||||||
|
|
||||||
|
<GroupsDialog
|
||||||
|
user={user}
|
||||||
|
groups={groups ?? []}
|
||||||
|
isOpen={isOpen}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupsDialog(props: {
|
||||||
|
user: User
|
||||||
|
groups: Group[]
|
||||||
|
isOpen: boolean
|
||||||
|
setIsOpen: (isOpen: boolean) => void
|
||||||
|
}) {
|
||||||
|
const { user, groups, isOpen, setIsOpen } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={isOpen} setOpen={setIsOpen}>
|
||||||
|
<Col className="rounded bg-white p-6">
|
||||||
|
<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>
|
||||||
|
<GroupsList groups={groups} />
|
||||||
|
</Col>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupsList(props: { groups: Group[] }) {
|
||||||
|
const { groups } = props
|
||||||
|
return (
|
||||||
|
<Col className="gap-2">
|
||||||
|
{groups.length === 0 && (
|
||||||
|
<div className="text-gray-500">No groups yet...</div>
|
||||||
|
)}
|
||||||
|
{groups
|
||||||
|
.sort((group1, group2) => group2.createdTime - group1.createdTime)
|
||||||
|
.map((group) => (
|
||||||
|
<GroupItem key={group.id} group={group} />
|
||||||
|
))}
|
||||||
|
</Col>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function GroupItem(props: { group: Group; className?: string }) {
|
||||||
|
const { group, className } = props
|
||||||
|
return (
|
||||||
|
<Row className={clsx('items-center justify-between gap-2 p-2', className)}>
|
||||||
|
<Row className="line-clamp-1 items-center gap-2">
|
||||||
|
<GroupLink group={group} />
|
||||||
|
</Row>
|
||||||
|
<JoinOrLeaveGroupButton group={group} />
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function JoinOrLeaveGroupButton(props: {
|
||||||
|
group: Group
|
||||||
|
small?: boolean
|
||||||
|
className?: string
|
||||||
|
}) {
|
||||||
|
const { group, small, className } = props
|
||||||
|
const currentUser = useUser()
|
||||||
|
const isFollowing = currentUser
|
||||||
|
? group.memberIds.includes(currentUser.id)
|
||||||
|
: false
|
||||||
|
const onJoinGroup = () => {
|
||||||
|
if (!currentUser) return
|
||||||
|
joinGroup(group, currentUser.id)
|
||||||
|
}
|
||||||
|
const onLeaveGroup = () => {
|
||||||
|
if (!currentUser) return
|
||||||
|
leaveGroup(group, currentUser.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
const smallStyle =
|
||||||
|
'btn !btn-xs border-2 border-gray-500 bg-white normal-case text-gray-500 hover:border-gray-500 hover:bg-white hover:text-gray-500'
|
||||||
|
|
||||||
|
if (!currentUser || isFollowing === undefined) {
|
||||||
|
if (!group.anyoneCanJoin)
|
||||||
|
return <div className={clsx(className, 'text-gray-500')}>Closed</div>
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={firebaseLogin}
|
||||||
|
className={clsx('btn btn-sm', small && smallStyle, className)}
|
||||||
|
>
|
||||||
|
Login to Join
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFollowing) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx(
|
||||||
|
'btn btn-outline btn-sm',
|
||||||
|
small && smallStyle,
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
onClick={withTracking(onLeaveGroup, 'leave group')}
|
||||||
|
>
|
||||||
|
Leave
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group.anyoneCanJoin)
|
||||||
|
return <div className={clsx(className, 'text-gray-500')}>Closed</div>
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className={clsx('btn btn-sm', small && smallStyle, className)}
|
||||||
|
onClick={withTracking(onJoinGroup, 'join group')}
|
||||||
|
>
|
||||||
|
Join
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
|
@ -185,7 +185,7 @@ export default function Sidebar(props: { className?: string }) {
|
||||||
const mobileNavigationOptions = !user
|
const mobileNavigationOptions = !user
|
||||||
? signedOutMobileNavigation
|
? signedOutMobileNavigation
|
||||||
: signedInMobileNavigation
|
: signedInMobileNavigation
|
||||||
const memberItems = (useMemberGroups(user) ?? []).map((group: Group) => ({
|
const memberItems = (useMemberGroups(user?.id) ?? []).map((group: Group) => ({
|
||||||
name: group.name,
|
name: group.name,
|
||||||
href: groupPath(group.slug),
|
href: groupPath(group.slug),
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -36,6 +36,7 @@ import { FollowersButton, FollowingButton } from './following-button'
|
||||||
import { useFollows } from 'web/hooks/use-follows'
|
import { useFollows } from 'web/hooks/use-follows'
|
||||||
import { FollowButton } from './follow-button'
|
import { FollowButton } from './follow-button'
|
||||||
import { PortfolioMetrics } from 'common/user'
|
import { PortfolioMetrics } from 'common/user'
|
||||||
|
import { GroupsButton } from 'web/components/groups/groups-button'
|
||||||
|
|
||||||
export function UserLink(props: {
|
export function UserLink(props: {
|
||||||
name: string
|
name: string
|
||||||
|
@ -197,6 +198,7 @@ export function UserPage(props: {
|
||||||
<Row className="gap-4">
|
<Row className="gap-4">
|
||||||
<FollowingButton user={user} />
|
<FollowingButton user={user} />
|
||||||
<FollowersButton user={user} />
|
<FollowersButton user={user} />
|
||||||
|
<GroupsButton user={user} />
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{user.website && (
|
{user.website && (
|
||||||
|
|
|
@ -29,11 +29,11 @@ export const useGroups = () => {
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useMemberGroups = (user: User | null | undefined) => {
|
export const useMemberGroups = (userId: string | null | undefined) => {
|
||||||
const [memberGroups, setMemberGroups] = useState<Group[] | undefined>()
|
const [memberGroups, setMemberGroups] = useState<Group[] | undefined>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user) return listenForMemberGroups(user.id, setMemberGroups)
|
if (userId) return listenForMemberGroups(userId, setMemberGroups)
|
||||||
}, [user])
|
}, [userId])
|
||||||
return memberGroups
|
return memberGroups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,3 +94,24 @@ export async function getGroupsWithContractId(
|
||||||
const groups = await getValues<Group>(q)
|
const groups = await getValues<Group>(q)
|
||||||
setGroups(groups)
|
setGroups(groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function joinGroup(group: Group, userId: string): Promise<Group> {
|
||||||
|
const { memberIds } = group
|
||||||
|
if (memberIds.includes(userId)) {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
const newMemberIds = [...memberIds, userId]
|
||||||
|
const newGroup = { ...group, memberIds: newMemberIds }
|
||||||
|
await updateGroup(newGroup, { memberIds: newMemberIds })
|
||||||
|
return newGroup
|
||||||
|
}
|
||||||
|
export async function leaveGroup(group: Group, userId: string): Promise<Group> {
|
||||||
|
const { memberIds } = group
|
||||||
|
if (!memberIds.includes(userId)) {
|
||||||
|
return group
|
||||||
|
}
|
||||||
|
const newMemberIds = memberIds.filter((id) => id !== userId)
|
||||||
|
const newGroup = { ...group, memberIds: newMemberIds }
|
||||||
|
await updateGroup(newGroup, { memberIds: newMemberIds })
|
||||||
|
return newGroup
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,8 @@ import { getUser, User } from 'web/lib/firebase/users'
|
||||||
import { Tabs } from 'web/components/layout/tabs'
|
import { Tabs } from 'web/components/layout/tabs'
|
||||||
import { GroupMembersList } from 'web/pages/group/[...slugs]'
|
import { GroupMembersList } from 'web/pages/group/[...slugs]'
|
||||||
import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params'
|
import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params'
|
||||||
|
import { SiteLink } from 'web/components/site-link'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
|
||||||
export async function getStaticProps() {
|
export async function getStaticProps() {
|
||||||
const groups = await listAllGroups().catch((_) => [])
|
const groups = await listAllGroups().catch((_) => [])
|
||||||
|
@ -202,3 +204,16 @@ export function GroupCard(props: { group: Group; creator: User | undefined }) {
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GroupLink(props: { group: Group; className?: string }) {
|
||||||
|
const { group, className } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SiteLink
|
||||||
|
href={groupPath(group.slug)}
|
||||||
|
className={clsx('z-10 truncate', className)}
|
||||||
|
>
|
||||||
|
{group.name}
|
||||||
|
</SiteLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user