From 4902271d4be2764537e019159651ed2dee74c535 Mon Sep 17 00:00:00 2001 From: Sebastien Castiel Date: Thu, 14 Dec 2023 11:09:59 -0500 Subject: [PATCH] First attemps at using route interception and modals --- package-lock.json | 125 ++++-- package.json | 3 +- .../(.)expenses/[expenseId]/edit/page.tsx | 26 ++ .../@modal/(.)expenses/create/page.tsx | 24 ++ src/app/groups/[groupId]/@modal/default.tsx | 3 + .../groups/[groupId]/@modal/expense-modal.tsx | 30 ++ .../expenses/[expenseId]/edit/page.tsx | 28 +- src/app/groups/[groupId]/expenses/actions.ts | 28 ++ .../groups/[groupId]/expenses/create/page.tsx | 21 +- .../[groupId]/expenses/expense-page.tsx | 19 + src/app/groups/[groupId]/expenses/layout.tsx | 9 + src/app/groups/[groupId]/layout.tsx | 5 +- src/components/expense-form.tsx | 391 +++++++++--------- src/components/ui/dialog.tsx | 122 ++++++ 14 files changed, 556 insertions(+), 278 deletions(-) create mode 100644 src/app/groups/[groupId]/@modal/(.)expenses/[expenseId]/edit/page.tsx create mode 100644 src/app/groups/[groupId]/@modal/(.)expenses/create/page.tsx create mode 100644 src/app/groups/[groupId]/@modal/default.tsx create mode 100644 src/app/groups/[groupId]/@modal/expense-modal.tsx create mode 100644 src/app/groups/[groupId]/expenses/actions.ts create mode 100644 src/app/groups/[groupId]/expenses/expense-page.tsx create mode 100644 src/app/groups/[groupId]/expenses/layout.tsx create mode 100644 src/components/ui/dialog.tsx diff --git a/package-lock.json b/package-lock.json index 8ffbc2a..a8e729a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@hookform/resolvers": "^3.3.2", "@prisma/client": "5.6.0", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", @@ -24,7 +25,7 @@ "clsx": "^2.0.0", "lucide-react": "^0.290.0", "nanoid": "^5.0.4", - "next": "^14.0.4", + "next": "^14.0.5-canary.12", "next-plausible": "^3.12.0", "next-themes": "^0.2.1", "next13-progressbar": "^1.1.1", @@ -279,9 +280,9 @@ } }, "node_modules/@next/env": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.4.tgz", - "integrity": "sha512-irQnbMLbUNQpP1wcE5NstJtbuA/69kRfzBrpAD7Gsn8zm/CY6YQYc3HQBz8QPxwISG26tIm5afvvVbu508oBeQ==" + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.0.5-canary.12.tgz", + "integrity": "sha512-YzePdvd+GcrANTHqDkoNx97YN7EYUGAUrEt4F+h30wD8MEW+QM4EhAHxL3JOUTSkSF0zmwrTyTtSvlGdlqUQiA==" }, "node_modules/@next/eslint-plugin-next": { "version": "14.0.4", @@ -313,9 +314,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.4.tgz", - "integrity": "sha512-mF05E/5uPthWzyYDyptcwHptucf/jj09i2SXBPwNzbgBNc+XnwzrL0U6BmPjQeOL+FiB+iG1gwBeq7mlDjSRPg==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.0.5-canary.12.tgz", + "integrity": "sha512-MQtCDCP/A4VOuHfTUjZMaSxBwJj5/K+l7ww+W5apI7KutJjtdbAxli1FF6kbgVQST47mdn2mHDsMGhBmt3sjpg==", "cpu": [ "arm64" ], @@ -328,9 +329,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.4.tgz", - "integrity": "sha512-IZQ3C7Bx0k2rYtrZZxKKiusMTM9WWcK5ajyhOZkYYTCc8xytmwSzR1skU7qLgVT/EY9xtXDG0WhY6fyujnI3rw==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.0.5-canary.12.tgz", + "integrity": "sha512-H482SA2gpel5FpJDEE2W/L+ZHHcNg1a6tIzluzTmCJ7OTFMgU8/SifGORHHKjKk2eH3oDmbKn8ClXs9UBYQClA==", "cpu": [ "x64" ], @@ -343,9 +344,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.4.tgz", - "integrity": "sha512-VwwZKrBQo/MGb1VOrxJ6LrKvbpo7UbROuyMRvQKTFKhNaXjUmKTu7wxVkIuCARAfiI8JpaWAnKR+D6tzpCcM4w==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.0.5-canary.12.tgz", + "integrity": "sha512-Wuyg36CDBFFrWO8gctSHdGQym7f4hSK5cLJxsPjvvzP+tXL4YVR8Gw7Za+mqJFEWmZ8OQRWwp4ZWgLt5GaRsyg==", "cpu": [ "arm64" ], @@ -358,9 +359,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.4.tgz", - "integrity": "sha512-8QftwPEW37XxXoAwsn+nXlodKWHfpMaSvt81W43Wh8dv0gkheD+30ezWMcFGHLI71KiWmHK5PSQbTQGUiidvLQ==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.0.5-canary.12.tgz", + "integrity": "sha512-rL+vKUcp8NWv19wQ6VqoQiWKtXc4Eztm1SoD1cjtuyNb1D0tllNADxubvkdzu25VXlolwjJvA6OXAmOZW1NTpw==", "cpu": [ "arm64" ], @@ -373,9 +374,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.4.tgz", - "integrity": "sha512-/s/Pme3VKfZAfISlYVq2hzFS8AcAIOTnoKupc/j4WlvF6GQ0VouS2Q2KEgPuO1eMBwakWPB1aYFIA4VNVh667A==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.0.5-canary.12.tgz", + "integrity": "sha512-hT3XMWZbaArw5/Ri9DyWqXt4hhxsJgAg1QajDnm98uWBQrBWuganRFpmm8VahpNjEIt+qkzQ1c+A8BdQnyQ8+A==", "cpu": [ "x64" ], @@ -388,9 +389,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.4.tgz", - "integrity": "sha512-m8z/6Fyal4L9Bnlxde5g2Mfa1Z7dasMQyhEhskDATpqr+Y0mjOBZcXQ7G5U+vgL22cI4T7MfvgtrM2jdopqWaw==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.0.5-canary.12.tgz", + "integrity": "sha512-Z5x1WbyG/oqKaWbpVTCJW+SdB9GQLeC6y1Bmzwzw2P0LY4fzKtho8f+/8iB+cXzpMA0vfj2DWMs7CBgHfbCFpw==", "cpu": [ "x64" ], @@ -403,9 +404,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.4.tgz", - "integrity": "sha512-7Wv4PRiWIAWbm5XrGz3D8HUkCVDMMz9igffZG4NB1p4u1KoItwx9qjATHz88kwCEal/HXmbShucaslXCQXUM5w==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.0.5-canary.12.tgz", + "integrity": "sha512-biqb28j9VJUaLKBtGFzbRjpaLWcp/SYCxRmoaK2VZZ97Kycpwp4wKaa8R9gFF+uHq0eqG27LN/NQ6PwuHc+TXQ==", "cpu": [ "arm64" ], @@ -418,9 +419,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.4.tgz", - "integrity": "sha512-zLeNEAPULsl0phfGb4kdzF/cAVIfaC7hY+kt0/d+y9mzcZHsMS3hAS829WbJ31DkSlVKQeHEjZHIdhN+Pg7Gyg==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.0.5-canary.12.tgz", + "integrity": "sha512-vfvcqfPms3/yoaWhSEQfCsMjtymPEiiaciIo1C263deJOIstZBKuuPe5wv4QnccHoK02i74vjNl5zN1bNJ/eWw==", "cpu": [ "ia32" ], @@ -433,9 +434,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.4.tgz", - "integrity": "sha512-yEh2+R8qDlDCjxVpzOTEpBLQTEFAcP2A8fUFLaWNap9GitYKkKv1//y2S6XY6zsR4rCOPRpU7plYDR+az2n30A==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.0.5-canary.12.tgz", + "integrity": "sha512-39FcDa/pP7fXL17PB9hIcbMNsfr1ew5RwvZEtKy1+qmuqXA/sApZt3Kbm3D/ggWP/bfHVRhfq5QKp8X4trADjw==", "cpu": [ "x64" ], @@ -688,6 +689,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -4198,11 +4235,11 @@ "license": "MIT" }, "node_modules/next": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/next/-/next-14.0.4.tgz", - "integrity": "sha512-qbwypnM7327SadwFtxXnQdGiKpkuhaRLE2uq62/nRul9cj9KhQ5LhHmlziTNqUidZotw/Q1I9OjirBROdUJNgA==", + "version": "14.0.5-canary.12", + "resolved": "https://registry.npmjs.org/next/-/next-14.0.5-canary.12.tgz", + "integrity": "sha512-+w3vbb8VRFvDM8NxB5XkqkOmS+Ox9zCi6P+Ww+oolsM5TLMxA8t0sh4y+BETEpFjdwq8BgewFIBk9tudvz5xkA==", "dependencies": { - "@next/env": "14.0.4", + "@next/env": "14.0.5-canary.12", "@swc/helpers": "0.5.2", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001406", @@ -4218,15 +4255,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.0.4", - "@next/swc-darwin-x64": "14.0.4", - "@next/swc-linux-arm64-gnu": "14.0.4", - "@next/swc-linux-arm64-musl": "14.0.4", - "@next/swc-linux-x64-gnu": "14.0.4", - "@next/swc-linux-x64-musl": "14.0.4", - "@next/swc-win32-arm64-msvc": "14.0.4", - "@next/swc-win32-ia32-msvc": "14.0.4", - "@next/swc-win32-x64-msvc": "14.0.4" + "@next/swc-darwin-arm64": "14.0.5-canary.12", + "@next/swc-darwin-x64": "14.0.5-canary.12", + "@next/swc-linux-arm64-gnu": "14.0.5-canary.12", + "@next/swc-linux-arm64-musl": "14.0.5-canary.12", + "@next/swc-linux-x64-gnu": "14.0.5-canary.12", + "@next/swc-linux-x64-musl": "14.0.5-canary.12", + "@next/swc-win32-arm64-msvc": "14.0.5-canary.12", + "@next/swc-win32-ia32-msvc": "14.0.5-canary.12", + "@next/swc-win32-x64-msvc": "14.0.5-canary.12" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/package.json b/package.json index 7fe84fc..8bf0202 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@hookform/resolvers": "^3.3.2", "@prisma/client": "5.6.0", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-hover-card": "^1.0.7", "@radix-ui/react-icons": "^1.3.0", @@ -25,7 +26,7 @@ "clsx": "^2.0.0", "lucide-react": "^0.290.0", "nanoid": "^5.0.4", - "next": "^14.0.4", + "next": "^14.0.5-canary.12", "next-plausible": "^3.12.0", "next-themes": "^0.2.1", "next13-progressbar": "^1.1.1", diff --git a/src/app/groups/[groupId]/@modal/(.)expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/@modal/(.)expenses/[expenseId]/edit/page.tsx new file mode 100644 index 0000000..fb70f45 --- /dev/null +++ b/src/app/groups/[groupId]/@modal/(.)expenses/[expenseId]/edit/page.tsx @@ -0,0 +1,26 @@ +import { ExpenseModal } from '@/app/groups/[groupId]/@modal/expense-modal' +import { ExpenseForm } from '@/components/expense-form' +import { getExpense, getGroup } from '@/lib/api' +import { Metadata } from 'next' +import { notFound } from 'next/navigation' + +export const metadata: Metadata = { + title: 'Edit expense', +} + +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() + + return ( + + + + ) +} diff --git a/src/app/groups/[groupId]/@modal/(.)expenses/create/page.tsx b/src/app/groups/[groupId]/@modal/(.)expenses/create/page.tsx new file mode 100644 index 0000000..93cc413 --- /dev/null +++ b/src/app/groups/[groupId]/@modal/(.)expenses/create/page.tsx @@ -0,0 +1,24 @@ +import { ExpenseModal } from '@/app/groups/[groupId]/@modal/expense-modal' +import { ExpenseForm } from '@/components/expense-form' +import { getGroup } from '@/lib/api' +import { Metadata } from 'next' +import { notFound } from 'next/navigation' + +export const metadata: Metadata = { + title: 'Create expense', +} + +export default async function ExpensePage({ + params: { groupId }, +}: { + params: { groupId: string } +}) { + const group = await getGroup(groupId) + if (!group) notFound() + + return ( + + + + ) +} diff --git a/src/app/groups/[groupId]/@modal/default.tsx b/src/app/groups/[groupId]/@modal/default.tsx new file mode 100644 index 0000000..86b9e9a --- /dev/null +++ b/src/app/groups/[groupId]/@modal/default.tsx @@ -0,0 +1,3 @@ +export default function Default() { + return null +} diff --git a/src/app/groups/[groupId]/@modal/expense-modal.tsx b/src/app/groups/[groupId]/@modal/expense-modal.tsx new file mode 100644 index 0000000..1470ef2 --- /dev/null +++ b/src/app/groups/[groupId]/@modal/expense-modal.tsx @@ -0,0 +1,30 @@ +'use client' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { useRouter } from 'next/navigation' +import { ReactNode } from 'react' + +export function ExpenseModal({ + children, + title, +}: { + children: ReactNode + title: ReactNode +}) { + const router = useRouter() + + return ( + router.back()}> + + + {title} + + {children} + + + ) +} diff --git a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx index 188d08f..aeefaa5 100644 --- a/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx +++ b/src/app/groups/[groupId]/expenses/[expenseId]/edit/page.tsx @@ -1,8 +1,8 @@ +import { ExpensePage } from '@/app/groups/[groupId]/expenses/expense-page' import { ExpenseForm } from '@/components/expense-form' -import { deleteExpense, getExpense, getGroup, updateExpense } from '@/lib/api' -import { expenseFormSchema } from '@/lib/schemas' +import { getExpense, getGroup } from '@/lib/api' import { Metadata } from 'next' -import { notFound, redirect } from 'next/navigation' +import { notFound } from 'next/navigation' export const metadata: Metadata = { title: 'Edit expense', @@ -18,25 +18,9 @@ export default async function EditExpensePage({ 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}`) - } - - async function deleteExpenseAction() { - 'use server' - await deleteExpense(expenseId) - redirect(`/groups/${groupId}`) - } - return ( - + + + ) } diff --git a/src/app/groups/[groupId]/expenses/actions.ts b/src/app/groups/[groupId]/expenses/actions.ts new file mode 100644 index 0000000..f39e477 --- /dev/null +++ b/src/app/groups/[groupId]/expenses/actions.ts @@ -0,0 +1,28 @@ +'use server' +import { createExpense, deleteExpense, updateExpense } from '@/lib/api' +import { expenseFormSchema } from '@/lib/schemas' +import { redirect } from 'next/navigation' + +export async function createExpenseAction(groupId: string, values: unknown) { + 'use server' + const expenseFormValues = expenseFormSchema.parse(values) + await createExpense(expenseFormValues, groupId) + redirect(`/groups/${groupId}`) +} + +export async function updateExpenseAction( + groupId: string, + expenseId: string, + values: unknown, +) { + 'use server' + const expenseFormValues = expenseFormSchema.parse(values) + await updateExpense(groupId, expenseId, expenseFormValues) + redirect(`/groups/${groupId}`) +} + +export async function deleteExpenseAction(groupId: string, expenseId: string) { + 'use server' + await deleteExpense(expenseId) + redirect(`/groups/${groupId}`) +} diff --git a/src/app/groups/[groupId]/expenses/create/page.tsx b/src/app/groups/[groupId]/expenses/create/page.tsx index e603e59..87f9c5b 100644 --- a/src/app/groups/[groupId]/expenses/create/page.tsx +++ b/src/app/groups/[groupId]/expenses/create/page.tsx @@ -1,14 +1,14 @@ +import { ExpensePage } from '@/app/groups/[groupId]/expenses/expense-page' import { ExpenseForm } from '@/components/expense-form' -import { createExpense, getGroup } from '@/lib/api' -import { expenseFormSchema } from '@/lib/schemas' +import { getGroup } from '@/lib/api' import { Metadata } from 'next' -import { notFound, redirect } from 'next/navigation' +import { notFound } from 'next/navigation' export const metadata: Metadata = { title: 'Create expense', } -export default async function ExpensePage({ +export default async function CreateExpensePage({ params: { groupId }, }: { params: { groupId: string } @@ -16,12 +16,9 @@ export default async function ExpensePage({ 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 + return ( + + + + ) } diff --git a/src/app/groups/[groupId]/expenses/expense-page.tsx b/src/app/groups/[groupId]/expenses/expense-page.tsx new file mode 100644 index 0000000..281c38b --- /dev/null +++ b/src/app/groups/[groupId]/expenses/expense-page.tsx @@ -0,0 +1,19 @@ +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' +import { ReactNode } from 'react' + +export function ExpensePage({ + children, + title, +}: { + children: ReactNode + title: ReactNode +}) { + return ( + + + {title} + + {children} + + ) +} diff --git a/src/app/groups/[groupId]/expenses/layout.tsx b/src/app/groups/[groupId]/expenses/layout.tsx new file mode 100644 index 0000000..cd65df9 --- /dev/null +++ b/src/app/groups/[groupId]/expenses/layout.tsx @@ -0,0 +1,9 @@ +import { ReactNode } from 'react' + +export default function GroupExpensesLayout({ + children, +}: { + children: ReactNode +}) { + return <>{children} +} diff --git a/src/app/groups/[groupId]/layout.tsx b/src/app/groups/[groupId]/layout.tsx index 3e649e7..40f6155 100644 --- a/src/app/groups/[groupId]/layout.tsx +++ b/src/app/groups/[groupId]/layout.tsx @@ -5,12 +5,13 @@ import { getGroup } from '@/lib/api' import { Metadata } from 'next' import Link from 'next/link' import { notFound } from 'next/navigation' -import { PropsWithChildren } from 'react' +import { PropsWithChildren, ReactNode } from 'react' type Props = { params: { groupId: string } + modal: ReactNode } export async function generateMetadata({ @@ -28,6 +29,7 @@ export async function generateMetadata({ export default async function GroupLayout({ children, + modal, params: { groupId }, }: PropsWithChildren) { const group = await getGroup(groupId) @@ -47,6 +49,7 @@ export default async function GroupLayout({ {children} + {modal} diff --git a/src/components/expense-form.tsx b/src/components/expense-form.tsx index bdee9d0..b4cf150 100644 --- a/src/components/expense-form.tsx +++ b/src/components/expense-form.tsx @@ -1,14 +1,12 @@ 'use client' +import { + createExpenseAction, + deleteExpenseAction, + updateExpenseAction, +} from '@/app/groups/[groupId]/expenses/actions' import { AsyncButton } from '@/components/async-button' import { SubmitButton } from '@/components/submit-button' import { Button } from '@/components/ui/button' -import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from '@/components/ui/card' import { Checkbox } from '@/components/ui/checkbox' import { Form, @@ -36,11 +34,9 @@ import { useForm } from 'react-hook-form' export type Props = { group: NonNullable>> expense?: NonNullable>> - onSubmit: (values: ExpenseFormValues) => Promise - onDelete?: () => Promise } -export function ExpenseForm({ group, expense, onSubmit, onDelete }: Props) { +export function ExpenseForm({ group, expense }: Props) { const isCreate = expense === undefined const searchParams = useSearchParams() const form = useForm({ @@ -68,201 +64,200 @@ export function ExpenseForm({ group, expense, onSubmit, onDelete }: Props) { return (
- onSubmit(values))}> - - - - {isCreate ? <>Create expense : <>Edit expense} - - - - ( - - Expense title + + expense + ? updateExpenseAction(group.id, expense.id, values) + : createExpenseAction(group.id, values), + )} + > +
+ ( + + Expense title + + + + + Enter a description for the expense. + + + + )} + /> + + ( + + Paid by + + + Select the participant who paid the expense. + + + + )} + /> + + ( + + Amount +
+ {group.currency} - - Enter a description for the expense. - - - - )} - /> +
+ - ( - - Paid by - - - Select the participant who paid the expense. - - - - )} - /> - - ( - - Amount -
- {group.currency} - - - -
- - - ( - - - - -
- This is a reimbursement -
-
- )} - /> -
- )} - /> - - ( - -
- - Paid for - - - - Select who the expense was paid for. - -
- {group.participants.map(({ id, name }) => ( - { - return ( - - - { - return checked - ? field.onChange([...field.value, id]) - : field.onChange( - field.value?.filter( - (value) => value !== id, - ), - ) - }} - /> - - - {name} - - - ) - }} - /> - ))} - -
- )} - /> - - - - Creating… : <>Saving…} - > - {isCreate ? <>Create : <>Save} - - {!isCreate && onDelete && ( - - Delete - + ( + + + + +
+ This is a reimbursement +
+
+ )} + /> +
)} - - + /> + + ( + +
+ + Paid for + + + + Select who the expense was paid for. + +
+ {group.participants.map(({ id, name }) => ( + { + return ( + + + { + return checked + ? field.onChange([...field.value, id]) + : field.onChange( + field.value?.filter( + (value) => value !== id, + ), + ) + }} + /> + + + {name} + + + ) + }} + /> + ))} + +
+ )} + /> +
+ +
+ Creating… : <>Saving…} + > + {isCreate ? <>Create : <>Save} + + {!isCreate && ( + deleteExpenseAction(group.id, expense.id)} + > + Delete + + )} +
) diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..cad6f58 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,122 @@ +"use client" + +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}