From ed55c696cd083bfaa5429082a9c5edd4ebde0cd8 Mon Sep 17 00:00:00 2001 From: Sebastien Castiel Date: Tue, 5 Dec 2023 17:39:05 -0500 Subject: First version --- src/app/groups/[groupId]/edit/page.tsx | 36 ++++++ .../[groupId]/expenses/[expenseId]/edit/page.tsx | 42 ++++++ src/app/groups/[groupId]/expenses/create/page.tsx | 37 ++++++ src/app/groups/[groupId]/page.tsx | 141 +++++++++++++++++++++ src/app/groups/[groupId]/save-recent-group.tsx | 18 +++ src/app/groups/create/page.tsx | 29 +++++ src/app/groups/page.tsx | 14 ++ src/app/groups/recent-group-list.tsx | 43 +++++++ src/app/groups/recent-groups-helpers.ts | 30 +++++ 9 files changed, 390 insertions(+) create mode 100644 src/app/groups/[groupId]/edit/page.tsx create mode 100644 src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx create mode 100644 src/app/groups/[groupId]/expenses/create/page.tsx create mode 100644 src/app/groups/[groupId]/page.tsx create mode 100644 src/app/groups/[groupId]/save-recent-group.tsx create mode 100644 src/app/groups/create/page.tsx create mode 100644 src/app/groups/page.tsx create mode 100644 src/app/groups/recent-group-list.tsx create mode 100644 src/app/groups/recent-groups-helpers.ts (limited to 'src/app/groups') diff --git a/src/app/groups/[groupId]/edit/page.tsx b/src/app/groups/[groupId]/edit/page.tsx new file mode 100644 index 0000000..23bd865 --- /dev/null +++ b/src/app/groups/[groupId]/edit/page.tsx @@ -0,0 +1,36 @@ +import { GroupForm } from '@/components/group-form' +import { Button } from '@/components/ui/button' +import { getGroup, updateGroup } from '@/lib/api' +import { groupFormSchema } from '@/lib/schemas' +import { ChevronLeft } from 'lucide-react' +import Link from 'next/link' +import { notFound, redirect } from 'next/navigation' + +export default async function EditGroupPage({ + params: { groupId }, +}: { + params: { groupId: string } +}) { + const group = await getGroup(groupId) + if (!group) notFound() + + async function updateGroupAction(values: unknown) { + 'use server' + const groupFormValues = groupFormSchema.parse(values) + const group = await updateGroup(groupId, groupFormValues) + redirect(`/groups/${group.id}`) + } + + return ( +
+
+ +
+ +
+ ) +} diff --git a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx new file mode 100644 index 0000000..f22ac08 --- /dev/null +++ b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx @@ -0,0 +1,42 @@ +import { ExpenseForm } from '@/components/expense-form' +import { Button } from '@/components/ui/button' +import { getExpense, getGroup, updateExpense } from '@/lib/api' +import { expenseFormSchema } from '@/lib/schemas' +import { ChevronLeft } from 'lucide-react' +import Link from 'next/link' +import { notFound, redirect } from 'next/navigation' + +export default async function EditExpensePage({ + params: { groupId, expenseId }, +}: { + params: { groupId: string; expenseId: string } +}) { + const group = await getGroup(groupId) + if (!group) notFound() + const expense = await getExpense(groupId, expenseId) + if (!expense) notFound() + + async function updateExpenseAction(values: unknown) { + 'use server' + const expenseFormValues = expenseFormSchema.parse(values) + await updateExpense(groupId, expenseId, expenseFormValues) + redirect(`/groups/${groupId}`) + } + + return ( +
+
+ +
+ +
+ ) +} diff --git a/src/app/groups/[groupId]/expenses/create/page.tsx b/src/app/groups/[groupId]/expenses/create/page.tsx new file mode 100644 index 0000000..f8d0ff9 --- /dev/null +++ b/src/app/groups/[groupId]/expenses/create/page.tsx @@ -0,0 +1,37 @@ +import { ExpenseForm } from '@/components/expense-form' +import { Button } from '@/components/ui/button' +import { createExpense, getGroup } from '@/lib/api' +import { expenseFormSchema } from '@/lib/schemas' +import { ChevronLeft } from 'lucide-react' +import Link from 'next/link' +import { notFound, redirect } from 'next/navigation' + +export default async function ExpensePage({ + params: { groupId }, +}: { + params: { groupId: string } +}) { + const group = await getGroup(groupId) + if (!group) notFound() + + async function createExpenseAction(values: unknown) { + 'use server' + const expenseFormValues = expenseFormSchema.parse(values) + await createExpense(expenseFormValues, groupId) + redirect(`/groups/${groupId}`) + } + + return ( +
+
+ +
+ + +
+ ) +} diff --git a/src/app/groups/[groupId]/page.tsx b/src/app/groups/[groupId]/page.tsx new file mode 100644 index 0000000..1577d7a --- /dev/null +++ b/src/app/groups/[groupId]/page.tsx @@ -0,0 +1,141 @@ +import { SaveGroupLocally } from '@/app/groups/[groupId]/save-recent-group' +import { Badge } from '@/components/ui/badge' +import { Button } from '@/components/ui/button' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@/components/ui/card' +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@/components/ui/table' +import { getGroup, getGroupExpenses } from '@/lib/api' +import { ChevronLeft, ChevronRight, Edit, Plus } from 'lucide-react' +import Link from 'next/link' +import { notFound } from 'next/navigation' + +export default async function GroupPage({ + params: { groupId }, +}: { + params: { groupId: string } +}) { + const group = await getGroup(groupId) + if (!group) notFound() + + const expenses = await getGroupExpenses(groupId) + + return ( +
+
+ + +
+ +

{group.name}

+ + +
+ + Expenses + + Here are the expenses that you created for your group. + + + + + +
+ + + + + Title + Paid by + Paid for + Amount + + + + + {expenses.map((expense) => ( + + {expense.title} + + + { + group.participants.find( + (p) => p.id === expense.paidById, + )?.name + } + + + + {expense.paidFor.map((paidFor, index) => ( + + { + group.participants.find( + (p) => p.id === paidFor.participantId, + )?.name + } + + ))} + + + $ {expense.amount.toFixed(2)} + + + + + + ))} + +
+
+
+ + + + Participants + + +
    + {group.participants.map((participant) => ( +
  • {participant.name}
  • + ))} +
+
+
+ +
+ ) +} diff --git a/src/app/groups/[groupId]/save-recent-group.tsx b/src/app/groups/[groupId]/save-recent-group.tsx new file mode 100644 index 0000000..e55d7c8 --- /dev/null +++ b/src/app/groups/[groupId]/save-recent-group.tsx @@ -0,0 +1,18 @@ +'use client' +import { + RecentGroup, + saveRecentGroup, +} from '@/app/groups/recent-groups-helpers' +import { useEffect } from 'react' + +type Props = { + group: RecentGroup +} + +export function SaveGroupLocally({ group }: Props) { + useEffect(() => { + saveRecentGroup(group) + }, []) + + return null +} diff --git a/src/app/groups/create/page.tsx b/src/app/groups/create/page.tsx new file mode 100644 index 0000000..4f220d2 --- /dev/null +++ b/src/app/groups/create/page.tsx @@ -0,0 +1,29 @@ +import { GroupForm } from '@/components/group-form' +import { Button } from '@/components/ui/button' +import { createGroup } from '@/lib/api' +import { groupFormSchema } from '@/lib/schemas' +import { ChevronLeft } from 'lucide-react' +import Link from 'next/link' +import { redirect } from 'next/navigation' + +export default function CreateGroupPage() { + async function createGroupAction(values: unknown) { + 'use server' + const groupFormValues = groupFormSchema.parse(values) + const group = await createGroup(groupFormValues) + redirect(`/groups/${group.id}`) + } + + return ( +
+
+ +
+ +
+ ) +} diff --git a/src/app/groups/page.tsx b/src/app/groups/page.tsx new file mode 100644 index 0000000..7e4f484 --- /dev/null +++ b/src/app/groups/page.tsx @@ -0,0 +1,14 @@ +import { RecentGroupList } from '@/app/groups/recent-group-list' +import { Button } from '@/components/ui/button' +import Link from 'next/link' + +export default async function GroupsPage() { + return ( +
+ + +
+ ) +} diff --git a/src/app/groups/recent-group-list.tsx b/src/app/groups/recent-group-list.tsx new file mode 100644 index 0000000..e728193 --- /dev/null +++ b/src/app/groups/recent-group-list.tsx @@ -0,0 +1,43 @@ +'use client' +import { getRecentGroups } from '@/app/groups/recent-groups-helpers' +import { Button } from '@/components/ui/button' +import Link from 'next/link' +import { useEffect, useState } from 'react' +import { z } from 'zod' + +const recentGroupsSchema = z.array( + z.object({ + id: z.string().min(1), + name: z.string(), + }), +) +type RecentGroups = z.infer + +type State = { status: 'pending' } | { status: 'success'; groups: RecentGroups } + +export function RecentGroupList() { + const [state, setState] = useState({ status: 'pending' }) + + useEffect(() => { + const groupsInStorage = getRecentGroups() + setState({ status: 'success', groups: groupsInStorage }) + }, []) + + return ( +
    + {state.status === 'pending' ? ( +
  • + Loading recent groups… +
  • + ) : ( + state.groups.map(({ id, name }) => ( +
  • + +
  • + )) + )} +
+ ) +} diff --git a/src/app/groups/recent-groups-helpers.ts b/src/app/groups/recent-groups-helpers.ts new file mode 100644 index 0000000..5d52141 --- /dev/null +++ b/src/app/groups/recent-groups-helpers.ts @@ -0,0 +1,30 @@ +import { z } from 'zod' + +export const recentGroupsSchema = z.array( + z.object({ + id: z.string().min(1), + name: z.string(), + }), +) + +export type RecentGroups = z.infer +export type RecentGroup = RecentGroups[number] + +const STORAGE_KEY = 'recentGroups' + +export function getRecentGroups() { + const groupsInStorageJson = localStorage.getItem(STORAGE_KEY) + const groupsInStorageRaw = groupsInStorageJson + ? JSON.parse(groupsInStorageJson) + : [] + const parseResult = recentGroupsSchema.safeParse(groupsInStorageRaw) + return parseResult.success ? parseResult.data : [] +} + +export function saveRecentGroup(group: RecentGroup) { + const recentGroups = getRecentGroups() + localStorage.setItem( + STORAGE_KEY, + JSON.stringify([group, ...recentGroups.filter((rg) => rg.id !== group.id)]), + ) +} -- cgit v1.3