From fd4c2e9ad62c23d421c0b99556cd4a2efed7f798 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 13 Nov 2023 22:19:33 +0800 Subject: [PATCH 01/47] Added initial landing page --- package-lock.json | 237 ++++++++++++++++++- package.json | 6 +- src/App.tsx | 53 ++--- src/Components/Buttons/Button.tsx | 64 +++++ src/Components/Plugins/Redux/Store/Store.tsx | 12 + src/Components/styles.tsx | 69 ++++++ src/Pages/LandingPage/LandingPage.tsx | 57 +++++ src/assets/citc_logo.jpg | Bin 0 -> 111414 bytes src/main.tsx | 13 +- 9 files changed, 461 insertions(+), 50 deletions(-) create mode 100644 src/Components/Buttons/Button.tsx create mode 100644 src/Components/Plugins/Redux/Store/Store.tsx create mode 100644 src/Components/styles.tsx create mode 100644 src/Pages/LandingPage/LandingPage.tsx create mode 100644 src/assets/citc_logo.jpg diff --git a/package-lock.json b/package-lock.json index 5704692..8b20b51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,12 @@ "name": "equipmenttracker-frontend", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.7", + "@tanstack/react-query": "^5.8.3", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-redux": "^8.1.3", + "react-router-dom": "^6.18.0" }, "devDependencies": { "@types/react": "^18.2.15", @@ -342,6 +346,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -930,6 +945,71 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", + "integrity": "sha512-t7v8ZPxhhKgOKtU+uyJT13lu4vL7az5aFi4IdoDs/eS548edn2M8Ik9h8fxgvMjGoAUVFSt6ZC1P5cWmQ014QQ==", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.11.0.tgz", + "integrity": "sha512-BHdhcWgeiudl91HvVa2wxqZjSHbheSgIiDvxrF1VjFzBzpTtuDPkOdOi3Iqvc08kXtFkLjhbS+ML9aM8mJS+wQ==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.8.3.tgz", + "integrity": "sha512-SWFMFtcHfttLYif6pevnnMYnBvxKf3C+MHMH7bevyYfpXpTMsLB9O6nNGBdWSoPwnZRXFNyNeVZOw25Wmdasow==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.8.3.tgz", + "integrity": "sha512-EDRrsMgUtKM+SjVmhDYBd4jwWWyHAw3kCaurKLIO90OfPQr/UhpwcqM2l/eQOaUYon9lfDB2ejQi1edHK7zEdA==", + "dependencies": { + "@tanstack/query-core": "5.8.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0", + "react-native": "*" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/@types/babel__core": { "version": "7.20.3", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.3.tgz", @@ -971,6 +1051,15 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.14", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", @@ -980,14 +1069,12 @@ "node_modules/@types/prop-types": { "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", - "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==", - "dev": true + "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" }, "node_modules/@types/react": { "version": "18.2.31", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.31.tgz", "integrity": "sha512-c2UnPv548q+5DFh03y8lEDeMfDwBn9G3dRwfkrxQMo/dOtRHUUO57k6pHvBIfH/VF4Nh+98mZ5aaSe+2echD5g==", - "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -998,7 +1085,7 @@ "version": "18.2.14", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } @@ -1006,8 +1093,7 @@ "node_modules/@types/scheduler": { "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", - "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==", - "dev": true + "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" }, "node_modules/@types/semver": { "version": "7.5.4", @@ -1015,6 +1101,11 @@ "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz", @@ -1449,8 +1540,7 @@ "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/debug": { "version": "4.3.4", @@ -2040,6 +2130,19 @@ "node": ">=4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2049,6 +2152,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2538,6 +2650,49 @@ "react": "^18.2.0" } }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, + "node_modules/react-redux": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -2547,6 +2702,62 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.18.0.tgz", + "integrity": "sha512-vk2y7Dsy8wI02eRRaRmOs9g2o+aE72YCx5q9VasT1N9v+lrdB79tIqrjMfByHiY5+6aYkH2rUa5X839nwWGPDg==", + "dependencies": { + "@remix-run/router": "1.11.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.18.0.tgz", + "integrity": "sha512-Ubrue4+Ercc/BoDkFQfc6og5zRQ4A8YxSO3Knsne+eRbZ+IepAsK249XBH/XaFuOYOYr3L3r13CXTLvYt5JDjw==", + "dependencies": { + "@remix-run/router": "1.11.0", + "react-router": "6.18.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2851,6 +3062,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/vite": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz", diff --git a/package.json b/package.json index d2ffc90..46cfb72 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,12 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^1.9.7", + "@tanstack/react-query": "^5.8.3", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-redux": "^8.1.3", + "react-router-dom": "^6.18.0" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/src/App.tsx b/src/App.tsx index afe48ac..0626e10 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,35 +1,26 @@ -import { useState } from 'react' -import reactLogo from './assets/react.svg' -import viteLogo from '/vite.svg' -import './App.css' +import LandingPage from "./Pages/LandingPage/LandingPage"; +import { createHashRouter, RouterProvider } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Provider } from "react-redux"; +import "./App.css"; +import store from "./Components/Plugins/Redux/Store/Store"; -function App() { - const [count, setCount] = useState(0) +const queryClient = new QueryClient(); +const router = createHashRouter([ + { + path: "/", + element: , + errorElement: <>, + }, +]); + +export default function App() { return ( - <> -
- - Vite logo - - - React logo - -
-

Vite + React

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

- - ) + + + + + + ); } - -export default App diff --git a/src/Components/Buttons/Button.tsx b/src/Components/Buttons/Button.tsx new file mode 100644 index 0000000..2ab15c5 --- /dev/null +++ b/src/Components/Buttons/Button.tsx @@ -0,0 +1,64 @@ +import React, { useState } from "react"; +import styles from "../styles"; +import { colors } from "../styles"; + +export interface props { + onClick: React.MouseEventHandler; + label?: string; + type: "light" | "dark"; + children?: React.ReactNode; +} +export default function Button(props: props) { + const [clicked, setClicked] = useState(false); + return ( +
+ +
+ ); +} diff --git a/src/Components/Plugins/Redux/Store/Store.tsx b/src/Components/Plugins/Redux/Store/Store.tsx new file mode 100644 index 0000000..8b8a1ec --- /dev/null +++ b/src/Components/Plugins/Redux/Store/Store.tsx @@ -0,0 +1,12 @@ +import { configureStore } from "@reduxjs/toolkit"; + +const store = configureStore({ + reducer: {}, +}); + +export default store; + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType; +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch; diff --git a/src/Components/styles.tsx b/src/Components/styles.tsx new file mode 100644 index 0000000..b2c5ef0 --- /dev/null +++ b/src/Components/styles.tsx @@ -0,0 +1,69 @@ +export const colors = { + background: "#FFFFFF", + header_color: "#141762", + font_dark: "#141762", + font_light: "#FFFFFF", + button_dark: "#141762", + button_light: "#FFFFFF", + button_border: "#141762", + red: "#a44141", + orange: "#c57331", + green: "#80b28a", +}; +const styles: { [key: string]: React.CSSProperties } = { + background: { + backgroundColor: colors.background, + position: "fixed", + top: 0, + left: 0, + height: "100%", + width: "100%", + minHeight: "100%", + minWidth: "100%", + }, + text_dark: { + color: colors.font_dark, + fontWeight: "bold", + }, + text_light: { + color: colors.font_light, + fontWeight: "bold", + }, + text_red: { + color: colors.red, + fontWeight: "bold", + }, + text_orange: { + color: colors.orange, + fontWeight: "bold", + }, + text_green: { + color: colors.green, + fontWeight: "bold", + }, + text_XL: { + fontSize: "clamp(1vw, 4rem, 2vw)", + }, + text_L: { + fontSize: "clamp(1vw, 2rem, 2vw)", + }, + text_M: { + fontSize: "clamp(1vw, 1rem, 2vw)", + }, + text_S: { + fontSize: "clamp(1vw, 0.5rem, 2vw)", + }, + text_XS: { + fontSize: "clamp(1vw, 0.2rem, 2vw)", + }, + flex_row: { + display: "flex", + flexDirection: "row", + }, + flex_column: { + display: "flex", + flexDirection: "column", + }, +}; + +export default styles; diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx new file mode 100644 index 0000000..3aca3b6 --- /dev/null +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -0,0 +1,57 @@ +import Button from "../../Components/Buttons/Button"; +import styles from "../../Components/styles"; +import citc_logo from "../../assets/citc_logo.jpg"; +export default function LandingPage() { + return ( +
+
+
+ +
+
+
+

+ CITC EQUIPMENT +
+ TRACKER +

+
+
+
+
+
+

heh

+
+ ); +} diff --git a/src/assets/citc_logo.jpg b/src/assets/citc_logo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a2a23c8be8f7660702b34d313d635375c948089d GIT binary patch literal 111414 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
(s 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

  • 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 literal 0 HcmV?d00001 diff --git a/src/main.tsx b/src/main.tsx index 3d7150d..eb0a604 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,10 +1,5 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import App from './App.tsx' -import './index.css' +import ReactDOM from "react-dom/client"; +import App from "./App.tsx"; +import "./index.css"; -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) +ReactDOM.createRoot(document.getElementById("root")!).render(); From 0cccaf25f11fc0f5809f2a23f7cffcadeca6fe76 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 13 Nov 2023 22:46:14 +0800 Subject: [PATCH 02/47] Added toast notifications --- package-lock.json | 23 ++++++++++++++++++++++- package.json | 3 ++- src/App.tsx | 13 +++++++++++-- src/Components/Buttons/Button.tsx | 4 ++-- src/Pages/LandingPage/LandingPage.tsx | 18 +++++++++++++++--- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b20b51..2681584 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.18.0", + "react-toastify": "^9.1.3" }, "devDependencies": { "@types/react": "^18.2.15", @@ -1496,6 +1497,14 @@ "node": ">=4" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2732,6 +2741,18 @@ "react-dom": ">=16.8" } }, + "node_modules/react-toastify": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-9.1.3.tgz", + "integrity": "sha512-fPfb8ghtn/XMxw3LkxQBk3IyagNpF/LIKjOBflbexr2AWxAH1MJgvnESwEwBn9liLFXgTKWgBSdZpw9m4OTHTg==", + "dependencies": { + "clsx": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", diff --git a/package.json b/package.json index 46cfb72..1074f9b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.18.0", + "react-toastify": "^9.1.3" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/src/App.tsx b/src/App.tsx index 0626e10..69770e8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,9 +4,9 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Provider } from "react-redux"; import "./App.css"; import store from "./Components/Plugins/Redux/Store/Store"; - const queryClient = new QueryClient(); - +import { ToastContainer } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; const router = createHashRouter([ { path: "/", @@ -21,6 +21,15 @@ export default function App() { + ); } diff --git a/src/Components/Buttons/Button.tsx b/src/Components/Buttons/Button.tsx index 2ab15c5..1320062 100644 --- a/src/Components/Buttons/Button.tsx +++ b/src/Components/Buttons/Button.tsx @@ -13,10 +13,10 @@ export default function Button(props: props) { return (
    -

    heh

    ); } From 299f0a960041769f666fc610adb93c9cffd084c5 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 13 Nov 2023 23:55:36 +0800 Subject: [PATCH 03/47] Added initial login modal --- package-lock.json | 826 ++++++++++++++++++++++- package.json | 9 +- src/App.css | 36 - src/App.tsx | 1 + src/Components/Buttons/Button.tsx | 4 +- src/Components/LoginModal/LoginModal.tsx | 77 +++ src/Pages/LandingPage/LandingPage.tsx | 28 +- src/styles.css | 20 + src/{Components => }/styles.tsx | 0 tsconfig.json | 6 +- 10 files changed, 939 insertions(+), 68 deletions(-) create mode 100644 src/Components/LoginModal/LoginModal.tsx create mode 100644 src/styles.css rename src/{Components => }/styles.tsx (100%) diff --git a/package-lock.json b/package-lock.json index 2681584..c61ba6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,20 @@ "name": "equipmenttracker-frontend", "version": "0.0.0", "dependencies": { + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.17", + "@mui/styled-engine": "^5.14.17", + "@mui/styled-engine-sc": "^6.0.0-alpha.5", "@reduxjs/toolkit": "^1.9.7", "@tanstack/react-query": "^5.8.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", "react-router-dom": "^6.18.0", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "reactjs-popup": "^2.0.6", + "styled-components": "^6.1.1" }, "devDependencies": { "@types/react": "^18.2.15", @@ -55,7 +62,6 @@ "version": "7.22.13", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", - "dev": true, "dependencies": { "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" @@ -190,7 +196,6 @@ "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", - "dev": true, "dependencies": { "@babel/types": "^7.22.15" }, @@ -254,7 +259,6 @@ "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -263,7 +267,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -295,7 +298,6 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", @@ -397,7 +399,6 @@ "version": "7.23.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", - "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -407,6 +408,156 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz", + "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" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/cache": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.11.0.tgz", + "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" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz", + "integrity": "sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/react": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.11.1.tgz", + "integrity": "sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/cache": "^11.11.0", + "@emotion/serialize": "^1.1.2", + "@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" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.2.tgz", + "integrity": "sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==", + "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" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.2.tgz", + "integrity": "sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==" + }, + "node_modules/@emotion/styled": { + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.11.0.tgz", + "integrity": "sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.11.0", + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/serialize": "^1.1.2", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.1", + "@emotion/utils": "^1.2.1" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz", + "integrity": "sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.1.tgz", + "integrity": "sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz", + "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==" + }, "node_modules/@esbuild/android-arm": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", @@ -830,6 +981,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -911,6 +1096,296 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.23", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.23.tgz", + "integrity": "sha512-9L8SQUGAWtd/Qi7Qem26+oSSgpY7f2iQTuvcz/rsGpyZjSomMMO6lwYeQSA0CpWM7+aN7eGoSY/WV6wxJiIxXw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@floating-ui/react-dom": "^2.0.2", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@popperjs/core": "^2.11.8", + "clsx": "^2.0.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.17.tgz", + "integrity": "sha512-eE0uxrpJAEL2ZXkeGLKg8HQDafsiXY+6eNpP4lcv3yIjFfGbU6Hj9/P7Adt8jpU+6JIhmxvILGj2r27pX+zdrQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.14.16", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.14.16.tgz", + "integrity": "sha512-wmOgslMEGvbHZjFLru8uH5E+pif/ciXAvKNw16q6joK6EWVWU5rDYWFknDaZhCvz8ZE/K8ZnJQ+lMG6GgHzXbg==", + "dependencies": { + "@babel/runtime": "^7.23.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.17.tgz", + "integrity": "sha512-+y0VeOLWfEA4Z98We/UH6KCo8+f2HLZDK45FY+sJf8kSojLy3VntadKtC/u0itqnXXb1Pr4wKB2tSIBW02zY4Q==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/base": "5.0.0-beta.23", + "@mui/core-downloads-tracker": "^5.14.17", + "@mui/system": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "@types/react-transition-group": "^4.4.8", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1", + "react-is": "^18.2.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/private-theming": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.17.tgz", + "integrity": "sha512-u4zxsCm9xmQrlhVPug+Ccrtsjv7o2+rehvrgHoh0siSguvVgVQq5O3Hh10+tp/KWQo2JR4/nCEwquSXgITS1+g==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/utils": "^5.14.17", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.17.tgz", + "integrity": "sha512-AqpVjBEA7wnBvKPW168bNlqB6EN7HxTjLOY7oi275AzD/b1C7V0wqELy6NWoJb2yya5sRf7ENf4iNi3+T5cOgw==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine-sc": { + "version": "6.0.0-alpha.5", + "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-6.0.0-alpha.5.tgz", + "integrity": "sha512-LbhqyaAjjwMnJ5ws2ZRAnGoxnnDUawJ7DfYWhF8DbzNVqQWNG+XaNvf+xwPvhW60cQfmLNnYi45YjDIbFWew9A==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "csstype": "^3.1.2", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "styled-components": "^6.0.0" + } + }, + "node_modules/@mui/system": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.17.tgz", + "integrity": "sha512-Ccz3XlbCqka6DnbHfpL3o3TfOeWQPR+ewvNAgm8gnS9M0yVMmzzmY6z0w/C1eebb+7ZP7IoLUj9vojg/GBaTPg==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@mui/private-theming": "^5.14.17", + "@mui/styled-engine": "^5.14.17", + "@mui/types": "^7.2.8", + "@mui/utils": "^5.14.17", + "clsx": "^2.0.0", + "csstype": "^3.1.2", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/system/node_modules/clsx": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", + "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@mui/types": { + "version": "7.2.8", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.8.tgz", + "integrity": "sha512-9u0ji+xspl96WPqvrYJF/iO+1tQ1L5GTaDOeG3vCR893yy7VcWwRNiVMmPdPNpMDqx0WV1wtEW9OMwK9acWJzQ==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.14.17", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.17.tgz", + "integrity": "sha512-yxnWgSS4J6DMFPw2Dof85yBkG02VTbEiqsikymMsnZnXDurtVGTIhlNuV24GTmFTuJMzEyTTU9UF+O7zaL8LEQ==", + "dependencies": { + "@babel/runtime": "^7.23.2", + "@types/prop-types": "^15.7.9", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -946,6 +1421,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@reduxjs/toolkit": { "version": "1.9.7", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.7.tgz", @@ -1067,6 +1551,11 @@ "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/prop-types": { "version": "15.7.9", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", @@ -1091,6 +1580,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.9.tgz", + "integrity": "sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/scheduler": { "version": "0.16.5", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", @@ -1102,6 +1599,11 @@ "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, + "node_modules/@types/stylis": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.3.tgz", + "integrity": "sha512-86XLCVEmWagiUEbr2AjSbeY4qHN9jMm3pgM3PuBYfLIbT0MpDSnA3GA/4W7KoH/C/eeK77kNaeIxZzjhKYIBgw==" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", @@ -1371,7 +1873,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -1394,6 +1895,20 @@ "node": ">=8" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1458,11 +1973,18 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001551", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz", @@ -1487,7 +2009,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -1509,7 +2030,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -1517,8 +2037,7 @@ "node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, "node_modules/concat-map": { "version": "0.0.1", @@ -1532,6 +2051,21 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "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" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1546,6 +2080,24 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/csstype": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", @@ -1598,12 +2150,29 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.563", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.563.tgz", "integrity": "sha512-dg5gj5qOgfZNkPNeyKBZQAQitIQ/xwfIDmEQJHCbXaD9ebTZxwJXUsDYcBlAvZGZLi+/354l35J1wkmP6CqYaw==", "dev": true }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/esbuild": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", @@ -1654,7 +2223,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -1998,6 +2566,11 @@ "node": ">=8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2054,6 +2627,14 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2134,11 +2715,21 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, "engines": { "node": ">=4" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -2174,7 +2765,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -2211,6 +2801,22 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2291,6 +2897,11 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2337,6 +2948,11 @@ "node": ">= 0.8.0" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2422,7 +3038,6 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, "funding": [ { "type": "github", @@ -2448,6 +3063,14 @@ "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2508,7 +3131,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -2516,6 +3138,23 @@ "node": ">=6" } }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2543,11 +3182,15 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -2555,8 +3198,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -2574,7 +3216,6 @@ "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "dev": true, "funding": [ { "type": "opencollective", @@ -2598,6 +3239,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2607,6 +3253,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -2753,6 +3414,33 @@ "react-dom": ">=16" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/reactjs-popup": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.6.tgz", + "integrity": "sha512-A+tt+x9wdgZiZjv0e2WzYLD3IfFwJALaRaqwrCSXGjo0iQdsry/EtBEbQXRSmQs7cHmOi5eytCiSlOm8k4C+dg==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/redux": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", @@ -2779,11 +3467,26 @@ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -2893,6 +3596,11 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -2923,11 +3631,18 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2956,11 +3671,47 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.1.tgz", + "integrity": "sha512-cpZZP5RrKRIClBW5Eby4JM1wElLVP4NQrJbJ0h10TidTyJf4SIIwa3zLXOoPb4gJi8MsJ8mjq5mu2IrEhZIAcQ==", + "dependencies": { + "@emotion/is-prop-valid": "^1.2.1", + "@emotion/unitless": "^0.8.0", + "@types/stylis": "^4.0.2", + "css-to-react-native": "^3.2.0", + "csstype": "^3.1.2", + "postcss": "^8.4.31", + "shallowequal": "^1.1.0", + "stylis": "^4.3.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.0.tgz", + "integrity": "sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ==" + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -2968,6 +3719,17 @@ "node": ">=4" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2978,7 +3740,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, "engines": { "node": ">=4" } @@ -3007,6 +3768,11 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3173,6 +3939,14 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 1074f9b..252a37e 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,20 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/styled": "^11.11.0", + "@mui/icons-material": "^5.14.16", + "@mui/material": "^5.14.17", + "@mui/styled-engine": "^5.14.17", + "@mui/styled-engine-sc": "^6.0.0-alpha.5", "@reduxjs/toolkit": "^1.9.7", "@tanstack/react-query": "^5.8.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", "react-router-dom": "^6.18.0", - "react-toastify": "^9.1.3" + "react-toastify": "^9.1.3", + "reactjs-popup": "^2.0.6", + "styled-components": "^6.1.1" }, "devDependencies": { "@types/react": "^18.2.15", diff --git a/src/App.css b/src/App.css index b9d355d..902778b 100644 --- a/src/App.css +++ b/src/App.css @@ -4,39 +4,3 @@ padding: 2rem; text-align: center; } - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/src/App.tsx b/src/App.tsx index 69770e8..2f1b2ae 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import store from "./Components/Plugins/Redux/Store/Store"; const queryClient = new QueryClient(); import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; +import "./styles.css"; const router = createHashRouter([ { path: "/", diff --git a/src/Components/Buttons/Button.tsx b/src/Components/Buttons/Button.tsx index 1320062..aed3496 100644 --- a/src/Components/Buttons/Button.tsx +++ b/src/Components/Buttons/Button.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; -import styles from "../styles"; -import { colors } from "../styles"; +import styles from "../../styles"; +import { colors } from "../../styles"; export interface props { onClick: React.MouseEventHandler; diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx new file mode 100644 index 0000000..598f1d9 --- /dev/null +++ b/src/Components/LoginModal/LoginModal.tsx @@ -0,0 +1,77 @@ +import { useState } from "react"; +import styles from "../../styles"; +import TextField from "@mui/material/TextField"; +import OutlinedInput from "@mui/material/OutlinedInput"; +import InputAdornment from "@mui/material/InputAdornment"; +import IconButton from "@mui/material/IconButton"; +import Visibility from "@mui/icons-material/Visibility"; +import VisibilityOff from "@mui/icons-material/VisibilityOff"; + +export default function LoginModal() { + const [showPassword, setShowPassword] = useState(false); + const [user, setUser] = useState({ + username: "", + password: "", + }); + return ( + <> +

    Welcome back!

    +
    + ) => + setUser({ ...user, username: e.target.value }) + } + value={user.username} + placeholder={"Enter username"} + /> + + setShowPassword(!showPassword)} + edge="end" + > + {showPassword ? : } + + + ), + }} + label="Password" + placeholder={"Enter password"} + onChange={(e: React.ChangeEvent) => + setUser({ ...user, password: e.target.value }) + } + value={user.password} + /> +
    + + ); +} diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index 4830129..ff6d199 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -1,8 +1,13 @@ import Button from "../../Components/Buttons/Button"; -import styles from "../../Components/styles"; +import styles from "../../styles"; import citc_logo from "../../assets/citc_logo.jpg"; import { toast } from "react-toastify"; +import Popup from "reactjs-popup"; +import "reactjs-popup/dist/index.css"; +import { useState } from "react"; +import LoginModal from "../../Components/LoginModal/LoginModal"; export default function LandingPage() { + const [open, setOpen] = useState(false); return (
    { - toast("Successfully logged in!"); + setOpen(!open); }} />
    diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..b07fb83 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,20 @@ +@keyframes anvil { + 0% { + transform: scale(1) translateY(0px); + opacity: 0; + box-shadow: 0 0 0 rgba(241, 241, 241, 0); + } + 1% { + transform: scale(0.96) translateY(10px); + opacity: 0; + box-shadow: 0 0 0 rgba(241, 241, 241, 0); + } + 100% { + transform: scale(1) translateY(0px); + opacity: 1; + box-shadow: 0 0 500px rgba(241, 241, 241, 0); + } +} +.popup-content { + -webkit-animation: anvil 0.3s cubic-bezier(0.38, 0.1, 0.36, 0.9) forwards; +} diff --git a/src/Components/styles.tsx b/src/styles.tsx similarity index 100% rename from src/Components/styles.tsx rename to src/styles.tsx diff --git a/tsconfig.json b/tsconfig.json index a7fc6fb..723f5e0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,11 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + + "paths":{ + "@mui/styled-engine": ["./node_modules/@mui/styled-engine-sc"] + } }, "include": ["src"], "references": [{ "path": "./tsconfig.node.json" }] From 6ece1f6a013126208c21f0c41ef8a4802aba0f66 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 13 Nov 2023 23:57:25 +0800 Subject: [PATCH 04/47] Code cleanup --- src/Components/LoginModal/LoginModal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index 598f1d9..3aea8a2 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import styles from "../../styles"; import TextField from "@mui/material/TextField"; -import OutlinedInput from "@mui/material/OutlinedInput"; import InputAdornment from "@mui/material/InputAdornment"; import IconButton from "@mui/material/IconButton"; import Visibility from "@mui/icons-material/Visibility"; From 95bdb998030c60378a89dbd0eb78fde06b0f573c Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Tue, 14 Nov 2023 00:59:32 +0800 Subject: [PATCH 05/47] Code cleanup and add login icon to login modal --- src/Components/Buttons/Button.tsx | 4 +- src/Components/LoginModal/LoginModal.tsx | 88 +++++++++++++++++------- src/styles.tsx | 12 ++++ 3 files changed, 78 insertions(+), 26 deletions(-) diff --git a/src/Components/Buttons/Button.tsx b/src/Components/Buttons/Button.tsx index aed3496..e5a4e76 100644 --- a/src/Components/Buttons/Button.tsx +++ b/src/Components/Buttons/Button.tsx @@ -27,8 +27,8 @@ export default function Button(props: props) { borderColor: colors.button_border, borderStyle: "solid", borderWidth: 2, - paddingBottom: "2vh", - paddingTop: "2vh", + paddingBottom: "0", + paddingTop: "0", paddingRight: "5vw", paddingLeft: "5vw", marginBottom: "0.5vh", diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index 3aea8a2..4ef16a4 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -1,35 +1,44 @@ import { useState } from "react"; import styles from "../../styles"; +import { colors } from "../../styles"; import TextField from "@mui/material/TextField"; import InputAdornment from "@mui/material/InputAdornment"; import IconButton from "@mui/material/IconButton"; import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; - +import LoginIcon from "@mui/icons-material/Login"; +import Checkbox from "@mui/material/Checkbox"; +import Button from "../Buttons/Button"; export default function LoginModal() { const [showPassword, setShowPassword] = useState(false); + const [remember, setRemember] = useState(true); const [user, setUser] = useState({ username: "", password: "", }); return ( <> -

    Welcome back!

    +
    + +

    Welcome back!

    +
    +
    ) => setUser({ ...user, username: e.target.value }) } @@ -39,17 +48,7 @@ export default function LoginModal() { @@ -70,7 +69,48 @@ export default function LoginModal() { } value={user.password} /> +
    +
    + setRemember(!remember)} + /> +

    Remember me

    +
    +
    +

    + Forgot password? +

    +
    +
    +
    +
    From 9c4a5fd4fba1fb20638ed3f2117cea95b8c583b6 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 17 Nov 2023 17:16:43 +0800 Subject: [PATCH 09/47] Added initial dashboard and error pages --- src/App.tsx | 12 ++++++++++-- src/Pages/DashboardPage/DashboardPage.tsx | 13 +++++++++++++ src/Pages/ErrorPage/ErrorPage.tsx | 3 +++ 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 src/Pages/DashboardPage/DashboardPage.tsx create mode 100644 src/Pages/ErrorPage/ErrorPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 69770e8..471ad7c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,14 +4,22 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { Provider } from "react-redux"; import "./App.css"; import store from "./Components/Plugins/Redux/Store/Store"; -const queryClient = new QueryClient(); import { ToastContainer } from "react-toastify"; import "react-toastify/dist/ReactToastify.css"; +import ErrorPage from "./Pages/ErrorPage/ErrorPage"; +import DashboardPage from "./Pages/DashboardPage/DashboardPage"; + +const queryClient = new QueryClient(); const router = createHashRouter([ { path: "/", element: , - errorElement: <>, + errorElement: , + }, + { + path: "/dashboard", + element: , + errorElement: , }, ]); diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx new file mode 100644 index 0000000..15c4f96 --- /dev/null +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -0,0 +1,13 @@ +import styles from "../../styles"; + +export default function Dashboard() { + return ( +
    +

    + CITC EQUIPMENT +
    + TRACKER +

    +
    + ); +} diff --git a/src/Pages/ErrorPage/ErrorPage.tsx b/src/Pages/ErrorPage/ErrorPage.tsx new file mode 100644 index 0000000..5045ac2 --- /dev/null +++ b/src/Pages/ErrorPage/ErrorPage.tsx @@ -0,0 +1,3 @@ +export default function ErrorPage() { + return
    {"ErrorPage"}
    ; +} From 2b5fe8171c0ba912aaa04c526ed0bf24e4eae0ee Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Fri, 17 Nov 2023 17:23:53 +0800 Subject: [PATCH 10/47] Added initial header --- src/Components/Header/Header.tsx | 23 +++++++++++++++++++++++ src/Components/LoginModal/LoginModal.tsx | 2 +- src/Pages/DashboardPage/DashboardPage.tsx | 8 +++----- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 src/Components/Header/Header.tsx diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx new file mode 100644 index 0000000..166208d --- /dev/null +++ b/src/Components/Header/Header.tsx @@ -0,0 +1,23 @@ +import styles, { colors } from "../../styles"; + +export interface props { + label: React.ReactNode; +} + +export default function Header(props: props) { + return ( +
    +

    {props.label}

    +
    + ); +} diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index 3cf8c5a..48f16df 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -116,7 +116,7 @@ export default function LoginModal() { type={"dark"} label={"Login"} onClick={() => { - navigate(0); + navigate("/dashboard"); }} /> diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 15c4f96..72f9e61 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -1,13 +1,11 @@ +import Header from "../../Components/Header/Header"; import styles from "../../styles"; export default function Dashboard() { return (
    -

    - CITC EQUIPMENT -
    - TRACKER -

    +
    +

    Dashboard Page

    ); } From 0f6407434530034c666a588637a02ded591a1ca6 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 14:50:39 +0800 Subject: [PATCH 11/47] Added initial popup sidebar --- src/App.tsx | 2 +- src/Components/Header/Header.tsx | 34 +++++++++- src/Components/SidebarModal/SidebarModal.tsx | 65 ++++++++++++++++++++ src/Pages/LandingPage/LandingPage.tsx | 4 +- src/styles.tsx | 10 +-- 5 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 src/Components/SidebarModal/SidebarModal.tsx diff --git a/src/App.tsx b/src/App.tsx index 471ad7c..d25be6c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -30,8 +30,8 @@ export default function App() { -

    {props.label}

    +
    + { + SetSidebarOpen(true); + }} + /> +
    +

    + {props.label} +

    +
    + SetSidebarOpen(false)}> + +
    ); } diff --git a/src/Components/SidebarModal/SidebarModal.tsx b/src/Components/SidebarModal/SidebarModal.tsx new file mode 100644 index 0000000..0a2d4ed --- /dev/null +++ b/src/Components/SidebarModal/SidebarModal.tsx @@ -0,0 +1,65 @@ +import styles, { colors } from "../../styles"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; +import HomeIcon from "@mui/icons-material/Home"; +export default function SidebarModal() { + return ( +
    +
    + +

    + Placeholder Name +

    +
    +
    +
    + +

    + Dashboard +

    +
    +
    + ); +} diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index c68e5f0..eee369c 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -74,7 +74,7 @@ export default function LandingPage() { modal position={"top center"} contentStyle={{ - width: "30vw", + width: "512px", borderRadius: 16, borderColor: "grey", borderStyle: "solid", @@ -93,7 +93,7 @@ export default function LandingPage() { modal position={"top center"} contentStyle={{ - width: "30vw", + width: "512px", borderRadius: 16, borderColor: "grey", borderStyle: "solid", diff --git a/src/styles.tsx b/src/styles.tsx index 61d5b75..4a8f257 100644 --- a/src/styles.tsx +++ b/src/styles.tsx @@ -42,19 +42,19 @@ const styles: { [key: string]: React.CSSProperties } = { fontWeight: "bold", }, text_XL: { - fontSize: "clamp(2rem, 4rem, 8rem)", + fontSize: "clamp(2rem, 3rem, 8rem)", }, text_L: { - fontSize: "clamp(1.5rem, 3rem, 6rem)", + fontSize: "clamp(1.5rem, 2rem, 6rem)", }, text_M: { - fontSize: "clamp(1rem, 2rem, 4rem)", + fontSize: "clamp(1rem, 1rem, 4rem)", }, text_S: { - fontSize: "clamp(0.5rem, 1rem, 2rem)", + fontSize: "clamp(0.6rem, 0.8rem, 1rem)", }, text_XS: { - fontSize: "clamp(0.2rem, 0.5rem, 1rem)", + fontSize: "clamp(0.5rem, 0.6rem, 0.8rem)", }, flex_row: { display: "flex", From 2aeb1ddd2738cee9d1ee64f3363bdca8b1a241df Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 15:44:11 +0800 Subject: [PATCH 12/47] Changed web app title --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index e4b78ea..6010514 100644 --- a/index.html +++ b/index.html @@ -1,10 +1,10 @@ - + - Vite + React + TS + CITC Equipment Tracker
    From 508e353fdb29f9bb75b8f01c9cebdbaef601b2b1 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 16:10:36 +0800 Subject: [PATCH 13/47] Added login functionality --- package-lock.json | 91 +++++++++++++++++++ package.json | 1 + src/Components/API/API.tsx | 67 ++++++++++++++ src/Components/Header/Header.tsx | 17 +++- src/Components/LoginModal/LoginModal.tsx | 28 ++++-- .../Redux/Slices/AuthSlice/AuthSlice.tsx | 19 ++++ src/Components/Plugins/Redux/Store/Store.tsx | 5 +- src/Components/Types/Types.tsx | 20 ++++ src/Pages/LandingPage/LandingPage.tsx | 14 ++- 9 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 src/Components/API/API.tsx create mode 100644 src/Components/Plugins/Redux/Slices/AuthSlice/AuthSlice.tsx create mode 100644 src/Components/Types/Types.tsx diff --git a/package-lock.json b/package-lock.json index c61ba6c..733d993 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@mui/styled-engine-sc": "^6.0.0-alpha.5", "@reduxjs/toolkit": "^1.9.7", "@tanstack/react-query": "^5.8.3", + "axios": "^1.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", @@ -1895,6 +1896,21 @@ "node": ">=8" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2039,6 +2055,17 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2126,6 +2153,14 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -2607,6 +2642,38 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3016,6 +3083,25 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3268,6 +3354,11 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", diff --git a/package.json b/package.json index 252a37e..57b26fb 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@mui/styled-engine-sc": "^6.0.0-alpha.5", "@reduxjs/toolkit": "^1.9.7", "@tanstack/react-query": "^5.8.3", + "axios": "^1.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx new file mode 100644 index 0000000..b92b7f2 --- /dev/null +++ b/src/Components/API/API.tsx @@ -0,0 +1,67 @@ +import axios from "axios"; +import { ActivationType, LoginType, RegisterType } from "../Types/Types"; + +// Product APIs +const instance = axios.create({ + baseURL: "http://localhost:8000/", +}); + +// User APIs + +export function RegisterAPI(register: RegisterType) { + return instance + .post("api/v1/accounts/users/", register) + .then(async (response) => { + console.log(response.data); + return true; + }) + .catch(() => { + console.log("Registration failed"); + return false; + }); +} + +export function LoginAPI(user: LoginType) { + return instance + .post("api/v1/accounts/jwt/create/", user) + .then(async (response) => { + localStorage.setItem("token", JSON.stringify(response.data.auth_token)); + console.log("Login Success "); + return true; + }) + .catch(() => { + console.log("Login Failed"); + return false; + }); +} + +export function UserAPI() { + const token = JSON.parse(localStorage.getItem("token") || "{}"); + return instance + .get("api/v1/accounts/users/me/", { + headers: { + Authorization: "Token " + token, + }, + }) + .then((response) => { + console.log(response.data); + return response.data; + }) + .catch(() => { + console.log("Error retrieving user data"); + return false; + }); +} + +export function ActivationAPI(activation: ActivationType) { + return instance + .post("api/v1/accounts/users/activation/", activation) + .then(async () => { + console.log("Activation Success"); + return true; + }) + .catch(() => { + console.log("Activation failed"); + return false; + }); +} diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index e71b017..424e57d 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,15 +1,28 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import styles, { colors } from "../../styles"; import MenuIcon from "@mui/icons-material/Menu"; import SidebarModal from "../SidebarModal/SidebarModal"; import { Drawer } from "@mui/material"; +import { useSelector } from "react-redux"; +import { RootState } from "../Plugins/Redux/Store/Store"; +import { useNavigate } from "react-router-dom"; export interface props { - label: React.ReactNode; + label: string; } export default function Header(props: props) { const [SidebarOpen, SetSidebarOpen] = useState(false); + const authenticated = useSelector((state: RootState) => state.auth.value); + const navigate = useNavigate(); + + useEffect(() => { + if (!authenticated) { + navigate("/"); + console.log("Not logged in. Redirecting to landing page"); + } + }, [authenticated, navigate]); + return (
    ) => - setUser({ ...user, username: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, username: e.target.value }); + setError(""); + }} value={user.username} placeholder={"Enter username"} /> @@ -56,7 +62,10 @@ export default function LoginModal() { setShowPassword(!showPassword)} + onClick={() => { + setShowPassword(!showPassword); + setError(""); + }} edge="end" > {showPassword ? : } @@ -112,11 +121,18 @@ export default function LoginModal() { marginBottom: 8, }} /> +

    {error}

    +
    + ); +} diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index b650641..b36f9c4 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import styles, { colors } from "../../styles"; import MenuIcon from "@mui/icons-material/Menu"; -import SidebarModal from "../SidebarModal/SidebarModal"; +import SidebarModal from "../Sidebar/Sidebar"; import { Drawer } from "@mui/material"; export interface props { label: string; diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index e570756..50a8507 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -8,11 +8,12 @@ import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import LoginIcon from "@mui/icons-material/Login"; import Checkbox from "@mui/material/Checkbox"; -import Button from "../Buttons/Button"; +import Button from "../Button/Button"; import { useNavigate } from "react-router-dom"; import { LoginAPI } from "../API/API"; import { useDispatch } from "react-redux"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; +import { toast } from "react-toastify"; export default function LoginModal() { const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); @@ -129,8 +130,17 @@ export default function LoginModal() { const status = await LoginAPI(user, remember_session); if (status === true) { await dispatch(auth_toggle()); - navigate("/dashboard"); + toast("Logged in", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } else { setError("Invalid login"); } diff --git a/src/Components/RegisterModal/RegisterModal.tsx b/src/Components/RegisterModal/RegisterModal.tsx index 80157e7..fad4327 100644 --- a/src/Components/RegisterModal/RegisterModal.tsx +++ b/src/Components/RegisterModal/RegisterModal.tsx @@ -7,11 +7,13 @@ import IconButton from "@mui/material/IconButton"; import Visibility from "@mui/icons-material/Visibility"; import VisibilityOff from "@mui/icons-material/VisibilityOff"; import { AppRegistration } from "@mui/icons-material"; -import Button from "../Buttons/Button"; +import Button from "../Button/Button"; import { useNavigate } from "react-router-dom"; +import { RegisterAPI } from "../API/API"; export default function RegisterModal() { const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); + const [user, setUser] = useState({ first_name: "", last_name: "", @@ -20,6 +22,7 @@ export default function RegisterModal() { password: "", confirm_password: "", }); + const [error, setError] = useState(""); return ( <>
    ) => - setUser({ ...user, first_name: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, first_name: e.target.value }); + setError(""); + }} value={user.first_name} placeholder={"Enter your first name"} /> @@ -63,9 +67,10 @@ export default function RegisterModal() { id="outlined-helperText" label="Username" style={styles.input_form} - onChange={(e: React.ChangeEvent) => - setUser({ ...user, username: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, username: e.target.value }); + setError(""); + }} value={user.username} placeholder={"Enter username"} /> @@ -112,12 +117,14 @@ export default function RegisterModal() { }} label="Confirm Password" placeholder={"Re-enter password"} - onChange={(e: React.ChangeEvent) => - setUser({ ...user, confirm_password: e.target.value }) - } + onChange={(e: React.ChangeEvent) => { + setUser({ ...user, confirm_password: e.target.value }); + setError(""); + }} value={user.confirm_password} />
    +

    {error}

    { - navigate(0); + onClick={async () => { + if (user.password !== user.confirm_password) { + setError("Passwords do not match"); + } else { + const status = await RegisterAPI(user); + if (status[0]) { + navigate("/"); + } else { + setError(status[1]); + } + } }} /> diff --git a/src/Components/Revalidator/Revalidator.tsx b/src/Components/Revalidator/Revalidator.tsx index 5ef104b..fc37f5d 100644 --- a/src/Components/Revalidator/Revalidator.tsx +++ b/src/Components/Revalidator/Revalidator.tsx @@ -1,22 +1,35 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useLocation } from "react-router-dom"; import { JWTRefreshAPI, setAccessToken, setRefreshToken } from "../API/API"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; import { RootState } from "../Plugins/Redux/Store/Store"; +import { toast } from "react-toastify"; export default function Revalidator() { const dispatch = useDispatch(); const navigate = useNavigate(); + const location = useLocation(); const authenticated = useSelector((state: RootState) => state.auth.value); const [rechecked, setRechecked] = useState(false); useEffect(() => { if (!authenticated && rechecked) { - navigate("/"); - console.log("Not logged in"); + if (location.pathname !== "/") { + navigate("/"); + toast("Please log in to continue", { + position: "bottom-center", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + } } - }, [authenticated, navigate, rechecked]); + }, [authenticated, location.pathname, navigate, rechecked]); useEffect(() => { if (!authenticated) { @@ -24,16 +37,25 @@ export default function Revalidator() { if (response) { await dispatch(auth_toggle()); navigate("/dashboard"); - console.log("User session restored"); + toast("User session restored", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); } else { await setRefreshToken(""); await setAccessToken(""); - console.log("User session expired"); } setRechecked(true); }); } - }, [authenticated, dispatch, navigate]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); return <>; } diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..3463d9b --- /dev/null +++ b/src/Components/Sidebar/Sidebar.tsx @@ -0,0 +1,114 @@ +import styles, { colors } from "../../styles"; +import AccountCircleIcon from "@mui/icons-material/AccountCircle"; +import HomeIcon from "@mui/icons-material/Home"; +import LogoutIcon from "@mui/icons-material/Logout"; +import { useQuery } from "@tanstack/react-query"; +import { UserAPI, setAccessToken, setRefreshToken } from "../API/API"; +import DrawerButton from "../DrawerButton/DrawerButton"; +import { useDispatch } from "react-redux"; +import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; +export default function Sidebar() { + const user = useQuery({ queryKey: ["user"], queryFn: UserAPI }); + const dispatch = useDispatch(); + const navigate = useNavigate(); + return ( +
    +
    + +

    + {user.data + ? user.data.username + : user.isError + ? "Error loading user" + : "Loading user..."} +

    +
    +
    +
    + +

    + Dashboard +

    +
    +
    + { + navigate("/"); + await dispatch(auth_toggle()); + await setAccessToken(""); + await setRefreshToken(""); + toast("Logged out", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }} + icon={ + + } + label={"Log out"} + /> +
    +
    + ); +} diff --git a/src/Components/SidebarModal/SidebarModal.tsx b/src/Components/SidebarModal/SidebarModal.tsx deleted file mode 100644 index 0a2d4ed..0000000 --- a/src/Components/SidebarModal/SidebarModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import styles, { colors } from "../../styles"; -import AccountCircleIcon from "@mui/icons-material/AccountCircle"; -import HomeIcon from "@mui/icons-material/Home"; -export default function SidebarModal() { - return ( -
    -
    - -

    - Placeholder Name -

    -
    -
    -
    - -

    - Dashboard -

    -
    -
    - ); -} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index de6f952..784a3b6 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -2,6 +2,8 @@ export type RegisterType = { email: string; username: string; password: string; + first_name: string; + last_name: string; }; export type LoginType = { diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index 86ce33d..ea0ac88 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -1,4 +1,4 @@ -import Button from "../../Components/Buttons/Button"; +import Button from "../../Components/Button/Button"; import styles from "../../styles"; import citc_logo from "../../assets/citc_logo.jpg"; import Popup from "reactjs-popup"; From ff1d465d3fac680dc70fc4f1cb78b5d0d6a34bd8 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 18:37:30 +0800 Subject: [PATCH 20/47] Improved drawer buttons --- src/Components/Sidebar/Sidebar.tsx | 102 ++++++++++++++--------------- 1 file changed, 49 insertions(+), 53 deletions(-) diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index 3463d9b..ab8ad0d 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -56,59 +56,55 @@ export default function Sidebar() { marginBottom: 8, }} /> -
    - -

    - Dashboard -

    -
    -
    - { - navigate("/"); - await dispatch(auth_toggle()); - await setAccessToken(""); - await setRefreshToken(""); - toast("Logged out", { - position: "top-right", - autoClose: 2000, - hideProgressBar: false, - closeOnClick: true, - pauseOnHover: true, - draggable: true, - progress: undefined, - theme: "light", - }); - }} - icon={ - - } - label={"Log out"} - /> -
    + { + navigate("/dashboard"); + }} + icon={ + + } + label={"Dashboard"} + /> + { + navigate("/"); + await dispatch(auth_toggle()); + await setAccessToken(""); + await setRefreshToken(""); + toast("Logged out", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + }} + icon={ + + } + label={"Log out"} + />
    ); } From 8a2c702da38f1e43d9d558d36ad7e9859f762b61 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 19:00:29 +0800 Subject: [PATCH 21/47] Improved registration flow --- src/Components/API/API.tsx | 22 ++++++++++- .../RegisterModal/RegisterModal.tsx | 38 ++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 6f99637..d00ab31 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -5,6 +5,7 @@ import { ActivationType, LoginType, RegisterType } from "../Types/Types"; const instance = axios.create({ baseURL: "http://localhost:8000/", }); + // Token Handling export async function getAccessToken() { const accessToken = await localStorage.getItem("access_token"); @@ -36,6 +37,25 @@ export async function GetConfig() { }; } +export function ParseError(error: { response: { data: string } }) { + if (error.response && error.response.data) { + if (error.response.data.length > 50) { + return "Error truncated (too long)"; + } + return JSON.stringify(error.response.data) + .replace(/[{}]/g, " ") + .replace(/\(/g, " ") + .replace(/\)/g, " ") + .replace(/"/g, " ") + .replace(/,/g, " ") + .replace(/\[/g, "") + .replace(/\]/g, "") + .replace(/\./g, "") + .replace(/non_field_errors/g, "") + .trim(); + } + return "Unable to reach server"; +} // User APIs export function RegisterAPI(register: RegisterType) { @@ -47,7 +67,7 @@ export function RegisterAPI(register: RegisterType) { }) .catch((error) => { console.log("Registration failed"); - return [false, error.response]; + return [false, ParseError(error)]; }); } diff --git a/src/Components/RegisterModal/RegisterModal.tsx b/src/Components/RegisterModal/RegisterModal.tsx index fad4327..ca75021 100644 --- a/src/Components/RegisterModal/RegisterModal.tsx +++ b/src/Components/RegisterModal/RegisterModal.tsx @@ -10,6 +10,7 @@ import { AppRegistration } from "@mui/icons-material"; import Button from "../Button/Button"; import { useNavigate } from "react-router-dom"; import { RegisterAPI } from "../API/API"; +import { toast } from "react-toastify"; export default function RegisterModal() { const navigate = useNavigate(); const [showPassword, setShowPassword] = useState(false); @@ -63,6 +64,16 @@ export default function RegisterModal() { value={user.last_name} placeholder={"Enter your last name"} /> + ) => + setUser({ ...user, email: e.target.value }) + } + value={user.email} + placeholder={"Enter your email"} + /> { + navigate(0); + }, 3000); + setUser({ + first_name: "", + last_name: "", + username: "", + email: "", + password: "", + confirm_password: "", + }); } else { - setError(status[1]); + setError(JSON.stringify(status[1])); } } }} From 742a1af9f8f8ed4f512ed200d9c0b5d13071afb4 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 19 Nov 2023 23:11:27 +0800 Subject: [PATCH 22/47] Improved registration and activation functionality --- src/App.tsx | 10 +++ src/Components/API/API.tsx | 2 +- .../Sidebar.tsx => Drawer/Drawer.tsx} | 2 +- src/Components/Header/Header.tsx | 2 +- src/Components/LoginModal/LoginModal.tsx | 6 +- .../RegisterModal/RegisterModal.tsx | 6 +- src/Pages/ActivationPage/ActivationPage.tsx | 88 +++++++++++++++++++ 7 files changed, 111 insertions(+), 5 deletions(-) rename src/Components/{Sidebar/Sidebar.tsx => Drawer/Drawer.tsx} (98%) create mode 100644 src/Pages/ActivationPage/ActivationPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 15f25b2..6db1229 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import "react-toastify/dist/ReactToastify.css"; import ErrorPage from "./Pages/ErrorPage/ErrorPage"; import DashboardPage from "./Pages/DashboardPage/DashboardPage"; import Revalidator from "./Components/Revalidator/Revalidator"; +import ActivationPage from "./Pages/ActivationPage/ActivationPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -32,6 +33,15 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/activation/:uid/:token", + element: ( + <> + + + ), + errorElement: , + }, ]); export default function App() { diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index d00ab31..2b22ca5 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -121,7 +121,7 @@ export async function UserAPI() { export function ActivationAPI(activation: ActivationType) { return instance .post("api/v1/accounts/users/activation/", activation) - .then(async () => { + .then(() => { console.log("Activation Success"); return true; }) diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Drawer/Drawer.tsx similarity index 98% rename from src/Components/Sidebar/Sidebar.tsx rename to src/Components/Drawer/Drawer.tsx index ab8ad0d..1360ac5 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Drawer/Drawer.tsx @@ -9,7 +9,7 @@ import { useDispatch } from "react-redux"; import { auth_toggle } from "../Plugins/Redux/Slices/AuthSlice/AuthSlice"; import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; -export default function Sidebar() { +export default function Drawer() { const user = useQuery({ queryKey: ["user"], queryFn: UserAPI }); const dispatch = useDispatch(); const navigate = useNavigate(); diff --git a/src/Components/Header/Header.tsx b/src/Components/Header/Header.tsx index b36f9c4..d0189e4 100644 --- a/src/Components/Header/Header.tsx +++ b/src/Components/Header/Header.tsx @@ -1,7 +1,7 @@ import { useState } from "react"; import styles, { colors } from "../../styles"; import MenuIcon from "@mui/icons-material/Menu"; -import SidebarModal from "../Sidebar/Sidebar"; +import SidebarModal from "../Drawer/Drawer"; import { Drawer } from "@mui/material"; export interface props { label: string; diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index 50a8507..6cc9459 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -29,7 +29,11 @@ export default function LoginModal() {
    { + if (uid && token && feedback == "") { + ActivationAPI({ uid, token }).then((response) => { + if (response) { + setFeedback("Activation successful"); + toast("Activation successful", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + setTimeout(() => { + navigate("/dashboard"); + }); + } else { + setFeedback("Invalid activation link"); + setError(true); + } + }); + } + if (!uid || !token) { + setFeedback("Missing uid or token"); + } + setLoading(false); + }, [uid, token, feedback, navigate]); + return ( +
    +
    + {loading ? ( + + ) : ( + <> + )} + {error && !loading ? ( + + ) : ( + + )} + +

    {feedback}

    +
    +

    + Activating your CITC Equipment Tracker Account +

    +
    +
    + ); +} From a8de8476e581fd9d0f93497da7ff9d673ab6b611 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 20 Nov 2023 00:00:30 +0800 Subject: [PATCH 23/47] Added reset password modal and confirm reset password page --- src/App.tsx | 10 ++ src/Components/API/API.tsx | 32 +++- src/Components/LoginModal/LoginModal.tsx | 10 -- .../ResetPasswordModal/ResetPasswordModal.tsx | 91 +++++++++++ src/Components/Types/Types.tsx | 6 + src/Pages/ActivationPage/ActivationPage.tsx | 10 ++ src/Pages/LandingPage/LandingPage.tsx | 46 +++++- .../ResetPasswordPage/ResetPasswordPage.tsx | 148 ++++++++++++++++++ 8 files changed, 335 insertions(+), 18 deletions(-) create mode 100644 src/Components/ResetPasswordModal/ResetPasswordModal.tsx create mode 100644 src/Pages/ResetPasswordPage/ResetPasswordPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 6db1229..9cf4075 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ import ErrorPage from "./Pages/ErrorPage/ErrorPage"; import DashboardPage from "./Pages/DashboardPage/DashboardPage"; import Revalidator from "./Components/Revalidator/Revalidator"; import ActivationPage from "./Pages/ActivationPage/ActivationPage"; +import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -42,6 +43,15 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/reset_password_confirm/:uid/:token", + element: ( + <> + + + ), + errorElement: , + }, ]); export default function App() { diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 2b22ca5..29fdaee 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -1,6 +1,11 @@ /* eslint-disable react-refresh/only-export-components */ import axios from "axios"; -import { ActivationType, LoginType, RegisterType } from "../Types/Types"; +import { + ActivationType, + LoginType, + RegisterType, + ResetPasswordConfirmType, +} from "../Types/Types"; const instance = axios.create({ baseURL: "http://localhost:8000/", @@ -130,3 +135,28 @@ export function ActivationAPI(activation: ActivationType) { return false; }); } +export function ResetPasswordAPI(email: string) { + return instance + .post("api/v1/accounts/users/reset_password/", { email: email }) + .then(() => { + console.log("Activation Success"); + return true; + }) + .catch(() => { + console.log("Activation failed"); + return false; + }); +} + +export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) { + return instance + .post("api/v1/accounts/users/reset_password_confirm/", info) + .then(() => { + console.log("Reset Success"); + return true; + }) + .catch(() => { + console.log("Reset failed"); + return false; + }); +} diff --git a/src/Components/LoginModal/LoginModal.tsx b/src/Components/LoginModal/LoginModal.tsx index 6cc9459..fb6a5c0 100644 --- a/src/Components/LoginModal/LoginModal.tsx +++ b/src/Components/LoginModal/LoginModal.tsx @@ -106,16 +106,6 @@ export default function LoginModal() { />

    Remember me

    -
    -

    - Forgot password? -

    -
    +
    + +

    Forgot Password

    +
    +

    + Enter your email to request a password reset +

    +
    + ) => { + setEmail(e.target.value); + setError(""); + }} + value={email} + placeholder={"Enter email associated with account"} + /> +

    {error}

    +
    +
    + + ); +} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index 784a3b6..efcb59a 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -16,6 +16,12 @@ export type ActivationType = { token: string; }; +export type ResetPasswordConfirmType = { + uid: string; + token: string; + new_password: string; +}; + export type AddEquipmentType = { name: string; remarks: string; diff --git a/src/Pages/ActivationPage/ActivationPage.tsx b/src/Pages/ActivationPage/ActivationPage.tsx index 3117228..e95cf28 100644 --- a/src/Pages/ActivationPage/ActivationPage.tsx +++ b/src/Pages/ActivationPage/ActivationPage.tsx @@ -27,6 +27,16 @@ export default function ActivationPage() { progress: undefined, theme: "light", }); + toast("Please login to continue", { + position: "top-right", + autoClose: 6000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); setTimeout(() => { navigate("/dashboard"); }); diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index ea0ac88..8c1d046 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -9,9 +9,11 @@ import RegisterModal from "../../Components/RegisterModal/RegisterModal"; import { useSelector } from "react-redux"; import { useNavigate } from "react-router-dom"; import { RootState } from "../../Components/Plugins/Redux/Store/Store"; +import ResetPasswordModal from "../../Components/ResetPasswordModal/ResetPasswordModal"; export default function LandingPage() { - const [LoginModalOpen, SetLoginModalOpen] = useState(false); - const [RegisterModalOpen, SetRegisterModalOpen] = useState(false); + const [loginmodalOpen, SetloginmodalOpen] = useState(false); + const [registermodalOpen, SetRegisterModalOpen] = useState(false); + const [resetmodalOpen, SetResetModalOpen] = useState(false); const authenticated = useSelector((state: RootState) => state.auth.value); const navigate = useNavigate(); @@ -68,8 +70,9 @@ export default function LandingPage() { type={"light"} label={"Login"} onClick={() => { - SetLoginModalOpen(true); + SetloginmodalOpen(true); SetRegisterModalOpen(false); + SetResetModalOpen(false); }} />
    diff --git a/src/Pages/ResetPasswordPage/ResetPasswordPage.tsx b/src/Pages/ResetPasswordPage/ResetPasswordPage.tsx new file mode 100644 index 0000000..c70bdde --- /dev/null +++ b/src/Pages/ResetPasswordPage/ResetPasswordPage.tsx @@ -0,0 +1,148 @@ +import { useNavigate, useParams } from "react-router-dom"; +import styles, { colors } from "../../styles"; +import { ResetPasswordConfirmAPI } from "../../Components/API/API"; +import { useState } from "react"; +import { toast } from "react-toastify"; +import { VisibilityOff, Visibility } from "@mui/icons-material"; +import { TextField, InputAdornment, IconButton } from "@mui/material"; +import Button from "../../Components/Button/Button"; +export default function ResetPasswordPage() { + const { uid, token } = useParams(); + const [feedback, setFeedback] = useState(""); + const [user, setUser] = useState({ + password: "", + confirm_password: "", + }); + const [showPassword, setShowPassword] = useState(false); + const navigate = useNavigate(); + return ( +
    +
    +

    + Confirm Password Reset +

    + + { + setShowPassword(!showPassword); + setFeedback(""); + }} + edge="end" + > + {showPassword ? : } + + + ), + }} + label="New Password" + placeholder={"Enter new password"} + onChange={(e: React.ChangeEvent) => + setUser({ ...user, password: e.target.value }) + } + value={user.password} + /> + + { + setShowPassword(!showPassword); + setFeedback(""); + }} + edge="end" + > + {showPassword ? : } + + + ), + }} + label="Confirm Password" + placeholder={"Re-enter password"} + onChange={(e: React.ChangeEvent) => + setUser({ ...user, password: e.target.value }) + } + value={user.password} + /> +
    +
    {" "} +
    + +

    {feedback}

    +
    +
    + ); +} From 150663587c7a6a95ddddb62b43268c3d6f7a48b1 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 20 Nov 2023 00:01:28 +0800 Subject: [PATCH 24/47] Update confirm button label in forgot password modal --- src/Components/ResetPasswordModal/ResetPasswordModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/ResetPasswordModal/ResetPasswordModal.tsx b/src/Components/ResetPasswordModal/ResetPasswordModal.tsx index 223e3d2..779e27d 100644 --- a/src/Components/ResetPasswordModal/ResetPasswordModal.tsx +++ b/src/Components/ResetPasswordModal/ResetPasswordModal.tsx @@ -61,7 +61,7 @@ export default function ResetPasswordModal() { /> + +
    + +
    + + + + + +
    ); } diff --git a/src/styles.tsx b/src/styles.tsx index 4a8f257..3ad2dc1 100644 --- a/src/styles.tsx +++ b/src/styles.tsx @@ -20,6 +20,7 @@ const styles: { [key: string]: React.CSSProperties } = { width: "100%", minHeight: "100%", minWidth: "100%", + overflowY: "scroll", }, text_dark: { color: colors.font_dark, From 26dc30f59dcb70b7b457efb7776dd0477ff75c40 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 19:20:22 +0800 Subject: [PATCH 29/47] Improved dashboard --- src/App.tsx | 22 +++++++++++++++++++ .../AddEquipmentInstancePage.tsx | 3 +++ src/Pages/DashboardPage/DashboardPage.tsx | 9 ++++++++ .../EquipmentInstancesListPage.tsx | 3 +++ 4 files changed, 37 insertions(+) create mode 100644 src/Pages/AddEquipmentInstancePage/AddEquipmentInstancePage.tsx create mode 100644 src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 9cf4075..dccbd57 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,6 +11,8 @@ import DashboardPage from "./Pages/DashboardPage/DashboardPage"; import Revalidator from "./Components/Revalidator/Revalidator"; import ActivationPage from "./Pages/ActivationPage/ActivationPage"; import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; +import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage"; +import AddEquipmentInstancePage from "./Pages/AddEquipmentInstancePage/AddEquipmentInstancePage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -34,6 +36,26 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/view/equipment_instances", + element: ( + <> + + + + ), + errorElement: , + }, + { + path: "/add/equipment_instance", + element: ( + <> + + + + ), + errorElement: , + }, { path: "/activation/:uid/:token", element: ( diff --git a/src/Pages/AddEquipmentInstancePage/AddEquipmentInstancePage.tsx b/src/Pages/AddEquipmentInstancePage/AddEquipmentInstancePage.tsx new file mode 100644 index 0000000..dcb4f99 --- /dev/null +++ b/src/Pages/AddEquipmentInstancePage/AddEquipmentInstancePage.tsx @@ -0,0 +1,3 @@ +export default function AddEquipmentInstancePage() { + return
    {"AddEquipmentInstancePage"}
    ; +} diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 0d33072..187ef13 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -10,7 +10,10 @@ import ChairIcon from "@mui/icons-material/Chair"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import AddToQueueIcon from "@mui/icons-material/AddToQueue"; import { colors } from "../../styles"; +import { useNavigate } from "react-router-dom"; export default function Dashboard() { + const navigate = useNavigate(); + const queries = useQueries({ queries: [ { @@ -238,6 +241,9 @@ export default function Dashboard() { flexWrap: "wrap", }, }} + onClick={() => { + navigate("/view/equipment_instances"); + }} > { + navigate("/add/equipment_instance"); + }} > {"EquipmentInstancesListPage"}
    ; +} From 09e048c8243319926415dfc986f2794768606e1e Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 19:20:30 +0800 Subject: [PATCH 30/47] Improved dashboard --- src/Pages/DashboardPage/DashboardPage.tsx | 40 ++++++++++++++++++++--- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 187ef13..03f81b5 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -9,6 +9,7 @@ import CameraOutdoorIcon from "@mui/icons-material/CameraOutdoor"; import ChairIcon from "@mui/icons-material/Chair"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import AddToQueueIcon from "@mui/icons-material/AddToQueue"; +import NoteAddIcon from "@mui/icons-material/NoteAdd"; import { colors } from "../../styles"; import { useNavigate } from "react-router-dom"; export default function Dashboard() { @@ -88,7 +89,7 @@ export default function Dashboard() { ...{ float: "left", position: "absolute" }, }} > - Equipment Types in Database + SKUs in Database

    - Equipment Count + Item Count

    - Working Items + Functional Item

    - Add Equipment + Add Item +

    + + From 791f11b8026ef6bfb1dd5b99d9edb0d8ed189f1f Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 19:25:24 +0800 Subject: [PATCH 31/47] Improved dashboard --- src/App.tsx | 11 +++++++ .../AddEquipmentPage/AddEquipmentPage.tsx | 3 ++ src/Pages/DashboardPage/DashboardPage.tsx | 32 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/Pages/AddEquipmentPage/AddEquipmentPage.tsx diff --git a/src/App.tsx b/src/App.tsx index dccbd57..1576413 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import ActivationPage from "./Pages/ActivationPage/ActivationPage"; import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage"; import AddEquipmentInstancePage from "./Pages/AddEquipmentInstancePage/AddEquipmentInstancePage"; +import AddEquipmentPage from "./Pages/AddEquipmentPage/AddEquipmentPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -56,6 +57,16 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/add/equipment", + element: ( + <> + + + + ), + errorElement: , + }, { path: "/activation/:uid/:token", element: ( diff --git a/src/Pages/AddEquipmentPage/AddEquipmentPage.tsx b/src/Pages/AddEquipmentPage/AddEquipmentPage.tsx new file mode 100644 index 0000000..2fda282 --- /dev/null +++ b/src/Pages/AddEquipmentPage/AddEquipmentPage.tsx @@ -0,0 +1,3 @@ +export default function AddEquipmentPage() { + return
    {"AddEquipmentPage"}
    ; +} diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 03f81b5..9d8cf6d 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -10,6 +10,7 @@ import ChairIcon from "@mui/icons-material/Chair"; import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import AddToQueueIcon from "@mui/icons-material/AddToQueue"; import NoteAddIcon from "@mui/icons-material/NoteAdd"; +import NoteIcon from "@mui/icons-material/Note"; import { colors } from "../../styles"; import { useNavigate } from "react-router-dom"; export default function Dashboard() { @@ -326,6 +327,37 @@ export default function Dashboard() { Add SKU

    +
    Date: Sat, 2 Dec 2023 20:10:14 +0800 Subject: [PATCH 32/47] Added add equipment modal --- src/App.tsx | 22 --- src/Components/API/API.tsx | 15 ++ src/Components/AddSKUModal/AddSKUModal.tsx | 166 ++++++++++++++++++ src/Components/Types/Types.tsx | 3 +- .../AddEquipmentInstancePage.tsx | 3 - .../AddEquipmentPage/AddEquipmentPage.tsx | 3 - src/Pages/DashboardPage/DashboardPage.tsx | 27 ++- src/Pages/LandingPage/LandingPage.tsx | 6 +- 8 files changed, 214 insertions(+), 31 deletions(-) create mode 100644 src/Components/AddSKUModal/AddSKUModal.tsx delete mode 100644 src/Pages/AddEquipmentInstancePage/AddEquipmentInstancePage.tsx delete mode 100644 src/Pages/AddEquipmentPage/AddEquipmentPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 1576413..a962f0c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,8 +12,6 @@ import Revalidator from "./Components/Revalidator/Revalidator"; import ActivationPage from "./Pages/ActivationPage/ActivationPage"; import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage"; -import AddEquipmentInstancePage from "./Pages/AddEquipmentInstancePage/AddEquipmentInstancePage"; -import AddEquipmentPage from "./Pages/AddEquipmentPage/AddEquipmentPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -47,26 +45,6 @@ const router = createHashRouter([ ), errorElement: , }, - { - path: "/add/equipment_instance", - element: ( - <> - - - - ), - errorElement: , - }, - { - path: "/add/equipment", - element: ( - <> - - - - ), - errorElement: , - }, { path: "/activation/:uid/:token", element: ( diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 9b49d7c..63d6866 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -7,6 +7,8 @@ import { RegisterType, ResetPasswordConfirmType, EquipmentInstanceListType, + EquipmentType, + AddEquipmentType, } from "../Types/Types"; const instance = axios.create({ @@ -171,6 +173,19 @@ export async function EquipmentsAPI() { }); } +export async function EquipmentCreateAPI(equipment: AddEquipmentType) { + const config = await GetConfig(); + return instance + .post("api/v1/equipments/equipments/", equipment, config) + .then((response) => { + return [true, response.data as EquipmentType]; + }) + .catch((error) => { + console.log("Error creating equipment"); + return [false, ParseError(error)]; + }); +} + export async function EquipmentInstancesAPI() { const config = await GetConfig(); return instance diff --git a/src/Components/AddSKUModal/AddSKUModal.tsx b/src/Components/AddSKUModal/AddSKUModal.tsx new file mode 100644 index 0000000..82cf06d --- /dev/null +++ b/src/Components/AddSKUModal/AddSKUModal.tsx @@ -0,0 +1,166 @@ +import { useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import TextField from "@mui/material/TextField"; +import NoteAddIcon from "@mui/icons-material/NoteAdd"; +import Button from "../Button/Button"; +import { toast } from "react-toastify"; +import { EquipmentCreateAPI } from "../API/API"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import Radio from "@mui/material/Radio"; +import { useQueryClient } from "@tanstack/react-query"; + +export default function AddSKUModal() { + const queryClient = useQueryClient(); + const [sku, setSKU] = useState({ + name: "", + description: "", + category: "", + }); + const [error, setError] = useState(""); + + return ( + <> +
    + +

    Add SKU

    +
    + +
    + ) => { + setSKU({ ...sku, name: e.target.value }); + setError(""); + }} + value={sku.name} + placeholder={"Enter SKU name"} + /> + ) => + setSKU({ ...sku, description: e.target.value }) + } + value={sku.description} + placeholder={"Give a brief description of the SKU"} + /> + + + Category + + ) => { + setSKU({ ...sku, category: e.target.value }); + }} + > +
    +
    + } + label="Workstation" + style={styles.text_dark} + /> + } + label="Networking" + style={styles.text_dark} + /> + } + label="CCTV" + style={styles.text_dark} + /> +
    +
    + } + label="Furniture" + style={styles.text_dark} + /> + } + label="Peripherals" + style={styles.text_dark} + /> + } + label="Miscellaneous" + style={styles.text_dark} + /> +
    +
    +
    +
    +
    +

    {error}

    +
    +
    + SetAddSKUModalOpen(false)} + modal + position={"top center"} + contentStyle={{ + width: "512px", + borderRadius: 16, + borderColor: "grey", + borderStyle: "solid", + borderWidth: 1, + padding: 16, + alignContent: "center", + justifyContent: "center", + textAlign: "center", + }} + > + +
    ); } diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index fd5ef05..6e18c0e 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -36,9 +36,12 @@ export default function LandingPage() { width: "100%", minHeight: "100%", minWidth: "100%", + flexWrap: "wrap", }} > -
    +
    Date: Sat, 2 Dec 2023 21:31:15 +0800 Subject: [PATCH 33/47] Made SKU and item adding fully functional --- src/Components/API/API.tsx | 21 +- src/Components/AddItemModal/AddItemModal.tsx | 235 +++++++++++++++++++ src/Components/AddSKUModal/AddSKUModal.tsx | 34 +-- src/Components/Types/Types.tsx | 6 + src/Pages/DashboardPage/DashboardPage.tsx | 26 +- 5 files changed, 304 insertions(+), 18 deletions(-) create mode 100644 src/Components/AddItemModal/AddItemModal.tsx diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 63d6866..667ad12 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -9,6 +9,8 @@ import { EquipmentInstanceListType, EquipmentType, AddEquipmentType, + AddEquipmentInstanceType, + EquipmentInstanceType, } from "../Types/Types"; const instance = axios.create({ @@ -56,7 +58,7 @@ export function ParseError(error: { response: { data: string } }) { .replace(/\(/g, " ") .replace(/\)/g, " ") .replace(/"/g, " ") - .replace(/,/g, " ") + .replace(/,/g, ",") .replace(/\[/g, "") .replace(/\]/g, "") .replace(/\./g, "") @@ -186,6 +188,8 @@ export async function EquipmentCreateAPI(equipment: AddEquipmentType) { }); } +// Equipment Instances APIs + export async function EquipmentInstancesAPI() { const config = await GetConfig(); return instance @@ -197,3 +201,18 @@ export async function EquipmentInstancesAPI() { console.log("Error retrieving equipments"); }); } + +export async function EquipmentInstanceCreateAPI( + equipment_instance: AddEquipmentInstanceType +) { + const config = await GetConfig(); + return instance + .post("api/v1/equipments/equipment_instances/", equipment_instance, config) + .then((response) => { + return [true, response.data as EquipmentInstanceType]; + }) + .catch((error) => { + console.log("Error creating equipment instance"); + return [false, ParseError(error)]; + }); +} diff --git a/src/Components/AddItemModal/AddItemModal.tsx b/src/Components/AddItemModal/AddItemModal.tsx new file mode 100644 index 0000000..76ea5b6 --- /dev/null +++ b/src/Components/AddItemModal/AddItemModal.tsx @@ -0,0 +1,235 @@ +import { useEffect, useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import TextField from "@mui/material/TextField"; +import AddToQueueIcon from "@mui/icons-material/AddToQueue"; +import Button from "../Button/Button"; +import { toast } from "react-toastify"; +import { EquipmentInstanceCreateAPI, EquipmentsAPI } from "../API/API"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import Radio from "@mui/material/Radio"; +import { useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; +import { CircularProgress } from "@mui/material"; +import React from "react"; + +export default function AddItemModal() { + const queryClient = useQueryClient(); + const [item, setItem] = useState({ + equipment: 0, + remarks: "", + status: "WORKING", + }); + const [error, setError] = useState(""); + + const equipments = useQuery({ + queryKey: ["equipments"], + queryFn: EquipmentsAPI, + }); + + useEffect(() => { + if (equipments.data) { + setItem({ ...item, equipment: equipments.data[0].id }); + } + }, [equipments.data, item]); + if (equipments.isLoading) { + return ( +
    + +

    + Loading +

    +
    + ); + } + return ( + <> +
    + +

    Add Item

    +
    + +
    + + + Select Associated SKU + + ) => { + setItem({ ...item, equipment: Number(e.target.value) }); + setError(""); + }} + > +
    + {equipments.data ? ( + equipments.data.map((equipment) => ( + + } + label={equipment.name} + style={styles.text_dark} + /> + + )) + ) : ( + <> + )} +
    +
    +
    + + + Item Status + + ) => { + setItem({ ...item, status: e.target.value }); + setError(""); + }} + > +
    + } + label="Working" + style={styles.text_dark} + /> + } + label="Broken" + style={styles.text_dark} + /> + } + label="Under Maintenance" + style={styles.text_dark} + /> + } + label="Decomissioned" + style={styles.text_dark} + /> +
    +
    +
    + ) => { + setItem({ ...item, remarks: e.target.value }); + setError(""); + }} + value={item.remarks} + placeholder={"Optionally add a brief description of the item"} + /> +
    +

    {error}

    +
    +
    ; + return
    {"EquipmentInstancesListPage"}
    ; } From f6a181074095ac664db4929a0d530128be1a2b24 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 22:04:46 +0800 Subject: [PATCH 35/47] Finalize equipment instances list page --- src/Components/Revalidator/Revalidator.tsx | 1 - src/Components/Types/Types.tsx | 2 + .../EquipmentInstancesListPage.tsx | 131 +++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/src/Components/Revalidator/Revalidator.tsx b/src/Components/Revalidator/Revalidator.tsx index fc37f5d..7631fcc 100644 --- a/src/Components/Revalidator/Revalidator.tsx +++ b/src/Components/Revalidator/Revalidator.tsx @@ -36,7 +36,6 @@ export default function Revalidator() { JWTRefreshAPI().then(async (response) => { if (response) { await dispatch(auth_toggle()); - navigate("/dashboard"); toast("User session restored", { position: "top-right", autoClose: 2000, diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index 3030362..3f263ce 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -48,11 +48,13 @@ export type AddEquipmentInstanceType = { export type EquipmentInstanceType = { id: number; equipment: string; + equipment_name: string; status: string; remarks: string; last_updated: string; last_updated_by: string; date_added: string; + category: string; }; export type EquipmentInstanceListType = Array; diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index 779ab7c..b56d28e 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -1,5 +1,134 @@ +import { useQuery } from "@tanstack/react-query"; +import Header from "../../Components/Header/Header"; import styles from "../../styles"; +import { EquipmentInstancesAPI } from "../../Components/API/API"; +import { CircularProgress } from "@mui/material"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import { colors } from "../../styles"; export default function EquipmentInstancesListPage() { - return
    {"EquipmentInstancesListPage"}
    ; + const equipment_instances = useQuery({ + queryKey: ["equipment_instances"], + queryFn: EquipmentInstancesAPI, + }); + if (equipment_instances.isLoading) { + return ( +
    +
    +
    + +

    + Loading +

    +
    +
    + ); + } + return ( +
    +
    +
    +
    + + + + + ID + + Name + + + Status + + + Category + + + Last Modified + + + + + {equipment_instances.data ? ( + equipment_instances.data.map((equipment) => ( + { + console.log("HEH"); + }} + > + + {equipment.id} + + + {equipment.equipment_name} + + + + {equipment.status} + + + {equipment.category} + + +
    +
    {equipment.last_updated}
    +
    + {equipment.last_updated_by + ? "by " + equipment.last_updated_by + : ""} +
    +
    +
    +
    + )) + ) : ( + <> + )} +
    +
    +
    +
    +
    +
    + ); } From dc0d339f279aec65571c2dada6cb2a4191d845c4 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 22:09:20 +0800 Subject: [PATCH 36/47] Add SKU list page --- src/App.tsx | 11 ++ .../EquipmentListPage/EquipmentListPage.tsx | 128 ++++++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 src/Pages/EquipmentListPage/EquipmentListPage.tsx diff --git a/src/App.tsx b/src/App.tsx index a962f0c..76e1368 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,6 +12,7 @@ import Revalidator from "./Components/Revalidator/Revalidator"; import ActivationPage from "./Pages/ActivationPage/ActivationPage"; import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage"; +import EquipmentListPage from "./Pages/EquipmentListPage/EquipmentListPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -45,6 +46,16 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/view/equipments", + element: ( + <> + + + + ), + errorElement: , + }, { path: "/activation/:uid/:token", element: ( diff --git a/src/Pages/EquipmentListPage/EquipmentListPage.tsx b/src/Pages/EquipmentListPage/EquipmentListPage.tsx new file mode 100644 index 0000000..83baf0b --- /dev/null +++ b/src/Pages/EquipmentListPage/EquipmentListPage.tsx @@ -0,0 +1,128 @@ +import { useQuery } from "@tanstack/react-query"; +import Header from "../../Components/Header/Header"; +import styles from "../../styles"; +import { EquipmentsAPI } from "../../Components/API/API"; +import { CircularProgress } from "@mui/material"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import { colors } from "../../styles"; + +export default function EquipmentListPage() { + const equipments = useQuery({ + queryKey: ["equipments"], + queryFn: EquipmentsAPI, + }); + if (equipments.isLoading) { + return ( +
    +
    +
    + +

    + Loading +

    +
    +
    + ); + } + return ( +
    +
    +
    +
    + + + + + ID + + Name + + + Description + + + Last Modified + + + + + {equipments.data ? ( + equipments.data.map((equipment) => ( + { + console.log("HEH"); + }} + > + + {equipment.id} + + + {equipment.name} + + + + {equipment.description} + + +
    +
    {equipment.last_updated}
    +
    + {equipment.last_updated_by + ? "by " + equipment.last_updated_by + : ""} +
    +
    +
    +
    + )) + ) : ( + <> + )} +
    +
    +
    +
    +
    +
    + ); +} From 2887a98e7d01c6eade573c09d29fcd1ef6832372 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 23:35:57 +0800 Subject: [PATCH 37/47] Added delete equipment instance functionality --- src/Components/API/API.tsx | 42 +++ .../EditItemModal/EditItemModal.tsx | 297 ++++++++++++++++++ src/Components/Types/Types.tsx | 5 + .../EquipmentInstancesListPage.tsx | 27 +- 4 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 src/Components/EditItemModal/EditItemModal.tsx diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 667ad12..322f794 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -11,6 +11,7 @@ import { AddEquipmentType, AddEquipmentInstanceType, EquipmentInstanceType, + PatchEquipmentInstanceType, } from "../Types/Types"; const instance = axios.create({ @@ -190,6 +191,47 @@ export async function EquipmentCreateAPI(equipment: AddEquipmentType) { // Equipment Instances APIs +export async function EquipmentInstanceAPI(id: number) { + const config = await GetConfig(); + return instance + .get(`api/v1/equipments/equipment_instances/${id}`, config) + .then((response) => { + return response.data as EquipmentInstanceType; + }) + .catch(() => { + console.log("Error retrieving equipment"); + }); +} + +export async function EquipmentInstanceUpdateAPI( + item: PatchEquipmentInstanceType, + id: number +) { + const config = await GetConfig(); + return instance + .patch(`api/v1/equipments/equipment_instances/${id}/`, item, config) + .then((response) => { + return [true, response.data as EquipmentInstanceType]; + }) + .catch((error) => { + console.log("Error updating equipment instance"); + return [false, ParseError(error)]; + }); +} + +export async function EquipmentInstanceRemoveAPI(id: number) { + const config = await GetConfig(); + return instance + .delete(`api/v1/equipments/equipment_instances/${id}/`, config) + .then((response) => { + return [true, response.data]; + }) + .catch((error) => { + console.log("Error updating equipment instance"); + return [false, ParseError(error)]; + }); +} + export async function EquipmentInstancesAPI() { const config = await GetConfig(); return instance diff --git a/src/Components/EditItemModal/EditItemModal.tsx b/src/Components/EditItemModal/EditItemModal.tsx new file mode 100644 index 0000000..bdcb36f --- /dev/null +++ b/src/Components/EditItemModal/EditItemModal.tsx @@ -0,0 +1,297 @@ +import { useEffect, useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import TextField from "@mui/material/TextField"; +import EditIcon from "@mui/icons-material/Edit"; +import Button from "../Button/Button"; +import { toast } from "react-toastify"; +import { + EquipmentInstanceAPI, + EquipmentInstanceRemoveAPI, + EquipmentInstanceUpdateAPI, +} from "../API/API"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import Radio from "@mui/material/Radio"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; +import { CircularProgress } from "@mui/material"; +import React from "react"; + +export default function EditItemModal(props: { + id: number; + setOpen: React.Dispatch>; +}) { + const queryClient = useQueryClient(); + const [item, setItem] = useState({ + remarks: "", + status: "", + }); + const [error, setError] = useState(""); + + const equipment = useQuery({ + queryKey: ["equipment_instance", props.id], + queryFn: () => EquipmentInstanceAPI(Number(props.id)), + }); + + useEffect(() => { + if (equipment.data) { + setItem({ + ...item, + remarks: equipment.data.remarks, + status: equipment.data.status, + }); + } + }, [equipment.data]); + + const update_mutation = useMutation({ + mutationFn: async () => { + const data = await EquipmentInstanceUpdateAPI(item, props.id); + if (data[0] != true) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["equipment_instances"] }); + queryClient.invalidateQueries({ + queryKey: ["equipment_instance", props.id], + }); + setError("Updated successfully"); + toast( + `Item updated successfuly, ${ + typeof data[1] == "object" ? "ID:" + data[1].id : "" + }`, + { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + } + ); + if (typeof data[1] == "object") { + setItem({ + ...item, + remarks: data[1].remarks, + status: data[1].status, + }); + } + }, + onError: (error) => { + setError(JSON.stringify(error)); + }, + }); + + const delete_mutation = useMutation({ + mutationFn: async () => { + const data = await EquipmentInstanceRemoveAPI(props.id); + if (data[0] != true) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["equipment_instances"] }); + queryClient.invalidateQueries({ + queryKey: ["equipment_instance", props.id], + }); + setError("Deleted successfully"); + toast("Item deleted successfuly", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + props.setOpen(false); + if (typeof data[1] == "object") { + setItem({ + ...item, + remarks: data[1].remarks, + status: data[1].status, + }); + } + }, + onError: (error) => { + setError(JSON.stringify(error)); + }, + }); + + if (equipment.isLoading) { + return ( +
    + +

    + Loading +

    +
    + ); + } + return ( + <> +
    + +

    Edit Item

    +
    + +
    + +
    +

    + Associated SKU: +

    +

    + {equipment.data?.equipment_name} + {" (SKU #" + equipment.data?.equipment + ")"} +

    +
    + + + Item Status + + ) => { + setItem({ ...item, status: e.target.value }); + setError(""); + }} + > +
    + } + label="Working" + style={styles.text_dark} + /> + } + label="Broken" + style={styles.text_dark} + /> + } + label="Under Maintenance" + style={styles.text_dark} + /> + } + label="Decomissioned" + style={styles.text_dark} + /> +
    +
    +
    + ) => { + setItem({ ...item, remarks: e.target.value }); + setError(""); + }} + value={item.remarks} + placeholder={"Optionally add a brief description of the item"} + /> +
    +

    {error}

    +
    +
    +
    + + ); +} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index 3f263ce..bab625f 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -45,6 +45,11 @@ export type AddEquipmentInstanceType = { remarks?: string; }; +export type PatchEquipmentInstanceType = { + status: string; + remarks?: string; +}; + export type EquipmentInstanceType = { id: number; equipment: string; diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index b56d28e..654c049 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -11,8 +11,13 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { colors } from "../../styles"; +import EditItemModal from "../../Components/EditItemModal/EditItemModal"; +import { useState } from "react"; +import Popup from "reactjs-popup"; export default function EquipmentInstancesListPage() { + const [editmodalOpen, SetEditModalOpen] = useState(false); + const [selectedItem, SetSelectedItem] = useState(0); const equipment_instances = useQuery({ queryKey: ["equipment_instances"], queryFn: EquipmentInstancesAPI, @@ -88,7 +93,8 @@ export default function EquipmentInstancesListPage() { key={equipment.id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} onClick={() => { - console.log("HEH"); + SetSelectedItem(equipment.id); + SetEditModalOpen(true); }} > @@ -129,6 +135,25 @@ export default function EquipmentInstancesListPage() {
    + SetEditModalOpen(false)} + modal + position={"top center"} + contentStyle={{ + width: "512px", + borderRadius: 16, + borderColor: "grey", + borderStyle: "solid", + borderWidth: 1, + padding: 16, + alignContent: "center", + justifyContent: "center", + textAlign: "center", + }} + > + +
    ); } From 3c0b2b97391312f3ca9b6112d2fa135cf8649262 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 23:36:33 +0800 Subject: [PATCH 38/47] Update component names --- .../EditItemInstanceModal.tsx} | 2 +- .../EquipmentInstancesListPage/EquipmentInstancesListPage.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/Components/{EditItemModal/EditItemModal.tsx => EditItemInstanceModal/EditItemInstanceModal.tsx} (99%) diff --git a/src/Components/EditItemModal/EditItemModal.tsx b/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx similarity index 99% rename from src/Components/EditItemModal/EditItemModal.tsx rename to src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx index bdcb36f..1ead57e 100644 --- a/src/Components/EditItemModal/EditItemModal.tsx +++ b/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx @@ -20,7 +20,7 @@ import { useQuery } from "@tanstack/react-query"; import { CircularProgress } from "@mui/material"; import React from "react"; -export default function EditItemModal(props: { +export default function EditItemInstanceModal(props: { id: number; setOpen: React.Dispatch>; }) { diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index 654c049..94425a3 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -11,7 +11,7 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { colors } from "../../styles"; -import EditItemModal from "../../Components/EditItemModal/EditItemModal"; +import EditItemModal from "../../Components/EditItemInstanceModal/EditItemInstanceModal"; import { useState } from "react"; import Popup from "reactjs-popup"; From 36a0111fe8e1c8f6d98b927c348005ef83662f04 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 23:53:26 +0800 Subject: [PATCH 39/47] Added edit and delete SKU functionality --- src/Components/API/API.tsx | 46 ++- src/Components/EditSKUModal/EditSKUModal.tsx | 299 ++++++++++++++++++ src/Components/Types/Types.tsx | 7 + .../EquipmentListPage/EquipmentListPage.tsx | 33 +- 4 files changed, 382 insertions(+), 3 deletions(-) create mode 100644 src/Components/EditSKUModal/EditSKUModal.tsx diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 322f794..e9ff3b1 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -12,6 +12,7 @@ import { AddEquipmentInstanceType, EquipmentInstanceType, PatchEquipmentInstanceType, + PatchEquipmentType, } from "../Types/Types"; const instance = axios.create({ @@ -164,6 +165,47 @@ export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) { // Equipment APIs +export async function EquipmentAPI(id: number) { + const config = await GetConfig(); + return instance + .get(`api/v1/equipments/equipments/${id}/`, config) + .then((response) => { + return response.data as EquipmentType; + }) + .catch(() => { + console.log("Error retrieving equipment"); + }); +} + +export async function EquipmentUpdateAPI( + equipment: PatchEquipmentType, + id: number +) { + const config = await GetConfig(); + return instance + .patch(`api/v1/equipments/equipments/${id}/`, equipment, config) + .then((response) => { + return [true, response.data as EquipmentType]; + }) + .catch((error) => { + console.log("Error updating equipment instance"); + return [false, ParseError(error)]; + }); +} + +export async function EquipmentRemoveAPI(id: number) { + const config = await GetConfig(); + return instance + .delete(`api/v1/equipments/equipments/${id}/`, config) + .then((response) => { + return [true, response.data as EquipmentType]; + }) + .catch((error) => { + console.log("Error deleting equipment instance"); + return [false, ParseError(error)]; + }); +} + export async function EquipmentsAPI() { const config = await GetConfig(); return instance @@ -194,7 +236,7 @@ export async function EquipmentCreateAPI(equipment: AddEquipmentType) { export async function EquipmentInstanceAPI(id: number) { const config = await GetConfig(); return instance - .get(`api/v1/equipments/equipment_instances/${id}`, config) + .get(`api/v1/equipments/equipment_instances/${id}/`, config) .then((response) => { return response.data as EquipmentInstanceType; }) @@ -227,7 +269,7 @@ export async function EquipmentInstanceRemoveAPI(id: number) { return [true, response.data]; }) .catch((error) => { - console.log("Error updating equipment instance"); + console.log("Error deleting equipment instance"); return [false, ParseError(error)]; }); } diff --git a/src/Components/EditSKUModal/EditSKUModal.tsx b/src/Components/EditSKUModal/EditSKUModal.tsx new file mode 100644 index 0000000..713421e --- /dev/null +++ b/src/Components/EditSKUModal/EditSKUModal.tsx @@ -0,0 +1,299 @@ +import { useEffect, useState } from "react"; +import styles from "../../styles"; +import { colors } from "../../styles"; +import TextField from "@mui/material/TextField"; +import EditIcon from "@mui/icons-material/Edit"; +import Button from "../Button/Button"; +import { toast } from "react-toastify"; +import { + EquipmentAPI, + EquipmentRemoveAPI, + EquipmentUpdateAPI, +} from "../API/API"; +import RadioGroup from "@mui/material/RadioGroup"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormControl from "@mui/material/FormControl"; +import FormLabel from "@mui/material/FormLabel"; +import Radio from "@mui/material/Radio"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useQuery } from "@tanstack/react-query"; +import { CircularProgress } from "@mui/material"; +import React from "react"; + +export default function EditSKUModal(props: { + id: number; + setOpen: React.Dispatch>; +}) { + const queryClient = useQueryClient(); + const [item, setItem] = useState({ + name: "", + description: "", + category: "", + }); + const [error, setError] = useState(""); + + const equipment = useQuery({ + queryKey: ["equipment", props.id], + queryFn: () => EquipmentAPI(Number(props.id)), + }); + + useEffect(() => { + if (equipment.data) { + setItem({ + ...item, + name: equipment.data.name, + description: equipment.data.description, + category: equipment.data.category, + }); + } + }, [equipment.data]); + + const update_mutation = useMutation({ + mutationFn: async () => { + const data = await EquipmentUpdateAPI(item, props.id); + if (data[0] != true) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["equipments"] }); + queryClient.invalidateQueries({ + queryKey: ["equipment", props.id], + }); + setError("Updated successfully"); + toast( + `Item updated successfuly, ${ + typeof data[1] == "object" ? "ID:" + data[1].id : "" + }`, + { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + } + ); + if (typeof data[1] == "object") { + setItem({ + ...item, + name: data[1].name, + description: data[1].description, + category: data[1].category, + }); + } + }, + onError: (error) => { + setError(JSON.stringify(error)); + }, + }); + + const delete_mutation = useMutation({ + mutationFn: async () => { + const data = await EquipmentRemoveAPI(props.id); + if (data[0] != true) { + return Promise.reject(new Error(JSON.stringify(data[1]))); + } + return data; + }, + onSuccess: (data) => { + queryClient.invalidateQueries({ queryKey: ["equipments"] }); + queryClient.invalidateQueries({ + queryKey: ["equipment", props.id], + }); + setError("Deleted successfully"); + toast("SKU deleted successfuly", { + position: "top-right", + autoClose: 2000, + hideProgressBar: false, + closeOnClick: true, + pauseOnHover: true, + draggable: true, + progress: undefined, + theme: "light", + }); + props.setOpen(false); + if (typeof data[1] == "object") { + setItem({ + ...item, + name: data[1].name, + description: data[1].description, + category: data[1].category, + }); + } + }, + onError: (error) => { + setError(JSON.stringify(error)); + }, + }); + + if (equipment.isLoading) { + return ( +
    + +

    + Loading +

    +
    + ); + } + return ( + <> +
    + +

    Edit SKU

    +
    + +
    + + ) => { + setItem({ ...item, name: e.target.value }); + setError(""); + }} + value={item.name} + placeholder={"Enter SKU name"} + /> + ) => + setItem({ ...item, description: e.target.value }) + } + value={item.description} + placeholder={"Give a brief description of the SKU"} + /> + + Category + + ) => { + setItem({ ...item, category: e.target.value }); + setError(""); + }} + value={item.category} + > +
    +
    + } + label="Workstation" + style={styles.text_dark} + /> + } + label="Networking" + style={styles.text_dark} + /> + } + label="CCTV" + style={styles.text_dark} + /> +
    +
    + } + label="Furniture" + style={styles.text_dark} + /> + } + label="Peripherals" + style={styles.text_dark} + /> + } + label="Miscellaneous" + style={styles.text_dark} + /> +
    +
    +
    +
    +
    +

    {error}

    +
    +
    +
    + + ); +} diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index bab625f..c7b6040 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -28,6 +28,12 @@ export type AddEquipmentType = { category?: string; }; +export type PatchEquipmentType = { + name: string; + description: string; + category?: string; +}; + export type EquipmentType = { id: number; name: string; @@ -35,6 +41,7 @@ export type EquipmentType = { last_updated: string; last_updated_by: string; date_added: string; + category: string; }; export type EquipmentListType = Array; diff --git a/src/Pages/EquipmentListPage/EquipmentListPage.tsx b/src/Pages/EquipmentListPage/EquipmentListPage.tsx index 83baf0b..c7576a2 100644 --- a/src/Pages/EquipmentListPage/EquipmentListPage.tsx +++ b/src/Pages/EquipmentListPage/EquipmentListPage.tsx @@ -11,8 +11,13 @@ import TableHead from "@mui/material/TableHead"; import TableRow from "@mui/material/TableRow"; import Paper from "@mui/material/Paper"; import { colors } from "../../styles"; +import EditSKUModal from "../../Components/EditSKUModal/EditSKUModal"; +import Popup from "reactjs-popup"; +import { useState } from "react"; export default function EquipmentListPage() { + const [editmodalOpen, SetEditModalOpen] = useState(false); + const [selectedItem, SetSelectedItem] = useState(0); const equipments = useQuery({ queryKey: ["equipments"], queryFn: EquipmentsAPI, @@ -73,6 +78,9 @@ export default function EquipmentListPage() { Description + + Category + Last Modified @@ -85,7 +93,8 @@ export default function EquipmentListPage() { key={equipment.id} sx={{ "&:last-child td, &:last-child th": { border: 0 } }} onClick={() => { - console.log("HEH"); + SetSelectedItem(equipment.id); + SetEditModalOpen(true); }} > @@ -98,6 +107,9 @@ export default function EquipmentListPage() { {equipment.description} + + {equipment.category} +
    + SetEditModalOpen(false)} + modal + position={"top center"} + contentStyle={{ + width: "512px", + borderRadius: 16, + borderColor: "grey", + borderStyle: "solid", + borderWidth: 1, + padding: 16, + alignContent: "center", + justifyContent: "center", + textAlign: "center", + }} + > + +
    ); } From 48867a847c1d6287b892f6321c6f88e241be4efb Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sat, 2 Dec 2023 23:55:55 +0800 Subject: [PATCH 40/47] Change popup sizing to rem --- .../EquipmentInstancesListPage/EquipmentInstancesListPage.tsx | 3 ++- src/Pages/EquipmentListPage/EquipmentListPage.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index 94425a3..0f6d0f9 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -141,7 +141,7 @@ export default function EquipmentInstancesListPage() { modal position={"top center"} contentStyle={{ - width: "512px", + width: "32rem", borderRadius: 16, borderColor: "grey", borderStyle: "solid", @@ -150,6 +150,7 @@ export default function EquipmentInstancesListPage() { alignContent: "center", justifyContent: "center", textAlign: "center", + flexWrap: "wrap", }} > diff --git a/src/Pages/EquipmentListPage/EquipmentListPage.tsx b/src/Pages/EquipmentListPage/EquipmentListPage.tsx index c7576a2..d0a6875 100644 --- a/src/Pages/EquipmentListPage/EquipmentListPage.tsx +++ b/src/Pages/EquipmentListPage/EquipmentListPage.tsx @@ -141,7 +141,7 @@ export default function EquipmentListPage() { modal position={"top center"} contentStyle={{ - width: "512px", + width: "32rem", borderRadius: 16, borderColor: "grey", borderStyle: "solid", @@ -150,6 +150,7 @@ export default function EquipmentListPage() { alignContent: "center", justifyContent: "center", textAlign: "center", + flexWrap: "wrap", }} > From 6783b02163fbe878f1f1386e314e504cfde2b312 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 3 Dec 2023 00:04:03 +0800 Subject: [PATCH 41/47] Disabled exhaustive deps alert for useEffect functions for edit modals --- src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx | 1 + src/Components/EditSKUModal/EditSKUModal.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx b/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx index 1ead57e..9fcc0f4 100644 --- a/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx +++ b/src/Components/EditItemInstanceModal/EditItemInstanceModal.tsx @@ -44,6 +44,7 @@ export default function EditItemInstanceModal(props: { status: equipment.data.status, }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [equipment.data]); const update_mutation = useMutation({ diff --git a/src/Components/EditSKUModal/EditSKUModal.tsx b/src/Components/EditSKUModal/EditSKUModal.tsx index 713421e..35fed94 100644 --- a/src/Components/EditSKUModal/EditSKUModal.tsx +++ b/src/Components/EditSKUModal/EditSKUModal.tsx @@ -46,6 +46,7 @@ export default function EditSKUModal(props: { category: equipment.data.category, }); } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [equipment.data]); const update_mutation = useMutation({ From fb41277746dc806d9d3cf76c0cfabcc0359c344d Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Sun, 3 Dec 2023 01:15:05 +0800 Subject: [PATCH 42/47] Make dashboard widgets more mobile responsive --- src/Components/API/API.tsx | 17 ++++++++++++++++- src/Pages/DashboardPage/DashboardPage.tsx | 12 ++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index e9ff3b1..3279505 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -15,8 +15,17 @@ import { PatchEquipmentType, } from "../Types/Types"; +const debug = false; +let backendURL; + +if (debug) { + backendURL = "http://localhost:8000/"; +} else { + backendURL = "https://equipment-tracker-backend.keannu1.duckdns.org/"; +} + const instance = axios.create({ - baseURL: "http://localhost:8000/", + baseURL: backendURL, }); // Token Handling @@ -136,9 +145,11 @@ export function ActivationAPI(activation: ActivationType) { .post("api/v1/accounts/users/activation/", activation) .then(() => { console.log("Activation Success"); + return true; }) .catch(() => { console.log("Activation failed"); + return false; }); } export function ResetPasswordAPI(email: string) { @@ -146,9 +157,11 @@ export function ResetPasswordAPI(email: string) { .post("api/v1/accounts/users/reset_password/", { email: email }) .then(() => { console.log("Activation Success"); + return true; }) .catch(() => { console.log("Activation failed"); + return false; }); } @@ -157,9 +170,11 @@ export function ResetPasswordConfirmAPI(info: ResetPasswordConfirmType) { .post("api/v1/accounts/users/reset_password_confirm/", info) .then(() => { console.log("Reset Success"); + return true; }) .catch(() => { console.log("Reset failed"); + return false; }); } diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index d495439..62e3df3 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -87,7 +87,7 @@ export default function Dashboard() { backgroundColor: "#a6a6a6", alignSelf: "center", justifyContent: "center", - width: "32rem", + width: "16rem", }} >

    Date: Sun, 3 Dec 2023 11:26:16 +0800 Subject: [PATCH 43/47] Fixed landing page being broken in mobile and merged popup styling --- .../EquipmentInstancesListPage.tsx | 13 +------------ src/Pages/EquipmentListPage/EquipmentListPage.tsx | 13 +------------ src/Pages/LandingPage/LandingPage.tsx | 7 ++----- src/styles.tsx | 12 ++++++++++++ 4 files changed, 16 insertions(+), 29 deletions(-) diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index 0f6d0f9..e6d15fc 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -140,18 +140,7 @@ export default function EquipmentInstancesListPage() { onClose={() => SetEditModalOpen(false)} modal position={"top center"} - contentStyle={{ - width: "32rem", - borderRadius: 16, - borderColor: "grey", - borderStyle: "solid", - borderWidth: 1, - padding: 16, - alignContent: "center", - justifyContent: "center", - textAlign: "center", - flexWrap: "wrap", - }} + contentStyle={styles.popup_center} > diff --git a/src/Pages/EquipmentListPage/EquipmentListPage.tsx b/src/Pages/EquipmentListPage/EquipmentListPage.tsx index d0a6875..6d6149f 100644 --- a/src/Pages/EquipmentListPage/EquipmentListPage.tsx +++ b/src/Pages/EquipmentListPage/EquipmentListPage.tsx @@ -140,18 +140,7 @@ export default function EquipmentListPage() { onClose={() => SetEditModalOpen(false)} modal position={"top center"} - contentStyle={{ - width: "32rem", - borderRadius: 16, - borderColor: "grey", - borderStyle: "solid", - borderWidth: 1, - padding: 16, - alignContent: "center", - justifyContent: "center", - textAlign: "center", - flexWrap: "wrap", - }} + contentStyle={styles.popup_center} > diff --git a/src/Pages/LandingPage/LandingPage.tsx b/src/Pages/LandingPage/LandingPage.tsx index 6e18c0e..2381447 100644 --- a/src/Pages/LandingPage/LandingPage.tsx +++ b/src/Pages/LandingPage/LandingPage.tsx @@ -39,14 +39,11 @@ export default function LandingPage() { flexWrap: "wrap", }} > -

    - +
    +
    Date: Sun, 3 Dec 2023 13:19:08 +0800 Subject: [PATCH 44/47] Fixed table rendering being broken when there are too many records and added logs pages --- src/App.tsx | 22 +++ src/Components/API/API.tsx | 25 ++++ src/Components/Types/Types.tsx | 26 ++++ src/Pages/DashboardPage/DashboardPage.tsx | 82 ++++++++++ .../EquipmentInstanceLogsPage.tsx | 140 ++++++++++++++++++ .../EquipmentInstancesListPage.tsx | 3 +- .../EquipmentListPage/EquipmentListPage.tsx | 3 +- .../EquipmentLogsPage/EquipmentLogsPage.tsx | 138 +++++++++++++++++ 8 files changed, 435 insertions(+), 4 deletions(-) create mode 100644 src/Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage.tsx create mode 100644 src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 76e1368..013db34 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,8 @@ import ActivationPage from "./Pages/ActivationPage/ActivationPage"; import ResetPasswordPage from "./Pages/ResetPasswordPage/ResetPasswordPage"; import EquipmentInstancesListPage from "./Pages/EquipmentInstancesListPage/EquipmentInstancesListPage"; import EquipmentListPage from "./Pages/EquipmentListPage/EquipmentListPage"; +import EquipmentLogsPage from "./Pages/EquipmentLogsPage/EquipmentLogsPage"; +import EquipmentInstanceLogsPage from "./Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage"; const queryClient = new QueryClient(); const router = createHashRouter([ @@ -46,6 +48,16 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/view/equipment_instances/logs", + element: ( + <> + + + + ), + errorElement: , + }, { path: "/view/equipments", element: ( @@ -56,6 +68,16 @@ const router = createHashRouter([ ), errorElement: , }, + { + path: "/view/equipments/logs", + element: ( + <> + + + + ), + errorElement: , + }, { path: "/activation/:uid/:token", element: ( diff --git a/src/Components/API/API.tsx b/src/Components/API/API.tsx index 3279505..d410b8e 100644 --- a/src/Components/API/API.tsx +++ b/src/Components/API/API.tsx @@ -13,6 +13,8 @@ import { EquipmentInstanceType, PatchEquipmentInstanceType, PatchEquipmentType, + EquipmentLogListType, + EquipmentInstanceLogListType, } from "../Types/Types"; const debug = false; @@ -246,8 +248,31 @@ export async function EquipmentCreateAPI(equipment: AddEquipmentType) { }); } +export async function EquipmentLogsAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/equipments/equipments/logs", config) + .then((response) => { + return response.data as EquipmentLogListType; + }) + .catch(() => { + console.log("Error retrieving equipment logs"); + }); +} + // Equipment Instances APIs +export async function EquipmentInstanceLogsAPI() { + const config = await GetConfig(); + return instance + .get("api/v1/equipments/equipment_instances/logs", config) + .then((response) => { + return response.data as EquipmentInstanceLogListType; + }) + .catch(() => { + console.log("Error retrieving equipment logs"); + }); +} export async function EquipmentInstanceAPI(id: number) { const config = await GetConfig(); return instance diff --git a/src/Components/Types/Types.tsx b/src/Components/Types/Types.tsx index c7b6040..b2af722 100644 --- a/src/Components/Types/Types.tsx +++ b/src/Components/Types/Types.tsx @@ -46,6 +46,18 @@ export type EquipmentType = { export type EquipmentListType = Array; +export type EquipmentLogType = { + history_id: number; + id: number; + name: string; + category: string; + description: string; + history_date: string; + history_user: string; +}; + +export type EquipmentLogListType = Array; + export type AddEquipmentInstanceType = { equipment: number; status: string; @@ -70,3 +82,17 @@ export type EquipmentInstanceType = { }; export type EquipmentInstanceListType = Array; + +export type EquipmentInstanceLogType = { + history_id: number; + id: number; + equipment: number; + equipment_name: string; + category: string; + status: string; + remarks: string; + history_date: string; + history_user: string; +}; + +export type EquipmentInstanceLogListType = Array; diff --git a/src/Pages/DashboardPage/DashboardPage.tsx b/src/Pages/DashboardPage/DashboardPage.tsx index 62e3df3..92cf0f1 100644 --- a/src/Pages/DashboardPage/DashboardPage.tsx +++ b/src/Pages/DashboardPage/DashboardPage.tsx @@ -11,6 +11,7 @@ import FormatListBulletedIcon from "@mui/icons-material/FormatListBulleted"; import AddToQueueIcon from "@mui/icons-material/AddToQueue"; import NoteAddIcon from "@mui/icons-material/NoteAdd"; import NoteIcon from "@mui/icons-material/Note"; +import ManageSearchIcon from "@mui/icons-material/ManageSearch"; import { colors } from "../../styles"; import { useNavigate } from "react-router-dom"; import { useState } from "react"; @@ -491,6 +492,87 @@ export default function Dashboard() {

    +

    + Logs +

    +
    + + +
    SetAddSKUModalOpen(false)} diff --git a/src/Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage.tsx b/src/Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage.tsx new file mode 100644 index 0000000..80dd527 --- /dev/null +++ b/src/Pages/EquipmentInstanceLogsPage/EquipmentInstanceLogsPage.tsx @@ -0,0 +1,140 @@ +import { useQuery } from "@tanstack/react-query"; +import Header from "../../Components/Header/Header"; +import styles from "../../styles"; +import { EquipmentInstanceLogsAPI } from "../../Components/API/API"; +import { CircularProgress } from "@mui/material"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import { colors } from "../../styles"; + +export default function EquipmentInstanceLogsPage() { + const equipment_instance_logs = useQuery({ + queryKey: ["equipment_instance_logs"], + queryFn: EquipmentInstanceLogsAPI, + }); + if (equipment_instance_logs.isLoading) { + return ( +
    +
    +
    + +

    + Loading +

    +
    +
    + ); + } + return ( +
    +
    +
    +
    + + + + + + Transaction ID + + + Item ID + + + SKU + + + Remarks + + + Status + + + Date Modified + + + + + {equipment_instance_logs.data ? ( + equipment_instance_logs.data.map((equipment_instance_log) => ( + + + {equipment_instance_log.history_id} + + + {equipment_instance_log.id} + + + {`SKU #${equipment_instance_log.equipment} - ${equipment_instance_log.equipment_name}`} + + + + {equipment_instance_log.remarks} + + + {equipment_instance_log.status} + + +
    +
    {equipment_instance_log.history_date}
    +
    + {equipment_instance_log.history_user + ? "by " + equipment_instance_log.history_user + : ""} +
    +
    +
    +
    + )) + ) : ( + <> + )} +
    +
    +
    +
    +
    +
    + ); +} diff --git a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx index e6d15fc..5762f60 100644 --- a/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx +++ b/src/Pages/EquipmentInstancesListPage/EquipmentInstancesListPage.tsx @@ -63,10 +63,9 @@ export default function EquipmentInstancesListPage() { minHeight: "100%", minWidth: "100%", flexWrap: "wrap", - overflowY: "scroll", }} > -
    +
    diff --git a/src/Pages/EquipmentListPage/EquipmentListPage.tsx b/src/Pages/EquipmentListPage/EquipmentListPage.tsx index 6d6149f..c2033e8 100644 --- a/src/Pages/EquipmentListPage/EquipmentListPage.tsx +++ b/src/Pages/EquipmentListPage/EquipmentListPage.tsx @@ -63,10 +63,9 @@ export default function EquipmentListPage() { minHeight: "100%", minWidth: "100%", flexWrap: "wrap", - overflowY: "scroll", }} > -
    +
    diff --git a/src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx b/src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx new file mode 100644 index 0000000..8f30b2b --- /dev/null +++ b/src/Pages/EquipmentLogsPage/EquipmentLogsPage.tsx @@ -0,0 +1,138 @@ +import { useQuery } from "@tanstack/react-query"; +import Header from "../../Components/Header/Header"; +import styles from "../../styles"; +import { EquipmentLogsAPI } from "../../Components/API/API"; +import { CircularProgress } from "@mui/material"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TableRow from "@mui/material/TableRow"; +import Paper from "@mui/material/Paper"; +import { colors } from "../../styles"; + +export default function EquipmentLogsPage() { + const equipment_logs = useQuery({ + queryKey: ["equipment_logs"], + queryFn: EquipmentLogsAPI, + }); + if (equipment_logs.isLoading) { + return ( +
    +
    +
    + +

    + Loading +

    +
    +
    + ); + } + return ( +
    +
    +
    +
    + +
    + + + + Transaction ID + + + SKU ID + + + Name + + + Description + + + Category + + + Date Modified + + + + + {equipment_logs.data ? ( + equipment_logs.data.map((equipment_log) => ( + + + {equipment_log.history_id} + + + {equipment_log.id} + + + {equipment_log.name} + + + + {equipment_log.description} + + + {equipment_log.category} + + +
    +
    {equipment_log.history_date}
    +
    + {equipment_log.history_user + ? "by " + equipment_log.history_user + : ""} +
    +
    +
    +
    + )) + ) : ( + <> + )} +
    +
    +
    +
    +
    +
    + ); +} From b45230f5c76b36c353fc8ec7e00a598f5954e44a Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 4 Dec 2023 13:38:17 +0800 Subject: [PATCH 45/47] Test commit --- test.test | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test.test diff --git a/test.test b/test.test new file mode 100644 index 0000000..e69de29 From c4f503e4a8e448a950c7b05446abbb9b0ed24810 Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 4 Dec 2023 13:40:46 +0800 Subject: [PATCH 46/47] Test commit 2 --- test.test | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test.test diff --git a/test.test b/test.test deleted file mode 100644 index e69de29..0000000 From 89f9808719be129dc82c09ae5c4f7d5c2ed2b3ad Mon Sep 17 00:00:00 2001 From: Keannu Bernasol Date: Mon, 4 Dec 2023 19:16:47 +0800 Subject: [PATCH 47/47] added woodpecker.yml --- woodpecker.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 woodpecker.yml diff --git a/woodpecker.yml b/woodpecker.yml new file mode 100644 index 0000000..a58b1ea --- /dev/null +++ b/woodpecker.yml @@ -0,0 +1,17 @@ +pipeline: + - name: build + image: node:14 + commands: + - npm install + - npm run build + - name: copy + image: alpine + environment: + - SSH_KEY: + from_secret: ssh_key + commands: + - apk add --no-cache openssh-client + - echo "$SSH_KEY" | tr -d '\r' > /root/.ssh/id_rsa + - chmod 600 /root/.ssh/id_rsa + - echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > /root/.ssh/config + - scp -r dist/* username@10.0.10.4:/mnt/sda1/projects/equipment_tracker_frontend