aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSebastien Castiel <sebastien@castiel.me>2024-01-08 15:49:07 -0500
committerSebastien Castiel <sebastien@castiel.me>2024-01-08 15:49:07 -0500
commit55883ce414f5358a75780fbffb755f5464e211d4 (patch)
treedadfe2abf1029194e7c5703542b64f9b3d1e4dbd /src
parentbec1dd270a1d3184ea13f0b849f192176354fc00 (diff)
Mark a group as favorite (fixes #29)
Diffstat (limited to 'src')
-rw-r--r--src/app/groups/recent-group-list.tsx140
-rw-r--r--src/app/groups/recent-groups-helpers.ts28
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)),
+ )
+}