Add tRPC, use it for group expenses, balances and information page (#246)

* Add tRPC, use it for group expense list

* Use tRPC for balances

* Use tRPC in group information + better loading states
This commit is contained in:
Sebastien Castiel
2024-10-19 17:42:11 -04:00
committed by GitHub
parent 727803ea5c
commit 66e15e419e
24 changed files with 671 additions and 239 deletions

View File

@@ -0,0 +1,138 @@
'use client'
import { BalancesList } from '@/app/groups/[groupId]/balances-list'
import { ReimbursementList } from '@/app/groups/[groupId]/reimbursement-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Skeleton } from '@/components/ui/skeleton'
import { getGroup } from '@/lib/api'
import { trpc } from '@/trpc/client'
import { useTranslations } from 'next-intl'
import { Fragment, useEffect } from 'react'
export default function BalancesAndReimbursements({
group,
}: {
group: NonNullable<Awaited<ReturnType<typeof getGroup>>>
}) {
const utils = trpc.useUtils()
useEffect(() => {
// Until we use tRPC more widely and can invalidate the cache on expense
// update, it's easier and safer to invalidate the cache on page load.
utils.groups.balances.invalidate()
}, [utils])
const t = useTranslations('Balances')
const { data, isLoading } = trpc.groups.balances.list.useQuery({
groupId: group.id,
})
return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent>
{isLoading || !data ? (
<BalancesLoading participantCount={group.participants.length} />
) : (
<BalancesList
balances={data.balances}
participants={group.participants}
currency={group.currency}
/>
)}
</CardContent>
</Card>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('Reimbursements.title')}</CardTitle>
<CardDescription>{t('Reimbursements.description')}</CardDescription>
</CardHeader>
<CardContent>
{isLoading || !data ? (
<ReimbursementsLoading
participantCount={group.participants.length}
/>
) : (
<ReimbursementList
reimbursements={data.reimbursements}
participants={group.participants}
currency={group.currency}
groupId={group.id}
/>
)}
</CardContent>
</Card>
</>
)
}
const ReimbursementsLoading = ({
participantCount,
}: {
participantCount: number
}) => {
return (
<div className="flex flex-col">
{Array(participantCount - 1)
.fill(undefined)
.map((_, index) => (
<div key={index} className="flex justify-between py-5">
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4">
<Skeleton className="h-3 w-32" />
<Skeleton className="h-3 w-24" />
</div>
<Skeleton className="h-3 w-16" />
</div>
))}
</div>
)
}
const BalancesLoading = ({
participantCount,
}: {
participantCount: number
}) => {
return (
<div className="grid grid-cols-2 py-1 gap-y-2">
{Array(participantCount)
.fill(undefined)
.map((_, index) =>
index % 2 === 0 ? (
<Fragment key={index}>
<div className="flex items-center justify-end pr-2">
<Skeleton className="h-3 w-16" />
</div>
<div className="self-start">
<Skeleton
className={`h-7 w-${(index % 3) + 1}/3 rounded-l-none`}
/>
</div>
</Fragment>
) : (
<Fragment key={index}>
<div className="flex items-center justify-end">
<Skeleton
className={`h-7 w-${(index % 3) + 1}/3 rounded-r-none`}
/>
</div>
<div className="flex items-center pl-2">
<Skeleton className="h-3 w-16" />
</div>
</Fragment>
),
)}
</div>
)
}

View File

@@ -1,21 +1,6 @@
import { cached } from '@/app/cached-functions'
import { BalancesList } from '@/app/groups/[groupId]/balances-list'
import { ReimbursementList } from '@/app/groups/[groupId]/reimbursement-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { getGroupExpenses } from '@/lib/api'
import {
getBalances,
getPublicBalances,
getSuggestedReimbursements,
} from '@/lib/balances'
import BalancesAndReimbursements from '@/app/groups/[groupId]/balances/balances-and-reimbursements'
import { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'
export const metadata: Metadata = {
@@ -27,44 +12,8 @@ export default async function GroupPage({
}: {
params: { groupId: string }
}) {
const t = await getTranslations('Balances')
const group = await cached.getGroup(groupId)
if (!group) notFound()
const expenses = await getGroupExpenses(groupId)
const balances = getBalances(expenses)
const reimbursements = getSuggestedReimbursements(balances)
const publicBalances = getPublicBalances(reimbursements)
return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent>
<BalancesList
balances={publicBalances}
participants={group.participants}
currency={group.currency}
/>
</CardContent>
</Card>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('Reimbursements.title')}</CardTitle>
<CardDescription>{t('Reimbursements.description')}</CardDescription>
</CardHeader>
<CardContent className="p-0">
<ReimbursementList
reimbursements={reimbursements}
participants={group.participants}
currency={group.currency}
groupId={groupId}
/>
</CardContent>
</Card>
</>
)
return <BalancesAndReimbursements group={group} />
}