diff --git a/app/components/Header.tsx b/app/components/Header.tsx index 2bd1ed5..7d902f6 100644 --- a/app/components/Header.tsx +++ b/app/components/Header.tsx @@ -28,9 +28,9 @@ const Header = ({ user, route }: Props) => { className="inline-block w-6 h-6 stroke-current" > @@ -138,9 +138,9 @@ const Header = ({ user, route }: Props) => { className="inline-block w-6 h-6 stroke-current" > diff --git a/app/routes/expenses/index.tsx b/app/routes/expenses/index.tsx index 2f677f7..d802f3a 100644 --- a/app/routes/expenses/index.tsx +++ b/app/routes/expenses/index.tsx @@ -83,9 +83,9 @@ export default function JokesIndexRoute() { className="inline-block lg:mr-2 w-6 h-6 stroke-current rotate-45" > @@ -98,7 +98,7 @@ export default function JokesIndexRoute() {
diff --git a/app/routes/expenses/new.tsx b/app/routes/expenses/new.tsx index 5296b37..7b54bfd 100644 --- a/app/routes/expenses/new.tsx +++ b/app/routes/expenses/new.tsx @@ -1,3 +1,4 @@ +import { User } from "@prisma/client"; import type { ActionFunction, LoaderFunction } from "remix"; import { useActionData, @@ -10,7 +11,7 @@ import { useLoaderData, } from "remix"; import { db } from "~/utils/db.server"; -import { requireUserId, getUserId } from "~/utils/session.server"; +import { requireUserId, getUser, getUserId } from "~/utils/session.server"; function validateExpenseDescription(description: string) { if (description.length < 2) { @@ -22,6 +23,8 @@ type ActionData = { formError?: string; fieldErrors?: { description: string | undefined; + amount?: string | undefined; + user?: string | undefined; }; fields?: { description: string; @@ -46,10 +49,15 @@ export const loader: LoaderFunction = async ({ request }) => { 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 = form.get("amount"); - if (typeof description !== "string" || typeof amount !== "number") { + 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.`, }); @@ -64,7 +72,7 @@ export const action: ActionFunction = async ({ request }) => { } const expense = await db.expense.create({ - data: { ...fields, userId: userId }, + data: { ...fields, userId: userId, teamId: user.teamId }, }); return redirect(`/expenses/${expense.id}`); }; @@ -93,53 +101,116 @@ export default function NewExpenseRoute() { } return ( -
-

Add an expense

-
-
-
+
    +
  • + + Back + +
  • +
  • + + Transfer + +
  • +
+ ); } diff --git a/app/routes/expenses/transfer.tsx b/app/routes/expenses/transfer.tsx index 5296b37..8ee6658 100644 --- a/app/routes/expenses/transfer.tsx +++ b/app/routes/expenses/transfer.tsx @@ -1,3 +1,4 @@ +import { User } from "@prisma/client"; import type { ActionFunction, LoaderFunction } from "remix"; import { useActionData, @@ -6,31 +7,34 @@ import { 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`; - } -} +import { + requireUserId, + getUser, + getUserId, + getUsersByTeam, +} from "~/utils/session.server"; type ActionData = { formError?: string; fieldErrors?: { description: string | undefined; + amount?: string | undefined; + user?: string | undefined; + toUser?: string | undefined; }; fields?: { description: string; amount: number; + toUser: string; }; }; type LoaderData = { userId: string | null; + teamUsers: User[]; }; const badRequest = (data: ActionData) => json(data, { status: 400 }); @@ -40,106 +44,156 @@ export const loader: LoaderFunction = async ({ request }) => { if (!userId) { throw new Response("Unauthorized", { status: 401 }); } - const data: LoaderData = { userId }; + const teamUsers = await getUsersByTeam(request); + const data: LoaderData = { + userId, + teamUsers: teamUsers?.filter((user) => user.id !== userId) ?? [], + }; 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 = form.get("amount"); - if (typeof description !== "string" || typeof amount !== "number") { + const toUser = form.get("toUser"); + const description = form.get("description") ?? "TRASFER"; + const amount = parseInt(form.get("amount")?.toString() || "0", 10); + if ( + typeof description !== "string" || + typeof amount !== "number" || + typeof toUser !== "string" || + 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 }, + const expenseFrom = await db.expense.create({ + data: { + description, + amount, + userId: userId, + teamId: user.teamId, + }, }); - return redirect(`/expenses/${expense.id}`); + const expenseTo = await db.expense.create({ + data: { + description, + amount: -amount, + userId: toUser, + teamId: user.teamId, + }, + }); + return redirect(`/expenses`); }; export default function NewExpenseRoute() { 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 ( -
-

Add an expense

-
-
-
+
    +
  • + + Back + +
  • +
  • + + Add new + +
  • +
+ ); } diff --git a/app/routes/login.tsx b/app/routes/login.tsx index b8b0b11..5312e5f 100644 --- a/app/routes/login.tsx +++ b/app/routes/login.tsx @@ -1,6 +1,5 @@ import type { ActionFunction, LinksFunction, MetaFunction } from "remix"; import { useActionData, json, Link, useSearchParams, Form } from "remix"; -import { db } from "~/utils/db.server"; import { login, createUserSession, register } from "~/utils/session.server"; import Header from "../components/Header"; @@ -27,12 +26,6 @@ function validatePassword(password: unknown) { } } -function validateTeamId(teamId: unknown) { - if (typeof teamId !== "string" || teamId.length < 1) { - return "You must indicate an arbitrary team ID"; - } -} - type ActionData = { formError?: string; fieldErrors?: { @@ -121,7 +114,7 @@ export default function Login() { } /> {actionData?.fieldErrors?.username && ( -
+
@@ -163,7 +156,7 @@ export default function Login() { } /> {actionData?.fieldErrors?.password && ( -
+
@@ -185,50 +178,49 @@ export default function Login() {
)}
-
- {actionData?.formError && ( -
-
- - - - -
+ {actionData?.formError && ( +
+
+ + + +
- )} +
+ )} +
+
-
-
-
-
    -
  • - Home -
  • -
  • - Sign-in -
  • -
-
-
+
    +
  • + + Home + +
  • +
  • + + Sign-in + +
  • +
); } diff --git a/app/utils/session.server.ts b/app/utils/session.server.ts index 2cb4648..37b4fea 100644 --- a/app/utils/session.server.ts +++ b/app/utils/session.server.ts @@ -113,6 +113,22 @@ export async function getUser(request: Request) { } } +export async function getUsersByTeam(request: Request) { + const user = await getUser(request); + if (!user) { + return null; + } + + try { + const users = await db.user.findMany({ + where: { teamId: user.teamId }, + }); + return users; + } catch { + throw logout(request); + } +} + export async function logout(request: Request) { const session = await storage.getSession(request.headers.get("Cookie")); return redirect("/", {