diff --git a/prefs_page/README.md b/prefs_page/README.md
new file mode 100644
index 0000000..74872fd
--- /dev/null
+++ b/prefs_page/README.md
@@ -0,0 +1,50 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+export default tseslint.config({
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
+- Optionally add `...tseslint.configs.stylisticTypeChecked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
+
+```js
+// eslint.config.js
+import react from 'eslint-plugin-react'
+
+export default tseslint.config({
+ // Set the react version
+ settings: { react: { version: '18.3' } },
+ plugins: {
+ // Add the react plugin
+ react,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended rules
+ ...react.configs.recommended.rules,
+ ...react.configs['jsx-runtime'].rules,
+ },
+})
+```
diff --git a/prefs_page/index.html b/prefs_page/index.html
new file mode 100644
index 0000000..ec5e6b7
--- /dev/null
+++ b/prefs_page/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ Team preference
+
+
+
+
+
+
diff --git a/prefs_page/package.json b/prefs_page/package.json
new file mode 100644
index 0000000..a9c449e
--- /dev/null
+++ b/prefs_page/package.json
@@ -0,0 +1,33 @@
+{
+ "name": "prefs",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@emotion/react": "^11.13.3",
+ "@emotion/styled": "^11.13.0",
+ "@mui/icons-material": "^6.1.5",
+ "@mui/material": "^6.1.5",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.13.0",
+ "@types/react": "^18.3.11",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.3",
+ "eslint": "^9.13.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.13",
+ "globals": "^15.11.0",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.10.0",
+ "vite": "^5.4.9"
+ }
+}
diff --git a/prefs_page/public/logo.svg b/prefs_page/public/logo.svg
new file mode 100644
index 0000000..f674658
--- /dev/null
+++ b/prefs_page/public/logo.svg
@@ -0,0 +1,55 @@
+
+
+
+
diff --git a/prefs_page/src/App.css b/prefs_page/src/App.css
new file mode 100644
index 0000000..3800e3c
--- /dev/null
+++ b/prefs_page/src/App.css
@@ -0,0 +1,14 @@
+#root {
+ max-width: 60vw;
+ margin: 0 auto;
+ padding: 2rem;
+ text-align: center;
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
diff --git a/prefs_page/src/App.tsx b/prefs_page/src/App.tsx
new file mode 100644
index 0000000..fc262ea
--- /dev/null
+++ b/prefs_page/src/App.tsx
@@ -0,0 +1,204 @@
+import "./App.css";
+import names from "./players.json";
+import * as React from "react";
+import { Theme, useTheme } from "@mui/material/styles";
+import Box from "@mui/material/Box";
+import OutlinedInput from "@mui/material/OutlinedInput";
+import InputLabel from "@mui/material/InputLabel";
+import MenuItem from "@mui/material/MenuItem";
+import FormControl from "@mui/material/FormControl";
+import Select, { SelectChangeEvent } from "@mui/material/Select";
+import Chip from "@mui/material/Chip";
+import {
+ Alert,
+ Button,
+ Dialog,
+ DialogTitle,
+ FormHelperText,
+ Stack,
+} from "@mui/material";
+
+function getStyles(name: string, players: readonly string[], theme: Theme) {
+ return {
+ fontWeight: players.includes(name)
+ ? theme.typography.fontWeightMedium
+ : theme.typography.fontWeightRegular,
+ };
+}
+
+async function submit(
+ person: string,
+ players: string[],
+ setResponseStatus: (value: number) => void
+) {
+ // console.log(JSON.stringify({ person: person, players: players }));
+ const response = await fetch("https://0124816.xyz/team/submit/", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ person: person, players: players }),
+ });
+ setResponseStatus(response.status);
+}
+
+type SubmitButtonProps = {
+ person: string;
+ players: string[];
+};
+
+function SubmitButton(props: SubmitButtonProps) {
+ const [responseStatus, setResponseStatus] = React.useState(0);
+
+ return (
+
+
+
+
+ );
+}
+
+type MultiSelectProps = {
+ person: string;
+ setPerson: (value: string) => void;
+ players: string[];
+ setPlayers: (value: string[]) => void;
+};
+
+function MultipleSelectChip(props: MultiSelectProps) {
+ const ITEM_HEIGHT = 48;
+ const ITEM_PADDING_TOP = 8;
+ const MenuProps = {
+ PaperProps: {
+ style: {
+ maxHeight: ITEM_HEIGHT * 6.5 + ITEM_PADDING_TOP,
+ width: 250,
+ },
+ },
+ };
+ const [alert, setAlert] = React.useState("none");
+ const maxLimit = 9;
+ const theme = useTheme();
+
+ const handlePersonChange = (
+ event: SelectChangeEvent
+ ) => {
+ const {
+ target: { value },
+ } = event;
+ props.setPerson(value);
+ const index = props.players.indexOf(value);
+ if (index > -1) {
+ props.players.splice(index, 1);
+ }
+ props.setPlayers(props.players);
+ };
+
+ const handleChange = (event: SelectChangeEvent) => {
+ const {
+ target: { value },
+ } = event;
+ if (value.length <= maxLimit) {
+ setAlert("none");
+ props.setPlayers(
+ // On autofill we get a stringified value.
+ typeof value === "string" ? value.split(",") : value
+ );
+ } else {
+ setAlert("");
+ }
+ };
+
+ return (
+
+
+
+ who are you?
+ }
+ MenuProps={MenuProps}
+ >
+ {names.map((name) => (
+
+ ))}
+
+
+
+
+ do not select more than {maxLimit} players
+
+
+
+ players
+ }
+ renderValue={(selected) => (
+
+ {selected.map((value) => (
+
+ ))}
+
+ )}
+ MenuProps={MenuProps}
+ >
+ {names.map((name) =>
+ name !== props.person ? (
+
+ ) : null
+ )}
+
+ max. {maxLimit}
+
+
+
+ );
+}
+
+function App() {
+ const [person, setPerson] = React.useState("");
+ const [players, setPlayers] = React.useState([]);
+
+ return (
+ <>
+ choose your best buddies for the Igloo
+
+ {MultipleSelectChip({ person, setPerson, players, setPlayers })}
+ {SubmitButton({ person, players })}
+
now: click submit.
+
+
+ something not working? message me.
+
+ >
+ );
+}
+
+export default App;
diff --git a/prefs_page/src/index.css b/prefs_page/src/index.css
new file mode 100644
index 0000000..6119ad9
--- /dev/null
+++ b/prefs_page/src/index.css
@@ -0,0 +1,68 @@
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/prefs_page/src/main.tsx b/prefs_page/src/main.tsx
new file mode 100644
index 0000000..bef5202
--- /dev/null
+++ b/prefs_page/src/main.tsx
@@ -0,0 +1,10 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import './index.css'
+import App from './App.tsx'
+
+createRoot(document.getElementById('root')!).render(
+
+
+ ,
+)
diff --git a/prefs_page/src/vite-env.d.ts b/prefs_page/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/prefs_page/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/prefs_page/tsconfig.app.json b/prefs_page/tsconfig.app.json
new file mode 100644
index 0000000..5a2def4
--- /dev/null
+++ b/prefs_page/tsconfig.app.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["src"]
+}
diff --git a/prefs_page/tsconfig.app.tsbuildinfo b/prefs_page/tsconfig.app.tsbuildinfo
new file mode 100644
index 0000000..60a28f6
--- /dev/null
+++ b/prefs_page/tsconfig.app.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts"],"version":"5.6.3"}
\ No newline at end of file
diff --git a/prefs_page/tsconfig.json b/prefs_page/tsconfig.json
new file mode 100644
index 0000000..1ffef60
--- /dev/null
+++ b/prefs_page/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "files": [],
+ "references": [
+ { "path": "./tsconfig.app.json" },
+ { "path": "./tsconfig.node.json" }
+ ]
+}
diff --git a/prefs_page/tsconfig.node.json b/prefs_page/tsconfig.node.json
new file mode 100644
index 0000000..9dad701
--- /dev/null
+++ b/prefs_page/tsconfig.node.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedSideEffectImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/prefs_page/tsconfig.node.tsbuildinfo b/prefs_page/tsconfig.node.tsbuildinfo
new file mode 100644
index 0000000..75ea001
--- /dev/null
+++ b/prefs_page/tsconfig.node.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./vite.config.ts"],"version":"5.6.3"}
\ No newline at end of file