|
1 | | -'use client'; |
2 | | - |
3 | | -import { useParams } from 'next/navigation'; |
4 | | -import { useEffect, useState } from 'react'; |
5 | | -import { apiFetch } from '../../../../lib/api'; |
| 1 | +import { DashboardLayout } from '../../../../src/components/layout'; |
| 2 | +import { backendFetch } from '../../../../src/lib/backend'; |
6 | 3 |
|
7 | 4 | type Balance = { id: string; userId: string; counterpartyUserId: string; amountCents: string }; |
8 | | -type Expense = { id: string; description: string; totalAmountCents: string; createdAt: string }; |
9 | 5 |
|
10 | | -export default function DashboardGroupPage() { |
11 | | - const params = useParams<{ id: string }>(); |
| 6 | +type Expense = { |
| 7 | + id: string; |
| 8 | + description: string; |
| 9 | + totalAmountCents: string; |
| 10 | + createdAt: string; |
| 11 | + payerId: string; |
| 12 | +}; |
| 13 | + |
| 14 | +type ExpensesResponse = { items: Expense[]; nextCursor: number | null }; |
| 15 | + |
| 16 | +type PageProps = { params: { id: string } }; |
| 17 | + |
| 18 | +export default async function DashboardGroupPage({ params }: PageProps) { |
12 | 19 | const groupId = params.id; |
13 | | - const [balances, setBalances] = useState<Balance[]>([]); |
14 | | - const [expenses, setExpenses] = useState<Expense[]>([]); |
15 | | - |
16 | | - useEffect(() => { |
17 | | - if (!groupId) { |
18 | | - return; |
19 | | - } |
20 | | - void apiFetch<Balance[]>(`/groups/${groupId}/balances`).then(setBalances).catch(() => setBalances([])); |
21 | | - void apiFetch<{ items: Expense[] }>(`/groups/${groupId}/expenses?cursor=0&limit=20`) |
22 | | - .then((data) => setExpenses(data.items)) |
23 | | - .catch(() => setExpenses([])); |
24 | | - }, [groupId]); |
| 20 | + |
| 21 | + let balances: Balance[] = []; |
| 22 | + let expenses: Expense[] = []; |
| 23 | + |
| 24 | + try { |
| 25 | + balances = await backendFetch<Balance[]>(`/groups/${groupId}/balances`); |
| 26 | + } catch { |
| 27 | + balances = []; |
| 28 | + } |
| 29 | + |
| 30 | + try { |
| 31 | + const data = await backendFetch<ExpensesResponse>(`/groups/${groupId}/expenses?cursor=0&limit=20`); |
| 32 | + expenses = data.items; |
| 33 | + } catch { |
| 34 | + expenses = []; |
| 35 | + } |
25 | 36 |
|
26 | 37 | return ( |
27 | | - <main className="mx-auto max-w-5xl px-6 py-10"> |
28 | | - <h1 className="text-3xl font-bold">Group Dashboard</h1> |
29 | | - <p className="mt-2 text-slate-600">{groupId}</p> |
30 | | - |
31 | | - <section className="mt-6"> |
32 | | - <h2 className="text-xl font-semibold">Balances</h2> |
33 | | - <div className="mt-3 space-y-2"> |
34 | | - {balances.map((balance) => ( |
35 | | - <div key={balance.id} className="rounded border bg-white p-3"> |
36 | | - {balance.userId} vs {balance.counterpartyUserId}: $ |
37 | | - {(Number(balance.amountCents) / 100).toFixed(2)} |
38 | | - </div> |
39 | | - ))} |
| 38 | + <DashboardLayout> |
| 39 | + <div className="rounded-2xl border border-border bg-card p-5 shadow-glass backdrop-blur-glass"> |
| 40 | + <p className="text-sm text-text-secondary">Group</p> |
| 41 | + <p className="mt-1 font-semibold text-text-primary">{groupId}</p> |
| 42 | + </div> |
| 43 | + |
| 44 | + <section className="grid gap-4 md:grid-cols-2"> |
| 45 | + <div className="rounded-2xl border border-border bg-card p-5 shadow-glass backdrop-blur-glass"> |
| 46 | + <h2 className="text-base font-semibold text-text-primary">Balances</h2> |
| 47 | + <div className="mt-3 space-y-2"> |
| 48 | + {balances.map((balance) => ( |
| 49 | + <div key={balance.id} className="rounded-xl border border-border/60 bg-surface/20 p-3 text-sm"> |
| 50 | + <span className="text-text-secondary">{balance.userId}</span> <span className="text-text-secondary">vs</span>{' '} |
| 51 | + <span className="text-text-secondary">{balance.counterpartyUserId}</span> |
| 52 | + <span className="ml-2 font-medium text-text-primary"> |
| 53 | + ${(Number(balance.amountCents) / 100).toFixed(2)} |
| 54 | + </span> |
| 55 | + </div> |
| 56 | + ))} |
| 57 | + {balances.length === 0 ? <p className="text-sm text-text-secondary">No balances found.</p> : null} |
| 58 | + </div> |
40 | 59 | </div> |
41 | | - </section> |
42 | 60 |
|
43 | | - <section className="mt-8"> |
44 | | - <h2 className="text-xl font-semibold">Recent Expenses</h2> |
45 | | - <div className="mt-3 space-y-2"> |
46 | | - {expenses.map((expense) => ( |
47 | | - <div key={expense.id} className="rounded border bg-white p-3"> |
48 | | - <p className="font-medium">{expense.description}</p> |
49 | | - <p className="text-sm text-slate-600">${(Number(expense.totalAmountCents) / 100).toFixed(2)}</p> |
50 | | - </div> |
51 | | - ))} |
| 61 | + <div className="rounded-2xl border border-border bg-card p-5 shadow-glass backdrop-blur-glass"> |
| 62 | + <h2 className="text-base font-semibold text-text-primary">Recent expenses</h2> |
| 63 | + <div className="mt-3 space-y-2"> |
| 64 | + {expenses.map((expense) => ( |
| 65 | + <div key={expense.id} className="rounded-xl border border-border/60 bg-surface/20 p-3"> |
| 66 | + <p className="text-sm font-medium text-text-primary">{expense.description}</p> |
| 67 | + <p className="mt-1 text-sm text-text-secondary"> |
| 68 | + ${(Number(expense.totalAmountCents) / 100).toFixed(2)} |
| 69 | + </p> |
| 70 | + </div> |
| 71 | + ))} |
| 72 | + {expenses.length === 0 ? <p className="text-sm text-text-secondary">No expenses yet.</p> : null} |
| 73 | + </div> |
52 | 74 | </div> |
53 | 75 | </section> |
54 | | - </main> |
| 76 | + </DashboardLayout> |
55 | 77 | ); |
56 | 78 | } |
0 commit comments