From 3f4d7c722dc13b61cd1d5ce1503877e85bd1e0c1 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 11 Jun 2024 15:37:42 +0200 Subject: [PATCH 01/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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/53] 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 4b8372d30faba7608341eb22d91a63d6d2b3f48a Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 25 Jun 2024 22:54:46 +0200 Subject: [PATCH 23/53] vingo: add database migrations --- vingo/database/cards.go | 10 ------ vingo/database/days.go | 9 ----- vingo/database/db.go | 41 +++++++++++++-------- vingo/database/migrations/1_init.down.sql | 6 ++++ vingo/database/migrations/1_init.up.sql | 31 ++++++++++++++++ vingo/database/scans.go | 11 ------ vingo/database/settings.go | 11 ------ vingo/database/users.go | 11 ------ vingo/go.mod | 7 ++++ vingo/go.sum | 44 +++++++++++++++++++++++ 10 files changed, 115 insertions(+), 66 deletions(-) create mode 100644 vingo/database/migrations/1_init.down.sql create mode 100644 vingo/database/migrations/1_init.up.sql diff --git a/vingo/database/cards.go b/vingo/database/cards.go index ca05186..29ab3fe 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -7,16 +7,6 @@ type Card struct { CreatedAt time.Time `json:"createdAt"` } -var ( - cardsCreateStmt = ` - CREATE TABLE IF NOT EXISTS cards ( - serial TEXT NOT NULL PRIMARY KEY UNIQUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - user_id INTEGER NOT NULL REFERENCES users(id) - ); - ` -) - func CreateCard(serial string, user_id int) error { _, err := db.Exec("INSERT INTO cards (serial, user_id) VALUES ($1, $2);", serial, user_id) return err diff --git a/vingo/database/days.go b/vingo/database/days.go index f78688a..3187cb9 100644 --- a/vingo/database/days.go +++ b/vingo/database/days.go @@ -7,15 +7,6 @@ type Day struct { Date time.Time } -var ( - daysCreateStmt = ` - CREATE TABLE IF NOT EXISTS days ( - id SERIAL NOT NULL PRIMARY KEY, - date DATE NOT NULL UNIQUE - ); - ` -) - func CreateDays(first_day time.Time, last_day time.Time) error { tx, err := db.Begin() if err != nil { diff --git a/vingo/database/db.go b/vingo/database/db.go index 63b6768..3528343 100644 --- a/vingo/database/db.go +++ b/vingo/database/db.go @@ -3,24 +3,17 @@ package database import ( "database/sql" "log" + + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + _ "github.com/golang-migrate/migrate/v4/source/file" + _ "github.com/lib/pq" ) var ( db *sql.DB ) -func createTables() { - // Tables to create - createStmts := []string{usersCreateStmt, settingsCreateStmt, cardsCreateStmt, scansCreateStmt, daysCreateStmt} - for _, stmt := range createStmts { - _, err := db.Exec(stmt) - if err != nil { - log.Println("Error creating table with query: \n", stmt) - log.Fatal(err) - } - } -} - func Get() *sql.DB { return db } @@ -28,9 +21,29 @@ func Get() *sql.DB { func OpenDatabase(conn string) { new_db, err := sql.Open("postgres", conn) if err != nil { - log.Panicln("Error opening database connection") + log.Println("Error opening database connection") + log.Fatal(err) + } + + driver, err := postgres.WithInstance(new_db, &postgres.Config{}) + if err != nil { + log.Println("Error creating migration driver") log.Fatal(err) } + + m, err := migrate.NewWithDatabaseInstance( + "file://database/migrations", + "postgres", driver) + if err != nil { + log.Println("Error creating migration instance") + log.Fatal(err) + } + + err = m.Up() + if err != nil && err != migrate.ErrNoChange { + log.Println("Error running migrations") + log.Fatal(err) + } + db = new_db - createTables() } diff --git a/vingo/database/migrations/1_init.down.sql b/vingo/database/migrations/1_init.down.sql new file mode 100644 index 0000000..921a752 --- /dev/null +++ b/vingo/database/migrations/1_init.down.sql @@ -0,0 +1,6 @@ +DROP TABLE days; + +DROP TABLE scans; +DROP TABLE cards; +DROP TABLE settings; +DROP TABLE users; \ No newline at end of file diff --git a/vingo/database/migrations/1_init.up.sql b/vingo/database/migrations/1_init.up.sql new file mode 100644 index 0000000..547662a --- /dev/null +++ b/vingo/database/migrations/1_init.up.sql @@ -0,0 +1,31 @@ +CREATE TABLE IF NOT EXISTS days ( + id SERIAL NOT NULL PRIMARY KEY, + date DATE NOT NULL UNIQUE + ); + +CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + username TEXT NOT NULL, + admin BOOLEAN DEFAULT FALSE NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + +CREATE TABLE IF NOT EXISTS settings ( + user_id INT NOT NULL PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, + public BOOLEAN NOT NULL DEFAULT FALSE, + scan_in_out BOOLEAN NOT NULL DEFAULT FALSE, + leaderboard BOOLEAN NOT NULL DEFAULT TRUE + ); + +CREATE TABLE IF NOT EXISTS cards ( + serial TEXT NOT NULL PRIMARY KEY UNIQUE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + user_id INTEGER NOT NULL REFERENCES users(id) + ); + +CREATE TABLE IF NOT EXISTS scans ( + id SERIAL NOT NULL PRIMARY KEY, + scan_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + scan_in BOOLEAN, + card_serial TEXT NOT NULL REFERENCES cards(serial) + ); \ No newline at end of file diff --git a/vingo/database/scans.go b/vingo/database/scans.go index 3aa0aa2..f965321 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -19,17 +19,6 @@ type LeaderboardItem struct { TotalDays int `json:"totalDays"` } -var ( - scansCreateStmt = ` - CREATE TABLE IF NOT EXISTS scans ( - id SERIAL NOT NULL PRIMARY KEY, - scan_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - scan_in BOOLEAN, - card_serial TEXT NOT NULL REFERENCES cards(serial) - ); - ` -) - func CreateScan(card_serial string) error { _, err := db.Exec("INSERT INTO scans (card_serial) VALUES ($1);", card_serial) return err diff --git a/vingo/database/settings.go b/vingo/database/settings.go index 8ccd784..ef1b1cb 100644 --- a/vingo/database/settings.go +++ b/vingo/database/settings.go @@ -6,17 +6,6 @@ type Settings struct { Public bool `json:"public"` } -var ( - settingsCreateStmt = ` - CREATE TABLE IF NOT EXISTS settings ( - user_id INT NOT NULL PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - public BOOLEAN NOT NULL DEFAULT FALSE, - scan_in_out BOOLEAN NOT NULL DEFAULT FALSE, - leaderboard BOOLEAN NOT NULL DEFAULT TRUE - ); - ` -) - func CreateSettings(user_id int) error { _, err := db.Exec("INSERT INTO settings (user_id) VALUES ($1) ON CONFLICT DO NOTHING;", user_id) return err diff --git a/vingo/database/users.go b/vingo/database/users.go index e46a148..bb20309 100644 --- a/vingo/database/users.go +++ b/vingo/database/users.go @@ -7,17 +7,6 @@ type User struct { Settings Settings `json:"settings"` } -var ( - usersCreateStmt = ` - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL, - admin BOOLEAN DEFAULT FALSE NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL - ); - ` -) - func CreateUserIfNew(user_id int, username string) error { _, err := db.Exec("INSERT INTO users (id, username) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username;", user_id, username) if err != nil { diff --git a/vingo/go.mod b/vingo/go.mod index fecd3f4..a6f5dd1 100644 --- a/vingo/go.mod +++ b/vingo/go.mod @@ -9,10 +9,17 @@ require ( github.com/lib/pq v1.10.9 ) +require ( + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + go.uber.org/atomic v1.7.0 // indirect +) + require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/utils v1.1.0 // indirect + github.com/golang-migrate/migrate/v4 v4.17.1 github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/vingo/go.sum b/vingo/go.sum index 46246cd..981e998 100644 --- a/vingo/go.sum +++ b/vingo/go.sum @@ -1,7 +1,22 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= +github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= +github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= @@ -10,8 +25,17 @@ github.com/gofiber/template/html/v2 v2.1.1 h1:QEy3O3EBkvwDthy5bXVGUseOyO6ldJoiDx github.com/gofiber/template/html/v2 v2.1.1/go.mod h1:2G0GHHOUx70C1LDncoBpe4T6maQbNa4x1CVNFW0wju0= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= +github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= @@ -25,11 +49,23 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -38,9 +74,17 @@ github.com/valyala/fasthttp v1.53.0 h1:lW/+SUkOxCx2vlIu0iaImv4JLrVRnbbkpCoaawvA4 github.com/valyala/fasthttp v1.53.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= +golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= +golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 7762f73c9fcbc30581de81ceda7525c276b67f0d Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 25 Jun 2024 23:29:23 +0200 Subject: [PATCH 24/53] vingo: fix api returning null when no results --- vingo/database/scans.go | 11 ++++++----- vingo/handlers/leaderboard.go | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/vingo/database/scans.go b/vingo/database/scans.go index f965321..db08ce4 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -1,6 +1,8 @@ package database -import "time" +import ( + "time" +) type Scan struct { ScanTime time.Time `json:"scanTime"` @@ -30,8 +32,7 @@ func GetScansForUser(user_id int) ([]Scan, error) { return nil, err } - var scans []Scan - + scans := []Scan{} for scans_rows.Next() { var scan Scan _ = scans_rows.Scan(&scan.ScanTime, &scan.Card) @@ -67,7 +68,7 @@ func GetPresenceHistory(user_id int) ([]Present, error) { return nil, err } - var presences []Present + presences := []Present{} for rows.Next() { var present Present _ = rows.Scan(&present.Date, &present.Present, &present.StreakDay) @@ -92,7 +93,7 @@ func TotalDaysPerUser() ([]LeaderboardItem, error) { return nil, err } - var leaderboard []LeaderboardItem + leaderboard := []LeaderboardItem{} for rows.Next() { var item LeaderboardItem _ = rows.Scan(&item.TotalDays, &item.Username, &item.Position) diff --git a/vingo/handlers/leaderboard.go b/vingo/handlers/leaderboard.go index f99f672..7851e55 100644 --- a/vingo/handlers/leaderboard.go +++ b/vingo/handlers/leaderboard.go @@ -13,5 +13,6 @@ func Leaderboard(c *fiber.Ctx) error { return c.Status(500).SendString("Error getting leaderboard") } + logger.Println(users) return c.JSON(users) } From 9bed9878da84aac2637af2091a3113983241774c Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 25 Jun 2024 23:44:49 +0200 Subject: [PATCH 25/53] vingo: add card register via api --- vingo/handlers/cards.go | 16 ++++++++++++++++ vingo/main.go | 1 + 2 files changed, 17 insertions(+) diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index 2dd06a3..1f2c756 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -7,6 +7,22 @@ import ( "github.com/gofiber/fiber/v2" ) +func StartCardRegisterAPI(c *fiber.Ctx) error { + user := getUserFromStore(c) + + if time.Now().Before(registering_end) { + // true if current user is already registering + return c.Status(503).JSON(map[string]bool{"is_current_user": registering_user == user.Id}) + } + + registering_user = user.Id + registering_end = time.Now().Add(time.Minute) + + logger.Println("Card registration started by user", registering_user) + + return c.SendStatus(200) +} + func StartCardRegister(c *fiber.Ctx) error { // keep track of the user that initiated the request in global state // since only one user can be registering a card at a time diff --git a/vingo/main.go b/vingo/main.go index fc0301b..8b5f6b0 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -67,6 +67,7 @@ func main() { api.Get("/leaderboard", handlers.Leaderboard) api.Get("/scans", handlers.Scans) api.Get("/cards", handlers.Cards) + api.Post("/cards/register", handlers.StartCardRegisterAPI) api.Get("/settings", handlers.Settings) } From 52df507a539fcebed6d16fa35e4e719f50a5f56c Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 26 Jun 2024 00:12:11 +0200 Subject: [PATCH 26/53] vingo: add card id and name --- vingo/database/cards.go | 11 +++++++++-- vingo/database/migrations/2_cards_extra_column.up.sql | 2 ++ 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 vingo/database/migrations/2_cards_extra_column.up.sql diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 29ab3fe..0860d9f 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -3,8 +3,10 @@ package database import "time" type Card struct { + Id int `json:"id"` Serial string `json:"serial"` CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` } func CreateCard(serial string, user_id int) error { @@ -13,7 +15,7 @@ func CreateCard(serial string, user_id int) error { } func GetCardsForUser(user_id int) ([]Card, error) { - rows, err := db.Query("SELECT serial, created_at FROM cards WHERE user_id = $1;", user_id) + rows, err := db.Query("SELECT id, serial, created_at, name FROM cards WHERE user_id = $1;", user_id) if err != nil { return nil, err } @@ -22,7 +24,7 @@ func GetCardsForUser(user_id int) ([]Card, error) { cards := make([]Card, 0) for rows.Next() { var card Card - err := rows.Scan(&card.Serial, &card.CreatedAt) + err := rows.Scan(&card.Id, &card.Serial, &card.CreatedAt, &card.Name) if err != nil { return nil, err } @@ -31,3 +33,8 @@ func GetCardsForUser(user_id int) ([]Card, error) { return cards, nil } + +func SetCardName(id int, name string, user_id int) error { + _, err := db.Exec("UPDATE cards SET name = $1 WHERE user_id = $2 and id = $3;", name, user_id, id) + return err +} diff --git a/vingo/database/migrations/2_cards_extra_column.up.sql b/vingo/database/migrations/2_cards_extra_column.up.sql new file mode 100644 index 0000000..3269256 --- /dev/null +++ b/vingo/database/migrations/2_cards_extra_column.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE cards ADD COLUMN name TEXT DEFAULT '' NOT NULL; +ALTER TABLE cards ADD COLUMN id SERIAL NOT NULL; From 632bfb0c188b743ad6a99b179251df9a7b49d409 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Fri, 28 Jun 2024 15:09:54 +0200 Subject: [PATCH 27/53] 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" From f13d543715b70435f0b906e42ed42acd176a25a9 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 6 Jul 2024 11:43:05 +0200 Subject: [PATCH 28/53] vinvoor: support adding new cards --- vingo/handlers/cards.go | 2 +- vinvoor/package.json | 2 + vinvoor/src/App.tsx | 2 - vinvoor/src/cards/Cards.tsx | 28 ++-- vinvoor/src/cards/CardsAdd.tsx | 135 ++++++++++++++----- vinvoor/src/cards/CardsDelete.tsx | 56 +++----- vinvoor/src/cards/CardsTable.tsx | 43 ++---- vinvoor/src/cards/CardsTableBody.tsx | 9 +- vinvoor/src/cards/CardsTableToolbar.tsx | 11 +- vinvoor/src/components/ConfirmationModal.tsx | 61 --------- vinvoor/src/hooks/useFetch.ts | 7 +- vinvoor/src/main.tsx | 13 +- vinvoor/src/types/cards.ts | 18 ++- vinvoor/src/types/table.ts | 1 + vinvoor/src/user/UserProvider.tsx | 4 +- vinvoor/src/util/fetch.ts | 46 +++++-- vinvoor/src/util/util.ts | 43 ++++++ vinvoor/yarn.lock | 23 ++++ 18 files changed, 301 insertions(+), 203 deletions(-) delete mode 100644 vinvoor/src/components/ConfirmationModal.tsx create mode 100644 vinvoor/src/util/util.ts diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index 1f2c756..dc35b75 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -20,7 +20,7 @@ func StartCardRegisterAPI(c *fiber.Ctx) error { logger.Println("Card registration started by user", registering_user) - return c.SendStatus(200) + return c.Status(200).JSON(map[string]bool{}) } func StartCardRegister(c *fiber.Ctx) error { diff --git a/vinvoor/package.json b/vinvoor/package.json index 2f17aa0..33653ec 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -20,7 +20,9 @@ "@types/react-router-dom": "^5.3.3", "@types/react-router-hash-link": "^2.4.9", "js-cookie": "^3.0.5", + "material-ui-confirm": "^3.0.16", "mdi-material-ui": "^7.9.1", + "notistack": "^3.0.1", "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 8371a6c..1cec1e6 100644 --- a/vinvoor/src/App.tsx +++ b/vinvoor/src/App.tsx @@ -29,5 +29,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..80818ed 100644 --- a/vinvoor/src/cards/Cards.tsx +++ b/vinvoor/src/cards/Cards.tsx @@ -1,21 +1,33 @@ -import { useState } from "react"; +import { createContext, Dispatch, SetStateAction, useState } from "react"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useFetch } from "../hooks/useFetch"; -import { Card } from "../types/cards"; +import { Card, convertCardJSON } from "../types/cards"; import { CardsEmpty } from "./CardsEmpty"; import { CardsTable } from "./CardsTable"; +interface CardContextProps { + cards: readonly Card[]; + setCards: Dispatch>; +} + +export const CardContext = createContext({ + cards: [], + setCards: () => {}, +}); + export const Cards = () => { const [cards, setCards] = useState([]); - const { loading, error: _ } = useFetch("cards", setCards); + const { loading, error: _ } = useFetch( + "cards", + setCards, + convertCardJSON + ); return ( - {!!cards.length ? ( - - ) : ( - - )} + + {!!cards.length ? : } + ); }; diff --git a/vinvoor/src/cards/CardsAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx index 1eb4b9b..e6b818e 100644 --- a/vinvoor/src/cards/CardsAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -1,47 +1,110 @@ -import { Add, CancelOutlined } from "@mui/icons-material"; +import { Add } from "@mui/icons-material"; import { Button, Typography } from "@mui/material"; -import { useState } from "react"; -import { ConfirmationModal } from "../components/ConfirmationModal"; +import { useConfirm } from "material-ui-confirm"; +import { useSnackbar } from "notistack"; +import { useContext, useState } from "react"; +import { Card, CardPostResponse, convertCardJSON } from "../types/cards"; +import { getApi, isResponseNot200Error, postApi } from "../util/fetch"; +import { equal, randomInt } from "../util/util"; +import { CardContext } from "./Cards"; + +const CHECK_INTERVAL = 1000; +const REGISTER_TIME = 60000; + +const confirmTitle = "Register a new card"; +const confirmContent = ` + Once you click 'register' you will have 60 seconds to hold your card to the scanner. + A popup will appear when the card is registered successfully and it will be added to your cards table. + `; + +const requestSuccess = "Register your card by holding it to vinscant"; +const requestYou = "You are already registering a card!"; +const requestOther = + "Failed to start the card registering process because another user is already registering a card. Please try again later."; +const requestFail = + "Failed to start the card registration process. Please try again later or contact a sysadmin"; + +const registerSucces = "Card registered successfully"; +const registerFail = "Failed to register card"; + +const getCards = () => + getApi("cards", convertCardJSON).catch((_) => null); + +const checkCardsChange = async (): Promise< + [boolean, readonly Card[] | null] +> => { + const startTime = Date.now(); + const cardsStart = await getCards(); + + if (!cardsStart) return [false, null]; + + let cardsNow: readonly Card[] | null = null; + while (Date.now() - startTime < REGISTER_TIME) { + cardsNow = await getCards(); + + if (!equal(cardsStart, cardsNow)) break; + + await new Promise((r) => setTimeout(r, CHECK_INTERVAL)); + } + + return [cardsNow !== null && cardsNow !== cardsStart, cardsNow]; +}; export const CardsAdd = () => { - const [open, setOpen] = useState(false); + const { setCards } = useContext(CardContext); + const [disabled, setDisabled] = useState(false); + const confirm = useConfirm(); + const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); + const startRegistering = () => + postApi("cards/register") + .then(() => { + const id = randomInt().toString(); + enqueueSnackbar(requestSuccess, { + variant: "info", + persist: true, + key: id, + }); + setDisabled(true); - const title = "Register a new card"; + checkCardsChange().then((result) => { + closeSnackbar(id); + setDisabled(false); - const content = ` - This feature is not yet implemented as I'm waiting for an endpoint. - Hannes................................................ - `; + if (result[0] && result[1] !== null) { + enqueueSnackbar(registerSucces, { variant: "success" }); + setCards(result[1]); + } else enqueueSnackbar(registerFail, { variant: "error" }); + }); + }) + .catch((error) => { + if (isResponseNot200Error(error)) { + error.response.json().then((response: CardPostResponse) => { + if (response.is_current_user) + enqueueSnackbar(requestYou, { variant: "warning" }); + else + enqueueSnackbar(requestOther, { variant: "error" }); + }); + } else enqueueSnackbar(requestFail, { variant: "error" }); + }); - const actions = ( - <> - - - - ); + const handleClick = () => { + confirm({ + title: confirmTitle, + description: confirmContent, + confirmationText: "Register", + }).then(() => startRegistering()); + }; return ( - <> - - - + ); }; diff --git a/vinvoor/src/cards/CardsDelete.tsx b/vinvoor/src/cards/CardsDelete.tsx index df7e274..6e318de 100644 --- a/vinvoor/src/cards/CardsDelete.tsx +++ b/vinvoor/src/cards/CardsDelete.tsx @@ -1,25 +1,17 @@ -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 { Card } from "../types/cards"; +import { IconButton, Tooltip } from "@mui/material"; +import { useConfirm } from "material-ui-confirm"; +import { FC } from "react"; interface CardDeleteProps { selected: readonly string[]; - setCards: Dispatch>; } -export const CardsDelete: FC = ({ selected, setCards }) => { - const [open, setOpen] = useState(false); - +export const CardsDelete: FC = ({ selected }) => { + const confirm = useConfirm(); 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" : "" @@ -29,33 +21,19 @@ export const CardsDelete: FC = ({ selected, setCards }) => { Hannnneeeeeeees........................... `; - const actions = ( - <> - - - - ); + const handleClick = () => { + confirm({ + title: title, + description: content, + confirmationText: "Delete", + }).then(() => console.log("Card deleted!")); + }; return ( - <> - - - - - - - + + + + + ); }; diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx index 8c463aa..c21829c 100644 --- a/vinvoor/src/cards/CardsTable.tsx +++ b/vinvoor/src/cards/CardsTable.tsx @@ -1,33 +1,18 @@ import { Paper, Table, TableContainer, TablePagination } from "@mui/material"; -import { - ChangeEvent, - Dispatch, - FC, - MouseEvent, - SetStateAction, - useMemo, - useState, -} from "react"; +import { ChangeEvent, MouseEvent, useContext, useMemo, useState } from "react"; import { Card } from "../types/cards"; import { TableOrder } from "../types/table"; +import { CardContext } from "./Cards"; 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; - } + if (b[orderBy] < a[orderBy]) return -1; + if (b[orderBy] > a[orderBy]) return 1; + return 0; }; @@ -47,18 +32,19 @@ 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 stabilized = array.map((el, index) => [el, index] as [T, number]); + stabilized.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]); + return stabilized.map((el) => el[0]); }; -export const CardsTable: FC = ({ cards, setCards }) => { +export const CardsTable = () => { + const { cards } = useContext(CardContext); const [order, setOrder] = useState("asc"); const [orderBy, setOrderBy] = useState("serial"); const [selected, setSelected] = useState([]); @@ -78,6 +64,7 @@ export const CardsTable: FC = ({ cards, setCards }) => { if (event.target.checked) { const newSelected = cards.map((n) => n.serial); setSelected(newSelected); + return; } @@ -114,9 +101,7 @@ export const CardsTable: FC = ({ cards, setCards }) => { const handleChangePage = ( _: MouseEvent | null, newPage: number - ) => { - setPage(newPage); - }; + ) => setPage(newPage); const handleChangeRowsPerPage = (event: ChangeEvent) => { setRowsPerPage(parseInt(event.target.value, 10)); @@ -134,12 +119,12 @@ export const CardsTable: FC = ({ cards, setCards }) => { page * rowsPerPage, page * rowsPerPage + rowsPerPage ), - [order, orderBy, page, rowsPerPage] + [cards, order, orderBy, page, rowsPerPage] ); return ( - +
= ({ align={headCell.align} padding={headCell.padding} > - {row[headCell.id]} + + {headCell.convert + ? headCell.convert(row[headCell.id]) + : (row[headCell.id] as string)} + ))} @@ -63,6 +67,3 @@ export const CardsTableBody: FC = ({ ); }; - -// TODO: Go over all mouse events -// TODO: Move all components props diff --git a/vinvoor/src/cards/CardsTableToolbar.tsx b/vinvoor/src/cards/CardsTableToolbar.tsx index 5057be0..f9dc865 100644 --- a/vinvoor/src/cards/CardsTableToolbar.tsx +++ b/vinvoor/src/cards/CardsTableToolbar.tsx @@ -1,19 +1,14 @@ import { Toolbar, Typography } from "@mui/material"; import { alpha } from "@mui/material/styles"; -import { Dispatch, FC, SetStateAction } from "react"; -import { Card } from "../types/cards"; +import { FC } from "react"; import { CardsAdd } from "./CardsAdd"; import { CardsDelete } from "./CardsDelete"; interface CardTableToolbarProps { selected: readonly string[]; - setCards: Dispatch>; } -export const CardsTableToolbar: FC = ({ - selected, - setCards, -}) => { +export const CardsTableToolbar: FC = ({ selected }) => { const numSelected = selected.length; return ( @@ -38,7 +33,7 @@ export const CardsTableToolbar: FC = ({ > {numSelected} selected - + ) : ( <> diff --git a/vinvoor/src/components/ConfirmationModal.tsx b/vinvoor/src/components/ConfirmationModal.tsx deleted file mode 100644 index 3d83924..0000000 --- a/vinvoor/src/components/ConfirmationModal.tsx +++ /dev/null @@ -1,61 +0,0 @@ -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/hooks/useFetch.ts b/vinvoor/src/hooks/useFetch.ts index 0a2b1ac..c26ac0b 100644 --- a/vinvoor/src/hooks/useFetch.ts +++ b/vinvoor/src/hooks/useFetch.ts @@ -1,5 +1,5 @@ import { Dispatch, SetStateAction, useEffect, useState } from "react"; -import { fetchApi } from "../util/fetch"; +import { getApi } from "../util/fetch"; interface useFetchResult { loading: boolean; @@ -8,13 +8,14 @@ interface useFetchResult { export const useFetch = ( endpoint: string, - setData: Dispatch> + setData: Dispatch>, + convertData?: (data: any) => T ): useFetchResult => { const [loading, setLoading] = useState(true); const [error, setError] = useState(undefined); useEffect(() => { - fetchApi(endpoint) + getApi(endpoint, convertData) .then((data) => setData(data)) .catch((error) => setError(error)) .finally(() => setLoading(false)); diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index 8f070ec..32df151 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -3,6 +3,8 @@ import "@fontsource/roboto/400.css"; import "@fontsource/roboto/500.css"; import "@fontsource/roboto/700.css"; import { CssBaseline } from "@mui/material"; +import { ConfirmProvider } from "material-ui-confirm"; +import { SnackbarProvider } from "notistack"; import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; @@ -47,7 +49,16 @@ ReactDOM.createRoot(document.getElementById("root")!).render( - + + + + + diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index 223a1d3..2f78d61 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -1,10 +1,21 @@ import { TableHeadCell } from "./table"; -export interface Card { +interface CardJSON { serial: string; createdAt: string; } +export interface Card { + serial: string; + createdAt: Date; +} + +export const convertCardJSON = (cardsJSON: CardJSON[]): Card[] => + cardsJSON.map((CardJSON) => ({ + serial: CardJSON.serial, + createdAt: new Date(CardJSON.createdAt), + })); + export const CardsHeadCells: readonly TableHeadCell[] = [ { id: "serial", @@ -17,5 +28,10 @@ export const CardsHeadCells: readonly TableHeadCell[] = [ label: "Created at", align: "right", padding: "normal", + convert: (value: Date) => value.toDateString(), }, ]; + +export interface CardPostResponse { + is_current_user: boolean; +} diff --git a/vinvoor/src/types/table.ts b/vinvoor/src/types/table.ts index 3d4a059..dc0603e 100644 --- a/vinvoor/src/types/table.ts +++ b/vinvoor/src/types/table.ts @@ -8,4 +8,5 @@ export interface TableHeadCell { label: string; align: TableAlignOptions; padding: TablePaddingOptions; + convert?: (value: any) => string; } diff --git a/vinvoor/src/user/UserProvider.tsx b/vinvoor/src/user/UserProvider.tsx index 7577470..f145e97 100644 --- a/vinvoor/src/user/UserProvider.tsx +++ b/vinvoor/src/user/UserProvider.tsx @@ -9,7 +9,7 @@ import { useState, } from "react"; import { User } from "../types/user"; -import { fetchApi } from "../util/fetch"; +import { getApi } from "../util/fetch"; interface UserProviderProps { children: ReactNode; @@ -55,7 +55,7 @@ export const UserProvider: FC = ({ children }) => { let newUserState = { ...userState }; - fetchApi("user") + getApi("user") .then((data) => (newUserState.user = data)) .catch((error) => { Cookies.remove("session_id"); diff --git a/vinvoor/src/util/fetch.ts b/vinvoor/src/util/fetch.ts index 0497de2..e1db30d 100644 --- a/vinvoor/src/util/fetch.ts +++ b/vinvoor/src/util/fetch.ts @@ -3,16 +3,46 @@ const URLS: { [key: string]: string } = { API: import.meta.env.VITE_API_URL, }; -export const fetchBase = (endpoint: string) => { - return _fetch(`${URLS.BASE}/${endpoint}`); +export const getApi = (endpoint: string, convertData?: (data: any) => T) => { + return _fetch(`${URLS.API}/${endpoint}`, {}, convertData); }; -export const fetchApi = (endpoint: string) => { - return _fetch(`${URLS.API}/${endpoint}`); +export const postApi = ( + endpoint: string, + body: { [key: string]: string } = {} +) => { + return _fetch(`${URLS.API}/${endpoint}`, { + method: "post", + body: JSON.stringify(body), + }); }; -const _fetch = async (url: string) => { - return fetch(url, { credentials: "include" }).then((response) => - response.json() - ); +interface ResponseNot200Error extends Error { + response: Response; +} + +export const isResponseNot200Error = ( + error: any +): error is ResponseNot200Error => { + return (error as ResponseNot200Error).response !== undefined; +}; + +const _fetch = async ( + url: string, + options: RequestInit = {}, + convertData?: (data: any) => T +): Promise => { + return fetch(url, { credentials: "include", ...options }) + .then((response) => { + if (!response.ok) { + const error = new Error( + "Fetch failed with status: " + response.status + ) as ResponseNot200Error; + error.response = response; + throw error; + } + + return response.json(); + }) + .then((data) => (convertData ? convertData(data) : data)); }; diff --git a/vinvoor/src/util/util.ts b/vinvoor/src/util/util.ts new file mode 100644 index 0000000..5ce3b09 --- /dev/null +++ b/vinvoor/src/util/util.ts @@ -0,0 +1,43 @@ +export const randomInt = (lower: number = 0, upper: number = 10000): number => { + return Math.floor(Math.random() * (upper - lower + 1) + lower); +}; + +export const equal = (left: any, right: any): boolean => { + if (typeof left !== typeof right) return false; + + if (Array.isArray(left) && Array.isArray(right)) + return equalArray(left, right); + if (typeof left === "object" && left !== null && right !== null) + return equalObject( + left as Record, + right as Record + ); + + return left === right; +}; + +const equalArray = (left: any[], right: any[]): boolean => { + if (left.length !== right.length) return false; + + for (let i = 0; i < left.length; i++) { + if (!equal(left[i], right[i])) return false; + } + + return true; +}; + +const equalObject = ( + left: Record, + right: Record +): boolean => { + const leftKeys = Object.keys(left); + const rightKeys = Object.keys(right); + + if (leftKeys.length !== rightKeys.length) return false; + + for (const key of leftKeys) { + if (!equal(left[key], right[key])) return false; + } + + return true; +}; diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index 78fe7ca..6f3d8bb 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -1016,6 +1016,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +clsx@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" @@ -1399,6 +1404,11 @@ globby@^11.1.0: merge2 "^1.4.1" slash "^3.0.0" +goober@^2.0.33: + version "2.1.14" + resolved "https://registry.yarnpkg.com/goober/-/goober-2.1.14.tgz#4a5c94fc34dc086a8e6035360ae1800005135acd" + integrity sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg== + graphemer@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" @@ -1579,6 +1589,11 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +material-ui-confirm@^3.0.16: + version "3.0.16" + resolved "https://registry.yarnpkg.com/material-ui-confirm/-/material-ui-confirm-3.0.16.tgz#8b25b4770a0f15d888c838bcd21180f655e03469" + integrity sha512-aJoa/FM/U/86qztoljlk8FWmjSJbAMzDWCdWbDqU5WwB0WzcWPyGrhBvIqihR9uKdHKBf1YrvMjn68uOrfsXAg== + 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" @@ -1626,6 +1641,14 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +notistack@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/notistack/-/notistack-3.0.1.tgz#daf59888ab7e2c30a1fa8f71f9cba2978773236e" + integrity sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA== + dependencies: + clsx "^1.1.0" + goober "^2.0.33" + object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" From 7c0519b7810b2684caddacb250cc15de0d427c82 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 6 Jul 2024 19:07:43 +0200 Subject: [PATCH 29/53] vinvoor: show current checkin status --- vingo/handlers/cards.go | 2 +- vinvoor/package.json | 1 + vinvoor/src/cards/Cards.tsx | 8 +- vinvoor/src/cards/CardsAdd.tsx | 2 +- vinvoor/src/overview/Overview.tsx | 83 ++- vinvoor/src/overview/checkin/CheckIn.tsx | 42 ++ vinvoor/src/overview/heatmap/Heatmap.tsx | 96 ++- vinvoor/src/overview/heatmap/utils.ts | 58 +- vinvoor/src/types/cards.ts | 2 +- vinvoor/src/types/scans.ts | 15 +- vinvoor/yarn.lock | 776 +++++++++++++---------- 11 files changed, 606 insertions(+), 479 deletions(-) create mode 100644 vinvoor/src/overview/checkin/CheckIn.tsx diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index dc35b75..e7687db 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -12,7 +12,7 @@ func StartCardRegisterAPI(c *fiber.Ctx) error { if time.Now().Before(registering_end) { // true if current user is already registering - return c.Status(503).JSON(map[string]bool{"is_current_user": registering_user == user.Id}) + return c.Status(503).JSON(map[string]bool{"isCurrentUser": registering_user == user.Id}) } registering_user = user.Id diff --git a/vinvoor/package.json b/vinvoor/package.json index 5002064..3e92f71 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -19,6 +19,7 @@ "@types/js-cookie": "^3.0.6", "@types/react-router-dom": "^5.3.3", "@types/react-router-hash-link": "^2.4.9", + "apexcharts": "^3.50.0", "js-cookie": "^3.0.5", "material-ui-confirm": "^3.0.16", "mdi-material-ui": "^7.9.1", diff --git a/vinvoor/src/cards/Cards.tsx b/vinvoor/src/cards/Cards.tsx index e40e44a..e4e00c4 100644 --- a/vinvoor/src/cards/Cards.tsx +++ b/vinvoor/src/cards/Cards.tsx @@ -1,7 +1,7 @@ import { createContext, Dispatch, SetStateAction, useState } from "react"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useFetch } from "../hooks/useFetch"; -import { Card } from "../types/cards"; +import { Card, convertCardJSON } from "../types/cards"; import { CardsEmpty } from "./CardsEmpty"; import { CardsTable } from "./CardsTable"; @@ -17,7 +17,11 @@ export const CardContext = createContext({ export const Cards = () => { const [cards, setCards] = useState([]); - const { loading } = useFetch("cards", setCards); + const { loading } = useFetch( + "cards", + setCards, + convertCardJSON + ); return ( diff --git a/vinvoor/src/cards/CardsAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx index e6b818e..00df0a0 100644 --- a/vinvoor/src/cards/CardsAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -80,7 +80,7 @@ export const CardsAdd = () => { .catch((error) => { if (isResponseNot200Error(error)) { error.response.json().then((response: CardPostResponse) => { - if (response.is_current_user) + if (response.isCurrentUser) enqueueSnackbar(requestYou, { variant: "warning" }); else enqueueSnackbar(requestOther, { variant: "error" }); diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index 1da7469..8bbb5b3 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -1,45 +1,74 @@ import { Box, Paper, Switch, Typography } from "@mui/material"; -import { useState } from "react"; +import Grid from "@mui/material/Grid"; +import { createContext, useState } from "react"; import { Tooltip } from "react-tooltip"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useFetch } from "../hooks/useFetch"; -import { Scan } from "../types/scans"; +import { convertScanJSON, Scan } from "../types/scans"; +import { CheckIn } from "./checkin/CheckIn"; import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap"; +interface ScanContextProps { + scans: readonly Scan[]; +} + +export const ScanContext = createContext({ + scans: [], +}); + export const Overview = () => { - const [scans, setScans] = useState([]); - const { loading } = useFetch("scans", setScans); + const [scans, setScans] = useState([]); + const { loading } = useFetch( + "scans", + setScans, + convertScanJSON + ); 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 - - - - + + + + + + + + + Months + + Days + + + + + + + ); }; + +// TODO: Checked in today +// TODO: Current streak +// TODO: Pie chart diff --git a/vinvoor/src/overview/checkin/CheckIn.tsx b/vinvoor/src/overview/checkin/CheckIn.tsx new file mode 100644 index 0000000..cdc4f55 --- /dev/null +++ b/vinvoor/src/overview/checkin/CheckIn.tsx @@ -0,0 +1,42 @@ +import { Alert, AlertTitle } from "@mui/material"; +import { EmoticonExcitedOutline, EmoticonFrownOutline } from "mdi-material-ui"; +import { useContext } from "react"; +import { ScanContext } from "../Overview"; + +const isTheSameDay = (date1: Date, date2: Date) => + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate(); + +export const CheckIn = () => { + const { scans } = useContext(ScanContext); + + const checkedIn = isTheSameDay( + scans[scans.length - 1].scanTime, + new Date() + ); + + return checkedIn ? ( + , + }} + > + Checked in + Thank you for stopping by the kelder! + + ) : ( + , + }} + > + Not checked in + We miss you in the kelder! + + ); +}; diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx index 42dd7a7..d30fab0 100644 --- a/vinvoor/src/overview/heatmap/Heatmap.tsx +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -1,4 +1,5 @@ -import { FC } from "react"; +import { FC, useContext } from "react"; +import { ScanContext } from "../Overview"; import "./heatmap.css"; import { dateTimeFormat, @@ -29,7 +30,6 @@ export enum HeatmapVariant { } interface HeatmapProps { - days: readonly Date[]; startDate: Date; endDate: Date; variant: HeatmapVariant; @@ -102,15 +102,12 @@ const getWeeksInMonth = (values: HeatmapItem[]): { [key: number]: number } => { const getClassNameForValue = (value: HeatmapItem, variant: HeatmapVariant) => { if (variant === HeatmapVariant.DAYS) { - if (value.count > 0) { - return `color-active`; - } + if (value.count > 0) return `color-active`; return `color-inactive`; } else { - if (value.count <= 5) { - return `color-${value.count}`; - } + if (value.count <= 5) return `color-${value.count}`; + return "color-5"; } }; @@ -118,34 +115,28 @@ const getClassNameForValue = (value: HeatmapItem, variant: HeatmapVariant) => { 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)}`; -}; +) => ({ + "data-tooltip-id": "heatmap", + "data-tooltip-content": + variant === HeatmapVariant.DAYS + ? getTooltipDataAttrsForDays(value) + : getTooltipDataAttrsForMonths(value), +}); + +const getTooltipDataAttrsForDays = (value: HeatmapItem) => + `${value.count > 0 ? "Present" : "Absent"} on ${dateTimeFormat.format( + value.date + )}`; -const getTooltipDataAttrsForMonths = (value: HeatmapItem) => { - return `${value.count} scan${ +const getTooltipDataAttrsForMonths = (value: HeatmapItem) => + `${value.count} scan${ value.count !== 1 ? "s" : "" } on the week of ${dateTimeFormat.format(value.date)}`; -}; -export const Heatmap: FC = ({ - days, - startDate, - endDate, - variant, -}) => { +export const Heatmap: FC = ({ startDate, endDate, variant }) => { + const { scans } = useContext(ScanContext); + const days = scans.map((scan) => scan.scanTime); + days.forEach((date) => date.setHours(0, 0, 0, 0)); startDate.setHours(0, 0, 0, 0); endDate.setHours(0, 0, 0, 0); @@ -164,18 +155,12 @@ export const Heatmap: FC = ({ let valueIndex = 0; const renderSquare = (row: number, column: number) => { - if (column === 0 && row < emptyStart) { - return null; - } + if (column === 0 && row < emptyStart) return null; - if (variant === HeatmapVariant.DAYS) { - if (column === columns - 1 && row > emptyEnd) { - 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); @@ -194,23 +179,20 @@ export const Heatmap: FC = ({ ); }; - const renderColumn = (column: number) => { - return ( - - {[ - ...Array( - variant === HeatmapVariant.DAYS - ? DAYS_IN_WEEK - : weeksInMonth[column] - ).keys(), - ].map((row) => renderSquare(row, column))} - - ); - }; + const renderColumn = (column: number) => ( + + {[ + ...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 renderColumns = () => + [...Array(columns).keys()].map((column) => renderColumn(column)); const renderMonthLabels = () => { if (variant === HeatmapVariant.DAYS) { diff --git a/vinvoor/src/overview/heatmap/utils.ts b/vinvoor/src/overview/heatmap/utils.ts index 641b87c..b95b1ff 100644 --- a/vinvoor/src/overview/heatmap/utils.ts +++ b/vinvoor/src/overview/heatmap/utils.ts @@ -31,64 +31,48 @@ export const dateTimeFormat = new Intl.DateTimeFormat("en-GB", { // Labels -export const getMonthLabelSize = () => { - return SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE; -}; +export const getMonthLabelSize = () => SQUARE_SIZE + MONTH_LABEL_GUTTER_SIZE; -export const getMonthLabelCoordinates = (column: number) => { - return [ - column * getSquareSize(), - getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE, - ]; -}; +export const getMonthLabelCoordinates = (column: number) => [ + column * getSquareSize(), + getMonthLabelSize() - MONTH_LABEL_GUTTER_SIZE, +]; // Transforms -export const getTransformForColumn = (column: number) => { - return `translate(${column * getSquareSize() + GUTTERSIZE}, 0)`; -}; +export const getTransformForColumn = (column: number) => + `translate(${column * getSquareSize() + GUTTERSIZE}, 0)`; -export const getTransformForAllWeeks = () => { - return `translate(0, ${getMonthLabelSize()})`; -}; +export const getTransformForAllWeeks = () => + `translate(0, ${getMonthLabelSize()})`; -export const getTransformForMonthLabels = () => { - return `translate(0, 0)`; -}; +export const getTransformForMonthLabels = () => `translate(0, 0)`; export const getWidth = ( startDate: Date, endDate: Date, variant: HeatmapVariant -) => { - return ( - getColumnCount(startDate, endDate, variant) * getSquareSize() + - GUTTERSIZE - ); -}; +) => getColumnCount(startDate, endDate, variant) * getSquareSize() + GUTTERSIZE; export const getHeight = (variant: HeatmapVariant) => { - if (variant === HeatmapVariant.DAYS) { + if (variant === HeatmapVariant.DAYS) return DAYS_IN_WEEK * getSquareSize() + getMonthLabelSize(); - } else { - return WEEKS_IN_MONTH * getSquareSize() + getMonthLabelSize(); - } + else return WEEKS_IN_MONTH * getSquareSize() + getMonthLabelSize(); }; // Coordinate -export const getSquareCoordinates = (dayIndex: number) => { - return [0, dayIndex * getSquareSize()]; -}; +export const getSquareCoordinates = (dayIndex: number) => [ + 0, + dayIndex * getSquareSize(), +]; // Utils export const getEmpty = (date: Date, variant: HeatmapVariant) => { - if (variant === HeatmapVariant.DAYS) { + if (variant === HeatmapVariant.DAYS) return (date.getDay() + DAYS_IN_WEEK - 1) % DAYS_IN_WEEK; - } else { - return Math.floor((date.getDate() - 1) / DAYS_IN_WEEK); - } + else return Math.floor((date.getDate() - 1) / DAYS_IN_WEEK); }; export const getColumnCount = ( @@ -120,6 +104,4 @@ export const shiftDate = (date: Date, numDays: number) => { const GUTTERSIZE = 5; const MONTH_LABEL_GUTTER_SIZE = 4; -const getSquareSize = () => { - return SQUARE_SIZE + GUTTERSIZE; -}; +const getSquareSize = () => SQUARE_SIZE + GUTTERSIZE; diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index 2f78d61..437ff91 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -33,5 +33,5 @@ export const CardsHeadCells: readonly TableHeadCell[] = [ ]; export interface CardPostResponse { - is_current_user: boolean; + isCurrentUser: boolean; } diff --git a/vinvoor/src/types/scans.ts b/vinvoor/src/types/scans.ts index e9cc504..313d943 100644 --- a/vinvoor/src/types/scans.ts +++ b/vinvoor/src/types/scans.ts @@ -1,4 +1,17 @@ -export interface Scan { +interface ScanJSON { scanTime: string; card: string; } + +export interface Scan { + scanTime: Date; + card: string; +} + +export const convertScanJSON = (scansJSON: ScanJSON[]): Scan[] => + scansJSON + .map((scanJSON) => ({ + scanTime: new Date(scanJSON.scanTime), + card: scanJSON.card, + })) + .sort((a, b) => a.scanTime.getTime() - b.scanTime.getTime()); diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index a381127..fc51ae0 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -230,120 +230,120 @@ 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== +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" @@ -353,9 +353,9 @@ 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== + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -377,32 +377,32 @@ 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== +"@floating-ui/core@^1.6.0": + version "1.6.4" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.4.tgz#0140cf5091c8dee602bff9da5ab330840ff91df6" + integrity sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA== dependencies: - "@floating-ui/utils" "^0.2.0" + "@floating-ui/utils" "^0.2.4" "@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== + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.7.tgz#85d22f731fcc5b209db504478fb1df5116a83015" + integrity sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng== dependencies: - "@floating-ui/core" "^1.0.0" - "@floating-ui/utils" "^0.2.0" + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.4" "@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== + version "2.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" + integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== 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== +"@floating-ui/utils@^0.2.4": + version "0.2.4" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.4.tgz#1d459cee5031893a08a0e064c406ad2130cced7c" + integrity sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA== "@fontsource/roboto@^5.0.13": version "5.0.13" @@ -473,29 +473,29 @@ 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/core-downloads-tracker@^5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.0.tgz#50153c698e321793c83a0283d8d7a9dc5d43858a" + integrity sha512-8SLffXYPRVpcZx5QzxNE8fytTqzp+IuU3deZbQWg/vSaTlDpR5YVrQ4qQtXTi5cRdhOufV5INylmwlKK+//nPw== "@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== + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.0.tgz#5269fda922fe5e6db3577ec497e8b987195606ef" + integrity sha512-6ISoOhkp9w5gD0PEW9JklrcbyARDkFWNTBdwXZ1Oy5IGlyu9B0zG0hnUIe4H17IaF1Vgj6C8VI+v4tkSdK0veg== 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== + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.0.tgz#2ef4f52ae773574fc0a681f25705f376f5cd13f7" + integrity sha512-DbR1NckTLpjt9Zut9EGQ70th86HfN0BYQgyYro6aXQrNfjzSwe3BJS1AyBQ5mJ7TdL6YVRqohfukxj9JlqZZUg== 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/core-downloads-tracker" "^5.16.0" + "@mui/system" "^5.16.0" "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" + "@mui/utils" "^5.16.0" "@types/react-transition-group" "^4.4.10" clsx "^2.1.0" csstype "^3.1.3" @@ -503,13 +503,13 @@ 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== +"@mui/private-theming@^5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.0.tgz#c1abfd3e0d9c95459048240ef4209dc7f25dc949" + integrity sha512-sYpubkO1MZOnxNyVOClrPNOTs0MfuRVVnAvCeMaOaXt6GimgQbnUcshYv2pSr6PFj+Mqzdff/FYOBceK8u5QgA== dependencies: "@babel/runtime" "^7.23.9" - "@mui/utils" "^5.15.14" + "@mui/utils" "^5.16.0" prop-types "^15.8.1" "@mui/styled-engine@^5.15.14": @@ -522,16 +522,16 @@ 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== +"@mui/system@^5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.0.tgz#e5b4cfbdfbc0ee9859f6b168e8b07d750303b7a0" + integrity sha512-9YbkC2m3+pNumAvubYv+ijLtog6puJ0fJ6rYfzfLCM47pWrw3m+30nXNM8zMgDaKL6vpfWJcCXm+LPaWBpy7sw== dependencies: "@babel/runtime" "^7.23.9" - "@mui/private-theming" "^5.15.14" + "@mui/private-theming" "^5.16.0" "@mui/styled-engine" "^5.15.14" "@mui/types" "^7.2.14" - "@mui/utils" "^5.15.14" + "@mui/utils" "^5.16.0" clsx "^2.1.0" csstype "^3.1.3" prop-types "^15.8.1" @@ -541,10 +541,10 @@ 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== +"@mui/utils@^5.15.14", "@mui/utils@^5.16.0": + version "5.16.0" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.0.tgz#3963127d9a619c251e5be1aef9adab0e89d3e7df" + integrity sha512-kLLi5J1xY+mwtUlMb8Ubdxf4qFAA1+U7WPBvjM/qQ4CIwLCohNb0sHo1oYPufjSIH/Z9+dhVxD7dJlfGjd1AVA== dependencies: "@babel/runtime" "^7.23.9" "@types/prop-types" "^15.7.11" @@ -577,10 +577,10 @@ 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== +"@remix-run/router@1.17.1": + version "1.17.1" + resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.17.1.tgz#bf93997beb81863fde042ebd05013a2618471362" + integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q== "@rollup/rollup-android-arm-eabi@4.18.0": version "4.18.0" @@ -662,84 +662,84 @@ 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-darwin-arm64@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.7.tgz#e98a0da9635297728a97faf7f4e11c46f8dfbb46" + integrity sha512-sNb+ghP2OhZyUjS7E5Mf3PqSvoXJ5gY6GBaH2qp8WQxx9VL7ozC4HVo6vkeFJBN5cmYqUCLnhrM3HU4W+7yMSA== + +"@swc/core-darwin-x64@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.6.7.tgz#fccd389046a8fe0d8b294f9657b3861046fcd3bb" + integrity sha512-LQwYm/ATYN5fYSYVPMfComPiFo5i8jh75h1ASvNWhXtS+/+k1dq1zXTJWZRuojd5NXgW3bb6mJtJ2evwYIgYbA== + +"@swc/core-linux-arm-gnueabihf@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.6.7.tgz#f384235e5f14870646157017eb06dfbaed0894c0" + integrity sha512-kEDzVhNci38LX3kdY99t68P2CDf+2QFDk5LawVamXH0iN5DRAO/+wjOhxL8KOHa6wQVqKEt5WrhD+Rrvk/34Yw== + +"@swc/core-linux-arm64-gnu@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.6.7.tgz#d2b8c0c6045eecb96bc3f3dfa7fb31b5ab708cdf" + integrity sha512-SyOBUGfl31xLGpIJ/Jd6GKHtkfZyHBXSwFlK7FmPN//MBQLtTBm4ZaWTnWnGo4aRsJwQdXWDKPyqlMBtnIl1nQ== + +"@swc/core-linux-arm64-musl@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.6.7.tgz#6ae2a160ba535b1f4747d35a124f410545092abe" + integrity sha512-1fOAXkDFbRfItEdMZPxT3du1QWYhgToa4YsnqTujjE8EqJW8K27hIcHRIkVuzp7PNhq8nLBg0JpJM4g27EWD7g== + +"@swc/core-linux-x64-gnu@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.6.7.tgz#6ebcf76fa868321c3b079e5c668c137b9b91df49" + integrity sha512-Gp7uCwPsNO5ATxbyvfTyeNCHUGD9oA+xKMm43G1tWCy+l07gLqWMKp7DIr3L3qPD05TfAVo3OuiOn2abpzOFbw== + +"@swc/core-linux-x64-musl@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.6.7.tgz#41531ef3e1c7123d87b7a7a1b984fa2689032621" + integrity sha512-QeruGBZJ15tadqEMQ77ixT/CYGk20MtlS8wmvJiV+Wsb8gPW5LgCjtupzcLLnoQzDG54JGNCeeZ0l/T8NYsOvA== + +"@swc/core-win32-arm64-msvc@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.6.7.tgz#af0b84a54d01bc3aad12acffa98ebb13fc03c3e6" + integrity sha512-ouRqgSnT95lTCiU/6kJRNS5b1o+p8I/V9jxtL21WUj/JOVhsFmBErqQ0MZyCu514noWiR5BIqOrZXR8C1Knx6Q== + +"@swc/core-win32-ia32-msvc@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.6.7.tgz#c454851c05c26f67d2edc399e1cde9d074744ce4" + integrity sha512-eZAP/EmJ0IcfgAx6B4/SpSjq3aT8gr0ooktfMqw/w0/5lnNrbMl2v+2kvxcneNcF7bp8VNcYZnoHlsP+LvmVbA== + +"@swc/core-win32-x64-msvc@1.6.7": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.6.7.tgz#6ee4a3caf3466971e6b5fb2fba4674924507a2de" + integrity sha512-QOdE+7GQg1UQPS6p0KxzJOh/8GLbJ5zI1vqKArCCB0unFqUfKIjYb2TaH0geEBy3w9qtXxe3ZW6hzxtZSS9lDg== "@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== + version "1.6.7" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.6.7.tgz#5d113df161fd8ec29ab8837f385240f41315735e" + integrity sha512-BBzORL9qWz5hZqAZ83yn+WNaD54RH5eludjqIOboolFOK/Pw+2l00/H77H4CEBJnzCIBQszsyqtITmrn4evp0g== dependencies: "@swc/counter" "^0.1.3" - "@swc/types" "^0.1.8" + "@swc/types" "^0.1.9" 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/core-darwin-arm64" "1.6.7" + "@swc/core-darwin-x64" "1.6.7" + "@swc/core-linux-arm-gnueabihf" "1.6.7" + "@swc/core-linux-arm64-gnu" "1.6.7" + "@swc/core-linux-arm64-musl" "1.6.7" + "@swc/core-linux-x64-gnu" "1.6.7" + "@swc/core-linux-x64-musl" "1.6.7" + "@swc/core-win32-arm64-msvc" "1.6.7" + "@swc/core-win32-ia32-msvc" "1.6.7" + "@swc/core-win32-x64-msvc" "1.6.7" "@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== +"@swc/types@^0.1.9": + version "0.1.9" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.9.tgz#e67cdcc2e4dd74a3cef4474b465eb398e7ae83e2" + integrity sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg== dependencies: "@swc/counter" "^0.1.3" @@ -817,61 +817,61 @@ 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== + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz#8eaf396ac2992d2b8f874b68eb3fcd6b179cb7f3" + integrity sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA== 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" + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/type-utils" "7.15.0" + "@typescript-eslint/utils" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.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" + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.15.0.tgz#f4a536e5fc6a1c05c82c4d263a2bfad2da235c80" + integrity sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A== + dependencies: + "@typescript-eslint/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.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== +"@typescript-eslint/scope-manager@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz#201b34b0720be8b1447df17b963941bf044999b2" + integrity sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw== dependencies: - "@typescript-eslint/types" "7.13.0" - "@typescript-eslint/visitor-keys" "7.13.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.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== +"@typescript-eslint/type-utils@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz#5b83c904c6de91802fb399305a50a56d10472c39" + integrity sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg== dependencies: - "@typescript-eslint/typescript-estree" "7.13.0" - "@typescript-eslint/utils" "7.13.0" + "@typescript-eslint/typescript-estree" "7.15.0" + "@typescript-eslint/utils" "7.15.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/types@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.15.0.tgz#fb894373a6e3882cbb37671ffddce44f934f62fc" + integrity sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw== -"@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== +"@typescript-eslint/typescript-estree@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz#e323bfa3966e1485b638ce751f219fc1f31eba37" + integrity sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ== dependencies: - "@typescript-eslint/types" "7.13.0" - "@typescript-eslint/visitor-keys" "7.13.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/visitor-keys" "7.15.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -879,22 +879,22 @@ 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== +"@typescript-eslint/utils@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.15.0.tgz#9e6253c4599b6e7da2fb64ba3f549c73eb8c1960" + integrity sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA== 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/scope-manager" "7.15.0" + "@typescript-eslint/types" "7.15.0" + "@typescript-eslint/typescript-estree" "7.15.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== +"@typescript-eslint/visitor-keys@7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz#1da0726201a859343fe6a05742a7c1792fff5b66" + integrity sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw== dependencies: - "@typescript-eslint/types" "7.13.0" + "@typescript-eslint/types" "7.15.0" eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": @@ -909,15 +909,20 @@ dependencies: "@swc/core" "^1.5.7" +"@yr/monotone-cubic-spline@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz#7272d89f8e4f6fb7a1600c28c378cc18d3b577b9" + integrity sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA== + 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== + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== ajv@^6.12.4: version "6.12.6" @@ -948,6 +953,19 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +apexcharts@^3.50.0: + version "3.50.0" + resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.50.0.tgz#9030f206183978df0d762602b3d15b59ec6e783b" + integrity sha512-LJT1PNAm+NoIU3aogL2P+ViC0y/Cjik54FdzzGV54UNnGQLBoLe5ok3fxsJDTgyez45BGYT8gqNpYKqhdfy5sg== + dependencies: + "@yr/monotone-cubic-spline" "^1.0.3" + svg.draggable.js "^2.2.2" + svg.easing.js "^2.0.0" + svg.filter.js "^2.0.2" + svg.pathmorphing.js "^0.1.3" + svg.resize.js "^1.4.3" + svg.select.js "^3.0.1" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -1016,15 +1034,16 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -clsx@^1.1.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" - integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== 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@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" + integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== + clsx@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" @@ -1130,34 +1149,34 @@ error-ex@^1.3.1: 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== +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== 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" + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" escape-string-regexp@^1.0.5: version "1.0.5" @@ -1428,7 +1447,7 @@ has-flag@^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: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -1479,11 +1498,11 @@ is-arrayish@^0.2.1: 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== + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== dependencies: - hasown "^2.0.0" + hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" @@ -1624,9 +1643,9 @@ minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: 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== + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" @@ -1733,7 +1752,7 @@ path-type@^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: +picocolors@^1.0.0, picocolors@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== @@ -1743,13 +1762,13 @@ picomatch@^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== +postcss@^8.4.39: + version "8.4.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.39.tgz#aa3c94998b61d3a9c259efa51db4b392e1bde0e3" + integrity sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw== dependencies: nanoid "^3.3.7" - picocolors "^1.0.0" + picocolors "^1.0.1" source-map-js "^1.2.0" prelude-ls@^1.2.1: @@ -1795,12 +1814,12 @@ react-is@^18.2.0: 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== + version "6.24.1" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.24.1.tgz#b1a22f7d6c5a1bfce30732bd370713f991ab4de4" + integrity sha512-U19KtXqooqw967Vw0Qcn5cOvrX5Ejo9ORmOtJMzYWtCT4/WOfFLIZGGsVLxcd9UkBO0mSTZtXqhZBsWlHr7+Sg== dependencies: - "@remix-run/router" "1.16.1" - react-router "6.23.1" + "@remix-run/router" "1.17.1" + react-router "6.24.1" react-router-hash-link@^2.4.3: version "2.4.3" @@ -1809,17 +1828,17 @@ react-router-hash-link@^2.4.3: 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" - integrity sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ== +react-router@6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.24.1.tgz#5a3bbba0000afba68d42915456ca4c806f37a7de" + integrity sha512-PTXFXGK2pyXpHzVo3rR9H7ip4lSPZZc0bHG5CARmj65fTT6qG7sTngmb6lcYu1gf3y/8KxORoy9yn59pGpCnpg== dependencies: - "@remix-run/router" "1.16.1" + "@remix-run/router" "1.17.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== + version "5.27.1" + resolved "https://registry.yarnpkg.com/react-tooltip/-/react-tooltip-5.27.1.tgz#a94481ba146d828d31642f14d6ab29b56998fcda" + integrity sha512-a+micPXcMOMt11CYlwJD4XShcqGziasHco4NPe1OFw298WBTILMyzUgNC1LAFViAe791JdHNVSJIpzhZm2MvDA== dependencies: "@floating-ui/dom" "^1.6.1" classnames "^2.3.0" @@ -1979,6 +1998,61 @@ supports-preserve-symlinks-flag@^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== +svg.draggable.js@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" + integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw== + dependencies: + svg.js "^2.0.1" + +svg.easing.js@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12" + integrity sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA== + dependencies: + svg.js ">=2.3.x" + +svg.filter.js@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203" + integrity sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw== + dependencies: + svg.js "^2.2.5" + +svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== + +svg.pathmorphing.js@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65" + integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww== + dependencies: + svg.js "^2.4.0" + +svg.resize.js@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332" + integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw== + dependencies: + svg.js "^2.6.5" + svg.select.js "^2.1.2" + +svg.select.js@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73" + integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ== + dependencies: + svg.js "^2.2.5" + +svg.select.js@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917" + integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw== + dependencies: + svg.js "^2.6.5" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -2014,9 +2088,9 @@ type-fest@^0.20.2: 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== + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== uri-js@^4.2.2: version "4.4.1" @@ -2026,12 +2100,12 @@ uri-js@^4.2.2: 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== + version "5.3.3" + resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.3.tgz#5265b1f0a825b3b6564c2d07524777c83e3c04c2" + integrity sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A== dependencies: - esbuild "^0.20.1" - postcss "^8.4.38" + esbuild "^0.21.3" + postcss "^8.4.39" rollup "^4.13.0" optionalDependencies: fsevents "~2.3.3" From ed6d70a3571d8e8970417499342d4081173aa4d3 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 6 Jul 2024 22:02:01 +0200 Subject: [PATCH 30/53] vinvoor: show current streak days --- vinvoor/package.json | 1 + vinvoor/src/overview/Overview.tsx | 46 ++++++----- vinvoor/src/overview/checkin/CheckIn.tsx | 6 +- vinvoor/src/overview/heatmap/Heatmap.tsx | 3 +- vinvoor/src/overview/heatmap/utils.ts | 8 +- vinvoor/src/overview/streak/Streak.tsx | 97 ++++++++++++++++++++++++ vinvoor/src/util/util.ts | 17 +++++ vinvoor/yarn.lock | 12 +++ 8 files changed, 159 insertions(+), 31 deletions(-) create mode 100644 vinvoor/src/overview/streak/Streak.tsx diff --git a/vinvoor/package.json b/vinvoor/package.json index 3e92f71..b035fc8 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -25,6 +25,7 @@ "mdi-material-ui": "^7.9.1", "notistack": "^3.0.1", "react": "^18.2.0", + "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1", "react-router-hash-link": "^2.4.3", diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index 8bbb5b3..e0c9bd2 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -1,12 +1,14 @@ import { Box, Paper, Switch, Typography } from "@mui/material"; import Grid from "@mui/material/Grid"; import { createContext, useState } from "react"; +import { BrowserView } from "react-device-detect"; import { Tooltip } from "react-tooltip"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useFetch } from "../hooks/useFetch"; import { convertScanJSON, Scan } from "../types/scans"; import { CheckIn } from "./checkin/CheckIn"; import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap"; +import { Streak } from "./streak/Streak"; interface ScanContextProps { scans: readonly Scan[]; @@ -23,7 +25,7 @@ export const Overview = () => { setScans, convertScanJSON ); - const [checked, setChecked] = useState(true); + const [checked, setChecked] = useState(false); const handleChange = (event: React.ChangeEvent) => { setChecked(event.target.checked); @@ -32,25 +34,35 @@ export const Overview = () => { return ( - - + + - + + + + - - Months - - Days - + + + Months + + Days + + - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate(); - export const CheckIn = () => { const { scans } = useContext(ScanContext); diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx index d30fab0..4c7c9aa 100644 --- a/vinvoor/src/overview/heatmap/Heatmap.tsx +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -1,4 +1,5 @@ import { FC, useContext } from "react"; +import { MILLISECONDS_IN_ONE_DAY, shiftDate } from "../../util/util"; import { ScanContext } from "../Overview"; import "./heatmap.css"; import { @@ -13,9 +14,7 @@ import { getTransformForColumn, getTransformForMonthLabels, getWidth, - MILLISECONDS_IN_ONE_DAY, MONTH_LABELS, - shiftDate, SQUARE_SIZE, } from "./utils"; diff --git a/vinvoor/src/overview/heatmap/utils.ts b/vinvoor/src/overview/heatmap/utils.ts index b95b1ff..388dc00 100644 --- a/vinvoor/src/overview/heatmap/utils.ts +++ b/vinvoor/src/overview/heatmap/utils.ts @@ -1,10 +1,10 @@ // Exports +import { MILLISECONDS_IN_ONE_DAY } from "../../util/util"; 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; @@ -93,12 +93,6 @@ export const getColumnCount = ( } }; -export const shiftDate = (date: Date, numDays: number) => { - const newDate = new Date(date); - newDate.setDate(newDate.getDate() + numDays); - return newDate; -}; - // Local functions const GUTTERSIZE = 5; diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx new file mode 100644 index 0000000..e34258e --- /dev/null +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -0,0 +1,97 @@ +import { Box, Typography } from "@mui/material"; +import { useContext } from "react"; +import { Scan } from "../../types/scans"; +import { + isTheSameDay, + MILLISECONDS_IN_ONE_DAY, + shiftDate, +} from "../../util/util"; +import { ScanContext } from "../Overview"; + +const isWeekendBetween = (date1: Date, date2: Date) => { + const diffDays = Math.floor( + (date2.getTime() - date1.getTime()) / MILLISECONDS_IN_ONE_DAY + ); + + if (diffDays > 2) return false; + + return date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay()); +}; + +const isStreakDay = (date1: Date, date2: Date) => { + console.log(date1, date2); + console.log(shiftDate(date2, 1)); + console.log(isTheSameDay(date1, shiftDate(date2, 1))); + + if (isTheSameDay(date1, shiftDate(date2, 1))) return true; + + if (date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay())) + return isWeekendBetween(date1, date2); + + return false; +}; + +const getStreak = (scans: readonly Scan[]): [boolean, number] => { + let streak = 0; + const isOnStreak = + isTheSameDay(scans[scans.length - 1].scanTime, new Date()) || + isWeekendBetween(scans[scans.length - 1].scanTime, new Date()); + + if (isOnStreak) { + let i = scans.length; + streak++; + + while (i-- > 1 && isStreakDay(scans[i].scanTime, scans[i - 1].scanTime)) + streak++; + } else { + streak = Math.floor( + (new Date().getTime() - + scans[scans.length - 1].scanTime.getTime()) / + MILLISECONDS_IN_ONE_DAY - + 1 + ); + } + + return [isOnStreak, streak]; +}; + +export const Streak = () => { + const { scans } = useContext(ScanContext); + const [isOnStreak, streak] = getStreak(scans); + + return isOnStreak ? ( + + + {streak} + + + day{streak > 1 ? "s" : ""} streak + + + ) : ( + + + {streak} + + + day{streak > 1 ? "s" : ""} absent + + + ); +}; diff --git a/vinvoor/src/util/util.ts b/vinvoor/src/util/util.ts index 5ce3b09..4f92678 100644 --- a/vinvoor/src/util/util.ts +++ b/vinvoor/src/util/util.ts @@ -2,6 +2,23 @@ export const randomInt = (lower: number = 0, upper: number = 10000): number => { return Math.floor(Math.random() * (upper - lower + 1) + lower); }; +// Date functions + +export const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000; + +export const isTheSameDay = (date1: Date, date2: Date) => + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate(); + +export const shiftDate = (date: Date, numDays: number) => { + const newDate = new Date(date); + newDate.setDate(newDate.getDate() + numDays); + return newDate; +}; + +// Compare functions + export const equal = (left: any, right: any): boolean => { if (typeof left !== typeof right) return false; diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index fc51ae0..ba0e3a8 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -1795,6 +1795,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +react-device-detect@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/react-device-detect/-/react-device-detect-2.2.3.tgz#97a7ae767cdd004e7c3578260f48cf70c036e7ca" + integrity sha512-buYY3qrCnQVlIFHrC5UcUoAj7iANs/+srdkwsnNjI7anr3Tt7UY6MqNxtMLlr0tMBied0O49UZVK8XKs3ZIiPw== + dependencies: + ua-parser-js "^1.0.33" + react-dom@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.3.1.tgz#c2265d79511b57d479b3dd3fdfa51536494c5cb4" @@ -2092,6 +2099,11 @@ typescript@^5.2.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== +ua-parser-js@^1.0.33: + version "1.0.38" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2" + integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" From 41e16cc039e09e56742039a77f4172ac1a5a47e2 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Sat, 6 Jul 2024 22:42:18 +0200 Subject: [PATCH 31/53] vinvoor: show the user's most common days --- vinvoor/package.json | 1 + vinvoor/src/overview/Overview.tsx | 6 +++ vinvoor/src/overview/days/Days.tsx | 58 ++++++++++++++++++++++++++ vinvoor/src/overview/streak/Streak.tsx | 10 +---- vinvoor/yarn.lock | 7 ++++ 5 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 vinvoor/src/overview/days/Days.tsx diff --git a/vinvoor/package.json b/vinvoor/package.json index b035fc8..4e2c34b 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -25,6 +25,7 @@ "mdi-material-ui": "^7.9.1", "notistack": "^3.0.1", "react": "^18.2.0", + "react-apexcharts": "^1.4.1", "react-device-detect": "^2.2.3", "react-dom": "^18.2.0", "react-router-dom": "^6.23.1", diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index e0c9bd2..779df9f 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -7,6 +7,7 @@ import { LoadingSkeleton } from "../components/LoadingSkeleton"; import { useFetch } from "../hooks/useFetch"; import { convertScanJSON, Scan } from "../types/scans"; import { CheckIn } from "./checkin/CheckIn"; +import { Days } from "./days/Days"; import { Heatmap, HeatmapVariant } from "./heatmap/Heatmap"; import { Streak } from "./streak/Streak"; @@ -75,6 +76,11 @@ export const Overview = () => { + + + + + diff --git a/vinvoor/src/overview/days/Days.tsx b/vinvoor/src/overview/days/Days.tsx new file mode 100644 index 0000000..d0f1dab --- /dev/null +++ b/vinvoor/src/overview/days/Days.tsx @@ -0,0 +1,58 @@ +import { ApexOptions } from "apexcharts"; +import { useContext } from "react"; +import Chart from "react-apexcharts"; +import { Scan } from "../../types/scans"; +import { ScanContext } from "../Overview"; + +const getDayCount = (scans: readonly Scan[]) => { + const days = [0, 0, 0, 0, 0, 0, 0]; + scans.forEach((scan) => { + days[scan.scanTime.getDay() - 1]++; + }); + return days.slice(0, -2); +}; + +export const Days = () => { + const { scans } = useContext(ScanContext); + + const state = { + options: { + labels: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"], + fill: { + opacity: 1, + }, + yaxis: { + show: false, + }, + legend: { + position: "bottom", + labels: { + useSeriesColors: true, + }, + }, + plotOptions: { + polarArea: { + rings: { + strokeWidth: 0, + }, + spokes: { + strokeWidth: 0, + }, + }, + }, + theme: { + monochrome: { + enabled: true, + color: "#ff7f00", + shadeTo: "light", + shadeIntensity: 1, + }, + }, + } as ApexOptions, + series: getDayCount(scans), + }; + + return ( + + ); +}; diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx index e34258e..071d1f7 100644 --- a/vinvoor/src/overview/streak/Streak.tsx +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -60,7 +60,7 @@ export const Streak = () => { const [isOnStreak, streak] = getStreak(scans); return isOnStreak ? ( - + { ) : ( - + Date: Tue, 16 Jul 2024 14:53:34 +0200 Subject: [PATCH 32/53] vinvoor: simple overview page --- vinvoor/package.json | 3 +- vinvoor/src/cards/CardsAdd.tsx | 8 +- .../src/leaderboard/LeaderboardTableBody.tsx | 31 +- vinvoor/src/overview/Overview.tsx | 4 - vinvoor/src/overview/heatmap/Heatmap.tsx | 2 +- vinvoor/src/overview/heatmap/heatmap.css | 14 + vinvoor/vite.config.ts | 3 +- vinvoor/yarn.lock | 326 +++++++++++++++++- 8 files changed, 371 insertions(+), 20 deletions(-) diff --git a/vinvoor/package.json b/vinvoor/package.json index 4e2c34b..fdfbba9 100644 --- a/vinvoor/package.json +++ b/vinvoor/package.json @@ -42,6 +42,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "typescript": "^5.2.2", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-plugin-svgr": "^4.2.0" } } diff --git a/vinvoor/src/cards/CardsAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx index 00df0a0..e57b600 100644 --- a/vinvoor/src/cards/CardsAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -47,7 +47,7 @@ const checkCardsChange = async (): Promise< await new Promise((r) => setTimeout(r, CHECK_INTERVAL)); } - return [cardsNow !== null && cardsNow !== cardsStart, cardsNow]; + return [cardsNow !== null && !equal(cardsNow, cardsStart), cardsNow]; }; export const CardsAdd = () => { @@ -93,7 +93,9 @@ export const CardsAdd = () => { title: confirmTitle, description: confirmContent, confirmationText: "Register", - }).then(() => startRegistering()); + }) + .then(() => startRegistering()) + .catch(() => {}); // Required otherwise the confirm dialog will throw an error in the console }; return ( @@ -108,3 +110,5 @@ export const CardsAdd = () => { ); }; + +// TODO: Make plus sign a spinner when registering diff --git a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx index 9fd6302..bea64ac 100644 --- a/vinvoor/src/leaderboard/LeaderboardTableBody.tsx +++ b/vinvoor/src/leaderboard/LeaderboardTableBody.tsx @@ -1,9 +1,17 @@ -import { TableBody, TableCell, TableRow, Typography } from "@mui/material"; +import { + Icon, + TableBody, + TableCell, + TableRow, + Typography, +} from "@mui/material"; import { alpha } from "@mui/material/styles"; -import { PodiumBronze, PodiumGold, PodiumSilver } from "mdi-material-ui"; import { FC, useContext } from "react"; import { leaderboardHeadCells, LeaderboardItem } from "../types/leaderboard"; import { UserContext } from "../user/UserProvider"; +import FirstPlaceIcon from "/first_place.svg"; +import SecondPlaceIcon from "/second_place.svg"; +import ThirdPlaceIcon from "/third_place.svg"; interface LeaderboardTableBodyProps { leaderboardItems: readonly LeaderboardItem[]; @@ -12,11 +20,24 @@ interface LeaderboardTableBodyProps { const getPosition = (position: number) => { switch (position) { case 1: - return ; + // return ; + return ( + + + + ); case 2: - return ; + return ( + + + + ); case 3: - return ; + return ( + + + + ); default: return {position}; } diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index 779df9f..dbe0b10 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -86,7 +86,3 @@ export const Overview = () => { ); }; - -// TODO: Checked in today -// TODO: Current streak -// TODO: Pie chart diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx index 4c7c9aa..b41a8bb 100644 --- a/vinvoor/src/overview/heatmap/Heatmap.tsx +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -172,7 +172,7 @@ export const Heatmap: FC = ({ startDate, endDate, variant }) => { y={y} rx={2} ry={2} - className={getClassNameForValue(value, variant)} + className={`rect ${getClassNameForValue(value, variant)}`} {...getTooltipDataAttrsForDate(value, variant)} /> ); diff --git a/vinvoor/src/overview/heatmap/heatmap.css b/vinvoor/src/overview/heatmap/heatmap.css index d9361cf..375039c 100644 --- a/vinvoor/src/overview/heatmap/heatmap.css +++ b/vinvoor/src/overview/heatmap/heatmap.css @@ -60,3 +60,17 @@ stroke-width: 1px; stroke: #ba5f02; } + +@keyframes createBox { + from { + width: 0; + height: 0; + } + to { + transform: scale(1); + } +} + +.rect { + animation: createBox 1s; +} diff --git a/vinvoor/vite.config.ts b/vinvoor/vite.config.ts index 03d504e..25b3d61 100644 --- a/vinvoor/vite.config.ts +++ b/vinvoor/vite.config.ts @@ -1,7 +1,8 @@ import react from "@vitejs/plugin-react-swc"; import { defineConfig } from "vite"; +import svgr from "vite-plugin-svgr"; // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [svgr(), react()], }); diff --git a/vinvoor/yarn.lock b/vinvoor/yarn.lock index 22d764f..8a3865f 100644 --- a/vinvoor/yarn.lock +++ b/vinvoor/yarn.lock @@ -2,6 +2,14 @@ # yarn lockfile v1 +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + "@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" @@ -10,6 +18,32 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" +"@babel/compat-data@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" + integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== + +"@babel/core@^7.21.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" + integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helpers" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" @@ -20,6 +54,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/helper-compilation-targets@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" + integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== + dependencies: + "@babel/compat-data" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.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" @@ -42,7 +87,7 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-module-imports@^7.16.7": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.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== @@ -50,6 +95,25 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helper-module-transforms@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" + integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + 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" @@ -67,6 +131,19 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== +"@babel/helper-validator-option@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" + integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== + +"@babel/helpers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" + integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/highlight@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" @@ -114,7 +191,7 @@ debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.24.7": +"@babel/types@^7.21.3", "@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== @@ -582,6 +659,15 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.17.1.tgz#bf93997beb81863fde042ebd05013a2618471362" integrity sha512-mCOMec4BKd6BRGBZeSnGiIgwsbLGp3yhVqAD8H+PxiRNEHgDpZb8J1TnrSDlg97t0ySKMQJTHCWBCmBpSmkF6Q== +"@rollup/pluginutils@^5.0.5": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + "@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" @@ -662,6 +748,89 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + +"@svgr/core@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + +"@svgr/plugin-jsx@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@swc/core-darwin-arm64@1.6.7": version "1.6.7" resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.6.7.tgz#e98a0da9635297728a97faf7f4e11c46f8dfbb46" @@ -743,7 +912,7 @@ dependencies: "@swc/counter" "^0.1.3" -"@types/estree@1.0.5": +"@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -1012,11 +1181,31 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" +browserslist@^4.22.2: + version "4.23.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.1.tgz#ce4af0534b3d37db5c1a4ca98b9080f985041e96" + integrity sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw== + dependencies: + caniuse-lite "^1.0.30001629" + electron-to-chromium "^1.4.796" + node-releases "^2.0.14" + update-browserslist-db "^1.0.16" + 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== +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001629: + version "1.0.30001640" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz#32c467d4bf1f1a0faa63fc793c2ba81169e7652f" + integrity sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA== + chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -1083,6 +1272,11 @@ convert-source-map@^1.5.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + cosmiconfig@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" @@ -1094,6 +1288,16 @@ cosmiconfig@^7.0.0: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -1108,7 +1312,7 @@ csstype@^3.0.2, csstype@^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: +debug@^4.1.0, 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== @@ -1142,6 +1346,24 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +electron-to-chromium@^1.4.796: + version "1.4.818" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.818.tgz#7762c8bfd15a07c3833b7f5deed990e9e5a4c24f" + integrity sha512-eGvIk2V0dGImV9gWLq8fDfTTsCAeMDwZqEPMr+jMInxZdnp9Us8UpovYpRCf9NQ7VOFgrN2doNSgvISbsbNpxA== + +entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + error-ex@^1.3.1: version "1.3.2" resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" @@ -1178,6 +1400,11 @@ esbuild@^0.21.3: "@esbuild/win32-ia32" "0.21.5" "@esbuild/win32-x64" "0.21.5" +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + 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" @@ -1283,6 +1510,11 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" @@ -1377,6 +1609,11 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + glob-parent@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1466,7 +1703,7 @@ ignore@^5.2.0, ignore@^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: +import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -1573,6 +1810,11 @@ json-stable-stringify-without-jsonify@^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== +json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -1612,6 +1854,20 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + material-ui-confirm@^3.0.16: version "3.0.16" resolved "https://registry.yarnpkg.com/material-ui-confirm/-/material-ui-confirm-3.0.16.tgz#8b25b4770a0f15d888c838bcd21180f655e03469" @@ -1664,6 +1920,19 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + notistack@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/notistack/-/notistack-3.0.1.tgz#daf59888ab7e2c30a1fa8f71f9cba2978773236e" @@ -1717,7 +1986,7 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" -parse-json@^5.0.0: +parse-json@^5.0.0, parse-json@^5.2.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== @@ -1944,6 +2213,11 @@ scheduler@^0.23.2: dependencies: loose-envify "^1.1.0" +semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + semver@^7.6.0: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" @@ -1966,6 +2240,14 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + 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" @@ -2012,6 +2294,11 @@ supports-preserve-symlinks-flag@^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== +svg-parser@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" + integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== + svg.draggable.js@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba" @@ -2089,6 +2376,11 @@ ts-api-utils@^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== +tslib@^2.0.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + 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" @@ -2111,6 +2403,14 @@ ua-parser-js@^1.0.33: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2" integrity sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ== +update-browserslist-db@^1.0.16: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2118,6 +2418,15 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +vite-plugin-svgr@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/vite-plugin-svgr/-/vite-plugin-svgr-4.2.0.tgz#9f3bf5206b0ec510287e56d16f1915e729bb4e6b" + integrity sha512-SC7+FfVtNQk7So0XMjrrtLAbEC8qjFPifyD7+fs/E6aaNdVde6umlVVh0QuwDLdOMu7vp5RiGFsB70nj5yo0XA== + dependencies: + "@rollup/pluginutils" "^5.0.5" + "@svgr/core" "^8.1.0" + "@svgr/plugin-jsx" "^8.1.0" + vite@^5.2.0: version "5.3.3" resolved "https://registry.yarnpkg.com/vite/-/vite-5.3.3.tgz#5265b1f0a825b3b6564c2d07524777c83e3c04c2" @@ -2146,6 +2455,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" From 5d0f8725c6a931959321767b3f63fc251ca55707 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 16 Jul 2024 20:19:34 +0200 Subject: [PATCH 33/53] vinvoor: fix crash when no scans are registered --- vinvoor/src/cards/CardsTable.tsx | 6 ++--- vinvoor/src/overview/checkin/CheckIn.tsx | 11 ++++---- vinvoor/src/overview/streak/Streak.tsx | 32 ++++++++---------------- 3 files changed, 19 insertions(+), 30 deletions(-) diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx index c21829c..6bf0619 100644 --- a/vinvoor/src/cards/CardsTable.tsx +++ b/vinvoor/src/cards/CardsTable.tsx @@ -20,8 +20,8 @@ const getComparator = ( order: TableOrder, orderBy: Key ): (( - a: { [key in Key]: number | string }, - b: { [key in Key]: number | string } + a: { [key in Key]: number | string | Date }, + b: { [key in Key]: number | string | Date } ) => number) => { return order === "desc" ? (a, b) => descendingComparator(a, b, orderBy) @@ -115,7 +115,7 @@ export const CardsTable = () => { const visibleRows = useMemo( () => - stableSort(cards, getComparator(order, orderBy)).slice( + stableSort(cards, getComparator(order, orderBy)).slice( page * rowsPerPage, page * rowsPerPage + rowsPerPage ), diff --git a/vinvoor/src/overview/checkin/CheckIn.tsx b/vinvoor/src/overview/checkin/CheckIn.tsx index a3fc065..d174615 100644 --- a/vinvoor/src/overview/checkin/CheckIn.tsx +++ b/vinvoor/src/overview/checkin/CheckIn.tsx @@ -7,10 +7,9 @@ import { ScanContext } from "../Overview"; export const CheckIn = () => { const { scans } = useContext(ScanContext); - const checkedIn = isTheSameDay( - scans[scans.length - 1].scanTime, - new Date() - ); + const checkedIn = + scans.length > 0 && + isTheSameDay(scans[scans.length - 1].scanTime, new Date()); return checkedIn ? ( { }} > Checked in - Thank you for stopping by the kelder! + Nice of you to stop by! ) : ( { }} > Not checked in - We miss you in the kelder! + We miss you! ); }; diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx index 071d1f7..26275c5 100644 --- a/vinvoor/src/overview/streak/Streak.tsx +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -34,8 +34,9 @@ const isStreakDay = (date1: Date, date2: Date) => { const getStreak = (scans: readonly Scan[]): [boolean, number] => { let streak = 0; const isOnStreak = - isTheSameDay(scans[scans.length - 1].scanTime, new Date()) || - isWeekendBetween(scans[scans.length - 1].scanTime, new Date()); + scans.length > 0 && + (isTheSameDay(scans[scans.length - 1].scanTime, new Date()) || + isWeekendBetween(scans[scans.length - 1].scanTime, new Date())); if (isOnStreak) { let i = scans.length; @@ -59,32 +60,21 @@ export const Streak = () => { const { scans } = useContext(ScanContext); const [isOnStreak, streak] = getStreak(scans); - return isOnStreak ? ( - - - {streak} - - - day{streak > 1 ? "s" : ""} streak - - - ) : ( - + const color = isOnStreak ? "primary" : "error"; + const textEnd = isOnStreak ? "streak" : "absent"; + + return ( + {streak} - - day{streak > 1 ? "s" : ""} absent + + day{streak > 1 ? "s" : ""} {textEnd} ); From bb8a8553b720c67cd05d57e1ede05dc43288c514 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 16 Jul 2024 20:35:40 +0200 Subject: [PATCH 34/53] vinvoor: center graphs in the overview --- vinvoor/src/overview/Overview.tsx | 59 ++++++++++++++++++++---- vinvoor/src/overview/heatmap/Heatmap.tsx | 49 ++++++++++++++------ vinvoor/src/overview/heatmap/heatmap.css | 4 +- vinvoor/src/overview/heatmap/utils.ts | 2 +- vinvoor/src/overview/streak/Streak.tsx | 4 -- 5 files changed, 88 insertions(+), 30 deletions(-) diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index dbe0b10..37f5d01 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -1,6 +1,6 @@ import { Box, Paper, Switch, Typography } from "@mui/material"; import Grid from "@mui/material/Grid"; -import { createContext, useState } from "react"; +import { createContext, useEffect, useRef, useState } from "react"; import { BrowserView } from "react-device-detect"; import { Tooltip } from "react-tooltip"; import { LoadingSkeleton } from "../components/LoadingSkeleton"; @@ -26,35 +26,64 @@ export const Overview = () => { setScans, convertScanJSON ); - const [checked, setChecked] = useState(false); + const [checked, setChecked] = useState(true); + const daysRef = useRef(null); + const heatmapSwitchRef = useRef(null); + const [heatmapSwitchHeight, setHeatmapSwitchHeight] = useState(0); + const [paperHeight, setPaperHeight] = useState(0); const handleChange = (event: React.ChangeEvent) => { setChecked(event.target.checked); }; + useEffect(() => { + if (daysRef.current) { + setPaperHeight(daysRef.current.clientHeight); + } + + if (heatmapSwitchRef.current) { + setHeatmapSwitchHeight(heatmapSwitchRef.current.clientHeight); + } + }); + return ( - + - - + + Months { - - + + @@ -86,3 +122,6 @@ export const Overview = () => { ); }; + +// Current height of the heatmap is calculated using ref's and calculus +// TODO: Change it as it us very very very very very very ugly ^^ diff --git a/vinvoor/src/overview/heatmap/Heatmap.tsx b/vinvoor/src/overview/heatmap/Heatmap.tsx index b41a8bb..0189a5f 100644 --- a/vinvoor/src/overview/heatmap/Heatmap.tsx +++ b/vinvoor/src/overview/heatmap/Heatmap.tsx @@ -1,3 +1,4 @@ +import { Box } from "@mui/material"; import { FC, useContext } from "react"; import { MILLISECONDS_IN_ONE_DAY, shiftDate } from "../../util/util"; import { ScanContext } from "../Overview"; @@ -32,6 +33,7 @@ interface HeatmapProps { startDate: Date; endDate: Date; variant: HeatmapVariant; + maxHeight: number; } const getAllValues = ( @@ -64,7 +66,9 @@ const getAllValues = ( ); } else { return Array.from( - { length: getColumnCount(startDate, endDate, HeatmapVariant.DAYS) }, + { + length: getColumnCount(startDate, endDate, HeatmapVariant.DAYS), + }, (_, i) => { const start = shiftDate(startDate, i * DAYS_IN_WEEK); const count = Array.from({ @@ -83,17 +87,26 @@ const getAllValues = ( } }; -const getWeeksInMonth = (values: HeatmapItem[]): { [key: number]: number } => { +const getWeeksInMonth = ( + values: HeatmapItem[], + startDate: Date +): { [key: number]: number } => { const startYear = values[0].date.getFullYear(); return values.reduce( (acc, value) => { const index = (value.date.getFullYear() - startYear) * 12 + - value.date.getMonth(); + value.date.getMonth() - + startDate.getMonth(); acc[index] = (acc[index] || 0) + 1; return acc; }, - { 0: getEmpty(values[0].date, HeatmapVariant.MONTHS) } as { + { + [startDate.getMonth()]: getEmpty( + values[0].date, + HeatmapVariant.MONTHS + ), + } as { [key: number]: number; } ); @@ -132,7 +145,12 @@ const getTooltipDataAttrsForMonths = (value: HeatmapItem) => value.count !== 1 ? "s" : "" } on the week of ${dateTimeFormat.format(value.date)}`; -export const Heatmap: FC = ({ startDate, endDate, variant }) => { +export const Heatmap: FC = ({ + startDate, + endDate, + variant, + maxHeight, +}) => { const { scans } = useContext(ScanContext); const days = scans.map((scan) => scan.scanTime); @@ -147,7 +165,10 @@ export const Heatmap: FC = ({ startDate, endDate, variant }) => { )}`; const weeksInMonth = - variant === HeatmapVariant.MONTHS ? getWeeksInMonth(values) : {}; // Amount of weeks in each month + variant === HeatmapVariant.MONTHS + ? getWeeksInMonth(values, startDate) + : {}; // 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 @@ -216,7 +237,7 @@ export const Heatmap: FC = ({ startDate, endDate, variant }) => { return ( - {MONTH_LABELS[column]} + {MONTH_LABELS[startDate.getMonth() + column]} ); }); @@ -224,11 +245,13 @@ export const Heatmap: FC = ({ startDate, endDate, variant }) => { }; return ( - - - {renderMonthLabels()} - - {renderColumns()} - + + + + {renderMonthLabels()} + + {renderColumns()} + + ); }; diff --git a/vinvoor/src/overview/heatmap/heatmap.css b/vinvoor/src/overview/heatmap/heatmap.css index 375039c..231fe10 100644 --- a/vinvoor/src/overview/heatmap/heatmap.css +++ b/vinvoor/src/overview/heatmap/heatmap.css @@ -4,8 +4,8 @@ } .heatmap rect:hover { - stroke: #555; stroke-width: 1px; + stroke-opacity: 0; } /* @@ -42,7 +42,7 @@ .heatmap .color-5 { fill: #ba5f02; stroke-width: 1px; - stroke: #ba5f02; + stroke: #934b01; } /* diff --git a/vinvoor/src/overview/heatmap/utils.ts b/vinvoor/src/overview/heatmap/utils.ts index 388dc00..efe2d1a 100644 --- a/vinvoor/src/overview/heatmap/utils.ts +++ b/vinvoor/src/overview/heatmap/utils.ts @@ -96,6 +96,6 @@ export const getColumnCount = ( // Local functions const GUTTERSIZE = 5; -const MONTH_LABEL_GUTTER_SIZE = 4; +const MONTH_LABEL_GUTTER_SIZE = 8; const getSquareSize = () => SQUARE_SIZE + GUTTERSIZE; diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx index 26275c5..e2dffeb 100644 --- a/vinvoor/src/overview/streak/Streak.tsx +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -19,10 +19,6 @@ const isWeekendBetween = (date1: Date, date2: Date) => { }; const isStreakDay = (date1: Date, date2: Date) => { - console.log(date1, date2); - console.log(shiftDate(date2, 1)); - console.log(isTheSameDay(date1, shiftDate(date2, 1))); - if (isTheSameDay(date1, shiftDate(date2, 1))) return true; if (date1.getDay() === 5 && [1, 6, 7].includes(date2.getDay())) From 3ab6c3d1218ce804bdb91ae95945ac376daf1432 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Tue, 16 Jul 2024 21:51:55 +0200 Subject: [PATCH 35/53] vinvoor: add support for new comers --- vinvoor/src/overview/streak/Streak.tsx | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/vinvoor/src/overview/streak/Streak.tsx b/vinvoor/src/overview/streak/Streak.tsx index e2dffeb..2f09037 100644 --- a/vinvoor/src/overview/streak/Streak.tsx +++ b/vinvoor/src/overview/streak/Streak.tsx @@ -41,12 +41,15 @@ const getStreak = (scans: readonly Scan[]): [boolean, number] => { while (i-- > 1 && isStreakDay(scans[i].scanTime, scans[i - 1].scanTime)) streak++; } else { - streak = Math.floor( - (new Date().getTime() - - scans[scans.length - 1].scanTime.getTime()) / - MILLISECONDS_IN_ONE_DAY - - 1 - ); + streak = + scans.length > 0 + ? Math.floor( + (new Date().getTime() - + scans[scans.length - 1].scanTime.getTime()) / + MILLISECONDS_IN_ONE_DAY - + 1 + ) + : 0; } return [isOnStreak, streak]; From c4bf18007ba03ee2579d37c3864d21196ab711f1 Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 16 Jul 2024 22:48:27 +0200 Subject: [PATCH 36/53] vingo: move to gorm --- vingo/database/cards.go | 34 +--- vingo/database/db.go | 44 +++-- vingo/database/migrations/1_init.down.sql | 6 - vingo/database/migrations/1_init.up.sql | 31 ---- .../migrations/2_cards_extra_column.up.sql | 2 - vingo/database/models.go | 46 +++++ vingo/database/scans.go | 26 +-- vingo/database/settings.go | 13 +- vingo/database/users.go | 46 ++--- vingo/go.mod | 21 ++- vingo/go.sum | 76 +++----- vingo/handlers/access.go | 8 - vingo/handlers/pages.go | 88 --------- vingo/handlers/settings.go | 8 +- vingo/handlers/store.go | 2 +- vingo/layouts/cards.html | 39 ---- vingo/layouts/days.html | 39 ---- vingo/layouts/landing.html | 1 - vingo/layouts/leaderboard.html | 18 -- vingo/layouts/main.html | 171 ------------------ vingo/layouts/partials/navbar.html | 43 ----- vingo/layouts/scans.html | 20 -- vingo/layouts/settings.html | 29 --- vingo/layouts/stats.html | 30 --- vingo/main.go | 38 +--- 25 files changed, 151 insertions(+), 728 deletions(-) delete mode 100644 vingo/database/migrations/1_init.down.sql delete mode 100644 vingo/database/migrations/1_init.up.sql delete mode 100644 vingo/database/migrations/2_cards_extra_column.up.sql create mode 100644 vingo/database/models.go delete mode 100644 vingo/handlers/pages.go delete mode 100644 vingo/layouts/cards.html delete mode 100644 vingo/layouts/days.html delete mode 100644 vingo/layouts/landing.html delete mode 100644 vingo/layouts/leaderboard.html delete mode 100644 vingo/layouts/main.html delete mode 100644 vingo/layouts/partials/navbar.html delete mode 100644 vingo/layouts/scans.html delete mode 100644 vingo/layouts/settings.html delete mode 100644 vingo/layouts/stats.html diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 0860d9f..77e2bae 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -1,40 +1,16 @@ package database -import "time" - -type Card struct { - Id int `json:"id"` - Serial string `json:"serial"` - CreatedAt time.Time `json:"createdAt"` - Name string `json:"name"` -} - func CreateCard(serial string, user_id int) error { - _, err := db.Exec("INSERT INTO cards (serial, user_id) VALUES ($1, $2);", serial, user_id) - return err + return gorm_db.Create(&Card{Serial: serial, UserId: user_id}).Error } func GetCardsForUser(user_id int) ([]Card, error) { - rows, err := db.Query("SELECT id, serial, created_at, name FROM cards WHERE user_id = $1;", user_id) - if err != nil { - return nil, err - } - defer rows.Close() - - cards := make([]Card, 0) - for rows.Next() { - var card Card - err := rows.Scan(&card.Id, &card.Serial, &card.CreatedAt, &card.Name) - if err != nil { - return nil, err - } - cards = append(cards, card) - } - - return cards, nil + var cards []Card + result := gorm_db.Where("user_id = ?", user_id).Find(&cards) + return cards, result.Error } func SetCardName(id int, name string, user_id int) error { - _, err := db.Exec("UPDATE cards SET name = $1 WHERE user_id = $2 and id = $3;", name, user_id, id) + err := gorm_db.Model(&Card{}).Where("id = ? AND user_id = ?", id, user_id).Update("name", name).Error return err } diff --git a/vingo/database/db.go b/vingo/database/db.go index 3528343..2a169dd 100644 --- a/vingo/database/db.go +++ b/vingo/database/db.go @@ -4,46 +4,56 @@ import ( "database/sql" "log" - "github.com/golang-migrate/migrate/v4" - "github.com/golang-migrate/migrate/v4/database/postgres" - _ "github.com/golang-migrate/migrate/v4/source/file" - _ "github.com/lib/pq" + "gorm.io/driver/postgres" + "gorm.io/gorm" ) var ( - db *sql.DB + db *sql.DB + gorm_db *gorm.DB ) func Get() *sql.DB { return db } -func OpenDatabase(conn string) { - new_db, err := sql.Open("postgres", conn) +func OpenDatabase(db_string string) { + new_db, err := gorm.Open(postgres.Open(db_string), &gorm.Config{}) if err != nil { log.Println("Error opening database connection") log.Fatal(err) } - driver, err := postgres.WithInstance(new_db, &postgres.Config{}) + err = new_db.AutoMigrate(&Settings{}) if err != nil { - log.Println("Error creating migration driver") + log.Println("Error migrating database") log.Fatal(err) } - m, err := migrate.NewWithDatabaseInstance( - "file://database/migrations", - "postgres", driver) + err = new_db.AutoMigrate(&User{}) if err != nil { - log.Println("Error creating migration instance") + log.Println("Error migrating database") log.Fatal(err) } - err = m.Up() - if err != nil && err != migrate.ErrNoChange { - log.Println("Error running migrations") + err = new_db.AutoMigrate(&Card{}) + if err != nil { + log.Println("Error migrating database") + log.Fatal(err) + } + + err = new_db.AutoMigrate(&Scan{}) + if err != nil { + log.Println("Error migrating database") + log.Fatal(err) + } + + err = new_db.AutoMigrate(&Day{}) + if err != nil { + log.Println("Error migrating database") log.Fatal(err) } - db = new_db + gorm_db = new_db + db, _ = new_db.DB() } diff --git a/vingo/database/migrations/1_init.down.sql b/vingo/database/migrations/1_init.down.sql deleted file mode 100644 index 921a752..0000000 --- a/vingo/database/migrations/1_init.down.sql +++ /dev/null @@ -1,6 +0,0 @@ -DROP TABLE days; - -DROP TABLE scans; -DROP TABLE cards; -DROP TABLE settings; -DROP TABLE users; \ No newline at end of file diff --git a/vingo/database/migrations/1_init.up.sql b/vingo/database/migrations/1_init.up.sql deleted file mode 100644 index 547662a..0000000 --- a/vingo/database/migrations/1_init.up.sql +++ /dev/null @@ -1,31 +0,0 @@ -CREATE TABLE IF NOT EXISTS days ( - id SERIAL NOT NULL PRIMARY KEY, - date DATE NOT NULL UNIQUE - ); - -CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL, - admin BOOLEAN DEFAULT FALSE NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL - ); - -CREATE TABLE IF NOT EXISTS settings ( - user_id INT NOT NULL PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, - public BOOLEAN NOT NULL DEFAULT FALSE, - scan_in_out BOOLEAN NOT NULL DEFAULT FALSE, - leaderboard BOOLEAN NOT NULL DEFAULT TRUE - ); - -CREATE TABLE IF NOT EXISTS cards ( - serial TEXT NOT NULL PRIMARY KEY UNIQUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - user_id INTEGER NOT NULL REFERENCES users(id) - ); - -CREATE TABLE IF NOT EXISTS scans ( - id SERIAL NOT NULL PRIMARY KEY, - scan_time TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, - scan_in BOOLEAN, - card_serial TEXT NOT NULL REFERENCES cards(serial) - ); \ No newline at end of file diff --git a/vingo/database/migrations/2_cards_extra_column.up.sql b/vingo/database/migrations/2_cards_extra_column.up.sql deleted file mode 100644 index 3269256..0000000 --- a/vingo/database/migrations/2_cards_extra_column.up.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE cards ADD COLUMN name TEXT DEFAULT '' NOT NULL; -ALTER TABLE cards ADD COLUMN id SERIAL NOT NULL; diff --git a/vingo/database/models.go b/vingo/database/models.go new file mode 100644 index 0000000..229fb3b --- /dev/null +++ b/vingo/database/models.go @@ -0,0 +1,46 @@ +package database + +import ( + "time" + + "gorm.io/gorm" +) + +type BaseModel struct { + Id int `json:"id" gorm:"primarykey"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` +} + +type User struct { + BaseModel + Username string `json:"username"` + Admin bool `json:"admin"` + SettingsId int + Settings Settings `json:"settings"` + Cards []Card `json:"-" gorm:"foreignKey:UserId;references:Id"` +} + +type Settings struct { + BaseModel + ScanInOut bool `json:"scanInOut"` + Leaderboard bool `json:"leaderboard"` + Public bool `json:"public"` +} + +type Card struct { + BaseModel + Serial string `json:"serial" gorm:"uniqueIndex"` + Name string `json:"name"` + UserId int `json:"-"` + User User `json:"-"` + Scans []Scan `json:"-" gorm:"foreignKey:CardSerial;references:Serial"` +} + +type Scan struct { + BaseModel + ScanTime time.Time `json:"scanTime"` + CardSerial string `json:"cardSerial" gorm:"index"` + Card Card `json:"-" gorm:"foreignKey:CardSerial;references:Serial"` +} diff --git a/vingo/database/scans.go b/vingo/database/scans.go index db08ce4..6a89142 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -4,11 +4,6 @@ import ( "time" ) -type Scan struct { - ScanTime time.Time `json:"scanTime"` - Card string `json:"card"` -} - type Present struct { Date time.Time Present bool @@ -22,24 +17,19 @@ type LeaderboardItem struct { } func CreateScan(card_serial string) error { - _, err := db.Exec("INSERT INTO scans (card_serial) VALUES ($1);", card_serial) - return err + return gorm_db.Create(&Scan{ScanTime: time.Now(), CardSerial: card_serial}).Error } func GetScansForUser(user_id int) ([]Scan, error) { - scans_rows, err := db.Query("SELECT scan_time, card_serial FROM scans WHERE card_serial IN (SELECT serial FROM cards WHERE user_id = $1) ORDER BY scan_time DESC;", user_id) - if err != nil { - return nil, err - } - - scans := []Scan{} - for scans_rows.Next() { - var scan Scan - _ = scans_rows.Scan(&scan.ScanTime, &scan.Card) + var user User + result := gorm_db.First(&user, user_id) - scans = append(scans, scan) + var scans []Scan + for _, card := range user.Cards { + scans = append(scans, card.Scans...) } - return scans, nil + + return scans, result.Error } func GetPresenceHistory(user_id int) ([]Present, error) { diff --git a/vingo/database/settings.go b/vingo/database/settings.go index ef1b1cb..c26f98b 100644 --- a/vingo/database/settings.go +++ b/vingo/database/settings.go @@ -1,21 +1,14 @@ package database -type Settings struct { - ScanInOut bool `json:"scanInOut"` - Leaderboard bool `json:"leaderboard"` - Public bool `json:"public"` -} - func CreateSettings(user_id int) error { _, err := db.Exec("INSERT INTO settings (user_id) VALUES ($1) ON CONFLICT DO NOTHING;", user_id) return err } func GetSettings(user_id int) (*Settings, error) { - row := db.QueryRow("SELECT scan_in_out, leaderboard, public FROM settings WHERE user_id = $1;", user_id) - settings := new(Settings) - err := row.Scan(&settings.ScanInOut, &settings.Leaderboard, &settings.Public) - return settings, err + var settings Settings + result := gorm_db.First(&settings, "user_id = ?", user_id) + return &settings, result.Error } func UpdateSettings(user_id int, settings Settings) error { diff --git a/vingo/database/users.go b/vingo/database/users.go index bb20309..441df16 100644 --- a/vingo/database/users.go +++ b/vingo/database/users.go @@ -1,44 +1,24 @@ package database -type User struct { - Id int `json:"id"` - Username string `json:"username"` - Admin bool `json:"admin"` - Settings Settings `json:"settings"` -} +import "time" func CreateUserIfNew(user_id int, username string) error { - _, err := db.Exec("INSERT INTO users (id, username) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET username = EXCLUDED.username;", user_id, username) - if err != nil { - return err - } - - err = CreateSettings(user_id) - return err + var user = &User{Username: username, Settings: Settings{ScanInOut: false, Leaderboard: true, Public: false}} + user.Id = user_id + user.Settings.CreatedAt = time.Now() + user.Settings.UpdatedAt = time.Now() + result := gorm_db.FirstOrCreate(&user) + return result.Error } func GetUser(user_id int) (*User, error) { - return getUser("SELECT id, username, admin, scan_in_out, leaderboard, public FROM users JOIN settings on id = user_id WHERE id = $1;", user_id) + var user User + result := gorm_db.First(&user, user_id) + return &user, result.Error } func GetUserFromCard(card_serial string) (*User, error) { - row := db.QueryRow(` - SELECT users.id, users.username, users.admin - FROM users - JOIN cards ON users.id = cards.user_id - WHERE cards.serial = $1; - `, card_serial) - user := new(User) - err := row.Scan(&user.Id, &user.Username, &user.Admin) - return user, err -} - -func getUser(query string, args ...interface{}) (*User, error) { - row := db.QueryRow(query, args...) - user := new(User) - settings := new(Settings) - err := row.Scan(&user.Id, &user.Username, &user.Admin, &settings.ScanInOut, &settings.Leaderboard, &settings.Public) - - user.Settings = *settings - return user, err + var card Card + result := gorm_db.First(&card, "serial = ?", card_serial) + return &card.User, result.Error } diff --git a/vingo/go.mod b/vingo/go.mod index a6f5dd1..37b414d 100644 --- a/vingo/go.mod +++ b/vingo/go.mod @@ -4,30 +4,35 @@ go 1.22.1 require ( github.com/gofiber/fiber/v2 v2.52.4 - github.com/gofiber/template/html/v2 v2.1.1 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 + gorm.io/driver/postgres v1.5.9 + gorm.io/gorm v1.25.11 ) require ( - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - go.uber.org/atomic v1.7.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + golang.org/x/crypto v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/text v0.16.0 // indirect ) require ( github.com/andybalholm/brotli v1.1.0 // indirect - github.com/gofiber/template v1.8.3 // indirect - github.com/gofiber/utils v1.1.0 // indirect - github.com/golang-migrate/migrate/v4 v4.17.1 github.com/google/uuid v1.6.0 // indirect github.com/klauspost/compress v1.17.8 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/stretchr/testify v1.8.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.53.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.22.0 // indirect ) diff --git a/vingo/go.sum b/vingo/go.sum index 981e998..cfc254a 100644 --- a/vingo/go.sum +++ b/vingo/go.sum @@ -1,41 +1,24 @@ -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= -github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dhui/dktest v0.4.1 h1:/w+IWuDXVymg3IrRJCHHOkMK10m9aNVMOyD0X12YVTg= -github.com/dhui/dktest v0.4.1/go.mod h1:DdOqcUpL7vgyP4GlF3X3w7HbSlz8cEQzwewPveYEQbA= -github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= -github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= -github.com/gofiber/template v1.8.3/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= -github.com/gofiber/template/html/v2 v2.1.1 h1:QEy3O3EBkvwDthy5bXVGUseOyO6ldJoiDxlF4+MJiV8= -github.com/gofiber/template/html/v2 v2.1.1/go.mod h1:2G0GHHOUx70C1LDncoBpe4T6maQbNa4x1CVNFW0wju0= -github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= -github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-migrate/migrate/v4 v4.17.1 h1:4zQ6iqL6t6AiItphxJctQb3cFqWiSpMnX7wLTPnnYO4= -github.com/golang-migrate/migrate/v4 v4.17.1/go.mod h1:m8hinFyWBn0SA4QKHuKh175Pm9wjmxj3S2Mia7dbXzM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= @@ -49,16 +32,6 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= @@ -66,6 +39,7 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -74,17 +48,21 @@ github.com/valyala/fasthttp v1.53.0 h1:lW/+SUkOxCx2vlIu0iaImv4JLrVRnbbkpCoaawvA4 github.com/valyala/fasthttp v1.53.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -golang.org/x/mod v0.11.0 h1:bUO06HqtnRcc/7l71XBe4WcqTZ+3AH1J59zWDDwLKgU= -golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/tools v0.10.0 h1:tvDr/iQoUqNdohiYm0LmmKcBk+q86lb9EprIUFhHHGg= -golang.org/x/tools v0.10.0/go.mod h1:UJwyiVBsOA2uwvK/e5OY3GTpDUJriEd+/YlqAwLPmyM= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8= +gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= +gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= +gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/vingo/handlers/access.go b/vingo/handlers/access.go index cf21b1f..e1ed9d0 100644 --- a/vingo/handlers/access.go +++ b/vingo/handlers/access.go @@ -3,14 +3,6 @@ package handlers import "github.com/gofiber/fiber/v2" func IsLoggedIn(c *fiber.Ctx) error { - if getUserFromStore(c) == nil { - return c.Redirect("/login") - } - - return c.Next() -} - -func IsLoggedInAPI(c *fiber.Ctx) error { if getUserFromStore(c) == nil { return c.Status(401).SendString("Unauthorized") } diff --git a/vingo/handlers/pages.go b/vingo/handlers/pages.go deleted file mode 100644 index 56067f2..0000000 --- a/vingo/handlers/pages.go +++ /dev/null @@ -1,88 +0,0 @@ -package handlers - -import ( - "time" - "vingo/database" - - "github.com/gofiber/fiber/v2" -) - -func Index(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - if current_user != nil { - return stats(c, current_user) - } else { - return landing(c) - } -} - -func landing(c *fiber.Ctx) error { - return c.Render("landing", nil, "main") -} - -func stats(c *fiber.Ctx, user *database.User) error { - days, err := database.GetPresenceHistory(user.Id) - if err != nil { - logger.Println("Error get presence history:", err) - return c.Status(500).SendString("Error getting presence history") - } - - return c.Render("stats", fiber.Map{"user": user, "days_present_7": days}, "main") -} - -func ScansPage(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - - scans, err := database.GetScansForUser(current_user.Id) - if err != nil { - logger.Println("Error get scans:", err) - return c.Status(500).SendString("Error getting scans") - } - - return c.Render("scans", fiber.Map{"user": current_user, "scans": scans}, "main") -} - -func CardsPage(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - - cards, err := database.GetCardsForUser(current_user.Id) - if err != nil { - logger.Println("", err) - return c.Status(500).SendString("Error getting cards") - } - - registering := time.Now().Before(registering_end) - registering_is_user := current_user.Id == registering_user - - return c.Render("cards", fiber.Map{"user": current_user, "cards": cards, "registering": registering, "reg_user": registering_is_user}, "main") -} - -func DaysPage(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - - days, err := database.GetDays() - if err != nil { - logger.Println("Error get days:", err) - return c.Status(500).SendString("Error getting days") - } - - return c.Render("days", fiber.Map{"user": current_user, "days": days}, "main") -} - -func LeaderboardPage(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - - leaderboard, err := database.TotalDaysPerUser() - if err != nil { - logger.Println("Error getting leaderboard:", err) - return c.Status(500).SendString("Error getting leaderboard") - } - - return c.Render("leaderboard", fiber.Map{"user": current_user, "leaderboard": leaderboard}, "main") -} - -func SettingsPage(c *fiber.Ctx) error { - current_user := getUserFromStore(c) - - return c.Render("settings", fiber.Map{"user": current_user}, "main") -} diff --git a/vingo/handlers/settings.go b/vingo/handlers/settings.go index 99f26a6..6ce76ce 100644 --- a/vingo/handlers/settings.go +++ b/vingo/handlers/settings.go @@ -34,11 +34,5 @@ func SettingsUpdate(c *fiber.Ctx) error { func Settings(c *fiber.Ctx) error { user := getUserFromStore(c) - settings, err := database.GetSettings(user.Id) - if err != nil { - logger.Println(err) - return c.Status(500).SendString("Error getting settings") - } - - return c.JSON(settings) + return c.JSON(user.Settings) } diff --git a/vingo/handlers/store.go b/vingo/handlers/store.go index 2f20560..a3cc732 100644 --- a/vingo/handlers/store.go +++ b/vingo/handlers/store.go @@ -34,12 +34,12 @@ func getUserFromStore(c *fiber.Ctx) *database.User { } user := sess.Get(STORE_USER) - logger.Println("User from store:", user) if user == nil { return nil } databaseUser := user.(database.User) + logger.Println("User from store:", databaseUser.Id) return &databaseUser } diff --git a/vingo/layouts/cards.html b/vingo/layouts/cards.html deleted file mode 100644 index db2ed35..0000000 --- a/vingo/layouts/cards.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
-
- If you don't have any cards yet, click the button below to start registering a new card.
- Once clicked, you will have 1 minute to scan the card you want to use at the scanner and it will be linked to your account. -
-
- {{ if .registering }} - {{ if .reg_user }} - - {{ else }} - - {{ end }} - {{ else }} - - {{ end }} -
- -
-
- {{ if .cards }} -
- - - - - - - {{ range .cards }} - - - - - {{ end }} -
cardcreated_at
{{ .Serial }}{{ .CreatedAt.Local.Format "2 January 2006 15:04:05" }}
- {{ else }} -

No cards yet!

- {{ end }} - \ No newline at end of file diff --git a/vingo/layouts/days.html b/vingo/layouts/days.html deleted file mode 100644 index 87be5ef..0000000 --- a/vingo/layouts/days.html +++ /dev/null @@ -1,39 +0,0 @@ -
-
- - - - - -
-
-
- {{ if .days }} - - - - - - - - {{ range .days }} - - - - - {{ end }} -
datedelete
{{ .Date.Format "Mon 2 January 2006" }} -
- -
-
- {{ else }} -

No days yet!

- {{ end }} -
\ No newline at end of file diff --git a/vingo/layouts/landing.html b/vingo/layouts/landing.html deleted file mode 100644 index 588e664..0000000 --- a/vingo/layouts/landing.html +++ /dev/null @@ -1 +0,0 @@ -

Ono you are not logged in!

\ No newline at end of file diff --git a/vingo/layouts/leaderboard.html b/vingo/layouts/leaderboard.html deleted file mode 100644 index 5925445..0000000 --- a/vingo/layouts/leaderboard.html +++ /dev/null @@ -1,18 +0,0 @@ -
- - - - - - - - - {{ range .leaderboard }} - - - - - - {{ end }} -
#NameTotal days scanned
{{.Position}}{{.Username}}{{.TotalDays}}
-
\ No newline at end of file diff --git a/vingo/layouts/main.html b/vingo/layouts/main.html deleted file mode 100644 index a1719f4..0000000 --- a/vingo/layouts/main.html +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - ZeSS - - - - - {{ template "partials/navbar" . }} -
- {{ embed }} -
- - diff --git a/vingo/layouts/partials/navbar.html b/vingo/layouts/partials/navbar.html deleted file mode 100644 index 84ce78d..0000000 --- a/vingo/layouts/partials/navbar.html +++ /dev/null @@ -1,43 +0,0 @@ - diff --git a/vingo/layouts/scans.html b/vingo/layouts/scans.html deleted file mode 100644 index 5804ef6..0000000 --- a/vingo/layouts/scans.html +++ /dev/null @@ -1,20 +0,0 @@ -
- {{ if .scans }} - - - - - - - - {{ range .scans }} - - - - - {{ end }} -
scan_timecard
{{ .ScanTime.Local.Format "Mon 2 January 2006 15:04:05" }}{{ .Card }}
- {{ else }} -
No scans yet!
- {{ end }} -
\ No newline at end of file diff --git a/vingo/layouts/settings.html b/vingo/layouts/settings.html deleted file mode 100644 index 9173a9c..0000000 --- a/vingo/layouts/settings.html +++ /dev/null @@ -1,29 +0,0 @@ -

- Yay! Settings! They don't do anything :( -

- -
- - - - -
- - \ No newline at end of file diff --git a/vingo/layouts/stats.html b/vingo/layouts/stats.html deleted file mode 100644 index dbf37ce..0000000 --- a/vingo/layouts/stats.html +++ /dev/null @@ -1,30 +0,0 @@ - - -
- {{ range .days_present_7 }} -
- {{ .Date.Format "Mon 02 Jan" }} -
- {{ end }} -
\ No newline at end of file diff --git a/vingo/main.go b/vingo/main.go index 8b5f6b0..703683d 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -10,7 +10,6 @@ import ( "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" ) @@ -26,10 +25,7 @@ func main() { db := database.Get() defer db.Close() - engine := html.New("./layouts", ".html") - public := fiber.New(fiber.Config{ - Views: engine, - }) + public := fiber.New(fiber.Config{}) public.Use(cors.New(cors.Config{ AllowOrigins: corsAllowOrigins, @@ -38,30 +34,12 @@ func main() { })) // Public routes - public.Get("/", handlers.Index) - public.Get("/login", handlers.Login) public.Get("/auth/callback", handlers.Callback) public.Post("/scans", handlers.ScanRegister) - // Logged in routes - logged := public.Group("/", handlers.IsLoggedIn) - { - logged.Get("/logout", handlers.Logout) - - logged.Get("/scans", handlers.ScansPage) - - logged.Get("/cards", handlers.CardsPage) - logged.Post("/cards/register", handlers.StartCardRegister) - - logged.Get("/leaderboard", handlers.LeaderboardPage) - - logged.Get("/settings", handlers.Settings) - logged.Post("/settings", handlers.SettingsUpdate) - } - - api := logged.Group("/api", handlers.IsLoggedInAPI) + api := public.Group("/api", handlers.IsLoggedIn) { api.Get("/user", handlers.User) api.Get("/leaderboard", handlers.Leaderboard) @@ -69,14 +47,12 @@ func main() { api.Get("/cards", handlers.Cards) api.Post("/cards/register", handlers.StartCardRegisterAPI) api.Get("/settings", handlers.Settings) - } - // Admin routes - admin := logged.Group("/", handlers.IsAdmin) - { - admin.Get("/days", handlers.DaysPage) - admin.Post("/days", handlers.DaysRegister) - admin.Post("/days/:id", handlers.DaysDelete) + admin := api.Group("/admin", handlers.IsAdmin) + { + admin.Post("/days", handlers.DaysRegister) + admin.Post("/days/:id", handlers.DaysDelete) + } } log.Println(public.Listen(":4000")) From c42c99e8bb61d3ced7c73f298c6d0bfb685d31e7 Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 16 Jul 2024 22:51:24 +0200 Subject: [PATCH 37/53] vingo: go get -u && go mod tidy --- vingo/go.mod | 6 +++--- vingo/go.sum | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/vingo/go.mod b/vingo/go.mod index 37b414d..5c1a039 100644 --- a/vingo/go.mod +++ b/vingo/go.mod @@ -3,7 +3,7 @@ module vingo go 1.22.1 require ( - github.com/gofiber/fiber/v2 v2.52.4 + github.com/gofiber/fiber/v2 v2.52.5 github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 gorm.io/driver/postgres v1.5.9 @@ -25,14 +25,14 @@ require ( require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/klauspost/compress v1.17.8 // indirect + github.com/klauspost/compress v1.17.9 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.53.0 // indirect + github.com/valyala/fasthttp v1.55.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect golang.org/x/sys v0.22.0 // indirect ) diff --git a/vingo/go.sum b/vingo/go.sum index cfc254a..5fce104 100644 --- a/vingo/go.sum +++ b/vingo/go.sum @@ -3,8 +3,8 @@ github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer5 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gofiber/fiber/v2 v2.52.4 h1:P+T+4iK7VaqUsq2PALYEfBBo6bJZ4q3FP8cZ84EggTM= -github.com/gofiber/fiber/v2 v2.52.4/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo= +github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= @@ -21,8 +21,8 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU= -github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -44,8 +44,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.53.0 h1:lW/+SUkOxCx2vlIu0iaImv4JLrVRnbbkpCoaawvA4zc= -github.com/valyala/fasthttp v1.53.0/go.mod h1:6dt4/8olwq9QARP/TDuPmWyWcl4byhpvTJ4AAtcz+QM= +github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8k8= +github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= From 371b89f26023f0de18389e07922ae4f903b47d9c Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 16 Jul 2024 23:13:47 +0200 Subject: [PATCH 38/53] vingo: card register status endpoint --- vingo/handlers/cards.go | 26 ++++++++------------------ vingo/main.go | 3 ++- 2 files changed, 10 insertions(+), 19 deletions(-) diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index e7687db..157df8e 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -7,7 +7,7 @@ import ( "github.com/gofiber/fiber/v2" ) -func StartCardRegisterAPI(c *fiber.Ctx) error { +func StartCardRegister(c *fiber.Ctx) error { user := getUserFromStore(c) if time.Now().Before(registering_end) { @@ -23,23 +23,6 @@ func StartCardRegisterAPI(c *fiber.Ctx) error { return c.Status(200).JSON(map[string]bool{}) } -func StartCardRegister(c *fiber.Ctx) error { - // keep track of the user that initiated the request in global state - // since only one user can be registering a card at a time - user := getUserFromStore(c) - - if time.Now().Before(registering_end) { - return c.Status(400).SendString("Another user is already registering a card") - } - - registering_user = user.Id - registering_end = time.Now().Add(time.Minute) - - logger.Println("Card registration started by user", registering_user) - - return c.Status(200).Redirect("/cards") -} - func Cards(c *fiber.Ctx) error { user := getUserFromStore(c) cards, err := database.GetCardsForUser(user.Id) @@ -50,3 +33,10 @@ func Cards(c *fiber.Ctx) error { return c.JSON(cards) } + +func CardRegisterStatus(c *fiber.Ctx) error { + user := getUserFromStore(c) + register_ongoing := time.Now().Before(registering_end) + is_current_user := registering_user == user.Id + return c.JSON(map[string]bool{"registering": register_ongoing, "isCurrentUser": is_current_user}) +} diff --git a/vingo/main.go b/vingo/main.go index 703683d..13607c0 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -45,7 +45,8 @@ func main() { api.Get("/leaderboard", handlers.Leaderboard) api.Get("/scans", handlers.Scans) api.Get("/cards", handlers.Cards) - api.Post("/cards/register", handlers.StartCardRegisterAPI) + api.Get("/cards/register", handlers.CardRegisterStatus) + api.Post("/cards/register", handlers.StartCardRegister) api.Get("/settings", handlers.Settings) admin := api.Group("/admin", handlers.IsAdmin) From 2f3737cf8efc230ef76dc61ed29e869fb97e6888 Mon Sep 17 00:00:00 2001 From: Hannes Date: Tue, 16 Jul 2024 23:30:53 +0200 Subject: [PATCH 39/53] vingo: ability to add name to card --- vingo/database/cards.go | 5 ++--- vingo/handlers/cards.go | 23 +++++++++++++++++++++++ vingo/main.go | 1 + 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 77e2bae..8c6cdc6 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -10,7 +10,6 @@ func GetCardsForUser(user_id int) ([]Card, error) { return cards, result.Error } -func SetCardName(id int, name string, user_id int) error { - err := gorm_db.Model(&Card{}).Where("id = ? AND user_id = ?", id, user_id).Update("name", name).Error - return err +func UpdateCardName(id int, name string, user_id int) error { + return gorm_db.Model(&Card{}).Where("id = ? AND user_id = ?", id, user_id).Update("name", name).Error } diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index 157df8e..44ea104 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -1,6 +1,7 @@ package handlers import ( + "strconv" "time" "vingo/database" @@ -40,3 +41,25 @@ func CardRegisterStatus(c *fiber.Ctx) error { is_current_user := registering_user == user.Id return c.JSON(map[string]bool{"registering": register_ongoing, "isCurrentUser": is_current_user}) } + +func CardNameUpdate(c *fiber.Ctx) error { + user := getUserFromStore(c) + card_id, err := strconv.Atoi(c.Params("id")) + if err != nil { + logger.Println(err) + return c.Status(400).SendString("Invalid card id") + } + + payload := struct { + Name string `json:"name"` + }{} + c.BodyParser(&payload) + + err = database.UpdateCardName(card_id, payload.Name, user.Id) + if err != nil { + logger.Println(err) + return c.Status(500).SendString("Error updating card name") + } + + return c.SendString("Card name updated") +} diff --git a/vingo/main.go b/vingo/main.go index 13607c0..a150e6c 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -45,6 +45,7 @@ func main() { api.Get("/leaderboard", handlers.Leaderboard) api.Get("/scans", handlers.Scans) api.Get("/cards", handlers.Cards) + api.Patch("/cards/:id", handlers.CardNameUpdate) api.Get("/cards/register", handlers.CardRegisterStatus) api.Post("/cards/register", handlers.StartCardRegister) api.Get("/settings", handlers.Settings) From c295ae8638b596a22aedfd9d0b3eb51d62e1ad92 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 17 Jul 2024 00:07:20 +0200 Subject: [PATCH 40/53] vingo: return if last register was success --- vingo/handlers/cards.go | 4 +++- vingo/handlers/scans.go | 1 + vingo/handlers/store.go | 5 +++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index 44ea104..e6829d5 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -18,6 +18,7 @@ func StartCardRegister(c *fiber.Ctx) error { registering_user = user.Id registering_end = time.Now().Add(time.Minute) + registering_success = false logger.Println("Card registration started by user", registering_user) @@ -39,7 +40,8 @@ func CardRegisterStatus(c *fiber.Ctx) error { user := getUserFromStore(c) register_ongoing := time.Now().Before(registering_end) is_current_user := registering_user == user.Id - return c.JSON(map[string]bool{"registering": register_ongoing, "isCurrentUser": is_current_user}) + return c.JSON(map[string]bool{"registering": register_ongoing, "isCurrentUser": is_current_user, "success": registering_success}) + } func CardNameUpdate(c *fiber.Ctx) error { diff --git a/vingo/handlers/scans.go b/vingo/handlers/scans.go index 670b387..bf68858 100644 --- a/vingo/handlers/scans.go +++ b/vingo/handlers/scans.go @@ -43,6 +43,7 @@ func ScanRegister(c *fiber.Ctx) error { logger.Println(err) return c.Status(500).SendString("Error registering card") } + registering_success = true return c.SendString("Card registered") } diff --git a/vingo/handlers/store.go b/vingo/handlers/store.go index a3cc732..d611854 100644 --- a/vingo/handlers/store.go +++ b/vingo/handlers/store.go @@ -15,8 +15,9 @@ var ( logger = log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile) // State for registering a new card - registering_user = 0 - registering_end = time.Now() + registering_user = 0 + registering_end = time.Now() + registering_success = false ) const ( From 40b24a3b738f3c4fa7a0594cf2882868a8736d74 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 17 Jul 2024 00:47:44 +0200 Subject: [PATCH 41/53] vingo: return extra stats for cards --- vingo/database/cards.go | 20 ++++++++++++++++++++ vingo/database/models.go | 34 +++++++++++++++++++++++++++++----- vingo/handlers/cards.go | 2 +- 3 files changed, 50 insertions(+), 6 deletions(-) diff --git a/vingo/database/cards.go b/vingo/database/cards.go index 8c6cdc6..15962b8 100644 --- a/vingo/database/cards.go +++ b/vingo/database/cards.go @@ -10,6 +10,26 @@ func GetCardsForUser(user_id int) ([]Card, error) { return cards, result.Error } +func GetCardsAndStatsForUser(user_id int) ([]CardAPI, error) { + rows, err := db.Query(` + SELECT cards.id, cards.created_at, serial, name, COUNT(scans.id), (select MAX(scan_time) from scans where card_serial = cards.serial) from cards LEFT JOIN scans on scans.card_serial = serial WHERE + user_id = $1 GROUP BY cards.id; + `, user_id) + + if err != nil { + return nil, err + } + + cards := []CardAPI{} + for rows.Next() { + var item CardAPI + _ = rows.Scan(&item.Id, &item.CreatedAt, &item.Serial, &item.Name, &item.AmountUsed, &item.LastUsed) + cards = append(cards, item) + } + + return cards, nil +} + func UpdateCardName(id int, name string, user_id int) error { return gorm_db.Model(&Card{}).Where("id = ? AND user_id = ?", id, user_id).Update("name", name).Error } diff --git a/vingo/database/models.go b/vingo/database/models.go index 229fb3b..ce66161 100644 --- a/vingo/database/models.go +++ b/vingo/database/models.go @@ -31,11 +31,35 @@ type Settings struct { type Card struct { BaseModel - Serial string `json:"serial" gorm:"uniqueIndex"` - Name string `json:"name"` - UserId int `json:"-"` - User User `json:"-"` - Scans []Scan `json:"-" gorm:"foreignKey:CardSerial;references:Serial"` + Serial string `gorm:"uniqueIndex"` + Name string + UserId int + User User + Scans []Scan `gorm:"foreignKey:CardSerial;references:Serial"` +} + +func Card_to_API(card Card) CardAPI { + var lastUsed time.Time = card.CreatedAt + if len(card.Scans) != 0 { + lastUsed = card.Scans[len(card.Scans)-1].ScanTime + } + + return CardAPI{ + Id: card.Id, + Serial: card.Serial, + Name: card.Name, + LastUsed: lastUsed, + AmountUsed: len(card.Scans), + } +} + +type CardAPI struct { + Id int `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Serial string `json:"serial"` + Name string `json:"name"` + LastUsed time.Time `json:"lastUsed"` + AmountUsed int `json:"amountUsed"` } type Scan struct { diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index e6829d5..4d127f2 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -27,7 +27,7 @@ func StartCardRegister(c *fiber.Ctx) error { func Cards(c *fiber.Ctx) error { user := getUserFromStore(c) - cards, err := database.GetCardsForUser(user.Id) + cards, err := database.GetCardsAndStatsForUser(user.Id) if err != nil { logger.Println("", err) return c.Status(500).SendString("Error getting cards") From 3735c44225fc00ee89e92140167106a2a52302a3 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 17 Jul 2024 00:57:11 +0200 Subject: [PATCH 42/53] vingo: time remaining on card status endpoint --- vingo/handlers/cards.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index 4d127f2..c2e2834 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -8,6 +8,8 @@ import ( "github.com/gofiber/fiber/v2" ) +var register_timeout = time.Minute + func StartCardRegister(c *fiber.Ctx) error { user := getUserFromStore(c) @@ -17,7 +19,7 @@ func StartCardRegister(c *fiber.Ctx) error { } registering_user = user.Id - registering_end = time.Now().Add(time.Minute) + registering_end = time.Now().Add(register_timeout) registering_success = false logger.Println("Card registration started by user", registering_user) @@ -40,8 +42,9 @@ func CardRegisterStatus(c *fiber.Ctx) error { user := getUserFromStore(c) register_ongoing := time.Now().Before(registering_end) is_current_user := registering_user == user.Id - return c.JSON(map[string]bool{"registering": register_ongoing, "isCurrentUser": is_current_user, "success": registering_success}) - + time_remaining := time.Until(registering_end).Seconds() + time_percentage := time_remaining / register_timeout.Seconds() + return c.JSON(map[string]interface{}{"registering": register_ongoing, "isCurrentUser": is_current_user, "success": registering_success, "time_remaining": time_remaining, "time_percentage": time_percentage}) } func CardNameUpdate(c *fiber.Ctx) error { From 1864bdc64e22fc2af7c0953ff7f7c8cda560d939 Mon Sep 17 00:00:00 2001 From: Topvennie Date: Wed, 17 Jul 2024 03:04:17 +0200 Subject: [PATCH 43/53] vinvoor: support new card features --- vingo/handlers/cards.go | 4 +- vinvoor/src/cards/CardsAdd.tsx | 163 +++++++++++++-------- vinvoor/src/cards/CardsDelete.tsx | 18 +-- vinvoor/src/cards/CardsTable.tsx | 4 +- vinvoor/src/cards/CardsTableBody.tsx | 80 ++++++++-- vinvoor/src/cards/CardsTableHead.tsx | 2 +- vinvoor/src/cards/CircularTimeProgress.tsx | 31 ++++ vinvoor/src/overview/Overview.tsx | 2 +- vinvoor/src/types/cards.ts | 52 ++++++- vinvoor/src/types/{table.ts => general.ts} | 10 ++ vinvoor/src/types/leaderboard.ts | 2 +- vinvoor/src/util/fetch.ts | 14 +- 12 files changed, 287 insertions(+), 95 deletions(-) create mode 100644 vinvoor/src/cards/CircularTimeProgress.tsx rename vinvoor/src/types/{table.ts => general.ts} (71%) diff --git a/vingo/handlers/cards.go b/vingo/handlers/cards.go index c2e2834..a1dc0e9 100644 --- a/vingo/handlers/cards.go +++ b/vingo/handlers/cards.go @@ -44,7 +44,7 @@ func CardRegisterStatus(c *fiber.Ctx) error { is_current_user := registering_user == user.Id time_remaining := time.Until(registering_end).Seconds() time_percentage := time_remaining / register_timeout.Seconds() - return c.JSON(map[string]interface{}{"registering": register_ongoing, "isCurrentUser": is_current_user, "success": registering_success, "time_remaining": time_remaining, "time_percentage": time_percentage}) + return c.JSON(map[string]interface{}{"registering": register_ongoing, "isCurrentUser": is_current_user, "success": registering_success, "timeRemaining": time_remaining, "timePercentage": time_percentage}) } func CardNameUpdate(c *fiber.Ctx) error { @@ -66,5 +66,5 @@ func CardNameUpdate(c *fiber.Ctx) error { return c.Status(500).SendString("Error updating card name") } - return c.SendString("Card name updated") + return c.Status(200).JSON(map[string]bool{}) } diff --git a/vinvoor/src/cards/CardsAdd.tsx b/vinvoor/src/cards/CardsAdd.tsx index e57b600..fa5eb00 100644 --- a/vinvoor/src/cards/CardsAdd.tsx +++ b/vinvoor/src/cards/CardsAdd.tsx @@ -2,14 +2,29 @@ import { Add } from "@mui/icons-material"; import { Button, Typography } from "@mui/material"; import { useConfirm } from "material-ui-confirm"; import { useSnackbar } from "notistack"; -import { useContext, useState } from "react"; -import { Card, CardPostResponse, convertCardJSON } from "../types/cards"; +import { useContext, useEffect, useState } from "react"; +import { + Card, + CardGetRegisterResponse, + CardPostResponse, + convertCardJSON, +} from "../types/cards"; import { getApi, isResponseNot200Error, postApi } from "../util/fetch"; -import { equal, randomInt } from "../util/util"; +import { randomInt } from "../util/util"; import { CardContext } from "./Cards"; +import { + CircularTimeProgress, + CircularTimeProgressProps, +} from "./CircularTimeProgress"; const CHECK_INTERVAL = 1000; const REGISTER_TIME = 60000; +const REGISTER_ENDPOINT = "cards/register"; + +const defaultProgressProps: CircularTimeProgressProps = { + time: REGISTER_TIME, + percentage: 1, +}; const confirmTitle = "Register a new card"; const confirmContent = ` @@ -27,66 +42,88 @@ const requestFail = const registerSucces = "Card registered successfully"; const registerFail = "Failed to register card"; -const getCards = () => - getApi("cards", convertCardJSON).catch((_) => null); - -const checkCardsChange = async (): Promise< - [boolean, readonly Card[] | null] -> => { - const startTime = Date.now(); - const cardsStart = await getCards(); - - if (!cardsStart) return [false, null]; - - let cardsNow: readonly Card[] | null = null; - while (Date.now() - startTime < REGISTER_TIME) { - cardsNow = await getCards(); - - if (!equal(cardsStart, cardsNow)) break; - - await new Promise((r) => setTimeout(r, CHECK_INTERVAL)); - } - - return [cardsNow !== null && !equal(cardsNow, cardsStart), cardsNow]; -}; - export const CardsAdd = () => { const { setCards } = useContext(CardContext); - const [disabled, setDisabled] = useState(false); + const [registering, setRegistering] = useState(false); + const [progressProps, setProgressProps] = + useState(defaultProgressProps); const confirm = useConfirm(); const { enqueueSnackbar, closeSnackbar } = useSnackbar(); - const startRegistering = () => - postApi("cards/register") - .then(() => { - const id = randomInt().toString(); - enqueueSnackbar(requestSuccess, { - variant: "info", - persist: true, - key: id, - }); - setDisabled(true); + const checkCardsChange = async (): Promise => { + let status: CardGetRegisterResponse = + await getApi(REGISTER_ENDPOINT); + while (status.registering && status.isCurrentUser) { + setProgressProps({ + time: status.timeRemaining, + percentage: status.timePercentage, + }); + status = await getApi(REGISTER_ENDPOINT); + await new Promise((r) => setTimeout(r, CHECK_INTERVAL)); + } - checkCardsChange().then((result) => { - closeSnackbar(id); - setDisabled(false); + return status.success; + }; - if (result[0] && result[1] !== null) { - enqueueSnackbar(registerSucces, { variant: "success" }); - setCards(result[1]); - } else enqueueSnackbar(registerFail, { variant: "error" }); - }); - }) - .catch((error) => { - if (isResponseNot200Error(error)) { - error.response.json().then((response: CardPostResponse) => { - if (response.isCurrentUser) - enqueueSnackbar(requestYou, { variant: "warning" }); - else - enqueueSnackbar(requestOther, { variant: "error" }); + const handleRegister = (start: boolean) => { + getApi(REGISTER_ENDPOINT) + .then(async (response) => { + let started = false; + if (!response.registering && start) { + await postApi(REGISTER_ENDPOINT) + .then(() => (started = true)) + .catch((error) => { + if (isResponseNot200Error(error)) + error.response + .json() + .then((response: CardPostResponse) => { + if (response.isCurrentUser) + enqueueSnackbar(requestYou, { + variant: "warning", + }); + else + enqueueSnackbar(requestOther, { + variant: "error", + }); + }); + else throw new Error(error); + }); + } + + if (response.registering && response.isCurrentUser) + started = true; + + if (started) { + setRegistering(true); + const id = randomInt().toString(); + enqueueSnackbar(requestSuccess, { + variant: "info", + persist: true, + key: id, }); - } else enqueueSnackbar(requestFail, { variant: "error" }); - }); + + checkCardsChange() + .then((scanned) => { + closeSnackbar(id); + setRegistering(false); + if (scanned) { + enqueueSnackbar(registerSucces, { + variant: "success", + }); + getApi( + "cards", + convertCardJSON + ).then((cards) => setCards(cards)); + } else + enqueueSnackbar(registerFail, { + variant: "error", + }); + }) + .finally(() => setProgressProps(defaultProgressProps)); + } + }) + .catch(() => enqueueSnackbar(requestFail, { variant: "error" })); + }; const handleClick = () => { confirm({ @@ -94,21 +131,27 @@ export const CardsAdd = () => { description: confirmContent, confirmationText: "Register", }) - .then(() => startRegistering()) + .then(() => handleRegister(true)) .catch(() => {}); // Required otherwise the confirm dialog will throw an error in the console }; + useEffect(() => { + handleRegister(false); + }, []); + return ( ); }; - -// TODO: Make plus sign a spinner when registering diff --git a/vinvoor/src/cards/CardsDelete.tsx b/vinvoor/src/cards/CardsDelete.tsx index 6e318de..fa63fcd 100644 --- a/vinvoor/src/cards/CardsDelete.tsx +++ b/vinvoor/src/cards/CardsDelete.tsx @@ -1,5 +1,5 @@ import DeleteIcon from "@mui/icons-material/Delete"; -import { IconButton, Tooltip } from "@mui/material"; +import { IconButton, Link, Tooltip, Typography } from "@mui/material"; import { useConfirm } from "material-ui-confirm"; import { FC } from "react"; @@ -12,14 +12,14 @@ export const CardsDelete: FC = ({ selected }) => { const numSelected = selected.length; 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 content = ( + + ` Are you sure you want to delete ${numSelected} card$ + {numSelected > 1 ? "s" : ""}? Unfortunately, this feature isn't + available yet. Let's convince Hannes to add this feature by signing + this petition!` + + ); const handleClick = () => { confirm({ diff --git a/vinvoor/src/cards/CardsTable.tsx b/vinvoor/src/cards/CardsTable.tsx index 6bf0619..512e777 100644 --- a/vinvoor/src/cards/CardsTable.tsx +++ b/vinvoor/src/cards/CardsTable.tsx @@ -1,7 +1,7 @@ import { Paper, Table, TableContainer, TablePagination } from "@mui/material"; import { ChangeEvent, MouseEvent, useContext, useMemo, useState } from "react"; import { Card } from "../types/cards"; -import { TableOrder } from "../types/table"; +import { TableOrder } from "../types/general"; import { CardContext } from "./Cards"; import { CardsTableBody } from "./CardsTableBody"; import { CardsTableHead } from "./CardsTableHead"; @@ -72,7 +72,7 @@ export const CardsTable = () => { }; const handleRowClick = ( - _: MouseEvent, + _: MouseEvent, serial: string ) => { const selectedIndex = selected.indexOf(serial); diff --git a/vinvoor/src/cards/CardsTableBody.tsx b/vinvoor/src/cards/CardsTableBody.tsx index f4d03aa..0f15dbc 100644 --- a/vinvoor/src/cards/CardsTableBody.tsx +++ b/vinvoor/src/cards/CardsTableBody.tsx @@ -1,42 +1,98 @@ +import { EditOutlined } from "@mui/icons-material"; import { Checkbox, + IconButton, TableBody, TableCell, TableRow, + TextField, Typography, } from "@mui/material"; -import { FC, MouseEvent } from "react"; -import { Card, CardsHeadCells } from "../types/cards"; +import { useConfirm } from "material-ui-confirm"; +import { useSnackbar } from "notistack"; +import { ChangeEvent, FC, MouseEvent, useContext } from "react"; +import { Card, CardsHeadCells, convertCardJSON } from "../types/cards"; +import { getApi, patchApi } from "../util/fetch"; +import { CardContext } from "./Cards"; interface CardsTableBodyProps { rows: readonly Card[]; isRowSelected: (serial: string) => boolean; handleClick: ( - event: MouseEvent, + event: MouseEvent, serial: string ) => void; emptyRows: number; } +const nameSaveSuccess = "New name saved successfully"; +const nameSaveFailure = "Unable to save new name"; + export const CardsTableBody: FC = ({ rows, isRowSelected, handleClick, emptyRows, }) => { + const { setCards } = useContext(CardContext); + const confirm = useConfirm(); + const { enqueueSnackbar } = useSnackbar(); + + const handleEditClick = (id: number, name: string) => { + let newName = name; + confirm({ + title: "Enter new name", + content: ( + ) => + (newName = event.target.value) + } + > + ), + confirmationText: "Save", + }) + .then(() => { + if (newName === name) { + enqueueSnackbar(nameSaveSuccess, { variant: "success" }); + return; + } + + patchApi(`cards/${id}`, { name: newName }) + .then(() => { + enqueueSnackbar(nameSaveSuccess, { + variant: "success", + }); + getApi("cards", convertCardJSON).then( + (cards) => setCards(cards) + ); + }) + .catch((error) => { + enqueueSnackbar(nameSaveFailure, { variant: "error" }); + console.log(error); + }); + }) + .catch(() => {}); // Required otherwise the confirm dialog will throw an error in the console + }; + + const editButton = (id: number, name: string) => ( + handleEditClick(id, name)}> + + + ); + return ( {rows.map((row) => { const isSelected = isRowSelected(row.serial); return ( - handleClick(event, row.serial)} - sx={{ cursor: "pointer" }} - > - + + handleClick(event, row.serial)} + padding="checkbox" + > {CardsHeadCells.map((headCell) => ( @@ -45,11 +101,13 @@ export const CardsTableBody: FC = ({ align={headCell.align} padding={headCell.padding} > - + {headCell.convert ? headCell.convert(row[headCell.id]) : (row[headCell.id] as string)} + {headCell.id === "name" && + editButton(row.id, row[headCell.id])} ))} diff --git a/vinvoor/src/cards/CardsTableHead.tsx b/vinvoor/src/cards/CardsTableHead.tsx index c8d5589..533fae7 100644 --- a/vinvoor/src/cards/CardsTableHead.tsx +++ b/vinvoor/src/cards/CardsTableHead.tsx @@ -8,7 +8,7 @@ import { } from "@mui/material"; import { ChangeEvent, FC, MouseEvent } from "react"; import { Card, CardsHeadCells } from "../types/cards"; -import { TableOrder } from "../types/table"; +import { TableOrder } from "../types/general"; interface CardTableHeadProps { numSelected: number; diff --git a/vinvoor/src/cards/CircularTimeProgress.tsx b/vinvoor/src/cards/CircularTimeProgress.tsx new file mode 100644 index 0000000..be2893a --- /dev/null +++ b/vinvoor/src/cards/CircularTimeProgress.tsx @@ -0,0 +1,31 @@ +import { Box, CircularProgress, Typography } from "@mui/material"; +import { FC } from "react"; + +export interface CircularTimeProgressProps { + time: number; + percentage: number; +} + +export const CircularTimeProgress: FC = ({ + time, + percentage, +}) => { + return ( + + + + {Math.round(time)} + + + ); +}; diff --git a/vinvoor/src/overview/Overview.tsx b/vinvoor/src/overview/Overview.tsx index 37f5d01..b9d5c3d 100644 --- a/vinvoor/src/overview/Overview.tsx +++ b/vinvoor/src/overview/Overview.tsx @@ -124,4 +124,4 @@ export const Overview = () => { }; // Current height of the heatmap is calculated using ref's and calculus -// TODO: Change it as it us very very very very very very ugly ^^ +// TODO: Change it as it is very very very very very very ugly ^^ diff --git a/vinvoor/src/types/cards.ts b/vinvoor/src/types/cards.ts index 437ff91..a44bae7 100644 --- a/vinvoor/src/types/cards.ts +++ b/vinvoor/src/types/cards.ts @@ -1,28 +1,52 @@ -import { TableHeadCell } from "./table"; +import { Base, BaseJSON, TableHeadCell } from "./general"; -interface CardJSON { +interface CardJSON extends BaseJSON { serial: string; - createdAt: string; + name: string; + lastUsed: string; + amountUsed: number; } -export interface Card { +export interface Card extends Base { serial: string; - createdAt: Date; + name: string; + lastUsed: Date; + amountUsed: number; } export const convertCardJSON = (cardsJSON: CardJSON[]): Card[] => cardsJSON.map((CardJSON) => ({ serial: CardJSON.serial, + name: CardJSON.name, + lastUsed: new Date(CardJSON.lastUsed), + amountUsed: CardJSON.amountUsed, + id: CardJSON.id, createdAt: new Date(CardJSON.createdAt), })); export const CardsHeadCells: readonly TableHeadCell[] = [ { - id: "serial", - label: "Serial", + id: "name", + label: "Name", align: "left", padding: "none", }, + { + id: "amountUsed", + label: "Amount of uses", + align: "right", + padding: "none", + }, + { + id: "lastUsed", + label: "Last used", + align: "right", + padding: "normal", + convert: (value: Date) => { + if (value.getFullYear() === 1) return "Not used"; + else return value.toDateString(); + }, + }, { id: "createdAt", label: "Created at", @@ -30,8 +54,22 @@ export const CardsHeadCells: readonly TableHeadCell[] = [ padding: "normal", convert: (value: Date) => value.toDateString(), }, + { + id: "serial", + label: "Serial", + align: "right", + padding: "normal", + }, ]; export interface CardPostResponse { isCurrentUser: boolean; } + +export interface CardGetRegisterResponse { + registering: boolean; + isCurrentUser: boolean; + success: boolean; + timeRemaining: number; + timePercentage: number; +} diff --git a/vinvoor/src/types/table.ts b/vinvoor/src/types/general.ts similarity index 71% rename from vinvoor/src/types/table.ts rename to vinvoor/src/types/general.ts index dc0603e..99dc2ce 100644 --- a/vinvoor/src/types/table.ts +++ b/vinvoor/src/types/general.ts @@ -1,3 +1,13 @@ +export interface BaseJSON { + id: number; + createdAt: string; +} + +export interface Base { + id: number; + createdAt: Date; +} + export type TableOrder = "asc" | "desc"; type TableAlignOptions = "right" | "left" | "center"; diff --git a/vinvoor/src/types/leaderboard.ts b/vinvoor/src/types/leaderboard.ts index 4493005..0096d08 100644 --- a/vinvoor/src/types/leaderboard.ts +++ b/vinvoor/src/types/leaderboard.ts @@ -1,4 +1,4 @@ -import { TableHeadCell } from "./table"; +import { TableHeadCell } from "./general"; export interface LeaderboardItem { position: number; diff --git a/vinvoor/src/util/fetch.ts b/vinvoor/src/util/fetch.ts index e1db30d..dbfe04e 100644 --- a/vinvoor/src/util/fetch.ts +++ b/vinvoor/src/util/fetch.ts @@ -12,8 +12,20 @@ export const postApi = ( body: { [key: string]: string } = {} ) => { return _fetch(`${URLS.API}/${endpoint}`, { - method: "post", + method: "POST", body: JSON.stringify(body), + headers: new Headers({ "content-type": "application/json" }), + }); +}; + +export const patchApi = ( + endpoint: string, + body: { [key: string]: string } = {} +) => { + return _fetch(`${URLS.API}/${endpoint}`, { + method: "PATCH", + body: JSON.stringify(body), + headers: new Headers({ "content-type": "application/json" }), }); }; From 2ac3951220447bfb8523dd97bb7fd1fbff496b33 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 17 Jul 2024 03:19:46 +0200 Subject: [PATCH 44/53] vingo: add leaderboard position change --- vingo/database/scans.go | 21 ++++++++++++--------- vingo/handlers/leaderboard.go | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/vingo/database/scans.go b/vingo/database/scans.go index 6a89142..6e77cc5 100644 --- a/vingo/database/scans.go +++ b/vingo/database/scans.go @@ -11,9 +11,11 @@ type Present struct { } type LeaderboardItem struct { - Position int `json:"position"` - Username string `json:"username"` - TotalDays int `json:"totalDays"` + Position int `json:"position"` + UserId int `json:"userId"` + Username string `json:"username"` + TotalDays int `json:"totalDays"` + PositionChange int `json:"poistionChange"` } func CreateScan(card_serial string) error { @@ -69,15 +71,16 @@ func GetPresenceHistory(user_id int) ([]Present, error) { return presences, nil } -func TotalDaysPerUser() ([]LeaderboardItem, error) { +func TotalDaysPerUser(before_time time.Time) ([]LeaderboardItem, error) { rows, err := db.Query(` - SELECT count, username, RANK() OVER (ORDER BY count desc) AS position - FROM (SELECT COUNT(DISTINCT ((scan_time - INTERVAL '4 hours') AT TIME ZONE 'Europe/Brussels')::date), username + SELECT user_id, count, username, RANK() OVER (ORDER BY count desc) AS position + FROM (SELECT COUNT(DISTINCT ((scan_time - INTERVAL '4 hours') AT TIME ZONE 'Europe/Brussels')::date), username, users.id as user_id FROM scans LEFT JOIN cards ON card_serial = serial LEFT JOIN users ON user_id = users.id - GROUP BY username); - `) + WHERE scan_time < $1 + GROUP BY username, users.id); + `, before_time) if err != nil { return nil, err @@ -86,7 +89,7 @@ func TotalDaysPerUser() ([]LeaderboardItem, error) { leaderboard := []LeaderboardItem{} for rows.Next() { var item LeaderboardItem - _ = rows.Scan(&item.TotalDays, &item.Username, &item.Position) + _ = rows.Scan(&item.UserId, &item.TotalDays, &item.Username, &item.Position) leaderboard = append(leaderboard, item) } diff --git a/vingo/handlers/leaderboard.go b/vingo/handlers/leaderboard.go index 7851e55..486dd04 100644 --- a/vingo/handlers/leaderboard.go +++ b/vingo/handlers/leaderboard.go @@ -1,18 +1,33 @@ package handlers import ( + "time" "vingo/database" "github.com/gofiber/fiber/v2" ) func Leaderboard(c *fiber.Ctx) error { - users, err := database.TotalDaysPerUser() + users, err := database.TotalDaysPerUser(time.Now()) if err != nil { logger.Println("Error getting leaderboard:", err) return c.Status(500).SendString("Error getting leaderboard") } - logger.Println(users) + users_last_week, err := database.TotalDaysPerUser(time.Now().AddDate(0, 0, -7)) + if err != nil { + logger.Println("Error getting leaderboard:", err) + return c.Status(500).SendString("Error getting leaderboard") + } + + for i, user := range users { + for _, user_last_week := range users_last_week { + if user.UserId == user_last_week.UserId { + users[i].PositionChange = user_last_week.Position - user.Position + break + } + } + } + return c.JSON(users) } From 0deb98a7274f1dcba9a82f644e7f3cc700a9bb96 Mon Sep 17 00:00:00 2001 From: Hannes Date: Wed, 17 Jul 2024 03:50:10 +0200 Subject: [PATCH 45/53] vingo: pieter post pieter post pieter post verdient de kost (met zijn post) --- vingo/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vingo/main.go b/vingo/main.go index a150e6c..73e5d8d 100644 --- a/vingo/main.go +++ b/vingo/main.go @@ -34,13 +34,14 @@ func main() { })) // Public routes - public.Get("/login", handlers.Login) + public.Post("/login", handlers.Login) public.Get("/auth/callback", handlers.Callback) public.Post("/scans", handlers.ScanRegister) api := public.Group("/api", handlers.IsLoggedIn) { + api.Post("/logout", handlers.Logout) api.Get("/user", handlers.User) api.Get("/leaderboard", handlers.Leaderboard) api.Get("/scans", handlers.Scans) From 77e6dacc97c32a35cefa7d10f5f42293b0e3d36f Mon Sep 17 00:00:00 2001 From: Topvennie Date: Wed, 17 Jul 2024 14:29:00 +0200 Subject: [PATCH 46/53] vinvoor: pieter post deed ambetant --- vinvoor/src/WelcomePage.tsx | 19 +++++++---- vinvoor/src/components/UnstyledLink.tsx | 4 ++- vinvoor/src/main.tsx | 9 +++-- vinvoor/src/navbar/NavBar.tsx | 29 ++++++++++++---- vinvoor/src/navbar/NavBarLogo.tsx | 2 ++ vinvoor/src/navbar/NavBarPages.tsx | 10 +++--- vinvoor/src/navbar/NavBarSandwich.tsx | 8 +++-- vinvoor/src/navbar/NavBarUserMenu.tsx | 44 +++++++++++-------------- vinvoor/src/user/Login.tsx | 17 ++++++---- vinvoor/src/user/Logout.tsx | 19 +++++++---- 10 files changed, 98 insertions(+), 63 deletions(-) diff --git a/vinvoor/src/WelcomePage.tsx b/vinvoor/src/WelcomePage.tsx index c8cd2f7..e4a7e28 100644 --- a/vinvoor/src/WelcomePage.tsx +++ b/vinvoor/src/WelcomePage.tsx @@ -2,7 +2,7 @@ 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"; +import { Login } from "./user/Login"; declare module "@mui/material/Button" { interface ButtonPropsColorOverrides { @@ -29,12 +29,10 @@ export const WelcomePage = () => { > Welcome to Vinvoor! Log in to start scanning - - - + + Log in with Zauth + + +// diff --git a/vinvoor/src/components/UnstyledLink.tsx b/vinvoor/src/components/UnstyledLink.tsx index 5de2736..d1c160c 100644 --- a/vinvoor/src/components/UnstyledLink.tsx +++ b/vinvoor/src/components/UnstyledLink.tsx @@ -2,5 +2,7 @@ import { FC } from "react"; import { Link, LinkProps } from "react-router-dom"; export const UnstyledLink: FC = (props) => { - return ; + return ( + + ); }; diff --git a/vinvoor/src/main.tsx b/vinvoor/src/main.tsx index 18069a6..6cded09 100644 --- a/vinvoor/src/main.tsx +++ b/vinvoor/src/main.tsx @@ -24,6 +24,10 @@ const router = createBrowserRouter([ element: , errorElement: , children: [ + { + path: "login", + element: , + }, { path: "logout", element: , @@ -38,11 +42,6 @@ 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 be65857..a660fff 100644 --- a/vinvoor/src/navbar/NavBar.tsx +++ b/vinvoor/src/navbar/NavBar.tsx @@ -1,4 +1,6 @@ +import LeaderboardIcon from "@mui/icons-material/Leaderboard"; import { AppBar, Box, Container, Toolbar } from "@mui/material"; +import { CreditCardMultipleOutline } from "mdi-material-ui"; import { useContext } from "react"; import { DarkModeToggle } from "../components/DarkModeToggle"; import { UserContext } from "../user/UserProvider"; @@ -7,8 +9,17 @@ import { NavBarPages } from "./NavBarPages"; import { NavBarSandwich } from "./NavBarSandwich"; import { NavBarUserMenu } from "./NavBarUserMenu"; -const pages = ["Cards", "Leaderboard"]; -const settings = ["Logout"]; +export interface PageIcon { + page: string; + icon: JSX.Element; +} + +const navBarPages: PageIcon[] = [ + { page: "Cards", icon: }, + { page: "Leaderboard", icon: }, +]; + +const userMenuPages: PageIcon[] = []; export const NavBar = () => { const { @@ -21,7 +32,13 @@ export const NavBar = () => { }; return ( - + {/* Display either the ZeSS logo or a sandwich menu */} @@ -31,7 +48,7 @@ export const NavBar = () => { {user && ( )} @@ -42,7 +59,7 @@ export const NavBar = () => { {user && ( )} @@ -54,7 +71,7 @@ export const NavBar = () => { - + diff --git a/vinvoor/src/navbar/NavBarLogo.tsx b/vinvoor/src/navbar/NavBarLogo.tsx index f2986a0..015a46d 100644 --- a/vinvoor/src/navbar/NavBarLogo.tsx +++ b/vinvoor/src/navbar/NavBarLogo.tsx @@ -1,4 +1,5 @@ import { Box, Button, SxProps, Theme, Typography } from "@mui/material"; +import { HexagonSlice6 } from "mdi-material-ui"; import { FC } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; @@ -18,6 +19,7 @@ export const NavBarLogo: FC = ({ sx }) => { color: "white", }} > + ; } -export const NavBarPages: FC = ({ pages, sx }) => { +export const NavBarPages: FC = ({ pageIcons, sx }) => { return ( - {pages.map((page) => ( + {pageIcons.map(({ page, icon }) => ( diff --git a/vinvoor/src/navbar/NavBarSandwich.tsx b/vinvoor/src/navbar/NavBarSandwich.tsx index 9c46319..71c63e2 100644 --- a/vinvoor/src/navbar/NavBarSandwich.tsx +++ b/vinvoor/src/navbar/NavBarSandwich.tsx @@ -10,13 +10,14 @@ import { } from "@mui/material"; import { FC, MouseEvent, useState } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; +import { PageIcon } from "./NavBar"; interface NavBarSandwichProps { - pages: readonly string[]; + pageIcons: readonly PageIcon[]; sx?: SxProps; } -export const NavBarSandwich: FC = ({ pages, sx }) => { +export const NavBarSandwich: FC = ({ pageIcons, sx }) => { const [anchorElNav, setAnchorElNav] = useState( undefined ); @@ -48,9 +49,10 @@ export const NavBarSandwich: FC = ({ pages, sx }) => { open={Boolean(anchorElNav)} onClose={handleCloseNavMenu} > - {pages.map((page) => ( + {pageIcons.map(({ page, icon }) => ( + {icon} {page} diff --git a/vinvoor/src/navbar/NavBarUserMenu.tsx b/vinvoor/src/navbar/NavBarUserMenu.tsx index 8d64df7..0ceb9c8 100644 --- a/vinvoor/src/navbar/NavBarUserMenu.tsx +++ b/vinvoor/src/navbar/NavBarUserMenu.tsx @@ -1,14 +1,18 @@ import { AccountCircle } from "@mui/icons-material"; import { Button, Menu, MenuItem, Typography } from "@mui/material"; +import ExitRun from "mdi-material-ui/ExitRun"; import { FC, MouseEvent, useContext, useState } from "react"; import { UnstyledLink } from "../components/UnstyledLink"; +import { Login } from "../user/Login"; +import { Logout } from "../user/Logout"; import { UserContext } from "../user/UserProvider"; +import { PageIcon } from "./NavBar"; interface NavBarUserMenuProps { - settings: readonly string[]; + pageIcons: readonly PageIcon[]; } -export const NavBarUserMenu: FC = ({ settings }) => { +export const NavBarUserMenu: FC = ({ pageIcons }) => { const { userState: { user }, } = useContext(UserContext); @@ -29,7 +33,6 @@ export const NavBarUserMenu: FC = ({ settings }) => { {user ? ( <> = ({ settings }) => { open={Boolean(anchorElUser)} onClose={handleCloseUserMenu} > - {settings.map((setting) => ( - + {pageIcons.map(({ page, icon }) => ( + - {setting} + {icon} + {page} ))} + + + + Logout + + ) : ( - - - + + Login + )} ); diff --git a/vinvoor/src/user/Login.tsx b/vinvoor/src/user/Login.tsx index 97276ee..6a0ac81 100644 --- a/vinvoor/src/user/Login.tsx +++ b/vinvoor/src/user/Login.tsx @@ -1,11 +1,16 @@ -import { useEffect } from "react"; +import { Button, ButtonProps } from "@mui/material"; +import { FC } from "react"; -export const Login = () => { +export const Login: FC = (props) => { const baseUrl = import.meta.env.VITE_BASE_URL; - useEffect(() => { - window.location.replace(`${baseUrl}/login`); - }, []); + const handleClick = () => { + const form = document.createElement("form"); + form.method = "POST"; + form.action = `${baseUrl}/login`; + document.body.appendChild(form); + form.submit(); + }; - return <>; + return