mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-04 12:06:11 +01:00
Mark a group as favorite (fixes #29)
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user