diff options
| author | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 20:54:11 -0500 |
|---|---|---|
| committer | Sebastien Castiel <sebastien@castiel.me> | 2023-12-07 20:57:48 -0500 |
| commit | 0e7b879aa8d771b58fc5783f78f981cb29432361 (patch) | |
| tree | 30d04a9c8894bc2215abbfb9dfdc32dcd06f4349 | |
| parent | 6f077f141edcc45f2d7467b7c796e62a8f0316f8 (diff) | |
Redesign the groups page
| -rw-r--r-- | src/app/groups/page.tsx | 26 | ||||
| -rw-r--r-- | src/app/groups/recent-group-list.tsx | 72 | ||||
| -rw-r--r-- | src/components/group-form.tsx | 2 | ||||
| -rw-r--r-- | src/lib/api.ts | 10 |
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, |
