From 3f4d7c722dc13b61cd1d5ce1503877e85bd1e0c1 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 11 Jun 2024 15:37:42 +0200 Subject: [PATCH 01/23] vinvoor: init --- vinvoor/.eslintrc.cjs | 18 + vinvoor/.gitignore | 24 + vinvoor/.tool-versions | 1 + vinvoor/Dockerfile | 22 + vinvoor/Dockerfile.dev | 7 + vinvoor/README.md | 17 + vinvoor/index.html | 13 + vinvoor/nginx.conf | 9 + vinvoor/package.json | 36 + vinvoor/public/zeus.svg | 2 + vinvoor/src/App.tsx | 5 + vinvoor/src/errors/ErrorPage.tsx | 28 + vinvoor/src/main.tsx | 23 + vinvoor/src/vite-env.d.ts | 1 + vinvoor/tsconfig.json | 25 + vinvoor/tsconfig.node.json | 11 + vinvoor/vite.config.ts | 7 + vinvoor/yarn.lock | 1998 ++++++++++++++++++++++++++++++ 18 files changed, 2247 insertions(+) create mode 100644 vinvoor/.eslintrc.cjs create mode 100644 vinvoor/.gitignore create mode 100644 vinvoor/.tool-versions create mode 100644 vinvoor/Dockerfile create mode 100644 vinvoor/Dockerfile.dev create mode 100644 vinvoor/README.md create mode 100644 vinvoor/index.html create mode 100644 vinvoor/nginx.conf create mode 100644 vinvoor/package.json create mode 100644 vinvoor/public/zeus.svg create mode 100644 vinvoor/src/App.tsx create mode 100644 vinvoor/src/errors/ErrorPage.tsx create mode 100644 vinvoor/src/main.tsx create mode 100644 vinvoor/src/vite-env.d.ts create mode 100644 vinvoor/tsconfig.json create mode 100644 vinvoor/tsconfig.node.json create mode 100644 vinvoor/vite.config.ts create mode 100644 vinvoor/yarn.lock diff --git a/vinvoor/.eslintrc.cjs b/vinvoor/.eslintrc.cjs new file mode 100644 index 0000000..d6c9537 --- /dev/null +++ b/vinvoor/.eslintrc.cjs @@ -0,0 +1,18 @@ +module.exports = { + root: true, + env: { browser: true, es2020: true }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react-hooks/recommended', + ], + ignorePatterns: ['dist', '.eslintrc.cjs'], + parser: '@typescript-eslint/parser', + plugins: ['react-refresh'], + rules: { + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, +} diff --git a/vinvoor/.gitignore b/vinvoor/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/vinvoor/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/vinvoor/.tool-versions b/vinvoor/.tool-versions new file mode 100644 index 0000000..dcf61b7 --- /dev/null +++ b/vinvoor/.tool-versions @@ -0,0 +1 @@ +nodejs 22.2.0 diff --git a/vinvoor/Dockerfile b/vinvoor/Dockerfile new file mode 100644 index 0000000..c48e4cd --- /dev/null +++ b/vinvoor/Dockerfile @@ -0,0 +1,22 @@ +FROM node:22.2.0-alpine3.19 as build-stage + +WORKDIR /app + +COPY package.json yarn.lock ./ + +RUN yarn install + +COPY ./ . + +RUN yarn run build + + +FROM nginx:alpine-slim as production-stage + +EXPOSE 3000 + +RUN mkdir /app + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +COPY --from=build-stage /app/dist /app diff --git a/vinvoor/Dockerfile.dev b/vinvoor/Dockerfile.dev new file mode 100644 index 0000000..5bb0f50 --- /dev/null +++ b/vinvoor/Dockerfile.dev @@ -0,0 +1,7 @@ +FROM node:22.2.0-alpine3.19 + +WORKDIR /frontend + +COPY package.json yarn.lock ./ + +CMD yarn install && yarn run host diff --git a/vinvoor/README.md b/vinvoor/README.md new file mode 100644 index 0000000..dad3819 --- /dev/null +++ b/vinvoor/README.md @@ -0,0 +1,17 @@ +# Vinvoor + +Keeping track of scans is cool and all but you need a place to show them. +That's what this does! + +## How to run (for development) + +- Install nodejs 22.2.0 +- Install yarn `npm install yarn` +- Install dependencies `yarn install` +- Start the frontend `yarn run dev` +- Visit: http://localhost:5173 + +## How to run (for production) + +- Build the image `docker build -t vinvoor:latest .`. +- Run the image `docker run -p 80:3000 vinvoor:latest`. diff --git a/vinvoor/index.html b/vinvoor/index.html new file mode 100644 index 0000000..1158735 --- /dev/null +++ b/vinvoor/index.html @@ -0,0 +1,13 @@ + + + + + + + ZeSS + + +
+ + + diff --git a/vinvoor/nginx.conf b/vinvoor/nginx.conf new file mode 100644 index 0000000..cdb8eaa --- /dev/null +++ b/vinvoor/nginx.conf @@ -0,0 +1,9 @@ +server { + listen 3000; + + location / { + root /app; + index index.html index.htm; + try_files $uri $uri/ /index.html; + } +} diff --git a/vinvoor/package.json b/vinvoor/package.json new file mode 100644 index 0000000..fb8f866 --- /dev/null +++ b/vinvoor/package.json @@ -0,0 +1,36 @@ +{ + "name": "vinvoor", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "host": "vite --host", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.11.4", + "@emotion/styled": "^11.11.5", + "@fontsource/roboto": "^5.0.13", + "@mui/icons-material": "^5.15.19", + "@mui/material": "^5.15.19", + "@types/react-router-dom": "^5.3.3", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.23.1" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react-swc": "^3.5.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/vinvoor/public/zeus.svg b/vinvoor/public/zeus.svg new file mode 100644 index 0000000..a18ec2b --- /dev/null +++ b/vinvoor/public/zeus.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx new file mode 100644 index 0000000..47f22a3 --- /dev/null +++ b/vinvoor/src/App.tsx @@ -0,0 +1,5 @@ +import { Typography } from "@mui/material"; + +export const App = () => { + return Zess Fronted, Coming Soon!; +}; diff --git a/vinvoor/src/errors/ErrorPage.tsx b/vinvoor/src/errors/ErrorPage.tsx new file mode 100644 index 0000000..60a47db --- /dev/null +++ b/vinvoor/src/errors/ErrorPage.tsx @@ -0,0 +1,28 @@ +import { isRouteErrorResponse, useRouteError } from "react-router-dom"; + +export const ErrorPage = () => { + const error = useRouteError(); + + return ( +
+

Oops!

+

Sorry, an unexpected error has occurred.

+

+ {get_error(error)} +

+
+ ); +}; + +const get_error = (error: unknown) => { + if (isRouteErrorResponse(error)) { + return `${error.status} ${error.statusText}`; + } else if (error instanceof Error) { + return error.message; + } else if (typeof error === "string") { + return error; + } else { + console.error(error); + return "Unknown error"; + } +}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx new file mode 100644 index 0000000..e76f59d --- /dev/null +++ b/vinvoor/src/main.tsx @@ -0,0 +1,23 @@ +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import { App } from "./App.tsx"; +import { ErrorPage } from "./errors/ErrorPage.tsx"; + +const router = createBrowserRouter([ + { + path: "/", + element: , + errorElement: , + }, +]); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + +); diff --git a/vinvoor/src/vite-env.d.ts b/vinvoor/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/vinvoor/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/vinvoor/tsconfig.json b/vinvoor/tsconfig.json new file mode 100644 index 0000000..a7fc6fb --- /dev/null +++ b/vinvoor/tsconfig.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, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/vinvoor/tsconfig.node.json b/vinvoor/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/vinvoor/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vinvoor/vite.config.ts b/vinvoor/vite.config.ts new file mode 100644 index 0000000..03d504e --- /dev/null +++ b/vinvoor/vite.config.ts @@ -0,0 +1,7 @@ +import react from "@vitejs/plugin-react-swc"; +import { defineConfig } from "vite"; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock new file mode 100644 index 0000000..3df7cc0 --- /dev/null +++ b/vinvoor/yarn.lock @@ -0,0 +1,1998 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== + dependencies: + "@babel/types" "^7.24.7" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-module-imports@^7.16.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== + +"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== + dependencies: + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/is-prop-valid@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337" + integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.4": + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3", "@emotion/serialize@^1.1.4": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" + integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/styled@^11.11.5": + version "11.11.5" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.11.5.tgz#0c5c8febef9d86e8a926e663b2e5488705545dfb" + integrity sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/is-prop-valid" "^1.2.2" + "@emotion/serialize" "^1.1.4" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + +"@esbuild/darwin-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz#90ed098e1f9dd8a9381695b207e1cff45540a0d0" + integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== + +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": + version "4.10.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.1.tgz#361461e5cb3845d874e61731c11cfedd664d83a0" + integrity sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA== + +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + +"@floating-ui/core@^1.0.0": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" + integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== + dependencies: + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/dom@^1.0.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" + integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.0.8": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" + integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/utils@^0.2.0": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" + integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== + +"@fontsource/roboto@^5.0.13": + version "5.0.13" + resolved "https://registry.yarnpkg.com/@fontsource/roboto/-/roboto-5.0.13.tgz#2d6ec431a2f9dfe38ca76525c2d6bf12241f575b" + integrity sha512-j61DHjsdUCKMXSdNLTOxcG701FWnF0jcqNNQi2iPCDxU8seN/sMxeh62dC++UiagCWq9ghTypX+Pcy7kX+QOeQ== + +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== + dependencies: + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" + integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" + integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@mui/base@5.0.0-beta.40": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + +"@mui/core-downloads-tracker@^5.15.19": + version "5.15.19" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.19.tgz#7af0025c871f126367a55219486681954e4821d7" + integrity sha512-tCHSi/Tomez9ERynFhZRvFO6n9ATyrPs+2N80DMDzp6xDVirbBjEwhPcE+x7Lj+nwYw0SqFkOxyvMP0irnm55w== + +"@mui/icons-material@^5.15.19": + version "5.15.19" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.15.19.tgz#0602da80d814af662812659eab891e435ec0d5c0" + integrity sha512-RsEiRxA5azN9b8gI7JRqekkgvxQUlitoBOtZglflb8cUDyP12/cP4gRwhb44Ea1/zwwGGjAj66ZJpGHhKfibNA== + dependencies: + "@babel/runtime" "^7.23.9" + +"@mui/material@^5.15.19": + version "5.15.19" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.19.tgz#a5bd50b6e68cee4ed39ea91dbecede5a020aaa97" + integrity sha512-lp5xQBbcRuxNtjpWU0BWZgIrv2XLUz4RJ0RqFXBdESIsKoGCQZ6P3wwU5ZPuj5TjssNiKv9AlM+vHopRxZhvVQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/core-downloads-tracker" "^5.15.19" + "@mui/system" "^5.15.15" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@types/react-transition-group" "^4.4.10" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + react-is "^18.2.0" + react-transition-group "^4.4.5" + +"@mui/private-theming@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee" + integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.15.14" + prop-types "^15.8.1" + +"@mui/styled-engine@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2" + integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/system@^5.15.15": + version "5.15.15" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.15.tgz#658771b200ce3c4a0f28e58169f02e5e718d1c53" + integrity sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.15.14" + "@mui/styled-engine" "^5.15.14" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.14": + version "7.2.14" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9" + integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ== + +"@mui/utils@^5.15.14": + version "5.15.14" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a" + integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA== + dependencies: + "@babel/runtime" "^7.23.9" + "@types/prop-types" "^15.7.11" + prop-types "^15.8.1" + react-is "^18.2.0" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@popperjs/core@^2.11.8": + version "2.11.8" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + +"@remix-run/router@1.16.1": + version "1.16.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.16.1.tgz#73db3c48b975eeb06d0006481bde4f5f2d17d1cd" + integrity sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig== + +"@rollup/rollup-android-arm-eabi@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" + integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== + +"@rollup/rollup-android-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" + integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== + +"@rollup/rollup-darwin-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" + integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== + +"@rollup/rollup-darwin-x64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz#e07d76de1cec987673e7f3d48ccb8e106d42c05c" + integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== + +"@rollup/rollup-linux-arm-gnueabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" + integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== + +"@rollup/rollup-linux-arm-musleabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" + integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== + +"@rollup/rollup-linux-arm64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" + integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== + +"@rollup/rollup-linux-arm64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" + integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" + integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== + +"@rollup/rollup-linux-riscv64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" + integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== + +"@rollup/rollup-linux-s390x-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" + integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== + +"@rollup/rollup-linux-x64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" + integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== + +"@rollup/rollup-linux-x64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" + integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== + +"@rollup/rollup-win32-arm64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" + integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== + +"@rollup/rollup-win32-ia32-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" + integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== + +"@rollup/rollup-win32-x64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" + integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== + +"@swc/core-darwin-arm64@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.5.27.tgz#659f01c7687d5615785cabecab0f985a4e433326" + integrity sha512-jyoygXBcUcwUya2BI7Uvl0jwcm4kd0RBDGGkWgcFAZmwucSuLT3EsbpWhOwlL3ACT4rpnRlvh+k8nJlq3+w2Aw== + +"@swc/core-darwin-x64@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.5.27.tgz#561e5ddeba0621ccd86445904327b5ac66887be6" + integrity sha512-eOC583D6b3MS9oODCcZUvAV7ajunjENAPVQL7aZaW+piERW+o4koZAiPlzFdMAUMj7UeVg+UN9sBBbTbJgruIA== + +"@swc/core-linux-arm-gnueabihf@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.5.27.tgz#b7d5e2504bc66f1b93766d8d2c3b6de6b6bb3821" + integrity sha512-bMvX0yF7WYzn1K+s0JWJhvyA3OeZHVrdjka8eZ4LSeuLfC0ggJefo+klyeuN2szn/LYP6/3oByyrWNY8RSHB4w== + +"@swc/core-linux-arm64-gnu@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.5.27.tgz#8418e0e0971e8ee5ad40bec940c1aa7e9569862b" + integrity sha512-KlkOcSPxrCqZTm4XrT/LT1o9gmyM2T6bw/hL6IwTYRBJg+sej4rc9iSfVRFZBfNuG3EVkFQSXxik+0yVOXR93Q== + +"@swc/core-linux-arm64-musl@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.5.27.tgz#f4c20b5f3c2b008d5d469d424d4f7de4f308ceea" + integrity sha512-EwdTt5qykxFXJu7kS+0X0Mp/IlwO8KJ6LVNak2+N8bt1J1q/nCdg1tRDOYQ1Z/MVa1Tm+lJ664Qs1y2NY2SDTw== + +"@swc/core-linux-x64-gnu@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.5.27.tgz#27585a3cb38cab77dd62aaba7eba811fa384e91e" + integrity sha512-RsBbxbiSNWLJ2jbAdITtv30J4eZw4O/JJ5zxYgWI54TdY7YrVsqIdzuX+ldximt+CYvO9irHm/mSr/IJY2YXrw== + +"@swc/core-linux-x64-musl@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.5.27.tgz#a9041d19e3770ef2c9c31c3ef30798ee4949d076" + integrity sha512-XpRx0Kpy6JEi1WSMqUfR3k8hXLqNOkVqFcUfzvfQ4QNBX5Ek7ywh7WAxlPhCrFp+wAfNAqqUyRY1xZpLvRU51A== + +"@swc/core-win32-arm64-msvc@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.5.27.tgz#cb05cd9ed099676a6c26251f1fd170e346e89cb5" + integrity sha512-pwSTUIokyIp+Ha1pur34qdYjxqL1QzhP/HM8anzsFs4yvV2LSI7c3qc4GWPNv2eQ9WiFXyo29uCEIRN6qig7wg== + +"@swc/core-win32-ia32-msvc@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.5.27.tgz#96e4ac203df0dd07bb8bb1125c280fb5feccadef" + integrity sha512-S0S6vqFscvmxPolwmpZvTRfTbYR+eGcyc0ge4x/+HcnBCm+m84rcGxmp3bBb1edNFaIV+X47BlGvvh85cJ4rkQ== + +"@swc/core-win32-x64-msvc@1.5.27": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.5.27.tgz#78d7c230d3dd442de72beb06187e76471703a11d" + integrity sha512-aem+BcNW42JPbvV6L3Jl3LLj6G80aYADzYenToYisy0Aop0XZAxL/0FbhV+xWORNFtIUKynNtaa1CK7w0UxehQ== + +"@swc/core@^1.5.7": + version "1.5.27" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.5.27.tgz#a3c44addc8f549010373a168b408b84078463375" + integrity sha512-HmSSCBoUSRDFAd8aEB+WILkCofIp1c2OU6ZJWu1aCt6pijwQSkA4y51CTBcdvyy/+zX1W3cic7alfdhmQxxeEQ== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.8" + optionalDependencies: + "@swc/core-darwin-arm64" "1.5.27" + "@swc/core-darwin-x64" "1.5.27" + "@swc/core-linux-arm-gnueabihf" "1.5.27" + "@swc/core-linux-arm64-gnu" "1.5.27" + "@swc/core-linux-arm64-musl" "1.5.27" + "@swc/core-linux-x64-gnu" "1.5.27" + "@swc/core-linux-x64-musl" "1.5.27" + "@swc/core-win32-arm64-msvc" "1.5.27" + "@swc/core-win32-ia32-msvc" "1.5.27" + "@swc/core-win32-x64-msvc" "1.5.27" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== + +"@swc/types@^0.1.8": + version "0.1.8" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.8.tgz#2c81d107c86cfbd0c3a05ecf7bb54c50dfa58a95" + integrity sha512-RNFA3+7OJFNYY78x0FYwi1Ow+iF1eF5WvmfY1nXPOEH4R2p/D4Cr1vzje7dNAI2aLFqpv8Wyz4oKSWqIZArpQA== + dependencies: + "@swc/counter" "^0.1.3" + +"@types/estree@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/history@^4.7.11": + version "4.7.11" + resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" + integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + +"@types/prop-types@*", "@types/prop-types@^15.7.11": + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== + +"@types/react-dom@^18.2.22": + version "18.3.0" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" + integrity sha512-EhwApuTmMBmXuFOikhQLIBUn6uFg81SwLMOAUgodJF14SOBOCMdU04gDoYi0WOJJHD144TL32z4yDqCW3dnkQg== + dependencies: + "@types/react" "*" + +"@types/react-router-dom@^5.3.3": + version "5.3.3" + resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" + integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router" "*" + +"@types/react-router@*": + version "5.1.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" + integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + +"@types/react-transition-group@^4.4.10": + version "4.4.10" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac" + integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.2.66": + version "18.3.3" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" + integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + +"@typescript-eslint/eslint-plugin@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.0.tgz#3cdeb5d44d051b21a9567535dd90702b2a42c6ff" + integrity sha512-FX1X6AF0w8MdVFLSdqwqN/me2hyhuQg4ykN6ZpVhh1ij/80pTvDKclX1sZB9iqex8SjQfVhwMKs3JtnnMLzG9w== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/type-utils" "7.13.0" + "@typescript-eslint/utils" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + graphemer "^1.4.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^7.2.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.13.0.tgz#9489098d68d57ad392f507495f2b82ce8b8f0a6b" + integrity sha512-EjMfl69KOS9awXXe83iRN7oIEXy9yYdqWfqdrFAYAAr6syP8eLEFI7ZE4939antx2mNgPRW/o1ybm2SFYkbTVA== + dependencies: + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/typescript-estree" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.13.0.tgz#6927d6451537ce648c6af67a2327378d4cc18462" + integrity sha512-ZrMCe1R6a01T94ilV13egvcnvVJ1pxShkE0+NDjDzH4nvG1wXpwsVI5bZCvE7AEDH1mXEx5tJSVR68bLgG7Dng== + dependencies: + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + +"@typescript-eslint/type-utils@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.13.0.tgz#4587282b5227a23753ea8b233805ecafc3924c76" + integrity sha512-xMEtMzxq9eRkZy48XuxlBFzpVMDurUAfDu5Rz16GouAtXm0TaAoTFzqWUFPPuQYXI/CDaH/Bgx/fk/84t/Bc9A== + dependencies: + "@typescript-eslint/typescript-estree" "7.13.0" + "@typescript-eslint/utils" "7.13.0" + debug "^4.3.4" + ts-api-utils "^1.3.0" + +"@typescript-eslint/types@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.13.0.tgz#0cca95edf1f1fdb0cfe1bb875e121b49617477c5" + integrity sha512-QWuwm9wcGMAuTsxP+qz6LBBd3Uq8I5Nv8xb0mk54jmNoCyDspnMvVsOxI6IsMmway5d1S9Su2+sCKv1st2l6eA== + +"@typescript-eslint/typescript-estree@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.0.tgz#4cc24fc155088ebf3b3adbad62c7e60f72c6de1c" + integrity sha512-cAvBvUoobaoIcoqox1YatXOnSl3gx92rCZoMRPzMNisDiM12siGilSM4+dJAekuuHTibI2hVC2fYK79iSFvWjw== + dependencies: + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/visitor-keys" "7.13.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/utils@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.13.0.tgz#f84e7e8aeceae945a9a3f40d077fd95915308004" + integrity sha512-jceD8RgdKORVnB4Y6BqasfIkFhl4pajB1wVxrF4akxD2QPM8GNYjgGwEzYS+437ewlqqrg7Dw+6dhdpjMpeBFQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "7.13.0" + "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/typescript-estree" "7.13.0" + +"@typescript-eslint/visitor-keys@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.0.tgz#2eb7ce8eb38c2b0d4a494d1fe1908e7071a1a353" + integrity sha512-nxn+dozQx+MK61nn/JP+M4eCkHDSxSLDpgE3WcQo0+fkjEolnaB5jswvIKC4K56By8MMgIho7f1PVxERHEo8rw== + dependencies: + "@typescript-eslint/types" "7.13.0" + eslint-visitor-keys "^3.4.3" + +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + +"@vitejs/plugin-react-swc@^3.5.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.7.0.tgz#e456c0a6d7f562268e1d231af9ac46b86ef47d88" + integrity sha512-yrknSb3Dci6svCd/qhHqhFPDSw0QtjumcqdKMoNNzmOl5lMXTTiqzjWtG4Qask2HdvvzaNgSunbQGet8/GrKdA== + dependencies: + "@swc/core" "^1.5.7" + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.9.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + +ajv@^6.12.4: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +clsx@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" + integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +csstype@^3.0.2, csstype@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" + integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== + +debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +esbuild@^0.20.1: + version "0.20.2" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.20.2.tgz#9d6b2386561766ee6b5a55196c6d766d28c87ea1" + integrity sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g== + optionalDependencies: + "@esbuild/aix-ppc64" "0.20.2" + "@esbuild/android-arm" "0.20.2" + "@esbuild/android-arm64" "0.20.2" + "@esbuild/android-x64" "0.20.2" + "@esbuild/darwin-arm64" "0.20.2" + "@esbuild/darwin-x64" "0.20.2" + "@esbuild/freebsd-arm64" "0.20.2" + "@esbuild/freebsd-x64" "0.20.2" + "@esbuild/linux-arm" "0.20.2" + "@esbuild/linux-arm64" "0.20.2" + "@esbuild/linux-ia32" "0.20.2" + "@esbuild/linux-loong64" "0.20.2" + "@esbuild/linux-mips64el" "0.20.2" + "@esbuild/linux-ppc64" "0.20.2" + "@esbuild/linux-riscv64" "0.20.2" + "@esbuild/linux-s390x" "0.20.2" + "@esbuild/linux-x64" "0.20.2" + "@esbuild/netbsd-x64" "0.20.2" + "@esbuild/openbsd-x64" "0.20.2" + "@esbuild/sunos-x64" "0.20.2" + "@esbuild/win32-arm64" "0.20.2" + "@esbuild/win32-ia32" "0.20.2" + "@esbuild/win32-x64" "0.20.2" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596" + integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== + +eslint-plugin-react-refresh@^0.4.6: + version "0.4.7" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz#1f597f9093b254f10ee0961c139a749acb19af7d" + integrity sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw== + +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^5.1.0, estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== + dependencies: + flatted "^3.2.9" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +glob-parent@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@^7.1.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== + dependencies: + type-fest "^0.20.2" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +ignore@^5.2.0, ignore@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + +import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + +json-parse-even-better-errors@^2.3.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +lines-and-columns@^1.1.6: + version "1.2.4" + resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +micromatch@^4.0.4: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^9.0.4: + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== + dependencies: + brace-expansion "^2.0.1" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +nanoid@^3.3.7: + version "3.3.7" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" + integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +optionator@^0.9.3: + version "0.9.4" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" + integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.5" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +postcss@^8.4.38: + version "8.4.38" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.38.tgz#b387d533baf2054288e337066d81c6bee9db9e0e" + integrity sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.0" + source-map-js "^1.2.0" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prop-types@^15.6.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +react-dom@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" + integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.2" + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" + integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== + +react-router-dom@^6.23.1: + version "6.23.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.23.1.tgz#30cbf266669693e9492aa4fc0dde2541ab02322f" + integrity sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ== + dependencies: + "@remix-run/router" "1.16.1" + react-router "6.23.1" + +react-router@6.23.1: + version "6.23.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9" + integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ== + dependencies: + "@remix-run/router" "1.16.1" + +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + +react@^18.2.0: + version "18.3.1" + resolved "https://registry.yarnpkg.com/react/-/react-18.3.1.tgz#49ab892009c53933625bd16b2533fc754cab2891" + integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== + dependencies: + loose-envify "^1.1.0" + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.19.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rollup@^4.13.0: + version "4.18.0" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.18.0.tgz#497f60f0c5308e4602cf41136339fbf87d5f5dda" + integrity sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg== + dependencies: + "@types/estree" "1.0.5" + optionalDependencies: + "@rollup/rollup-android-arm-eabi" "4.18.0" + "@rollup/rollup-android-arm64" "4.18.0" + "@rollup/rollup-darwin-arm64" "4.18.0" + "@rollup/rollup-darwin-x64" "4.18.0" + "@rollup/rollup-linux-arm-gnueabihf" "4.18.0" + "@rollup/rollup-linux-arm-musleabihf" "4.18.0" + "@rollup/rollup-linux-arm64-gnu" "4.18.0" + "@rollup/rollup-linux-arm64-musl" "4.18.0" + "@rollup/rollup-linux-powerpc64le-gnu" "4.18.0" + "@rollup/rollup-linux-riscv64-gnu" "4.18.0" + "@rollup/rollup-linux-s390x-gnu" "4.18.0" + "@rollup/rollup-linux-x64-gnu" "4.18.0" + "@rollup/rollup-linux-x64-musl" "4.18.0" + "@rollup/rollup-win32-arm64-msvc" "4.18.0" + "@rollup/rollup-win32-ia32-msvc" "4.18.0" + "@rollup/rollup-win32-x64-msvc" "4.18.0" + fsevents "~2.3.2" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +scheduler@^0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.2.tgz#414ba64a3b282892e944cf2108ecc078d115cdc3" + integrity sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== + dependencies: + loose-envify "^1.1.0" + +semver@^7.6.0: + version "7.6.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" + integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +typescript@^5.2.2: + version "5.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.5.tgz#42ccef2c571fdbd0f6718b1d1f5e6e5ef006f611" + integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +vite@^5.2.0: + version "5.2.13" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.2.13.tgz#945ababcbe3d837ae2479c29f661cd20bc5e1a80" + integrity sha512-SSq1noJfY9pR3I1TUENL3rQYDQCFqgD+lM6fTRAM8Nv6Lsg5hDLaXkjETVeBt+7vZBCMoibD+6IWnT2mJ+Zb/A== + dependencies: + esbuild "^0.20.1" + postcss "^8.4.38" + rollup "^4.13.0" + optionalDependencies: + fsevents "~2.3.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" + integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== From 0328a81540e9574f4880f4273cc9d92442bf2880 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 11 Jun 2024 15:56:29 +0200 Subject: [PATCH 02/23] zess: development docker --- .gitignore | 96 +------------------------------------------- README.md | 38 ++++++++++++++---- dev.sh | 51 +++++++++++++++++++++++ docker-compose.yml | 39 ++++++++++++++++++ vingo/.air.toml | 51 +++++++++++++++++++++++ vingo/.gitignore | 2 + vingo/Dockerfile.dev | 10 +++++ vingo/README.md | 2 + vingo/dev.env | 2 +- vinscant/.gitignore | 6 +++ vinscant/README.md | 12 ++++-- 11 files changed, 201 insertions(+), 108 deletions(-) create mode 100755 dev.sh create mode 100644 docker-compose.yml create mode 100644 vingo/.air.toml create mode 100644 vingo/.gitignore create mode 100644 vingo/Dockerfile.dev create mode 100644 vinscant/.gitignore diff --git a/.gitignore b/.gitignore index ae970e8..1343767 100644 --- a/.gitignore +++ b/.gitignore @@ -1,12 +1,3 @@ -.config -*.o -*.pyc - -# gtags -GTAGS -GRTAGS -GPATH - # emacs .dir-locals.el @@ -21,52 +12,6 @@ GPATH # MacOS directory files .DS_Store -# cache dir -.cache/ - -# Doc build artifacts -docs/_build/ -docs/doxygen_sqlite3.db - -# Downloaded font files -docs/_static/DejaVuSans.ttf -docs/_static/NotoSansSC-Regular.otf - -# Components Unit Test Apps files -components/**/build/ -components/**/build_*_*/ -components/**/sdkconfig -components/**/sdkconfig.old - -# Example project files -examples/**/build/ -examples/**/build_*_*/ -examples/**/sdkconfig -examples/**/sdkconfig.old - -# Unit test app files -tools/unit-test-app/build -tools/unit-test-app/build_*_*/ -tools/unit-test-app/sdkconfig -tools/unit-test-app/sdkconfig.old - -# test application build files -tools/test_apps/**/build/ -tools/test_apps/**/build_*_*/ -tools/test_apps/**/sdkconfig -tools/test_apps/**/sdkconfig.old - -TEST_LOGS/ -build_summary_*.xml - -# gcov coverage reports -*.gcda -*.gcno -coverage.info -coverage_report/ - -test_multi_heap_host - # VS Code Settings .vscode/ @@ -78,44 +23,5 @@ test_multi_heap_host *.sublime-project *.sublime-workspace -# Clion IDE CMake build & config +# IDEA files .idea/ -cmake-build-*/ - -# Results for the checking of the Python coding style and static analysis -.mypy_cache -flake8_output.txt - -# ESP-IDF default build directory name -build - -# lock files for examples and components -dependencies.lock - -# managed_components for examples -managed_components - -# pytest log -pytest_embedded_log/ -list_job*.txt -size_info*.txt -XUNIT_RESULT*.xml - -# clang config (for LSP) -.clangd - -# Vale -.vale/styles/* - -# pio -.pio - -# sqlite -*.db - -.env - -# vinscant -key.txt -mfrc522.py -webrepl_cli.py diff --git a/README.md b/README.md index d0621f2..79f28ab 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,39 @@ Scans a badge at the scanner (vinscant) which registers its serial number at the Features: -- Supports a daily check-in, keeping track of which days you have visited the kelder. +- Supports a daily check-in, keeping track of which days you have visited the kelder. Goals: -- Support check-in and check-out, keeping track of how many hours(, minutes(, seconds)) you have been in the kelder -- Cool stats :D +- Support check-in and check-out, keeping track of how many hours(, minutes(, seconds)) you have been in the kelder +- Cool stats :D Secret goals: -- Streaks -- Data -- More Data -- Skins -- Battlepass +- Streaks +- Data +- More Data +- Skins +- Battlepass + +## Structure + +- `Vinscant` -> Scanner +- `Vingo` -> Backend +- `Vinvoor` -> Frontend + +## How to run (for development) + +### Easy & Quick + +- Install Docker and Docker Compose +- Run the script `./dev.sh` with optional flags: + - `-b`: Show the output of the backend. + - `-f`: Show the output of the frontend. + - If both flags or no flags are provided, the output of both the backend and frontend are shown. + +The backend is accessible at `localhost:3000`, and the frontend at `localhost:5173`. + +### Manual + +- Each part has it's own `README.md` with instructions on how to run it. diff --git a/dev.sh b/dev.sh new file mode 100755 index 0000000..e44fb82 --- /dev/null +++ b/dev.sh @@ -0,0 +1,51 @@ +#!/bin/sh + +# Exit function + +ctrl_c() { + echo "-------------------------------------" + echo "Stopping all containers..." + echo "-------------------------------------" + docker-compose -f docker-compose.yml stop + exit 0 +} + +trap ctrl_c INT + +# Parse input + +backend=false +frontend=false + +while getopts 'bf' flag; do + case "${flag}" in + b) backend=true ;; + f) frontend=true ;; + *) echo "Unexpected option ${flag}" ;; + esac +done + +# Check for the required files + +if [ ! -f vingo/.env ]; then + cp vingo/dev.env vingo/.env +fi + +# Start the docker containers + +docker-compose -f docker-compose.yml up -d + +echo "-------------------------------------" +echo "Following logs..." +echo "Press CTRL + C to stop all containers" +echo "-------------------------------------" + +if [ "$backend" = true ] && [ "$frontend" = false ]; then + docker-compose -f docker-compose.yml logs -f zess-backend +elif [ "$backend" = false ] && [ "$frontend" = true ]; then + docker-compose -f docker-compose.yml logs -f zess-frontend +else + docker-compose -f docker-compose.yml logs -f zess-backend zess-frontend +fi + +docker-compose -f docker-compose.yml down diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..1fe6b79 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,39 @@ +services: + zess-db: + image: postgres:alpine3.20 + environment: + - POSTGRES_PASSWORD=zess + - POSTGRES_USER=postgres + - POSTGRES_DB=zess + ports: + - 5432:5432 + extra_hosts: + - "host.docker.internal:host-gateway" + + zess-backend: + build: + context: vingo + dockerfile: Dockerfile.dev + ports: + - 4000:4000 + volumes: + - ./vingo:/backend + - backend-packages:/go/pkg/mod + extra_hosts: + - "host.docker.internal:host-gateway" + depends_on: + - zess-db + + zess-frontend: + build: + context: vinvoor + dockerfile: Dockerfile.dev + ports: + - 5173:5173 + volumes: + - ./vinvoor:/frontend + extra_hosts: + - "host.docker.internal:host-gateway" + +volumes: + backend-packages: diff --git a/vingo/.air.toml b/vingo/.air.toml new file mode 100644 index 0000000..58fff2a --- /dev/null +++ b/vingo/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/vingo/.gitignore b/vingo/.gitignore new file mode 100644 index 0000000..a703a4a --- /dev/null +++ b/vingo/.gitignore @@ -0,0 +1,2 @@ +.env +tmp/ diff --git a/vingo/Dockerfile.dev b/vingo/Dockerfile.dev new file mode 100644 index 0000000..94460f4 --- /dev/null +++ b/vingo/Dockerfile.dev @@ -0,0 +1,10 @@ +FROM golang:1.22.1-alpine3.19 + +WORKDIR /backend + +RUN go install github.com/air-verse/air@latest +COPY .air.toml . + +COPY go.mod go.sum ./ + +CMD go mod tidy && air -c .air.toml diff --git a/vingo/README.md b/vingo/README.md index 3b0a440..b0357ea 100644 --- a/vingo/README.md +++ b/vingo/README.md @@ -1,4 +1,5 @@ # Vingo + The webserver that keeps track of the scans :D Register a scan by posting `card_serial;scan_key` to the `/scans` endpoint. @@ -7,6 +8,7 @@ Register a scan by posting `card_serial;scan_key` to the `/scans` endpoint. To register a card, click the "Start registering a new card" button in the cards view, after which the server will register the next scanned card for the user that initiated the request. Only 1 user can register a card at a time. ## How to run (for development) + - install go - install docker - `docker run --name zess-postgres -e POSTGRES_PASSWORD=zess -d -p 5432:5432 postgres` diff --git a/vingo/dev.env b/vingo/dev.env index b4310be..9039b43 100644 --- a/vingo/dev.env +++ b/vingo/dev.env @@ -2,4 +2,4 @@ ZAUTH_CLIENT_ID="tomtest" ZAUHT_CLIENT_SECRET="blargh" SCAN_KEY="bad_key" -POSTGRES_CONNECTION_STRING="postgres://postgres:zess@localhost/zess?sslmode=disable" \ No newline at end of file +POSTGRES_CONNECTION_STRING="postgres://postgres:zess@host.docker.internal/zess?sslmode=disable" diff --git a/vinscant/.gitignore b/vinscant/.gitignore new file mode 100644 index 0000000..3e652d3 --- /dev/null +++ b/vinscant/.gitignore @@ -0,0 +1,6 @@ +key.txt +mfrc522.py +webrepl_cli.py + +# ESP-IDF default build directory name +build diff --git a/vinscant/README.md b/vinscant/README.md index 7420504..3133ca7 100644 --- a/vinscant/README.md +++ b/vinscant/README.md @@ -1,10 +1,12 @@ ono # Hardware: + - ESP32-S2 - RFID-RC522 Connect RFID-RC522 Rfid reader on these pins: + ``` SDA/CS: 34 MOSI: 35 @@ -14,19 +16,21 @@ RST: 16 ``` # Setup: + - If you're on windows and the board is not detected: install the ESP32-S2 toolchain from https://dl.espressif.com/dl/esp-idf/ - - Or just install the usb chip driver for your board (eg. for CP2102N: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads) + - Or just install the usb chip driver for your board (eg. for CP2102N: https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers?tab=downloads) - Install micropython - - Get binary for correct chip. Current: https://micropython.org/download/ESP32_GENERIC_S2/ - - install using commands on that website + - Get binary for correct chip. Current: https://micropython.org/download/ESP32_GENERIC_S2/ + - install using commands on that website - connect to serial - connect to wifi and setup webrepl (see https://docs.micropython.org/en/latest/esp8266/tutorial/repl.html) - get `webrepl_cli.py` from https://github.com/micropython/webrepl - copy `boot.py`, `main.py` and `key.txt` (with the correct key set on vingo) to the microcontroller using `upload_file.sh` -- download https://github.com/danjperron/micropython-mfrc522/blob/master/mfrc522.py and copy it to the microcontroller as well +- download https://github.com/danjperron/micropython-mfrc522/blob/master/mfrc522.py and copy it to the microcontroller as well - beep boop # Future additions + - Beeps - Boops - Status light based on server response From f2301909d1e206f1cce27e63a3d0815ada5eede9 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sun, 16 Jun 2024 21:33:54 +0200 Subject: [PATCH 03/23] vingo: customizable redirect --- vingo/dev.env | 3 ++- vingo/handlers/auth.go | 6 ++++-- vingo/main.go | 18 +++++++++++++++--- vingo/template.env | 5 +++-- 4 files changed, 24 insertions(+), 8 deletions(-) diff --git a/vingo/dev.env b/vingo/dev.env index 9039b43..6dd49d6 100644 --- a/vingo/dev.env +++ b/vingo/dev.env @@ -1,5 +1,6 @@ ZAUTH_CLIENT_ID="tomtest" -ZAUHT_CLIENT_SECRET="blargh" +ZAUTH_CLIENT_SECRET="blargh" +ZAUTH_REDIRECT_URI="http://localhost:5173" SCAN_KEY="bad_key" POSTGRES_CONNECTION_STRING="postgres://postgres:zess@host.docker.internal/zess?sslmode=disable" diff --git a/vingo/handlers/auth.go b/vingo/handlers/auth.go index f8aa840..1f80815 100644 --- a/vingo/handlers/auth.go +++ b/vingo/handlers/auth.go @@ -17,11 +17,13 @@ const ( var ( ZauthClientId = "" ZauthClientSecret = "" + ZauthRedirectUri = "" ) -func SetZauth(client_id string, client_secret string) { +func SetZauth(client_id string, client_secret string, redirect_uri string) { ZauthClientId = client_id ZauthClientSecret = client_secret + ZauthRedirectUri = redirect_uri } func Login(c *fiber.Ctx) error { @@ -134,5 +136,5 @@ func Callback(c *fiber.Ctx) error { sess.Set(STORE_USER, &user) sess.Save() - return c.Status(200).Redirect("/") + return c.Status(200).Redirect(ZauthRedirectUri) } diff --git a/vingo/main.go b/vingo/main.go index 529df07..af0a76a 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -9,6 +9,7 @@ import ( "log" "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/template/html/v2" "github.com/joho/godotenv" _ "github.com/lib/pq" @@ -28,6 +29,12 @@ func main() { Views: engine, }) + public.Use(cors.New(cors.Config{ + AllowOrigins: "http://localhost:5173", + AllowHeaders: "Origin, Content-Type, Accept", + AllowCredentials: true, + })) + // Public routes public.Get("/", handlers.Index) @@ -84,12 +91,17 @@ func setupFromEnv() { log.Fatal("ZAUTH_CLIENT_ID environment variable not set") } - zauth_client_secret, secret_ok := os.LookupEnv("ZAUHT_CLIENT_SECRET") + zauth_client_secret, secret_ok := os.LookupEnv("ZAUTH_CLIENT_SECRET") if !secret_ok { - log.Fatal("ZAUHT_CLIENT_SECRET environment variable not set") + log.Fatal("ZAUTH_CLIENT_SECRET environment variable not set") + } + + zauth_redirect_uri, redirect_ok := os.LookupEnv("ZAUTH_REDIRECT_URI") + if !redirect_ok { + log.Fatal("ZAUTH_REDIRECT_URI environment variable not set") } - handlers.SetZauth(zauth_client_id, zauth_client_secret) + handlers.SetZauth(zauth_client_id, zauth_client_secret, zauth_redirect_uri) // PSK that will authorize the scanner scan_key, key_ok := os.LookupEnv("SCAN_KEY") diff --git a/vingo/template.env b/vingo/template.env index 078974e..fbebfe8 100644 --- a/vingo/template.env +++ b/vingo/template.env @@ -1,5 +1,6 @@ ZAUTH_CLIENT_ID= -ZAUHT_CLIENT_SECRET= +ZAUTH_CLIENT_SECRET= +ZAUTH_REDIRECT_URI= SCAN_KEY= -POSTGRES_CONNECTION_STRING= \ No newline at end of file +POSTGRES_CONNECTION_STRING= From 2a795d8bb8485a8a0de5a0a44cf1773f25d2c8b2 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sun, 16 Jun 2024 21:35:20 +0200 Subject: [PATCH 04/23] zess: add -c option --- dev.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/dev.sh b/dev.sh index e44fb82..3e856ad 100755 --- a/dev.sh +++ b/dev.sh @@ -16,15 +16,25 @@ trap ctrl_c INT backend=false frontend=false +clean=false -while getopts 'bf' flag; do +while getopts 'bfc' flag; do case "${flag}" in b) backend=true ;; f) frontend=true ;; + c) clean=true ;; *) echo "Unexpected option ${flag}" ;; esac done +# Build the docker containers if clean flag is set + +if [ "$clean" = true ]; then + rm vingo/.env || true + docker-compose -f docker-compose.yml build +fi + + # Check for the required files if [ ! -f vingo/.env ]; then From ee1567eb05e3295525b669e3341f4b3c4bb2c434 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sun, 16 Jun 2024 22:33:22 +0200 Subject: [PATCH 05/23] vinvoor: add login --- vinvoor/.gitignore | 4 ++++ vinvoor/package.json | 2 ++ vinvoor/src/App.tsx | 49 ++++++++++++++++++++++++++++++++++++++++++-- vinvoor/yarn.lock | 10 +++++++++ 4 files changed, 63 insertions(+), 2 deletions(-) diff --git a/vinvoor/.gitignore b/vinvoor/.gitignore index a547bf3..6aed7f3 100644 --- a/vinvoor/.gitignore +++ b/vinvoor/.gitignore @@ -22,3 +22,7 @@ dist-ssr *.njsproj *.sln *.sw? + +.vite/ +bin/ +pkg/ diff --git a/vinvoor/package.json b/vinvoor/package.json index fb8f866..22b4c54 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -16,7 +16,9 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.19", "@mui/material": "^5.15.19", + "@types/js-cookie": "^3.0.6", "@types/react-router-dom": "^5.3.3", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1" diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 47f22a3..0ea1651 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,5 +1,50 @@ -import { Typography } from "@mui/material"; +import { Button, Grid, Typography } from "@mui/material"; +import Cookies from "js-cookie"; +import { useEffect, useState } from "react"; export const App = () => { - return Zess Fronted, Coming Soon!; + const [name, setName] = useState(""); + const sessionId = Cookies.get("sessionId"); + + const login = () => { + window.location.href = "http://localhost:4000/login"; + }; + + useEffect(() => { + if (!sessionId) { + return; + } + + fetch("http://localhost:4000/api/user", { + credentials: "include", + }) + .then((response) => response.json()) + .then((data) => { + console.log(data); + setName(data.Username); + }) + .catch((error) => { + console.error("Error:", error); + }); + }, [name]); + + return ( + + Zess Fronted, Coming Soon! + {name ? ( + {name} + ) : ( + + )} + + ); }; diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index 3df7cc0..3ce100e 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -753,6 +753,11 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== +"@types/js-cookie@^3.0.6": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95" + integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ== + "@types/parse-json@^4.0.0": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" @@ -1484,6 +1489,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" From ec72885d82d44a9c7bb2bb050226540f29ce3b70 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:12:58 +0200 Subject: [PATCH 06/23] vingo: add cors --- vingo/main.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vingo/main.go b/vingo/main.go index af0a76a..33f2cb5 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -30,8 +30,7 @@ func main() { }) public.Use(cors.New(cors.Config{ - AllowOrigins: "http://localhost:5173", - AllowHeaders: "Origin, Content-Type, Accept", + AllowOrigins: "http://localhost:5173, https://zess.zeus.gent", AllowCredentials: true, })) From 3747e67f5309189e8d5230388458f0b8f111cc6d Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:14:22 +0200 Subject: [PATCH 07/23] vingo: add json serialization --- vingo/database/cards.go | 4 ++-- vingo/database/settings.go | 6 +++--- vingo/database/users.go | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 4866c1e..6273c45 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -3,8 +3,8 @@ package database import "time" type Card struct { - Serial string - CreatedAt time.Time + Serial string `json:"serial"` + CreatedAt time.Time `json:"created_at"` } var ( diff --git a/vingo/database/settings.go b/vingo/database/settings.go index 6c37a98..1114c86 100644 --- a/vingo/database/settings.go +++ b/vingo/database/settings.go @@ -1,9 +1,9 @@ package database type Settings struct { - ScanInOut bool - Leaderboard bool - Public bool + ScanInOut bool `json:"scan_in_out"` + Leaderboard bool `json:"leaderboard"` + Public bool `json:"public"` } var ( diff --git a/vingo/database/users.go b/vingo/database/users.go index 1d57f5b..e46a148 100644 --- a/vingo/database/users.go +++ b/vingo/database/users.go @@ -1,10 +1,10 @@ package database type User struct { - Id int - Username string - Admin bool - Settings Settings + Id int `json:"id"` + Username string `json:"username"` + Admin bool `json:"admin"` + Settings Settings `json:"settings"` } var ( From f9d81c7b161479a4f9652837fd34858c577b5cfb Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:34:09 +0200 Subject: [PATCH 08/23] vinvoor: add login and logout --- vingo/handlers/auth.go | 2 +- vingo/main.go | 1 + vinvoor/public/zeus_white.svg | 2 + vinvoor/src/App.css | 3 + vinvoor/src/App.tsx | 43 ++---- vinvoor/src/Login.tsx | 9 ++ vinvoor/src/Logout.tsx | 9 ++ vinvoor/src/NavBar.tsx | 250 ++++++++++++++++++++++++++++++++++ vinvoor/src/main.tsx | 12 ++ vinvoor/src/types/User.ts | 12 ++ 10 files changed, 312 insertions(+), 31 deletions(-) create mode 100644 vinvoor/public/zeus_white.svg create mode 100644 vinvoor/src/App.css create mode 100644 vinvoor/src/Login.tsx create mode 100644 vinvoor/src/Logout.tsx create mode 100644 vinvoor/src/NavBar.tsx create mode 100644 vinvoor/src/types/User.ts diff --git a/vingo/handlers/auth.go b/vingo/handlers/auth.go index 1f80815..9629359 100644 --- a/vingo/handlers/auth.go +++ b/vingo/handlers/auth.go @@ -49,7 +49,7 @@ func Logout(c *fiber.Ctx) error { } sess.Destroy() - return c.Status(200).Redirect("/") + return c.Status(200).Redirect(ZauthRedirectUri) } // Zauth access token diff --git a/vingo/main.go b/vingo/main.go index 33f2cb5..ed29e81 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -31,6 +31,7 @@ func main() { public.Use(cors.New(cors.Config{ AllowOrigins: "http://localhost:5173, https://zess.zeus.gent", + AllowHeaders: "Origin, Content-Type, Accept, Access-Control-Allow-Origin", AllowCredentials: true, })) diff --git a/vinvoor/public/zeus_white.svg b/vinvoor/public/zeus_white.svg new file mode 100644 index 0000000..3968024 --- /dev/null +++ b/vinvoor/public/zeus_white.svg @@ -0,0 +1,2 @@ + +image/svg+xml \ No newline at end of file diff --git a/vinvoor/src/App.css b/vinvoor/src/App.css new file mode 100644 index 0000000..699a279 --- /dev/null +++ b/vinvoor/src/App.css @@ -0,0 +1,3 @@ +body { + margin: 0; +} diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 0ea1651..65ffe0b 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,14 +1,13 @@ -import { Button, Grid, Typography } from "@mui/material"; import Cookies from "js-cookie"; import { useEffect, useState } from "react"; +import { Outlet } from "react-router-dom"; +import "./App.css"; +import { NavBar } from "./NavBar"; +import { User } from "./types/User"; export const App = () => { - const [name, setName] = useState(""); - const sessionId = Cookies.get("sessionId"); - - const login = () => { - window.location.href = "http://localhost:4000/login"; - }; + const [user, setUser] = useState(null); + const sessionId = Cookies.get("session_id"); useEffect(() => { if (!sessionId) { @@ -20,31 +19,15 @@ export const App = () => { }) .then((response) => response.json()) .then((data) => { - console.log(data); - setName(data.Username); + setUser(data); }) - .catch((error) => { - console.error("Error:", error); - }); - }, [name]); + .catch(() => Cookies.remove("session_id")); + }, [sessionId]); return ( - - Zess Fronted, Coming Soon! - {name ? ( - {name} - ) : ( - - )} - + <> + + + ); }; diff --git a/vinvoor/src/Login.tsx b/vinvoor/src/Login.tsx new file mode 100644 index 0000000..0403bb9 --- /dev/null +++ b/vinvoor/src/Login.tsx @@ -0,0 +1,9 @@ +import { useEffect } from "react"; + +export const Login = () => { + useEffect(() => { + window.location.href = "http://localhost:4000/login"; + }, []); + + return <>; +}; diff --git a/vinvoor/src/Logout.tsx b/vinvoor/src/Logout.tsx new file mode 100644 index 0000000..d59257d --- /dev/null +++ b/vinvoor/src/Logout.tsx @@ -0,0 +1,9 @@ +import { useEffect } from "react"; + +export const Logout = () => { + useEffect(() => { + window.location.href = "http://localhost:4000/logout"; + }, []); + + return <>; +}; diff --git a/vinvoor/src/NavBar.tsx b/vinvoor/src/NavBar.tsx new file mode 100644 index 0000000..8485d8e --- /dev/null +++ b/vinvoor/src/NavBar.tsx @@ -0,0 +1,250 @@ +import MenuIcon from "@mui/icons-material/Menu"; +import { + AppBar, + Box, + Button, + Container, + IconButton, + MenuItem, + Toolbar, + Tooltip, + Typography, +} from "@mui/material"; +import Menu from "@mui/material/Menu"; +import { FC, useState } from "react"; +import { Link } from "react-router-dom"; +import { User } from "./types/User"; + +const pages = []; +const settings = ["Logout"]; + +interface Props { + user: User | null; +} + +export const NavBar: FC = ({ user }) => { + const [anchorElNav, setAnchorElNav] = useState(null); + const [anchorElUser, setAnchorElUser] = useState(null); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(null); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(null); + }; + + return ( + + + + + + + + + + + + + {pages.map((page) => ( + + + + {page} + + + + ))} + + + + + + + + + + + {pages.map((page) => ( + + + + ))} + + + + {user ? ( + <> + + + + + {settings.map((setting) => ( + + + + {setting} + + + + ))} + + + ) : ( + + + + )} + + + + + ); +}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index e76f59d..930372d 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -7,12 +7,24 @@ import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { App } from "./App.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; +import { Login } from "./Login.tsx"; +import { Logout } from "./Logout.tsx"; const router = createBrowserRouter([ { path: "/", element: , errorElement: , + children: [ + { + path: "login", + element: , + }, + { + path: "logout", + element: , + }, + ], }, ]); diff --git a/vinvoor/src/types/User.ts b/vinvoor/src/types/User.ts new file mode 100644 index 0000000..52ccc51 --- /dev/null +++ b/vinvoor/src/types/User.ts @@ -0,0 +1,12 @@ +export interface User { + id: number; + username: string; + admin: boolean; + settings: Settings; +} + +export interface Settings { + scan_in_out: boolean; + leaderboard: boolean; + public: boolean; +} From 4b9d47f679b263d75360fbe20bfd518ea214ade9 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:35:10 +0200 Subject: [PATCH 09/23] vinvoor: add a cards page --- vinvoor/src/Cards.tsx | 61 ++++++++++++++++++++++++++++++++++++++ vinvoor/src/NavBar.tsx | 2 +- vinvoor/src/main.tsx | 5 ++++ vinvoor/src/types/Cards.ts | 4 +++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 vinvoor/src/Cards.tsx create mode 100644 vinvoor/src/types/Cards.ts diff --git a/vinvoor/src/Cards.tsx b/vinvoor/src/Cards.tsx new file mode 100644 index 0000000..c499169 --- /dev/null +++ b/vinvoor/src/Cards.tsx @@ -0,0 +1,61 @@ +import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined"; +import { + Card, + CardActions, + CardContent, + Grid, + IconButton, + Paper, + Typography, +} from "@mui/material"; +import { useEffect, useState } from "react"; +import { CardType } from "./types/Cards"; + +export const Cards = () => { + const [cards, setCards] = useState([]); + + useEffect(() => { + fetch("http://localhost:4000/api/cards", { + credentials: "include", + }) + .then((response) => response.json()) + .then((data) => { + console.log(data); + + setCards(data); + }); + }, []); + + if (cards.length === 1) { + console.log(typeof cards[0].created_at); + } + + return ( + + {cards.map((card) => ( + + + + + + {card.serial} + + + {card.created_at} + + + + {}} + sx={{ marginLeft: "auto" }} + > + + + + + + + ))} + + ); +}; diff --git a/vinvoor/src/NavBar.tsx b/vinvoor/src/NavBar.tsx index 8485d8e..b398a57 100644 --- a/vinvoor/src/NavBar.tsx +++ b/vinvoor/src/NavBar.tsx @@ -15,7 +15,7 @@ import { FC, useState } from "react"; import { Link } from "react-router-dom"; import { User } from "./types/User"; -const pages = []; +const pages = ["Cards"]; const settings = ["Logout"]; interface Props { diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index 930372d..a41ab62 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -6,6 +6,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { App } from "./App.tsx"; +import { Cards } from "./Cards.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; import { Login } from "./Login.tsx"; import { Logout } from "./Logout.tsx"; @@ -24,6 +25,10 @@ const router = createBrowserRouter([ path: "logout", element: , }, + { + path: "cards", + element: , + }, ], }, ]); diff --git a/vinvoor/src/types/Cards.ts b/vinvoor/src/types/Cards.ts new file mode 100644 index 0000000..b177ae2 --- /dev/null +++ b/vinvoor/src/types/Cards.ts @@ -0,0 +1,4 @@ +export interface CardType { + serial: string; + created_at: string; +} From 9e861d3d3940e0dd50ecb7026af0a3a7ef77dc51 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:35:38 +0200 Subject: [PATCH 10/23] zess: add development instructions --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 79f28ab..4af124b 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,11 @@ Secret goals: - Run the script `./dev.sh` with optional flags: - `-b`: Show the output of the backend. - `-f`: Show the output of the frontend. - - If both flags or no flags are provided, the output of both the backend and frontend are shown. + - `-c`: Rebuilds the docker containers. + - If both `-b` and `-f` or no flags are provided, the output of both the backend and frontend are shown. The backend is accessible at `localhost:3000`, and the frontend at `localhost:5173`. +Both the backend and the frontend support hot module reloading (HMR). ### Manual From ddf3e91ecc822c2537097f66995da314a56a0e7a Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 14:53:24 +0200 Subject: [PATCH 11/23] vinvoor: add a theme --- vinvoor/src/App.tsx | 6 ++++-- vinvoor/src/theme.ts | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 vinvoor/src/theme.ts diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 65ffe0b..dcecd41 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,8 +1,10 @@ +import { ThemeProvider } from "@mui/material"; import Cookies from "js-cookie"; import { useEffect, useState } from "react"; import { Outlet } from "react-router-dom"; import "./App.css"; import { NavBar } from "./NavBar"; +import { theme } from "./theme"; import { User } from "./types/User"; export const App = () => { @@ -25,9 +27,9 @@ export const App = () => { }, [sessionId]); return ( - <> + - + ); }; diff --git a/vinvoor/src/theme.ts b/vinvoor/src/theme.ts new file mode 100644 index 0000000..91f9695 --- /dev/null +++ b/vinvoor/src/theme.ts @@ -0,0 +1,13 @@ +import { createTheme } from "@mui/material"; + +export const theme = createTheme({ + palette: { + mode: "light", + primary: { + main: "#ff7f00", + }, + secondary: { + main: "#002379", + }, + }, +}); From ffb934b365e055d6530403218d23e01709d89784 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 17:17:31 +0200 Subject: [PATCH 12/23] vinvoor: refactor --- vinvoor/src/App.tsx | 32 +-- vinvoor/src/NavBar.tsx | 250 ------------------------ vinvoor/src/{ => cards}/Cards.tsx | 10 +- vinvoor/src/components/UnstyledLink.tsx | 20 ++ vinvoor/src/main.tsx | 6 +- vinvoor/src/navbar/NavBar.tsx | 59 ++++++ vinvoor/src/navbar/NavBarLogo.tsx | 33 ++++ vinvoor/src/navbar/NavBarPages.tsx | 29 +++ vinvoor/src/navbar/NavBarSandwich.tsx | 61 ++++++ vinvoor/src/navbar/NavBarUserMenu.tsx | 89 +++++++++ vinvoor/src/{ => theme}/theme.ts | 0 vinvoor/src/{ => user}/Login.tsx | 0 vinvoor/src/{ => user}/Logout.tsx | 0 vinvoor/src/user/UserProvider.tsx | 50 +++++ 14 files changed, 353 insertions(+), 286 deletions(-) delete mode 100644 vinvoor/src/NavBar.tsx rename vinvoor/src/{ => cards}/Cards.tsx (88%) create mode 100644 vinvoor/src/components/UnstyledLink.tsx create mode 100644 vinvoor/src/navbar/NavBar.tsx create mode 100644 vinvoor/src/navbar/NavBarLogo.tsx create mode 100644 vinvoor/src/navbar/NavBarPages.tsx create mode 100644 vinvoor/src/navbar/NavBarSandwich.tsx create mode 100644 vinvoor/src/navbar/NavBarUserMenu.tsx rename vinvoor/src/{ => theme}/theme.ts (100%) rename vinvoor/src/{ => user}/Login.tsx (100%) rename vinvoor/src/{ => user}/Logout.tsx (100%) create mode 100644 vinvoor/src/user/UserProvider.tsx diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index dcecd41..a02206e 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,35 +1,17 @@ import { ThemeProvider } from "@mui/material"; -import Cookies from "js-cookie"; -import { useEffect, useState } from "react"; import { Outlet } from "react-router-dom"; import "./App.css"; -import { NavBar } from "./NavBar"; -import { theme } from "./theme"; -import { User } from "./types/User"; +import { NavBar } from "./navbar/NavBar"; +import { theme } from "./theme/theme"; +import { UserProvider } from "./user/UserProvider"; export const App = () => { - const [user, setUser] = useState(null); - const sessionId = Cookies.get("session_id"); - - useEffect(() => { - if (!sessionId) { - return; - } - - fetch("http://localhost:4000/api/user", { - credentials: "include", - }) - .then((response) => response.json()) - .then((data) => { - setUser(data); - }) - .catch(() => Cookies.remove("session_id")); - }, [sessionId]); - return ( - - + + + + ); }; diff --git a/vinvoor/src/NavBar.tsx b/vinvoor/src/NavBar.tsx deleted file mode 100644 index b398a57..0000000 --- a/vinvoor/src/NavBar.tsx +++ /dev/null @@ -1,250 +0,0 @@ -import MenuIcon from "@mui/icons-material/Menu"; -import { - AppBar, - Box, - Button, - Container, - IconButton, - MenuItem, - Toolbar, - Tooltip, - Typography, -} from "@mui/material"; -import Menu from "@mui/material/Menu"; -import { FC, useState } from "react"; -import { Link } from "react-router-dom"; -import { User } from "./types/User"; - -const pages = ["Cards"]; -const settings = ["Logout"]; - -interface Props { - user: User | null; -} - -export const NavBar: FC = ({ user }) => { - const [anchorElNav, setAnchorElNav] = useState(null); - const [anchorElUser, setAnchorElUser] = useState(null); - - const handleOpenNavMenu = (event: React.MouseEvent) => { - setAnchorElNav(event.currentTarget); - }; - const handleOpenUserMenu = (event: React.MouseEvent) => { - setAnchorElUser(event.currentTarget); - }; - - const handleCloseNavMenu = () => { - setAnchorElNav(null); - }; - - const handleCloseUserMenu = () => { - setAnchorElUser(null); - }; - - return ( - - - - - - - - - - - - - {pages.map((page) => ( - - - - {page} - - - - ))} - - - - - - - - - - - {pages.map((page) => ( - - - - ))} - - - - {user ? ( - <> - - - - - {settings.map((setting) => ( - - - - {setting} - - - - ))} - - - ) : ( - - - - )} - - - - - ); -}; diff --git a/vinvoor/src/Cards.tsx b/vinvoor/src/cards/Cards.tsx similarity index 88% rename from vinvoor/src/Cards.tsx rename to vinvoor/src/cards/Cards.tsx index c499169..24c573c 100644 --- a/vinvoor/src/Cards.tsx +++ b/vinvoor/src/cards/Cards.tsx @@ -9,7 +9,7 @@ import { Typography, } from "@mui/material"; import { useEffect, useState } from "react"; -import { CardType } from "./types/Cards"; +import { CardType } from "../types/Cards"; export const Cards = () => { const [cards, setCards] = useState([]); @@ -20,20 +20,14 @@ export const Cards = () => { }) .then((response) => response.json()) .then((data) => { - console.log(data); - setCards(data); }); }, []); - if (cards.length === 1) { - console.log(typeof cards[0].created_at); - } - return ( {cards.map((card) => ( - + diff --git a/vinvoor/src/components/UnstyledLink.tsx b/vinvoor/src/components/UnstyledLink.tsx new file mode 100644 index 0000000..430162d --- /dev/null +++ b/vinvoor/src/components/UnstyledLink.tsx @@ -0,0 +1,20 @@ +import { FC, HTMLAttributes, ReactNode } from "react"; +import { Link } from "react-router-dom"; + +interface UnstyledLinkProps { + to: string; + children: ReactNode; + properties?: HTMLAttributes; +} + +export const UnstyledLink: FC = ({ + to, + children, + properties, +}) => { + return ( + + {children} + + ); +}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index a41ab62..1de4acf 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -6,10 +6,10 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { App } from "./App.tsx"; -import { Cards } from "./Cards.tsx"; +import { Cards } from "./cards/Cards.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; -import { Login } from "./Login.tsx"; -import { Logout } from "./Logout.tsx"; +import { Login } from "./user/Login.tsx"; +import { Logout } from "./user/Logout.tsx"; const router = createBrowserRouter([ { diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx new file mode 100644 index 0000000..f241a52 --- /dev/null +++ b/vinvoor/src/navbar/NavBar.tsx @@ -0,0 +1,59 @@ +import { AppBar, Box, Container, Toolbar } from "@mui/material"; +import { useContext } from "react"; +import { UserContext } from "../user/UserProvider"; +import { NavBarLogo } from "./NavBarLogo"; +import { NavBarPages } from "./NavBarPages"; +import { NavBarSandwich } from "./NavBarSandwich"; +import { NavBarUserMenu } from "./NavBarUserMenu"; + +const pages = ["Cards"]; +const settings = ["Logout"]; + +export const NavBar = () => { + const { user } = useContext(UserContext); + + const screenSize = { + mobile: { xs: "flex", md: "none" }, + desktop: { xs: "none", md: "flex" }, + }; + + return ( + + + + {/* Display either the ZeSS logo or a sandwich menu */} + + + + + {user && ( + + )} + + + {/* Display either all the pages or the ZeSS logo */} + + + {user && ( + + )} + + + + + {/* Display the user menu */} + + + + + + + + ); +}; diff --git a/vinvoor/src/navbar/NavBarLogo.tsx b/vinvoor/src/navbar/NavBarLogo.tsx new file mode 100644 index 0000000..6855500 --- /dev/null +++ b/vinvoor/src/navbar/NavBarLogo.tsx @@ -0,0 +1,33 @@ +import { Button, SxProps, Theme, Typography } from "@mui/material"; +import { FC } from "react"; +import { UnstyledLink } from "../components/UnstyledLink"; + +interface NavBarLogoProps { + sx?: SxProps; +} + +export const NavBarLogo: FC = ({ sx }) => { + return ( + + + + ); +}; diff --git a/vinvoor/src/navbar/NavBarPages.tsx b/vinvoor/src/navbar/NavBarPages.tsx new file mode 100644 index 0000000..2ec5c76 --- /dev/null +++ b/vinvoor/src/navbar/NavBarPages.tsx @@ -0,0 +1,29 @@ +import { Box, Button, SxProps, Theme } from "@mui/material"; +import { FC } from "react"; +import { UnstyledLink } from "../components/UnstyledLink"; + +interface NavBarPagesProps { + pages: string[]; + sx?: SxProps; +} + +export const NavBarPages: FC = ({ pages, sx }) => { + return ( + + {pages.map((page) => ( + + + + ))} + + ); +}; diff --git a/vinvoor/src/navbar/NavBarSandwich.tsx b/vinvoor/src/navbar/NavBarSandwich.tsx new file mode 100644 index 0000000..a3d4ba4 --- /dev/null +++ b/vinvoor/src/navbar/NavBarSandwich.tsx @@ -0,0 +1,61 @@ +import MenuIcon from "@mui/icons-material/Menu"; +import { + Box, + IconButton, + Menu, + MenuItem, + SxProps, + Theme, + Typography, +} from "@mui/material"; +import { FC, useState } from "react"; +import { UnstyledLink } from "../components/UnstyledLink"; + +interface NavBarSandwichProps { + pages: string[]; + sx?: SxProps; +} + +export const NavBarSandwich: FC = ({ pages, sx }) => { + const [anchorElNav, setAnchorElNav] = useState( + undefined + ); + + const handleOpenNavMenu = (event: React.MouseEvent) => { + setAnchorElNav(event.currentTarget); + }; + + const handleCloseNavMenu = () => { + setAnchorElNav(undefined); + }; + + return ( + + + + + + {pages.map((page) => ( + + + {page} + + + ))} + + + ); +}; diff --git a/vinvoor/src/navbar/NavBarUserMenu.tsx b/vinvoor/src/navbar/NavBarUserMenu.tsx new file mode 100644 index 0000000..aeb8c6b --- /dev/null +++ b/vinvoor/src/navbar/NavBarUserMenu.tsx @@ -0,0 +1,89 @@ +import { AccountCircle } from "@mui/icons-material"; +import { Button, Menu, MenuItem, Typography } from "@mui/material"; +import { FC, useContext, useState } from "react"; +import { UnstyledLink } from "../components/UnstyledLink"; +import { UserContext } from "../user/UserProvider"; + +interface NavBarUserMenuProps { + settings: string[]; +} + +export const NavBarUserMenu: FC = ({ settings }) => { + const { user } = useContext(UserContext); + const [anchorElUser, setAnchorElUser] = useState( + undefined + ); + + const handleOpenUserMenu = (event: React.MouseEvent) => { + setAnchorElUser(event.currentTarget); + }; + + const handleCloseUserMenu = () => { + setAnchorElUser(undefined); + }; + + return ( + <> + {user ? ( + <> + + + {settings.map((setting) => ( + + + {setting} + + + ))} + + + ) : ( + + + + )} + + ); +}; diff --git a/vinvoor/src/theme.ts b/vinvoor/src/theme/theme.ts similarity index 100% rename from vinvoor/src/theme.ts rename to vinvoor/src/theme/theme.ts diff --git a/vinvoor/src/Login.tsx b/vinvoor/src/user/Login.tsx similarity index 100% rename from vinvoor/src/Login.tsx rename to vinvoor/src/user/Login.tsx diff --git a/vinvoor/src/Logout.tsx b/vinvoor/src/user/Logout.tsx similarity index 100% rename from vinvoor/src/Logout.tsx rename to vinvoor/src/user/Logout.tsx diff --git a/vinvoor/src/user/UserProvider.tsx b/vinvoor/src/user/UserProvider.tsx new file mode 100644 index 0000000..9a867bf --- /dev/null +++ b/vinvoor/src/user/UserProvider.tsx @@ -0,0 +1,50 @@ +import Cookies from "js-cookie"; +import { + createContext, + Dispatch, + FC, + ReactNode, + SetStateAction, + useEffect, + useState, +} from "react"; +import { User } from "../types/User"; + +interface UserProviderProps { + children: ReactNode; +} + +export const UserContext = createContext<{ + user: User | undefined; + setUser: Dispatch>; +}>({ + user: undefined, + setUser: () => {}, +}); + +export const UserProvider: FC = ({ children }) => { + const [user, setUser] = useState(undefined); + + useEffect(() => { + const sessionId = Cookies.get("session_id"); + + if (!sessionId) { + return; + } + + fetch("http://localhost:4000/api/user", { + credentials: "include", + }) + .then((response) => response.json()) + .then((data) => { + setUser(data); + }) + .catch(() => Cookies.remove("session_id")); + }, []); + + return ( + + {children} + + ); +}; From 874ccc8e02ee4a1ab87784c08886910f11e95b2a Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 21 Jun 2024 19:51:51 +0200 Subject: [PATCH 13/23] vinvoor: add support for dark and light mode --- vinvoor/src/App.tsx | 16 ++++---- vinvoor/src/components/DarkModeToggle.tsx | 22 +++++++++++ vinvoor/src/navbar/NavBar.tsx | 4 +- vinvoor/src/navbar/NavBarLogo.tsx | 1 - vinvoor/src/navbar/NavBarPages.tsx | 2 - vinvoor/src/navbar/NavBarUserMenu.tsx | 4 -- vinvoor/src/theme/ThemeProvider.tsx | 47 +++++++++++++++++++++++ vinvoor/src/theme/theme.ts | 23 ++++++++++- vinvoor/src/user/UserProvider.tsx | 6 ++- 9 files changed, 107 insertions(+), 18 deletions(-) create mode 100644 vinvoor/src/components/DarkModeToggle.tsx create mode 100644 vinvoor/src/theme/ThemeProvider.tsx diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index a02206e..6e4dd8f 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,17 +1,19 @@ -import { ThemeProvider } from "@mui/material"; +import { CssBaseline } from "@mui/material"; import { Outlet } from "react-router-dom"; import "./App.css"; import { NavBar } from "./navbar/NavBar"; -import { theme } from "./theme/theme"; +import { ThemeProvider } from "./theme/ThemeProvider"; import { UserProvider } from "./user/UserProvider"; export const App = () => { return ( - - - - - + + + + + + + ); }; diff --git a/vinvoor/src/components/DarkModeToggle.tsx b/vinvoor/src/components/DarkModeToggle.tsx new file mode 100644 index 0000000..cbfaf08 --- /dev/null +++ b/vinvoor/src/components/DarkModeToggle.tsx @@ -0,0 +1,22 @@ +import { DarkModeOutlined, LightModeOutlined } from "@mui/icons-material"; +import { IconButton } from "@mui/material"; +import { useContext } from "react"; +import { ThemeContext } from "../theme/ThemeProvider"; + +export const DarkModeToggle = () => { + const { themeMode, toggleTheme } = useContext(ThemeContext); + + const handleThemeChange = () => { + toggleTheme(); + }; + + return ( + + {themeMode === "light" ? ( + + ) : ( + + )} + + ); +}; diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx index f241a52..ec92d4c 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -1,5 +1,6 @@ import { AppBar, Box, Container, Toolbar } from "@mui/material"; import { useContext } from "react"; +import { DarkModeToggle } from "../components/DarkModeToggle"; import { UserContext } from "../user/UserProvider"; import { NavBarLogo } from "./NavBarLogo"; import { NavBarPages } from "./NavBarPages"; @@ -47,9 +48,10 @@ export const NavBar = () => { - {/* Display the user menu */} + {/* Display a dark mode switch and the user menu */} + diff --git a/vinvoor/src/navbar/NavBarLogo.tsx b/vinvoor/src/navbar/NavBarLogo.tsx index 6855500..58c6e2a 100644 --- a/vinvoor/src/navbar/NavBarLogo.tsx +++ b/vinvoor/src/navbar/NavBarLogo.tsx @@ -14,7 +14,6 @@ export const NavBarLogo: FC = ({ sx }) => { sx={{ ...sx, textTransform: "none", - my: 2, color: "white", }} > diff --git a/vinvoor/src/navbar/NavBarPages.tsx b/vinvoor/src/navbar/NavBarPages.tsx index 2ec5c76..636cb27 100644 --- a/vinvoor/src/navbar/NavBarPages.tsx +++ b/vinvoor/src/navbar/NavBarPages.tsx @@ -15,9 +15,7 @@ export const NavBarPages: FC = ({ pages, sx }) => { + + + ); + + return ( + <> + + + + ); +}; diff --git a/vinvoor/src/cards/CardDelete.tsx b/vinvoor/src/cards/CardDelete.tsx new file mode 100644 index 0000000..7dcdad8 --- /dev/null +++ b/vinvoor/src/cards/CardDelete.tsx @@ -0,0 +1,61 @@ +import { CancelOutlined } from "@mui/icons-material"; +import DeleteIcon from "@mui/icons-material/Delete"; +import { Button, IconButton, Tooltip, Typography } from "@mui/material"; +import { Dispatch, FC, SetStateAction, useState } from "react"; +import { ConfirmationModal } from "../components/ConfirmationModal"; +import { CardType } from "../types/Cards"; + +interface CardDeleteProps { + selected: readonly string[]; + setCards: Dispatch>; +} + +export const CardDelete: FC = ({ selected, setCards }) => { + const [open, setOpen] = useState(false); + + const numSelected = selected.length; + + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + const title = `Delete card${numSelected > 1 ? "s" : ""}`; + + const content = ` + Are you sure you want to delete ${numSelected} card${ + numSelected > 1 ? "s" : "" + }? Unfortunately, this + feature isn't implemented yet. Again, I'm waiting + for an endpoint. + Hannnneeeeeeees........................... + `; + + const actions = ( + <> + + + + ); + + return ( + <> + + + + + + + + ); +}; diff --git a/vinvoor/src/cards/CardTable.tsx b/vinvoor/src/cards/CardTable.tsx new file mode 100644 index 0000000..9eb6eb6 --- /dev/null +++ b/vinvoor/src/cards/CardTable.tsx @@ -0,0 +1,222 @@ +import { + Box, + Checkbox, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TablePagination, + TableRow, + Typography, +} from "@mui/material"; +import { + ChangeEvent, + Dispatch, + FC, + MouseEvent, + SetStateAction, + useMemo, + useState, +} from "react"; +import { CardType, Order } from "../types/Cards"; +import { CardTableHead } from "./CardTableHead"; +import { CardTableToolbar } from "./CardTableToolBar"; + +interface CardTableProps { + cards: readonly CardType[]; + setCards: Dispatch>; +} + +const descendingComparator = (a: T, b: T, orderBy: keyof T) => { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +}; + +const getComparator = ( + order: Order, + orderBy: Key +): (( + a: { [key in Key]: number | string }, + b: { [key in Key]: number | string } +) => number) => { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +}; + +const stableSort = ( + array: readonly T[], + comparator: (a: T, b: T) => number +) => { + const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +}; + +export const CardTable: FC = ({ cards, setCards }) => { + const [order, setOrder] = useState("asc"); + const [orderBy, setOrderBy] = useState("serial"); + const [selected, setSelected] = useState([]); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + + const handleRequestSort = ( + _: MouseEvent, + property: keyof CardType + ) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + const handleSelectAllClick = (event: ChangeEvent) => { + if (event.target.checked) { + const newSelected = cards.map((n) => n.serial); + setSelected(newSelected); + return; + } + + setSelected([]); + }; + + const handleClick = (_: MouseEvent, serial: string) => { + const selectedIndex = selected.indexOf(serial); + let newSelected: readonly string[] = []; + + switch (selectedIndex) { + case -1: + newSelected = newSelected.concat(selected, serial); + break; + case 0: + newSelected = newSelected.concat(selected.slice(1)); + break; + case selected.length - 1: + newSelected = newSelected.concat(selected.slice(0, -1)); + break; + default: + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1) + ); + break; + } + + setSelected(newSelected); + }; + + const handleChangePage = ( + _: MouseEvent | null, + newPage: number + ) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event: ChangeEvent) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const isSelected = (serial: string) => selected.indexOf(serial) !== -1; + + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - cards.length) : 0; + + const visibleRows = useMemo( + () => + stableSort(cards, getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ), + [order, orderBy, page, rowsPerPage] + ); + + return ( + + + + + + + + {visibleRows.map((row, _) => { + const isItemSelected = isSelected(row.serial); + + return ( + + handleClick(event, row.serial) + } + role="checkbox" + tabIndex={-1} + key={row.serial} + selected={isItemSelected} + sx={{ cursor: "pointer" }} + > + + + + + + {row.serial} + + + + + {row.created_at} + + + + ); + })} + {emptyRows > 0 && ( + + + + )} + +
+
+ +
+
+ ); +}; diff --git a/vinvoor/src/cards/CardTableHead.tsx b/vinvoor/src/cards/CardTableHead.tsx new file mode 100644 index 0000000..ad85e03 --- /dev/null +++ b/vinvoor/src/cards/CardTableHead.tsx @@ -0,0 +1,77 @@ +import { + Box, + Checkbox, + TableCell, + TableHead, + TableRow, + TableSortLabel, + Typography, +} from "@mui/material"; +import { visuallyHidden } from "@mui/utils"; +import { ChangeEvent, FC, MouseEvent } from "react"; +import { CardType, headCells, Order } from "../types/Cards"; + +interface CardTableHeadProps { + numSelected: number; + onRequestSort: ( + event: MouseEvent, + property: keyof CardType + ) => void; + onSelectAllClick: (event: ChangeEvent) => void; + order: Order; + orderBy: string; + rowCount: number; +} + +export const CardTableHead: FC = ({ + numSelected, + onRequestSort, + onSelectAllClick, + order, + orderBy, + rowCount, +}) => { + const createSortHandler = + (property: keyof CardType) => (event: MouseEvent) => + onRequestSort(event, property); + + return ( + + + + 0 && numSelected < rowCount + } + checked={rowCount > 0 && numSelected === rowCount} + onChange={onSelectAllClick} + /> + + {headCells.map((headCell) => ( + + + {headCell.label} + {orderBy === headCell.id ? ( + + {order === "desc" + ? "sorted descending" + : "sorted ascending"} + + ) : null} + + + ))} + + + ); +}; diff --git a/vinvoor/src/cards/CardTableToolBar.tsx b/vinvoor/src/cards/CardTableToolBar.tsx new file mode 100644 index 0000000..8d604c8 --- /dev/null +++ b/vinvoor/src/cards/CardTableToolBar.tsx @@ -0,0 +1,53 @@ +import { Toolbar, Typography } from "@mui/material"; +import { alpha } from "@mui/material/styles"; +import { Dispatch, FC, SetStateAction } from "react"; +import { CardType } from "../types/Cards"; +import { CardAdd } from "./CardAdd"; +import { CardDelete } from "./CardDelete"; + +interface CardTableToolBarProps { + selected: readonly string[]; + setCards: Dispatch>; +} + +export const CardTableToolbar: FC = ({ + selected, + setCards, +}) => { + const numSelected = selected.length; + + return ( + 0 && { + bgcolor: (theme) => + alpha( + theme.palette.primary.main, + theme.palette.action.activatedOpacity + ), + }), + }} + > + {numSelected > 0 ? ( + + {numSelected} selected + + ) : ( + + Cards + + )} + {numSelected > 0 ? ( + + ) : ( + + )} + + ); +}; diff --git a/vinvoor/src/cards/Cards.tsx b/vinvoor/src/cards/Cards.tsx deleted file mode 100644 index 35e5c42..0000000 --- a/vinvoor/src/cards/Cards.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined"; -import { - Card, - CardActions, - CardContent, - Grid, - IconButton, - Paper, - Typography, -} from "@mui/material"; -import { useEffect, useState } from "react"; -import { CardType } from "../types/Cards"; -import { fetchApi } from "../util/fetch"; - -export const Cards = () => { - const [cards, setCards] = useState([]); - - useEffect(() => { - fetchApi("cards").then((data) => { - setCards(data); - }); - }, []); - - return ( - - {cards.map((card) => ( - - - - - - {card.serial} - - - {card.created_at} - - - - {}} - sx={{ marginLeft: "auto" }} - > - - - - - - - ))} - - ); -}; diff --git a/vinvoor/src/components/ConfirmationModal.tsx b/vinvoor/src/components/ConfirmationModal.tsx new file mode 100644 index 0000000..3d83924 --- /dev/null +++ b/vinvoor/src/components/ConfirmationModal.tsx @@ -0,0 +1,61 @@ +import { Box, Modal } from "@mui/material"; +import { FC, ReactNode } from "react"; +import { TypographyG } from "./TypographyG"; + +interface ConfirmationModalProps { + open: boolean; + onClose: () => void; + title: string; + content: ReactNode; + actions: ReactNode; +} + +export const ConfirmationModal: FC = ({ + open, + onClose, + title, + content, + actions, +}) => { + return ( + + + + {title} + {content} + + + {actions} + + + + ); +}; diff --git a/vinvoor/src/components/DarkModeToggle.tsx b/vinvoor/src/components/DarkModeToggle.tsx index cbfaf08..ab4833e 100644 --- a/vinvoor/src/components/DarkModeToggle.tsx +++ b/vinvoor/src/components/DarkModeToggle.tsx @@ -1,5 +1,5 @@ import { DarkModeOutlined, LightModeOutlined } from "@mui/icons-material"; -import { IconButton } from "@mui/material"; +import { IconButton, Tooltip } from "@mui/material"; import { useContext } from "react"; import { ThemeContext } from "../theme/ThemeProvider"; @@ -11,12 +11,17 @@ export const DarkModeToggle = () => { }; return ( - - {themeMode === "light" ? ( - - ) : ( - - )} - + + + {themeMode === "light" ? ( + + ) : ( + + )} + + ); }; diff --git a/vinvoor/src/components/TypographyG.tsx b/vinvoor/src/components/TypographyG.tsx new file mode 100644 index 0000000..245d91d --- /dev/null +++ b/vinvoor/src/components/TypographyG.tsx @@ -0,0 +1,6 @@ +import { Typography, TypographyProps } from "@mui/material"; +import { FC } from "react"; + +export const TypographyG: FC = (props) => { + return ; +}; diff --git a/vinvoor/src/components/UnstyledLink.tsx b/vinvoor/src/components/UnstyledLink.tsx index 430162d..5de2736 100644 --- a/vinvoor/src/components/UnstyledLink.tsx +++ b/vinvoor/src/components/UnstyledLink.tsx @@ -1,20 +1,6 @@ -import { FC, HTMLAttributes, ReactNode } from "react"; -import { Link } from "react-router-dom"; +import { FC } from "react"; +import { Link, LinkProps } from "react-router-dom"; -interface UnstyledLinkProps { - to: string; - children: ReactNode; - properties?: HTMLAttributes; -} - -export const UnstyledLink: FC = ({ - to, - children, - properties, -}) => { - return ( - - {children} - - ); +export const UnstyledLink: FC = (props) => { + return ; }; diff --git a/vinvoor/src/errors/ErrorPage.tsx b/vinvoor/src/errors/ErrorPage.tsx index 60a47db..9563f20 100644 --- a/vinvoor/src/errors/ErrorPage.tsx +++ b/vinvoor/src/errors/ErrorPage.tsx @@ -1,19 +1,6 @@ +import { Box, Typography } from "@mui/material"; import { isRouteErrorResponse, useRouteError } from "react-router-dom"; -export const ErrorPage = () => { - const error = useRouteError(); - - return ( -
-

Oops!

-

Sorry, an unexpected error has occurred.

-

- {get_error(error)} -

-
- ); -}; - const get_error = (error: unknown) => { if (isRouteErrorResponse(error)) { return `${error.status} ${error.statusText}`; @@ -26,3 +13,32 @@ const get_error = (error: unknown) => { return "Unknown error"; } }; + +export const ErrorPage = () => { + const error = useRouteError(); + + return ( + + + Oops! + + + Sorry, an unexpected error has occurred. + + + {get_error(error)} + + + ); +}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index 1de4acf..b64ddd8 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -6,7 +6,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { App } from "./App.tsx"; -import { Cards } from "./cards/Cards.tsx"; +import { Cards } from "./cards/Card.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; import { Login } from "./user/Login.tsx"; import { Logout } from "./user/Logout.tsx"; diff --git a/vinvoor/src/navbar/NavBarPages.tsx b/vinvoor/src/navbar/NavBarPages.tsx index 636cb27..7e086f3 100644 --- a/vinvoor/src/navbar/NavBarPages.tsx +++ b/vinvoor/src/navbar/NavBarPages.tsx @@ -1,9 +1,9 @@ -import { Box, Button, SxProps, Theme } from "@mui/material"; +import { Box, Button, SxProps, Theme, Typography } from "@mui/material"; import { FC } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; interface NavBarPagesProps { - pages: string[]; + pages: readonly string[]; sx?: SxProps; } @@ -18,7 +18,7 @@ export const NavBarPages: FC = ({ pages, sx }) => { color: "white", }} > - {page} + {page} ))} diff --git a/vinvoor/src/navbar/NavBarSandwich.tsx b/vinvoor/src/navbar/NavBarSandwich.tsx index a3d4ba4..9c46319 100644 --- a/vinvoor/src/navbar/NavBarSandwich.tsx +++ b/vinvoor/src/navbar/NavBarSandwich.tsx @@ -8,11 +8,11 @@ import { Theme, Typography, } from "@mui/material"; -import { FC, useState } from "react"; +import { FC, MouseEvent, useState } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; interface NavBarSandwichProps { - pages: string[]; + pages: readonly string[]; sx?: SxProps; } @@ -21,7 +21,7 @@ export const NavBarSandwich: FC = ({ pages, sx }) => { undefined ); - const handleOpenNavMenu = (event: React.MouseEvent) => { + const handleOpenNavMenu = (event: MouseEvent) => { setAnchorElNav(event.currentTarget); }; diff --git a/vinvoor/src/navbar/NavBarUserMenu.tsx b/vinvoor/src/navbar/NavBarUserMenu.tsx index c3adc2f..1eb6bd6 100644 --- a/vinvoor/src/navbar/NavBarUserMenu.tsx +++ b/vinvoor/src/navbar/NavBarUserMenu.tsx @@ -1,20 +1,20 @@ import { AccountCircle } from "@mui/icons-material"; import { Button, Menu, MenuItem, Typography } from "@mui/material"; -import { FC, useContext, useState } from "react"; +import { FC, MouseEvent, useContext, useState } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; import { UserContext } from "../user/UserProvider"; interface NavBarUserMenuProps { - settings: string[]; + settings: readonly string[]; } export const NavBarUserMenu: FC = ({ settings }) => { const { user } = useContext(UserContext); - const [anchorElUser, setAnchorElUser] = useState( + const [anchorElUser, setAnchorElUser] = useState( undefined ); - const handleOpenUserMenu = (event: React.MouseEvent) => { + const handleOpenUserMenu = (event: MouseEvent) => { setAnchorElUser(event.currentTarget); }; @@ -76,7 +76,7 @@ export const NavBarUserMenu: FC = ({ settings }) => { color: "white", }} > - Login + Login )} diff --git a/vinvoor/src/theme/ThemeProvider.tsx b/vinvoor/src/theme/ThemeProvider.tsx index 62066f8..9410443 100644 --- a/vinvoor/src/theme/ThemeProvider.tsx +++ b/vinvoor/src/theme/ThemeProvider.tsx @@ -20,7 +20,9 @@ export const ThemeContext = createContext({ }); export const ThemeProvider: FC = ({ children }) => { - const [themeMode, setThemeMode] = useState("light"); + const [themeMode, setThemeMode] = useState( + (import.meta.env.VITE_DEFAULT_THEME_MODE as ThemeMode) || "light" + ); const toggleTheme = () => { setThemeMode((prevMode) => (prevMode === "light" ? "dark" : "light")); diff --git a/vinvoor/src/types/Cards.ts b/vinvoor/src/types/Cards.ts index b177ae2..595874b 100644 --- a/vinvoor/src/types/Cards.ts +++ b/vinvoor/src/types/Cards.ts @@ -2,3 +2,27 @@ export interface CardType { serial: string; created_at: string; } + +export type Order = "asc" | "desc"; + +interface HeadCell { + id: keyof CardType; + label: string; + timestamp: boolean; + disablePadding: boolean; +} + +export const headCells: readonly HeadCell[] = [ + { + id: "serial", + label: "Serial", + timestamp: false, + disablePadding: true, + }, + { + id: "created_at", + label: "Created at", + timestamp: true, + disablePadding: false, + }, +]; diff --git a/vinvoor/src/util/fetch.ts b/vinvoor/src/util/fetch.ts index 4a385e9..0497de2 100644 --- a/vinvoor/src/util/fetch.ts +++ b/vinvoor/src/util/fetch.ts @@ -3,14 +3,14 @@ const URLS: { [key: string]: string } = { API: import.meta.env.VITE_API_URL, }; -export const fetchApi = (endpoint: string) => { - return _fetch(`${URLS.API}/${endpoint}`); -}; - export const fetchBase = (endpoint: string) => { return _fetch(`${URLS.BASE}/${endpoint}`); }; +export const fetchApi = (endpoint: string) => { + return _fetch(`${URLS.API}/${endpoint}`); +}; + const _fetch = async (url: string) => { return fetch(url, { credentials: "include" }).then((response) => response.json() From 98c1bf4c5015de9807f6971dbf02658b9920c2fe Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 22 Jun 2024 20:24:31 +0200 Subject: [PATCH 17/23] vingo: more serialization --- vingo/database/scans.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vingo/database/scans.go b/vingo/database/scans.go index 7161450..fe9030e 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -3,8 +3,8 @@ package database import "time" type Scan struct { - ScanTime time.Time - Card string + ScanTime time.Time `json:"scan_time"` + Card string `json:"card"` } type Present struct { @@ -14,9 +14,9 @@ type Present struct { } type LeaderboardItem struct { - Position int - Username string - TotalDays int + Position int `json:"position"` + Username string `json:"username"` + TotalDays int `json:"total_days"` } var ( @@ -57,7 +57,7 @@ func GetPresenceHistory(user_id int) ([]Present, error) { WITH date_series AS ( SELECT generate_series(CURRENT_DATE AT TIME ZONE 'Europe/Brussels' - INTERVAL '6 days', CURRENT_DATE AT TIME ZONE 'Europe/Brussels', '1 day')::date AS date ) - SELECT + SELECT ds.date, CASE WHEN scans.scan_date IS NOT NULL THEN TRUE ELSE FALSE END AS present, CASE WHEN days.date IS NOT NULL THEN TRUE ELSE FALSE END AS streak_day From 506895dbb8635a0ac24c9b9b27dfccf45bb75a33 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 22 Jun 2024 22:07:41 +0200 Subject: [PATCH 18/23] vinvoor: refactor the cards page --- vinvoor/src/cards/Card.tsx | 52 ---- vinvoor/src/cards/CardTable.tsx | 222 ------------------ vinvoor/src/cards/CardTableToolBar.tsx | 53 ----- vinvoor/src/cards/Cards.tsx | 21 ++ .../src/cards/{CardAdd.tsx => CardsAdd.tsx} | 2 +- .../cards/{CardDelete.tsx => CardsDelete.tsx} | 6 +- vinvoor/src/cards/CardsEmpty.tsx | 32 +++ vinvoor/src/cards/CardsTable.tsx | 172 ++++++++++++++ vinvoor/src/cards/CardsTableBody.tsx | 70 ++++++ .../{CardTableHead.tsx => CardsTableHead.tsx} | 28 +-- vinvoor/src/cards/CardsTableToolBar.tsx | 53 +++++ vinvoor/src/components/LoadingSkeleton.tsx | 19 ++ vinvoor/src/hooks/useFetch.ts | 24 ++ vinvoor/src/types/Cards.ts | 28 --- vinvoor/src/types/cards.ts | 21 ++ vinvoor/src/user/UserProvider.tsx | 6 +- 16 files changed, 427 insertions(+), 382 deletions(-) delete mode 100644 vinvoor/src/cards/Card.tsx delete mode 100644 vinvoor/src/cards/CardTable.tsx delete mode 100644 vinvoor/src/cards/CardTableToolBar.tsx create mode 100644 vinvoor/src/cards/Cards.tsx rename vinvoor/src/cards/{CardAdd.tsx => CardsAdd.tsx} (97%) rename vinvoor/src/cards/{CardDelete.tsx => CardsDelete.tsx} (90%) create mode 100644 vinvoor/src/cards/CardsEmpty.tsx create mode 100644 vinvoor/src/cards/CardsTable.tsx create mode 100644 vinvoor/src/cards/CardsTableBody.tsx rename vinvoor/src/cards/{CardTableHead.tsx => CardsTableHead.tsx} (63%) create mode 100644 vinvoor/src/cards/CardsTableToolBar.tsx create mode 100644 vinvoor/src/components/LoadingSkeleton.tsx create mode 100644 vinvoor/src/hooks/useFetch.ts delete mode 100644 vinvoor/src/types/Cards.ts create mode 100644 vinvoor/src/types/cards.ts diff --git a/vinvoor/src/cards/Card.tsx b/vinvoor/src/cards/Card.tsx deleted file mode 100644 index b50a968..0000000 --- a/vinvoor/src/cards/Card.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Box, Paper, Skeleton } from "@mui/material"; -import { useEffect, useState } from "react"; -import { TypographyG } from "../components/TypographyG"; -import { CardType } from "../types/Cards"; -import { fetchApi } from "../util/fetch"; -import { CardAdd } from "./CardAdd"; -import { CardTable } from "./CardTable"; - -export const Cards = () => { - const [cards, setCards] = useState( - undefined - ); - - useEffect(() => { - fetchApi("cards").then((data) => setCards(data)); - }, []); - - return ( - - {cards === undefined ? ( - - ) : !!cards.length ? ( - - ) : ( - - No Registered Cards - - You currently have no registered cards. Register your - first card to start scanning! - - - Once you begin registration, you will have one minute to - present your card to the scanner (Vinscant). Upon - successful registration, your card will be linked to - your account and displayed here. - - - - )} - - ); -}; diff --git a/vinvoor/src/cards/CardTable.tsx b/vinvoor/src/cards/CardTable.tsx deleted file mode 100644 index 9eb6eb6..0000000 --- a/vinvoor/src/cards/CardTable.tsx +++ /dev/null @@ -1,222 +0,0 @@ -import { - Box, - Checkbox, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TablePagination, - TableRow, - Typography, -} from "@mui/material"; -import { - ChangeEvent, - Dispatch, - FC, - MouseEvent, - SetStateAction, - useMemo, - useState, -} from "react"; -import { CardType, Order } from "../types/Cards"; -import { CardTableHead } from "./CardTableHead"; -import { CardTableToolbar } from "./CardTableToolBar"; - -interface CardTableProps { - cards: readonly CardType[]; - setCards: Dispatch>; -} - -const descendingComparator = (a: T, b: T, orderBy: keyof T) => { - if (b[orderBy] < a[orderBy]) { - return -1; - } - if (b[orderBy] > a[orderBy]) { - return 1; - } - return 0; -}; - -const getComparator = ( - order: Order, - orderBy: Key -): (( - a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } -) => number) => { - return order === "desc" - ? (a, b) => descendingComparator(a, b, orderBy) - : (a, b) => -descendingComparator(a, b, orderBy); -}; - -const stableSort = ( - array: readonly T[], - comparator: (a: T, b: T) => number -) => { - const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); - stabilizedThis.sort((a, b) => { - const order = comparator(a[0], b[0]); - if (order !== 0) { - return order; - } - return a[1] - b[1]; - }); - return stabilizedThis.map((el) => el[0]); -}; - -export const CardTable: FC = ({ cards, setCards }) => { - const [order, setOrder] = useState("asc"); - const [orderBy, setOrderBy] = useState("serial"); - const [selected, setSelected] = useState([]); - const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - - const handleRequestSort = ( - _: MouseEvent, - property: keyof CardType - ) => { - const isAsc = orderBy === property && order === "asc"; - setOrder(isAsc ? "desc" : "asc"); - setOrderBy(property); - }; - - const handleSelectAllClick = (event: ChangeEvent) => { - if (event.target.checked) { - const newSelected = cards.map((n) => n.serial); - setSelected(newSelected); - return; - } - - setSelected([]); - }; - - const handleClick = (_: MouseEvent, serial: string) => { - const selectedIndex = selected.indexOf(serial); - let newSelected: readonly string[] = []; - - switch (selectedIndex) { - case -1: - newSelected = newSelected.concat(selected, serial); - break; - case 0: - newSelected = newSelected.concat(selected.slice(1)); - break; - case selected.length - 1: - newSelected = newSelected.concat(selected.slice(0, -1)); - break; - default: - newSelected = newSelected.concat( - selected.slice(0, selectedIndex), - selected.slice(selectedIndex + 1) - ); - break; - } - - setSelected(newSelected); - }; - - const handleChangePage = ( - _: MouseEvent | null, - newPage: number - ) => { - setPage(newPage); - }; - - const handleChangeRowsPerPage = (event: ChangeEvent) => { - setRowsPerPage(parseInt(event.target.value, 10)); - setPage(0); - }; - - const isSelected = (serial: string) => selected.indexOf(serial) !== -1; - - const emptyRows = - page > 0 ? Math.max(0, (1 + page) * rowsPerPage - cards.length) : 0; - - const visibleRows = useMemo( - () => - stableSort(cards, getComparator(order, orderBy)).slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ), - [order, orderBy, page, rowsPerPage] - ); - - return ( - - - - - - - - {visibleRows.map((row, _) => { - const isItemSelected = isSelected(row.serial); - - return ( - - handleClick(event, row.serial) - } - role="checkbox" - tabIndex={-1} - key={row.serial} - selected={isItemSelected} - sx={{ cursor: "pointer" }} - > - - - - - - {row.serial} - - - - - {row.created_at} - - - - ); - })} - {emptyRows > 0 && ( - - - - )} - -
-
- -
-
- ); -}; diff --git a/vinvoor/src/cards/CardTableToolBar.tsx b/vinvoor/src/cards/CardTableToolBar.tsx deleted file mode 100644 index 8d604c8..0000000 --- a/vinvoor/src/cards/CardTableToolBar.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Toolbar, Typography } from "@mui/material"; -import { alpha } from "@mui/material/styles"; -import { Dispatch, FC, SetStateAction } from "react"; -import { CardType } from "../types/Cards"; -import { CardAdd } from "./CardAdd"; -import { CardDelete } from "./CardDelete"; - -interface CardTableToolBarProps { - selected: readonly string[]; - setCards: Dispatch>; -} - -export const CardTableToolbar: FC = ({ - selected, - setCards, -}) => { - const numSelected = selected.length; - - return ( - 0 && { - bgcolor: (theme) => - alpha( - theme.palette.primary.main, - theme.palette.action.activatedOpacity - ), - }), - }} - > - {numSelected > 0 ? ( - - {numSelected} selected - - ) : ( - - Cards - - )} - {numSelected > 0 ? ( - - ) : ( - - )} - - ); -}; diff --git a/vinvoor/src/cards/Cards.tsx b/vinvoor/src/cards/Cards.tsx new file mode 100644 index 0000000..fa2c699 --- /dev/null +++ b/vinvoor/src/cards/Cards.tsx @@ -0,0 +1,21 @@ +import { useState } from "react"; +import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useFetch } from "../hooks/useFetch"; +import { Card } from "../types/cards"; +import { CardsEmpty } from "./CardsEmpty"; +import { CardsTable } from "./CardsTable"; + +export const Cards = () => { + const [cards, setCards] = useState([]); + const { loading, error: _ } = useFetch("cards", setCards); + + return ( + + {!!cards.length ? ( + + ) : ( + + )} + + ); +}; diff --git a/vinvoor/src/cards/CardAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx similarity index 97% rename from vinvoor/src/cards/CardAdd.tsx rename to vinvoor/src/cards/CardsAdd.tsx index 3a7c6e1..1eb4b9b 100644 --- a/vinvoor/src/cards/CardAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -3,7 +3,7 @@ import { Button, Typography } from "@mui/material"; import { useState } from "react"; import { ConfirmationModal } from "../components/ConfirmationModal"; -export const CardAdd = () => { +export const CardsAdd = () => { const [open, setOpen] = useState(false); const handleOpen = () => setOpen(true); diff --git a/vinvoor/src/cards/CardDelete.tsx b/vinvoor/src/cards/CardsDelete.tsx similarity index 90% rename from vinvoor/src/cards/CardDelete.tsx rename to vinvoor/src/cards/CardsDelete.tsx index 7dcdad8..df7e274 100644 --- a/vinvoor/src/cards/CardDelete.tsx +++ b/vinvoor/src/cards/CardsDelete.tsx @@ -3,14 +3,14 @@ import DeleteIcon from "@mui/icons-material/Delete"; import { Button, IconButton, Tooltip, Typography } from "@mui/material"; import { Dispatch, FC, SetStateAction, useState } from "react"; import { ConfirmationModal } from "../components/ConfirmationModal"; -import { CardType } from "../types/Cards"; +import { Card } from "../types/cards"; interface CardDeleteProps { selected: readonly string[]; - setCards: Dispatch>; + setCards: Dispatch>; } -export const CardDelete: FC = ({ selected, setCards }) => { +export const CardsDelete: FC = ({ selected, setCards }) => { const [open, setOpen] = useState(false); const numSelected = selected.length; diff --git a/vinvoor/src/cards/CardsEmpty.tsx b/vinvoor/src/cards/CardsEmpty.tsx new file mode 100644 index 0000000..07751d1 --- /dev/null +++ b/vinvoor/src/cards/CardsEmpty.tsx @@ -0,0 +1,32 @@ +import { Paper } from "@mui/material"; +import { TypographyG } from "../components/TypographyG"; +import { CardsAdd } from "./CardsAdd"; + +export const CardsEmpty = () => { + return ( + + No Registered Cards + + You currently have no registered cards. Register your first card + to start scanning! + + + Once you begin registration, you will have one minute to present + your card to the scanner (Vinscant). Upon successful + registration, your card will be linked to your account and + displayed here. + + + + ); +}; diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx new file mode 100644 index 0000000..4eeb7d3 --- /dev/null +++ b/vinvoor/src/cards/CardsTable.tsx @@ -0,0 +1,172 @@ +import { Paper, Table, TableContainer, TablePagination } from "@mui/material"; +import { + ChangeEvent, + Dispatch, + FC, + MouseEvent, + SetStateAction, + useMemo, + useState, +} from "react"; +import { Card } from "../types/cards"; +import { TableOrder } from "../types/table"; +import { CardsTableBody } from "./CardsTableBody"; +import { CardsTableHead } from "./CardsTableHead"; +import { CardsTableToolbar } from "./CardsTableToolBar"; + +interface CardTableProps { + cards: readonly Card[]; + setCards: Dispatch>; +} + +const rowsPerPageOptions = [10, 25, 50]; + +const descendingComparator = (a: T, b: T, orderBy: keyof T) => { + if (b[orderBy] < a[orderBy]) { + return -1; + } + if (b[orderBy] > a[orderBy]) { + return 1; + } + return 0; +}; + +const getComparator = ( + order: TableOrder, + orderBy: Key +): (( + a: { [key in Key]: number | string }, + b: { [key in Key]: number | string } +) => number) => { + return order === "desc" + ? (a, b) => descendingComparator(a, b, orderBy) + : (a, b) => -descendingComparator(a, b, orderBy); +}; + +const stableSort = ( + array: readonly T[], + comparator: (a: T, b: T) => number +) => { + const stabilizedThis = array.map((el, index) => [el, index] as [T, number]); + stabilizedThis.sort((a, b) => { + const order = comparator(a[0], b[0]); + if (order !== 0) { + return order; + } + return a[1] - b[1]; + }); + return stabilizedThis.map((el) => el[0]); +}; + +export const CardsTable: FC = ({ cards, setCards }) => { + const [order, setOrder] = useState("asc"); + const [orderBy, setOrderBy] = useState("serial"); + const [selected, setSelected] = useState([]); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + + const handleRequestSort = ( + _: MouseEvent, + property: keyof Card + ) => { + const isAsc = orderBy === property && order === "asc"; + setOrder(isAsc ? "desc" : "asc"); + setOrderBy(property); + }; + + const handleSelectAllClick = (event: ChangeEvent) => { + if (event.target.checked) { + const newSelected = cards.map((n) => n.serial); + setSelected(newSelected); + return; + } + + setSelected([]); + }; + + const handleRowClick = ( + _: MouseEvent, + serial: string + ) => { + const selectedIndex = selected.indexOf(serial); + let newSelected: readonly string[] = []; + + switch (selectedIndex) { + case -1: + newSelected = newSelected.concat(selected, serial); + break; + case 0: + newSelected = newSelected.concat(selected.slice(1)); + break; + case selected.length - 1: + newSelected = newSelected.concat(selected.slice(0, -1)); + break; + default: + newSelected = newSelected.concat( + selected.slice(0, selectedIndex), + selected.slice(selectedIndex + 1) + ); + } + + setSelected(newSelected); + }; + + const handleChangePage = ( + _: MouseEvent | null, + newPage: number + ) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = (event: ChangeEvent) => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + const isSelected = (serial: string) => selected.indexOf(serial) !== -1; + + const emptyRows = + page > 0 ? Math.max(0, (1 + page) * rowsPerPage - cards.length) : 0; + + const visibleRows = useMemo( + () => + stableSort(cards, getComparator(order, orderBy)).slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ), + [order, orderBy, page, rowsPerPage] + ); + + return ( + + + + + + +
+
+ +
+ ); +}; diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx new file mode 100644 index 0000000..f103afb --- /dev/null +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -0,0 +1,70 @@ +import { + Checkbox, + TableBody, + TableCell, + TableRow, + Typography, +} from "@mui/material"; +import { FC, MouseEvent } from "react"; +import { Card, CardsHeadCells } from "../types/cards"; + +interface CardsTableBodyProps { + rows: readonly Card[]; + isRowSelected: (serial: string) => boolean; + handleClick: ( + event: MouseEvent, + serial: string + ) => void; + emptyRows: number; +} + +export const CardsTableBody: FC = ({ + rows, + isRowSelected, + handleClick, + emptyRows, +}) => { + return ( + + {rows.map((row) => { + const isSelected = isRowSelected(row.serial); + + return ( + handleClick(event, row.serial)} + sx={{ cursor: "pointer" }} + > + + + + {CardsHeadCells.map((headCell) => ( + + {row[headCell.id]} + + ))} + + ); + })} + {emptyRows > 0 && ( + + + + )} + + ); +}; + +// TODO: Go over all mouse events +// TODO: Move all components props diff --git a/vinvoor/src/cards/CardTableHead.tsx b/vinvoor/src/cards/CardsTableHead.tsx similarity index 63% rename from vinvoor/src/cards/CardTableHead.tsx rename to vinvoor/src/cards/CardsTableHead.tsx index ad85e03..6d0ff66 100644 --- a/vinvoor/src/cards/CardTableHead.tsx +++ b/vinvoor/src/cards/CardsTableHead.tsx @@ -1,5 +1,4 @@ import { - Box, Checkbox, TableCell, TableHead, @@ -7,23 +6,23 @@ import { TableSortLabel, Typography, } from "@mui/material"; -import { visuallyHidden } from "@mui/utils"; import { ChangeEvent, FC, MouseEvent } from "react"; -import { CardType, headCells, Order } from "../types/Cards"; +import { Card, CardsHeadCells } from "../types/cards"; +import { TableOrder } from "../types/table"; interface CardTableHeadProps { numSelected: number; onRequestSort: ( - event: MouseEvent, - property: keyof CardType + event: MouseEvent, + property: keyof Card ) => void; onSelectAllClick: (event: ChangeEvent) => void; - order: Order; + order: TableOrder; orderBy: string; rowCount: number; } -export const CardTableHead: FC = ({ +export const CardsTableHead: FC = ({ numSelected, onRequestSort, onSelectAllClick, @@ -32,7 +31,7 @@ export const CardTableHead: FC = ({ rowCount, }) => { const createSortHandler = - (property: keyof CardType) => (event: MouseEvent) => + (property: keyof Card) => (event: MouseEvent) => onRequestSort(event, property); return ( @@ -40,7 +39,6 @@ export const CardTableHead: FC = ({ 0 && numSelected < rowCount } @@ -48,12 +46,11 @@ export const CardTableHead: FC = ({ onChange={onSelectAllClick} /> - {headCells.map((headCell) => ( + {CardsHeadCells.map((headCell) => ( = ({ onClick={createSortHandler(headCell.id)} > {headCell.label} - {orderBy === headCell.id ? ( - - {order === "desc" - ? "sorted descending" - : "sorted ascending"} - - ) : null} ))} diff --git a/vinvoor/src/cards/CardsTableToolBar.tsx b/vinvoor/src/cards/CardsTableToolBar.tsx new file mode 100644 index 0000000..21ed402 --- /dev/null +++ b/vinvoor/src/cards/CardsTableToolBar.tsx @@ -0,0 +1,53 @@ +import { Toolbar, Typography } from "@mui/material"; +import { alpha } from "@mui/material/styles"; +import { Dispatch, FC, SetStateAction } from "react"; +import { Card } from "../types/cards"; +import { CardsAdd } from "./CardsAdd"; +import { CardsDelete } from "./CardsDelete"; + +interface CardTableToolBarProps { + selected: readonly string[]; + setCards: Dispatch>; +} + +export const CardsTableToolbar: FC = ({ + selected, + setCards, +}) => { + const numSelected = selected.length; + + return ( + 0 && { + bgcolor: (theme) => + alpha( + theme.palette.primary.main, + theme.palette.action.activatedOpacity + ), + }), + }} + > + {numSelected > 0 ? ( + <> + + {numSelected} selected + + + + ) : ( + <> + + Cards + + + + )} + + ); +}; diff --git a/vinvoor/src/components/LoadingSkeleton.tsx b/vinvoor/src/components/LoadingSkeleton.tsx new file mode 100644 index 0000000..04ff32e --- /dev/null +++ b/vinvoor/src/components/LoadingSkeleton.tsx @@ -0,0 +1,19 @@ +import { Skeleton, SkeletonProps } from "@mui/material"; +import { FC, ReactNode } from "react"; + +interface LoadingSkeletonProps extends SkeletonProps { + loading: boolean; + children: ReactNode; +} + +export const LoadingSkeleton: FC = ({ + loading, + children, + ...props +}) => { + return loading ? ( + + ) : ( + children + ); +}; diff --git a/vinvoor/src/hooks/useFetch.ts b/vinvoor/src/hooks/useFetch.ts new file mode 100644 index 0000000..0a2b1ac --- /dev/null +++ b/vinvoor/src/hooks/useFetch.ts @@ -0,0 +1,24 @@ +import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { fetchApi } from "../util/fetch"; + +interface useFetchResult { + loading: boolean; + error: Error | undefined; +} + +export const useFetch = ( + endpoint: string, + setData: Dispatch> +): useFetchResult => { + const [loading, setLoading] = useState(true); + const [error, setError] = useState(undefined); + + useEffect(() => { + fetchApi(endpoint) + .then((data) => setData(data)) + .catch((error) => setError(error)) + .finally(() => setLoading(false)); + }, [endpoint]); + + return { loading, error }; +}; diff --git a/vinvoor/src/types/Cards.ts b/vinvoor/src/types/Cards.ts deleted file mode 100644 index 595874b..0000000 --- a/vinvoor/src/types/Cards.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface CardType { - serial: string; - created_at: string; -} - -export type Order = "asc" | "desc"; - -interface HeadCell { - id: keyof CardType; - label: string; - timestamp: boolean; - disablePadding: boolean; -} - -export const headCells: readonly HeadCell[] = [ - { - id: "serial", - label: "Serial", - timestamp: false, - disablePadding: true, - }, - { - id: "created_at", - label: "Created at", - timestamp: true, - disablePadding: false, - }, -]; diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts new file mode 100644 index 0000000..0efd465 --- /dev/null +++ b/vinvoor/src/types/cards.ts @@ -0,0 +1,21 @@ +import { TableHeadCell } from "./table"; + +export interface Card { + serial: string; + createdAt: string; +} + +export const CardsHeadCells: readonly TableHeadCell[] = [ + { + id: "serial", + label: "Serial", + align: "left", + disablePadding: true, + }, + { + id: "createdAt", + label: "Created at", + align: "right", + disablePadding: false, + }, +]; diff --git a/vinvoor/src/user/UserProvider.tsx b/vinvoor/src/user/UserProvider.tsx index 9096e93..6f3a86f 100644 --- a/vinvoor/src/user/UserProvider.tsx +++ b/vinvoor/src/user/UserProvider.tsx @@ -8,7 +8,7 @@ import { useEffect, useState, } from "react"; -import { User } from "../types/User"; +import { User } from "../types/user"; import { fetchApi } from "../util/fetch"; interface UserProviderProps { @@ -36,9 +36,7 @@ export const UserProvider: FC = ({ children }) => { } fetchApi("user") - .then((data) => { - setUser(data); - }) + .then((data) => setUser(data)) .catch(() => Cookies.remove("session_id")); }, []); From d7e9ec40c92672d06ca27cc8e54204108ef91afa Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 22 Jun 2024 22:08:21 +0200 Subject: [PATCH 19/23] vinvoor: change to camelCase --- vingo/database/cards.go | 2 +- vingo/database/scans.go | 4 ++-- vingo/database/settings.go | 2 +- vingo/database/users.go | 2 +- vinvoor/src/types/table.ts | 10 ++++++++++ vinvoor/src/types/{User.ts => user.ts} | 2 +- 6 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 vinvoor/src/types/table.ts rename vinvoor/src/types/{User.ts => user.ts} (87%) diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 6273c45..ca05186 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -4,7 +4,7 @@ import "time" type Card struct { Serial string `json:"serial"` - CreatedAt time.Time `json:"created_at"` + CreatedAt time.Time `json:"createdAt"` } var ( diff --git a/vingo/database/scans.go b/vingo/database/scans.go index fe9030e..3aa0aa2 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -3,7 +3,7 @@ package database import "time" type Scan struct { - ScanTime time.Time `json:"scan_time"` + ScanTime time.Time `json:"scanTime"` Card string `json:"card"` } @@ -16,7 +16,7 @@ type Present struct { type LeaderboardItem struct { Position int `json:"position"` Username string `json:"username"` - TotalDays int `json:"total_days"` + TotalDays int `json:"totalDays"` } var ( diff --git a/vingo/database/settings.go b/vingo/database/settings.go index 1114c86..8ccd784 100644 --- a/vingo/database/settings.go +++ b/vingo/database/settings.go @@ -1,7 +1,7 @@ package database type Settings struct { - ScanInOut bool `json:"scan_in_out"` + ScanInOut bool `json:"scanInOut"` Leaderboard bool `json:"leaderboard"` Public bool `json:"public"` } diff --git a/vingo/database/users.go b/vingo/database/users.go index e46a148..136258c 100644 --- a/vingo/database/users.go +++ b/vingo/database/users.go @@ -1,10 +1,10 @@ package database type User struct { - Id int `json:"id"` Username string `json:"username"` Admin bool `json:"admin"` Settings Settings `json:"settings"` + Id int `json:"id"` } var ( diff --git a/vinvoor/src/types/table.ts b/vinvoor/src/types/table.ts new file mode 100644 index 0000000..16adf90 --- /dev/null +++ b/vinvoor/src/types/table.ts @@ -0,0 +1,10 @@ +export type TableOrder = "asc" | "desc"; + +type TableAlignOptions = "right" | "left" | "center"; + +export interface TableHeadCell { + id: keyof T; + label: string; + align: TableAlignOptions; + disablePadding: boolean; +} diff --git a/vinvoor/src/types/User.ts b/vinvoor/src/types/user.ts similarity index 87% rename from vinvoor/src/types/User.ts rename to vinvoor/src/types/user.ts index 52ccc51..f5e6f1a 100644 --- a/vinvoor/src/types/User.ts +++ b/vinvoor/src/types/user.ts @@ -6,7 +6,7 @@ export interface User { } export interface Settings { - scan_in_out: boolean; + scanInOut: boolean; leaderboard: boolean; public: boolean; } From 71e942acd3f39bb4666866ed13124c1e906e5ed5 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 22 Jun 2024 23:56:26 +0200 Subject: [PATCH 20/23] vinvoor: add a leaderboard --- vinvoor/package.json | 5 +- .../public/first-first-place-svgrepo-com.svg | 28 +++++++ vinvoor/public/second-svgrepo-com.svg | 29 +++++++ vinvoor/public/third-svgrepo-com.svg | 29 +++++++ vinvoor/src/cards/CardsTable.tsx | 2 +- vinvoor/src/cards/CardsTableBody.tsx | 4 +- vinvoor/src/cards/CardsTableHead.tsx | 2 +- ...TableToolBar.tsx => CardsTableToolbar.tsx} | 6 +- vinvoor/src/leaderboard/Leaderboard.tsx | 36 +++++++++ .../src/leaderboard/LeaderboardTableBody.tsx | 76 +++++++++++++++++++ .../src/leaderboard/LeaderboardTableHead.tsx | 16 ++++ .../leaderboard/LeaderboardTableToolbar.tsx | 27 +++++++ vinvoor/src/main.tsx | 7 +- vinvoor/src/navbar/NavBar.tsx | 2 +- vinvoor/src/types/cards.ts | 4 +- vinvoor/src/types/leaderboard.ts | 28 +++++++ vinvoor/src/types/table.ts | 3 +- vinvoor/yarn.lock | 25 +++++- 18 files changed, 313 insertions(+), 16 deletions(-) create mode 100644 vinvoor/public/first-first-place-svgrepo-com.svg create mode 100644 vinvoor/public/second-svgrepo-com.svg create mode 100644 vinvoor/public/third-svgrepo-com.svg rename vinvoor/src/cards/{CardsTableToolBar.tsx => CardsTableToolbar.tsx} (90%) create mode 100644 vinvoor/src/leaderboard/Leaderboard.tsx create mode 100644 vinvoor/src/leaderboard/LeaderboardTableBody.tsx create mode 100644 vinvoor/src/leaderboard/LeaderboardTableHead.tsx create mode 100644 vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx create mode 100644 vinvoor/src/types/leaderboard.ts diff --git a/vinvoor/package.json b/vinvoor/package.json index 22b4c54..2f17aa0 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -18,10 +18,13 @@ "@mui/material": "^5.15.19", "@types/js-cookie": "^3.0.6", "@types/react-router-dom": "^5.3.3", + "@types/react-router-hash-link": "^2.4.9", "js-cookie": "^3.0.5", + "mdi-material-ui": "^7.9.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.23.1" + "react-router-dom": "^6.23.1", + "react-router-hash-link": "^2.4.3" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/vinvoor/public/first-first-place-svgrepo-com.svg b/vinvoor/public/first-first-place-svgrepo-com.svg new file mode 100644 index 0000000..12810a7 --- /dev/null +++ b/vinvoor/public/first-first-place-svgrepo-com.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vinvoor/public/second-svgrepo-com.svg b/vinvoor/public/second-svgrepo-com.svg new file mode 100644 index 0000000..c1f4bb3 --- /dev/null +++ b/vinvoor/public/second-svgrepo-com.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vinvoor/public/third-svgrepo-com.svg b/vinvoor/public/third-svgrepo-com.svg new file mode 100644 index 0000000..b9d2951 --- /dev/null +++ b/vinvoor/public/third-svgrepo-com.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx index 4eeb7d3..8c463aa 100644 --- a/vinvoor/src/cards/CardsTable.tsx +++ b/vinvoor/src/cards/CardsTable.tsx @@ -12,7 +12,7 @@ import { Card } from "../types/cards"; import { TableOrder } from "../types/table"; import { CardsTableBody } from "./CardsTableBody"; import { CardsTableHead } from "./CardsTableHead"; -import { CardsTableToolbar } from "./CardsTableToolBar"; +import { CardsTableToolbar } from "./CardsTableToolbar"; interface CardTableProps { cards: readonly Card[]; diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx index f103afb..0035265 100644 --- a/vinvoor/src/cards/CardsTableBody.tsx +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -43,9 +43,7 @@ export const CardsTableBody: FC = ({ {row[headCell.id]} diff --git a/vinvoor/src/cards/CardsTableHead.tsx b/vinvoor/src/cards/CardsTableHead.tsx index 6d0ff66..c8d5589 100644 --- a/vinvoor/src/cards/CardsTableHead.tsx +++ b/vinvoor/src/cards/CardsTableHead.tsx @@ -50,7 +50,7 @@ export const CardsTableHead: FC = ({ >; } -export const CardsTableToolbar: FC = ({ +export const CardsTableToolbar: FC = ({ selected, setCards, }) => { @@ -32,7 +32,7 @@ export const CardsTableToolbar: FC = ({ {numSelected > 0 ? ( <> diff --git a/vinvoor/src/leaderboard/Leaderboard.tsx b/vinvoor/src/leaderboard/Leaderboard.tsx new file mode 100644 index 0000000..eed034c --- /dev/null +++ b/vinvoor/src/leaderboard/Leaderboard.tsx @@ -0,0 +1,36 @@ +import { Divider, Paper, Table, TableContainer } from "@mui/material"; +import { useState } from "react"; +import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useFetch } from "../hooks/useFetch"; +import { LeaderboardItem } from "../types/leaderboard"; +import { LeaderboardTableBody } from "./LeaderboardTableBody"; +import { LeaderboardTableToolbar } from "./LeaderboardTableToolbar"; + +export const Leaderboard = () => { + const [leaderboardItems, setLeaderboardItems] = useState< + readonly LeaderboardItem[] + >([]); + const { loading, error: _ } = useFetch( + "leaderboard", + setLeaderboardItems + ); + + return ( + + + + + + + {/* */} + +
+
+
+
+ ); +}; diff --git a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx new file mode 100644 index 0000000..81a286d --- /dev/null +++ b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx @@ -0,0 +1,76 @@ +import { + styled, + TableBody, + TableCell, + tableCellClasses, + TableRow, + Typography, +} from "@mui/material"; +import { PodiumBronze, PodiumGold, PodiumSilver } from "mdi-material-ui"; +import { FC } from "react"; +import { leaderboardHeadCells, LeaderboardItem } from "../types/leaderboard"; + +interface LeaderboardTableBodyProps { + leaderboardItems: readonly LeaderboardItem[]; +} + +const StyledTableCell = styled(TableCell)(({ theme }) => ({ + [`&.${tableCellClasses.head}`]: { + backgroundColor: theme.palette.common.black, + color: theme.palette.common.white, + }, + [`&.${tableCellClasses.body}`]: { + fontSize: 14, + }, +})); + +const StyledTableRow = styled(TableRow)(({ theme }) => ({ + "&:nth-of-type(odd)": { + backgroundColor: theme.palette.action.hover, + }, + // hide last border + "&:last-child td, &:last-child th": { + border: 0, + }, +})); + +const getPosition = (position: number) => { + switch (position) { + case 1: + return ; + case 2: + return ; + case 3: + return ; + default: + return {position}; + } +}; + +export const LeaderboardTableBody: FC = ({ + leaderboardItems: rows, +}) => { + return ( + + {rows.map((row) => { + return ( + + {leaderboardHeadCells.map((headCell) => ( + + {headCell.id === "position" ? ( + getPosition(row[headCell.id]) + ) : ( + {row[headCell.id]} + )} + + ))} + + ); + })} + + ); +}; diff --git a/vinvoor/src/leaderboard/LeaderboardTableHead.tsx b/vinvoor/src/leaderboard/LeaderboardTableHead.tsx new file mode 100644 index 0000000..9f44f14 --- /dev/null +++ b/vinvoor/src/leaderboard/LeaderboardTableHead.tsx @@ -0,0 +1,16 @@ +import { TableCell, TableHead, TableRow, Typography } from "@mui/material"; +import { leaderboardHeadCells } from "../types/leaderboard"; + +export const LeaderboardTableHead = () => { + return ( + + + {leaderboardHeadCells.map((headCell) => ( + + {headCell.label} + + ))} + + + ); +}; diff --git a/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx new file mode 100644 index 0000000..4bd090f --- /dev/null +++ b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx @@ -0,0 +1,27 @@ +import { MoveDown } from "@mui/icons-material"; +import { Button, Toolbar, Typography } from "@mui/material"; +import { FC, useContext } from "react"; +import { HashLink } from "react-router-hash-link"; +import { UserContext } from "../user/UserProvider"; + +interface LeaderboardTableToolbarProps {} + +export const LeaderboardTableToolbar: FC< + LeaderboardTableToolbarProps +> = ({}) => { + const { user } = useContext(UserContext); + + return ( + + + Ranking + + + + + + ); +}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index b64ddd8..ea7dba5 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -6,8 +6,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import { App } from "./App.tsx"; -import { Cards } from "./cards/Card.tsx"; +import { Cards } from "./cards/Cards.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; +import { Leaderboard } from "./leaderboard/Leaderboard.tsx"; import { Login } from "./user/Login.tsx"; import { Logout } from "./user/Logout.tsx"; @@ -29,6 +30,10 @@ const router = createBrowserRouter([ path: "cards", element: , }, + { + path: "leaderboard", + element: , + }, ], }, ]); diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx index ec92d4c..4608b3b 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -7,7 +7,7 @@ import { NavBarPages } from "./NavBarPages"; import { NavBarSandwich } from "./NavBarSandwich"; import { NavBarUserMenu } from "./NavBarUserMenu"; -const pages = ["Cards"]; +const pages = ["Cards", "Leaderboard"]; const settings = ["Logout"]; export const NavBar = () => { diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index 0efd465..223a1d3 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -10,12 +10,12 @@ export const CardsHeadCells: readonly TableHeadCell[] = [ id: "serial", label: "Serial", align: "left", - disablePadding: true, + padding: "none", }, { id: "createdAt", label: "Created at", align: "right", - disablePadding: false, + padding: "normal", }, ]; diff --git a/vinvoor/src/types/leaderboard.ts b/vinvoor/src/types/leaderboard.ts new file mode 100644 index 0000000..4493005 --- /dev/null +++ b/vinvoor/src/types/leaderboard.ts @@ -0,0 +1,28 @@ +import { TableHeadCell } from "./table"; + +export interface LeaderboardItem { + position: number; + username: string; + totalDays: number; +} + +export const leaderboardHeadCells: readonly TableHeadCell[] = [ + { + id: "position", + label: "#", + align: "center", + padding: "checkbox", + }, + { + id: "username", + label: "Username", + align: "left", + padding: "normal", + }, + { + id: "totalDays", + label: "Total Days", + align: "right", + padding: "normal", + }, +]; diff --git a/vinvoor/src/types/table.ts b/vinvoor/src/types/table.ts index 16adf90..3d4a059 100644 --- a/vinvoor/src/types/table.ts +++ b/vinvoor/src/types/table.ts @@ -1,10 +1,11 @@ export type TableOrder = "asc" | "desc"; type TableAlignOptions = "right" | "left" | "center"; +type TablePaddingOptions = "none" | "normal" | "checkbox"; export interface TableHeadCell { id: keyof T; label: string; align: TableAlignOptions; - disablePadding: boolean; + padding: TablePaddingOptions; } diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index 3ce100e..78fe7ca 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -775,7 +775,7 @@ dependencies: "@types/react" "*" -"@types/react-router-dom@^5.3.3": +"@types/react-router-dom@^5.3.0", "@types/react-router-dom@^5.3.3": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw== @@ -784,6 +784,15 @@ "@types/react" "*" "@types/react-router" "*" +"@types/react-router-hash-link@^2.4.9": + version "2.4.9" + resolved "https://registry.yarnpkg.com/@types/react-router-hash-link/-/react-router-hash-link-2.4.9.tgz#b9f069fb5faeba2477426b3932205d080f72ba99" + integrity sha512-zl/VMj+lfJZhvjOAQXIlBVPNKSK+/fRG8AUHhlP9++LhlA2ziLeTmbRxIMJI3PCiCTS+W/FosEoDRoNOGH0OzA== + dependencies: + "@types/history" "^4.7.11" + "@types/react" "*" + "@types/react-router-dom" "^5.3.0" + "@types/react-router@*": version "5.1.20" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c" @@ -1570,6 +1579,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +mdi-material-ui@^7.9.1: + version "7.9.1" + resolved "https://registry.yarnpkg.com/mdi-material-ui/-/mdi-material-ui-7.9.1.tgz#f28dbd6883a8c6198ca78e19c9f23728b2599226" + integrity sha512-13Ecd6hbYZWzY1yLCwQRGqnYzIv9HjSbRM7Vl+4GsllBY6KWv0IhCr4gVlcMGf+ahJxBH3Ba5ImZICls1Aqtyg== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1716,7 +1730,7 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prop-types@^15.6.2, prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -1761,6 +1775,13 @@ react-router-dom@^6.23.1: "@remix-run/router" "1.16.1" react-router "6.23.1" +react-router-hash-link@^2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-2.4.3.tgz#570824d53d6c35ce94d73a46c8e98673a127bf08" + integrity sha512-NU7GWc265m92xh/aYD79Vr1W+zAIXDWp3L2YZOYP4rCqPnJ6LI6vh3+rKgkidtYijozHclaEQTAHaAaMWPVI4A== + dependencies: + prop-types "^15.7.2" + react-router@6.23.1: version "6.23.1" resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.23.1.tgz#d08cbdbd9d6aedc13eea6e94bc6d9b29cb1c4be9" From 221bd9ae7fc98bb540b0192a4f3a5037048ce190 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sun, 23 Jun 2024 01:57:36 +0200 Subject: [PATCH 21/23] vinvoor: add a welcome page --- vingo/database/users.go | 2 +- ...-place-svgrepo-com.svg => first_place.svg} | 0 ...econd-svgrepo-com.svg => second_place.svg} | 0 ...{third-svgrepo-com.svg => third_place.svg} | 0 vinvoor/src/App.tsx | 39 ++++++++++----- vinvoor/src/WelcomePage.tsx | 49 +++++++++++++++++++ .../leaderboard/LeaderboardTableToolbar.tsx | 4 +- vinvoor/src/main.tsx | 20 ++++++-- vinvoor/src/navbar/NavBar.tsx | 4 +- vinvoor/src/navbar/NavBarUserMenu.tsx | 4 +- vinvoor/src/theme/theme.ts | 16 ++++++ vinvoor/src/user/Login.tsx | 2 +- vinvoor/src/user/Logout.tsx | 2 +- vinvoor/src/user/UserProvider.tsx | 43 +++++++++++++--- 14 files changed, 153 insertions(+), 32 deletions(-) rename vinvoor/public/{first-first-place-svgrepo-com.svg => first_place.svg} (100%) rename vinvoor/public/{second-svgrepo-com.svg => second_place.svg} (100%) rename vinvoor/public/{third-svgrepo-com.svg => third_place.svg} (100%) create mode 100644 vinvoor/src/WelcomePage.tsx diff --git a/vingo/database/users.go b/vingo/database/users.go index 136258c..e46a148 100644 --- a/vingo/database/users.go +++ b/vingo/database/users.go @@ -1,10 +1,10 @@ package database type User struct { + Id int `json:"id"` Username string `json:"username"` Admin bool `json:"admin"` Settings Settings `json:"settings"` - Id int `json:"id"` } var ( diff --git a/vinvoor/public/first-first-place-svgrepo-com.svg b/vinvoor/public/first_place.svg similarity index 100% rename from vinvoor/public/first-first-place-svgrepo-com.svg rename to vinvoor/public/first_place.svg diff --git a/vinvoor/public/second-svgrepo-com.svg b/vinvoor/public/second_place.svg similarity index 100% rename from vinvoor/public/second-svgrepo-com.svg rename to vinvoor/public/second_place.svg diff --git a/vinvoor/public/third-svgrepo-com.svg b/vinvoor/public/third_place.svg similarity index 100% rename from vinvoor/public/third-svgrepo-com.svg rename to vinvoor/public/third_place.svg diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 8e766e0..8371a6c 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,20 +1,33 @@ -import { Container, CssBaseline } from "@mui/material"; -import { Outlet } from "react-router-dom"; +import { Container } from "@mui/material"; +import { useContext } from "react"; +import { Navigate, Outlet } from "react-router-dom"; +import { LoadingSkeleton } from "./components/LoadingSkeleton"; import { NavBar } from "./navbar/NavBar"; -import { ThemeProvider } from "./theme/ThemeProvider"; -import { UserProvider } from "./user/UserProvider"; +import { UserContext } from "./user/UserProvider"; +import { WelcomePage } from "./WelcomePage"; export const App = () => { + const { + userState: { user, loading }, + } = useContext(UserContext); + return ( - - - - - + <> + + + + {user !== undefined ? ( - - - - + ) : ( + <> + + + + )} + + + ); }; + +// TODO: Add link to the github repo diff --git a/vinvoor/src/WelcomePage.tsx b/vinvoor/src/WelcomePage.tsx new file mode 100644 index 0000000..c8cd2f7 --- /dev/null +++ b/vinvoor/src/WelcomePage.tsx @@ -0,0 +1,49 @@ +import { GitHub } from "@mui/icons-material"; +import { Box, Button, Typography } from "@mui/material"; +import { ShakerOutline } from "mdi-material-ui"; +import { TypographyG } from "./components/TypographyG"; +import { UnstyledLink } from "./components/UnstyledLink"; + +declare module "@mui/material/Button" { + interface ButtonPropsColorOverrides { + github: true; + } +} + +export const WelcomePage = () => { + const handleClick = () => { + window.location.replace("https://github.com/ZeusWPI/ZeSS"); + }; + + return ( + + Welcome to Vinvoor! + Log in to start scanning + + + + + + ); +}; diff --git a/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx index 4bd090f..44f9103 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx +++ b/vinvoor/src/leaderboard/LeaderboardTableToolbar.tsx @@ -9,7 +9,9 @@ interface LeaderboardTableToolbarProps {} export const LeaderboardTableToolbar: FC< LeaderboardTableToolbarProps > = ({}) => { - const { user } = useContext(UserContext); + const { + userState: { user }, + } = useContext(UserContext); return ( diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index ea7dba5..8f070ec 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -2,6 +2,7 @@ import "@fontsource/roboto/300.css"; import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; +import { CssBaseline } from "@mui/material"; import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; @@ -9,8 +10,10 @@ import { App } from "./App.tsx"; import { Cards } from "./cards/Cards.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; import { Leaderboard } from "./leaderboard/Leaderboard.tsx"; +import { ThemeProvider } from "./theme/ThemeProvider"; import { Login } from "./user/Login.tsx"; import { Logout } from "./user/Logout.tsx"; +import { UserProvider } from "./user/UserProvider.tsx"; const router = createBrowserRouter([ { @@ -18,10 +21,6 @@ const router = createBrowserRouter([ element: , errorElement: , children: [ - { - path: "login", - element: , - }, { path: "logout", element: , @@ -36,10 +35,21 @@ const router = createBrowserRouter([ }, ], }, + { + path: "/login", + element: , + errorElement: , + }, ]); ReactDOM.createRoot(document.getElementById("root")!).render( - + + + + + + + ); diff --git a/vinvoor/src/navbar/NavBar.tsx b/vinvoor/src/navbar/NavBar.tsx index 4608b3b..be65857 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -11,7 +11,9 @@ const pages = ["Cards", "Leaderboard"]; const settings = ["Logout"]; export const NavBar = () => { - const { user } = useContext(UserContext); + const { + userState: { user }, + } = useContext(UserContext); const screenSize = { mobile: { xs: "flex", md: "none" }, diff --git a/vinvoor/src/navbar/NavBarUserMenu.tsx b/vinvoor/src/navbar/NavBarUserMenu.tsx index 1eb6bd6..8d64df7 100644 --- a/vinvoor/src/navbar/NavBarUserMenu.tsx +++ b/vinvoor/src/navbar/NavBarUserMenu.tsx @@ -9,7 +9,9 @@ interface NavBarUserMenuProps { } export const NavBarUserMenu: FC = ({ settings }) => { - const { user } = useContext(UserContext); + const { + userState: { user }, + } = useContext(UserContext); const [anchorElUser, setAnchorElUser] = useState( undefined ); diff --git a/vinvoor/src/theme/theme.ts b/vinvoor/src/theme/theme.ts index 072d299..fca57c3 100644 --- a/vinvoor/src/theme/theme.ts +++ b/vinvoor/src/theme/theme.ts @@ -9,6 +9,9 @@ export const lightTheme = createTheme({ secondary: { main: "#002379", }, + github: { + main: "#FFF4F2", + }, }, }); @@ -21,6 +24,9 @@ export const darkTheme = createTheme({ secondary: { main: "#002379", }, + github: { + main: "#996860", + }, }, components: { MuiAppBar: { @@ -32,3 +38,13 @@ export const darkTheme = createTheme({ }, }, }); + +declare module "@mui/material/styles" { + interface Palette { + github: Palette["primary"]; + } + + interface PaletteOptions { + github?: PaletteOptions["primary"]; + } +} diff --git a/vinvoor/src/user/Login.tsx b/vinvoor/src/user/Login.tsx index a19456b..97276ee 100644 --- a/vinvoor/src/user/Login.tsx +++ b/vinvoor/src/user/Login.tsx @@ -4,7 +4,7 @@ export const Login = () => { const baseUrl = import.meta.env.VITE_BASE_URL; useEffect(() => { - window.location.href = `${baseUrl}/login`; + window.location.replace(`${baseUrl}/login`); }, []); return <>; diff --git a/vinvoor/src/user/Logout.tsx b/vinvoor/src/user/Logout.tsx index 9009a6d..f4b8222 100644 --- a/vinvoor/src/user/Logout.tsx +++ b/vinvoor/src/user/Logout.tsx @@ -4,7 +4,7 @@ export const Logout = () => { const baseUrl = import.meta.env.VITE_BASE_URL; useEffect(() => { - window.location.href = `${baseUrl}/logout`; + window.location.replace(`${baseUrl}/logout`); }, []); return <>; diff --git a/vinvoor/src/user/UserProvider.tsx b/vinvoor/src/user/UserProvider.tsx index 6f3a86f..7577470 100644 --- a/vinvoor/src/user/UserProvider.tsx +++ b/vinvoor/src/user/UserProvider.tsx @@ -15,33 +15,60 @@ interface UserProviderProps { children: ReactNode; } -interface UserContextProps { +interface UserState { user: User | undefined; - setUser: Dispatch>; + loading: boolean; + error: Error | undefined; } -export const UserContext = createContext({ +interface UserContextProps { + userState: UserState; + setUserState: Dispatch>; +} + +const defaultUserState: UserState = { user: undefined, - setUser: () => {}, + loading: true, + error: undefined, +}; + +export const UserContext = createContext({ + userState: defaultUserState, + setUserState: () => {}, }); export const UserProvider: FC = ({ children }) => { - const [user, setUser] = useState(undefined); + const [userState, setUserState] = useState(defaultUserState); useEffect(() => { const sessionId = Cookies.get("session_id"); if (!sessionId) { + setUserState({ + user: undefined, + loading: false, + error: new Error("No session ID"), + }); + return; } + let newUserState = { ...userState }; + fetchApi("user") - .then((data) => setUser(data)) - .catch(() => Cookies.remove("session_id")); + .then((data) => (newUserState.user = data)) + .catch((error) => { + Cookies.remove("session_id"); + newUserState.error = error; + }) + .finally(() => { + newUserState.loading = false; + setUserState(newUserState); + }); }, []); return ( - + {children} ); From 5fbf7d9b4cfb1f13f53fd9a90e20273fea778048 Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 25 Jun 2024 21:39:02 +0200 Subject: [PATCH 22/23] zess: change env var to export in dev script --- dev.sh | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/dev.sh b/dev.sh index 4a5229e..68b1c8e 100755 --- a/dev.sh +++ b/dev.sh @@ -1,20 +1,7 @@ -#!/bin/sh +#!/usr/bin/env bash # Current user - -UID=$(id -u):$(id -g) - -# Exit function - -ctrl_c() { - echo "-------------------------------------" - echo "Stopping all containers..." - echo "-------------------------------------" - CURRENT_UID=$UID docker-compose -f docker-compose.yml down - exit 0 -} - -trap ctrl_c INT +export CURRENT_UID=$(id -u):$(id -g) # Parse input @@ -50,8 +37,7 @@ if [ ! -f vinvoor/.env ]; then fi # Start the docker containers - -CURRENT_UID=$UID docker-compose -f docker-compose.yml up -d +docker-compose -f docker-compose.yml up -d echo "-------------------------------------" echo "Following logs..." @@ -66,4 +52,4 @@ else docker-compose -f docker-compose.yml logs -f zess-backend zess-frontend fi -CURRENT_UID=$UID docker-compose -f docker-compose.yml down +docker-compose -f docker-compose.yml down From 632bfb0c188b743ad6a99b179251df9a7b49d409 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 28 Jun 2024 15:09:54 +0200 Subject: [PATCH 23/23] vinvoor: add a github activity style overview --- vinvoor/package.json | 3 +- vinvoor/src/App.tsx | 13 +- vinvoor/src/cards/Cards.tsx | 2 +- vinvoor/src/cards/CardsTableBody.tsx | 3 - vinvoor/src/leaderboard/Leaderboard.tsx | 3 +- .../src/leaderboard/LeaderboardTableBody.tsx | 63 ++--- .../src/leaderboard/LeaderboardTableHead.tsx | 16 -- vinvoor/src/main.tsx | 1 + vinvoor/src/navbar/NavBarLogo.tsx | 39 +-- vinvoor/src/overview/Overview.tsx | 45 ++++ vinvoor/src/overview/heatmap/Heatmap.tsx | 253 ++++++++++++++++++ vinvoor/src/overview/heatmap/heatmap.css | 62 +++++ vinvoor/src/overview/heatmap/utils.ts | 125 +++++++++ vinvoor/src/types/scans.ts | 4 + vinvoor/yarn.lock | 15 +- 15 files changed, 567 insertions(+), 80 deletions(-) delete mode 100644 vinvoor/src/leaderboard/LeaderboardTableHead.tsx create mode 100644 vinvoor/src/overview/Overview.tsx create mode 100644 vinvoor/src/overview/heatmap/Heatmap.tsx create mode 100644 vinvoor/src/overview/heatmap/heatmap.css create mode 100644 vinvoor/src/overview/heatmap/utils.ts create mode 100644 vinvoor/src/types/scans.ts diff --git a/vinvoor/package.json b/vinvoor/package.json index 2f17aa0..28cc4c0 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -24,7 +24,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1", - "react-router-hash-link": "^2.4.3" + "react-router-hash-link": "^2.4.3", + "react-tooltip": "^5.27.0" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/vinvoor/src/App.tsx b/vinvoor/src/App.tsx index 8371a6c..76278d2 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -1,8 +1,9 @@ import { Container } from "@mui/material"; import { useContext } from "react"; -import { Navigate, Outlet } from "react-router-dom"; +import { Navigate, Outlet, useOutlet } from "react-router-dom"; import { LoadingSkeleton } from "./components/LoadingSkeleton"; import { NavBar } from "./navbar/NavBar"; +import { Overview } from "./overview/Overview"; import { UserContext } from "./user/UserProvider"; import { WelcomePage } from "./WelcomePage"; @@ -11,13 +12,19 @@ export const App = () => { userState: { user, loading }, } = useContext(UserContext); + const outlet = useOutlet(); + return ( <> {user !== undefined ? ( - + outlet !== null ? ( + + ) : ( + + ) ) : ( <> @@ -29,5 +36,3 @@ export const App = () => { ); }; - -// TODO: Add link to the github repo diff --git a/vinvoor/src/cards/Cards.tsx b/vinvoor/src/cards/Cards.tsx index fa2c699..5570818 100644 --- a/vinvoor/src/cards/Cards.tsx +++ b/vinvoor/src/cards/Cards.tsx @@ -7,7 +7,7 @@ import { CardsTable } from "./CardsTable"; export const Cards = () => { const [cards, setCards] = useState([]); - const { loading, error: _ } = useFetch("cards", setCards); + const { loading } = useFetch("cards", setCards); return ( diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx index 0035265..a6a64b0 100644 --- a/vinvoor/src/cards/CardsTableBody.tsx +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -63,6 +63,3 @@ export const CardsTableBody: FC = ({ ); }; - -// TODO: Go over all mouse events -// TODO: Move all components props diff --git a/vinvoor/src/leaderboard/Leaderboard.tsx b/vinvoor/src/leaderboard/Leaderboard.tsx index eed034c..ca9ebb6 100644 --- a/vinvoor/src/leaderboard/Leaderboard.tsx +++ b/vinvoor/src/leaderboard/Leaderboard.tsx @@ -10,7 +10,7 @@ export const Leaderboard = () => { const [leaderboardItems, setLeaderboardItems] = useState< readonly LeaderboardItem[] >([]); - const { loading, error: _ } = useFetch( + const { loading } = useFetch( "leaderboard", setLeaderboardItems ); @@ -24,7 +24,6 @@ export const Leaderboard = () => { /> - {/* */} diff --git a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx index 81a286d..9fd6302 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx +++ b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx @@ -1,39 +1,14 @@ -import { - styled, - TableBody, - TableCell, - tableCellClasses, - TableRow, - Typography, -} from "@mui/material"; +import { TableBody, TableCell, TableRow, Typography } from "@mui/material"; +import { alpha } from "@mui/material/styles"; import { PodiumBronze, PodiumGold, PodiumSilver } from "mdi-material-ui"; -import { FC } from "react"; +import { FC, useContext } from "react"; import { leaderboardHeadCells, LeaderboardItem } from "../types/leaderboard"; +import { UserContext } from "../user/UserProvider"; interface LeaderboardTableBodyProps { leaderboardItems: readonly LeaderboardItem[]; } -const StyledTableCell = styled(TableCell)(({ theme }) => ({ - [`&.${tableCellClasses.head}`]: { - backgroundColor: theme.palette.common.black, - color: theme.palette.common.white, - }, - [`&.${tableCellClasses.body}`]: { - fontSize: 14, - }, -})); - -const StyledTableRow = styled(TableRow)(({ theme }) => ({ - "&:nth-of-type(odd)": { - backgroundColor: theme.palette.action.hover, - }, - // hide last border - "&:last-child td, &:last-child th": { - border: 0, - }, -})); - const getPosition = (position: number) => { switch (position) { case 1: @@ -50,13 +25,33 @@ const getPosition = (position: number) => { export const LeaderboardTableBody: FC = ({ leaderboardItems: rows, }) => { + const { + userState: { user }, + } = useContext(UserContext); + return ( - {rows.map((row) => { + {rows.map((row, index) => { return ( - + + theme.palette.action.hover, + }), + ...(row.username === user!.username && { + backgroundColor: (theme) => + alpha( + theme.palette.primary.main, + theme.palette.action.activatedOpacity + ), + }), + }} + > {leaderboardHeadCells.map((headCell) => ( - = ({ ) : ( {row[headCell.id]} )} - + ))} - + ); })} diff --git a/vinvoor/src/leaderboard/LeaderboardTableHead.tsx b/vinvoor/src/leaderboard/LeaderboardTableHead.tsx deleted file mode 100644 index 9f44f14..0000000 --- a/vinvoor/src/leaderboard/LeaderboardTableHead.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { TableCell, TableHead, TableRow, Typography } from "@mui/material"; -import { leaderboardHeadCells } from "../types/leaderboard"; - -export const LeaderboardTableHead = () => { - return ( - - - {leaderboardHeadCells.map((headCell) => ( - - {headCell.label} - - ))} - - - ); -}; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index 8f070ec..b79cca9 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -6,6 +6,7 @@ import { CssBaseline } from "@mui/material"; import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import "react-tooltip/dist/react-tooltip.css"; import { App } from "./App.tsx"; import { Cards } from "./cards/Cards.tsx"; import { ErrorPage } from "./errors/ErrorPage.tsx"; diff --git a/vinvoor/src/navbar/NavBarLogo.tsx b/vinvoor/src/navbar/NavBarLogo.tsx index 58c6e2a..f2986a0 100644 --- a/vinvoor/src/navbar/NavBarLogo.tsx +++ b/vinvoor/src/navbar/NavBarLogo.tsx @@ -1,4 +1,4 @@ -import { Button, SxProps, Theme, Typography } from "@mui/material"; +import { Box, Button, SxProps, Theme, Typography } from "@mui/material"; import { FC } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; @@ -8,25 +8,28 @@ interface NavBarLogoProps { export const NavBarLogo: FC = ({ sx }) => { return ( - - - + + ZeSS + + + + + ); }; diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx new file mode 100644 index 0000000..1da7469 --- /dev/null +++ b/vinvoor/src/overview/Overview.tsx @@ -0,0 +1,45 @@ +import { Box, Paper, Switch, Typography } from "@mui/material"; +import { useState } from "react"; +import { Tooltip } from "react-tooltip"; +import { LoadingSkeleton } from "../components/LoadingSkeleton"; +import { useFetch } from "../hooks/useFetch"; +import { Scan } from "../types/scans"; +import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap"; + +export const Overview = () => { + const [scans, setScans] = useState([]); + const { loading } = useFetch("scans", setScans); + const [checked, setChecked] = useState(true); + + const dates = scans.map((scan) => new Date(scan.scanTime)); + + const handleChange = (event: React.ChangeEvent) => { + setChecked(event.target.checked); + }; + + return ( + + + + Months + + Days + + + + + + ); +}; diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx new file mode 100644 index 0000000..42dd7a7 --- /dev/null +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -0,0 +1,253 @@ +import { FC } from "react"; +import "./heatmap.css"; +import { + dateTimeFormat, + DAYS_IN_WEEK, + getColumnCount, + getEmpty, + getHeight, + getMonthLabelCoordinates, + getSquareCoordinates, + getTransformForAllWeeks, + getTransformForColumn, + getTransformForMonthLabels, + getWidth, + MILLISECONDS_IN_ONE_DAY, + MONTH_LABELS, + shiftDate, + SQUARE_SIZE, +} from "./utils"; + +export interface HeatmapItem { + date: Date; + count: number; +} + +export enum HeatmapVariant { + DAYS, + MONTHS, +} + +interface HeatmapProps { + days: readonly Date[]; + startDate: Date; + endDate: Date; + variant: HeatmapVariant; +} + +const getAllValues = ( + days: readonly Date[], + startDate: Date, + endDate: Date, + variant: HeatmapVariant +): HeatmapItem[] => { + const values: readonly HeatmapItem[] = days.map((date) => ({ + date, + count: 1, + })); + if (variant === HeatmapVariant.DAYS) { + return Array.from( + { + length: + (endDate.getTime() - startDate.getTime()) / + MILLISECONDS_IN_ONE_DAY + + 1, + }, + (_, i) => { + const date = shiftDate(startDate, i); + return ( + values.find((v) => v.date.getTime() === date.getTime()) || { + date, + count: 0, + } + ); + } + ); + } else { + return Array.from( + { length: getColumnCount(startDate, endDate, HeatmapVariant.DAYS) }, + (_, i) => { + const start = shiftDate(startDate, i * DAYS_IN_WEEK); + const count = Array.from({ + length: DAYS_IN_WEEK, + }).reduce((sum, _, j) => { + const date = shiftDate(start, j); + const value = values.find( + (v) => v.date.getTime() === date.getTime() + ); + return sum + (value ? value.count : 0); + }, 0); + + return { date: start, count }; + } + ); + } +}; + +const getWeeksInMonth = (values: HeatmapItem[]): { [key: number]: number } => { + const startYear = values[0].date.getFullYear(); + return values.reduce( + (acc, value) => { + const index = + (value.date.getFullYear() - startYear) * 12 + + value.date.getMonth(); + acc[index] = (acc[index] || 0) + 1; + return acc; + }, + { 0: getEmpty(values[0].date, HeatmapVariant.MONTHS) } as { + [key: number]: number; + } + ); +}; + +const getClassNameForValue = (value: HeatmapItem, variant: HeatmapVariant) => { + if (variant === HeatmapVariant.DAYS) { + if (value.count > 0) { + return `color-active`; + } + + return `color-inactive`; + } else { + if (value.count <= 5) { + return `color-${value.count}`; + } + return "color-5"; + } +}; + +const getTooltipDataAttrsForDate = ( + value: HeatmapItem, + variant: HeatmapVariant +) => { + return { + "data-tooltip-id": "heatmap", + "data-tooltip-content": + variant === HeatmapVariant.DAYS + ? getTooltipDataAttrsForDays(value) + : getTooltipDataAttrsForMonths(value), + }; +}; + +const getTooltipDataAttrsForDays = (value: HeatmapItem) => { + return `${ + value.count > 0 ? "Present" : "Absent" + } on ${dateTimeFormat.format(value.date)}`; +}; + +const getTooltipDataAttrsForMonths = (value: HeatmapItem) => { + return `${value.count} scan${ + value.count !== 1 ? "s" : "" + } on the week of ${dateTimeFormat.format(value.date)}`; +}; + +export const Heatmap: FC = ({ + days, + startDate, + endDate, + variant, +}) => { + days.forEach((date) => date.setHours(0, 0, 0, 0)); + startDate.setHours(0, 0, 0, 0); + endDate.setHours(0, 0, 0, 0); + + const values = getAllValues(days, startDate, endDate, variant); + + const viewBox = `0 0 ${getWidth(startDate, endDate, variant)} ${getHeight( + variant + )}`; + + const weeksInMonth = + variant === HeatmapVariant.MONTHS ? getWeeksInMonth(values) : {}; // Amount of weeks in each month + const columns = getColumnCount(startDate, endDate, variant); // Amount of columns of squares + const emptyStart = getEmpty(startDate, variant); // Amount of empty squares at the start + const emptyEnd = getEmpty(endDate, variant); // Amount of empty squares at the end + + let valueIndex = 0; + const renderSquare = (row: number, column: number) => { + if (column === 0 && row < emptyStart) { + return null; + } + + if (variant === HeatmapVariant.DAYS) { + if (column === columns - 1 && row > emptyEnd) { + return null; + } + } + + const value = values[valueIndex++]; + console.log(value); + + const [x, y] = getSquareCoordinates(row); + + return ( + + ); + }; + + const renderColumn = (column: number) => { + return ( + + {[ + ...Array( + variant === HeatmapVariant.DAYS + ? DAYS_IN_WEEK + : weeksInMonth[column] + ).keys(), + ].map((row) => renderSquare(row, column))} + + ); + }; + + const renderColumns = () => { + return [...Array(columns).keys()].map((column) => renderColumn(column)); + }; + + const renderMonthLabels = () => { + if (variant === HeatmapVariant.DAYS) { + return [...Array(columns).keys()].map((column) => { + const endOfWeek = shiftDate(startDate, column * DAYS_IN_WEEK); + const [x, y] = getMonthLabelCoordinates(column); + + return endOfWeek.getDate() >= 1 && + endOfWeek.getDate() <= DAYS_IN_WEEK ? ( + + {MONTH_LABELS[endOfWeek.getMonth()]} + + ) : null; + }); + } else { + return [...Array(columns).keys()].map((column) => { + if (column % 2 === 1) { + return null; + } + + const [x, y] = getMonthLabelCoordinates(column); + + return ( + + {MONTH_LABELS[column]} + + ); + }); + } + }; + + return ( + + + {renderMonthLabels()} + + {renderColumns()} + + ); +}; diff --git a/vinvoor/src/overview/heatmap/heatmap.css b/vinvoor/src/overview/heatmap/heatmap.css new file mode 100644 index 0000000..d9361cf --- /dev/null +++ b/vinvoor/src/overview/heatmap/heatmap.css @@ -0,0 +1,62 @@ +.heatmap text { + font-size: 10px; + fill: #aaa; +} + +.heatmap rect:hover { + stroke: #555; + stroke-width: 1px; +} + +/* + Gradients for months variant +*/ + +.heatmap .color-0 { + fill: #eeeeee; + stroke-width: 1px; + stroke: #fcce9f; +} +.heatmap .color-1 { + fill: #fcce9f; + stroke-width: 1px; + stroke: #fcbb79; +} + +.heatmap .color-2 { + fill: #fcbb79; + stroke-width: 1px; + stroke: #fa922a; +} +.heatmap .color-3 { + fill: #fa922a; + stroke-width: 1px; + stroke: #ff7f00; +} + +.heatmap .color-4 { + fill: #ff7f00; + stroke-width: 1px; + stroke: #ba5f02; +} +.heatmap .color-5 { + fill: #ba5f02; + stroke-width: 1px; + stroke: #ba5f02; +} + +/* + Active or not active for days variant +*/ + +.heatmap .color-inactive { + fill: #eeeeee; + stroke-width: 1px; + stroke: #fcce9f; +} + +.heatmap .color-active { + fill: #ff7f00; + stroke-width: 1px; + stroke: #ba5f02; +} diff --git a/vinvoor/src/overview/heatmap/utils.ts b/vinvoor/src/overview/heatmap/utils.ts new file mode 100644 index 0000000..641b87c --- /dev/null +++ b/vinvoor/src/overview/heatmap/utils.ts @@ -0,0 +1,125 @@ +// Exports + +import { HeatmapVariant } from "./Heatmap"; + +// Constants + +export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000; +export const DAYS_IN_WEEK = 7; +export const WEEKS_IN_MONTH = 5; +export const SQUARE_SIZE = 10; + +export const MONTH_LABELS = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; +export const dateTimeFormat = new Intl.DateTimeFormat("en-GB", { + year: "2-digit", + month: "short", + day: "numeric", +}); + +// Labels + +export const getMonthLabelSize = () => { + return SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE; +}; + +export const getMonthLabelCoordinates = (column: number) => { + return [ + column * getSquareSize(), + getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE, + ]; +}; + +// Transforms + +export const getTransformForColumn = (column: number) => { + return `translate(${column * getSquareSize() + GUTTERSIZE}, 0)`; +}; + +export const getTransformForAllWeeks = () => { + return `translate(0, ${getMonthLabelSize()})`; +}; + +export const getTransformForMonthLabels = () => { + return `translate(0, 0)`; +}; + +export const getWidth = ( + startDate: Date, + endDate: Date, + variant: HeatmapVariant +) => { + return ( + getColumnCount(startDate, endDate, variant) * getSquareSize() + + GUTTERSIZE + ); +}; + +export const getHeight = (variant: HeatmapVariant) => { + if (variant === HeatmapVariant.DAYS) { + return DAYS_IN_WEEK * getSquareSize() + getMonthLabelSize(); + } else { + return WEEKS_IN_MONTH * getSquareSize() + getMonthLabelSize(); + } +}; + +// Coordinate + +export const getSquareCoordinates = (dayIndex: number) => { + return [0, dayIndex * getSquareSize()]; +}; + +// Utils + +export const getEmpty = (date: Date, variant: HeatmapVariant) => { + if (variant === HeatmapVariant.DAYS) { + return (date.getDay() + DAYS_IN_WEEK - 1) % DAYS_IN_WEEK; + } else { + return Math.floor((date.getDate() - 1) / DAYS_IN_WEEK); + } +}; + +export const getColumnCount = ( + startDate: Date, + endDate: Date, + variant: HeatmapVariant +) => { + if (variant === HeatmapVariant.DAYS) { + return Math.ceil( + (endDate.getTime() - startDate.getTime()) / + (DAYS_IN_WEEK * MILLISECONDS_IN_ONE_DAY) + ); + } else { + return ( + (endDate.getFullYear() - startDate.getFullYear()) * 12 + + (endDate.getMonth() - startDate.getMonth() + 1) + ); + } +}; + +export const shiftDate = (date: Date, numDays: number) => { + const newDate = new Date(date); + newDate.setDate(newDate.getDate() + numDays); + return newDate; +}; + +// Local functions + +const GUTTERSIZE = 5; +const MONTH_LABEL_GUTTER_SIZE = 4; + +const getSquareSize = () => { + return SQUARE_SIZE + GUTTERSIZE; +}; diff --git a/vinvoor/src/types/scans.ts b/vinvoor/src/types/scans.ts new file mode 100644 index 0000000..e9cc504 --- /dev/null +++ b/vinvoor/src/types/scans.ts @@ -0,0 +1,4 @@ +export interface Scan { + scanTime: string; + card: string; +} diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index 78fe7ca..5d56e0c 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -384,7 +384,7 @@ dependencies: "@floating-ui/utils" "^0.2.0" -"@floating-ui/dom@^1.0.0": +"@floating-ui/dom@^1.0.0", "@floating-ui/dom@^1.6.1": version "1.6.5" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== @@ -1016,6 +1016,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +classnames@^2.3.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" + integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== + clsx@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" @@ -1789,6 +1794,14 @@ react-router@6.23.1: dependencies: "@remix-run/router" "1.16.1" +react-tooltip@^5.27.0: + version "5.27.0" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.27.0.tgz#d502d1055b259c26b10ebfc6d925621f2afd3119" + integrity sha512-JXROcdfCEbCqkAkh8LyTSP3guQ0dG53iY2E2o4fw3D8clKzziMpE6QG6CclDaHELEKTzpMSeAOsdtg0ahoQosw== + dependencies: + "@floating-ui/dom" "^1.6.1" + classnames "^2.3.0" + react-transition-group@^4.4.5: version "4.4.5" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"