diff options
Diffstat (limited to 'src/app/groups/recent-group-list.tsx')
| -rw-r--r-- | src/app/groups/recent-group-list.tsx | 263 |
1 files changed, 97 insertions, 166 deletions
diff --git a/src/app/groups/recent-group-list.tsx b/src/app/groups/recent-group-list.tsx index 92a8067..5f41fdb 100644 --- a/src/app/groups/recent-group-list.tsx +++ b/src/app/groups/recent-group-list.tsx @@ -1,73 +1,80 @@ 'use client' import { getGroupsAction } from '@/app/groups/actions' import { - deleteRecentGroup, + RecentGroups, + getArchivedGroups, getRecentGroups, getStarredGroups, - saveRecentGroup, - starGroup, - unstarGroup, } from '@/app/groups/recent-groups-helpers' import { Button } from '@/components/ui/button' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' -import { Skeleton } from '@/components/ui/skeleton' -import { ToastAction } from '@/components/ui/toast' -import { useToast } from '@/components/ui/use-toast' import { getGroups } from '@/lib/api' -import { StarFilledIcon } from '@radix-ui/react-icons' -import { Calendar, Loader2, MoreHorizontal, Star, Users } from 'lucide-react' +import { Loader2 } from 'lucide-react' import Link from 'next/link' -import { useRouter } from 'next/navigation' -import { useEffect, useState } from 'react' -import { z } from 'zod' - -const recentGroupsSchema = z.array( - z.object({ - id: z.string().min(1), - name: z.string(), - }), -) -type RecentGroups = z.infer<typeof recentGroupsSchema> +import { SetStateAction, useEffect, useState } from 'react' +import { RecentGroupListCard } from './recent-group-list-card' -type State = +export type RecentGroupsState = | { status: 'pending' } - | { status: 'partial'; groups: RecentGroups; starredGroups: string[] } + | { + status: 'partial' + groups: RecentGroups + starredGroups: string[] + archivedGroups: string[] + } | { status: 'complete' groups: RecentGroups groupsDetails: Awaited<ReturnType<typeof getGroups>> starredGroups: string[] + archivedGroups: string[] } -type Props = { - getGroupsAction: (groupIds: string[]) => ReturnType<typeof getGroups> +function sortGroups( + state: RecentGroupsState & { status: 'complete' | 'partial' }, +) { + const starredGroupInfo = [] + const groupInfo = [] + const archivedGroupInfo = [] + for (const group of state.groups) { + if (state.starredGroups.includes(group.id)) { + starredGroupInfo.push(group) + } else if (state.archivedGroups.includes(group.id)) { + archivedGroupInfo.push(group) + } else { + groupInfo.push(group) + } + } + return { + starredGroupInfo, + groupInfo, + archivedGroupInfo, + } } export function RecentGroupList() { - const [state, setState] = useState<State>({ status: 'pending' }) + const [state, setState] = useState<RecentGroupsState>({ status: 'pending' }) useEffect(() => { const groupsInStorage = getRecentGroups() const starredGroups = getStarredGroups() - setState({ status: 'partial', groups: groupsInStorage, starredGroups }) + const archivedGroups = getArchivedGroups() + setState({ + status: 'partial', + groups: groupsInStorage, + starredGroups, + archivedGroups, + }) getGroupsAction(groupsInStorage.map((g) => g.id)).then((groupsDetails) => { setState({ status: 'complete', groups: groupsInStorage, groupsDetails, starredGroups, + archivedGroups, }) }) }, []) - const router = useRouter() - const toast = useToast() - if (state.status === 'pending') { return ( <p> @@ -91,139 +98,63 @@ export function RecentGroupList() { ) } + const { starredGroupInfo, groupInfo, archivedGroupInfo } = sortGroups(state) + + return ( + <> + {starredGroupInfo.length > 0 && ( + <> + <h2 className="mb-2">Starred groups</h2> + <GroupList + groups={starredGroupInfo} + state={state} + setState={setState} + /> + </> + )} + + {groupInfo.length > 0 && ( + <> + <h2 className="mt-6 mb-2">Recent groups</h2> + <GroupList groups={groupInfo} state={state} setState={setState} /> + </> + )} + + {archivedGroupInfo.length > 0 && ( + <> + <h2 className="mt-6 mb-2 opacity-50">Archived groups</h2> + <div className="opacity-50"> + <GroupList + groups={archivedGroupInfo} + state={state} + setState={setState} + /> + </div> + </> + )} + </> + ) +} + +function GroupList({ + groups, + state, + setState, +}: { + groups: RecentGroups + state: RecentGroupsState + setState: (state: SetStateAction<RecentGroupsState>) => void +}) { return ( <ul className="grid grid-cols-1 gap-2 sm:grid-cols-3"> - {state.groups - .toSorted( - (first, second) => - (state.starredGroups.includes(second.id) ? 2 : 0) - - (state.starredGroups.includes(first.id) ? 1 : 0), - ) - .map((group) => { - const details = - state.status === 'complete' - ? state.groupsDetails.find((d) => d.id === group.id) - : null - return ( - <li key={group.id}> - <Button variant="outline" className="h-fit w-full py-3" asChild> - <div - className="text-base" - onClick={() => router.push(`/groups/${group.id}`)} - > - <div className="w-full flex flex-col gap-1"> - <div className="text-base flex gap-2 justify-between"> - <Link - href={`/groups/${group.id}`} - className="flex-1 overflow-hidden text-ellipsis" - > - {group.name} - </Link> - <span className="flex-shrink-0"> - <Button - size="icon" - variant="ghost" - className="-my-3 -ml-3 -mr-1.5" - onClick={(event) => { - event.stopPropagation() - if (state.starredGroups.includes(group.id)) { - unstarGroup(group.id) - } else { - starGroup(group.id) - } - setState({ - ...state, - starredGroups: getStarredGroups(), - }) - }} - > - {state.starredGroups.includes(group.id) ? ( - <StarFilledIcon className="w-4 h-4 text-orange-400" /> - ) : ( - <Star className="w-4 h-4 text-muted-foreground" /> - )} - </Button> - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - size="icon" - variant="ghost" - className="-my-3 -mr-3 -ml-1.5" - > - <MoreHorizontal className="w-4 h-4" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end"> - <DropdownMenuItem - className="text-destructive" - onClick={(event) => { - event.stopPropagation() - deleteRecentGroup(group) - setState({ - ...state, - groups: state.groups.filter( - (g) => g.id !== group.id, - ), - }) - toast.toast({ - title: 'Group has been removed', - description: - 'The group was removed from your recent groups list.', - action: ( - <ToastAction - altText="Undo group removal" - onClick={() => { - saveRecentGroup(group) - setState({ - ...state, - groups: state.groups, - }) - }} - > - Undo - </ToastAction> - ), - }) - }} - > - Remove from recent groups - </DropdownMenuItem> - </DropdownMenuContent> - </DropdownMenu> - </span> - </div> - <div className="text-muted-foreground font-normal text-xs"> - {details ? ( - <div className="w-full flex items-center justify-between"> - <div className="flex items-center"> - <Users className="w-3 h-3 inline mr-1" /> - <span>{details._count.participants}</span> - </div> - <div className="flex items-center"> - <Calendar className="w-3 h-3 inline mx-1" /> - <span> - {new Date(details.createdAt).toLocaleDateString( - 'en-US', - { - dateStyle: 'medium', - }, - )} - </span> - </div> - </div> - ) : ( - <div className="flex justify-between"> - <Skeleton className="h-4 w-6 rounded-full" /> - <Skeleton className="h-4 w-24 rounded-full" /> - </div> - )} - </div> - </div> - </div> - </Button> - </li> - ) - })} + {groups.map((group) => ( + <RecentGroupListCard + key={group.id} + group={group} + state={state} + setState={setState} + /> + ))} </ul> ) } |
