diff --git a/app/components/header.tsx b/app/components/header.tsx
new file mode 100644
index 0000000..645df7b
--- /dev/null
+++ b/app/components/header.tsx
@@ -0,0 +1,40 @@
+import {LinkContainer} from 'react-router-bootstrap'
+import {Container, Nav, Navbar, NavDropdown} from 'react-bootstrap';
+
+function Header() {
+ return (
+
+
+
+ K-FRIDGE
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Header;
\ No newline at end of file
diff --git a/app/root.tsx b/app/root.tsx
index 781d6fe..44a7fb2 100644
--- a/app/root.tsx
+++ b/app/root.tsx
@@ -1,29 +1,25 @@
import type { LinksFunction } from "@remix-run/node";
import {
Links,
- LiveReload,
- Outlet
+ Scripts,
+ Outlet,
+ useRouteError,
+ isRouteErrorResponse
} from "@remix-run/react";
+import type { PropsWithChildren } from "react";
-import globalLargeStylesUrl from "~/styles/global-large.css?url";
-import globalMediumStylesUrl from "~/styles/global-medium.css?url";
-import globalStylesUrl from "~/styles/global.css?url";
+import stylesheet from "bootstrap/dist/css/bootstrap.min.css?url";
export const links: LinksFunction = () => [
- { rel: "stylesheet", href: globalStylesUrl },
- {
- rel: "stylesheet",
- href: globalMediumStylesUrl,
- media: "print, (min-width: 640px)",
- },
- {
- rel: "stylesheet",
- href: globalLargeStylesUrl,
- media: "screen and (min-width: 1024px)",
- },
+ { rel: "stylesheet", href: stylesheet },
];
-export default function App() {
+import Header from "~/components/header";
+
+function Document({
+ children,
+ title = "K-FRIDGE",
+}: PropsWithChildren<{ title?: string }>) {
return (
@@ -32,13 +28,53 @@ export default function App() {
name="viewport"
content="width=device-width, initial-scale=1"
/>
- Remix: So great, it's funny!
+ {title}
-
-
+
+ {children}
+
);
+}
+
+export default function App() {
+ return (
+
+
+
+ );
+}
+
+export function ErrorBoundary() {
+ const error = useRouteError();
+
+ if (isRouteErrorResponse(error)) {
+ return (
+
+
+
+ {error.status} {error.statusText}
+
+
+
+ );
+ }
+
+ const errorMessage =
+ error instanceof Error
+ ? error.message
+ : "Unknown error";
+ return (
+
+
+
App Error
+
{errorMessage}
+
+
+ );
}
\ No newline at end of file
diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx
index 0b8a880..d5de973 100644
--- a/app/routes/_index.tsx
+++ b/app/routes/_index.tsx
@@ -1,11 +1,3 @@
-import type { LinksFunction } from "@remix-run/node";
-
-import stylesUrl from "~/styles/index.css?url";
-
-export const links: LinksFunction = () => [
- { rel: "stylesheet", href: stylesUrl },
-];
-
export default function Index() {
return (
diff --git a/app/routes/beers.$beerid.tsx b/app/routes/beers.$beerid.tsx
index 531a706..4d83d04 100644
--- a/app/routes/beers.$beerid.tsx
+++ b/app/routes/beers.$beerid.tsx
@@ -1,9 +1,39 @@
-import type { LoaderFunctionArgs } from "@remix-run/node";
-import { json } from "@remix-run/node";
-import { useLoaderData } from "@remix-run/react";
+import type {
+ LoaderFunctionArgs,
+ ActionFunctionArgs
+} from "@remix-run/node";
+import { json, redirect } from "@remix-run/node";
+import {
+ useLoaderData,
+ useParams,
+ isRouteErrorResponse,
+ useRouteError } from "@remix-run/react";
import { db } from "~/utils/db.server";
+export const action = async ({
+ params,
+ request,
+ }: ActionFunctionArgs) => {
+ const form = await request.formData();
+ if (form.get("intent") !== "delete") {
+ throw new Response(
+ `The intent ${form.get("intent")} is not supported`,
+ { status: 400 }
+ );
+ }
+ const beer = await db.beer.findUnique({
+ where: { id: params.beerid },
+ });
+ if (!beer) {
+ throw new Response("Can't delete what does not exist", {
+ status: 404,
+ });
+ }
+ await db.beer.delete({ where: { id: params.beerid } });
+ return redirect("/beers");
+ };
+
export const loader = async ({
params,
}: LoaderFunctionArgs) => {
@@ -11,7 +41,9 @@ export const loader = async ({
where: { id: params.beerid },
});
if (!beer) {
- throw new Error("Beer not found");
+ throw new Response("Beer not found.", {
+ status: 404,
+ });
}
return json({ beer });
};
@@ -24,6 +56,45 @@ export default function BeerRoute() {
You selected this beer:
{data.beer.name}
ABV {data.beer.abv}%
+
);
}
+
+export function ErrorBoundary() {
+ const { beerid } = useParams();
+ const error = useRouteError();
+
+ if (isRouteErrorResponse(error)) {
+ if (error.status === 400) {
+ return (
+
+ What you're trying to do is not allowed.
+
+ );
+ }
+ if (error.status === 404) {
+ return (
+
+ Huh? What the heck is "{beerid}"?
+
+ );
+ }
+ }
+
+ return (
+
+ There was an error loading beer by the id "{beerid}".
+ Sorry.
+
+ );
+ }
\ No newline at end of file
diff --git a/app/routes/beers._index.tsx b/app/routes/beers._index.tsx
index e995422..9a5ca0e 100644
--- a/app/routes/beers._index.tsx
+++ b/app/routes/beers._index.tsx
@@ -18,7 +18,7 @@ export default function BeersIndexRoute() {
return (
-
Here's a random Beer:
+
Here's a random Beer:
{data.randomBeer.name}
"{data.randomBeer.name}" Permalink
@@ -26,3 +26,11 @@ export default function BeersIndexRoute() {
);
}
+
+export function ErrorBoundary() {
+ return (
+
+ Something unexpected went wrong. Sorry about that.
+
+ );
+ }
\ No newline at end of file
diff --git a/app/routes/beers.new.tsx b/app/routes/beers.new.tsx
index d9cd9ea..6b7dc6a 100644
--- a/app/routes/beers.new.tsx
+++ b/app/routes/beers.new.tsx
@@ -1,7 +1,25 @@
import type { ActionFunctionArgs } from "@remix-run/node";
import { redirect } from "@remix-run/node";
+import { useActionData } from "@remix-run/react";
import { db } from "~/utils/db.server";
+import { badRequest } from "~/utils/request.server";
+
+function validateBeerAbv(abv: number) {
+ if (abv < 0) {
+ return "Negative ABV not allowed";
+ }
+
+ if (abv > 100) {
+ return "ABV can not be more than 100%";
+ }
+ }
+
+ function validateBeerName(name: string) {
+ if (name.length < 3) {
+ return "That beer's name is too short";
+ }
+ }
export const action = async ({
request,
@@ -15,36 +33,115 @@ export const action = async ({
typeof name !== "string" ||
typeof abv !== "number"
) {
- throw new Error("Form not submitted correctly.");
+ return badRequest({
+ fieldErrors: null,
+ fields: null,
+ formError: "Form not submitted correctly.",
+ });
}
+ const fieldErrors = {
+ abv: validateBeerAbv(abv),
+ name: validateBeerName(name),
+ };
const fields = { name, abv };
+ if (Object.values(fieldErrors).some(Boolean)) {
+ return badRequest({
+ fieldErrors,
+ fields,
+ formError: null,
+ });
+ }
const beer = await db.beer.create({ data: fields });
return redirect(`/beers/${beer.id}`);
};
export default function NewBeerRoute() {
+ const actionData = useActionData();
return (
);
+ }
+
+ export function ErrorBoundary() {
+ return (
+
+ Something unexpected went wrong. Sorry about that.
+
+ );
}
\ No newline at end of file
diff --git a/app/routes/beers.tsx b/app/routes/beers.tsx
index b907327..91d6bab 100644
--- a/app/routes/beers.tsx
+++ b/app/routes/beers.tsx
@@ -1,4 +1,3 @@
-import type { LinksFunction } from "@remix-run/node";
import { json } from "@remix-run/node";
import {
Link,
@@ -6,13 +5,8 @@ import {
useLoaderData,
} from "@remix-run/react";
-import stylesUrl from "~/styles/beers.css?url";
import { db } from "~/utils/db.server";
-export const links: LinksFunction = () => [
- { rel: "stylesheet", href: stylesUrl },
-];
-
export const loader = async () => {
return json({
beerListItems: await db.beer.findMany({
@@ -27,40 +21,42 @@ export default function BeersRoute() {
const data = useLoaderData();
return (
-
-
-
-
+
+
-
-
-
-
Get a random beer
-
Here are a few more beers to check out:
-
- {data.beerListItems.map(({ id, name }) => (
- -
- {name}
-
- ))}
-
-
- Add your own
-
-
-
-
-
+
+
+
+
Get a random beer
+
Here are a few more beers to check out:
+
+ {data.beerListItems.map(({ id, name }) => (
+ -
+ {name}
+
+ ))}
+
+
+
+ Add your own
+
+
+
+
+
+
diff --git a/app/styles/beers.css b/app/styles/beers.css
deleted file mode 100644
index 8e524dd..0000000
--- a/app/styles/beers.css
+++ /dev/null
@@ -1,93 +0,0 @@
-.beers-layout {
- display: flex;
- flex-direction: column;
- min-height: inherit;
- }
-
- .beers-header {
- padding-top: 1rem;
- padding-bottom: 1rem;
- border-bottom: 1px solid var(--color-border);
- }
-
- .beers-header .container {
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .beers-header .home-link {
- font-family: var(--font-display);
- font-size: 3rem;
- }
-
- .beers-header .home-link a {
- color: var(--color-foreground);
- }
-
- .beers-header .home-link a:hover {
- text-decoration: none;
- }
-
- .beers-header .logo-medium {
- display: none;
- }
-
- .beers-header a:hover {
- text-decoration-style: wavy;
- text-decoration-thickness: 1px;
- }
-
- .beers-header .user-info {
- display: flex;
- gap: 1rem;
- align-items: center;
- white-space: nowrap;
- }
-
- .beers-main {
- padding-top: 2rem;
- padding-bottom: 2rem;
- flex: 1 1 100%;
- }
-
- .beers-main .container {
- display: flex;
- gap: 1rem;
- }
-
- .beers-list {
- max-width: 12rem;
- }
-
- .beers-outlet {
- flex: 1;
- }
-
- .beers-footer {
- padding-top: 2rem;
- padding-bottom: 1rem;
- border-top: 1px solid var(--color-border);
- }
-
- @media print, (min-width: 640px) {
- .beers-header .logo {
- display: none;
- }
-
- .beers-header .logo-medium {
- display: block;
- }
-
- .beers-main {
- padding-top: 3rem;
- padding-bottom: 3rem;
- }
- }
-
- @media (max-width: 639px) {
- .beers-main .container {
- flex-direction: column;
- }
- }
-
\ No newline at end of file
diff --git a/app/styles/global-large.css b/app/styles/global-large.css
deleted file mode 100644
index 8039877..0000000
--- a/app/styles/global-large.css
+++ /dev/null
@@ -1,25 +0,0 @@
-h1 {
- font-size: 3.75rem;
- line-height: 1;
- }
-
- h2 {
- font-size: 1.875rem;
- line-height: 2.25rem;
- }
-
- h3 {
- font-size: 1.5rem;
- line-height: 2rem;
- }
-
- h4 {
- font-size: 1.25rem;
- line-height: 1.75rem;
- }
-
- h5 {
- font-size: 1.125rem;
- line-height: 1.75rem;
- }
-
\ No newline at end of file
diff --git a/app/styles/global-medium.css b/app/styles/global-medium.css
deleted file mode 100644
index 69a1446..0000000
--- a/app/styles/global-medium.css
+++ /dev/null
@@ -1,30 +0,0 @@
-h1 {
- font-size: 3rem;
- line-height: 1;
- }
-
- h2 {
- font-size: 2.25rem;
- line-height: 2.5rem;
- }
-
- h3 {
- font-size: 1.25rem;
- line-height: 1.75rem;
- }
-
- h4 {
- font-size: 1.125rem;
- line-height: 1.75rem;
- }
-
- h5,
- h6 {
- font-size: 1rem;
- line-height: 1.5rem;
- }
-
- .container {
- --gutter: 40px;
- }
-
\ No newline at end of file
diff --git a/app/styles/global.css b/app/styles/global.css
deleted file mode 100644
index 993bcb3..0000000
--- a/app/styles/global.css
+++ /dev/null
@@ -1,355 +0,0 @@
-
- :root {
- --hs-links: 48 100%;
- --color-foreground: hsl(0, 0%, 100%);
- --color-background: hsl(278, 73%, 19%);
- --color-links: hsl(var(--hs-links) 50%);
- --color-links-hover: hsl(var(--hs-links) 45%);
- --color-border: hsl(277, 85%, 38%);
- --color-invalid: hsl(356, 100%, 71%);
- --gradient-background: radial-gradient(
- circle,
- rgba(152, 11, 238, 1) 0%,
- rgba(118, 15, 181, 1) 35%,
- rgba(58, 13, 85, 1) 100%
- );
- --font-body: -apple-system, "Segoe UI", Helvetica Neue, Helvetica,
- Roboto, Arial, sans-serif, system-ui, "Apple Color Emoji",
- "Segoe UI Emoji";
- --font-display: var(--font-body);
- }
-
- html {
- box-sizing: border-box;
- }
-
- *,
- *::before,
- *::after {
- box-sizing: inherit;
- }
-
- :-moz-focusring {
- outline: auto;
- }
-
- :focus {
- outline: var(--color-links) solid 2px;
- outline-offset: 2px;
- }
-
- html,
- body {
- padding: 0;
- margin: 0;
- color: var(--color-foreground);
- background-color: var(--color-background);
- }
-
- [data-light] {
- --color-invalid: hsl(356, 70%, 39%);
- color: var(--color-background);
- background-color: var(--color-foreground);
- }
-
- body {
- font-family: var(--font-body);
- line-height: 1.5;
- background-repeat: no-repeat;
- min-height: 100vh;
- min-height: calc(100vh - env(safe-area-inset-bottom));
- }
-
- a {
- color: var(--color-links);
- text-decoration: none;
- }
-
- a:hover {
- color: var(--color-links-hover);
- text-decoration: underline;
- }
-
- hr {
- display: block;
- height: 1px;
- border: 0;
- background-color: var(--color-border);
- margin-top: 2rem;
- margin-bottom: 2rem;
- }
-
- h1,
- h2,
- h3,
- h4,
- h5,
- h6 {
- font-family: var(--font-display);
- margin: 0;
- }
-
- h1 {
- font-size: 2.25rem;
- line-height: 2.5rem;
- }
-
- h2 {
- font-size: 1.5rem;
- line-height: 2rem;
- }
-
- h3 {
- font-size: 1.25rem;
- line-height: 1.75rem;
- }
-
- h4 {
- font-size: 1.125rem;
- line-height: 1.75rem;
- }
-
- h5,
- h6 {
- font-size: 0.875rem;
- line-height: 1.25rem;
- }
-
- .sr-only {
- position: absolute;
- width: 1px;
- height: 1px;
- padding: 0;
- margin: -1px;
- overflow: hidden;
- clip: rect(0, 0, 0, 0);
- white-space: nowrap;
- border-width: 0;
- }
-
- .container {
- --gutter: 16px;
- width: 1024px;
- max-width: calc(100% - var(--gutter) * 2);
- margin-right: auto;
- margin-left: auto;
- }
-
- /* buttons */
-
- .button {
- --shadow-color: hsl(var(--hs-links) 30%);
- --shadow-size: 3px;
- -webkit-appearance: none;
- -moz-appearance: none;
- cursor: pointer;
- appearance: none;
- display: inline-flex;
- align-items: center;
- justify-content: center;
- background-color: var(--color-links);
- color: var(--color-background);
- font-family: var(--font-display);
- font-weight: bold;
- line-height: 1;
- font-size: 1.125rem;
- margin: 0;
- padding: 0.625em 1em;
- border: 0;
- border-radius: 4px;
- box-shadow: 0 var(--shadow-size) 0 0 var(--shadow-color);
- outline-offset: 2px;
- transform: translateY(0);
- transition: background-color 50ms ease-out, box-shadow
- 50ms ease-out,
- transform 100ms cubic-bezier(0.3, 0.6, 0.8, 1.25);
- }
-
- .button:hover {
- --raise: 1px;
- color: var(--color-background);
- text-decoration: none;
- box-shadow: 0 calc(var(--shadow-size) + var(--raise)) 0 0 var(
- --shadow-color
- );
- transform: translateY(calc(var(--raise) * -1));
- }
-
- .button:active {
- --press: 1px;
- box-shadow: 0 calc(var(--shadow-size) - var(--press)) 0 0 var(
- --shadow-color
- );
- transform: translateY(var(--press));
- background-color: var(--color-links-hover);
- }
-
- .button[disabled],
- .button[aria-disabled="true"] {
- transform: translateY(0);
- pointer-events: none;
- opacity: 0.7;
- }
-
- .button:focus:not(:focus-visible) {
- outline: none;
- }
-
- /* forms */
-
- form {
- display: flex;
- flex-direction: column;
- gap: 1rem;
- width: 100%;
- }
-
- fieldset {
- margin: 0;
- padding: 0;
- border: 0;
- }
-
- legend {
- display: block;
- max-width: 100%;
- margin-bottom: 0.5rem;
- color: inherit;
- white-space: normal;
- }
-
- [type="text"],
- [type="password"],
- [type="date"],
- [type="datetime"],
- [type="datetime-local"],
- [type="month"],
- [type="week"],
- [type="email"],
- [type="number"],
- [type="search"],
- [type="tel"],
- [type="time"],
- [type="url"],
- [type="color"],
- textarea {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- display: block;
- display: flex;
- align-items: center;
- width: 100%;
- height: 2.5rem;
- margin: 0;
- padding: 0.5rem 0.75rem;
- border: 1px solid var(--color-border);
- border-radius: 4px;
- background-color: hsl(0 0% 100% / 10%);
- background-blend-mode: luminosity;
- box-shadow: none;
- font-family: var(--font-body);
- font-size: 1rem;
- font-weight: normal;
- line-height: 1.5;
- color: var(--color-foreground);
- transition: box-shadow 200ms, border-color 50ms ease-out,
- background-color 50ms ease-out, color 50ms ease-out;
- }
-
- [data-light] [type="text"],
- [data-light] [type="password"],
- [data-light] [type="date"],
- [data-light] [type="datetime"],
- [data-light] [type="datetime-local"],
- [data-light] [type="month"],
- [data-light] [type="week"],
- [data-light] [type="email"],
- [data-light] [type="number"],
- [data-light] [type="search"],
- [data-light] [type="tel"],
- [data-light] [type="time"],
- [data-light] [type="url"],
- [data-light] [type="color"],
- [data-light] textarea {
- color: var(--color-background);
- background-color: hsl(0 0% 0% / 10%);
- }
-
- [type="text"][aria-invalid="true"],
- [type="password"][aria-invalid="true"],
- [type="date"][aria-invalid="true"],
- [type="datetime"][aria-invalid="true"],
- [type="datetime-local"][aria-invalid="true"],
- [type="month"][aria-invalid="true"],
- [type="week"][aria-invalid="true"],
- [type="email"][aria-invalid="true"],
- [type="number"][aria-invalid="true"],
- [type="search"][aria-invalid="true"],
- [type="tel"][aria-invalid="true"],
- [type="time"][aria-invalid="true"],
- [type="url"][aria-invalid="true"],
- [type="color"][aria-invalid="true"],
- textarea[aria-invalid="true"] {
- border-color: var(--color-invalid);
- }
-
- textarea {
- display: block;
- min-height: 50px;
- max-width: 100%;
- }
-
- textarea[rows] {
- height: auto;
- }
-
- input:disabled,
- input[readonly],
- textarea:disabled,
- textarea[readonly] {
- opacity: 0.7;
- cursor: not-allowed;
- }
-
- [type="file"],
- [type="checkbox"],
- [type="radio"] {
- margin: 0;
- }
-
- [type="file"] {
- width: 100%;
- }
-
- label {
- margin: 0;
- }
-
- [type="checkbox"] + label,
- [type="radio"] + label {
- margin-left: 0.5rem;
- }
-
- label > [type="checkbox"],
- label > [type="radio"] {
- margin-right: 0.5rem;
- }
-
- ::placeholder {
- color: hsl(0 0% 100% / 65%);
- }
-
- .form-validation-error {
- margin: 0;
- margin-top: 0.25em;
- color: var(--color-invalid);
- font-size: 0.8rem;
- }
-
- .error-container {
- background-color: hsla(356, 77%, 59%, 0.747);
- border-radius: 0.25rem;
- padding: 0.5rem 1rem;
- }
-
\ No newline at end of file
diff --git a/app/styles/index.css b/app/styles/index.css
deleted file mode 100644
index 2f15400..0000000
--- a/app/styles/index.css
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * when the user visits this page, this style will apply, when they leave, it
- * will get unloaded, so don't worry so much about conflicting styles between
- * pages!
- */
-
- body {
- background-image: var(--gradient-background);
- }
-
- .container {
- min-height: inherit;
- }
-
- .container,
- .content {
- display: flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- }
-
- .content {
- padding-top: 3rem;
- padding-bottom: 3rem;
- }
-
- h1 {
- margin: 0;
- text-shadow: 0 3px 0 rgba(0, 0, 0, 0.75);
- text-align: center;
- line-height: 0.5;
- }
-
- h1 span {
- display: block;
- font-size: 4.5rem;
- line-height: 1;
- text-transform: uppercase;
- text-shadow: 0 0.2em 0.5em rgba(0, 0, 0, 0.5), 0 5px 0
- rgba(0, 0, 0, 0.75);
- }
-
- nav ul {
- list-style: none;
- margin: 0;
- padding: 0;
- display: flex;
- gap: 1rem;
- font-family: var(--font-display);
- font-size: 1.125rem;
- line-height: 1;
- }
-
- nav ul a:hover {
- text-decoration-style: wavy;
- text-decoration-thickness: 1px;
- }
-
- @media print, (min-width: 640px) {
- h1 span {
- font-size: 6rem;
- }
-
- nav ul {
- font-size: 1.25rem;
- gap: 1.5rem;
- }
- }
-
- @media screen and (min-width: 1024px) {
- h1 span {
- font-size: 8rem;
- }
- }
-
\ No newline at end of file
diff --git a/app/utils/request.server.ts b/app/utils/request.server.ts
new file mode 100644
index 0000000..5100e1a
--- /dev/null
+++ b/app/utils/request.server.ts
@@ -0,0 +1,8 @@
+import { json } from "@remix-run/node";
+
+/**
+ * This helper function helps us to return the accurate HTTP status,
+ * 400 Bad Request, to the client.
+ */
+export const badRequest = (data: T) =>
+ json(data, { status: 400 });
diff --git a/package-lock.json b/package-lock.json
index edfa6c4..39fc83a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,15 +10,20 @@
"@remix-run/node": "^2.9.1",
"@remix-run/react": "^2.9.1",
"@remix-run/serve": "^2.9.1",
+ "bootstrap": "^5.3.3",
"isbot": "^4.1.0",
"react": "^18.2.0",
+ "react-bootstrap": "^2.10.2",
"react-dom": "^18.2.0",
+ "react-router-bootstrap": "^0.26.2",
"tsx": "^4.7.3"
},
"devDependencies": {
"@remix-run/dev": "^2.9.1",
+ "@tailwindcss/forms": "^0.5.7",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
+ "@types/react-router-bootstrap": "^0.26.6",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"eslint": "^8.38.0",
@@ -45,6 +50,19 @@
"node": ">=0.10.0"
}
},
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -608,7 +626,6 @@
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
"integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
- "dev": true,
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
@@ -1455,6 +1472,15 @@
"node": ">=14"
}
},
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
"node_modules/@prisma/client": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.13.0.tgz",
@@ -1517,6 +1543,20 @@
"@prisma/debug": "5.13.0"
}
},
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.2",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.2.tgz",
+ "integrity": "sha512-0gKkgDYdnq1w+ey8KzG9l+H5Z821qh9vVjztk55rUg71vTk/Eaebeir+WtzcLLwTjw3m/asIjx8Y59y1lJZhBw==",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0"
+ }
+ },
"node_modules/@remix-run/dev": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@remix-run/dev/-/dev-2.9.1.tgz",
@@ -1780,6 +1820,45 @@
"web-streams-polyfill": "^3.1.1"
}
},
+ "node_modules/@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@restart/ui": {
+ "version": "1.6.8",
+ "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.6.8.tgz",
+ "integrity": "sha512-6ndCv3oZ7r9vuP1Ok9KH55TM1/UkdBnP/fSraW0DFDMbPMzWKhVKeFAIEUCRCSdzayjZDcFYK6xbMlipN9dmMA==",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0",
+ "@popperjs/core": "^2.11.6",
+ "@react-aria/ssr": "^3.5.0",
+ "@restart/hooks": "^0.4.9",
+ "@types/warning": "^3.0.0",
+ "dequal": "^2.0.3",
+ "dom-helpers": "^5.2.0",
+ "uncontrollable": "^8.0.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ }
+ },
+ "node_modules/@restart/ui/node_modules/uncontrollable": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz",
+ "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==",
+ "peerDependencies": {
+ "react": ">=16.14.0"
+ }
+ },
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.16.4",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.16.4.tgz",
@@ -1988,6 +2067,26 @@
"win32"
]
},
+ "node_modules/@swc/helpers": {
+ "version": "0.5.11",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz",
+ "integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==",
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/forms": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz",
+ "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==",
+ "dev": true,
+ "dependencies": {
+ "mini-svg-data-uri": "^1.2.3"
+ },
+ "peerDependencies": {
+ "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1"
+ }
+ },
"node_modules/@tsconfig/node10": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
@@ -2112,14 +2211,12 @@
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
- "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
- "dev": true
+ "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q=="
},
"node_modules/@types/react": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.0.tgz",
"integrity": "sha512-DiUcKjzE6soLyln8NNZmyhcQjVv+WsUIFSqetMN0p8927OztKT4VTfFTqsbAi5oAGIcgOmOajlfBqyptDDjZRw==",
- "dev": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
@@ -2134,6 +2231,23 @@
"@types/react": "*"
}
},
+ "node_modules/@types/react-router-bootstrap": {
+ "version": "0.26.6",
+ "resolved": "https://registry.npmjs.org/@types/react-router-bootstrap/-/react-router-bootstrap-0.26.6.tgz",
+ "integrity": "sha512-uqDwCdHp/qkXUPd8iOgZ92HQnTfyjpIwqgvbenW5SejrN3l6bkcObZQ/kkirHk39AQK4MJVpQBvcSu/SE6Crnw==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/react-transition-group": {
+ "version": "4.4.10",
+ "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
+ "integrity": "sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
@@ -2146,6 +2260,11 @@
"integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==",
"dev": true
},
+ "node_modules/@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q=="
+ },
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
@@ -2531,6 +2650,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
@@ -2897,6 +3023,24 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
+ "node_modules/bootstrap": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz",
+ "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/twbs"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/bootstrap"
+ }
+ ],
+ "peerDependencies": {
+ "@popperjs/core": "^2.11.8"
+ }
+ },
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -3072,6 +3216,16 @@
"node": ">=6"
}
},
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001612",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001612.tgz",
@@ -3190,6 +3344,11 @@
"node": ">=10"
}
},
+ "node_modules/classnames": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz",
+ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="
+ },
"node_modules/clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -3260,6 +3419,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
@@ -3443,8 +3612,7 @@
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -3619,7 +3787,6 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
"integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -3633,6 +3800,13 @@
"npm": "1.2.8000 || >= 1.4.16"
}
},
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/diff": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz",
@@ -3654,6 +3828,13 @@
"node": ">=8"
}
},
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@@ -3666,6 +3847,15 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
@@ -5563,6 +5753,14 @@
"node": ">= 0.4"
}
},
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -6139,6 +6337,16 @@
"integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==",
"dev": true
},
+ "node_modules/jiti": {
+ "version": "1.21.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz",
+ "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -6295,6 +6503,13 @@
"url": "https://github.com/sponsors/antonk52"
}
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/loader-utils": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz",
@@ -7327,6 +7542,15 @@
"node": ">=6"
}
},
+ "node_modules/mini-svg-data-uri": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz",
+ "integrity": "sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==",
+ "dev": true,
+ "bin": {
+ "mini-svg-data-uri": "cli.js"
+ }
+ },
"node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
@@ -7579,6 +7803,18 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@@ -7707,11 +7943,20 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
@@ -8145,6 +8390,26 @@
"node": ">=0.10"
}
},
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
+ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/pkg-types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz",
@@ -8204,6 +8469,44 @@
"postcss": "^8.2.15"
}
},
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
+ "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
"node_modules/postcss-load-config": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
@@ -8317,6 +8620,26 @@
"postcss": "^8.1.0"
}
},
+ "node_modules/postcss-nested": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
+ "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "postcss-selector-parser": "^6.0.11"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
"node_modules/postcss-selector-parser": {
"version": "6.0.16",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz",
@@ -8429,13 +8752,24 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
+ "node_modules/prop-types-extra": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz",
+ "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==",
+ "dependencies": {
+ "react-is": "^16.3.2",
+ "warning": "^4.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=0.14.0"
+ }
+ },
"node_modules/property-information": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
@@ -8563,6 +8897,35 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-bootstrap": {
+ "version": "2.10.2",
+ "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.2.tgz",
+ "integrity": "sha512-UvB7mRqQjivdZNxJNEA2yOQRB7L9N43nBnKc33K47+cH90/ujmnMwatTCwQLu83gLhrzAl8fsa6Lqig/KLghaA==",
+ "dependencies": {
+ "@babel/runtime": "^7.22.5",
+ "@restart/hooks": "^0.4.9",
+ "@restart/ui": "^1.6.8",
+ "@types/react-transition-group": "^4.4.6",
+ "classnames": "^2.3.2",
+ "dom-helpers": "^5.2.1",
+ "invariant": "^2.2.4",
+ "prop-types": "^15.8.1",
+ "prop-types-extra": "^1.1.0",
+ "react-transition-group": "^4.4.5",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.14.8",
+ "react": ">=16.14.0",
+ "react-dom": ">=16.14.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/react-dom": {
"version": "18.3.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.0.tgz",
@@ -8578,8 +8941,12 @@
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
- "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+ },
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-refresh": {
"version": "0.14.1",
@@ -8604,6 +8971,18 @@
"react": ">=16.8"
}
},
+ "node_modules/react-router-bootstrap": {
+ "version": "0.26.2",
+ "resolved": "https://registry.npmjs.org/react-router-bootstrap/-/react-router-bootstrap-0.26.2.tgz",
+ "integrity": "sha512-YlpI9Xi+Uqp6zFAUO8D/wu6P8mr1ujqq+0V5MhJG1kx9dr/95fAMoGk4J+/CsysOkwtR3tYSac4DDWmHwXvC8w==",
+ "dependencies": {
+ "prop-types": "^15.7.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.13.1",
+ "react-router-dom": ">=6.0.0"
+ }
+ },
"node_modules/react-router-dom": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.0.tgz",
@@ -8620,6 +8999,31 @@
"react-dom": ">=16.8"
}
},
+ "node_modules/react-transition-group": {
+ "version": "4.4.5",
+ "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
+ "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
+ "dependencies": {
+ "@babel/runtime": "^7.5.5",
+ "dom-helpers": "^5.0.1",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16.6.0",
+ "react-dom": ">=16.6.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
@@ -8669,8 +9073,7 @@
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
- "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
- "dev": true
+ "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.2",
@@ -9582,6 +9985,29 @@
"inline-style-parser": "0.1.1"
}
},
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -9606,6 +10032,67 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwindcss": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz",
+ "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.5.3",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.0",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.0",
+ "lilconfig": "^2.1.0",
+ "micromatch": "^4.0.5",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.0.0",
+ "postcss": "^8.4.23",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.1",
+ "postcss-nested": "^6.0.1",
+ "postcss-selector-parser": "^6.0.11",
+ "resolve": "^1.22.2",
+ "sucrase": "^3.32.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/tailwindcss/node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "dev": true,
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
@@ -9721,6 +10208,29 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "peer": true,
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
"node_modules/through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@@ -9827,6 +10337,13 @@
"typescript": ">=4.2.0"
}
},
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "peer": true
+ },
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
@@ -9925,6 +10442,11 @@
"node": ">=6"
}
},
+ "node_modules/tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ },
"node_modules/tsx": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.7.3.tgz",
@@ -10473,6 +10995,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0"
+ }
+ },
"node_modules/undici": {
"version": "6.14.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.14.1.tgz",
@@ -11301,6 +11837,14 @@
"@esbuild/win32-x64": "0.20.2"
}
},
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
diff --git a/package.json b/package.json
index 841d16e..8898bcb 100644
--- a/package.json
+++ b/package.json
@@ -15,15 +15,20 @@
"@remix-run/node": "^2.9.1",
"@remix-run/react": "^2.9.1",
"@remix-run/serve": "^2.9.1",
+ "bootstrap": "^5.3.3",
"isbot": "^4.1.0",
"react": "^18.2.0",
+ "react-bootstrap": "^2.10.2",
"react-dom": "^18.2.0",
+ "react-router-bootstrap": "^0.26.2",
"tsx": "^4.7.3"
},
"devDependencies": {
"@remix-run/dev": "^2.9.1",
+ "@tailwindcss/forms": "^0.5.7",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
+ "@types/react-router-bootstrap": "^0.26.6",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"eslint": "^8.38.0",
@@ -43,4 +48,4 @@
"prisma": {
"seed": "tsx prisma/seed.ts"
}
-}
\ No newline at end of file
+}