Improve Customize UI

This commit is contained in:
James Grugett 2022-09-17 18:30:27 -05:00
parent 438c12da57
commit 1fbadf8181
4 changed files with 105 additions and 46 deletions

View File

@ -1,14 +1,22 @@
import clsx from 'clsx' import clsx from 'clsx'
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd' import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'
import { MenuIcon } from '@heroicons/react/solid' import { MenuIcon } from '@heroicons/react/solid'
import { toast } from 'react-hot-toast'
import { Col } from 'web/components/layout/col' import { Col } from 'web/components/layout/col'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { Subtitle } from 'web/components/subtitle' import { Subtitle } from 'web/components/subtitle'
import { keyBy } from 'lodash' import { keyBy } from 'lodash'
import { XCircleIcon } from '@heroicons/react/outline'
import { Button } from './button'
import { updateUser } from 'web/lib/firebase/users'
import { leaveGroup } from 'web/lib/firebase/groups'
import { User } from 'common/user'
import { useUser } from 'web/hooks/use-user'
import { Group } from 'common/group'
export function ArrangeHome(props: { export function ArrangeHome(props: {
sections: { label: string; id: string }[] sections: { label: string; id: string; group?: Group }[]
setSectionIds: (sections: string[]) => void setSectionIds: (sections: string[]) => void
}) { }) {
const { sections, setSectionIds } = props const { sections, setSectionIds } = props
@ -40,8 +48,9 @@ export function ArrangeHome(props: {
function DraggableList(props: { function DraggableList(props: {
title: string title: string
items: { id: string; label: string }[] items: { id: string; label: string; group?: Group }[]
}) { }) {
const user = useUser()
const { title, items } = props const { title, items } = props
return ( return (
<Droppable droppableId={title.toLowerCase()}> <Droppable droppableId={title.toLowerCase()}>
@ -66,6 +75,7 @@ function DraggableList(props: {
snapshot.isDragging && 'z-[9000] bg-gray-200' snapshot.isDragging && 'z-[9000] bg-gray-200'
)} )}
item={item} item={item}
user={user}
/> />
</div> </div>
)} )}
@ -79,23 +89,52 @@ function DraggableList(props: {
} }
const SectionItem = (props: { const SectionItem = (props: {
item: { id: string; label: string } item: { id: string; label: string; group?: Group }
user: User | null | undefined
className?: string className?: string
}) => { }) => {
const { item, className } = props const { item, user, className } = props
const { group } = item
return ( return (
<div <Row
className={clsx( className={clsx(
className, className,
'flex flex-row items-center gap-4 rounded bg-gray-50 p-2' 'items-center justify-between gap-4 rounded bg-gray-50 p-2'
)} )}
> >
<Row className="items-center gap-4">
<MenuIcon <MenuIcon
className="h-5 w-5 flex-shrink-0 text-gray-500" className="h-5 w-5 flex-shrink-0 text-gray-500"
aria-hidden="true" aria-hidden="true"
/>{' '} />{' '}
{item.label} {item.label}
</div> </Row>
{group && (
<Button
color="gray-white"
onClick={() => {
if (user) {
const homeSections = (user.homeSections ?? []).filter(
(id) => id !== group.id
)
updateUser(user.id, { homeSections })
toast.promise(leaveGroup(group, user.id), {
loading: 'Unfollowing group...',
success: `Unfollowed ${group.name}`,
error: "Couldn't unfollow group, try again?",
})
}
}}
>
<XCircleIcon
className={clsx('h-5 w-5 flex-shrink-0')}
aria-hidden="true"
/>
</Button>
)}
</Row>
) )
} }

View File

@ -2,6 +2,7 @@ import { useEffect, useState } from 'react'
import { Group } from 'common/group' import { Group } from 'common/group'
import { User } from 'common/user' import { User } from 'common/user'
import { import {
getGroup,
getMemberGroups, getMemberGroups,
GroupMemberDoc, GroupMemberDoc,
groupMembers, groupMembers,
@ -102,6 +103,21 @@ export const useMemberGroupIds = (user: User | null | undefined) => {
return memberGroupIds return memberGroupIds
} }
export function useMemberGroupsSubscription(user: User | null | undefined) {
const cachedGroups = useMemberGroups(user?.id) ?? []
const groupIds = useMemberGroupIds(user)
const [groups, setGroups] = useState(cachedGroups)
useEffect(() => {
if (groupIds) {
Promise.all(groupIds.map((id) => getGroup(id))).then((groups) =>
setGroups(filterDefined(groups))
)
}
}, [groupIds])
return groups
}
export function useMembers(groupId: string | undefined) { export function useMembers(groupId: string | undefined) {
const [members, setMembers] = useState<User[]>([]) const [members, setMembers] = useState<User[]>([])
useEffect(() => { useEffect(() => {

View File

@ -7,12 +7,12 @@ import { Row } from 'web/components/layout/row'
import { Page } from 'web/components/page' import { Page } from 'web/components/page'
import { SiteLink } from 'web/components/site-link' import { SiteLink } from 'web/components/site-link'
import { Title } from 'web/components/title' import { Title } from 'web/components/title'
import { useMemberGroups } from 'web/hooks/use-group' import { useMemberGroupsSubscription } from 'web/hooks/use-group'
import { useTracking } from 'web/hooks/use-tracking' import { useTracking } from 'web/hooks/use-tracking'
import { useUser } from 'web/hooks/use-user' import { useUser } from 'web/hooks/use-user'
import { updateUser } from 'web/lib/firebase/users' import { updateUser } from 'web/lib/firebase/users'
import { track } from 'web/lib/service/analytics' import { track } from 'web/lib/service/analytics'
import { getHomeItems } from '.' import { getHomeItems, TrendingGroupsSection } from '.'
export default function Home() { export default function Home() {
const user = useUser() const user = useUser()
@ -27,7 +27,7 @@ export default function Home() {
setHomeSections(newHomeSections) setHomeSections(newHomeSections)
} }
const groups = useMemberGroups(user?.id) ?? [] const groups = useMemberGroupsSubscription(user)
const { sections } = getHomeItems(groups, homeSections) const { sections } = getHomeItems(groups, homeSections)
return ( return (
@ -38,7 +38,15 @@ export default function Home() {
<DoneButton /> <DoneButton />
</Row> </Row>
<ArrangeHome sections={sections} setSectionIds={updateHomeSections} /> <Col className="gap-8 md:flex-row">
<Col className="flex-1">
<ArrangeHome
sections={sections}
setSectionIds={updateHomeSections}
/>
</Col>
<TrendingGroupsSection className="flex-1" user={user} full />
</Col>
</Col> </Col>
</Page> </Page>
) )

View File

@ -1,4 +1,4 @@
import React, { ReactNode, useEffect, useState } from 'react' import React, { ReactNode } from 'react'
import Router from 'next/router' import Router from 'next/router'
import { import {
AdjustmentsIcon, AdjustmentsIcon,
@ -22,18 +22,13 @@ import { SiteLink } from 'web/components/site-link'
import { usePrivateUser, useUser } from 'web/hooks/use-user' import { usePrivateUser, useUser } from 'web/hooks/use-user'
import { import {
useMemberGroupIds, useMemberGroupIds,
useMemberGroups, useMemberGroupsSubscription,
useTrendingGroups, useTrendingGroups,
} from 'web/hooks/use-group' } from 'web/hooks/use-group'
import { Button } from 'web/components/button' import { Button } from 'web/components/button'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { ProbChangeTable } from 'web/components/contract/prob-change-table' import { ProbChangeTable } from 'web/components/contract/prob-change-table'
import { import { groupPath, joinGroup, leaveGroup } from 'web/lib/firebase/groups'
getGroup,
groupPath,
joinGroup,
leaveGroup,
} from 'web/lib/firebase/groups'
import { usePortfolioHistory } from 'web/hooks/use-portfolio-history' import { usePortfolioHistory } from 'web/hooks/use-portfolio-history'
import { formatMoney } from 'common/util/format' import { formatMoney } from 'common/util/format'
import { useProbChanges } from 'web/hooks/use-prob-changes' import { useProbChanges } from 'web/hooks/use-prob-changes'
@ -57,17 +52,7 @@ export default function Home() {
useSaveReferral() useSaveReferral()
usePrefetch(user?.id) usePrefetch(user?.id)
const cachedGroups = useMemberGroups(user?.id) ?? [] const groups = useMemberGroupsSubscription(user)
const groupIds = useMemberGroupIds(user)
const [groups, setGroups] = useState(cachedGroups)
useEffect(() => {
if (groupIds) {
Promise.all(groupIds.map((id) => getGroup(id))).then((groups) =>
setGroups(filterDefined(groups))
)
}
}, [groupIds])
const { sections } = getHomeItems(groups, user?.homeSections ?? []) const { sections } = getHomeItems(groups, user?.homeSections ?? [])
@ -77,7 +62,10 @@ export default function Home() {
<Col className="pm:mx-10 gap-4 px-4 pb-12 pt-4 sm:pt-0"> <Col className="pm:mx-10 gap-4 px-4 pb-12 pt-4 sm:pt-0">
<Row className={'mb-2 w-full items-center justify-between gap-8'}> <Row className={'mb-2 w-full items-center justify-between gap-8'}>
<Row className="items-center gap-2">
<Title className="!mt-0 !mb-0" text="Home" /> <Title className="!mt-0 !mb-0" text="Home" />
<CustomizeButton justIcon />
</Row>
<DailyStats user={user} /> <DailyStats user={user} />
</Row> </Row>
@ -110,11 +98,12 @@ export const getHomeItems = (groups: Group[], sections: string[]) => {
// Accommodate old home sections. // Accommodate old home sections.
if (!isArray(sections)) sections = [] if (!isArray(sections)) sections = []
const items = [ const items: { id: string; label: string; group?: Group }[] = [
...HOME_SECTIONS, ...HOME_SECTIONS,
...groups.map((g) => ({ ...groups.map((g) => ({
label: g.name, label: g.name,
id: g.id, id: g.id,
group: g,
})), })),
] ]
const itemsById = keyBy(items, 'id') const itemsById = keyBy(items, 'id')
@ -225,7 +214,6 @@ function GroupSection(props: {
<Col> <Col>
<SectionHeader label={group.name} href={groupPath(group.slug)}> <SectionHeader label={group.name} href={groupPath(group.slug)}>
<Button <Button
className=""
color="gray-white" color="gray-white"
onClick={() => { onClick={() => {
if (user) { if (user) {
@ -312,20 +300,24 @@ function DailyStats(props: {
) )
} }
function TrendingGroupsSection(props: { user: User | null | undefined }) { export function TrendingGroupsSection(props: {
const { user } = props user: User | null | undefined
full?: boolean
className?: string
}) {
const { user, full, className } = props
const memberGroupIds = useMemberGroupIds(user) || [] const memberGroupIds = useMemberGroupIds(user) || []
const groups = useTrendingGroups().filter( const groups = useTrendingGroups().filter(
(g) => !memberGroupIds.includes(g.id) (g) => !memberGroupIds.includes(g.id)
) )
const count = 25 const count = full ? 100 : 25
const chosenGroups = groups.slice(0, count) const chosenGroups = groups.slice(0, count)
return ( return (
<Col> <Col className={className}>
<SectionHeader label="Trending groups" href="/explore-groups"> <SectionHeader label="Trending groups" href="/explore-groups">
<CustomizeButton /> {!full && <CustomizeButton className="mb-1" />}
</SectionHeader> </SectionHeader>
<Row className="flex-wrap gap-2"> <Row className="flex-wrap gap-2">
{chosenGroups.map((g) => ( {chosenGroups.map((g) => (
@ -359,10 +351,14 @@ function TrendingGroupsSection(props: { user: User | null | undefined }) {
) )
} }
function CustomizeButton() { function CustomizeButton(props: { justIcon?: boolean; className?: string }) {
const { justIcon, className } = props
return ( return (
<SiteLink <SiteLink
className="mb-2 flex flex-row items-center text-xl hover:no-underline" className={clsx(
className,
'flex flex-row items-center text-xl hover:no-underline'
)}
href="/home/edit" href="/home/edit"
> >
<Button size="lg" color="gray" className={clsx('flex gap-2')}> <Button size="lg" color="gray" className={clsx('flex gap-2')}>
@ -370,7 +366,7 @@ function CustomizeButton() {
className={clsx('h-[24px] w-5 text-gray-500')} className={clsx('h-[24px] w-5 text-gray-500')}
aria-hidden="true" aria-hidden="true"
/> />
Customize {!justIcon && 'Customize'}
</Button> </Button>
</SiteLink> </SiteLink>
) )