diff --git a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
index bb32da0..984d30f 100644
--- a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
+++ b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx
@@ -1,5 +1,11 @@
import { ExpenseForm } from '@/components/expense-form'
-import { deleteExpense, getExpense, getCategories, getGroup, updateExpense } from '@/lib/api'
+import {
+ deleteExpense,
+ getCategories,
+ getExpense,
+ getGroup,
+ updateExpense,
+} from '@/lib/api'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
diff --git a/src/app/groups/[groupId]/expenses/create/page.tsx b/src/app/groups/[groupId]/expenses/create/page.tsx
index 405db26..adbdff9 100644
--- a/src/app/groups/[groupId]/expenses/create/page.tsx
+++ b/src/app/groups/[groupId]/expenses/create/page.tsx
@@ -1,5 +1,5 @@
import { ExpenseForm } from '@/components/expense-form'
-import { createExpense, getGroup, getCategories } from '@/lib/api'
+import { createExpense, getCategories, getGroup } from '@/lib/api'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
@@ -24,5 +24,11 @@ export default async function ExpensePage({
redirect(`/groups/${groupId}`)
}
- return
+ return (
+
+ )
}
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx
index 8aeeb59..967c6b7 100644
--- a/src/app/groups/page.tsx
+++ b/src/app/groups/page.tsx
@@ -11,18 +11,19 @@ export const metadata: Metadata = {
export default async function GroupsPage() {
return (
<>
-
+
- Recently visited groups
+ My groups
-
-
+
+
+
>
)
}
diff --git a/src/app/groups/recent-group-list-card.tsx b/src/app/groups/recent-group-list-card.tsx
new file mode 100644
index 0000000..7809566
--- /dev/null
+++ b/src/app/groups/recent-group-list-card.tsx
@@ -0,0 +1,185 @@
+'use client'
+import { RecentGroupsState } from '@/app/groups/recent-group-list'
+import {
+ RecentGroup,
+ archiveGroup,
+ deleteRecentGroup,
+ getArchivedGroups,
+ getStarredGroups,
+ saveRecentGroup,
+ starGroup,
+ unarchiveGroup,
+ 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 { StarFilledIcon } from '@radix-ui/react-icons'
+import { Calendar, MoreHorizontal, Star, Users } from 'lucide-react'
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+import { SetStateAction } from 'react'
+
+export function RecentGroupListCard({
+ group,
+ state,
+ setState,
+}: {
+ group: RecentGroup
+ state: RecentGroupsState
+ setState: (state: SetStateAction
) => void
+}) {
+ const router = useRouter()
+ const toast = useToast()
+
+ const details =
+ state.status === 'complete'
+ ? state.groupsDetails.find((d) => d.id === group.id)
+ : null
+
+ if (state.status === 'pending') return null
+
+ const refreshGroupsFromStorage = () =>
+ setState({
+ ...state,
+ starredGroups: getStarredGroups(),
+ archivedGroups: getArchivedGroups(),
+ })
+
+ const isStarred = state.starredGroups.includes(group.id)
+ const isArchived = state.archivedGroups.includes(group.id)
+
+ return (
+
+
+ router.push(`/groups/${group.id}`)}
+ >
+
+
+
+ {group.name}
+
+
+ {
+ event.stopPropagation()
+ if (isStarred) {
+ unstarGroup(group.id)
+ } else {
+ starGroup(group.id)
+ unarchiveGroup(group.id)
+ }
+ refreshGroupsFromStorage()
+ }}
+ >
+ {isStarred ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+ {
+ 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: (
+ {
+ saveRecentGroup(group)
+ setState({
+ ...state,
+ groups: state.groups,
+ })
+ }}
+ >
+ Undo
+
+ ),
+ })
+ }}
+ >
+ Remove from recent groups
+
+ {
+ event.stopPropagation()
+ if (isArchived) {
+ unarchiveGroup(group.id)
+ } else {
+ archiveGroup(group.id)
+ unstarGroup(group.id)
+ }
+ refreshGroupsFromStorage()
+ }}
+ >
+ {isArchived ? <>Unarchive group> : <>Archive group>}
+
+
+
+
+
+
+ {details ? (
+
+
+
+ {details._count.participants}
+
+
+
+
+ {new Date(details.createdAt).toLocaleDateString('en-US', {
+ dateStyle: 'medium',
+ })}
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+
+
+
+
+ )
+}
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'
+import { SetStateAction, useEffect, useState } from 'react'
+import { RecentGroupListCard } from './recent-group-list-card'
-const recentGroupsSchema = z.array(
- z.object({
- id: z.string().min(1),
- name: z.string(),
- }),
-)
-type RecentGroups = z.infer
-
-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>
starredGroups: string[]
+ archivedGroups: string[]
}
-type Props = {
- getGroupsAction: (groupIds: string[]) => ReturnType
+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({ status: 'pending' })
+ const [state, setState] = useState({ 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 (
@@ -91,139 +98,63 @@ export function RecentGroupList() {
)
}
+ const { starredGroupInfo, groupInfo, archivedGroupInfo } = sortGroups(state)
+
+ return (
+ <>
+ {starredGroupInfo.length > 0 && (
+ <>
+
Starred groups
+
+ >
+ )}
+
+ {groupInfo.length > 0 && (
+ <>
+ Recent groups
+
+ >
+ )}
+
+ {archivedGroupInfo.length > 0 && (
+ <>
+ Archived groups
+
+
+
+ >
+ )}
+ >
+ )
+}
+
+function GroupList({
+ groups,
+ state,
+ setState,
+}: {
+ groups: RecentGroups
+ state: RecentGroupsState
+ setState: (state: SetStateAction) => void
+}) {
return (
- {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 (
- -
-
-
router.push(`/groups/${group.id}`)}
- >
-
-
-
- {group.name}
-
-
- {
- event.stopPropagation()
- if (state.starredGroups.includes(group.id)) {
- unstarGroup(group.id)
- } else {
- starGroup(group.id)
- }
- setState({
- ...state,
- starredGroups: getStarredGroups(),
- })
- }}
- >
- {state.starredGroups.includes(group.id) ? (
-
- ) : (
-
- )}
-
-
-
-
-
-
-
-
- {
- 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: (
- {
- saveRecentGroup(group)
- setState({
- ...state,
- groups: state.groups,
- })
- }}
- >
- Undo
-
- ),
- })
- }}
- >
- Remove from recent groups
-
-
-
-
-
-
- {details ? (
-
-
-
- {details._count.participants}
-
-
-
-
- {new Date(details.createdAt).toLocaleDateString(
- 'en-US',
- {
- dateStyle: 'medium',
- },
- )}
-
-
-
- ) : (
-
-
-
-
- )}
-
-
-
-
-
- )
- })}
+ {groups.map((group) => (
+
+ ))}
)
}
diff --git a/src/app/groups/recent-groups-helpers.ts b/src/app/groups/recent-groups-helpers.ts
index 72c5d28..0d718e2 100644
--- a/src/app/groups/recent-groups-helpers.ts
+++ b/src/app/groups/recent-groups-helpers.ts
@@ -8,12 +8,14 @@ export const recentGroupsSchema = z.array(
)
export const starredGroupsSchema = z.array(z.string())
+export const archivedGroupsSchema = z.array(z.string())
export type RecentGroups = z.infer
export type RecentGroup = RecentGroups[number]
const STORAGE_KEY = 'recentGroups'
const STARRED_GROUPS_STORAGE_KEY = 'starredGroups'
+const ARCHIVED_GROUPS_STORAGE_KEY = 'archivedGroups'
export function getRecentGroups() {
const groupsInStorageJson = localStorage.getItem(STORAGE_KEY)
@@ -64,3 +66,28 @@ export function unstarGroup(groupId: string) {
JSON.stringify(starredGroups.filter((g) => g !== groupId)),
)
}
+
+export function getArchivedGroups() {
+ const archivedGroupsJson = localStorage.getItem(ARCHIVED_GROUPS_STORAGE_KEY)
+ const archivedGroupsRaw = archivedGroupsJson
+ ? JSON.parse(archivedGroupsJson)
+ : []
+ const parseResult = archivedGroupsSchema.safeParse(archivedGroupsRaw)
+ return parseResult.success ? parseResult.data : []
+}
+
+export function archiveGroup(groupId: string) {
+ const archivedGroups = getArchivedGroups()
+ localStorage.setItem(
+ ARCHIVED_GROUPS_STORAGE_KEY,
+ JSON.stringify([...archivedGroups, groupId]),
+ )
+}
+
+export function unarchiveGroup(groupId: string) {
+ const archivedGroups = getArchivedGroups()
+ localStorage.setItem(
+ ARCHIVED_GROUPS_STORAGE_KEY,
+ JSON.stringify(archivedGroups.filter((g) => g !== groupId)),
+ )
+}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 2a03ded..c27e584 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -218,7 +218,11 @@ export async function getGroupExpenses(groupId: string) {
const prisma = await getPrisma()
return prisma.expense.findMany({
where: { groupId },
- include: { paidFor: { include: { participant: true } }, paidBy: true, category: true },
+ include: {
+ paidFor: { include: { participant: true } },
+ paidBy: true,
+ category: true,
+ },
orderBy: { expenseDate: 'desc' },
})
}