2022-02-21 18:08:35 +01:00
|
|
|
import type { LoaderFunction, ActionFunction } from "remix";
|
|
|
|
|
import { useLoaderData, useCatch, redirect, Link, Form } from "remix";
|
2022-02-14 17:21:00 +01:00
|
|
|
import type { Team, User } from "@prisma/client";
|
|
|
|
|
import Header from "~/components/Header";
|
2022-02-21 18:08:35 +01:00
|
|
|
import { db } from "~/utils/db.server";
|
|
|
|
|
import { requireUserId, getUser } from "~/utils/session.server";
|
2022-02-14 17:21:00 +01:00
|
|
|
|
|
|
|
|
type LoaderData = {
|
|
|
|
|
user: User & {
|
|
|
|
|
team: Team & {
|
|
|
|
|
members: User[];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export const loader: LoaderFunction = async ({ request }) => {
|
|
|
|
|
const user = await getUser(request);
|
|
|
|
|
if (!user) {
|
|
|
|
|
return redirect("/login");
|
|
|
|
|
}
|
|
|
|
|
const data: LoaderData = { user };
|
|
|
|
|
return data;
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-21 18:08:35 +01:00
|
|
|
export const action: ActionFunction = async ({ request }) => {
|
|
|
|
|
const form = await request.formData();
|
|
|
|
|
if (form.get("_method") === "patch") {
|
|
|
|
|
const userId = await requireUserId(request);
|
|
|
|
|
const user = await getUser(request);
|
|
|
|
|
|
|
|
|
|
const balanceByIncomeField = form.get("balanceByIncome");
|
|
|
|
|
const balanceByIncome =
|
|
|
|
|
typeof balanceByIncomeField === "boolean"
|
|
|
|
|
? balanceByIncomeField
|
|
|
|
|
: typeof balanceByIncomeField === "string"
|
|
|
|
|
? Boolean(balanceByIncomeField)
|
|
|
|
|
: false;
|
|
|
|
|
|
|
|
|
|
const team = await db.team.findUnique({
|
|
|
|
|
where: { id: user?.teamId },
|
|
|
|
|
});
|
|
|
|
|
if (!team) {
|
|
|
|
|
throw new Response("Can't update what does not exist", { status: 404 });
|
|
|
|
|
}
|
|
|
|
|
if (user?.teamId !== team.id) {
|
|
|
|
|
throw new Response("Pssh, nice try. That's not your expense", {
|
|
|
|
|
status: 401,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
await db.team.update({ where: { id: team.id }, data: { balanceByIncome } });
|
|
|
|
|
return redirect("/expenses");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2022-02-14 17:21:00 +01:00
|
|
|
export default function JokesIndexRoute() {
|
|
|
|
|
const data = useLoaderData<LoaderData>();
|
|
|
|
|
|
2022-02-21 18:08:35 +01:00
|
|
|
const allUsersHaveSetAvgIncome = data.user.team.members?.every(
|
|
|
|
|
(m) => m.avgIncome && m.avgIncome > 0
|
|
|
|
|
);
|
|
|
|
|
|
2022-02-14 17:21:00 +01:00
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Header user={data.user} route="/account" />
|
|
|
|
|
<main className="p-2 lg:py-4 lg:px-6">
|
|
|
|
|
<h1 className="mb-8 lg:mb-10 text-2xl">
|
|
|
|
|
Team: <strong className="text-primary">{data.user.team.id}</strong>
|
|
|
|
|
</h1>
|
|
|
|
|
{!data.user.team.members || data.user.team.members?.length === 0 ? (
|
|
|
|
|
<p>No members</p>
|
|
|
|
|
) : (
|
|
|
|
|
data.user.team.members.map((member) => (
|
|
|
|
|
<div
|
|
|
|
|
key={member.id}
|
|
|
|
|
className="card shadow-lg compact side bg-base-100 mb-4"
|
|
|
|
|
>
|
|
|
|
|
<div className="flex-row items-center space-x-4 card-body">
|
|
|
|
|
<div>
|
|
|
|
|
<div className="rounded-full w-10 h-10 m-1 inline-flex justify-center items-center bg-primary-content text-primary text-3xl">
|
|
|
|
|
{member.icon ?? member.username[0]}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="card-title !mb-0">{member.username}</h2>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))
|
|
|
|
|
)}
|
2022-02-21 18:08:35 +01:00
|
|
|
<div className="mt-10">
|
|
|
|
|
<h2 className="mb-4 text-xl">Balance based on income</h2>
|
|
|
|
|
<p>
|
|
|
|
|
To have an equal split based on everyone's income, you can select
|
|
|
|
|
this option. If of two people, one earns 1.5 times as much as the
|
|
|
|
|
other, then his or her share in the common expenses will be 1.5
|
|
|
|
|
times as much as the other's.
|
|
|
|
|
</p>
|
|
|
|
|
<Form method="post">
|
|
|
|
|
<input type="hidden" name="_method" value="patch" />
|
|
|
|
|
<fieldset disabled={!allUsersHaveSetAvgIncome}>
|
|
|
|
|
<div className="form-control mt-4">
|
|
|
|
|
<label className="cursor-pointer label justify-start">
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
name="balanceByIncome"
|
|
|
|
|
defaultChecked={data.user.team.balanceByIncome ?? false}
|
|
|
|
|
className="checkbox checkbox-primary"
|
|
|
|
|
/>
|
|
|
|
|
<span className="label-text ml-2">
|
|
|
|
|
Enable balance based on income
|
|
|
|
|
</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</fieldset>
|
|
|
|
|
{!allUsersHaveSetAvgIncome ? (
|
|
|
|
|
<div className="alert shadow-lg">
|
|
|
|
|
<div>
|
|
|
|
|
<svg
|
|
|
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
|
|
|
fill="none"
|
|
|
|
|
viewBox="0 0 24 24"
|
|
|
|
|
className="stroke-info-content flex-shrink-0 w-6 h-6"
|
|
|
|
|
>
|
|
|
|
|
<path
|
|
|
|
|
stroke-linecap="round"
|
|
|
|
|
stroke-linejoin="round"
|
|
|
|
|
stroke-width="2"
|
|
|
|
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
|
|
|
></path>
|
|
|
|
|
</svg>
|
|
|
|
|
<span>
|
|
|
|
|
Not all users from this team set their average income.
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex-none">
|
|
|
|
|
<Link to="/account/manage" className="btn btn-sm">
|
|
|
|
|
Check in the Account page
|
|
|
|
|
</Link>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<button type="submit" className="mt-6 btn btn-primary">
|
|
|
|
|
Save
|
|
|
|
|
</button>
|
|
|
|
|
)}
|
|
|
|
|
</Form>
|
|
|
|
|
</div>
|
2022-02-14 17:21:00 +01:00
|
|
|
</main>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function CatchBoundary() {
|
|
|
|
|
const caught = useCatch();
|
|
|
|
|
|
|
|
|
|
if (caught.status === 404) {
|
|
|
|
|
return (
|
|
|
|
|
<div className="error-container">There are no users to display.</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
throw new Error(`Unexpected caught response with status: ${caught.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function ErrorBoundary() {
|
|
|
|
|
return <div className="error-container">I did a whoopsies.</div>;
|
|
|
|
|
}
|