diff options
| -rw-r--r-- | src/app/groups/recent-group-list.tsx | 140 | ||||
| -rw-r--r-- | src/app/groups/recent-groups-helpers.ts | 28 |
2 files changed, 124 insertions, 44 deletions
diff --git a/src/app/groups/recent-group-list.tsx b/src/app/groups/recent-group-list.tsx index b8f8ff6..0491650 100644 --- a/src/app/groups/recent-group-list.tsx +++ b/src/app/groups/recent-group-list.tsx @@ -1,11 +1,18 @@ 'use client' import { getGroupsAction } from '@/app/groups/actions' -import { getRecentGroups } from '@/app/groups/recent-groups-helpers' +import { + getRecentGroups, + getStarredGroups, + starGroup, + unstarGroup, +} from '@/app/groups/recent-groups-helpers' import { Button } from '@/components/ui/button' import { Skeleton } from '@/components/ui/skeleton' import { getGroups } from '@/lib/api' -import { Calendar, Loader2, Users } from 'lucide-react' +import { StarFilledIcon } from '@radix-ui/react-icons' +import { Calendar, Loader2, Star, Users } from 'lucide-react' import Link from 'next/link' +import { useRouter } from 'next/navigation' import { useEffect, useState } from 'react' import { z } from 'zod' @@ -19,11 +26,12 @@ type RecentGroups = z.infer<typeof recentGroupsSchema> type State = | { status: 'pending' } - | { status: 'partial'; groups: RecentGroups } + | { status: 'partial'; groups: RecentGroups; starredGroups: string[] } | { status: 'complete' groups: RecentGroups groupsDetails: Awaited<ReturnType<typeof getGroups>> + starredGroups: string[] } type Props = { @@ -35,12 +43,20 @@ export function RecentGroupList() { useEffect(() => { const groupsInStorage = getRecentGroups() - setState({ status: 'partial', groups: groupsInStorage }) + const starredGroups = getStarredGroups() + setState({ status: 'partial', groups: groupsInStorage, starredGroups }) getGroupsAction(groupsInStorage.map((g) => g.id)).then((groupsDetails) => { - setState({ status: 'complete', groups: groupsInStorage, groupsDetails }) + setState({ + status: 'complete', + groups: groupsInStorage, + groupsDetails, + starredGroups, + }) }) }, []) + const router = useRouter() + if (state.status === 'pending') { return ( <p> @@ -66,49 +82,85 @@ export function RecentGroupList() { return ( <ul className="grid grid-cols-1 gap-2 sm:grid-cols-3"> - {state.groups.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> - <Link href={`/groups/${group.id}`} className="text-base"> - <div className="w-full flex flex-col gap-1"> - <div className="text-base">{group.name}</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> + {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 justify-between"> + <Link href={`/groups/${group.id}`}>{group.name}</Link> + <span> + <Button + size="icon" + variant="ghost" + className="-m-3" + 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> + </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 items-center"> - <Calendar className="w-3 h-3 inline mx-1" /> - <span> - {new Date(details.createdAt).toLocaleDateString( - 'en-US', - { - dateStyle: 'medium', - }, - )} - </span> + ) : ( + <div className="flex justify-between"> + <Skeleton className="h-4 w-6 rounded-full" /> + <Skeleton className="h-4 w-24 rounded-full" /> </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> - </Link> - </Button> - </li> - ) - })} + </Button> + </li> + ) + })} </ul> ) } diff --git a/src/app/groups/recent-groups-helpers.ts b/src/app/groups/recent-groups-helpers.ts index 5d52141..5ccb5ae 100644 --- a/src/app/groups/recent-groups-helpers.ts +++ b/src/app/groups/recent-groups-helpers.ts @@ -7,10 +7,13 @@ export const recentGroupsSchema = z.array( }), ) +export const starredGroupsSchema = z.array(z.string()) + export type RecentGroups = z.infer<typeof recentGroupsSchema> export type RecentGroup = RecentGroups[number] const STORAGE_KEY = 'recentGroups' +const STARRED_GROUPS_STORAGE_KEY = 'starredGroups' export function getRecentGroups() { const groupsInStorageJson = localStorage.getItem(STORAGE_KEY) @@ -28,3 +31,28 @@ export function saveRecentGroup(group: RecentGroup) { JSON.stringify([group, ...recentGroups.filter((rg) => rg.id !== group.id)]), ) } + +export function getStarredGroups() { + const starredGroupsJson = localStorage.getItem(STARRED_GROUPS_STORAGE_KEY) + const starredGroupsRaw = starredGroupsJson + ? JSON.parse(starredGroupsJson) + : [] + const parseResult = starredGroupsSchema.safeParse(starredGroupsRaw) + return parseResult.success ? parseResult.data : [] +} + +export function starGroup(groupId: string) { + const starredGroups = getStarredGroups() + localStorage.setItem( + STARRED_GROUPS_STORAGE_KEY, + JSON.stringify([...starredGroups, groupId]), + ) +} + +export function unstarGroup(groupId: string) { + const starredGroups = getStarredGroups() + localStorage.setItem( + STARRED_GROUPS_STORAGE_KEY, + JSON.stringify(starredGroups.filter((g) => g !== groupId)), + ) +} |
