diff --git a/app/routes/expenses/$expenseId/edit.tsx b/app/routes/expenses/$expenseId/edit.tsx new file mode 100644 index 0000000..9fde247 --- /dev/null +++ b/app/routes/expenses/$expenseId/edit.tsx @@ -0,0 +1,255 @@ +import { Expense } from "@prisma/client"; +import type { ActionFunction, LoaderFunction } from "remix"; +import { + useActionData, + redirect, + json, + useCatch, + Link, + Form, + useTransition, + useLoaderData, +} from "remix"; +import { db } from "~/utils/db.server"; +import { requireUserId, getUser } from "~/utils/session.server"; + +function validateExpenseDescription(description: string) { + if (description.length < 2) { + return `That expense's description is too short`; + } +} + +type ActionData = { + formError?: string; + fieldErrors?: { + description: string | undefined; + amount?: string | undefined; + user?: string | undefined; + }; + fields?: { + description: string; + amount: number; + }; +}; + +type LoaderData = { + userId: string; + expense: Expense; +}; + +const badRequest = (data: ActionData) => json(data, { status: 400 }); + +export const loader: LoaderFunction = async ({ request, params }) => { + const userId = await requireUserId(request); + if (!userId) { + throw new Response("Unauthorized", { status: 401 }); + } + + const expense = await db.expense.findUnique({ + where: { id: params.expenseId }, + }); + if (!expense) { + throw new Response("What an expense! Not found.", { status: 404 }); + } + + const data: LoaderData = { userId, expense }; + return data; +}; + +export const action: ActionFunction = async ({ request }) => { + const userId = await requireUserId(request); + const user = await getUser(request); + const form = await request.formData(); + const description = form.get("description"); + const amount = parseInt(form.get("amount")?.toString() || "0", 10); + if ( + typeof description !== "string" || + typeof amount !== "number" || + user === null + ) { + return badRequest({ + formError: `Form not submitted correctly.`, + }); + } + + const fieldErrors = { + description: validateExpenseDescription(description), + }; + const fields = { description, amount }; + if (Object.values(fieldErrors).some(Boolean)) { + return badRequest({ fieldErrors, fields }); + } + + const expense = await db.expense.create({ + data: { ...fields, userId: userId, teamId: user.teamId }, + }); + if (!expense) { + return badRequest({ + formError: `Could not create expense.`, + }); + } + return redirect(`/expenses/${expense.id}`); +}; + +export default function EditExpenseRoute() { + const data = useLoaderData(); + const actionData = useActionData(); + const transition = useTransition(); + + if (transition.submission) { + const description = transition.submission.formData.get("description"); + const amount = transition.submission.formData.get("content"); + if ( + typeof description === "string" && + typeof amount === "number" && + !validateExpenseDescription(description) + ) { + return ( +
+

Description: {description}

+

Amount: {amount}€

+

User: {data.userId}

+
+ ); + } + } + + return ( + <> +
+
+
+

Edit expense

+
+
+ + + {actionData?.fieldErrors?.description && ( +
+
+ + + + +
+
+ )} +
+
+ + +
+ {actionData?.formError && ( +
+
+ + + + +
+
+ )} +
+ +
+
+
+
+
+ + + ); +} + +export function CatchBoundary() { + const caught = useCatch(); + + if (caught.status === 401) { + return ( +
+

You must be logged in to submit an expense.

+ Login +
+ ); + } +} + +export function ErrorBoundary() { + return ( +
+ Something unexpected went wrong. Sorry about that. +
+ ); +} diff --git a/app/routes/expenses/$expenseId.tsx b/app/routes/expenses/$expenseId/index.tsx similarity index 96% rename from app/routes/expenses/$expenseId.tsx rename to app/routes/expenses/$expenseId/index.tsx index ae8164c..39e3804 100644 --- a/app/routes/expenses/$expenseId.tsx +++ b/app/routes/expenses/$expenseId/index.tsx @@ -134,11 +134,17 @@ export default function ExpenseRoute() { {data.isOwner && ( <>
+ + Edit +
-