feat: add transfer page placeholder
This commit is contained in:
parent
66f09a8150
commit
4e35698de5
165
app/routes/expenses/transfer.tsx
Normal file
165
app/routes/expenses/transfer.tsx
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
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, getUserId } 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;
|
||||||
|
};
|
||||||
|
fields?: {
|
||||||
|
description: string;
|
||||||
|
amount: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
type LoaderData = {
|
||||||
|
userId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const badRequest = (data: ActionData) => json(data, { status: 400 });
|
||||||
|
|
||||||
|
export const loader: LoaderFunction = async ({ request }) => {
|
||||||
|
const userId = await getUserId(request);
|
||||||
|
if (!userId) {
|
||||||
|
throw new Response("Unauthorized", { status: 401 });
|
||||||
|
}
|
||||||
|
const data: LoaderData = { userId };
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const action: ActionFunction = async ({ request }) => {
|
||||||
|
const userId = await requireUserId(request);
|
||||||
|
const form = await request.formData();
|
||||||
|
const description = form.get("description");
|
||||||
|
const amount = form.get("amount");
|
||||||
|
if (typeof description !== "string" || typeof amount !== "number") {
|
||||||
|
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 },
|
||||||
|
});
|
||||||
|
return redirect(`/expenses/${expense.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function NewExpenseRoute() {
|
||||||
|
const data = useLoaderData<LoaderData>();
|
||||||
|
const actionData = useActionData<ActionData>();
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<p>Description: {description}</p>
|
||||||
|
<p>Amount: {amount}€</p>
|
||||||
|
<p>User: {data.userId}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p>Add an expense</p>
|
||||||
|
<Form method="post">
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Description:{" "}
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="description"
|
||||||
|
defaultValue={actionData?.fields?.description}
|
||||||
|
aria-invalid={
|
||||||
|
Boolean(actionData?.fieldErrors?.description) || undefined
|
||||||
|
}
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.fieldErrors?.description
|
||||||
|
? "description-error"
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
{actionData?.fieldErrors?.description && (
|
||||||
|
<p
|
||||||
|
className="form-validation-error"
|
||||||
|
role="alert"
|
||||||
|
id="description-error"
|
||||||
|
>
|
||||||
|
{actionData.fieldErrors.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
Amount:{" "}
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
name="content"
|
||||||
|
defaultValue={actionData?.fields?.amount}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button type="submit" className="button">
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CatchBoundary() {
|
||||||
|
const caught = useCatch();
|
||||||
|
|
||||||
|
if (caught.status === 401) {
|
||||||
|
return (
|
||||||
|
<div className="error-container">
|
||||||
|
<p>You must be logged in to submit an expense.</p>
|
||||||
|
<Link to="/login">Login</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ErrorBoundary() {
|
||||||
|
return (
|
||||||
|
<div className="error-container">
|
||||||
|
Something unexpected went wrong. Sorry about that.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue