feat: add new expense form, add transfer form
This commit is contained in:
parent
9cf8d8a32b
commit
140d706683
|
|
@ -28,9 +28,9 @@ const Header = ({ user, route }: Props) => {
|
||||||
className="inline-block w-6 h-6 stroke-current"
|
className="inline-block w-6 h-6 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M4 6h16M4 12h16M4 18h16"
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -138,9 +138,9 @@ const Header = ({ user, route }: Props) => {
|
||||||
className="inline-block w-6 h-6 stroke-current"
|
className="inline-block w-6 h-6 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M6 18L18 6M6 6l12 12"
|
d="M6 18L18 6M6 6l12 12"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -83,9 +83,9 @@ export default function JokesIndexRoute() {
|
||||||
className="inline-block lg:mr-2 w-6 h-6 stroke-current rotate-45"
|
className="inline-block lg:mr-2 w-6 h-6 stroke-current rotate-45"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M6 18L18 6M6 6l12 12"
|
d="M6 18L18 6M6 6l12 12"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -98,7 +98,7 @@ export default function JokesIndexRoute() {
|
||||||
<div className="card shadow-lg compact side md:bg-base-100 order-first md:order-none">
|
<div className="card shadow-lg compact side md:bg-base-100 order-first md:order-none">
|
||||||
<div className="flex-row items-center justify-center space-x-4 card-body">
|
<div className="flex-row items-center justify-center space-x-4 card-body">
|
||||||
<Link
|
<Link
|
||||||
to="new"
|
to="transfer"
|
||||||
className="btn btn-primary flex-wrap py-3 h-auto w-full"
|
className="btn btn-primary flex-wrap py-3 h-auto w-full"
|
||||||
>
|
>
|
||||||
<Group className="inline-block lg:mr-2 w-6 h-6 stroke-current" />
|
<Group className="inline-block lg:mr-2 w-6 h-6 stroke-current" />
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { User } from "@prisma/client";
|
||||||
import type { ActionFunction, LoaderFunction } from "remix";
|
import type { ActionFunction, LoaderFunction } from "remix";
|
||||||
import {
|
import {
|
||||||
useActionData,
|
useActionData,
|
||||||
|
|
@ -10,7 +11,7 @@ import {
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
} from "remix";
|
} from "remix";
|
||||||
import { db } from "~/utils/db.server";
|
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) {
|
function validateExpenseDescription(description: string) {
|
||||||
if (description.length < 2) {
|
if (description.length < 2) {
|
||||||
|
|
@ -22,6 +23,8 @@ type ActionData = {
|
||||||
formError?: string;
|
formError?: string;
|
||||||
fieldErrors?: {
|
fieldErrors?: {
|
||||||
description: string | undefined;
|
description: string | undefined;
|
||||||
|
amount?: string | undefined;
|
||||||
|
user?: string | undefined;
|
||||||
};
|
};
|
||||||
fields?: {
|
fields?: {
|
||||||
description: string;
|
description: string;
|
||||||
|
|
@ -46,10 +49,15 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||||
|
|
||||||
export const action: ActionFunction = async ({ request }) => {
|
export const action: ActionFunction = async ({ request }) => {
|
||||||
const userId = await requireUserId(request);
|
const userId = await requireUserId(request);
|
||||||
|
const user = await getUser(request);
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
const description = form.get("description");
|
const description = form.get("description");
|
||||||
const amount = form.get("amount");
|
const amount = parseInt(form.get("amount")?.toString() || "0", 10);
|
||||||
if (typeof description !== "string" || typeof amount !== "number") {
|
if (
|
||||||
|
typeof description !== "string" ||
|
||||||
|
typeof amount !== "number" ||
|
||||||
|
user === null
|
||||||
|
) {
|
||||||
return badRequest({
|
return badRequest({
|
||||||
formError: `Form not submitted correctly.`,
|
formError: `Form not submitted correctly.`,
|
||||||
});
|
});
|
||||||
|
|
@ -64,7 +72,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const expense = await db.expense.create({
|
const expense = await db.expense.create({
|
||||||
data: { ...fields, userId: userId },
|
data: { ...fields, userId: userId, teamId: user.teamId },
|
||||||
});
|
});
|
||||||
return redirect(`/expenses/${expense.id}`);
|
return redirect(`/expenses/${expense.id}`);
|
||||||
};
|
};
|
||||||
|
|
@ -93,15 +101,27 @@ export default function NewExpenseRoute() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<p>Add an expense</p>
|
<div className="container mx-auto min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<Form method="post">
|
<div className="card bg-neutral w-full shadow-lg max-w-lg">
|
||||||
<div>
|
<div className="card-body w-full">
|
||||||
<label>
|
<h1 className="card-title">Add an expense</h1>
|
||||||
Description:{" "}
|
<Form
|
||||||
|
method="post"
|
||||||
|
className="mt-5"
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.formError ? "form-error-message" : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label className="label" htmlFor="description-input">
|
||||||
|
<span className="label-text">Description</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
className="input"
|
||||||
name="description"
|
name="description"
|
||||||
|
id="description-input"
|
||||||
defaultValue={actionData?.fields?.description}
|
defaultValue={actionData?.fields?.description}
|
||||||
aria-invalid={
|
aria-invalid={
|
||||||
Boolean(actionData?.fieldErrors?.description) || undefined
|
Boolean(actionData?.fieldErrors?.description) || undefined
|
||||||
|
|
@ -112,34 +132,85 @@ export default function NewExpenseRoute() {
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</label>
|
|
||||||
{actionData?.fieldErrors?.description && (
|
{actionData?.fieldErrors?.description && (
|
||||||
<p
|
<div className="alert alert-error mt-3" role="alert">
|
||||||
className="form-validation-error"
|
<div className="flex-1">
|
||||||
role="alert"
|
<svg
|
||||||
id="description-error"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
>
|
>
|
||||||
{actionData.fieldErrors.description}
|
<path
|
||||||
</p>
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="description-error">
|
||||||
|
{actionData?.fieldErrors.description}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="form-control mb-3">
|
||||||
<label>
|
<label className="label" htmlFor="amount-input">
|
||||||
Amount:{" "}
|
<span className="label-text">Amount</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="content"
|
className="input"
|
||||||
|
id="amount-input"
|
||||||
|
name="amount"
|
||||||
defaultValue={actionData?.fields?.amount}
|
defaultValue={actionData?.fields?.amount}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
{actionData?.formError && (
|
||||||
|
<div className="alert alert-error mt-5" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="form-error-message">
|
||||||
|
{actionData?.formError}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<button type="submit" className="button">
|
)}
|
||||||
|
<div className="text-center max-w-xs mx-auto mt-10">
|
||||||
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="menu px-3 menu-horizontal rounded-box max-w-xs mx-auto flex items-center justify-evenly">
|
||||||
|
<li>
|
||||||
|
<Link to="/expenses" className="btn btn-outline btn-accent">
|
||||||
|
Back
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/expenses/transfer" className="btn btn-outline btn-accent">
|
||||||
|
Transfer
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { User } from "@prisma/client";
|
||||||
import type { ActionFunction, LoaderFunction } from "remix";
|
import type { ActionFunction, LoaderFunction } from "remix";
|
||||||
import {
|
import {
|
||||||
useActionData,
|
useActionData,
|
||||||
|
|
@ -6,31 +7,34 @@ import {
|
||||||
useCatch,
|
useCatch,
|
||||||
Link,
|
Link,
|
||||||
Form,
|
Form,
|
||||||
useTransition,
|
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
} from "remix";
|
} from "remix";
|
||||||
import { db } from "~/utils/db.server";
|
import { db } from "~/utils/db.server";
|
||||||
import { requireUserId, getUserId } from "~/utils/session.server";
|
import {
|
||||||
|
requireUserId,
|
||||||
function validateExpenseDescription(description: string) {
|
getUser,
|
||||||
if (description.length < 2) {
|
getUserId,
|
||||||
return `That expense's description is too short`;
|
getUsersByTeam,
|
||||||
}
|
} from "~/utils/session.server";
|
||||||
}
|
|
||||||
|
|
||||||
type ActionData = {
|
type ActionData = {
|
||||||
formError?: string;
|
formError?: string;
|
||||||
fieldErrors?: {
|
fieldErrors?: {
|
||||||
description: string | undefined;
|
description: string | undefined;
|
||||||
|
amount?: string | undefined;
|
||||||
|
user?: string | undefined;
|
||||||
|
toUser?: string | undefined;
|
||||||
};
|
};
|
||||||
fields?: {
|
fields?: {
|
||||||
description: string;
|
description: string;
|
||||||
amount: number;
|
amount: number;
|
||||||
|
toUser: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type LoaderData = {
|
type LoaderData = {
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
|
teamUsers: User[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const badRequest = (data: ActionData) => json(data, { status: 400 });
|
const badRequest = (data: ActionData) => json(data, { status: 400 });
|
||||||
|
|
@ -40,106 +44,156 @@ export const loader: LoaderFunction = async ({ request }) => {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw new Response("Unauthorized", { status: 401 });
|
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;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const action: ActionFunction = async ({ request }) => {
|
export const action: ActionFunction = async ({ request }) => {
|
||||||
const userId = await requireUserId(request);
|
const userId = await requireUserId(request);
|
||||||
|
const user = await getUser(request);
|
||||||
const form = await request.formData();
|
const form = await request.formData();
|
||||||
const description = form.get("description");
|
const toUser = form.get("toUser");
|
||||||
const amount = form.get("amount");
|
const description = form.get("description") ?? "TRASFER";
|
||||||
if (typeof description !== "string" || typeof amount !== "number") {
|
const amount = parseInt(form.get("amount")?.toString() || "0", 10);
|
||||||
|
if (
|
||||||
|
typeof description !== "string" ||
|
||||||
|
typeof amount !== "number" ||
|
||||||
|
typeof toUser !== "string" ||
|
||||||
|
user === null
|
||||||
|
) {
|
||||||
return badRequest({
|
return badRequest({
|
||||||
formError: `Form not submitted correctly.`,
|
formError: `Form not submitted correctly.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldErrors = {
|
const expenseFrom = await db.expense.create({
|
||||||
description: validateExpenseDescription(description),
|
data: {
|
||||||
};
|
description,
|
||||||
const fields = { description, amount };
|
amount,
|
||||||
if (Object.values(fieldErrors).some(Boolean)) {
|
userId: userId,
|
||||||
return badRequest({ fieldErrors, fields });
|
teamId: user.teamId,
|
||||||
}
|
},
|
||||||
|
|
||||||
const expense = await db.expense.create({
|
|
||||||
data: { ...fields, userId: userId },
|
|
||||||
});
|
});
|
||||||
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() {
|
export default function NewExpenseRoute() {
|
||||||
const data = useLoaderData<LoaderData>();
|
const data = useLoaderData<LoaderData>();
|
||||||
const actionData = useActionData<ActionData>();
|
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 (
|
return (
|
||||||
<div>
|
<>
|
||||||
<p>Description: {description}</p>
|
<div className="container mx-auto min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<p>Amount: {amount}€</p>
|
<div className="card bg-neutral w-full shadow-lg max-w-lg">
|
||||||
<p>User: {data.userId}</p>
|
<div className="card-body w-full">
|
||||||
|
<h1 className="card-title">Transfer to user</h1>
|
||||||
|
<Form
|
||||||
|
method="post"
|
||||||
|
className="mt-5"
|
||||||
|
aria-describedby={
|
||||||
|
actionData?.formError ? "form-error-message" : undefined
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="form-control mb-3">
|
||||||
|
<label className="label" htmlFor="toUser-input">
|
||||||
|
<span className="label-text">To</span>
|
||||||
|
</label>
|
||||||
|
<select
|
||||||
|
name="toUser"
|
||||||
|
id="toUser-input"
|
||||||
|
className="select select-bordered w-full max-w-xs"
|
||||||
|
defaultValue={actionData?.fields?.toUser}
|
||||||
|
>
|
||||||
|
<option disabled selected>
|
||||||
|
Choose an user from your team
|
||||||
|
</option>
|
||||||
|
{data?.teamUsers.map((user) => (
|
||||||
|
<option key={user.id} value={user.id}>
|
||||||
|
{user.username}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
);
|
<div className="form-control mb-3">
|
||||||
}
|
<label className="label" htmlFor="description-input">
|
||||||
}
|
<span className="label-text">Description</span>
|
||||||
|
</label>
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<p>Add an expense</p>
|
|
||||||
<Form method="post">
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
Description:{" "}
|
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
className="input"
|
||||||
name="description"
|
name="description"
|
||||||
|
id="description-input"
|
||||||
defaultValue={actionData?.fields?.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>
|
||||||
<div>
|
<div className="form-control mb-3">
|
||||||
<label>
|
<label className="label" htmlFor="amount-input">
|
||||||
Amount:{" "}
|
<span className="label-text">Amount</span>
|
||||||
|
</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
name="content"
|
className="input"
|
||||||
|
id="amount-input"
|
||||||
|
name="amount"
|
||||||
defaultValue={actionData?.fields?.amount}
|
defaultValue={actionData?.fields?.amount}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
{actionData?.formError && (
|
||||||
|
<div className="alert alert-error mt-5" role="alert">
|
||||||
|
<div className="flex-1">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
<label id="form-error-message">
|
||||||
|
{actionData?.formError}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
</div>
|
||||||
<button type="submit" className="button">
|
)}
|
||||||
|
<div className="text-center max-w-xs mx-auto mt-10">
|
||||||
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="menu px-3 menu-horizontal rounded-box max-w-xs mx-auto flex items-center justify-evenly">
|
||||||
|
<li>
|
||||||
|
<Link to="/expenses" className="btn btn-outline btn-accent">
|
||||||
|
Back
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Link to="/expenses/new" className="btn btn-outline btn-accent">
|
||||||
|
Add new
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
import type { ActionFunction, LinksFunction, MetaFunction } from "remix";
|
||||||
import { useActionData, json, Link, useSearchParams, Form } 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 { login, createUserSession, register } from "~/utils/session.server";
|
||||||
import Header from "../components/Header";
|
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 = {
|
type ActionData = {
|
||||||
formError?: string;
|
formError?: string;
|
||||||
fieldErrors?: {
|
fieldErrors?: {
|
||||||
|
|
@ -121,7 +114,7 @@ export default function Login() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{actionData?.fieldErrors?.username && (
|
{actionData?.fieldErrors?.username && (
|
||||||
<div className="alert alert-error" role="alert">
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -130,9 +123,9 @@ export default function Login() {
|
||||||
className="w-6 h-6 mx-2 stroke-current"
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -163,7 +156,7 @@ export default function Login() {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
{actionData?.fieldErrors?.password && (
|
{actionData?.fieldErrors?.password && (
|
||||||
<div className="alert alert-error" role="alert">
|
<div className="alert alert-error mt-2" role="alert">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -172,9 +165,9 @@ export default function Login() {
|
||||||
className="w-6 h-6 mx-2 stroke-current"
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
@ -185,9 +178,8 @@ export default function Login() {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div id="form-error-message" className="mt-6 mb-3">
|
|
||||||
{actionData?.formError && (
|
{actionData?.formError && (
|
||||||
<div className="alert alert-error" role="alert">
|
<div className="alert alert-error mt-5" role="alert">
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
|
@ -196,39 +188,39 @@ export default function Login() {
|
||||||
className="w-6 h-6 mx-2 stroke-current"
|
className="w-6 h-6 mx-2 stroke-current"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
strokeLinejoin="round"
|
||||||
stroke-width="2"
|
strokeWidth="2"
|
||||||
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"
|
||||||
></path>
|
></path>
|
||||||
</svg>
|
</svg>
|
||||||
<label>{actionData?.formError}</label>
|
<label id="form-error-message">
|
||||||
|
{actionData?.formError}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
<div className="text-center max-w-xs mx-auto mt-10">
|
||||||
<button
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
type="submit"
|
Login
|
||||||
className="btn btn-primary mt-10 block w-full"
|
|
||||||
>
|
|
||||||
Submit
|
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="container">
|
<ul className="menu px-3 menu-horizontal rounded-box max-w-xs mx-auto flex items-center justify-evenly">
|
||||||
<div className="links">
|
|
||||||
<ul>
|
|
||||||
<li>
|
<li>
|
||||||
<Link to="/">Home</Link>
|
<Link to="/" className="btn btn-outline btn-accent">
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<Link to="/signin">Sign-in</Link>
|
<Link to="/signin" className="btn btn-outline btn-accent">
|
||||||
|
Sign-in
|
||||||
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
export async function logout(request: Request) {
|
||||||
const session = await storage.getSession(request.headers.get("Cookie"));
|
const session = await storage.getSession(request.headers.get("Cookie"));
|
||||||
return redirect("/", {
|
return redirect("/", {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue