Skip to content

Commit

Permalink
Use tRPC in other group pages (#249)
Browse files Browse the repository at this point in the history
* Use tRPC in group edition + group layout

* Use tRPC in group modals

* Use tRPC in group stats

* Use tRPC in group activity
  • Loading branch information
scastiel authored Oct 20, 2024
1 parent 66e15e4 commit 210c12b
Show file tree
Hide file tree
Showing 35 changed files with 706 additions and 495 deletions.
11 changes: 6 additions & 5 deletions src/app/groups/[groupId]/activity/activity-item.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
'use client'
import { Button } from '@/components/ui/button'
import { getGroupExpenses } from '@/lib/api'
import { DateTimeStyle, cn, formatDate } from '@/lib/utils'
import { Activity, ActivityType, Participant } from '@prisma/client'
import { AppRouterOutput } from '@/trpc/routers/_app'
import { ActivityType, Participant } from '@prisma/client'
import { ChevronRight } from 'lucide-react'
import { useLocale, useTranslations } from 'next-intl'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

export type Activity =
AppRouterOutput['groups']['activities']['list']['activities'][number]

type Props = {
groupId: string
activity: Activity
participant?: Participant
expense?: Awaited<ReturnType<typeof getGroupExpenses>>[number]
dateStyle: DateTimeStyle
}

Expand Down Expand Up @@ -44,13 +46,12 @@ export function ActivityItem({
groupId,
activity,
participant,
expense,
dateStyle,
}: Props) {
const router = useRouter()
const locale = useLocale()

const expenseExists = expense !== undefined
const expenseExists = activity.expense !== undefined
const summary = useSummary(activity, participant?.name)

return (
Expand Down
94 changes: 69 additions & 25 deletions src/app/groups/[groupId]/activity/activity-list.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { ActivityItem } from '@/app/groups/[groupId]/activity/activity-item'
import { getGroupExpenses } from '@/lib/api'
import { Activity, Participant } from '@prisma/client'
'use client'
import {
Activity,
ActivityItem,
} from '@/app/groups/[groupId]/activity/activity-item'
import { Skeleton } from '@/components/ui/skeleton'
import { trpc } from '@/trpc/client'
import dayjs, { type Dayjs } from 'dayjs'
import { useTranslations } from 'next-intl'
import { forwardRef, useEffect } from 'react'
import { useInView } from 'react-intersection-observer'

type Props = {
groupId: string
participants: Participant[]
expenses: Awaited<ReturnType<typeof getGroupExpenses>>
activities: Activity[]
}
const PAGE_SIZE = 20

const DATE_GROUPS = {
TODAY: 'today',
Expand Down Expand Up @@ -48,23 +49,64 @@ function getDateGroup(date: Dayjs, today: Dayjs) {
function getGroupedActivitiesByDate(activities: Activity[]) {
const today = dayjs()
return activities.reduce(
(result: { [key: string]: Activity[] }, activity: Activity) => {
(result, activity) => {
const activityGroup = getDateGroup(dayjs(activity.time), today)
result[activityGroup] = result[activityGroup] ?? []
result[activityGroup].push(activity)
return result
},
{},
{} as {
[key: string]: Activity[]
},
)
}

export function ActivityList({
groupId,
participants,
expenses,
activities,
}: Props) {
const ActivitiesLoading = forwardRef<HTMLDivElement>((_, ref) => {
return (
<div ref={ref} className="flex flex-col gap-4">
<Skeleton className="mt-2 h-3 w-24" />
{Array(5)
.fill(undefined)
.map((_, index) => (
<div key={index} className="flex gap-2 p-2">
<div className="flex-0">
<Skeleton className="h-3 w-12" />
</div>
<div className="flex-1">
<Skeleton className="h-3 w-48" />
</div>
</div>
))}
</div>
)
})
ActivitiesLoading.displayName = 'ActivitiesLoading'

export function ActivityList({ groupId }: { groupId: string }) {
const t = useTranslations('Activity')

const { data: groupData, isLoading: groupIsLoading } =
trpc.groups.get.useQuery({ groupId })

const {
data: activitiesData,
isLoading,
fetchNextPage,
} = trpc.groups.activities.list.useInfiniteQuery(
{ groupId, limit: PAGE_SIZE },
{ getNextPageParam: ({ nextCursor }) => nextCursor },
)
const { ref: loadingRef, inView } = useInView()

const activities = activitiesData?.pages.flatMap((page) => page.activities)
const hasMore = activitiesData?.pages.at(-1)?.hasMore ?? false

useEffect(() => {
if (inView && hasMore && !isLoading) fetchNextPage()
}, [fetchNextPage, hasMore, inView, isLoading])

if (isLoading || !activities || !groupData) return <ActivitiesLoading />

const groupedActivitiesByDate = getGroupedActivitiesByDate(activities)

return activities.length > 0 ? (
Expand All @@ -86,27 +128,29 @@ export function ActivityList({
>
{t(`Groups.${dateGroup}`)}
</div>
{groupActivities.map((activity: Activity) => {
{groupActivities.map((activity) => {
const participant =
activity.participantId !== null
? participants.find((p) => p.id === activity.participantId)
: undefined
const expense =
activity.expenseId !== null
? expenses.find((e) => e.id === activity.expenseId)
? groupData.group.participants.find(
(p) => p.id === activity.participantId,
)
: undefined
return (
<ActivityItem
key={activity.id}
{...{ groupId, activity, participant, expense, dateStyle }}
groupId={groupId}
activity={activity}
participant={participant}
dateStyle={dateStyle}
/>
)
})}
</div>
)
})}
{hasMore && <ActivitiesLoading ref={loadingRef} />}
</>
) : (
<p className="px-6 text-sm py-6">{t('noActivity')}</p>
<p className="text-sm py-6">{t('noActivity')}</p>
)
}
32 changes: 32 additions & 0 deletions src/app/groups/[groupId]/activity/page.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ActivityList } from '@/app/groups/[groupId]/activity/activity-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { Metadata } from 'next'
import { useTranslations } from 'next-intl'

export const metadata: Metadata = {
title: 'Activity',
}

export function ActivityPageClient({ groupId }: { groupId: string }) {
const t = useTranslations('Activity')

return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col space-y-4">
<ActivityList groupId={groupId} />
</CardContent>
</Card>
</>
)
}
41 changes: 2 additions & 39 deletions src/app/groups/[groupId]/activity/page.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import { cached } from '@/app/cached-functions'
import { ActivityList } from '@/app/groups/[groupId]/activity/activity-list'
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import { getActivities, getGroupExpenses } from '@/lib/api'
import { ActivityPageClient } from '@/app/groups/[groupId]/activity/page.client'
import { Metadata } from 'next'
import { getTranslations } from 'next-intl/server'
import { notFound } from 'next/navigation'

export const metadata: Metadata = {
title: 'Activity',
Expand All @@ -21,31 +10,5 @@ export default async function ActivityPage({
}: {
params: { groupId: string }
}) {
const t = await getTranslations('Activity')
const group = await cached.getGroup(groupId)
if (!group) notFound()

const expenses = await getGroupExpenses(groupId)
const activities = await getActivities(groupId)

return (
<>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col space-y-4">
<ActivityList
{...{
groupId,
participants: group.participants,
expenses,
activities,
}}
/>
</CardContent>
</Card>
</>
)
return <ActivityPageClient groupId={groupId} />
}
51 changes: 28 additions & 23 deletions src/app/groups/[groupId]/balances/balances-and-reimbursements.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,32 @@ import {
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,
groupId,
}: {
group: NonNullable<Awaited<ReturnType<typeof getGroup>>>
groupId: string
}) {
const utils = trpc.useUtils()
const { data: groupData, isLoading: groupIsLoading } =
trpc.groups.get.useQuery({ groupId })
const { data: balancesData, isLoading: balancesAreLoading } =
trpc.groups.balances.list.useQuery({
groupId,
})
const t = useTranslations('Balances')

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,
})
const isLoading =
balancesAreLoading || !balancesData || groupIsLoading || !groupData?.group

return (
<>
Expand All @@ -42,13 +45,15 @@ export default function BalancesAndReimbursements({
<CardDescription>{t('description')}</CardDescription>
</CardHeader>
<CardContent>
{isLoading || !data ? (
<BalancesLoading participantCount={group.participants.length} />
{isLoading ? (
<BalancesLoading
participantCount={groupData?.group.participants.length}
/>
) : (
<BalancesList
balances={data.balances}
participants={group.participants}
currency={group.currency}
balances={balancesData.balances}
participants={groupData.group.participants}
currency={groupData.group.currency}
/>
)}
</CardContent>
Expand All @@ -59,16 +64,16 @@ export default function BalancesAndReimbursements({
<CardDescription>{t('Reimbursements.description')}</CardDescription>
</CardHeader>
<CardContent>
{isLoading || !data ? (
{isLoading ? (
<ReimbursementsLoading
participantCount={group.participants.length}
participantCount={groupData?.group.participants.length}
/>
) : (
<ReimbursementList
reimbursements={data.reimbursements}
participants={group.participants}
currency={group.currency}
groupId={group.id}
reimbursements={balancesData.reimbursements}
participants={groupData.group.participants}
currency={groupData.group.currency}
groupId={groupData.group.id}
/>
)}
</CardContent>
Expand All @@ -78,9 +83,9 @@ export default function BalancesAndReimbursements({
}

const ReimbursementsLoading = ({
participantCount,
participantCount = 3,
}: {
participantCount: number
participantCount?: number
}) => {
return (
<div className="flex flex-col">
Expand All @@ -100,9 +105,9 @@ const ReimbursementsLoading = ({
}

const BalancesLoading = ({
participantCount,
participantCount = 3,
}: {
participantCount: number
participantCount?: number
}) => {
return (
<div className="grid grid-cols-2 py-1 gap-y-2">
Expand Down
2 changes: 1 addition & 1 deletion src/app/groups/[groupId]/balances/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ export default async function GroupPage({
const group = await cached.getGroup(groupId)
if (!group) notFound()

return <BalancesAndReimbursements group={group} />
return <BalancesAndReimbursements groupId={groupId} />
}
Loading

0 comments on commit 210c12b

Please sign in to comment.