aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastien Castiel <sebastien@castiel.me>2023-12-07 20:54:11 -0500
committerSebastien Castiel <sebastien@castiel.me>2023-12-07 20:57:48 -0500
commit0e7b879aa8d771b58fc5783f78f981cb29432361 (patch)
tree30d04a9c8894bc2215abbfb9dfdc32dcd06f4349
parent6f077f141edcc45f2d7467b7c796e62a8f0316f8 (diff)
Redesign the groups page
-rw-r--r--src/app/groups/page.tsx26
-rw-r--r--src/app/groups/recent-group-list.tsx72
-rw-r--r--src/components/group-form.tsx2
-rw-r--r--src/lib/api.ts10
4 files changed, 86 insertions, 24 deletions
diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx
index 86c63ac..0c46d99 100644
--- a/src/app/groups/page.tsx
+++ b/src/app/groups/page.tsx
@@ -1,5 +1,7 @@
import { RecentGroupList } from '@/app/groups/recent-group-list'
import { Button } from '@/components/ui/button'
+import { getGroups } from '@/lib/api'
+import { Plus } from 'lucide-react'
import { Metadata } from 'next'
import Link from 'next/link'
@@ -8,15 +10,25 @@ export const metadata: Metadata = {
}
export default async function GroupsPage() {
+ async function getGroupsAction(groupIds: string[]) {
+ 'use server'
+ return getGroups(groupIds)
+ }
+
return (
<main>
- <h1 className="font-bold text-2xl mb-4">
- <Link href="/groups">Recently visited groups</Link>
- </h1>
- <Button asChild>
- <Link href="/groups/create">New group</Link>
- </Button>
- <RecentGroupList />
+ <div className="flex flex-col sm:flex-row sm:justify-between sm:items-baseline mb-2">
+ <h1 className="font-bold text-2xl mb-4">
+ <Link href="/groups">Recently visited groups</Link>
+ </h1>
+ <Button asChild>
+ <Link href="/groups/create">
+ <Plus className="w-4 h-4 mr-2" />
+ Create group
+ </Link>
+ </Button>
+ </div>
+ <RecentGroupList getGroupsAction={getGroupsAction} />
</main>
)
}
diff --git a/src/app/groups/recent-group-list.tsx b/src/app/groups/recent-group-list.tsx
index e728193..ef43372 100644
--- a/src/app/groups/recent-group-list.tsx
+++ b/src/app/groups/recent-group-list.tsx
@@ -1,6 +1,8 @@
'use client'
import { getRecentGroups } from '@/app/groups/recent-groups-helpers'
import { Button } from '@/components/ui/button'
+import { getGroups } from '@/lib/api'
+import { Loader2 } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { z } from 'zod'
@@ -13,31 +15,69 @@ const recentGroupsSchema = z.array(
)
type RecentGroups = z.infer<typeof recentGroupsSchema>
-type State = { status: 'pending' } | { status: 'success'; groups: RecentGroups }
+type State =
+ | { status: 'pending' }
+ | { status: 'partial'; groups: RecentGroups }
+ | {
+ status: 'complete'
+ groups: RecentGroups
+ groupsDetails: Awaited<ReturnType<typeof getGroups>>
+ }
-export function RecentGroupList() {
+type Props = {
+ getGroupsAction: (groupIds: string[]) => ReturnType<typeof getGroups>
+}
+
+export function RecentGroupList({ getGroupsAction }: Props) {
const [state, setState] = useState<State>({ status: 'pending' })
useEffect(() => {
const groupsInStorage = getRecentGroups()
- setState({ status: 'success', groups: groupsInStorage })
- }, [])
+ setState({ status: 'partial', groups: groupsInStorage })
+ getGroupsAction(groupsInStorage.map((g) => g.id)).then((groupsDetails) => {
+ setState({ status: 'complete', groups: groupsInStorage, groupsDetails })
+ })
+ }, [getGroupsAction])
+
+ if (state.status === 'pending')
+ return (
+ <p>
+ <Loader2 className="w-4 m-4 mr-2 inline animate-spin" /> Loading recent
+ groups…
+ </p>
+ )
return (
- <ul className="flex flex-col gap-2 mt-2">
- {state.status === 'pending' ? (
- <li>
- <em>Loading recent groups…</em>
- </li>
- ) : (
- state.groups.map(({ id, name }) => (
- <li key={id}>
- <Button asChild variant="outline">
- <Link href={`/groups/${id}`}>{name}</Link>
+ <ul className="grid grid-cols-1 gap-2 mt-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 items-start gap-1">
+ <div className="text-base">{group.name}</div>
+ <div className="text-muted-foreground font-normal text-xs">
+ {details ? (
+ <>
+ {details._count.participants} participants ·{' '}
+ {details.currency}
+ </>
+ ) : (
+ <>
+ <Loader2 className="w-3 h-3 mr-1 inline animate-spin" />
+ </>
+ )}
+ </div>
+ </div>
+ </Link>
</Button>
</li>
- ))
- )}
+ )
+ })}
</ul>
)
}
diff --git a/src/components/group-form.tsx b/src/components/group-form.tsx
index 7bf0f8e..0d6ea68 100644
--- a/src/components/group-form.tsx
+++ b/src/components/group-form.tsx
@@ -128,7 +128,7 @@ export function GroupForm({
<CardContent>
<ul className="flex flex-col gap-2">
{fields.map((item, index) => (
- <li key={item.id}>
+ <li key={item.key}>
<FormField
control={form.control}
name={`participants.${index}.name`}
diff --git a/src/lib/api.ts b/src/lib/api.ts
index 2968cae..34e4473 100644
--- a/src/lib/api.ts
+++ b/src/lib/api.ts
@@ -1,5 +1,6 @@
import { getPrisma } from '@/lib/prisma'
import { ExpenseFormValues, GroupFormValues } from '@/lib/schemas'
+import { delay } from '@/lib/utils'
import { Expense } from '@prisma/client'
import { v4 as uuidv4 } from 'uuid'
@@ -78,6 +79,15 @@ export async function getGroupExpensesParticipants(groupId: string) {
)
}
+export async function getGroups(groupIds: string[]) {
+ await delay(2000)
+ const prisma = await getPrisma()
+ return prisma.group.findMany({
+ where: { id: { in: groupIds } },
+ include: { _count: { select: { participants: true } } },
+ })
+}
+
export async function updateExpense(
groupId: string,
expenseId: string,