Mark a group as favorite (fixes #29)

This commit is contained in:
Sebastien Castiel
2024-01-08 15:49:07 -05:00
parent bec1dd270a
commit 55883ce414
2 changed files with 124 additions and 44 deletions

View File

@@ -1,11 +1,18 @@
'use client' 'use client'
import { getGroupsAction } from '@/app/groups/actions' 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 { Button } from '@/components/ui/button'
import { Skeleton } from '@/components/ui/skeleton' import { Skeleton } from '@/components/ui/skeleton'
import { getGroups } from '@/lib/api' 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 Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { z } from 'zod' import { z } from 'zod'
@@ -19,11 +26,12 @@ type RecentGroups = z.infer<typeof recentGroupsSchema>
type State = type State =
| { status: 'pending' } | { status: 'pending' }
| { status: 'partial'; groups: RecentGroups } | { status: 'partial'; groups: RecentGroups; starredGroups: string[] }
| { | {
status: 'complete' status: 'complete'
groups: RecentGroups groups: RecentGroups
groupsDetails: Awaited<ReturnType<typeof getGroups>> groupsDetails: Awaited<ReturnType<typeof getGroups>>
starredGroups: string[]
} }
type Props = { type Props = {
@@ -35,12 +43,20 @@ export function RecentGroupList() {
useEffect(() => { useEffect(() => {
const groupsInStorage = getRecentGroups() 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) => { 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') { if (state.status === 'pending') {
return ( return (
<p> <p>
@@ -66,49 +82,85 @@ export function RecentGroupList() {
return ( return (
<ul className="grid grid-cols-1 gap-2 sm:grid-cols-3"> <ul className="grid grid-cols-1 gap-2 sm:grid-cols-3">
{state.groups.map((group) => { {state.groups
const details = .toSorted(
state.status === 'complete' (first, second) =>
? state.groupsDetails.find((d) => d.id === group.id) (state.starredGroups.includes(second.id) ? 2 : 0) -
: null (state.starredGroups.includes(first.id) ? 1 : 0),
return ( )
<li key={group.id}> .map((group) => {
<Button variant="outline" className="h-fit w-full py-3" asChild> const details =
<Link href={`/groups/${group.id}`} className="text-base"> state.status === 'complete'
<div className="w-full flex flex-col gap-1"> ? state.groupsDetails.find((d) => d.id === group.id)
<div className="text-base">{group.name}</div> : null
<div className="text-muted-foreground font-normal text-xs"> return (
{details ? ( <li key={group.id}>
<div className="w-full flex items-center justify-between"> <Button variant="outline" className="h-fit w-full py-3" asChild>
<div className="flex items-center"> <div
<Users className="w-3 h-3 inline mr-1" /> className="text-base"
<span>{details._count.participants}</span> 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>
<div className="flex items-center"> ) : (
<Calendar className="w-3 h-3 inline mx-1" /> <div className="flex justify-between">
<span> <Skeleton className="h-4 w-6 rounded-full" />
{new Date(details.createdAt).toLocaleDateString( <Skeleton className="h-4 w-24 rounded-full" />
'en-US',
{
dateStyle: 'medium',
},
)}
</span>
</div> </div>
</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> </div>
</Link> </Button>
</Button> </li>
</li> )
) })}
})}
</ul> </ul>
) )
} }

View File

@@ -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 RecentGroups = z.infer<typeof recentGroupsSchema>
export type RecentGroup = RecentGroups[number] export type RecentGroup = RecentGroups[number]
const STORAGE_KEY = 'recentGroups' const STORAGE_KEY = 'recentGroups'
const STARRED_GROUPS_STORAGE_KEY = 'starredGroups'
export function getRecentGroups() { export function getRecentGroups() {
const groupsInStorageJson = localStorage.getItem(STORAGE_KEY) 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)]), 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)),
)
}