mirror of
https://github.com/spliit-app/spliit.git
synced 2026-03-06 12:36:11 +01:00
Use tRPC for recent groups page (#253)
* Use tRPC for recent groups page * Use tRPC for adding group by URL * Use tRPC for saving visited group * Group context
This commit is contained in:
@@ -9,6 +9,7 @@ import dayjs, { type Dayjs } from 'dayjs'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { forwardRef, useEffect } from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -82,11 +83,9 @@ const ActivitiesLoading = forwardRef<HTMLDivElement>((_, ref) => {
|
||||
})
|
||||
ActivitiesLoading.displayName = 'ActivitiesLoading'
|
||||
|
||||
export function ActivityList({ groupId }: { groupId: string }) {
|
||||
export function ActivityList() {
|
||||
const t = useTranslations('Activity')
|
||||
|
||||
const { data: groupData, isLoading: groupIsLoading } =
|
||||
trpc.groups.get.useQuery({ groupId })
|
||||
const { group, groupId } = useCurrentGroup()
|
||||
|
||||
const {
|
||||
data: activitiesData,
|
||||
@@ -105,7 +104,7 @@ export function ActivityList({ groupId }: { groupId: string }) {
|
||||
if (inView && hasMore && !isLoading) fetchNextPage()
|
||||
}, [fetchNextPage, hasMore, inView, isLoading])
|
||||
|
||||
if (isLoading || !activities || !groupData) return <ActivitiesLoading />
|
||||
if (isLoading || !activities || !group) return <ActivitiesLoading />
|
||||
|
||||
const groupedActivitiesByDate = getGroupedActivitiesByDate(activities)
|
||||
|
||||
@@ -131,7 +130,7 @@ export function ActivityList({ groupId }: { groupId: string }) {
|
||||
{groupActivities.map((activity) => {
|
||||
const participant =
|
||||
activity.participantId !== null
|
||||
? groupData.group.participants.find(
|
||||
? group.participants.find(
|
||||
(p) => p.id === activity.participantId,
|
||||
)
|
||||
: undefined
|
||||
|
||||
@@ -13,7 +13,7 @@ export const metadata: Metadata = {
|
||||
title: 'Activity',
|
||||
}
|
||||
|
||||
export function ActivityPageClient({ groupId }: { groupId: string }) {
|
||||
export function ActivityPageClient() {
|
||||
const t = useTranslations('Activity')
|
||||
|
||||
return (
|
||||
@@ -24,7 +24,7 @@ export function ActivityPageClient({ groupId }: { groupId: string }) {
|
||||
<CardDescription>{t('description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col space-y-4">
|
||||
<ActivityList groupId={groupId} />
|
||||
<ActivityList />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -5,10 +5,6 @@ export const metadata: Metadata = {
|
||||
title: 'Activity',
|
||||
}
|
||||
|
||||
export default async function ActivityPage({
|
||||
params: { groupId },
|
||||
}: {
|
||||
params: { groupId: string }
|
||||
}) {
|
||||
return <ActivityPageClient groupId={groupId} />
|
||||
export default async function ActivityPage() {
|
||||
return <ActivityPageClient />
|
||||
}
|
||||
|
||||
@@ -13,15 +13,12 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { Fragment, useEffect } from 'react'
|
||||
import { match } from 'ts-pattern'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
export default function BalancesAndReimbursements({
|
||||
groupId,
|
||||
}: {
|
||||
groupId: string
|
||||
}) {
|
||||
export default function BalancesAndReimbursements() {
|
||||
const utils = trpc.useUtils()
|
||||
const { data: groupData, isLoading: groupIsLoading } =
|
||||
trpc.groups.get.useQuery({ groupId })
|
||||
const { groupId, group } = useCurrentGroup()
|
||||
const { data: balancesData, isLoading: balancesAreLoading } =
|
||||
trpc.groups.balances.list.useQuery({
|
||||
groupId,
|
||||
@@ -34,8 +31,7 @@ export default function BalancesAndReimbursements({
|
||||
utils.groups.balances.invalidate()
|
||||
}, [utils])
|
||||
|
||||
const isLoading =
|
||||
balancesAreLoading || !balancesData || groupIsLoading || !groupData?.group
|
||||
const isLoading = balancesAreLoading || !balancesData || !group
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -46,14 +42,12 @@ export default function BalancesAndReimbursements({
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<BalancesLoading
|
||||
participantCount={groupData?.group.participants.length}
|
||||
/>
|
||||
<BalancesLoading participantCount={group?.participants.length} />
|
||||
) : (
|
||||
<BalancesList
|
||||
balances={balancesData.balances}
|
||||
participants={groupData.group.participants}
|
||||
currency={groupData.group.currency}
|
||||
participants={group?.participants}
|
||||
currency={group?.currency}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -66,14 +60,14 @@ export default function BalancesAndReimbursements({
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<ReimbursementsLoading
|
||||
participantCount={groupData?.group.participants.length}
|
||||
participantCount={group?.participants.length}
|
||||
/>
|
||||
) : (
|
||||
<ReimbursementList
|
||||
reimbursements={balancesData.reimbursements}
|
||||
participants={groupData.group.participants}
|
||||
currency={groupData.group.currency}
|
||||
groupId={groupData.group.id}
|
||||
participants={group?.participants}
|
||||
currency={group?.currency}
|
||||
groupId={groupId}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
@@ -109,6 +103,12 @@ const BalancesLoading = ({
|
||||
}: {
|
||||
participantCount?: number
|
||||
}) => {
|
||||
const barWidth = (index: number) =>
|
||||
match(index % 3)
|
||||
.with(0, () => 'w-1/3')
|
||||
.with(1, () => 'w-2/3')
|
||||
.otherwise(() => 'w-full')
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 py-1 gap-y-2">
|
||||
{Array(participantCount)
|
||||
@@ -120,17 +120,13 @@ const BalancesLoading = ({
|
||||
<Skeleton className="h-3 w-16" />
|
||||
</div>
|
||||
<div className="self-start">
|
||||
<Skeleton
|
||||
className={`h-7 w-${(index % 3) + 1}/3 rounded-l-none`}
|
||||
/>
|
||||
<Skeleton className={`h-7 ${barWidth(index)} 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`}
|
||||
/>
|
||||
<Skeleton className={`h-7 ${barWidth(index)} rounded-r-none`} />
|
||||
</div>
|
||||
<div className="flex items-center pl-2">
|
||||
<Skeleton className="h-3 w-16" />
|
||||
|
||||
@@ -1,19 +1,10 @@
|
||||
import { cached } from '@/app/cached-functions'
|
||||
import BalancesAndReimbursements from '@/app/groups/[groupId]/balances/balances-and-reimbursements'
|
||||
import { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Balances',
|
||||
}
|
||||
|
||||
export default async function GroupPage({
|
||||
params: { groupId },
|
||||
}: {
|
||||
params: { groupId: string }
|
||||
}) {
|
||||
const group = await cached.getGroup(groupId)
|
||||
if (!group) notFound()
|
||||
|
||||
return <BalancesAndReimbursements groupId={groupId} />
|
||||
export default async function GroupPage() {
|
||||
return <BalancesAndReimbursements />
|
||||
}
|
||||
|
||||
30
src/app/groups/[groupId]/current-group-context.tsx
Normal file
30
src/app/groups/[groupId]/current-group-context.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { AppRouterOutput } from '@/trpc/routers/_app'
|
||||
import { PropsWithChildren, createContext, useContext } from 'react'
|
||||
|
||||
type Group = NonNullable<AppRouterOutput['groups']['get']['group']>
|
||||
|
||||
type GroupContext =
|
||||
| { isLoading: false; groupId: string; group: Group }
|
||||
| { isLoading: true; groupId: string; group: undefined }
|
||||
|
||||
const CurrentGroupContext = createContext<GroupContext | null>(null)
|
||||
|
||||
export const useCurrentGroup = () => {
|
||||
const context = useContext(CurrentGroupContext)
|
||||
if (!context)
|
||||
throw new Error(
|
||||
'Missing context. Should be called inside a CurrentGroupProvider.',
|
||||
)
|
||||
return context
|
||||
}
|
||||
|
||||
export const CurrentGroupProvider = ({
|
||||
children,
|
||||
...props
|
||||
}: PropsWithChildren<GroupContext>) => {
|
||||
return (
|
||||
<CurrentGroupContext.Provider value={props}>
|
||||
{children}
|
||||
</CurrentGroupContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
import { GroupForm } from '@/components/group-form'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
export const EditGroup = ({ groupId }: { groupId: string }) => {
|
||||
const { data, isLoading } = trpc.groups.get.useQuery({ groupId })
|
||||
export const EditGroup = () => {
|
||||
const { groupId } = useCurrentGroup()
|
||||
const { data, isLoading } = trpc.groups.getDetails.useQuery({ groupId })
|
||||
const { mutateAsync } = trpc.groups.update.useMutation()
|
||||
const utils = trpc.useUtils()
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ export const metadata: Metadata = {
|
||||
title: 'Settings',
|
||||
}
|
||||
|
||||
export default async function EditGroupPage({
|
||||
params: { groupId },
|
||||
}: {
|
||||
params: { groupId: string }
|
||||
}) {
|
||||
return <EditGroup groupId={groupId} />
|
||||
export default async function EditGroupPage() {
|
||||
return <EditGroup />
|
||||
}
|
||||
|
||||
@@ -34,14 +34,11 @@ import { getImageData, usePresignedUpload } from 'next-s3-upload'
|
||||
import Image from 'next/image'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { PropsWithChildren, ReactNode, useState } from 'react'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 ** 2
|
||||
|
||||
export function CreateFromReceiptButton({ groupId }: { groupId: string }) {
|
||||
return <CreateFromReceiptButton_ groupId={groupId} />
|
||||
}
|
||||
|
||||
function CreateFromReceiptButton_({ groupId }: { groupId: string }) {
|
||||
export function CreateFromReceiptButton() {
|
||||
const t = useTranslations('CreateFromReceipt')
|
||||
const isDesktop = useMediaQuery('(min-width: 640px)')
|
||||
|
||||
@@ -70,15 +67,14 @@ function CreateFromReceiptButton_({ groupId }: { groupId: string }) {
|
||||
}
|
||||
description={<>{t('Dialog.description')}</>}
|
||||
>
|
||||
<ReceiptDialogContent groupId={groupId} />
|
||||
<ReceiptDialogContent />
|
||||
</DialogOrDrawer>
|
||||
)
|
||||
}
|
||||
|
||||
function ReceiptDialogContent({ groupId }: { groupId: string }) {
|
||||
const { data: groupData } = trpc.groups.get.useQuery({ groupId })
|
||||
function ReceiptDialogContent() {
|
||||
const { group } = useCurrentGroup()
|
||||
const { data: categoriesData } = trpc.categories.list.useQuery()
|
||||
const group = groupData?.group
|
||||
const categories = categoriesData?.categories
|
||||
|
||||
const locale = useLocale()
|
||||
|
||||
@@ -64,7 +64,7 @@ const enforceCurrencyPattern = (value: string) =>
|
||||
.replace(/[^-\d.]/g, '') // remove all non-numeric characters
|
||||
|
||||
const getDefaultSplittingOptions = (
|
||||
group: AppRouterOutput['groups']['get']['group'],
|
||||
group: NonNullable<AppRouterOutput['groups']['get']['group']>,
|
||||
) => {
|
||||
const defaultValue = {
|
||||
splitMode: 'EVENLY' as const,
|
||||
@@ -145,7 +145,7 @@ export function ExpenseForm({
|
||||
onDelete,
|
||||
runtimeFeatureFlags,
|
||||
}: {
|
||||
group: AppRouterOutput['groups']['get']['group']
|
||||
group: NonNullable<AppRouterOutput['groups']['get']['group']>
|
||||
categories: AppRouterOutput['categories']['list']['categories']
|
||||
expense?: AppRouterOutput['groups']['expenses']['get']['expense']
|
||||
onSubmit: (value: ExpenseFormValues, participantId?: string) => Promise<void>
|
||||
@@ -250,7 +250,6 @@ export function ExpenseForm({
|
||||
>(new Set())
|
||||
|
||||
const sExpense = isIncome ? 'Income' : 'Expense'
|
||||
const sPaid = isIncome ? 'received' : 'paid'
|
||||
|
||||
useEffect(() => {
|
||||
setManuallyEditedParticipants(new Set())
|
||||
|
||||
@@ -11,6 +11,7 @@ import Link from 'next/link'
|
||||
import { forwardRef, useEffect, useMemo, useState } from 'react'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import { useDebounce } from 'use-debounce'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
const PAGE_SIZE = 20
|
||||
|
||||
@@ -56,12 +57,12 @@ function getGroupedExpensesByDate(expenses: ExpensesType) {
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function ExpenseList({ groupId }: { groupId: string }) {
|
||||
const { data: groupData } = trpc.groups.get.useQuery({ groupId })
|
||||
export function ExpenseList() {
|
||||
const { groupId, group } = useCurrentGroup()
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [debouncedSearchText] = useDebounce(searchText, 300)
|
||||
|
||||
const participants = groupData?.group.participants
|
||||
const participants = group?.participants
|
||||
|
||||
useEffect(() => {
|
||||
if (!participants) return
|
||||
@@ -103,6 +104,7 @@ const ExpenseListForSearch = ({
|
||||
searchText: string
|
||||
}) => {
|
||||
const utils = trpc.useUtils()
|
||||
const { group } = useCurrentGroup()
|
||||
|
||||
useEffect(() => {
|
||||
// Until we use tRPC more widely and can invalidate the cache on expense
|
||||
@@ -124,11 +126,7 @@ const ExpenseListForSearch = ({
|
||||
const expenses = data?.pages.flatMap((page) => page.expenses)
|
||||
const hasMore = data?.pages.at(-1)?.hasMore ?? false
|
||||
|
||||
const { data: groupData, isLoading: groupIsLoading } =
|
||||
trpc.groups.get.useQuery({ groupId })
|
||||
|
||||
const isLoading =
|
||||
expensesAreLoading || !expenses || groupIsLoading || !groupData
|
||||
const isLoading = expensesAreLoading || !expenses || !group
|
||||
|
||||
useEffect(() => {
|
||||
if (inView && hasMore && !isLoading) fetchNextPage()
|
||||
@@ -172,7 +170,7 @@ const ExpenseListForSearch = ({
|
||||
<ExpenseCard
|
||||
key={expense.id}
|
||||
expense={expense}
|
||||
currency={groupData.group.currency}
|
||||
currency={group.currency}
|
||||
groupId={groupId}
|
||||
/>
|
||||
))}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Download, Plus } from 'lucide-react'
|
||||
import { Metadata } from 'next'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import Link from 'next/link'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
export const revalidate = 3600
|
||||
|
||||
@@ -23,13 +24,12 @@ export const metadata: Metadata = {
|
||||
}
|
||||
|
||||
export default function GroupExpensesPageClient({
|
||||
groupId,
|
||||
enableReceiptExtract,
|
||||
}: {
|
||||
groupId: string
|
||||
enableReceiptExtract: boolean
|
||||
}) {
|
||||
const t = useTranslations('Expenses')
|
||||
const { groupId } = useCurrentGroup()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -50,9 +50,7 @@ export default function GroupExpensesPageClient({
|
||||
<Download className="w-4 h-4" />
|
||||
</Link>
|
||||
</Button>
|
||||
{enableReceiptExtract && (
|
||||
<CreateFromReceiptButton groupId={groupId} />
|
||||
)}
|
||||
{enableReceiptExtract && <CreateFromReceiptButton />}
|
||||
<Button asChild size="icon">
|
||||
<Link
|
||||
href={`/groups/${groupId}/expenses/create`}
|
||||
@@ -65,7 +63,7 @@ export default function GroupExpensesPageClient({
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 pt-2 pb-4 sm:pb-6 flex flex-col gap-4 relative">
|
||||
<ExpenseList groupId={groupId} />
|
||||
<ExpenseList />
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
|
||||
@@ -8,14 +8,9 @@ export const metadata: Metadata = {
|
||||
title: 'Expenses',
|
||||
}
|
||||
|
||||
export default async function GroupExpensesPage({
|
||||
params: { groupId },
|
||||
}: {
|
||||
params: { groupId: string }
|
||||
}) {
|
||||
export default async function GroupExpensesPage() {
|
||||
return (
|
||||
<GroupExpensesPageClient
|
||||
groupId={groupId}
|
||||
enableReceiptExtract={env.NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -3,27 +3,27 @@
|
||||
import { GroupTabs } from '@/app/groups/[groupId]/group-tabs'
|
||||
import { ShareButton } from '@/app/groups/[groupId]/share-button'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import Link from 'next/link'
|
||||
import { useCurrentGroup } from './current-group-context'
|
||||
|
||||
export const GroupHeader = ({ groupId }: { groupId: string }) => {
|
||||
const { data, isLoading } = trpc.groups.get.useQuery({ groupId })
|
||||
export const GroupHeader = () => {
|
||||
const { isLoading, groupId, group } = useCurrentGroup()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between gap-3">
|
||||
<h1 className="font-bold text-2xl">
|
||||
<Link href={`/groups/${groupId}`}>
|
||||
{isLoading || !data ? (
|
||||
{isLoading ? (
|
||||
<Skeleton className="mt-1.5 mb-1.5 h-5 w-32" />
|
||||
) : (
|
||||
<div className="flex">{data.group.name}</div>
|
||||
<div className="flex">{group.name}</div>
|
||||
)}
|
||||
</Link>
|
||||
</h1>
|
||||
|
||||
<div className="flex gap-2 justify-between">
|
||||
<GroupTabs groupId={groupId} />
|
||||
{data?.group && <ShareButton group={data.group} />}
|
||||
{group && <ShareButton group={group} />}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,14 +9,14 @@ import {
|
||||
CardTitle,
|
||||
} from '@/components/ui/card'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import { Pencil } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import Link from 'next/link'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
export default function GroupInformation({ groupId }: { groupId: string }) {
|
||||
const t = useTranslations('Information')
|
||||
const { data, isLoading } = trpc.groups.get.useQuery({ groupId })
|
||||
const { isLoading, group } = useCurrentGroup()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -35,13 +35,13 @@ export default function GroupInformation({ groupId }: { groupId: string }) {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="prose prose-sm sm:prose-base max-w-full whitespace-break-spaces">
|
||||
{isLoading || !data ? (
|
||||
{isLoading ? (
|
||||
<div className="py-1 flex flex-col gap-2">
|
||||
<Skeleton className="h-3 w-3/4" />
|
||||
<Skeleton className="h-3 w-1/2" />
|
||||
</div>
|
||||
) : data.group.information ? (
|
||||
<p className="text-foreground">{data.group.information}</p>
|
||||
) : group.information ? (
|
||||
<p className="text-foreground">{group.information}</p>
|
||||
) : (
|
||||
<p className="text-muted-foreground text-sm">{t('empty')}</p>
|
||||
)}
|
||||
|
||||
49
src/app/groups/[groupId]/layout.client.tsx
Normal file
49
src/app/groups/[groupId]/layout.client.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
'use client'
|
||||
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { PropsWithChildren, useEffect } from 'react'
|
||||
import { CurrentGroupProvider } from './current-group-context'
|
||||
import { GroupHeader } from './group-header'
|
||||
import { SaveGroupLocally } from './save-recent-group'
|
||||
|
||||
export function GroupLayoutClient({
|
||||
groupId,
|
||||
children,
|
||||
}: PropsWithChildren<{ groupId: string }>) {
|
||||
const { data, isLoading } = trpc.groups.get.useQuery({ groupId })
|
||||
const t = useTranslations('Groups.NotFound')
|
||||
const { toast } = useToast()
|
||||
|
||||
useEffect(() => {
|
||||
if (data && !data.group) {
|
||||
toast({
|
||||
description: t('text'),
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
}, [data])
|
||||
|
||||
const props =
|
||||
isLoading || !data?.group
|
||||
? { isLoading: true as const, groupId, group: undefined }
|
||||
: { isLoading: false as const, groupId, group: data.group }
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<CurrentGroupProvider {...props}>
|
||||
<GroupHeader />
|
||||
{children}
|
||||
</CurrentGroupProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<CurrentGroupProvider {...props}>
|
||||
<GroupHeader />
|
||||
{children}
|
||||
<SaveGroupLocally />
|
||||
</CurrentGroupProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { cached } from '@/app/cached-functions'
|
||||
import { GroupHeader } from '@/app/groups/[groupId]/group-header'
|
||||
import { SaveGroupLocally } from '@/app/groups/[groupId]/save-recent-group'
|
||||
import { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { GroupLayoutClient } from './layout.client'
|
||||
|
||||
type Props = {
|
||||
params: {
|
||||
@@ -24,20 +22,9 @@ export async function generateMetadata({
|
||||
}
|
||||
}
|
||||
|
||||
export default async function GroupLayout({
|
||||
export default function GroupLayout({
|
||||
children,
|
||||
params: { groupId },
|
||||
}: PropsWithChildren<Props>) {
|
||||
const group = await cached.getGroup(groupId)
|
||||
if (!group) notFound()
|
||||
|
||||
return (
|
||||
<>
|
||||
<GroupHeader groupId={groupId} />
|
||||
|
||||
{children}
|
||||
|
||||
<SaveGroupLocally group={{ id: group.id, name: group.name }} />
|
||||
</>
|
||||
)
|
||||
return <GroupLayoutClient groupId={groupId}>{children}</GroupLayoutClient>
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
'use client'
|
||||
import {
|
||||
RecentGroup,
|
||||
saveRecentGroup,
|
||||
} from '@/app/groups/recent-groups-helpers'
|
||||
import { saveRecentGroup } from '@/app/groups/recent-groups-helpers'
|
||||
import { useEffect } from 'react'
|
||||
import { useCurrentGroup } from './current-group-context'
|
||||
|
||||
type Props = {
|
||||
group: RecentGroup
|
||||
}
|
||||
export function SaveGroupLocally() {
|
||||
const { group } = useCurrentGroup()
|
||||
|
||||
export function SaveGroupLocally({ group }: Props) {
|
||||
useEffect(() => {
|
||||
saveRecentGroup(group)
|
||||
if (group) saveRecentGroup({ id: group.id, name: group.name })
|
||||
}, [group])
|
||||
|
||||
return null
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@/components/ui/card'
|
||||
import { useTranslations } from 'next-intl'
|
||||
|
||||
export function TotalsPageClient({ groupId }: { groupId: string }) {
|
||||
export function TotalsPageClient() {
|
||||
const t = useTranslations('Stats')
|
||||
|
||||
return (
|
||||
@@ -19,7 +19,7 @@ export function TotalsPageClient({ groupId }: { groupId: string }) {
|
||||
<CardDescription>{t('Totals.description')}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-col space-y-4">
|
||||
<Totals groupId={groupId} />
|
||||
<Totals />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
@@ -5,10 +5,6 @@ export const metadata: Metadata = {
|
||||
title: 'Totals',
|
||||
}
|
||||
|
||||
export default async function TotalsPage({
|
||||
params: { groupId },
|
||||
}: {
|
||||
params: { groupId: string }
|
||||
}) {
|
||||
return <TotalsPageClient groupId={groupId} />
|
||||
export default async function TotalsPage() {
|
||||
return <TotalsPageClient />
|
||||
}
|
||||
|
||||
@@ -5,16 +5,17 @@ import { TotalsYourSpendings } from '@/app/groups/[groupId]/stats/totals-your-sp
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { useActiveUser } from '@/lib/hooks'
|
||||
import { trpc } from '@/trpc/client'
|
||||
import { useCurrentGroup } from '../current-group-context'
|
||||
|
||||
export function Totals({ groupId }: { groupId: string }) {
|
||||
export function Totals() {
|
||||
const { groupId, group } = useCurrentGroup()
|
||||
const activeUser = useActiveUser(groupId)
|
||||
|
||||
const participantId =
|
||||
activeUser && activeUser !== 'None' ? activeUser : undefined
|
||||
const { data } = trpc.groups.stats.get.useQuery({ groupId, participantId })
|
||||
const { data: groupData } = trpc.groups.get.useQuery({ groupId })
|
||||
|
||||
if (!data || !groupData)
|
||||
if (!data || !group)
|
||||
return (
|
||||
<div className="flex flex-col gap-7">
|
||||
{[0, 1, 2].map((index) => (
|
||||
@@ -31,7 +32,6 @@ export function Totals({ groupId }: { groupId: string }) {
|
||||
totalParticipantShare,
|
||||
totalParticipantSpendings,
|
||||
} = data
|
||||
const { group } = groupData
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user