Improve Customize UI
This commit is contained in:
parent
438c12da57
commit
1fbadf8181
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user