diff --git a/assets/js/react/components/FieldSet.tsx b/assets/js/react/components/FieldSet.tsx
new file mode 100644
index 0000000..ef5dd11
--- /dev/null
+++ b/assets/js/react/components/FieldSet.tsx
@@ -0,0 +1,66 @@
+import * as React from "react"
+
+export const FieldContext = React.createContext<{
+ required?: boolean
+ disabled?: boolean
+ error?: string | boolean
+}>({})
+
+function Wrapper(props: React.ComponentPropsWithoutRef<"div">) {
+ const fieldContext = React.useContext(FieldContext)
+ const { children, ...other } = props
+
+ return (
+ <>
+
{children}
+ {typeof fieldContext?.error === "string" && (
+ {fieldContext?.error}
+ )}
+ >
+ )
+}
+
+type WrapperProps =
+ React.ComponentPropsWithoutRef & {
+ slotProps?: {
+ root?: React.ComponentPropsWithoutRef<"div">
+ }
+ }
+
+export const Field = React.forwardRef<
+ HTMLDivElement,
+ WrapperProps<"div"> & {
+ required?: boolean
+ disabled?: boolean
+ error?: string | boolean
+ }
+>(function Field(props, ref) {
+ const { children, slotProps, required, disabled, error, ...other } = props
+
+ return (
+
+
+ {children}
+
+
+ )
+})
+
+export const FieldSet = React.forwardRef<
+ HTMLFieldSetElement,
+ WrapperProps<"fieldset"> & {
+ required?: boolean
+ disabled?: boolean
+ error?: string | boolean
+ }
+>(function FieldSet(props, ref) {
+ const { children, slotProps, required, disabled, error, ...other } = props
+
+ return (
+
+
+ {children}
+
+
+ )
+})
diff --git a/assets/js/react/components/FilterModal.tsx b/assets/js/react/components/FilterModal.tsx
new file mode 100644
index 0000000..980f3b3
--- /dev/null
+++ b/assets/js/react/components/FilterModal.tsx
@@ -0,0 +1,138 @@
+import * as React from "react"
+import { Controller, useForm } from "react-hook-form"
+
+import { Button } from "./Button"
+import { Field } from "./FieldSet"
+import { Input } from "./Input"
+import { Label } from "./Label"
+import { Modal } from "./Modal"
+import { Slider } from "./Slider"
+
+import {
+ FIDE_RATING_MIN,
+ FIDE_RATING_MAX,
+ SearchParams,
+} from "../types/SearchParams"
+
+const computeStepLabels = (
+ min: number,
+ max: number,
+ // The number of labels (+ 1) that should be produced.
+ steps: number,
+ // To which value numbers should be rounded to.
+ round: number
+) => {
+ let labels = []
+ const delta = Math.floor((max - min) / steps)
+ for (let i = min; i <= max; i += delta) {
+ if (i % round <= round / 2) {
+ labels.push(i - (i % round))
+ } else {
+ labels.push(i + round - (i % round))
+ }
+ }
+
+ labels[labels.length - 1] = max
+
+ return labels
+}
+
+interface FilterModalProps {
+ open: boolean
+ defaultValues: SearchParams
+ onClose: () => void
+ onSubmit: (p: SearchParams) => void
+}
+
+export function FilterModal({
+ open,
+ defaultValues,
+ onClose,
+ onSubmit,
+}: FilterModalProps) {
+ const idPrefix = React.useId()
+
+ const { watch, reset, control, register, setValue, handleSubmit } =
+ useForm({ defaultValues })
+
+ // Default values are processed immediately despite the modal not being open
+ // at the start. Furthermore, values are preserved after closing and
+ // re-opening the modal, but we want closing the modal to signify canceling.
+ // A simple workaround is to reset everytime we open the modal.
+ React.useEffect(() => reset(defaultValues), [open])
+
+ // Registration
+
+ const controlFIDERating = register("fideRating")
+
+ return (
+
+ Search coaches
+
+ ),
+ onSubmit: handleSubmit(onSubmit),
+ }}
+ >
+
+
+ FIDE Rating:
+
+ Find coaches that have a rating within the specified range. Keep in
+ mind, a higher rating does not necessarily mean a better coach{" "}
+ for you . If you are unsure of this or do not have any
+ preference, leave as is.
+
+
+
(
+ {
+ event && onChange(event)
+ setValue("fideRating.0", newValue[0])
+ setValue("fideRating.1", newValue[1])
+ }}
+ step={10}
+ min={FIDE_RATING_MIN}
+ max={FIDE_RATING_MAX}
+ marks={computeStepLabels(
+ FIDE_RATING_MIN,
+ FIDE_RATING_MAX,
+ 7,
+ 50
+ ).map((s) => ({ value: s, label: `${s}` }))}
+ />
+ )}
+ />
+
+
+
+
+
+ )
+}
diff --git a/assets/js/react/components/Input.tsx b/assets/js/react/components/Input.tsx
new file mode 100644
index 0000000..d8dbbc7
--- /dev/null
+++ b/assets/js/react/components/Input.tsx
@@ -0,0 +1,41 @@
+import * as React from "react"
+import clsx from "clsx"
+import {
+ Input as BaseInput,
+ InputProps,
+ InputOwnerState,
+} from "@mui/base/Input"
+
+import { FieldContext } from "./FieldSet"
+import { resolveSlotProps } from "../utils/props"
+
+export const Input = React.forwardRef(
+ function Input(props: InputProps, ref: React.ForwardedRef) {
+ const fieldContext = React.useContext(FieldContext)
+ const { disabled = fieldContext?.disabled, ...other } = props
+
+ const inputSlotProps = (ownerState: InputOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.input, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "text-sm font-normal leading-5 px-3 py-2 rounded-lg shadow-md border border-solid shadow-slate-100 focus:shadow-lg bg-white text-slate-900",
+ fieldContext?.error
+ ? "border-amber-800 focus-visible:outline focus-visible:outline-1 focus-visible:outline-amber-800"
+ : "border-slate-300",
+ { "opacity-60": ownerState.disabled },
+ resolved?.className
+ ),
+ }
+ }
+
+ return (
+
+ )
+ }
+)
diff --git a/assets/js/react/components/Label.tsx b/assets/js/react/components/Label.tsx
new file mode 100644
index 0000000..59eb5b5
--- /dev/null
+++ b/assets/js/react/components/Label.tsx
@@ -0,0 +1,36 @@
+import * as React from "react"
+import clsx from "clsx"
+
+import { FieldContext } from "./FieldSet"
+
+type LabelProps = {
+ children: React.ReactNode
+ sublabel?: boolean
+} & React.ComponentPropsWithoutRef<"label">
+
+export const Label = React.forwardRef(
+ function Label(props, ref) {
+ const fieldContext = React.useContext(FieldContext)
+ const { children, sublabel, className, ...other } = props
+
+ return (
+
+ {children}
+
+ )
+ }
+)
diff --git a/assets/js/react/components/Modal.tsx b/assets/js/react/components/Modal.tsx
new file mode 100644
index 0000000..7486722
--- /dev/null
+++ b/assets/js/react/components/Modal.tsx
@@ -0,0 +1,93 @@
+import * as React from "react"
+import clsx from "clsx"
+import {
+ Modal as BaseModal,
+ ModalBackdropSlotProps,
+ ModalProps,
+} from "@mui/base/Modal"
+
+import XIcon from "../icons/X"
+import { FadeIn } from "./FadeIn"
+
+const Backdrop = React.forwardRef(
+ function Backdrop(props, ref) {
+ const { open, ownerState, onClick, ...other } = props
+
+ return (
+
+ )
+ }
+)
+
+type FrameProps =
+ React.ComponentPropsWithoutRef & {
+ as?: T
+ title: string
+ footer: React.ReactNode
+ onClose?: ModalProps["onClose"]
+ }
+
+function Frame(props: FrameProps) {
+ const { as, title, footer, onClose, children, ...other } = props
+ let Component = as ?? "div"
+
+ return (
+
+
+
+ {title}
+
+ {children}
+ {footer}
+
+ )
+}
+
+export function Modal(
+ props: ModalProps & { frame?: FrameProps }
+) {
+ const { className, children, frame, onClose, ...other } = props
+
+ return (
+
+
+
+ {frame ? (
+
+ {children}
+
+ ) : (
+ children
+ )}
+
+
+
+ )
+}
+
+Modal.displayName = "Modal"
diff --git a/assets/js/react/components/Slider.tsx b/assets/js/react/components/Slider.tsx
new file mode 100644
index 0000000..cf680ba
--- /dev/null
+++ b/assets/js/react/components/Slider.tsx
@@ -0,0 +1,101 @@
+import * as React from "react"
+import clsx from "clsx"
+import {
+ Slider as BaseSlider,
+ SliderProps as BaseSliderProps,
+ SliderOwnerState,
+} from "@mui/base/Slider"
+
+import { resolveSlotProps } from "../utils/props"
+
+export type SliderProps = BaseSliderProps
+
+export const Slider = React.forwardRef(
+ function Slider(
+ props: SliderProps,
+ ref: React.ForwardedRef
+ ) {
+ const rootSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.root, ownerState)
+ return {
+ ...resolved,
+ className: clsx("hover:opacity-100", resolved?.className),
+ }
+ }
+
+ const railSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.rail, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "block absolute w-full h-1 rounded-sm bg-slate-300",
+ resolved?.className
+ ),
+ }
+ }
+
+ const trackSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.track, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "block absolute w-full h-1 rounded-sm bg-current",
+ resolved?.className
+ ),
+ }
+ }
+
+ const thumbSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.thumb, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "absolute w-6 h-6 -ml-2 -mt-2.5 box-border rounded-full bg-current hover:shadow hover:shadow-slate-300",
+ resolved?.className
+ ),
+ }
+ }
+
+ const markSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.mark, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "absolute w-1 h-1 opacity-70 bg-current rounded-sm -translate-x-2/4 top-1/2",
+ resolved?.className
+ ),
+ }
+ }
+
+ const markLabelSlotProps = (ownerState: SliderOwnerState) => {
+ const resolved = resolveSlotProps(props.slotProps?.markLabel, ownerState)
+ return {
+ ...resolved,
+ className: clsx(
+ "absolute text-sm top-5 -translate-x-1/2",
+ resolved?.className
+ ),
+ }
+ }
+
+ return (
+
+ )
+ }
+)
diff --git a/assets/js/react/pages/Search.tsx b/assets/js/react/pages/Search.tsx
index 01b7ffc..a71c646 100644
--- a/assets/js/react/pages/Search.tsx
+++ b/assets/js/react/pages/Search.tsx
@@ -3,14 +3,15 @@ import axios from "axios"
import { useQuery } from "@tanstack/react-query"
import type { Coach } from "../types/Coach"
-import { type SearchParams, defaultSearchParams } from "../types/SearchParams"
import { Container } from "../components/Container"
import { FadeIn, FadeInStagger } from "../components/FadeIn"
import { FallbackMessage } from "../components/FallbackMessage"
+import { FilterModal } from "../components/FilterModal"
import { FilterScroll } from "../components/FilterScroll"
import { Loading } from "../components/Loading"
import { SearchResult } from "../components/SearchResult"
+import { defaultSearchParams } from "../types/SearchParams"
function SearchResults() {
const { isLoading, isError, data } = useQuery({
@@ -54,13 +55,23 @@ function SearchResults() {
export function Search() {
const [searchParams, setSearchParams] = React.useState(defaultSearchParams)
+ const [modalOpen, setModalOpen] = React.useState(false)
return (
{}}
+ onModal={() => setModalOpen(true)}
+ />
+ setModalOpen(false)}
+ onSubmit={(q) => {
+ setSearchParams(q)
+ setModalOpen(false)
+ }}
/>
diff --git a/assets/js/react/types/SearchParams.ts b/assets/js/react/types/SearchParams.ts
index 77204c1..847d99d 100644
--- a/assets/js/react/types/SearchParams.ts
+++ b/assets/js/react/types/SearchParams.ts
@@ -2,8 +2,8 @@ export type SearchParams = {
fideRating: [number, number]
}
-const FIDE_RATING_MIN = 1500
-const FIDE_RATING_MAX = 3200
+export const FIDE_RATING_MIN = 1500
+export const FIDE_RATING_MAX = 3200
export const defaultSearchParams: SearchParams = {
fideRating: [FIDE_RATING_MIN, FIDE_RATING_MAX],
diff --git a/assets/js/react/utils/props.ts b/assets/js/react/utils/props.ts
new file mode 100644
index 0000000..d6b7afc
--- /dev/null
+++ b/assets/js/react/utils/props.ts
@@ -0,0 +1,11 @@
+// Used when overriding slot props. `SlotComponentProps` (the generic type being
+// overridden) is one of the following:
+//
+// 1. `Partial> & TOverrides`
+// 2. `(ownerState: TOwnerState) =>
+// Partial> & TOverrides`
+//
+// Refer to `mui/base/utils/types.d.ts`. The following checks which of the two
+// we're working with and returns the `Partial` type.
+export const resolveSlotProps = (fn: any, args: any) =>
+ typeof fn === "function" ? fn(args) : fn
diff --git a/assets/node-packages.nix b/assets/node-packages.nix
index 94ee809..e1db8f7 100644
--- a/assets/node-packages.nix
+++ b/assets/node-packages.nix
@@ -4,6 +4,15 @@
let
sources = {
+ "@babel/runtime-7.23.5" = {
+ name = "_at_babel_slash_runtime";
+ packageName = "@babel/runtime";
+ version = "7.23.5";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.5.tgz";
+ sha512 = "NdUTHcPe4C99WxPub+K9l9tK5/lV4UXIoaHSYgzco9BCyjKAAwzdBI+wWtYqHt7LJdbo74ZjRPJgzVweq1sz0w==";
+ };
+ };
"@emotion/is-prop-valid-0.8.8" = {
name = "_at_emotion_slash_is-prop-valid";
packageName = "@emotion/is-prop-valid";
@@ -22,6 +31,78 @@ let
sha512 = "Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==";
};
};
+ "@floating-ui/core-1.5.1" = {
+ name = "_at_floating-ui_slash_core";
+ packageName = "@floating-ui/core";
+ version = "1.5.1";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.1.tgz";
+ sha512 = "QgcKYwzcc8vvZ4n/5uklchy8KVdjJwcOeI+HnnTNclJjs2nYsy23DOCf+sSV1kBwD9yDAoVKCkv/gEPzgQU3Pw==";
+ };
+ };
+ "@floating-ui/dom-1.5.3" = {
+ name = "_at_floating-ui_slash_dom";
+ packageName = "@floating-ui/dom";
+ version = "1.5.3";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz";
+ sha512 = "ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==";
+ };
+ };
+ "@floating-ui/react-dom-2.0.4" = {
+ name = "_at_floating-ui_slash_react-dom";
+ packageName = "@floating-ui/react-dom";
+ version = "2.0.4";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz";
+ sha512 = "CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==";
+ };
+ };
+ "@floating-ui/utils-0.1.6" = {
+ name = "_at_floating-ui_slash_utils";
+ packageName = "@floating-ui/utils";
+ version = "0.1.6";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz";
+ sha512 = "OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==";
+ };
+ };
+ "@mui/base-5.0.0-beta.25" = {
+ name = "_at_mui_slash_base";
+ packageName = "@mui/base";
+ version = "5.0.0-beta.25";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.25.tgz";
+ sha512 = "Iiv+IcappRRv6IBlknIVmLkXxfp51NEX1+l9f+dIbBuPU4PaRULegr1lCeHKsC45KU5ruxM5xMg4R/de03aJQg==";
+ };
+ };
+ "@mui/types-7.2.10" = {
+ name = "_at_mui_slash_types";
+ packageName = "@mui/types";
+ version = "7.2.10";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@mui/types/-/types-7.2.10.tgz";
+ sha512 = "wX1vbDC+lzF7FlhT6A3ffRZgEoKWPF8VqRoTu4lZwouFX2t90KyCMsgepMw5DxLak1BSp/KP86CmtZttikb/gQ==";
+ };
+ };
+ "@mui/utils-5.14.19" = {
+ name = "_at_mui_slash_utils";
+ packageName = "@mui/utils";
+ version = "5.14.19";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@mui/utils/-/utils-5.14.19.tgz";
+ sha512 = "qAHvTXzk7basbyqPvhgWqN6JbmI2wLB/mf97GkSlz5c76MiKYV6Ffjvw9BjKZQ1YRb8rDX9kgdjRezOcoB91oQ==";
+ };
+ };
+ "@popperjs/core-2.11.8" = {
+ name = "_at_popperjs_slash_core";
+ packageName = "@popperjs/core";
+ version = "2.11.8";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz";
+ sha512 = "P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==";
+ };
+ };
"@remix-run/router-1.13.1" = {
name = "_at_remix-run_slash_router";
packageName = "@remix-run/router";
@@ -31,6 +112,69 @@ let
sha512 = "so+DHzZKsoOcoXrILB4rqDkMDy7NLMErRdOxvzvOKb507YINKUP4Di+shbTZDhSE/pBZ+vr7XGIpcOO0VLSA+Q==";
};
};
+ "@tanstack/query-core-5.12.1" = {
+ name = "_at_tanstack_slash_query-core";
+ packageName = "@tanstack/query-core";
+ version = "5.12.1";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.12.1.tgz";
+ sha512 = "WbZztNmKq0t6QjdNmHzezbi/uifYo9j6e2GLJkodsYaYUlzMbAp91RDyeHkIZrm7EfO4wa6Sm5sxJZm5SPlh6w==";
+ };
+ };
+ "@tanstack/react-query-5.12.2" = {
+ name = "_at_tanstack_slash_react-query";
+ packageName = "@tanstack/react-query";
+ version = "5.12.2";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.12.2.tgz";
+ sha512 = "BeWZu8zVFH20oRc+S/K9ADPgWjEzP/XQCGBNz5IbApUwPQAdwkQYbXODVL5AyAlWiSxhx+P2xlARPBApj2Yrog==";
+ };
+ };
+ "@types/prop-types-15.7.11" = {
+ name = "_at_types_slash_prop-types";
+ packageName = "@types/prop-types";
+ version = "15.7.11";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz";
+ sha512 = "ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==";
+ };
+ };
+ "@types/react-18.2.40" = {
+ name = "_at_types_slash_react";
+ packageName = "@types/react";
+ version = "18.2.40";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@types/react/-/react-18.2.40.tgz";
+ sha512 = "H+BUhb9C1zBtogDLAk+KCNRKiHDrqSwQT/0z0PVTwMFBxqg3011ByLomADtgkgMkfwj4AMOiXBReyLTUBg681g==";
+ };
+ };
+ "@types/scheduler-0.16.8" = {
+ name = "_at_types_slash_scheduler";
+ packageName = "@types/scheduler";
+ version = "0.16.8";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz";
+ sha512 = "WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==";
+ };
+ };
+ "asynckit-0.4.0" = {
+ name = "asynckit";
+ packageName = "asynckit";
+ version = "0.4.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz";
+ sha512 = "Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==";
+ };
+ };
+ "axios-1.6.2" = {
+ name = "axios";
+ packageName = "axios";
+ version = "1.6.2";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz";
+ sha512 = "7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==";
+ };
+ };
"clsx-2.0.0" = {
name = "clsx";
packageName = "clsx";
@@ -40,6 +184,51 @@ let
sha512 = "rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==";
};
};
+ "combined-stream-1.0.8" = {
+ name = "combined-stream";
+ packageName = "combined-stream";
+ version = "1.0.8";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz";
+ sha512 = "FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==";
+ };
+ };
+ "csstype-3.1.2" = {
+ name = "csstype";
+ packageName = "csstype";
+ version = "3.1.2";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz";
+ sha512 = "I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==";
+ };
+ };
+ "delayed-stream-1.0.0" = {
+ name = "delayed-stream";
+ packageName = "delayed-stream";
+ version = "1.0.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz";
+ sha512 = "ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==";
+ };
+ };
+ "follow-redirects-1.15.3" = {
+ name = "follow-redirects";
+ packageName = "follow-redirects";
+ version = "1.15.3";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz";
+ sha512 = "1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==";
+ };
+ };
+ "form-data-4.0.0" = {
+ name = "form-data";
+ packageName = "form-data";
+ version = "4.0.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz";
+ sha512 = "ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==";
+ };
+ };
"framer-motion-10.16.12" = {
name = "framer-motion";
packageName = "framer-motion";
@@ -67,6 +256,51 @@ let
sha512 = "lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==";
};
};
+ "mime-db-1.52.0" = {
+ name = "mime-db";
+ packageName = "mime-db";
+ version = "1.52.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz";
+ sha512 = "sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==";
+ };
+ };
+ "mime-types-2.1.35" = {
+ name = "mime-types";
+ packageName = "mime-types";
+ version = "2.1.35";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz";
+ sha512 = "ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==";
+ };
+ };
+ "object-assign-4.1.1" = {
+ name = "object-assign";
+ packageName = "object-assign";
+ version = "4.1.1";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz";
+ sha512 = "rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==";
+ };
+ };
+ "prop-types-15.8.1" = {
+ name = "prop-types";
+ packageName = "prop-types";
+ version = "15.8.1";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz";
+ sha512 = "oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==";
+ };
+ };
+ "proxy-from-env-1.1.0" = {
+ name = "proxy-from-env";
+ packageName = "proxy-from-env";
+ version = "1.1.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz";
+ sha512 = "D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==";
+ };
+ };
"react-18.2.0" = {
name = "react";
packageName = "react";
@@ -85,6 +319,33 @@ let
sha512 = "6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==";
};
};
+ "react-hook-form-7.48.2" = {
+ name = "react-hook-form";
+ packageName = "react-hook-form";
+ version = "7.48.2";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz";
+ sha512 = "H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==";
+ };
+ };
+ "react-is-16.13.1" = {
+ name = "react-is";
+ packageName = "react-is";
+ version = "16.13.1";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz";
+ sha512 = "24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==";
+ };
+ };
+ "react-is-18.2.0" = {
+ name = "react-is";
+ packageName = "react-is";
+ version = "18.2.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz";
+ sha512 = "xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==";
+ };
+ };
"react-router-6.20.1" = {
name = "react-router";
packageName = "react-router";
@@ -103,6 +364,15 @@ let
sha512 = "npzfPWcxfQN35psS7rJgi/EW0Gx6EsNjfdJSAk73U/HqMEJZ2k/8puxfwHFgDQhBGmS3+sjnGbMdMSV45axPQw==";
};
};
+ "regenerator-runtime-0.14.0" = {
+ name = "regenerator-runtime";
+ packageName = "regenerator-runtime";
+ version = "0.14.0";
+ src = fetchurl {
+ url = "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz";
+ sha512 = "srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==";
+ };
+ };
"scheduler-0.23.0" = {
name = "scheduler";
packageName = "scheduler";
@@ -128,17 +398,50 @@ let
version = "0.1.0";
src = ./.;
dependencies = [
+ sources."@babel/runtime-7.23.5"
sources."@emotion/is-prop-valid-0.8.8"
sources."@emotion/memoize-0.7.4"
+ sources."@floating-ui/core-1.5.1"
+ sources."@floating-ui/dom-1.5.3"
+ sources."@floating-ui/react-dom-2.0.4"
+ sources."@floating-ui/utils-0.1.6"
+ sources."@mui/base-5.0.0-beta.25"
+ sources."@mui/types-7.2.10"
+ sources."@mui/utils-5.14.19"
+ sources."@popperjs/core-2.11.8"
sources."@remix-run/router-1.13.1"
+ sources."@tanstack/query-core-5.12.1"
+ sources."@tanstack/react-query-5.12.2"
+ sources."@types/prop-types-15.7.11"
+ sources."@types/react-18.2.40"
+ sources."@types/scheduler-0.16.8"
+ sources."asynckit-0.4.0"
+ sources."axios-1.6.2"
sources."clsx-2.0.0"
+ sources."combined-stream-1.0.8"
+ sources."csstype-3.1.2"
+ sources."delayed-stream-1.0.0"
+ sources."follow-redirects-1.15.3"
+ sources."form-data-4.0.0"
sources."framer-motion-10.16.12"
sources."js-tokens-4.0.0"
sources."loose-envify-1.4.0"
+ sources."mime-db-1.52.0"
+ sources."mime-types-2.1.35"
+ sources."object-assign-4.1.1"
+ (sources."prop-types-15.8.1" // {
+ dependencies = [
+ sources."react-is-16.13.1"
+ ];
+ })
+ sources."proxy-from-env-1.1.0"
sources."react-18.2.0"
sources."react-dom-18.2.0"
+ sources."react-hook-form-7.48.2"
+ sources."react-is-18.2.0"
sources."react-router-6.20.1"
sources."react-router-dom-6.20.1"
+ sources."regenerator-runtime-0.14.0"
sources."scheduler-0.23.0"
sources."tslib-2.6.2"
];
diff --git a/assets/package-lock.json b/assets/package-lock.json
index f9128d6..f56f65a 100644
--- a/assets/package-lock.json
+++ b/assets/package-lock.json
@@ -15,6 +15,7 @@
"framer-motion": "^10.16.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-hook-form": "^7.48.2",
"react-router-dom": "^6.20.1"
},
"devDependencies": {
@@ -465,6 +466,21 @@
"react": "^18.2.0"
}
},
+ "node_modules/react-hook-form": {
+ "version": "7.48.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz",
+ "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==",
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/react-hook-form"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17 || ^18"
+ }
+ },
"node_modules/react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
@@ -818,6 +834,12 @@
"scheduler": "^0.23.0"
}
},
+ "react-hook-form": {
+ "version": "7.48.2",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.48.2.tgz",
+ "integrity": "sha512-H0T2InFQb1hX7qKtDIZmvpU1Xfn/bdahWBN1fH19gSe4bBEqTfmlr7H3XWTaVtiK4/tpPaI1F3355GPMZYge+A==",
+ "requires": {}
+ },
"react-is": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
diff --git a/assets/package.json b/assets/package.json
index f94ddb6..ca357ae 100644
--- a/assets/package.json
+++ b/assets/package.json
@@ -9,6 +9,7 @@
"framer-motion": "^10.16.12",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-hook-form": "^7.48.2",
"react-router-dom": "^6.20.1"
},
"devDependencies": {
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..4ff32e6
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,6 @@
+{
+ "name": "website",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}