From b2b10f54bd3b05e231ef34e9fde72a6d7a6d4d6a Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 30 Jun 2024 09:45:13 +0800 Subject: [PATCH 01/63] feat: use typescript --- frontend/index.html | 2 +- frontend/package-lock.json | 24 +++++++++++++++---- frontend/package.json | 7 +++--- frontend/src/{App.jsx => App.tsx} | 9 ++++--- frontend/src/{main.jsx => main.tsx} | 4 ++-- frontend/tsconfig.json | 26 +++++++++++++++++++++ frontend/tsconfig.node.json | 10 ++++++++ frontend/vite-env.d.ts | 1 + frontend/{vite.config.js => vite.config.ts} | 0 9 files changed, 69 insertions(+), 14 deletions(-) rename frontend/src/{App.jsx => App.tsx} (52%) rename frontend/src/{main.jsx => main.tsx} (62%) create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite-env.d.ts rename frontend/{vite.config.js => vite.config.ts} (100%) diff --git a/frontend/index.html b/frontend/index.html index 7d838f1..86b354f 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,6 +8,6 @@
- + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ec4154f..6caf98f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,8 +20,8 @@ "sweetalert2-react-content": "^5.0.7" }, "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", "eslint": "^8.57.0", @@ -30,6 +30,7 @@ "eslint-plugin-react-refresh": "^0.4.6", "postcss": "^8.4.38", "tailwindcss": "^3.4.3", + "typescript": "^5.5.2", "vite": "^5.2.0" } }, @@ -1279,9 +1280,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.3.2", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.2.tgz", - "integrity": "sha512-Btgg89dAnqD4vV7R3hlwOxgqobUQKgx3MmrQRi0yYbs/P0ym8XozIAlkqVilPqHQwXs4e9Tf63rrCgl58BcO4w==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -5154,6 +5155,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 4db38d7..484e585 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,7 +5,7 @@ "type": "module", "scripts": { "dev": "vite", - "build": "vite build", + "build": "tsc && vite build", "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview" }, @@ -22,8 +22,8 @@ "sweetalert2-react-content": "^5.0.7" }, "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", "eslint": "^8.57.0", @@ -32,6 +32,7 @@ "eslint-plugin-react-refresh": "^0.4.6", "postcss": "^8.4.38", "tailwindcss": "^3.4.3", + "typescript": "^5.5.2", "vite": "^5.2.0" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.tsx similarity index 52% rename from frontend/src/App.jsx rename to frontend/src/App.tsx index 7dcb539..38e27b6 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.tsx @@ -2,14 +2,17 @@ import Home from "./pages/index"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; import Laporan from "./pages/laporan"; import WebSocketDemo from "./pages/index"; +import MainLayout from "./layout/MainLayout"; function App() { return ( - } /> - } /> - } /> + }> + } /> + } /> + } /> + ); diff --git a/frontend/src/main.jsx b/frontend/src/main.tsx similarity index 62% rename from frontend/src/main.jsx rename to frontend/src/main.tsx index b91620d..6b8de68 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.tsx @@ -1,9 +1,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App.jsx"; +import App from "./App"; import "./index.css"; -ReactDOM.createRoot(document.getElementById("root")).render( +ReactDOM.createRoot(document.getElementById("root")!!).render( diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..744b105 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["./src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite-env.d.ts b/frontend/vite-env.d.ts new file mode 100644 index 0000000..151aa68 --- /dev/null +++ b/frontend/vite-env.d.ts @@ -0,0 +1 @@ +/// \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.ts similarity index 100% rename from frontend/vite.config.js rename to frontend/vite.config.ts From 6a84abc538cf1eb368e60143d2ab9e6146aff725 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 30 Jun 2024 09:45:58 +0800 Subject: [PATCH 02/63] feat: cleanup --- frontend/src/components/ImageStreamViewer.tsx | 34 +++ frontend/src/components/inspector.jsx | 60 ++++ frontend/src/components/sidebar.jsx | 77 ----- frontend/src/components/sidebar.tsx | 106 +++++++ frontend/src/layout/MainLayout.tsx | 13 + frontend/src/pages/index.jsx | 267 +----------------- frontend/src/pages/laporan.jsx | 136 +++++---- 7 files changed, 293 insertions(+), 400 deletions(-) create mode 100644 frontend/src/components/ImageStreamViewer.tsx create mode 100644 frontend/src/components/inspector.jsx delete mode 100644 frontend/src/components/sidebar.jsx create mode 100644 frontend/src/components/sidebar.tsx create mode 100644 frontend/src/layout/MainLayout.tsx diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx new file mode 100644 index 0000000..4d24163 --- /dev/null +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -0,0 +1,34 @@ +import { useEffect, useState } from "react"; + +const getBlobUrl = (data: any) => { + if(data) { + return `data:image/jpeg;base64, ${data}`; + } + return + +}; + +export function ImageStreamViewer({ + socketEvent, +}: { + socketEvent: MessageEvent | null; +}) { + const [base64String, setBase64String] = useState( + undefined + ); + useEffect(() => { + setBase64String(getBlobUrl(socketEvent?.data)); + console.log(`sizedata: ${base64String}`) + }, [socketEvent]); + return ( +
+ {base64String ? ( + CCTV Stream + ) : ( +

+ Tidak Ada Video CCTV +

+ )} +
+ ); +} diff --git a/frontend/src/components/inspector.jsx b/frontend/src/components/inspector.jsx new file mode 100644 index 0000000..f256a38 --- /dev/null +++ b/frontend/src/components/inspector.jsx @@ -0,0 +1,60 @@ +import { IoMdRefresh } from "react-icons/io"; +import { MdOutlinePlayCircle } from "react-icons/md"; + +const Inspector = () => { + return ( +
+
+

OPSI :

+

Tampilan Label Prediksi

+
+
+
+

SOURCE :

+ +
+

CAMERA SOURCE URL (RTSP)

+
+ + +
+ +

+ CAMERA WITH LABELED SOURCE (RTSP) +

+
+ + +
+ +

LABEL SOURCE (WEBSOCKET)

+
+ + +
+
+
+ ); +}; + +export {Inspector} \ No newline at end of file diff --git a/frontend/src/components/sidebar.jsx b/frontend/src/components/sidebar.jsx deleted file mode 100644 index 6413f05..0000000 --- a/frontend/src/components/sidebar.jsx +++ /dev/null @@ -1,77 +0,0 @@ -import { useState } from "react"; -import { useNavigate, useLocation } from "react-router-dom"; -import { IoMdArrowDropleft } from "react-icons/io"; -import { MdMonitor } from "react-icons/md"; -import { HiClipboardDocumentList } from "react-icons/hi2"; - -export default function Sidebar() { - const [open, setOpen] = useState(false); // Mulai dari tertutup - const navigate = useNavigate(); - const location = useLocation(); - - const Menus = [ - { title: "Tampilan Langsung", path: "/" }, - { - title: "Laporan", - path: "/laporan", - icon: , - }, - ]; - - return ( - <> -
- {/* Title */} - setOpen(!open)} - /> -
- -

- Dashboard -

-
- - {/* Sub Menu */} -
    - {Menus.map((menu, index) => ( -
  • navigate(menu.path)} - > - - {menu.icon ? menu.icon : } - - - {menu.title} - -
  • - ))} -
-
- - ); -} diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx new file mode 100644 index 0000000..22fb40a --- /dev/null +++ b/frontend/src/components/sidebar.tsx @@ -0,0 +1,106 @@ +import { useState } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { IoMdArrowDropleft } from "react-icons/io"; +import { MdMonitor } from "react-icons/md"; +import { HiClipboardDocumentList } from "react-icons/hi2"; +import { MdOutlineVideoLabel } from "react-icons/md"; + +function TitleSidebar() { + return ( +
+ +
+

+ Dashboard +

+
+ ); +} + +function SubMenu({ + title, + icon, + isActive, + onClick, + isExpanded, +}: { + title: string; + icon: JSX.Element; + isActive: boolean; + isExpanded: boolean; + onClick: () => void; +}) { + const mainColor = isActive + ? "bg-[#F59024] text-white" + : "hover:bg-[#F59024] hover:text-white"; + + return ( +
  • + {icon} + + {title} + +
  • + ); +} + +export default function Sidebar() { + const [open, setOpen] = useState(true); + const navigate = useNavigate(); + const location = useLocation(); + + const Menus = [ + { title: "Tampilan Langsung", path: "/", icon: }, + { + title: "Laporan", + path: "/laporan", + icon: , + }, + ]; + + return ( + <> +
    + {/* setOpen(!open)} + /> */} + + + {/* Sub Menu */} +
      + {Menus.map((menu, index) => ( + navigate(menu.path)} + title={menu.title} + isExpanded={open} + /> + ))} +
    +
    + + ); +} diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx new file mode 100644 index 0000000..e0e527f --- /dev/null +++ b/frontend/src/layout/MainLayout.tsx @@ -0,0 +1,13 @@ +import { Outlet } from "react-router-dom"; +import Sidebar from "../components/sidebar"; + +export default function MainLayout() { + return ( +
    + +
    + +
    +
    + ); +} \ No newline at end of file diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index b1a0d26..ecf0e42 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -1,267 +1,30 @@ -import { useEffect, useState, useRef } from "react"; -import Sidebar from "../components/sidebar"; +import { useEffect, useState} from "react"; import Title from "../components/titleDashboard"; -import Flashphoner from "@flashphoner/websdk"; -import { MdOutlinePlayCircle } from "react-icons/md"; -import Swal from "sweetalert2"; -import { IoMdRefresh } from "react-icons/io"; import useWebSocket from "react-use-websocket"; -import { FaCirclePlay } from "react-icons/fa6"; -import { PiVideoCameraFill } from "react-icons/pi"; +import { Inspector } from "../components/inspector"; +import { ImageStreamViewer } from "../components/ImageStreamViewer"; const Home = () => { - const [labelVisible, setLabelVisible] = useState(false); - const [RTSP_URL, setRTSPUrl] = useState(""); - const [newRTSPUrl, setNewRTSPUrl] = useState(""); const [socketUrl] = useState("ws://localhost:7000/v1/test"); - const [, setStreamFailed] = useState(false); const { lastMessage } = useWebSocket(socketUrl); - const [videoPlaying, setVideoPlaying] = useState(false); - const getBlobUrl = (data) => { - try { - return `data:image/jpeg;base64, ${data}`; - } catch { - return null; - } - }; - useEffect(() => {}, [lastMessage]); - - const toggleLabelVisibility = () => { - setLabelVisible(!labelVisible); - }; - - const sessionRef = useRef(null); - const streamRef = useRef(null); - - useEffect(() => { - const initApi = async () => { - console.log("Inisialisasi Flashphoner API"); - await Flashphoner.init({}); - connect(); - }; - - const connect = () => { - if (!sessionRef.current) { - sessionRef.current = Flashphoner.createSession({ - urlServer: "wss://demo.flashphoner.com", - }) - .on(Flashphoner.constants.SESSION_STATUS.ESTABLISHED, (session) => { - console.log("Koneksi WebSocket berhasil dibuat"); - if (videoPlaying) { - playStream(session); - } - }) - .on(Flashphoner.constants.SESSION_STATUS.DISCONNECTED, (error) => { - console.error("Koneksi WebSocket terputus:", error); - }) - .on(Flashphoner.constants.SESSION_STATUS.FAILED, (error) => { - console.error("Koneksi WebSocket gagal dibuat:", error); - }); - } - }; - - const playStream = (session) => { - if (streamRef.current) { - streamRef.current.stop(); - streamRef.current = null; - } - - streamRef.current = session - .createStream({ - name: RTSP_URL, - display: document.getElementById("play"), - }) - .on(Flashphoner.constants.STREAM_STATUS.PLAYING, () => { - console.log("Stream mulai diputar"); - setStreamFailed(false); - }) - .on(Flashphoner.constants.STREAM_STATUS.STOPPED, () => { - console.log("Stream dihentikan"); - }) - .on(Flashphoner.constants.STREAM_STATUS.FAILED, (error) => { - console.error("Stream gagal diputar:", error); - Swal.fire({ - icon: "error", - title: "Error!", - text: "Terjadi kesalahan saat memutar stream.", - }); - setStreamFailed(true); - }) - .play(); - }; - - if (RTSP_URL && videoPlaying) { - initApi(); - } - - return () => { - if (streamRef.current) { - streamRef.current.stop(); - streamRef.current = null; - } - if (sessionRef.current) { - sessionRef.current.disconnect(); - sessionRef.current = null; - } - }; - }, [RTSP_URL, videoPlaying]); - - const handleRTSPUrlChange = () => { - Swal.fire({ - title: "Are you sure?", - text: "Are you sure you want to use this RTSP URL?", - icon: "warning", - showCancelButton: true, - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33", - confirmButtonText: "Yes, use it!", - }).then((result) => { - if (result.isConfirmed) { - setRTSPUrl(newRTSPUrl); - Swal.fire({ - title: "Success!", - text: "Your RTSP URL has been updated.", - icon: "success", - }); - } - }); - }; - - const handleRefresh = () => { - console.log("Refreshing..."); - if (streamRef.current) { - streamRef.current.stop(); - streamRef.current = null; - } - - if (sessionRef.current) { - sessionRef.current.disconnect(); - sessionRef.current = null; - } - - setTimeout(() => { - setRTSPUrl((prevUrl) => { - const newUrl = prevUrl.includes("?") ? prevUrl.split("?")[0] : prevUrl; - return `${newUrl}?refresh=${Date.now()}`; - }); - }, 500); - }; - - const startVideo = () => { - Swal.fire({ - title: "Start Recording?", - text: "Are you sure you want to start recording CCTV video?", - icon: "question", - showCancelButton: true, - confirmButtonColor: "#3085d6", - cancelButtonColor: "#d33", - confirmButtonText: "Yes, start recording!", - }).then((result) => { - if (result.isConfirmed) { - setVideoPlaying(true); // Mengaktifkan pemutaran video - } - }); - }; + useEffect(() => {}, [lastMessage]); return ( -
    -
    - -
    -
    - - <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> - <div className="flex"> - <div className="flex-[74%] mt-2 text-center"> - <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> - <h3>Tanggal: 19 Februari 2024</h3> - <div className=" object-cover h-[600px] w-[75rem] border-4 border-solid rounded-lg flex place-content-center mt-5 px-10"> - <div id="play"></div> - {(lastMessage?.data && ( - <img src={getBlobUrl(lastMessage?.data)} alt="CCTV Stream" /> - )) || ( - <h1 className="place-content-center text-2xl bg-slate-400 mx-auto my-auto px-7 py-2 rounded-lg bg-opacity-30"> - Tidak Ada Video CCTV - </h1> - )} - </div> - <br /> - <div className="flex text-slate-700 items-center text-xl"> - <PiVideoCameraFill className="text-2xl" /> - <p className="ml-3">Mulai Merekam?</p> - </div> - <div className="flex items-center mt-3"> - <button - className="flex items-center bg-slate-700 px-5 py-2 text-xl font-semibold text-white mb-10 rounded-lg hover:bg-slate-800" - onClick={startVideo} - > - <FaCirclePlay className="mr-3" /> - Mulai - </button> - </div> - </div> - <div className="border-l border-[#66ABB1] h-[40rem]"></div> - <div> - <div className="flex-[45%] mx-5 mt-2"> - <h1 className="font-bold text-xl">OPSI :</h1> - <h1 className="font-semibold text-md">Tampilan Label Prediksi</h1> - <button - onClick={toggleLabelVisibility} - className={`px-4 py-2 rounded-md mt-2 ${ - labelVisible ? "bg-[#F59024]" : "bg-[#2C353C]" - } text-white`} - > - {labelVisible ? "Tampilkan" : "Sembunyikan"} - </button> - </div> - <div className="flex-[40%] mx-5 mt-10"> - <div className="flex items-center"> - <h1 className="font-bold text-xl">SOURCE :</h1> - <button - className="ml-2 focus:outline-none" - onClick={handleRefresh} - > - <IoMdRefresh size={20} /> - </button> - </div> - <h1 className="font-semibold text-sm"> - CAMERA SOURCE URL (RTSP) - </h1> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="RTSP://localhost:8082/A301.live" - value={newRTSPUrl} - onChange={(e) => setNewRTSPUrl(e.target.value)} - /> - <button - className="ml-2 focus:outline-none" - onClick={handleRTSPUrlChange} - > - <MdOutlinePlayCircle size={20} /> - </button> - </div> - - <h1 className="font-semibold text-sm mt-5"> - CAMERA WITH LABELED SOURCE (RTSP) - </h1> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="RTSP://localhost:8082/A301-label.live" - /> - <button className="ml-2 focus:outline-none"> - <MdOutlinePlayCircle size={20} /> - </button> - </div> - </div> - </div> + <div className="w-full p-4"> + <Title /> + <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> + <div className="flex"> + <div className="flex-[74%] mt-2 text-center"> + <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> + <h3>Tanggal: 19 Februari 2024</h3> + <ImageStreamViewer socketEvent={lastMessage} /> + <br /> </div> + <div className="border-l border-[#66ABB1] h-[40rem]"></div> + <Inspector /> </div> </div> ); diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/laporan.jsx index 18012a3..dbe4b78 100644 --- a/frontend/src/pages/laporan.jsx +++ b/frontend/src/pages/laporan.jsx @@ -1,5 +1,4 @@ import { useState } from "react"; -import Sidebar from "../components/sidebar"; import Title from "../components/titleDashboard"; import { DataCctv as initialDataCctv } from "../model/dataset"; import { FaEdit } from "react-icons/fa"; @@ -32,77 +31,72 @@ export default function Laporan() { }; return ( - <div className="flex font-primary"> - <div className="fixed z-50"> - <Sidebar /> - </div> - <div className="w-full container mx-auto ml-28"> - <Title /> - <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> - {/* Data Pengunjung */} - <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> - <div className="overflow-x-auto pr-5 lg:pr-0"> - <table className="table-auto w-full"> - <thead> - <tr> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> - No - </th> - <th className="px-4 py-2 bg-slate-500 text-white">ID</th> - <th className="px-4 py-2 bg-slate-500 text-white">Status</th> - <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> - <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> - Edit - </th> - </tr> - </thead> - <tbody> - {dataCctv.map((entry, index) => ( - <tr - key={index} - className={ - index % 2 === 0 ? "bg-primary-50" : "bg-primary-100" - } + <div className="w-full container mx-auto ml-28"> + <Title /> + <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> + {/* Data Pengunjung */} + <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> + <div className="overflow-x-auto pr-5 lg:pr-0"> + <table className="table-auto w-full"> + <thead> + <tr> + <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> + No + </th> + <th className="px-4 py-2 bg-slate-500 text-white">ID</th> + <th className="px-4 py-2 bg-slate-500 text-white">Status</th> + <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> + <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> + <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> + Edit + </th> + </tr> + </thead> + <tbody> + {dataCctv.map((entry, index) => ( + <tr + key={index} + className={ + index % 2 === 0 ? "bg-primary-50" : "bg-primary-100" + } + > + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + {index + 1} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} + > + {entry.id} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} > - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {index + 1} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.id} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.status} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.persen} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {entry.nama} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - <FaEdit - className="cursor-pointer" - onClick={() => handleEdit(index)} - /> - </td> - </tr> - ))} - </tbody> - </table> - </div> + {entry.status} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} + > + {entry.persen} + </td> + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + {entry.nama} + </td> + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + <FaEdit + className="cursor-pointer" + onClick={() => handleEdit(index)} + /> + </td> + </tr> + ))} + </tbody> + </table> </div> </div> </div> From d93b9390c925b745675d69b60389a6243189bcd1 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 30 Jun 2024 10:20:06 +0800 Subject: [PATCH 03/63] feat: cleanup UI --- frontend/src/components/ImageStreamViewer.tsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 4d24163..6f3e550 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,11 +1,10 @@ import { useEffect, useState } from "react"; const getBlobUrl = (data: any) => { - if(data) { - return `data:image/jpeg;base64, ${data}`; - } - return - + if (data) { + return `data:image/jpeg;base64, ${data}`; + } + return; }; export function ImageStreamViewer({ @@ -18,17 +17,19 @@ export function ImageStreamViewer({ ); useEffect(() => { setBase64String(getBlobUrl(socketEvent?.data)); - console.log(`sizedata: ${base64String}`) + console.log(`sizedata: ${base64String}`); }, [socketEvent]); return ( - <div className="border-4 border-solid rounded-lg flex place-content-center mt-5 px-10"> - {base64String ? ( - <img src={base64String} alt="CCTV Stream" /> - ) : ( - <h1 className="place-content-center text-2xl bg-slate-400 mx-auto my-auto px-7 py-2 rounded-lg bg-opacity-30"> - Tidak Ada Video CCTV - </h1> - )} + <div className="flex justify-center"> + <div className="border-4 border-solid min-w-[640px] aspect-video rounded-lg flex place-content-center mt-5 px-10"> + {base64String ? ( + <img src={base64String} alt="CCTV Stream" /> + ) : ( + <h1 className="place-content-center text-2xl bg-slate-400 mx-auto my-auto px-7 py-2 rounded-lg bg-opacity-30"> + Tidak Ada Video CCTV + </h1> + )} + </div> </div> ); } From 709a6ed4ea68687faf40a6f2c980764cc7ec2184 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Thu, 4 Jul 2024 10:05:31 +0800 Subject: [PATCH 04/63] feat: put websocket into context provider --- frontend/package-lock.json | 934 ++++++++++-------- frontend/package.json | 2 + frontend/src/components/ImageStreamViewer.tsx | 8 +- frontend/src/context/CameraSocketContext.tsx | 42 + frontend/src/main.tsx | 5 +- frontend/src/pages/index.jsx | 17 +- 6 files changed, 583 insertions(+), 425 deletions(-) create mode 100644 frontend/src/context/CameraSocketContext.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6caf98f..2da3919 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,6 +29,8 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "postcss": "^8.4.38", + "prettier": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", "typescript": "^5.5.2", "vite": "^5.2.0" @@ -60,12 +62,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dev": true, "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -73,30 +75,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -112,12 +114,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.24.7", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -127,13 +129,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", "browserslist": "^4.22.2", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -143,62 +145,66 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dev": true, "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dev": true, "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -208,86 +214,86 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "dev": true, "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", "dev": true, "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -297,9 +303,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -309,12 +315,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.5.tgz", - "integrity": "sha512-RtCJoUO2oYrYwFPtR1/jkoBEcFuI1ae9a9IMxeyAVa3a1Ap4AnxmyIKG2b2FaJKqkidw/0cxRbWN+HOs6ZWd1w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -324,12 +330,12 @@ } }, "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.1.tgz", - "integrity": "sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -339,33 +345,33 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -374,13 +380,13 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -388,9 +394,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -404,9 +410,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -420,9 +426,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -436,9 +442,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -452,9 +458,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -468,9 +474,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -484,9 +490,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -500,9 +506,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -516,9 +522,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -532,9 +538,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -548,9 +554,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -564,9 +570,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -580,9 +586,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -596,9 +602,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -612,9 +618,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -628,9 +634,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -644,9 +650,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -660,9 +666,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -676,9 +682,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -692,9 +698,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -708,9 +714,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -724,9 +730,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -740,9 +746,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -771,9 +777,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -827,9 +833,9 @@ } }, "node_modules/@flashphoner/websdk": { - "version": "2.0.243", - "resolved": "https://registry.npmjs.org/@flashphoner/websdk/-/websdk-2.0.243.tgz", - "integrity": "sha512-b5gFgK9guf3J9tuQ1+tbBHmLOXTObmy86mE01mBuJqpVpBk1HUBLflQ3yLmwSDx8eN3wnEPF+EO+6QlIHdyiPg==", + "version": "2.0.244", + "resolved": "https://registry.npmjs.org/@flashphoner/websdk/-/websdk-2.0.244.tgz", + "integrity": "sha512-Qs8qb9aHJI7hrphFZpbmFgnmIH5eZz1eUmJHpLPqVqRsGIAfUIHNuCIGdtHNQAPAWKgT9VGkGBtlEdQoBptC5g==", "dependencies": { "adapterjs": "0.15.5", "es6-promise": "4.0.5", @@ -844,6 +850,7 @@ "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", @@ -871,6 +878,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, "node_modules/@isaacs/cliui": { @@ -1011,17 +1019,17 @@ } }, "node_modules/@remix-run/router": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz", - "integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz", + "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==", "engines": { "node": ">=14.0.0" } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", - "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", "cpu": [ "arm" ], @@ -1032,9 +1040,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", - "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", "cpu": [ "arm64" ], @@ -1045,9 +1053,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", - "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", "cpu": [ "arm64" ], @@ -1058,9 +1066,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", - "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", "cpu": [ "x64" ], @@ -1071,9 +1079,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", - "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", "cpu": [ "arm" ], @@ -1084,9 +1092,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", - "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", "cpu": [ "arm" ], @@ -1097,9 +1105,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", - "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", "cpu": [ "arm64" ], @@ -1110,9 +1118,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", - "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", "cpu": [ "arm64" ], @@ -1123,9 +1131,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", - "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", "cpu": [ "ppc64" ], @@ -1136,9 +1144,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", - "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", "cpu": [ "riscv64" ], @@ -1149,9 +1157,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", - "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", "cpu": [ "s390x" ], @@ -1162,9 +1170,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", - "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", "cpu": [ "x64" ], @@ -1175,9 +1183,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", - "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", "cpu": [ "x64" ], @@ -1188,9 +1196,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", - "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", "cpu": [ "arm64" ], @@ -1201,9 +1209,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", - "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", "cpu": [ "ia32" ], @@ -1214,9 +1222,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", - "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", "cpu": [ "x64" ], @@ -1259,9 +1267,9 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", - "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7" @@ -1305,16 +1313,16 @@ "dev": true }, "node_modules/@vitejs/plugin-react": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", - "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", + "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", "dev": true, "dependencies": { - "@babel/core": "^7.23.5", - "@babel/plugin-transform-react-jsx-self": "^7.23.3", - "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@babel/core": "^7.24.5", + "@babel/plugin-transform-react-jsx-self": "^7.24.5", + "@babel/plugin-transform-react-jsx-source": "^7.24.1", "@types/babel__core": "^7.20.5", - "react-refresh": "^0.14.0" + "react-refresh": "^0.14.2" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -1324,9 +1332,9 @@ } }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1527,16 +1535,19 @@ } }, "node_modules/array.prototype.tosorted": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.3.tgz", - "integrity": "sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.7", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.1.0", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/arraybuffer.prototype.slice": { @@ -1642,21 +1653,21 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "dev": true, "funding": [ { @@ -1673,10 +1684,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -1723,9 +1734,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001618", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001618.tgz", - "integrity": "sha512-p407+D1tIkDvsEAPS22lJxLQQaG8OTBEqo0KhzfABGk0TU4juBNDSfH0hyAp/HRyx+M8L17z/ltyhxh27FTfQg==", + "version": "1.0.30001638", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001638.tgz", + "integrity": "sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==", "dev": true, "funding": [ { @@ -1912,9 +1923,9 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -2007,9 +2018,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.769", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.769.tgz", - "integrity": "sha512-bZu7p623NEA2rHTc9K1vykl57ektSPQYFFqQir8BOYf6EKOB+yIsbFB9Kpm7Cgt6tsLr9sRkqfqSZUw7LP1XxQ==", + "version": "1.4.815", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.815.tgz", + "integrity": "sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==", "dev": true }, "node_modules/emoji-regex": { @@ -2182,9 +2193,9 @@ "integrity": "sha512-4QUVu8ljexIQebW0h+5EhEqVKvh8p7Wu3zi3AqPmX3tFL2bf6MnZ2ytC/3xuUt1mo6kE2GSYNmjWyDo2SjkAsg==" }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -2194,29 +2205,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -2293,29 +2304,29 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.1.tgz", - "integrity": "sha512-N97CxlouPT1AHt8Jn0mhhN2RrADlUAsk1/atcT2KyA/l9Q/E6ll7OIGwNumFmWfZ9skV3XXccYS19h80rHtgkw==", + "version": "7.34.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", + "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlast": "^1.2.4", + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", "array.prototype.toreversed": "^1.1.2", - "array.prototype.tosorted": "^1.1.3", + "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.17", + "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", - "object.entries": "^1.1.7", - "object.fromentries": "^2.0.7", - "object.hasown": "^1.1.3", - "object.values": "^1.1.7", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.10" + "string.prototype.matchall": "^4.0.11" }, "engines": { "node": ">=4" @@ -2609,9 +2620,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -2666,9 +2677,9 @@ } }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -2799,6 +2810,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -2994,6 +3006,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -3104,12 +3117,15 @@ } }, "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==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3413,9 +3429,9 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -3431,9 +3447,9 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, "bin": { "jiti": "bin/jiti.js" @@ -3616,12 +3632,12 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3641,9 +3657,9 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" @@ -3732,10 +3748,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3889,6 +3908,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -3951,9 +3976,9 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", + "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", "dev": true, "engines": { "node": "14 || >=16.14" @@ -4005,9 +4030,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", "dev": true, "funding": [ { @@ -4025,7 +4050,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "source-map-js": "^1.2.0" }, "engines": { @@ -4121,9 +4146,9 @@ } }, "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", - "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", "dev": true, "engines": { "node": ">=14" @@ -4152,9 +4177,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.16", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", - "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -4179,6 +4204,95 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", + "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.5.tgz", + "integrity": "sha512-axfeOArc/RiGHjOIy9HytehlC0ZLeMaqY09mm8YCkMzznKiDkwFzOpBvtuhuv3xG5qB73+Mj7OCe2j/L1ryfuQ==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, "node_modules/promise-polyfill": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", @@ -4289,11 +4403,11 @@ } }, "node_modules/react-router": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz", - "integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", + "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==", "dependencies": { - "@remix-run/router": "1.16.1" + "@remix-run/router": "1.17.0" }, "engines": { "node": ">=14.0.0" @@ -4303,12 +4417,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz", - "integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==", + "version": "6.24.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz", + "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==", "dependencies": { - "@remix-run/router": "1.16.1", - "react-router": "6.23.1" + "@remix-run/router": "1.17.0", + "react-router": "6.24.0" }, "engines": { "node": ">=14.0.0" @@ -4427,6 +4541,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -4439,9 +4554,9 @@ } }, "node_modules/rollup": { - "version": "4.17.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", - "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", "dev": true, "dependencies": { "@types/estree": "1.0.5" @@ -4454,22 +4569,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.17.2", - "@rollup/rollup-android-arm64": "4.17.2", - "@rollup/rollup-darwin-arm64": "4.17.2", - "@rollup/rollup-darwin-x64": "4.17.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", - "@rollup/rollup-linux-arm-musleabihf": "4.17.2", - "@rollup/rollup-linux-arm64-gnu": "4.17.2", - "@rollup/rollup-linux-arm64-musl": "4.17.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", - "@rollup/rollup-linux-riscv64-gnu": "4.17.2", - "@rollup/rollup-linux-s390x-gnu": "4.17.2", - "@rollup/rollup-linux-x64-gnu": "4.17.2", - "@rollup/rollup-linux-x64-musl": "4.17.2", - "@rollup/rollup-win32-arm64-msvc": "4.17.2", - "@rollup/rollup-win32-ia32-msvc": "4.17.2", - "@rollup/rollup-win32-x64-msvc": "4.17.2", + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", "fsevents": "~2.3.2" } }, @@ -4866,16 +4981,17 @@ } }, "node_modules/sucrase/node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" @@ -4888,9 +5004,9 @@ } }, "node_modules/sucrase/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -4927,9 +5043,9 @@ } }, "node_modules/sweetalert2": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.11.0.tgz", - "integrity": "sha512-wKCTtoE6lQVDKaJ5FFq+znk/YykJmJlD8RnLZps8C7DyivctCoRlVeeOwnKfgwKS+QJYon7s++3dmNi3/am1tw==", + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.12.1.tgz", + "integrity": "sha512-xV3/YI7Ah6BeP+bXKcrHy1yn6duh8eqlX2TSI9I/rTIzGLYQvnnTa3mOIo5RHUobAjSmacC2IhPApxjvppZaEQ==", "funding": { "type": "individual", "url": "https://github.com/sponsors/limonte" @@ -4951,9 +5067,9 @@ "integrity": "sha512-zcr0lueMzUum1y95CNVFpJHxjPchbFFsBHujRW4iQhAw7pwotzS0gsSGgs4O0gylCrMhTKYI899dlwsc4Eq0Bg==" }, "node_modules/tailwindcss": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", - "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", + "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -5237,12 +5353,12 @@ } }, "node_modules/vite": { - "version": "5.2.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", - "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz", + "integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==", "dev": true, "dependencies": { - "esbuild": "^0.20.1", + "esbuild": "^0.21.3", "postcss": "^8.4.38", "rollup": "^4.13.0" }, @@ -5547,9 +5663,9 @@ "dev": true }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", "dev": true, "bin": { "yaml": "bin.mjs" diff --git a/frontend/package.json b/frontend/package.json index 484e585..3a81ee2 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,8 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", "postcss": "^8.4.38", + "prettier": "^3.3.2", + "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", "typescript": "^5.5.2", "vite": "^5.2.0" diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 6f3e550..5ad9bf0 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -8,17 +8,17 @@ const getBlobUrl = (data: any) => { }; export function ImageStreamViewer({ - socketEvent, + base64Data, }: { - socketEvent: MessageEvent<any> | null; + base64Data: string | null; }) { const [base64String, setBase64String] = useState<string | undefined>( undefined ); useEffect(() => { - setBase64String(getBlobUrl(socketEvent?.data)); + setBase64String(getBlobUrl(base64Data)); console.log(`sizedata: ${base64String}`); - }, [socketEvent]); + }, [base64Data]); return ( <div className="flex justify-center"> <div className="border-4 border-solid min-w-[640px] aspect-video rounded-lg flex place-content-center mt-5 px-10"> diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/context/CameraSocketContext.tsx new file mode 100644 index 0000000..8ba103c --- /dev/null +++ b/frontend/src/context/CameraSocketContext.tsx @@ -0,0 +1,42 @@ +import { createContext, useState } from "react"; + +export interface IStreamSocketContext { + base64data: string; + refreshSource: () => void; +} + +export const StreamSocketContext = createContext< + IStreamSocketContext | undefined +>(undefined); + +/** + * Provider for `StreamSocketContext` + * @returns JSX.Element + */ +export function StreamSocketProvider({ children }: { children: JSX.Element }) { + const [streamList, setStreamList] = useState([]); + const [currentUrl, setCurrentUrl] = useState(""); + const [wsInstance, setWsInstance] = useState<WebSocket>(); + const [base64Stream, setBase64Stream] = useState("ws://localhost:7000/v1/test"); + + const data: IStreamSocketContext = { + base64data: base64Stream, + refreshSource() { + const ws = new WebSocket(currentUrl); + ws.onmessage = (ev) => { + setBase64Stream(ev.data); + }; + ws.onclose = (ev) => { + console.log("WS: close from"); + ev.stopPropagation(); + }; + setWsInstance(ws); + }, + }; + + return ( + <StreamSocketContext.Provider value={data}> + {children} + </StreamSocketContext.Provider> + ); +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 6b8de68..1320d0b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,9 +2,12 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; +import { StreamSocketProvider } from "./context/CameraSocketContext"; ReactDOM.createRoot(document.getElementById("root")!!).render( <React.StrictMode> - <App /> + <StreamSocketProvider> + <App /> + </StreamSocketProvider> </React.StrictMode> ); diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index ecf0e42..7d56c81 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -1,27 +1,22 @@ -import { useEffect, useState} from "react"; +import { useContext } from "react"; import Title from "../components/titleDashboard"; -import useWebSocket from "react-use-websocket"; import { Inspector } from "../components/inspector"; import { ImageStreamViewer } from "../components/ImageStreamViewer"; +import { StreamSocketContext } from "../context/CameraSocketContext"; const Home = () => { - const [socketUrl] = useState("ws://localhost:7000/v1/test"); - const { lastMessage } = useWebSocket(socketUrl); - - - - useEffect(() => {}, [lastMessage]); + const stream = useContext(StreamSocketContext); return ( <div className="w-full p-4"> <Title /> <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> <div className="flex"> - <div className="flex-[74%] mt-2 text-center"> + <div className="flex-grow flex flex-col items-center"> <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> <h3>Tanggal: 19 Februari 2024</h3> - <ImageStreamViewer socketEvent={lastMessage} /> - <br /> + <ImageStreamViewer base64Data={stream.base64data} /> + <br /> </div> <div className="border-l border-[#66ABB1] h-[40rem]"></div> <Inspector /> From 7a8ac4b2a40fe48e6e065840a6ebe64c04dbf869 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Thu, 4 Jul 2024 10:25:33 +0800 Subject: [PATCH 05/63] feat: update code --- frontend/src/context/CameraSocketContext.tsx | 43 ++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/context/CameraSocketContext.tsx index 8ba103c..9cced82 100644 --- a/frontend/src/context/CameraSocketContext.tsx +++ b/frontend/src/context/CameraSocketContext.tsx @@ -1,8 +1,27 @@ import { createContext, useState } from "react"; export interface IStreamSocketContext { - base64data: string; - refreshSource: () => void; + /** + * raw `base64` string. remember you must include `"data:image/jpeg;${base64}"` manually at client side to display the stream image + */ + base64data: string | undefined; + + /** + * change websocket url, changing this won't stop the current running websocket. + * @param url - websocket url, ex: `ws://localhost/v1/test` + * @returns + */ + setWebsocketURL: (url: string) => void; + /** + * call `stop()` if the existing websocket is available, and start new connection to websocket source. + * @returns + */ + startOrReload: () => void; + /** + * Stop all connection to websocsket source + * @returns + */ + stop: () => void; } export const StreamSocketContext = createContext< @@ -14,14 +33,20 @@ export const StreamSocketContext = createContext< * @returns JSX.Element */ export function StreamSocketProvider({ children }: { children: JSX.Element }) { - const [streamList, setStreamList] = useState([]); - const [currentUrl, setCurrentUrl] = useState(""); - const [wsInstance, setWsInstance] = useState<WebSocket>(); - const [base64Stream, setBase64Stream] = useState("ws://localhost:7000/v1/test"); + const [currentUrl, setCurrentUrl] = useState("ws://localhost:7000/v1/test"); + const [wsInstance, setWsInstance] = useState<WebSocket | null>(); + + const [base64Stream, setBase64Stream] = useState(""); const data: IStreamSocketContext = { base64data: base64Stream, - refreshSource() { + setWebsocketURL(url) { + setCurrentUrl(url); + }, + startOrReload() { + if (wsInstance) { + this.stop(); + } const ws = new WebSocket(currentUrl); ws.onmessage = (ev) => { setBase64Stream(ev.data); @@ -32,6 +57,10 @@ export function StreamSocketProvider({ children }: { children: JSX.Element }) { }; setWsInstance(ws); }, + stop() { + wsInstance?.close(); + setWsInstance(null); + }, }; return ( From befd3ca1c66c44ed53b7fc4b00754fcb21d75068 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 5 Jul 2024 16:22:23 +0800 Subject: [PATCH 06/63] fix: rename CMD to ENTRYPOINT to fix entrypoint not found --- backend/services/report/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/services/report/Dockerfile b/backend/services/report/Dockerfile index 098a435..6e806aa 100644 --- a/backend/services/report/Dockerfile +++ b/backend/services/report/Dockerfile @@ -12,4 +12,4 @@ RUN npm install COPY . . # setup port # EXPOSE 8091 -CMD [ "npm", "run", "start" ] \ No newline at end of file +ENTRYPOINT [ "npm", "run", "start" ] \ No newline at end of file From 6a170d5ee082fed2752a517564b02bc6154059cd Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 7 Jul 2024 00:54:20 +0800 Subject: [PATCH 07/63] feat: update context --- frontend/package-lock.json | 232 ++++++++++++++++++- frontend/package.json | 2 + frontend/src/App.tsx | 18 +- frontend/src/context/AuthContext.tsx | 42 ++++ frontend/src/context/CameraSocketContext.tsx | 85 +++++-- frontend/src/hooks/useLocalStorage.tsx | 30 +++ frontend/src/main.tsx | 9 +- 7 files changed, 389 insertions(+), 29 deletions(-) create mode 100644 frontend/src/context/AuthContext.tsx create mode 100644 frontend/src/hooks/useLocalStorage.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2da3919..93f335b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,8 @@ "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "sweetalert2": "^11.11.0", "sweetalert2-react-content": "^5.0.7" }, @@ -1234,6 +1236,11 @@ "win32" ] }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1275,12 +1282,33 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", @@ -1331,6 +1359,18 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", @@ -1630,6 +1670,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1839,6 +1887,26 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -1926,7 +1994,6 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2029,6 +2096,46 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -3644,6 +3751,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", @@ -3668,8 +3794,7 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mz": { "version": "2.7.0", @@ -3706,6 +3831,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -4763,6 +4896,58 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -5299,6 +5484,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/update-browserslist-db": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", @@ -5352,6 +5542,14 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/vite": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz", @@ -5656,6 +5854,34 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3a81ee2..a542877 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,8 @@ "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "sweetalert2": "^11.11.0", "sweetalert2-react-content": "^5.0.7" }, diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 38e27b6..5010749 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,10 @@ import Home from "./pages/index"; -import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { + BrowserRouter as Router, + Routes, + Route, + Outlet, +} from "react-router-dom"; import Laporan from "./pages/laporan"; import WebSocketDemo from "./pages/index"; import MainLayout from "./layout/MainLayout"; @@ -8,10 +13,13 @@ function App() { return ( <Router> <Routes> - <Route path="/" element={<MainLayout />}> - <Route index element={<Home />} /> - <Route path="laporan" element={<Laporan />} /> - <Route path="websoket" element={<WebSocketDemo />} /> + <Route path="/" element={<Outlet />}> + + <Route path="app" element={<MainLayout />}> + <Route index element={<Home />} /> + <Route path="laporan" element={<Laporan />} /> + <Route path="websoket" element={<WebSocketDemo />} /> + </Route> </Route> </Routes> </Router> diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx new file mode 100644 index 0000000..be165e8 --- /dev/null +++ b/frontend/src/context/AuthContext.tsx @@ -0,0 +1,42 @@ +import React, { createContext, useContext, useMemo, useState } from "react"; +import cookie from "cookie"; +import { useLocalStorage } from "../hooks/useLocalStorage"; +import { useNavigate } from "react-router-dom"; + +interface IAuthContext { + login: (email: string, password: string) => void; + logout: () => void; +} + +const KEY_ACCESS_TOKEN = "storage_access_token"; +const KEY_REFRESH_TOKEN = "storage_refresh_token"; + +const AuthContext = createContext<IAuthContext | undefined>(undefined); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [accessToken, setAccessToken] = useLocalStorage(KEY_ACCESS_TOKEN, ""); + const [refreshToken, setRefreshToken] = useLocalStorage( + KEY_REFRESH_TOKEN, + "" + ); + const navigate = useNavigate(); + const [loggedIn, setLoggedIn] = useState(false); + + const data: IAuthContext = useMemo( + () => ({ + login(email, password) { + //TODO: fetch login, save tokens, and navigate to `/app` + }, + logout() { + setAccessToken(""); + setRefreshToken(""); + navigate("/", { replace: true }); + }, + }), + [accessToken, refreshToken] + ); + + return <AuthContext.Provider value={data}>{children}</AuthContext.Provider>; +} + +export const useAuth = () => useContext(AuthContext) as IAuthContext; diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/context/CameraSocketContext.tsx index 9cced82..be408a8 100644 --- a/frontend/src/context/CameraSocketContext.tsx +++ b/frontend/src/context/CameraSocketContext.tsx @@ -1,4 +1,6 @@ -import { createContext, useState } from "react"; +import { createContext, useEffect, useRef, useState } from "react"; +import { Socket } from "socket.io"; +import { io } from "socket.io-client"; export interface IStreamSocketContext { /** @@ -13,10 +15,12 @@ export interface IStreamSocketContext { */ setWebsocketURL: (url: string) => void; /** - * call `stop()` if the existing websocket is available, and start new connection to websocket source. + * start new connection to websocket source, and don't create new instance if existing instance is active. + * + * Please close the existing instance by call `stop()` if you want to call this function * @returns */ - startOrReload: () => void; + start: () => void; /** * Stop all connection to websocsket source * @returns @@ -33,8 +37,15 @@ export const StreamSocketContext = createContext< * @returns JSX.Element */ export function StreamSocketProvider({ children }: { children: JSX.Element }) { - const [currentUrl, setCurrentUrl] = useState("ws://localhost:7000/v1/test"); - const [wsInstance, setWsInstance] = useState<WebSocket | null>(); + const initialized = useRef(false) + const [currentUrl, setCurrentUrl] = useState("http://localhost:7000"); + const [wsInstance, setWsInstance] = useState<WebSocket | undefined>( + undefined + ); + + const [socketInstance, setSocketInstance] = useState<Socket | undefined>( + undefined + ); const [base64Stream, setBase64Stream] = useState(""); @@ -43,26 +54,64 @@ export function StreamSocketProvider({ children }: { children: JSX.Element }) { setWebsocketURL(url) { setCurrentUrl(url); }, - startOrReload() { - if (wsInstance) { - this.stop(); + start() { + if (socketInstance) { + return; } - const ws = new WebSocket(currentUrl); - ws.onmessage = (ev) => { - setBase64Stream(ev.data); - }; - ws.onclose = (ev) => { - console.log("WS: close from"); - ev.stopPropagation(); - }; - setWsInstance(ws); + + setSocketInstance((prevVal) => { + if (prevVal) { + return prevVal; + } + const socket = io(currentUrl); + socket.on("connect", () => { + console.log(`socket established: ${socket.id}`); + }); + socket.on("disconnect", () => { + console.log(`socket closed: ${socket.id}`); + }); + socket.on("data", (val) => { + console.log(`data: ${val}`); + }); + return socket as unknown as Socket; + }); + + // if (wsInstance) { + // this.stop(); + // } + // const ws = new WebSocket(currentUrl); + // ws.onopen = (ev) => { + // console.log(`connection established`) + // } + // ws.onmessage = (ev) => { + // setBase64Stream(ev.data); + // }; + // ws.onclose = (ev) => { + // console.log("WS: close from"); + // ev.stopPropagation(); + // }; + // setWsInstance(ws); }, stop() { wsInstance?.close(); - setWsInstance(null); + socketInstance?.disconnect(true); + setWsInstance(undefined); + setSocketInstance(undefined); }, }; + //INITIALIZE + useEffect(() => { + if(!initialized.current) { + initialized.current = true + + data.start(); + } + return () => { + data.stop(); + }; + }, []); + return ( <StreamSocketContext.Provider value={data}> {children} diff --git a/frontend/src/hooks/useLocalStorage.tsx b/frontend/src/hooks/useLocalStorage.tsx new file mode 100644 index 0000000..21f9340 --- /dev/null +++ b/frontend/src/hooks/useLocalStorage.tsx @@ -0,0 +1,30 @@ +import { useState } from "react"; + +export const useLocalStorage = ( + key: string, + defaultValue: string +): [string, (newValue: string) => void] => { + const [storedValue, setStoredValue] = useState(() => { + try { + const value = localStorage.getItem(key); + if (value) { + return value.toString(); + } else { + window.localStorage.setItem(key, defaultValue); + return defaultValue; + } + } catch (e) { + return defaultValue; + } + }); + + const setValue = (newValue: string) => { + try { + window.localStorage.setItem(key, JSON.stringify(newValue)); + } catch (err) { + console.log(err); + } + setStoredValue(newValue); + }; + return [storedValue, setValue]; +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 1320d0b..0bc72b0 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,11 +3,14 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; import { StreamSocketProvider } from "./context/CameraSocketContext"; +import { AuthProvider } from "./context/AuthContext"; ReactDOM.createRoot(document.getElementById("root")!!).render( <React.StrictMode> - <StreamSocketProvider> - <App /> - </StreamSocketProvider> + <AuthProvider> + <StreamSocketProvider> + <App /> + </StreamSocketProvider> + </AuthProvider> </React.StrictMode> ); From 310510c078b6258ab35ead89ccfcbc5862a486f8 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sat, 13 Jul 2024 16:39:36 +0800 Subject: [PATCH 08/63] fix: router error due to incorrect context order --- frontend/src/App.tsx | 36 ++++++++++++++++------------ frontend/src/context/AuthContext.tsx | 5 ++-- frontend/src/main.tsx | 14 ++++------- frontend/src/pages/login.tsx | 7 ++++++ 4 files changed, 35 insertions(+), 27 deletions(-) create mode 100644 frontend/src/pages/login.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5010749..1b51e3e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,27 +1,33 @@ -import Home from "./pages/index"; import { + Outlet, + Route, BrowserRouter as Router, Routes, - Route, - Outlet, } from "react-router-dom"; -import Laporan from "./pages/laporan"; -import WebSocketDemo from "./pages/index"; import MainLayout from "./layout/MainLayout"; +import { default as Home, default as WebSocketDemo } from "./pages/index"; +import Laporan from "./pages/laporan"; +import { LoginPage } from "./pages/login"; +import { AuthProvider } from "./context/AuthContext"; +import { StreamSocketProvider } from "./context/CameraSocketContext"; function App() { return ( <Router> - <Routes> - <Route path="/" element={<Outlet />}> - - <Route path="app" element={<MainLayout />}> - <Route index element={<Home />} /> - <Route path="laporan" element={<Laporan />} /> - <Route path="websoket" element={<WebSocketDemo />} /> - </Route> - </Route> - </Routes> + <AuthProvider> + <StreamSocketProvider> + <Routes> + <Route path="/" element={<Outlet />}> + <Route index element={<LoginPage />} /> + <Route path="app" element={<MainLayout />}> + <Route index element={<Home />} /> + <Route path="laporan" element={<Laporan />} /> + <Route path="websoket" element={<WebSocketDemo />} /> + </Route> + </Route> + </Routes> + </StreamSocketProvider> + </AuthProvider> </Router> ); } diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index be165e8..4acb13e 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,5 +1,4 @@ import React, { createContext, useContext, useMemo, useState } from "react"; -import cookie from "cookie"; import { useLocalStorage } from "../hooks/useLocalStorage"; import { useNavigate } from "react-router-dom"; @@ -17,7 +16,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const [accessToken, setAccessToken] = useLocalStorage(KEY_ACCESS_TOKEN, ""); const [refreshToken, setRefreshToken] = useLocalStorage( KEY_REFRESH_TOKEN, - "" + "", ); const navigate = useNavigate(); const [loggedIn, setLoggedIn] = useState(false); @@ -33,7 +32,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { navigate("/", { replace: true }); }, }), - [accessToken, refreshToken] + [accessToken, refreshToken], ); return <AuthContext.Provider value={data}>{children}</AuthContext.Provider>; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 0bc72b0..04fb315 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -2,15 +2,11 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import "./index.css"; -import { StreamSocketProvider } from "./context/CameraSocketContext"; -import { AuthProvider } from "./context/AuthContext"; -ReactDOM.createRoot(document.getElementById("root")!!).render( +const container = document.getElementById("root") as ReactDOM.Container; + +ReactDOM.createRoot(container).render( <React.StrictMode> - <AuthProvider> - <StreamSocketProvider> - <App /> - </StreamSocketProvider> - </AuthProvider> - </React.StrictMode> + <App /> + </React.StrictMode>, ); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx new file mode 100644 index 0000000..530076f --- /dev/null +++ b/frontend/src/pages/login.tsx @@ -0,0 +1,7 @@ +export function LoginPage() { + return ( + <div> + <h1>Login Page</h1> + </div> + ); +} From a5378f25f6ac911694230f0abc6d3255fe202ecd Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sat, 13 Jul 2024 16:40:27 +0800 Subject: [PATCH 09/63] feat: modify eslint configuration to fix eslint and TS confict on editor --- frontend/.eslintrc.cjs | 21 -- frontend/.eslintrc.json | 23 ++ frontend/package-lock.json | 685 ++++++++++++++++++++++++++----------- frontend/package.json | 9 +- 4 files changed, 506 insertions(+), 232 deletions(-) delete mode 100644 frontend/.eslintrc.cjs create mode 100644 frontend/.eslintrc.json diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs deleted file mode 100644 index 3e212e1..0000000 --- a/frontend/.eslintrc.cjs +++ /dev/null @@ -1,21 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], - rules: { - 'react/jsx-no-target-blank': 'off', - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 0000000..53daac6 --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,23 @@ +{ + "root": true, + "env": { "browser": true, "es2020": true }, + "parser": "@typescript-eslint/parser", + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "plugin:react/jsx-runtime", + "plugin:react-hooks/recommended" + ], + "ignorePatterns": ["dist", ".eslintrc.cjs"], + "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, + "settings": { "react": { "version": "18.2" } }, + "plugins": ["@typescript-eslint", "react-refresh"], + "rules": { + "react/jsx-no-target-blank": "off", + "react/react-in-jsx-scope": "off", + "react-refresh/only-export-components": [ + "warn", + { "allowConstantExport": true } + ] + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 93f335b..e073096 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,19 +22,24 @@ "sweetalert2-react-content": "^5.0.7" }, "devDependencies": { + "@eslint/compat": "^1.1.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", + "eslint": "^9.7.0", + "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "globals": "^15.8.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", "typescript": "^5.5.2", + "typescript-eslint": "^7.16.0", "vite": "^5.2.0" } }, @@ -381,6 +386,15 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@babel/types": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", @@ -787,16 +801,39 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/compat": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.1.1.tgz", + "integrity": "sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", + "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -804,34 +841,40 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.7.0.tgz", + "integrity": "sha512-ChuWDQenef8OSFnvuxv0TCVxEwmu3+hPNKvM9B34qpM0rDRbjL8t5QkQeHHeAfsKQjuH9wS82WeCi1J/owatng==", "dev": true, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@flashphoner/websdk": { @@ -848,21 +891,6 @@ "webrtc-adapter": "^7.2.6" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -876,12 +904,18 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1334,11 +1368,226 @@ "@types/react": "*" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", + "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/type-utils": "7.16.0", + "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", + "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/utils": "7.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", + "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", + "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", + "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", + "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", + "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", + "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.16.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, "node_modules/@vitejs/plugin-react": { "version": "4.3.1", @@ -1372,9 +1621,9 @@ } }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -1506,6 +1755,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -2060,24 +2318,24 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "path-type": "^4.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2356,41 +2614,37 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz", + "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.7.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.2", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.1.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -2404,10 +2658,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-plugin-react": { @@ -2476,16 +2730,16 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2564,19 +2818,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/has-flag": { @@ -2601,17 +2852,29 @@ } }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", + "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", "dev": true, "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2715,15 +2978,15 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -2755,17 +3018,16 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -2812,12 +3074,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2913,27 +3169,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -2947,12 +3182,15 @@ } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "15.8.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.8.0.tgz", + "integrity": "sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==", "dev": true, "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globalthis": { @@ -2971,6 +3209,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3109,23 +3367,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -3985,15 +4226,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4068,15 +4300,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4117,6 +4340,15 @@ "node": "14 || >=16.14" } }, + "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" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -4670,22 +4902,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -4896,6 +5112,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/socket.io": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", @@ -5353,6 +5578,18 @@ "node": ">=8.0" } }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -5371,18 +5608,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -5469,6 +5694,54 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.16.0.tgz", + "integrity": "sha512-kaVRivQjOzuoCXU6+hLnjo3/baxyzWVO5GrnExkFzETRYJKVHYkrJglOu2OCm8Hi9RPDWX1PTNNTpU5KRV0+RA==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "7.16.0", + "@typescript-eslint/parser": "7.16.0", + "@typescript-eslint/utils": "7.16.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", + "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", @@ -5848,12 +6121,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index a542877..3d7bf3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,19 +24,24 @@ "sweetalert2-react-content": "^5.0.7" }, "devDependencies": { + "@eslint/compat": "^1.1.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", + "eslint": "^9.7.0", + "eslint-plugin-react": "^7.34.3", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.6", + "globals": "^15.8.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", "typescript": "^5.5.2", + "typescript-eslint": "^7.16.0", "vite": "^5.2.0" } } From 71c36cadbfa8b31fe6f977654ee8e08465a0e62b Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sat, 13 Jul 2024 20:34:09 +0800 Subject: [PATCH 10/63] feat: re-expose context using custom hook --- frontend/src/context/AuthContext.tsx | 8 ++++- frontend/src/context/CameraSocketContext.tsx | 31 ++++++++++++++------ frontend/src/pages/index.jsx | 9 +++--- 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 4acb13e..113d2c9 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -23,8 +23,14 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const data: IAuthContext = useMemo( () => ({ - login(email, password) { + async login(email, password) { //TODO: fetch login, save tokens, and navigate to `/app` + const result = await fetch("http://localhost:8889/v1/camera", { + method: "GET", + keepalive: true, + body: JSON.stringify({ email, password }), + }); + console.log(result.statusText); }, logout() { setAccessToken(""); diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/context/CameraSocketContext.tsx index be408a8..3d01a87 100644 --- a/frontend/src/context/CameraSocketContext.tsx +++ b/frontend/src/context/CameraSocketContext.tsx @@ -1,7 +1,10 @@ -import { createContext, useEffect, useRef, useState } from "react"; +import { createContext, useContext, useEffect, useRef, useState } from "react"; import { Socket } from "socket.io"; import { io } from "socket.io-client"; +/** +interface of `StreamSocketContext` + */ export interface IStreamSocketContext { /** * raw `base64` string. remember you must include `"data:image/jpeg;${base64}"` manually at client side to display the stream image @@ -28,23 +31,33 @@ export interface IStreamSocketContext { stop: () => void; } -export const StreamSocketContext = createContext< - IStreamSocketContext | undefined ->(undefined); +const StreamSocketContext = createContext<IStreamSocketContext | undefined>( + undefined, +); + +export const useStreamSocket = () => { + const context = useContext(StreamSocketContext); + if (context === undefined) { + throw new Error( + "useStreamSocket must be used within a StreamSocketProvider", + ); + } + return context as IStreamSocketContext; +}; /** * Provider for `StreamSocketContext` * @returns JSX.Element */ export function StreamSocketProvider({ children }: { children: JSX.Element }) { - const initialized = useRef(false) + const initialized = useRef(false); const [currentUrl, setCurrentUrl] = useState("http://localhost:7000"); const [wsInstance, setWsInstance] = useState<WebSocket | undefined>( - undefined + undefined, ); const [socketInstance, setSocketInstance] = useState<Socket | undefined>( - undefined + undefined, ); const [base64Stream, setBase64Stream] = useState(""); @@ -102,8 +115,8 @@ export function StreamSocketProvider({ children }: { children: JSX.Element }) { //INITIALIZE useEffect(() => { - if(!initialized.current) { - initialized.current = true + if (!initialized.current) { + initialized.current = true; data.start(); } diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index 7d56c81..87cf295 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -1,11 +1,10 @@ -import { useContext } from "react"; -import Title from "../components/titleDashboard"; -import { Inspector } from "../components/inspector"; import { ImageStreamViewer } from "../components/ImageStreamViewer"; -import { StreamSocketContext } from "../context/CameraSocketContext"; +import { Inspector } from "../components/inspector"; +import Title from "../components/titleDashboard"; +import { useStreamSocket } from "../context/CameraSocketContext"; const Home = () => { - const stream = useContext(StreamSocketContext); + const stream = useStreamSocket(); return ( <div className="w-full p-4"> From 673a61bd72c88050d4bfe69b8c87072d034a19aa Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 14 Jul 2024 21:18:38 +0800 Subject: [PATCH 11/63] feat: cleanup deps and linter --- frontend/.eslintrc.json | 23 -- frontend/eslint.config.js | 18 + frontend/package-lock.json | 686 ++++++++++++++++++------------------- frontend/package.json | 15 +- frontend/tsconfig.json | 8 +- frontend/vite.config.ts | 13 +- 6 files changed, 379 insertions(+), 384 deletions(-) delete mode 100644 frontend/.eslintrc.json create mode 100644 frontend/eslint.config.js diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json deleted file mode 100644 index 53daac6..0000000 --- a/frontend/.eslintrc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "root": true, - "env": { "browser": true, "es2020": true }, - "parser": "@typescript-eslint/parser", - "extends": [ - "eslint:recommended", - "plugin:react/recommended", - "plugin:react/jsx-runtime", - "plugin:react-hooks/recommended" - ], - "ignorePatterns": ["dist", ".eslintrc.cjs"], - "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, - "settings": { "react": { "version": "18.2" } }, - "plugins": ["@typescript-eslint", "react-refresh"], - "rules": { - "react/jsx-no-target-blank": "off", - "react/react-in-jsx-scope": "off", - "react-refresh/only-export-components": [ - "warn", - { "allowConstantExport": true } - ] - } -} diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..97567ae --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,18 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; +import pluginReactConfig from "eslint-plugin-react/configs/recommended.js"; + +export default [ + { files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"] }, + { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } } }, + { languageOptions: { globals: globals.browser } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, + pluginReactConfig, + { + rules: { + "react/react-in-jsx-scope" : "off" + } + } +]; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e073096..50ce7d4 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,8 +8,6 @@ "name": "bye-cheating", "version": "0.0.0", "dependencies": { - "@flashphoner/websdk": "^2.0.243", - "hls.js": "^1.5.11", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", @@ -23,22 +21,21 @@ }, "devDependencies": { "@eslint/compat": "^1.1.1", + "@eslint/js": "^9.7.0", + "@types/eslint__js": "^8.42.3", + "@types/node": "^20.14.10", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.16.0", - "@typescript-eslint/parser": "^7.16.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", - "eslint": "^9.7.0", - "eslint-plugin-react": "^7.34.3", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.4", "globals": "^15.8.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "typescript-eslint": "^7.16.0", "vite": "^5.2.0" } @@ -810,30 +807,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", - "dev": true, - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -841,19 +824,22 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -868,27 +854,19 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@flashphoner/websdk": { - "version": "2.0.244", - "resolved": "https://registry.npmjs.org/@flashphoner/websdk/-/websdk-2.0.244.tgz", - "integrity": "sha512-Qs8qb9aHJI7hrphFZpbmFgnmIH5eZz1eUmJHpLPqVqRsGIAfUIHNuCIGdtHNQAPAWKgT9VGkGBtlEdQoBptC5g==", "dependencies": { - "adapterjs": "0.15.5", - "es6-promise": "4.0.5", - "kalmanjs": "^1.1.0", - "promise-polyfill": "^6.0.1", - "swfobject": "^2.2.1", - "uuid": "^8.3.0", - "webrtc-adapter": "^7.2.6" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -904,18 +882,12 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "dev": true, - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1329,12 +1301,37 @@ "@types/node": "*" } }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint__js": { + "version": "8.42.3", + "resolved": "https://registry.npmjs.org/@types/eslint__js/-/eslint__js-8.42.3.tgz", + "integrity": "sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==", + "dev": true, + "dependencies": { + "@types/eslint": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "node_modules/@types/node": { "version": "20.14.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", @@ -1368,116 +1365,6 @@ "@types/react": "*" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", - "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", - "dev": true, - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/type-utils": "7.16.0", - "@typescript-eslint/utils": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", - "graphemer": "^1.4.0", - "ignore": "^5.3.1", - "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", - "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", - "dev": true, - "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/utils": "7.16.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", - "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", - "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || >=20.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@typescript-eslint/scope-manager": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", @@ -1589,6 +1476,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", @@ -1641,16 +1534,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/adapterjs": { - "version": "0.15.5", - "resolved": "https://registry.npmjs.org/adapterjs/-/adapterjs-0.15.5.tgz", - "integrity": "sha512-DbeQ8QXx7oUR2JcXrrTF47+F4BeVQUHgxSntpalp9fdKmp/02lxcpTPEF417ZOh5gqqCB320y20znzata2LiMw==", - "engines": { - "grunt": ">=0.4.1", - "node": ">=0.10.26", - "npm": ">=1.4.6" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -2336,6 +2219,18 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2552,11 +2447,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es6-promise": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.0.5.tgz", - "integrity": "sha512-4QUVu8ljexIQebW0h+5EhEqVKvh8p7Wu3zi3AqPmX3tFL2bf6MnZ2ytC/3xuUt1mo6kE2GSYNmjWyDo2SjkAsg==" - }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -2614,37 +2504,41 @@ } }, "node_modules/eslint": { - "version": "9.7.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.7.0.tgz", - "integrity": "sha512-FzJ9D/0nGiCGBf8UXO/IGLTgLVzIxze1zpfA8Ton2mjLovXdAPlYDv+MQDcqj3TmrhAGYfOpz9RfR+ent0AgAw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.11.0", - "@eslint/config-array": "^0.17.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.7.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.2", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -2658,16 +2552,16 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-plugin-react": { - "version": "7.34.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", - "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "version": "7.34.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz", + "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", @@ -2678,16 +2572,17 @@ "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" @@ -2696,27 +2591,6 @@ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.7.tgz", - "integrity": "sha512-yrj+KInFmwuQS2UQcg1SF83ha1tuHC1jMQbRNyuWtlEzzKRDgAl7L4Yp4NlDUZTZNlWvHEzOtJhMi40R7JxcSw==", - "dev": true, - "peerDependencies": { - "eslint": ">=7" - } - }, "node_modules/eslint-plugin-react/node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2730,16 +2604,16 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", - "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2757,6 +2631,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2818,16 +2701,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint/node_modules/has-flag": { @@ -2852,29 +2738,17 @@ } }, "node_modules/espree": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", - "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.12.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2978,15 +2852,15 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { @@ -3018,16 +2892,17 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -3074,6 +2949,12 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3169,6 +3050,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3328,11 +3230,6 @@ "node": ">= 0.4" } }, - "node_modules/hls.js": { - "version": "1.5.11", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.11.tgz", - "integrity": "sha512-q3We1izi2+qkOO+TvZdHv+dx6aFzdtk3xc1/Qesrvto4thLTT/x/1FK85c5h1qZE4MmMBNgKg+MIW8nxQfxwBw==" - }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -3367,6 +3264,23 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -3877,11 +3791,6 @@ "node": ">=4.0" } }, - "node_modules/kalmanjs": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/kalmanjs/-/kalmanjs-1.1.0.tgz", - "integrity": "sha512-Gkm2DAKIX8geuDcEJ92e80EmQ6TyIzWBJk6AFk+aDLofQ4G/KoDe1RCY9WMuXFe0NQxsTi7HYh9BVQneOtlvkg==" - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4192,23 +4101,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", @@ -4226,6 +4118,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4300,6 +4201,15 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -4658,11 +4568,6 @@ } } }, - "node_modules/promise-polyfill": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", - "integrity": "sha512-g0LWaH0gFsxovsU7R5LrrhHhWAWiHRnh1GPrhXnPgYsDkIqjRYUYSZEsej/wtleDrz5xVSIDbeKfidztp2XHFQ==" - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4902,6 +4807,22 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rollup": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", @@ -4937,18 +4858,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rtcpeerconnection-shim": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz", - "integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==", - "dependencies": { - "sdp": "^2.6.0" - }, - "engines": { - "node": ">=6.0.0", - "npm": ">=3.10.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5015,11 +4924,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/sdp": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz", - "integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==" - }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5273,6 +5177,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -5471,11 +5385,6 @@ "sweetalert2": "^11.0.0" } }, - "node_modules/swfobject": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/swfobject/-/swfobject-2.2.1.tgz", - "integrity": "sha512-zcr0lueMzUum1y95CNVFpJHxjPchbFFsBHujRW4iQhAw7pwotzS0gsSGgs4O0gylCrMhTKYI899dlwsc4Eq0Bg==" - }, "node_modules/tailwindcss": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", @@ -5608,6 +5517,18 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -5682,9 +5603,9 @@ } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5720,6 +5641,94 @@ } } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", + "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/type-utils": "7.16.0", + "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", + "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/utils": "7.16.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", + "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.16.0", + "@typescript-eslint/types": "7.16.0", + "@typescript-eslint/typescript-estree": "7.16.0", + "@typescript-eslint/visitor-keys": "7.16.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", @@ -5807,14 +5816,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5878,19 +5879,6 @@ } } }, - "node_modules/webrtc-adapter": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.1.tgz", - "integrity": "sha512-TbrbBmiQBL9n0/5bvDdORc6ZfRY/Z7JnEj+EYOD1ghseZdpJ+nF2yx14k3LgQKc7JZnG7HAcL+zHnY25So9d7A==", - "dependencies": { - "rtcpeerconnection-shim": "^1.2.15", - "sdp": "^2.12.0" - }, - "engines": { - "node": ">=6.0.0", - "npm": ">=3.10.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6121,6 +6109,12 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 3d7bf3b..6d9820b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,6 @@ "preview": "vite preview" }, "dependencies": { - "@flashphoner/websdk": "^2.0.243", - "hls.js": "^1.5.11", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", @@ -25,22 +23,21 @@ }, "devDependencies": { "@eslint/compat": "^1.1.1", + "@eslint/js": "^9.7.0", + "@types/eslint__js": "^8.42.3", + "@types/node": "^20.14.10", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", - "@typescript-eslint/eslint-plugin": "^7.16.0", - "@typescript-eslint/parser": "^7.16.0", "@vitejs/plugin-react": "^4.2.1", "autoprefixer": "^10.4.19", - "eslint": "^9.7.0", - "eslint-plugin-react": "^7.34.3", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", + "eslint": "^8.57.0", + "eslint-plugin-react": "^7.34.4", "globals": "^15.8.0", "postcss": "^8.4.38", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", - "typescript": "^5.5.2", + "typescript": "^5.5.3", "typescript-eslint": "^7.16.0", "vite": "^5.2.0" } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 744b105..e30cb5a 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -19,8 +19,12 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } }, - "include": ["./src"], + "include": ["./src", "eslint.config.js"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 5a33944..62f7471 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,12 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "node:path"; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], -}) + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +}); From 53caad4bedbc8306b4c419ea378cbfd3232fc264 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 14 Jul 2024 21:56:20 +0800 Subject: [PATCH 12/63] feat: fix import, remove unused inspector props --- frontend/src/App.tsx | 12 ++-- frontend/src/components/ImageStreamViewer.tsx | 4 +- frontend/src/components/inspector.jsx | 60 ------------------- frontend/src/components/sidebar.tsx | 6 +- frontend/src/context/AuthContext.tsx | 2 +- frontend/src/pages/index.jsx | 44 +++++++++++++- 6 files changed, 53 insertions(+), 75 deletions(-) delete mode 100644 frontend/src/components/inspector.jsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1b51e3e..2b7fb69 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,12 +4,12 @@ import { BrowserRouter as Router, Routes, } from "react-router-dom"; -import MainLayout from "./layout/MainLayout"; -import { default as Home, default as WebSocketDemo } from "./pages/index"; -import Laporan from "./pages/laporan"; -import { LoginPage } from "./pages/login"; -import { AuthProvider } from "./context/AuthContext"; -import { StreamSocketProvider } from "./context/CameraSocketContext"; +import MainLayout from "@/layout/MainLayout"; +import { default as Home, default as WebSocketDemo } from "@/pages/index"; +import Laporan from "@/pages/laporan"; +import { LoginPage } from "@/pages/login"; +import { AuthProvider } from "@/context/AuthContext"; +import { StreamSocketProvider } from "@/context/CameraSocketContext"; function App() { return ( diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 5ad9bf0..e0970e6 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -const getBlobUrl = (data: any) => { +const getBlobUrl = (data: string | null) => { if (data) { return `data:image/jpeg;base64, ${data}`; } @@ -16,7 +16,7 @@ export function ImageStreamViewer({ undefined ); useEffect(() => { - setBase64String(getBlobUrl(base64Data)); + setBase64String(getBlobUrl(base64Data )); console.log(`sizedata: ${base64String}`); }, [base64Data]); return ( diff --git a/frontend/src/components/inspector.jsx b/frontend/src/components/inspector.jsx deleted file mode 100644 index f256a38..0000000 --- a/frontend/src/components/inspector.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import { IoMdRefresh } from "react-icons/io"; -import { MdOutlinePlayCircle } from "react-icons/md"; - -const Inspector = () => { - return ( - <div> - <div className="flex-[45%] mx-5 mt-2"> - <h1 className="font-bold text-xl">OPSI :</h1> - <h1 className="font-semibold text-md">Tampilan Label Prediksi</h1> - </div> - <div className="flex-[40%] mx-5 mt-10"> - <div className="flex items-center"> - <h1 className="font-bold text-xl">SOURCE :</h1> - <button className="ml-2 focus:outline-none"> - <IoMdRefresh size={20} /> - </button> - </div> - <h1 className="font-semibold text-sm">CAMERA SOURCE URL (RTSP)</h1> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="RTSP://localhost:8082/A301.live" - /> - <button className="ml-2 focus:outline-none"> - <MdOutlinePlayCircle size={20} /> - </button> - </div> - - <h1 className="font-semibold text-sm mt-5"> - CAMERA WITH LABELED SOURCE (RTSP) - </h1> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="RTSP://localhost:8082/A301-label.live" - /> - <button className="ml-2 focus:outline-none"> - <MdOutlinePlayCircle size={20} /> - </button> - </div> - - <h1 className="font-semibold text-sm mt-5">LABEL SOURCE (WEBSOCKET)</h1> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="WS://localhost:8082/ws" - /> - <button className="ml-2 focus:outline-none"> - <MdOutlinePlayCircle size={20} /> - </button> - </div> - </div> - </div> - ); -}; - -export {Inspector} \ No newline at end of file diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index 22fb40a..a84ef6b 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -1,9 +1,7 @@ import { useState } from "react"; import { useNavigate, useLocation } from "react-router-dom"; -import { IoMdArrowDropleft } from "react-icons/io"; import { MdMonitor } from "react-icons/md"; import { HiClipboardDocumentList } from "react-icons/hi2"; -import { MdOutlineVideoLabel } from "react-icons/md"; function TitleSidebar() { return ( @@ -66,10 +64,10 @@ export default function Sidebar() { const location = useLocation(); const Menus = [ - { title: "Tampilan Langsung", path: "/", icon: <MdMonitor /> }, + { title: "Tampilan Langsung", path: "/app", icon: <MdMonitor /> }, { title: "Laporan", - path: "/laporan", + path: "/app/laporan", icon: <HiClipboardDocumentList />, }, ]; diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 113d2c9..09c9602 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,5 +1,5 @@ import React, { createContext, useContext, useMemo, useState } from "react"; -import { useLocalStorage } from "../hooks/useLocalStorage"; +import { useLocalStorage } from "@/hooks/useLocalStorage"; import { useNavigate } from "react-router-dom"; interface IAuthContext { diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx index 87cf295..0eb5d68 100644 --- a/frontend/src/pages/index.jsx +++ b/frontend/src/pages/index.jsx @@ -1,7 +1,8 @@ +import { IoMdRefresh } from "react-icons/io"; import { ImageStreamViewer } from "../components/ImageStreamViewer"; -import { Inspector } from "../components/inspector"; import Title from "../components/titleDashboard"; import { useStreamSocket } from "../context/CameraSocketContext"; +import { MdOutlinePlayCircle } from "react-icons/md"; const Home = () => { const stream = useStreamSocket(); @@ -9,7 +10,7 @@ const Home = () => { return ( <div className="w-full p-4"> <Title /> - <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> + <hr className="mt-2 border-2 border-[#66ABB1]" /> <div className="flex"> <div className="flex-grow flex flex-col items-center"> <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> @@ -24,4 +25,43 @@ const Home = () => { ); }; +const Inspector = () => { + return ( + <div className="flex flex-col p-4 gap-4"> + <div className="flex flex-col"> + <h1 className="font-bold text-xl">OPSI :</h1> + <h1 className="font-semibold text-md">Tampilan Label Prediksi</h1> + </div> + <div className="flex flex-col"> + <div className="flex items-center"> + <h1 className="font-bold text-xl">SOURCE :</h1> + <button className="ml-2 focus:outline-none"> + <IoMdRefresh size={20} /> + </button> + </div> + <RtspInput/> + </div> + </div> + ); +}; + +const RtspInput = () => { + return ( + <> + <label htmlFor="cam-rtsp">RTSP URL</label> + <div className="flex items-center border border-gray-400 rounded px-3 py-2"> + <input + type="text" + className="flex-1 focus:outline-none" + placeholder="RTSP://localhost:8082/A301.live" + id="cam-source-rtsp" + /> + <button className="ml-2 focus:outline-none"> + <MdOutlinePlayCircle size={20} /> + </button> + </div> + </> + ); +}; + export default Home; From 33e94ca2a6bb4b661983b96f672a864a70e9f4a3 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 14 Jul 2024 22:32:16 +0800 Subject: [PATCH 13/63] feat: migrate to local fonts, fix missing assets --- frontend/package-lock.json | 12 ++++++++++++ frontend/package.json | 2 ++ frontend/src/components/titleDashboard.jsx | 3 ++- frontend/src/index.css | 2 -- frontend/src/main.tsx | 4 +++- frontend/src/pages/laporan.jsx | 2 +- 6 files changed, 20 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 50ce7d4..d98c229 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,8 @@ "name": "bye-cheating", "version": "0.0.0", "dependencies": { + "@fontsource-variable/karla": "^5.0.21", + "@fontsource/poppins": "^5.0.14", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", @@ -854,6 +856,16 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fontsource-variable/karla": { + "version": "5.0.21", + "resolved": "https://registry.npmjs.org/@fontsource-variable/karla/-/karla-5.0.21.tgz", + "integrity": "sha512-YE1oSLPy+/x/BSmQISA/RE6qx+0zCWTZ3mvEha85K/JBIEkDNgqHfxNk4iGjwzcqzd6qifjFtpr8BO6QvgOGcA==" + }, + "node_modules/@fontsource/poppins": { + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@fontsource/poppins/-/poppins-5.0.14.tgz", + "integrity": "sha512-nmM1zpPo3Uh4JcGAVSQuWaZNYh2FbbwWhZ5t6hRaynmJaNTBW85d3nEh9zMmzI0HX7X5xqQVdRHeDatKpOGsnA==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6d9820b..321832f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,6 +10,8 @@ "preview": "vite preview" }, "dependencies": { + "@fontsource-variable/karla": "^5.0.21", + "@fontsource/poppins": "^5.0.14", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", diff --git a/frontend/src/components/titleDashboard.jsx b/frontend/src/components/titleDashboard.jsx index 09f2881..90947c2 100644 --- a/frontend/src/components/titleDashboard.jsx +++ b/frontend/src/components/titleDashboard.jsx @@ -3,7 +3,8 @@ export default function Title() { <> <div className="flex my-10 mx-16 place-content-center"> <div className=" items-end"> - <img src="logo.png" /> + {/* dont forget add `/` like `/logo.png` to avoid missing asset */} + <img src="/logo.png" /> </div> <div className="my-10 text-[#66ABB1]"> <h1 className=" text-7xl font-extrabold">BYE-CHEATING</h1> diff --git a/frontend/src/index.css b/frontend/src/index.css index a8996d5..9bb37ee 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,5 +1,3 @@ -@import url("https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); -@import url("https://fonts.googleapis.com/css2?family=Karla:ital,wght@0,200..800;1,200..800&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap"); @tailwind base; @tailwind components; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 04fb315..375d048 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,8 @@ import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; +import "@fontsource/poppins"; +import "@fontsource-variable/karla"; import "./index.css"; const container = document.getElementById("root") as ReactDOM.Container; @@ -8,5 +10,5 @@ const container = document.getElementById("root") as ReactDOM.Container; ReactDOM.createRoot(container).render( <React.StrictMode> <App /> - </React.StrictMode>, + </React.StrictMode> ); diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/laporan.jsx index dbe4b78..77a50a7 100644 --- a/frontend/src/pages/laporan.jsx +++ b/frontend/src/pages/laporan.jsx @@ -31,7 +31,7 @@ export default function Laporan() { }; return ( - <div className="w-full container mx-auto ml-28"> + <div className="w-full container mx-auto"> <Title /> <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> {/* Data Pengunjung */} From cb7f1c002f47348ad8954df6a49d0f39a6d68968 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 23 Jul 2024 20:04:05 +0800 Subject: [PATCH 14/63] feat: convert to mantine ui --- frontend/index.html | 2 +- frontend/package-lock.json | 416 ++++++++++++++++++++- frontend/package.json | 7 +- frontend/postcss.config.js | 10 + frontend/src/App.tsx | 46 ++- frontend/src/components/sidebar.tsx | 129 +++---- frontend/src/components/titleDashboard.jsx | 18 - frontend/src/components/titleDashboard.tsx | 22 ++ frontend/src/index.css | 4 +- frontend/src/layout/MainLayout.tsx | 46 ++- frontend/src/main.tsx | 41 +- frontend/src/pages/CameraView.jsx | 50 +++ frontend/src/pages/index.jsx | 67 ---- frontend/src/pages/laporan.jsx | 168 +++++---- frontend/src/pages/login.tsx | 68 +++- frontend/src/pages/register.tsx | 80 ++++ 16 files changed, 894 insertions(+), 280 deletions(-) delete mode 100644 frontend/src/components/titleDashboard.jsx create mode 100644 frontend/src/components/titleDashboard.tsx create mode 100644 frontend/src/pages/CameraView.jsx delete mode 100644 frontend/src/pages/index.jsx create mode 100644 frontend/src/pages/register.tsx diff --git a/frontend/index.html b/frontend/index.html index 86b354f..6561136 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ <meta charset="UTF-8" /> <link rel="icon" type="image/png" href="/LogoBgWhite.png" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> - <title>Bye-Cheating + Bye Cheating
    diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d98c229..75f49c3 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -10,6 +10,9 @@ "dependencies": { "@fontsource-variable/karla": "^5.0.21", "@fontsource/poppins": "^5.0.14", + "@mantine/core": "^7.11.2", + "@mantine/form": "^7.11.2", + "@mantine/hooks": "^7.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", @@ -33,7 +36,9 @@ "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.4", "globals": "^15.8.0", - "postcss": "^8.4.38", + "postcss": "^8.4.39", + "postcss-preset-mantine": "^1.15.0", + "postcss-simple-vars": "^7.0.1", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", @@ -350,6 +355,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.8.tgz", + "integrity": "sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", @@ -856,6 +872,54 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz", + "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==", + "dependencies": { + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz", + "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.4" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.19", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.19.tgz", + "integrity": "sha512-Jk6zITdjjIvjO/VdQFvpRaD3qPwOHH6AoDHxjhpy+oK4KFgaSP871HYWUAPdnLmx1gQ+w/pB312co3tVml+BXA==", + "dependencies": { + "@floating-ui/react-dom": "^2.1.1", + "@floating-ui/utils": "^0.2.4", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA==" + }, "node_modules/@fontsource-variable/karla": { "version": "5.0.21", "resolved": "https://registry.npmjs.org/@fontsource-variable/karla/-/karla-5.0.21.tgz", @@ -993,6 +1057,55 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mantine/core": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/core/-/core-7.11.2.tgz", + "integrity": "sha512-T64RjdgY8UPAv249miW1lQyPPot1JbCcKKsAZMNQHgcttcxLhrFpKVvglc4/48hdSoxI4LYJPNvqp7zciZmucQ==", + "dependencies": { + "@floating-ui/react": "^0.26.9", + "clsx": "^2.1.1", + "react-number-format": "^5.3.1", + "react-remove-scroll": "^2.5.7", + "react-textarea-autosize": "8.5.3", + "type-fest": "^4.12.0" + }, + "peerDependencies": { + "@mantine/hooks": "7.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/core/node_modules/type-fest": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.21.0.tgz", + "integrity": "sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mantine/form": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/form/-/form-7.11.2.tgz", + "integrity": "sha512-NRY4HDgcArDEP+wUaHITnoVh0Ce3rM3Blo9fLNj2VYO8k7AfuSWp+fQdqrjDI0k9wGU3YEj4/dbwOjKbtXEhxw==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "klona": "^2.0.6" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/@mantine/hooks": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-7.11.2.tgz", + "integrity": "sha512-jhyVe/sbDEG2U8rr2lMecUPgQxcfr5hh9HazqGfkS7ZRIMDO7uJ947yAcTMGGkp5Lxtt5TBFt1Cb6tiB2/1agg==", + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1356,13 +1469,13 @@ "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "dev": true, + "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2004,6 +2117,14 @@ "node": ">= 6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -2090,7 +2211,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -2207,6 +2328,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2811,8 +2937,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -3045,6 +3170,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -3307,6 +3440,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -3812,6 +3953,14 @@ "json-buffer": "3.0.1" } }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "engines": { + "node": ">= 8" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4444,6 +4593,28 @@ "url": "https://github.com/sponsors/antonk52" } }, + "node_modules/postcss-mixins": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-9.0.4.tgz", + "integrity": "sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "postcss-js": "^4.0.0", + "postcss-simple-vars": "^7.0.0", + "sugarss": "^4.0.1" + }, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-nested": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", @@ -4463,6 +4634,19 @@ "postcss": "^8.2.14" } }, + "node_modules/postcss-preset-mantine": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/postcss-preset-mantine/-/postcss-preset-mantine-1.15.0.tgz", + "integrity": "sha512-OKPs6uoORSXlU/GFH1ZtFaslecHBPwuoSikdL5W3WKJm4ZPAQM0mw9x9m3toa/Mo1JhoBmYMM28i+zEdav5Edg==", + "dev": true, + "dependencies": { + "postcss-mixins": "^9.0.4", + "postcss-nested": "^6.0.1" + }, + "peerDependencies": { + "postcss": ">=8.0.0" + } + }, "node_modules/postcss-selector-parser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", @@ -4476,6 +4660,22 @@ "node": ">=4" } }, + "node_modules/postcss-simple-vars": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-simple-vars/-/postcss-simple-vars-7.0.1.tgz", + "integrity": "sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==", + "dev": true, + "engines": { + "node": ">=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.1" + } + }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", @@ -4660,6 +4860,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-number-format": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-number-format/-/react-number-format-5.4.0.tgz", + "integrity": "sha512-NWdICrqLhI7rAS8yUeLVd6Wr4cN7UjJ9IBTS0f/a9i7UB4x4Ti70kGnksBtZ7o4Z7YRbvCMMR/jQmkoOBa/4fg==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-player": { "version": "2.16.0", "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.16.0.tgz", @@ -4684,6 +4896,51 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.5.10", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.10.tgz", + "integrity": "sha512-m3zvBRANPBw3qxVVjEIPEQinkcwlFZ4qyomuWVpNJdv4c6MvHfXV0C3L9Jx5rr3HeBHKNRX+1jreB5QloDIJjA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", + "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.24.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", @@ -4714,6 +4971,44 @@ "react-dom": ">=16.8" } }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.3.tgz", + "integrity": "sha512-XT1024o2pqCuZSuBt9FwHlaDeNtVrtCXu0Rnz88t1jUGheCLa3PhjE1GH8Ctm2axEtvdCl5SUHYschyQ0L5QHQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-use-websocket": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.8.1.tgz", @@ -4765,6 +5060,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", @@ -5354,6 +5654,22 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sugarss": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-4.0.1.tgz", + "integrity": "sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -5397,6 +5713,11 @@ "sweetalert2": "^11.0.0" } }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, "node_modules/tailwindcss": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", @@ -5517,6 +5838,11 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5822,6 +6148,84 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", + "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.3.0.tgz", + "integrity": "sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", + "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.2.1.tgz", + "integrity": "sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 321832f..7d71108 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,9 @@ "dependencies": { "@fontsource-variable/karla": "^5.0.21", "@fontsource/poppins": "^5.0.14", + "@mantine/core": "^7.11.2", + "@mantine/form": "^7.11.2", + "@mantine/hooks": "^7.11.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.2.1", @@ -35,7 +38,9 @@ "eslint": "^8.57.0", "eslint-plugin-react": "^7.34.4", "globals": "^15.8.0", - "postcss": "^8.4.38", + "postcss": "^8.4.39", + "postcss-preset-mantine": "^1.15.0", + "postcss-simple-vars": "^7.0.1", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", "tailwindcss": "^3.4.3", diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js index 2e7af2b..a620c85 100644 --- a/frontend/postcss.config.js +++ b/frontend/postcss.config.js @@ -2,5 +2,15 @@ export default { plugins: { tailwindcss: {}, autoprefixer: {}, + 'postcss-preset-mantine': {}, + 'postcss-simple-vars': { + variables: { + 'mantine-breakpoint-xs': '36em', + 'mantine-breakpoint-sm': '48em', + 'mantine-breakpoint-md': '62em', + 'mantine-breakpoint-lg': '75em', + 'mantine-breakpoint-xl': '88em', + }, + }, }, } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2b7fb69..13116fc 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,34 +1,30 @@ -import { - Outlet, - Route, - BrowserRouter as Router, - Routes, -} from "react-router-dom"; +import { Outlet, Route, Routes } from "react-router-dom"; import MainLayout from "@/layout/MainLayout"; -import { default as Home, default as WebSocketDemo } from "@/pages/index"; +import { + default as CameraView, + default as WebSocketDemo, +} from "@/pages/CameraView"; import Laporan from "@/pages/laporan"; import { LoginPage } from "@/pages/login"; -import { AuthProvider } from "@/context/AuthContext"; -import { StreamSocketProvider } from "@/context/CameraSocketContext"; +import { RegisterPage } from "./pages/register"; function App() { return ( - - - - - }> - } /> - }> - } /> - } /> - } /> - - - - - - + + }> + } /> + } /> + } /> + }> + } /> + } /> + } /> + test view
    } /> + 404 not found
    } /> + + + 404 not found
    } /> + ); } diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index a84ef6b..fe9f28b 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -1,104 +1,69 @@ -import { useState } from "react"; -import { useNavigate, useLocation } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { MdMonitor } from "react-icons/md"; import { HiClipboardDocumentList } from "react-icons/hi2"; +import { NavLink, Text } from "@mantine/core"; +import { useState } from "react"; +import { HiLogout } from "react-icons/hi"; + +const menuLists = [ + { title: "Tampilan Langsung", path: "/app", icon: }, + { + title: "Laporan", + path: "/app/laporan", + icon: , + }, +]; function TitleSidebar() { return ( -
    +
    -

    - Dashboard -

    + Dashboard
    ); } -function SubMenu({ - title, - icon, - isActive, - onClick, - isExpanded, -}: { - title: string; - icon: JSX.Element; - isActive: boolean; - isExpanded: boolean; - onClick: () => void; -}) { - const mainColor = isActive - ? "bg-[#F59024] text-white" - : "hover:bg-[#F59024] hover:text-white"; - - return ( -
  • - {icon} - - {title} - -
  • - ); -} - -export default function Sidebar() { - const [open, setOpen] = useState(true); - const navigate = useNavigate(); +export default function NavbarContent() { const location = useLocation(); - - const Menus = [ - { title: "Tampilan Langsung", path: "/app", icon: }, - { - title: "Laporan", - path: "/app/laporan", - icon: , - }, - ]; + const navigate = useNavigate(); + const [active, setActive] = useState( + menuLists.findIndex((item) => item.path == (location.pathname ?? "/app")) + ); return ( <> -
    - {/* setOpen(!open)} - /> */} - - - {/* Sub Menu */} -
      - {Menus.map((menu, index) => ( - navigate(menu.path)} - title={menu.title} - isExpanded={open} - /> - ))} -
    -
    + + {menuLists.map((val, idx) => ( + { + navigate(val.path); + setActive(idx); + }} + /> + ))} + } + onClick={() => { + console.log("logout"); + }} + /> ); } diff --git a/frontend/src/components/titleDashboard.jsx b/frontend/src/components/titleDashboard.jsx deleted file mode 100644 index 90947c2..0000000 --- a/frontend/src/components/titleDashboard.jsx +++ /dev/null @@ -1,18 +0,0 @@ -export default function Title() { - return ( - <> -
    -
    - {/* dont forget add `/` like `/logo.png` to avoid missing asset */} - -
    -
    -

    BYE-CHEATING

    -

    - Jaminan Integritas, Langkah Cerdas Mengawal Keadilan Akademik! -

    -
    -
    - - ); -} diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx new file mode 100644 index 0000000..f5e403c --- /dev/null +++ b/frontend/src/components/titleDashboard.tsx @@ -0,0 +1,22 @@ +import { Box, Group, Text } from "@mantine/core"; + +export default function Title({leftSection}:{leftSection: React.ReactNode}) { + return ( + <> + + {leftSection} +
    + {/* dont forget add `/` like `/logo.png` to avoid missing asset */} + +
    + +

    BYE-CHEATING

    + + + Jaminan Integritas, Langkah Cerdas Mengawal Keadilan Akademik! + +
    +
    + + ); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 9bb37ee..2eb7be1 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,6 @@ -@tailwind base; +@layer tailwind { + @tailwind base; + } @tailwind components; @tailwind utilities; diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index e0e527f..7011881 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,13 +1,45 @@ import { Outlet } from "react-router-dom"; -import Sidebar from "../components/sidebar"; +import NavbarContent from "../components/sidebar"; +import { AppShell, Burger, Group } from "@mantine/core"; +import Title from "@/components/titleDashboard"; +import { useDisclosure } from "@mantine/hooks"; export default function MainLayout() { + const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); return ( -
    - -
    + + + + + + + <Burger + opened={mobileOpened} + onClick={toggleMobile} + hiddenFrom="sm" + size="sm" + /> + </> + } + /> + </AppShell.Header> + <AppShell.Main> <Outlet /> - </div> - </div> + </AppShell.Main> + <AppShell.Footer zIndex={300}> + <Group w={"100%"} h={"100%"} bg={"myColor"} px={8}> + <p>this is footer</p> + </Group> + </AppShell.Footer> + </AppShell> ); -} \ No newline at end of file +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 375d048..79b3f25 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -3,12 +3,49 @@ import ReactDOM from "react-dom/client"; import App from "./App"; import "@fontsource/poppins"; import "@fontsource-variable/karla"; -import "./index.css"; +import "@/index.css"; +import "@mantine/core/styles.css"; +import { + createTheme, + MantineColorsTuple, + MantineProvider, +} from "@mantine/core"; +import { BrowserRouter } from "react-router-dom"; +import { AuthProvider } from "./context/AuthContext"; +import { StreamSocketProvider } from "./context/CameraSocketContext"; const container = document.getElementById("root") as ReactDOM.Container; +const myColor: MantineColorsTuple = [ + "#fff4e2", + "#ffe9cc", + "#ffd09c", + "#fdb766", + "#fca13a", + "#fb931d", + "#fc8c0c", + "#e17900", + "#c86a00", + "#ae5a00", +]; + +const theme = createTheme({ + colors: { + myColor, + }, + primaryColor: "myColor", +}); + ReactDOM.createRoot(container).render( <React.StrictMode> - <App /> + <MantineProvider theme={theme}> + <BrowserRouter> + <AuthProvider> + <StreamSocketProvider> + <App /> + </StreamSocketProvider> + </AuthProvider> + </BrowserRouter> + </MantineProvider> </React.StrictMode> ); diff --git a/frontend/src/pages/CameraView.jsx b/frontend/src/pages/CameraView.jsx new file mode 100644 index 0000000..b67cd08 --- /dev/null +++ b/frontend/src/pages/CameraView.jsx @@ -0,0 +1,50 @@ +import { ImageStreamViewer } from "../components/ImageStreamViewer"; +import { useStreamSocket } from "../context/CameraSocketContext"; +import { MdOutlinePlayArrow } from "react-icons/md"; +import { ActionIcon, Text, TextInput } from "@mantine/core"; + +const CameraView = () => { + const stream = useStreamSocket(); + + return ( + <div className="w-full p-4"> + <div className="flex"> + <div className="flex-grow flex flex-col items-center"> + <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> + <h3>Tanggal: 19 Februari 2024</h3> + <ImageStreamViewer base64Data={stream.base64data} /> + <br /> + </div> + <div className="border-l border-[#66ABB1] h-[40rem]"></div> + <Inspector /> + </div> + </div> + ); +}; + +const Inspector = () => { + return ( + <div className="flex flex-col p-2 gap-2"> + <Text size="xl">Pengaturan</Text> + <RtspInput /> + </div> + ); +}; + +const RtspInput = () => { + return ( + <> + <TextInput + label="RTSP URL" + placeholder="rtsp://urlname" + rightSection={ + <ActionIcon variant="gradient"> + <MdOutlinePlayArrow /> + </ActionIcon> + } + /> + </> + ); +}; + +export default CameraView; diff --git a/frontend/src/pages/index.jsx b/frontend/src/pages/index.jsx deleted file mode 100644 index 0eb5d68..0000000 --- a/frontend/src/pages/index.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import { IoMdRefresh } from "react-icons/io"; -import { ImageStreamViewer } from "../components/ImageStreamViewer"; -import Title from "../components/titleDashboard"; -import { useStreamSocket } from "../context/CameraSocketContext"; -import { MdOutlinePlayCircle } from "react-icons/md"; - -const Home = () => { - const stream = useStreamSocket(); - - return ( - <div className="w-full p-4"> - <Title /> - <hr className="mt-2 border-2 border-[#66ABB1]" /> - <div className="flex"> - <div className="flex-grow flex flex-col items-center"> - <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> - <h3>Tanggal: 19 Februari 2024</h3> - <ImageStreamViewer base64Data={stream.base64data} /> - <br /> - </div> - <div className="border-l border-[#66ABB1] h-[40rem]"></div> - <Inspector /> - </div> - </div> - ); -}; - -const Inspector = () => { - return ( - <div className="flex flex-col p-4 gap-4"> - <div className="flex flex-col"> - <h1 className="font-bold text-xl">OPSI :</h1> - <h1 className="font-semibold text-md">Tampilan Label Prediksi</h1> - </div> - <div className="flex flex-col"> - <div className="flex items-center"> - <h1 className="font-bold text-xl">SOURCE :</h1> - <button className="ml-2 focus:outline-none"> - <IoMdRefresh size={20} /> - </button> - </div> - <RtspInput/> - </div> - </div> - ); -}; - -const RtspInput = () => { - return ( - <> - <label htmlFor="cam-rtsp">RTSP URL</label> - <div className="flex items-center border border-gray-400 rounded px-3 py-2"> - <input - type="text" - className="flex-1 focus:outline-none" - placeholder="RTSP://localhost:8082/A301.live" - id="cam-source-rtsp" - /> - <button className="ml-2 focus:outline-none"> - <MdOutlinePlayCircle size={20} /> - </button> - </div> - </> - ); -}; - -export default Home; diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/laporan.jsx index 77a50a7..2c79b90 100644 --- a/frontend/src/pages/laporan.jsx +++ b/frontend/src/pages/laporan.jsx @@ -1,10 +1,25 @@ import { useState } from "react"; -import Title from "../components/titleDashboard"; import { DataCctv as initialDataCctv } from "../model/dataset"; import { FaEdit } from "react-icons/fa"; import Swal from "sweetalert2"; +import { Divider, ScrollArea, Table } from "@mantine/core"; export default function Laporan() { + return ( + <div className="w-full mx-auto p-4"> + <div className="flex flex-col"> + <h2 className="text-lg font-bold">Laporan</h2> + </div> + <Divider my={"md"}/> + {/* Data Pengunjung */} + <ScrollArea h={600}> + <ReportTable /> + </ScrollArea> + </div> + ); +} + +function ReportTableBackup() { const [dataCctv, setDataCctv] = useState(initialDataCctv); const handleEdit = (index) => { @@ -29,76 +44,93 @@ export default function Laporan() { } }); }; - return ( - <div className="w-full container mx-auto"> - <Title /> - <hr style={{ border: "1px solid #66ABB1" }} className="mt-2 " /> - {/* Data Pengunjung */} - <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> - <div className="overflow-x-auto pr-5 lg:pr-0"> - <table className="table-auto w-full"> - <thead> - <tr> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> - No - </th> - <th className="px-4 py-2 bg-slate-500 text-white">ID</th> - <th className="px-4 py-2 bg-slate-500 text-white">Status</th> - <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> - <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> - Edit - </th> - </tr> - </thead> - <tbody> - {dataCctv.map((entry, index) => ( - <tr - key={index} - className={ - index % 2 === 0 ? "bg-primary-50" : "bg-primary-100" - } + <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> + <div className="overflow-x-auto pr-5 lg:pr-0"> + <table className="table-auto w-full"> + <thead> + <tr> + <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> + No + </th> + <th className="px-4 py-2 bg-slate-500 text-white">ID</th> + <th className="px-4 py-2 bg-slate-500 text-white">Status</th> + <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> + <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> + <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> + Edit + </th> + </tr> + </thead> + <tbody> + {dataCctv.map((entry, index) => ( + <tr + key={index} + className={index % 2 === 0 ? "bg-primary-50" : "bg-primary-100"} + > + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + {index + 1} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} + > + {entry.id} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} > - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {index + 1} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.id} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.status} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.persen} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {entry.nama} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - <FaEdit - className="cursor-pointer" - onClick={() => handleEdit(index)} - /> - </td> - </tr> - ))} - </tbody> - </table> - </div> + {entry.status} + </td> + <td + className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ + entry.status === "Mencontek" ? "text-red-500" : "" + }`} + > + {entry.persen} + </td> + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + {entry.nama} + </td> + <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> + <FaEdit + className="cursor-pointer" + onClick={() => handleEdit(index)} + /> + </td> + </tr> + ))} + </tbody> + </table> </div> </div> ); } + +function ReportTable() { + return ( + <Table> + <Table.Thead> + <Table.Th>ID</Table.Th> + <Table.Th>Status</Table.Th> + <Table.Th>Akurasi</Table.Th> + <Table.Th>Nama</Table.Th> + </Table.Thead> + <Table.Tbody> + {initialDataCctv.map((val, idx) => { + return ( + <Table.Tr key={idx}> + <Table.Td>{val.id}</Table.Td> + <Table.Td>{val.status}</Table.Td> + <Table.Td>{val.persen}</Table.Td> + <Table.Td>{val.nama}</Table.Td> + </Table.Tr> + ); + })} + </Table.Tbody> + </Table> + ); +} diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 530076f..1d17d92 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,7 +1,71 @@ +import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { useDisclosure } from "@mantine/hooks"; +import { useNavigate } from "react-router-dom"; + export function LoginPage() { return ( - <div> - <h1>Login Page</h1> + <div className="w-full h-svh flex flex-col items-center justify-center"> + <LoginCard /> + </div> + ); +} + +function LoginCard() { + const navigate = useNavigate(); + const [passwordVisible, { toggle: togglePasswordVisibility }] = + useDisclosure(false); + + const form = useForm({ + mode: "uncontrolled", + initialValues: { + email: "", + password: "", + }, + validate: { + email: (value) => (/^\S+@\S+$/.test(value) ? null : "Invalid email"), + password: (value) => + /^(?=.*[A-Z])[A-Za-z\d]{8,}$/.test(value) + ? null + : "At least 8 chars and contain at least 1 uppercase", + }, + }); + + return ( + <div className="border-2 rounded-md p-4 flex flex-col gap-4 w-96"> + <Flex justify={"center"}> + <h1 className="text-2xl font-bold">Login</h1> + </Flex> + <form + className="flex flex-col gap-2" + onSubmit={form.onSubmit((values) => console.log(values))} + > + <TextInput + withAsterisk + label="email" + placeholder="your@email.com" + key={form.key("email")} + {...form.getInputProps("email")} + /> + <PasswordInput + withAsterisk + label="password" + visible={passwordVisible} + onVisibilityChange={togglePasswordVisibility} + key={form.key("password")} + {...form.getInputProps("password")} + /> + <Divider /> + <Flex direction={"column"} gap={"4px"}> + <Button type="submit">Login</Button> + <Button + onClick={() => navigate("/register", { replace: true })} + variant="outline" + > + Create new account + </Button> + </Flex> + </form> </div> ); } diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx new file mode 100644 index 0000000..3e4d1cb --- /dev/null +++ b/frontend/src/pages/register.tsx @@ -0,0 +1,80 @@ +import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; +import { useForm } from "@mantine/form"; +import { useDisclosure } from "@mantine/hooks"; +import { useNavigate } from "react-router-dom"; + +export function RegisterPage() { + return ( + <div className="w-full h-svh flex flex-col items-center justify-center"> + <LoginCard /> + </div> + ); +} + +function LoginCard() { + const navigate = useNavigate(); + const [passwordVisible, { toggle: togglePasswordVisibility }] = + useDisclosure(false); + + const form = useForm({ + mode: "uncontrolled", + initialValues: { + email: "", + name: "", + password: "", + }, + validate: { + email: (value) => (/^\S+@\S+$/.test(value) ? null : "Invalid email"), + name: (value) => (/^\S+$/.test(value) ? null : "Field is required"), + password: (value) => + /^(?=.*[A-Z])[A-Za-z\d]{8,}$/.test(value) + ? null + : "At least 8 chars and contain at least 1 uppercase", + }, + }); + + return ( + <div className="border-2 rounded-md p-4 flex flex-col gap-4 w-96"> + <Flex justify={"center"}> + <h1 className="text-2xl font-bold">Register</h1> + </Flex> + <form + className="flex flex-col gap-2" + onSubmit={form.onSubmit((values) => console.log(values))} + > + <TextInput + withAsterisk + label="Email" + placeholder="your@email.com" + key={form.key("email")} + {...form.getInputProps("email")} + /> + <TextInput + withAsterisk + label="Name" + placeholder="your name" + key={form.key("name")} + {...form.getInputProps("name")} + /> + <PasswordInput + withAsterisk + label="password" + visible={passwordVisible} + onVisibilityChange={togglePasswordVisibility} + key={form.key("password")} + {...form.getInputProps("password")} + /> + <Divider /> + <Flex direction={"column"} gap={"4px"}> + <Button type="submit">Register</Button> + <Button + onClick={() => navigate("/login", { replace: true })} + variant="outline" + > + Back to Login + </Button> + </Flex> + </form> + </div> + ); +} From 5238da72adc4f40a44bbe3b64349949feeef587a Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Wed, 24 Jul 2024 22:01:19 +0800 Subject: [PATCH 15/63] update report ui and overrall fonts --- frontend/src/components/titleDashboard.tsx | 13 ++++++++----- frontend/src/layout/MainLayout.tsx | 4 ++-- frontend/src/main.tsx | 2 ++ frontend/src/pages/CameraView.jsx | 2 +- frontend/src/pages/laporan.jsx | 18 ++++++++++-------- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx index f5e403c..f10ed07 100644 --- a/frontend/src/components/titleDashboard.tsx +++ b/frontend/src/components/titleDashboard.tsx @@ -1,18 +1,21 @@ import { Box, Group, Text } from "@mantine/core"; -export default function Title({leftSection}:{leftSection: React.ReactNode}) { +export default function TitleDashboard({ + leftSection, +}: { + leftSection: React.ReactNode; +}) { return ( <> <Group h={"100%"} px={"md"}> {leftSection} <div className=" items-end"> {/* dont forget add `/` like `/logo.png` to avoid missing asset */} - <img src="/logo.png" className="w-24" /> + <img src="/logo.png" className="w-24" /> </div> <Box className="text-[#66ABB1] overflow-clip"> - <h1 className=" text-xl font-extrabold">BYE-CHEATING</h1> - - <Text visibleFrom="md" size="xs"> + <h1 className=" text-2xl font-extrabold">BYE-CHEATING</h1> + <Text visibleFrom="md" fw={"normal"} size="xs"> Jaminan Integritas, Langkah Cerdas Mengawal Keadilan Akademik! </Text> </Box> diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 7011881..bae05bf 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,7 +1,7 @@ import { Outlet } from "react-router-dom"; import NavbarContent from "../components/sidebar"; import { AppShell, Burger, Group } from "@mantine/core"; -import Title from "@/components/titleDashboard"; +import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; export default function MainLayout() { @@ -19,7 +19,7 @@ export default function MainLayout() { <NavbarContent /> </AppShell.Navbar> <AppShell.Header> - <Title + <TitleDashboard leftSection={ <> <Burger diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 79b3f25..5edf3f7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -33,6 +33,8 @@ const theme = createTheme({ colors: { myColor, }, + fontFamily: "Poppins,Karla, sans-serif", + // headings: {fontFamily:"Poppins, sans-serif"}, primaryColor: "myColor", }); diff --git a/frontend/src/pages/CameraView.jsx b/frontend/src/pages/CameraView.jsx index b67cd08..78de266 100644 --- a/frontend/src/pages/CameraView.jsx +++ b/frontend/src/pages/CameraView.jsx @@ -38,7 +38,7 @@ const RtspInput = () => { label="RTSP URL" placeholder="rtsp://urlname" rightSection={ - <ActionIcon variant="gradient"> + <ActionIcon variant="filled"> <MdOutlinePlayArrow /> </ActionIcon> } diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/laporan.jsx index 2c79b90..08b3e74 100644 --- a/frontend/src/pages/laporan.jsx +++ b/frontend/src/pages/laporan.jsx @@ -10,11 +10,11 @@ export default function Laporan() { <div className="flex flex-col"> <h2 className="text-lg font-bold">Laporan</h2> </div> - <Divider my={"md"}/> + <Divider my={"md"} /> {/* Data Pengunjung */} - <ScrollArea h={600}> + <Table.ScrollContainer className="border-[1px]" h={600}> <ReportTable /> - </ScrollArea> + </Table.ScrollContainer> </div> ); } @@ -112,12 +112,14 @@ function ReportTableBackup() { function ReportTable() { return ( - <Table> + <Table stickyHeader striped> <Table.Thead> - <Table.Th>ID</Table.Th> - <Table.Th>Status</Table.Th> - <Table.Th>Akurasi</Table.Th> - <Table.Th>Nama</Table.Th> + <Table.Tr> + <Table.Th>ID</Table.Th> + <Table.Th>Status</Table.Th> + <Table.Th>Akurasi</Table.Th> + <Table.Th>Nama</Table.Th> + </Table.Tr> </Table.Thead> <Table.Tbody> {initialDataCctv.map((val, idx) => { From e8d9da98e22584e8f6b78a741ef92c3055e5ee35 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Sun, 28 Jul 2024 18:58:27 +0800 Subject: [PATCH 16/63] feat: update layout --- frontend/src/components/ImageStreamViewer.tsx | 46 +++++++++++-------- frontend/src/components/sidebar.tsx | 1 + frontend/src/context/CameraSocketContext.tsx | 4 +- frontend/src/layout/MainLayout.tsx | 13 ++++-- frontend/src/pages/CameraView.jsx | 8 ++-- frontend/src/pages/login.tsx | 3 +- frontend/src/pages/register.tsx | 4 +- 7 files changed, 48 insertions(+), 31 deletions(-) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index e0970e6..43380a8 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,35 +1,41 @@ import { useEffect, useState } from "react"; +import { + MdOutlineWarningAmber, +} from "react-icons/md"; -const getBlobUrl = (data: string | null) => { - if (data) { - return `data:image/jpeg;base64, ${data}`; - } - return; -}; - +// const getBlobUrl = (data: string | null) => { +// if (data) { +// return `data:image/jpeg;base64, ${data}`; +// } +// return; +// }; +//todo: create error handling like result, error, and loading. export function ImageStreamViewer({ base64Data, + error }: { - base64Data: string | null; + /** + * Base64 image string, not include `data:image/jpeg;base64,` + */ + base64Data?: string; + error?: string }) { const [base64String, setBase64String] = useState<string | undefined>( undefined ); useEffect(() => { - setBase64String(getBlobUrl(base64Data )); - console.log(`sizedata: ${base64String}`); + setBase64String(base64Data ?? ""); }, [base64Data]); return ( - <div className="flex justify-center"> - <div className="border-4 border-solid min-w-[640px] aspect-video rounded-lg flex place-content-center mt-5 px-10"> - {base64String ? ( - <img src={base64String} alt="CCTV Stream" /> - ) : ( - <h1 className="place-content-center text-2xl bg-slate-400 mx-auto my-auto px-7 py-2 rounded-lg bg-opacity-30"> - Tidak Ada Video CCTV - </h1> - )} - </div> + <div className="border-2 border-solid min-w-[640px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10"> + {base64String ? ( + <img src={base64String} alt="CCTV Stream" /> + ) : ( + <p className="flex gap-2 justify-center items-center"> + {" "} + <MdOutlineWarningAmber /> {error ?? "Masalah tidak diketahui"} + </p> + )} </div> ); } diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index fe9f28b..1b27a2f 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -42,6 +42,7 @@ export default function NavbarContent() { {menuLists.map((val, idx) => ( <NavLink variant="filled" + key={val.title} component={"button"} label={val.title} diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/context/CameraSocketContext.tsx index 3d01a87..ff0a248 100644 --- a/frontend/src/context/CameraSocketContext.tsx +++ b/frontend/src/context/CameraSocketContext.tsx @@ -39,7 +39,7 @@ export const useStreamSocket = () => { const context = useContext(StreamSocketContext); if (context === undefined) { throw new Error( - "useStreamSocket must be used within a StreamSocketProvider", + "useStreamSocket must be used inside of `<StreamSocketProvider>` component", ); } return context as IStreamSocketContext; @@ -85,6 +85,7 @@ export function StreamSocketProvider({ children }: { children: JSX.Element }) { }); socket.on("data", (val) => { console.log(`data: ${val}`); + setBase64Stream(val) }); return socket as unknown as Socket; }); @@ -117,7 +118,6 @@ export function StreamSocketProvider({ children }: { children: JSX.Element }) { useEffect(() => { if (!initialized.current) { initialized.current = true; - data.start(); } return () => { diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index bae05bf..25dd9ce 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,6 +1,10 @@ import { Outlet } from "react-router-dom"; import NavbarContent from "../components/sidebar"; -import { AppShell, Burger, Group } from "@mantine/core"; +import { + AppShell, + Burger, + Group, +} from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; @@ -10,10 +14,13 @@ export default function MainLayout() { <AppShell navbar={{ width: 200, + breakpoint: "sm", - collapsed: { mobile: !mobileOpened}, + collapsed: { mobile: !mobileOpened }, }} header={{ height: 100 }} + layout="alt" + > <AppShell.Navbar> <NavbarContent /> @@ -32,7 +39,7 @@ export default function MainLayout() { } /> </AppShell.Header> - <AppShell.Main> + <AppShell.Main className="flex flex-col"> <Outlet /> </AppShell.Main> <AppShell.Footer zIndex={300}> diff --git a/frontend/src/pages/CameraView.jsx b/frontend/src/pages/CameraView.jsx index 78de266..4353164 100644 --- a/frontend/src/pages/CameraView.jsx +++ b/frontend/src/pages/CameraView.jsx @@ -7,15 +7,15 @@ const CameraView = () => { const stream = useStreamSocket(); return ( - <div className="w-full p-4"> - <div className="flex"> + <div className="flex flex-grow p-4"> + <div className="flex flex-grow"> <div className="flex-grow flex flex-col items-center"> - <h1 className="text-xl font-bold">KAMERA CCTV RUANG A301</h1> + <h1 className="text-xl font-bold">Nama Camera</h1> <h3>Tanggal: 19 Februari 2024</h3> <ImageStreamViewer base64Data={stream.base64data} /> <br /> </div> - <div className="border-l border-[#66ABB1] h-[40rem]"></div> + <div className="border-l border-gray-300 min-h-full"></div> <Inspector /> </div> </div> diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 1d17d92..34c0fe1 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -37,7 +37,7 @@ function LoginCard() { <h1 className="text-2xl font-bold">Login</h1> </Flex> <form - className="flex flex-col gap-2" + className="flex flex-col gap-2 min-h-96" onSubmit={form.onSubmit((values) => console.log(values))} > <TextInput @@ -55,6 +55,7 @@ function LoginCard() { key={form.key("password")} {...form.getInputProps("password")} /> + <div className="flex-grow"></div> <Divider /> <Flex direction={"column"} gap={"4px"}> <Button type="submit">Login</Button> diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx index 3e4d1cb..936f5cb 100644 --- a/frontend/src/pages/register.tsx +++ b/frontend/src/pages/register.tsx @@ -39,7 +39,7 @@ function LoginCard() { <h1 className="text-2xl font-bold">Register</h1> </Flex> <form - className="flex flex-col gap-2" + className="flex flex-col gap-2 min-h-96" onSubmit={form.onSubmit((values) => console.log(values))} > <TextInput @@ -64,6 +64,8 @@ function LoginCard() { key={form.key("password")} {...form.getInputProps("password")} /> + <div className="flex-grow"></div> + <Divider /> <Flex direction={"column"} gap={"4px"}> <Button type="submit">Register</Button> From 945f4e1545dcd91461266ea8d566ece98859fbad Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Wed, 31 Jul 2024 02:26:12 +0800 Subject: [PATCH 17/63] feat: add table view into main dashboard --- frontend/src/components/ImageStreamViewer.tsx | 8 +- frontend/src/components/ReportTable.tsx | 29 +++ frontend/src/model/{dataset.js => dataset.ts} | 130 ++++++----- frontend/src/pages/CameraView.jsx | 39 +++- frontend/src/pages/laporan.jsx | 215 ++++++++---------- 5 files changed, 230 insertions(+), 191 deletions(-) create mode 100644 frontend/src/components/ReportTable.tsx rename frontend/src/model/{dataset.js => dataset.ts} (54%) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 43380a8..d5edea0 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -12,13 +12,15 @@ import { //todo: create error handling like result, error, and loading. export function ImageStreamViewer({ base64Data, - error + error, + w, }: { /** * Base64 image string, not include `data:image/jpeg;base64,` */ base64Data?: string; - error?: string + error?: string; + w?: number, }) { const [base64String, setBase64String] = useState<string | undefined>( undefined @@ -27,7 +29,7 @@ export function ImageStreamViewer({ setBase64String(base64Data ?? ""); }, [base64Data]); return ( - <div className="border-2 border-solid min-w-[640px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10"> + <div className={`border-2 border-solid min-w-[${w ?? 640}px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10`}> {base64String ? ( <img src={base64String} alt="CCTV Stream" /> ) : ( diff --git a/frontend/src/components/ReportTable.tsx b/frontend/src/components/ReportTable.tsx new file mode 100644 index 0000000..48a81eb --- /dev/null +++ b/frontend/src/components/ReportTable.tsx @@ -0,0 +1,29 @@ +import { DetectionData } from "@/model/dataset"; +import { Table } from "@mantine/core"; + +export function ReportTable({data}:{data: DetectionData[]}) { + return ( + <Table stickyHeader striped> + <Table.Thead> + <Table.Tr> + <Table.Th>ID</Table.Th> + <Table.Th>Status</Table.Th> + <Table.Th>Akurasi</Table.Th> + <Table.Th>Nama</Table.Th> + </Table.Tr> + </Table.Thead> + <Table.Tbody> + {data.map((val, idx) => { + return ( + <Table.Tr key={idx}> + <Table.Td>{val.id}</Table.Td> + <Table.Td>{val.status}</Table.Td> + <Table.Td>{val.detection}</Table.Td> + <Table.Td>{val.name}</Table.Td> + </Table.Tr> + ); + })} + </Table.Tbody> + </Table> + ); + } \ No newline at end of file diff --git a/frontend/src/model/dataset.js b/frontend/src/model/dataset.ts similarity index 54% rename from frontend/src/model/dataset.js rename to frontend/src/model/dataset.ts index d68c8f7..9b8da6e 100644 --- a/frontend/src/model/dataset.js +++ b/frontend/src/model/dataset.ts @@ -1,182 +1,190 @@ -export const DataCctv = [ + +export interface DetectionData { + id: number + status: string + detection: number + name: string + +} +export const sampleDataCCTV: DetectionData[] = [ { id: 1, status: "Mengerjakan", - persen: 0.9, - nama: "", + detection: 0.9, + name: "", }, { id: 2, status: "Mencontek", - persen: 0.6, - nama: "", + detection: 0.6, + name: "", }, { id: 3, status: "Melihat Teman", - persen: 0.7, - nama: "", + detection: 0.7, + name: "", }, { id: 4, status: "Mengerjakan", - persen: 0.85, - nama: "", + detection: 0.85, + name: "", }, { id: 5, status: "Melihat Teman", - persen: 0.75, - nama: "", + detection: 0.75, + name: "", }, { id: 6, status: "Mencontek", - persen: 0.5, - nama: "", + detection: 0.5, + name: "", }, { id: 7, status: "Mengerjakan", - persen: 0.82, - nama: "", + detection: 0.82, + name: "", }, { id: 8, status: "Melihat Teman", - persen: 0.65, - nama: "", + detection: 0.65, + name: "", }, { id: 9, status: "Mencontek", - persen: 0.55, - nama: "", + detection: 0.55, + name: "", }, { id: 10, status: "Mengerjakan", - persen: 0.88, - nama: "", + detection: 0.88, + name: "", }, { id: 11, status: "Melihat Teman", - persen: 0.6, - nama: "", + detection: 0.6, + name: "", }, { id: 12, status: "Mencontek", - persen: 0.45, - nama: "", + detection: 0.45, + name: "", }, { id: 13, status: "Mengerjakan", - persen: 0.9, - nama: "", + detection: 0.9, + name: "", }, { id: 14, status: "Melihat Teman", - persen: 0.7, - nama: "", + detection: 0.7, + name: "", }, { id: 15, status: "Mencontek", - persen: 0.4, - nama: "", + detection: 0.4, + name: "", }, { id: 16, status: "Mengerjakan", - persen: 0.8, - nama: "", + detection: 0.8, + name: "", }, { id: 17, status: "Melihat Teman", - persen: 0.62, - nama: "", + detection: 0.62, + name: "", }, { id: 18, status: "Mencontek", - persen: 0.38, - nama: "", + detection: 0.38, + name: "", }, { id: 19, status: "Mengerjakan", - persen: 0.84, - nama: "", + detection: 0.84, + name: "", }, { id: 20, status: "Melihat Teman", - persen: 0.68, - nama: "", + detection: 0.68, + name: "", }, { id: 21, status: "Mencontek", - persen: 0.52, - nama: "", + detection: 0.52, + name: "", }, { id: 22, status: "Mengerjakan", - persen: 0.86, - nama: "", + detection: 0.86, + name: "", }, { id: 23, status: "Melihat Teman", - persen: 0.78, - nama: "", + detection: 0.78, + name: "", }, { id: 24, status: "Mencontek", - persen: 0.48, - nama: "", + detection: 0.48, + name: "", }, { id: 25, status: "Mengerjakan", - persen: 0.89, - nama: "", + detection: 0.89, + name: "", }, { id: 26, status: "Melihat Teman", - persen: 0.64, - nama: "", + detection: 0.64, + name: "", }, { id: 27, status: "Mencontek", - persen: 0.58, - nama: "", + detection: 0.58, + name: "", }, { id: 28, status: "Mengerjakan", - persen: 0.91, - nama: "", + detection: 0.91, + name: "", }, { id: 29, status: "Melihat Teman", - persen: 0.66, - nama: "", + detection: 0.66, + name: "", }, { id: 30, status: "Mencontek", - persen: 0.42, - nama: "", + detection: 0.42, + name: "", }, ]; diff --git a/frontend/src/pages/CameraView.jsx b/frontend/src/pages/CameraView.jsx index 4353164..4f182b0 100644 --- a/frontend/src/pages/CameraView.jsx +++ b/frontend/src/pages/CameraView.jsx @@ -1,18 +1,45 @@ import { ImageStreamViewer } from "../components/ImageStreamViewer"; import { useStreamSocket } from "../context/CameraSocketContext"; -import { MdOutlinePlayArrow } from "react-icons/md"; -import { ActionIcon, Text, TextInput } from "@mantine/core"; +import { + MdCamera, + MdCameraAlt, + MdOutlinePlayArrow, + MdTableView, +} from "react-icons/md"; +import { ActionIcon, Table, Text, TextInput } from "@mantine/core"; +import { useState } from "react"; +import { ReportTable } from "@/components/ReportTable"; +import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { const stream = useStreamSocket(); + const [showTable, setShowTable] = useState(true); return ( <div className="flex flex-grow p-4"> <div className="flex flex-grow"> - <div className="flex-grow flex flex-col items-center"> - <h1 className="text-xl font-bold">Nama Camera</h1> - <h3>Tanggal: 19 Februari 2024</h3> - <ImageStreamViewer base64Data={stream.base64data} /> + <div className="flex-grow flex flex-col items-start"> + <span className="flex w-full justify-between pr-4"> + <span className="flex flex-col"> + <h1 className="text-xl font-bold">Nama Camera</h1> + <h3>Tanggal: 19 Februari 2024</h3> + </span> + <ActionIcon.Group> + <ActionIcon variant="default" size={"lg"} onClick={()=> setShowTable(false)} aria-label="Camera"> + <MdCamera /> + </ActionIcon> + <ActionIcon variant="default" size={"lg"} onClick={()=> setShowTable(true)} aria-label="Table View"> + <MdTableView /> + </ActionIcon> + </ActionIcon.Group> + </span> + {showTable ? ( + <Table.ScrollContainer className="border-[1px] aspect-video" w={640}> + <ReportTable data={sampleDataCCTV} /> + </Table.ScrollContainer> + ) : ( + <ImageStreamViewer base64Data={stream.base64data} /> + )} <br /> </div> <div className="border-l border-gray-300 min-h-full"></div> diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/laporan.jsx index 08b3e74..aedca37 100644 --- a/frontend/src/pages/laporan.jsx +++ b/frontend/src/pages/laporan.jsx @@ -1,8 +1,6 @@ -import { useState } from "react"; -import { DataCctv as initialDataCctv } from "../model/dataset"; -import { FaEdit } from "react-icons/fa"; -import Swal from "sweetalert2"; -import { Divider, ScrollArea, Table } from "@mantine/core"; +import { Divider, Table } from "@mantine/core"; +import { ReportTable } from "@/components/ReportTable"; +import { sampleDataCCTV } from "@/model/dataset"; export default function Laporan() { return ( @@ -13,126 +11,101 @@ export default function Laporan() { <Divider my={"md"} /> {/* Data Pengunjung */} <Table.ScrollContainer className="border-[1px]" h={600}> - <ReportTable /> + <ReportTable data={sampleDataCCTV}/> </Table.ScrollContainer> </div> ); } -function ReportTableBackup() { - const [dataCctv, setDataCctv] = useState(initialDataCctv); +// function ReportTableBackup() { +// const [dataCctv, setDataCctv] = useState(initialDataCctv); + +// const handleEdit = (index) => { +// Swal.fire({ +// title: "Edit Nama", +// input: "text", +// inputValue: dataCctv[index].nama, +// showCancelButton: true, +// confirmButtonText: "Simpan", +// preConfirm: (nama) => { +// if (!nama) { +// Swal.showValidationMessage("Nama tidak boleh kosong"); +// } +// return nama; +// }, +// }).then((result) => { +// if (result.isConfirmed) { +// const newDataCctv = [...dataCctv]; +// newDataCctv[index].nama = result.value; +// setDataCctv(newDataCctv); +// Swal.fire("Berhasil!", "Nama berhasil diubah.", "success"); +// } +// }); +// }; +// return ( +// <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> +// <div className="overflow-x-auto pr-5 lg:pr-0"> +// <table className="table-auto w-full"> +// <thead> +// <tr> +// <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> +// No +// </th> +// <th className="px-4 py-2 bg-slate-500 text-white">ID</th> +// <th className="px-4 py-2 bg-slate-500 text-white">Status</th> +// <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> +// <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> +// <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> +// Edit +// </th> +// </tr> +// </thead> +// <tbody> +// {dataCctv.map((entry, index) => ( +// <tr +// key={index} +// className={index % 2 === 0 ? "bg-primary-50" : "bg-primary-100"} +// > +// <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> +// {index + 1} +// </td> +// <td +// className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ +// entry.status === "Mencontek" ? "text-red-500" : "" +// }`} +// > +// {entry.id} +// </td> +// <td +// className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ +// entry.status === "Mencontek" ? "text-red-500" : "" +// }`} +// > +// {entry.status} +// </td> +// <td +// className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ +// entry.status === "Mencontek" ? "text-red-500" : "" +// }`} +// > +// {entry.persen} +// </td> +// <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> +// {entry.nama} +// </td> +// <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> +// <FaEdit +// className="cursor-pointer" +// onClick={() => handleEdit(index)} +// /> +// </td> +// </tr> +// ))} +// </tbody> +// </table> +// </div> +// </div> +// ); +// } - const handleEdit = (index) => { - Swal.fire({ - title: "Edit Nama", - input: "text", - inputValue: dataCctv[index].nama, - showCancelButton: true, - confirmButtonText: "Simpan", - preConfirm: (nama) => { - if (!nama) { - Swal.showValidationMessage("Nama tidak boleh kosong"); - } - return nama; - }, - }).then((result) => { - if (result.isConfirmed) { - const newDataCctv = [...dataCctv]; - newDataCctv[index].nama = result.value; - setDataCctv(newDataCctv); - Swal.fire("Berhasil!", "Nama berhasil diubah.", "success"); - } - }); - }; - return ( - <div className="container mx-auto pb-10 lg:pl-3 pl-5 mt-5"> - <div className="overflow-x-auto pr-5 lg:pr-0"> - <table className="table-auto w-full"> - <thead> - <tr> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tl-lg"> - No - </th> - <th className="px-4 py-2 bg-slate-500 text-white">ID</th> - <th className="px-4 py-2 bg-slate-500 text-white">Status</th> - <th className="px-4 py-2 bg-slate-500 text-white">Persen</th> - <th className="px-4 py-2 bg-slate-500 text-white">Nama</th> - <th className="px-4 py-2 bg-slate-500 text-white rounded-tr-lg"> - Edit - </th> - </tr> - </thead> - <tbody> - {dataCctv.map((entry, index) => ( - <tr - key={index} - className={index % 2 === 0 ? "bg-primary-50" : "bg-primary-100"} - > - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {index + 1} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.id} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.status} - </td> - <td - className={`border border-bg-slate-500 px-4 py-2 text-center font-semibold ${ - entry.status === "Mencontek" ? "text-red-500" : "" - }`} - > - {entry.persen} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - {entry.nama} - </td> - <td className="border border-bg-slate-500 px-4 py-2 text-center font-semibold"> - <FaEdit - className="cursor-pointer" - onClick={() => handleEdit(index)} - /> - </td> - </tr> - ))} - </tbody> - </table> - </div> - </div> - ); -} -function ReportTable() { - return ( - <Table stickyHeader striped> - <Table.Thead> - <Table.Tr> - <Table.Th>ID</Table.Th> - <Table.Th>Status</Table.Th> - <Table.Th>Akurasi</Table.Th> - <Table.Th>Nama</Table.Th> - </Table.Tr> - </Table.Thead> - <Table.Tbody> - {initialDataCctv.map((val, idx) => { - return ( - <Table.Tr key={idx}> - <Table.Td>{val.id}</Table.Td> - <Table.Td>{val.status}</Table.Td> - <Table.Td>{val.persen}</Table.Td> - <Table.Td>{val.nama}</Table.Td> - </Table.Tr> - ); - })} - </Table.Tbody> - </Table> - ); -} From 0764dc21d00f3f6110feb97f22c41f3d36e03843 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Wed, 31 Jul 2024 02:30:50 +0800 Subject: [PATCH 18/63] feat: refactor page name, fix bug --- frontend/src/App.tsx | 4 ++-- frontend/src/components/ImageStreamViewer.tsx | 12 +++++++----- .../src/pages/{CameraView.jsx => app/livecam.jsx} | 9 ++++----- frontend/src/pages/{laporan.jsx => app/report.jsx} | 0 4 files changed, 13 insertions(+), 12 deletions(-) rename frontend/src/pages/{CameraView.jsx => app/livecam.jsx} (88%) rename frontend/src/pages/{laporan.jsx => app/report.jsx} (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 13116fc..7f1ed35 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,8 +3,8 @@ import MainLayout from "@/layout/MainLayout"; import { default as CameraView, default as WebSocketDemo, -} from "@/pages/CameraView"; -import Laporan from "@/pages/laporan"; +} from "@/pages/app/livecam"; +import Laporan from "@/pages/app/report"; import { LoginPage } from "@/pages/login"; import { RegisterPage } from "./pages/register"; diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index d5edea0..bd2c2cb 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,7 +1,5 @@ import { useEffect, useState } from "react"; -import { - MdOutlineWarningAmber, -} from "react-icons/md"; +import { MdOutlineWarningAmber } from "react-icons/md"; // const getBlobUrl = (data: string | null) => { // if (data) { @@ -20,16 +18,20 @@ export function ImageStreamViewer({ */ base64Data?: string; error?: string; - w?: number, + w?: number; }) { const [base64String, setBase64String] = useState<string | undefined>( undefined ); + useEffect(() => { setBase64String(base64Data ?? ""); }, [base64Data]); return ( - <div className={`border-2 border-solid min-w-[${w ?? 640}px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10`}> + <div + style={{ width: w ?? 600 }} + className={`border-2 border-solid min-w-[400px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10`} + > {base64String ? ( <img src={base64String} alt="CCTV Stream" /> ) : ( diff --git a/frontend/src/pages/CameraView.jsx b/frontend/src/pages/app/livecam.jsx similarity index 88% rename from frontend/src/pages/CameraView.jsx rename to frontend/src/pages/app/livecam.jsx index 4f182b0..f1890a3 100644 --- a/frontend/src/pages/CameraView.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -1,8 +1,7 @@ -import { ImageStreamViewer } from "../components/ImageStreamViewer"; -import { useStreamSocket } from "../context/CameraSocketContext"; +import { ImageStreamViewer } from "../../components/ImageStreamViewer"; +import { useStreamSocket } from "../../context/CameraSocketContext"; import { MdCamera, - MdCameraAlt, MdOutlinePlayArrow, MdTableView, } from "react-icons/md"; @@ -13,7 +12,7 @@ import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { const stream = useStreamSocket(); - const [showTable, setShowTable] = useState(true); + const [showTable, setShowTable] = useState(false); return ( <div className="flex flex-grow p-4"> @@ -38,7 +37,7 @@ const CameraView = () => { <ReportTable data={sampleDataCCTV} /> </Table.ScrollContainer> ) : ( - <ImageStreamViewer base64Data={stream.base64data} /> + <ImageStreamViewer base64Data={stream.base64data} w={640} /> )} <br /> </div> diff --git a/frontend/src/pages/laporan.jsx b/frontend/src/pages/app/report.jsx similarity index 100% rename from frontend/src/pages/laporan.jsx rename to frontend/src/pages/app/report.jsx From 52646cda8c19b401d6b472d1efc5c1af8c374974 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Wed, 31 Jul 2024 03:37:35 +0800 Subject: [PATCH 19/63] feat: decouple into separated component --- frontend/src/components/ImageStreamViewer.tsx | 10 +-- frontend/src/components/ReportTable.tsx | 52 ++++++------ frontend/src/pages/app/livecam.jsx | 80 +++++++++++-------- 3 files changed, 79 insertions(+), 63 deletions(-) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index bd2c2cb..12c3635 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -11,17 +11,17 @@ import { MdOutlineWarningAmber } from "react-icons/md"; export function ImageStreamViewer({ base64Data, error, - w, + minW, }: { /** * Base64 image string, not include `data:image/jpeg;base64,` */ base64Data?: string; error?: string; - w?: number; + minW?: number; }) { const [base64String, setBase64String] = useState<string | undefined>( - undefined + undefined, ); useEffect(() => { @@ -29,8 +29,8 @@ export function ImageStreamViewer({ }, [base64Data]); return ( <div - style={{ width: w ?? 600 }} - className={`border-2 border-solid min-w-[400px] aspect-video rounded-lg flex justify-center items-center mt-5 px-10`} + style={{ minWidth: minW ?? 600, minHeight: ((minW ?? 600) * 9) / 16 }} + className={`border-[1px] resize overflow-auto border-solid aspect-video rounded-sm flex justify-center items-center`} > {base64String ? ( <img src={base64String} alt="CCTV Stream" /> diff --git a/frontend/src/components/ReportTable.tsx b/frontend/src/components/ReportTable.tsx index 48a81eb..06ab83f 100644 --- a/frontend/src/components/ReportTable.tsx +++ b/frontend/src/components/ReportTable.tsx @@ -1,29 +1,29 @@ import { DetectionData } from "@/model/dataset"; import { Table } from "@mantine/core"; -export function ReportTable({data}:{data: DetectionData[]}) { - return ( - <Table stickyHeader striped> - <Table.Thead> - <Table.Tr> - <Table.Th>ID</Table.Th> - <Table.Th>Status</Table.Th> - <Table.Th>Akurasi</Table.Th> - <Table.Th>Nama</Table.Th> - </Table.Tr> - </Table.Thead> - <Table.Tbody> - {data.map((val, idx) => { - return ( - <Table.Tr key={idx}> - <Table.Td>{val.id}</Table.Td> - <Table.Td>{val.status}</Table.Td> - <Table.Td>{val.detection}</Table.Td> - <Table.Td>{val.name}</Table.Td> - </Table.Tr> - ); - })} - </Table.Tbody> - </Table> - ); - } \ No newline at end of file +export function ReportTable({ data }: { data: DetectionData[] }) { + return ( + <Table stickyHeader striped> + <Table.Thead> + <Table.Tr> + <Table.Th>ID</Table.Th> + <Table.Th>Status</Table.Th> + <Table.Th>Akurasi</Table.Th> + <Table.Th>Nama</Table.Th> + </Table.Tr> + </Table.Thead> + <Table.Tbody> + {data.map((val, idx) => { + return ( + <Table.Tr key={idx}> + <Table.Td>{val.id}</Table.Td> + <Table.Td>{val.status}</Table.Td> + <Table.Td>{val.detection}</Table.Td> + <Table.Td>{val.name}</Table.Td> + </Table.Tr> + ); + })} + </Table.Tbody> + </Table> + ); +} diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/livecam.jsx index f1890a3..43afadf 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -1,45 +1,17 @@ -import { ImageStreamViewer } from "../../components/ImageStreamViewer"; -import { useStreamSocket } from "../../context/CameraSocketContext"; -import { - MdCamera, - MdOutlinePlayArrow, - MdTableView, -} from "react-icons/md"; +import { ImageStreamViewer } from "@/components/ImageStreamViewer"; +import { useStreamSocket } from "@/context/CameraSocketContext"; +import { MdCamera, MdOutlinePlayArrow, MdTableView } from "react-icons/md"; import { ActionIcon, Table, Text, TextInput } from "@mantine/core"; import { useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { - const stream = useStreamSocket(); - const [showTable, setShowTable] = useState(false); - return ( <div className="flex flex-grow p-4"> <div className="flex flex-grow"> <div className="flex-grow flex flex-col items-start"> - <span className="flex w-full justify-between pr-4"> - <span className="flex flex-col"> - <h1 className="text-xl font-bold">Nama Camera</h1> - <h3>Tanggal: 19 Februari 2024</h3> - </span> - <ActionIcon.Group> - <ActionIcon variant="default" size={"lg"} onClick={()=> setShowTable(false)} aria-label="Camera"> - <MdCamera /> - </ActionIcon> - <ActionIcon variant="default" size={"lg"} onClick={()=> setShowTable(true)} aria-label="Table View"> - <MdTableView /> - </ActionIcon> - </ActionIcon.Group> - </span> - {showTable ? ( - <Table.ScrollContainer className="border-[1px] aspect-video" w={640}> - <ReportTable data={sampleDataCCTV} /> - </Table.ScrollContainer> - ) : ( - <ImageStreamViewer base64Data={stream.base64data} w={640} /> - )} - <br /> + <MainView /> </div> <div className="border-l border-gray-300 min-h-full"></div> <Inspector /> @@ -48,6 +20,50 @@ const CameraView = () => { ); }; +const MainView = () => { + const stream = useStreamSocket(); + const [showTable, setShowTable] = useState(false); + return ( + <> + <span className="flex w-full justify-between pr-4"> + <span className="flex flex-col"> + <h1 className="text-xl font-bold">Nama Camera</h1> + <h3>Tanggal: 19 Februari 2024</h3> + </span> + <ActionIcon.Group> + <ActionIcon + variant={showTable ? "default" : "filled"} + size={"lg"} + onClick={() => setShowTable(false)} + aria-label="Camera" + > + <MdCamera /> + </ActionIcon> + <ActionIcon + variant={showTable ? "filled" : "default"} + size={"lg"} + onClick={() => setShowTable(true)} + aria-label="Table View" + > + <MdTableView /> + </ActionIcon> + </ActionIcon.Group> + </span> + {showTable ? ( + <Table.ScrollContainer + className="border-[1px] hover:resize aspect-video" + minWidth={720} + > + <ReportTable data={sampleDataCCTV} /> + </Table.ScrollContainer> + ) : ( + <ImageStreamViewer base64Data={stream.base64data} minW={720} /> + )} + <br /> + </> + ); +}; + const Inspector = () => { return ( <div className="flex flex-col p-2 gap-2"> From 423c9ae0d52eca06c17713f6a8f077252b127900 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Wed, 31 Jul 2024 11:38:17 +0800 Subject: [PATCH 20/63] feat: add dark mode, improve UI --- frontend/src/components/ImageStreamViewer.tsx | 10 ++-- frontend/src/components/titleDashboard.tsx | 48 ++++++++++++----- frontend/src/main.tsx | 2 +- frontend/src/pages/app/livecam.jsx | 51 ++++++++++--------- 4 files changed, 69 insertions(+), 42 deletions(-) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 12c3635..ccbe4bf 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,3 +1,4 @@ +import { Box } from "@mantine/core"; import { useEffect, useState } from "react"; import { MdOutlineWarningAmber } from "react-icons/md"; @@ -21,16 +22,17 @@ export function ImageStreamViewer({ minW?: number; }) { const [base64String, setBase64String] = useState<string | undefined>( - undefined, + undefined ); useEffect(() => { setBase64String(base64Data ?? ""); }, [base64Data]); return ( - <div + <Box + bd={"1px solid myColor.5"} style={{ minWidth: minW ?? 600, minHeight: ((minW ?? 600) * 9) / 16 }} - className={`border-[1px] resize overflow-auto border-solid aspect-video rounded-sm flex justify-center items-center`} + className={`resize overflow-auto aspect-video rounded-sm flex justify-center items-center`} > {base64String ? ( <img src={base64String} alt="CCTV Stream" /> @@ -40,6 +42,6 @@ export function ImageStreamViewer({ <MdOutlineWarningAmber /> {error ?? "Masalah tidak diketahui"} </p> )} - </div> + </Box> ); } diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx index f10ed07..0661030 100644 --- a/frontend/src/components/titleDashboard.tsx +++ b/frontend/src/components/titleDashboard.tsx @@ -1,25 +1,47 @@ -import { Box, Group, Text } from "@mantine/core"; +import { + ActionIcon, + ActionIconGroup, + Box, + Flex, + Group, + Stack, + Text, + useMantineColorScheme, +} from "@mantine/core"; +import { MdDarkMode, MdLightMode } from "react-icons/md"; export default function TitleDashboard({ leftSection, }: { leftSection: React.ReactNode; }) { + + const {colorScheme, setColorScheme } = useMantineColorScheme({keepTransitions:true}) return ( <> - <Group h={"100%"} px={"md"}> + <Flex h={"100%"} align={"center"} px={"md"} pr={48}> {leftSection} - <div className=" items-end"> - {/* dont forget add `/` like `/logo.png` to avoid missing asset */} - <img src="/logo.png" className="w-24" /> - </div> - <Box className="text-[#66ABB1] overflow-clip"> - <h1 className=" text-2xl font-extrabold">BYE-CHEATING</h1> - <Text visibleFrom="md" fw={"normal"} size="xs"> - Jaminan Integritas, Langkah Cerdas Mengawal Keadilan Akademik! - </Text> - </Box> - </Group> + <Flex align={"center"} className="flex-grow"> + <div className=" items-end"> + {/* dont forget add `/` like `/logo.png` to avoid missing asset */} + <img src="/logo.png" className="w-24" /> + </div> + <Stack gap={"xs"} className="text-[#66ABB1] overflow-clip"> + <h1 className=" text-2xl font-extrabold">BYE-CHEATING</h1> + <Text visibleFrom="md" fw={"normal"} size="xs"> + Jaminan Integritas, Langkah Cerdas Mengawal Keadilan Akademik! + </Text> + </Stack> + </Flex> + <ActionIconGroup> + <ActionIcon variant={colorScheme ==="light" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("light")}> + <MdLightMode /> + </ActionIcon> + <ActionIcon variant={colorScheme ==="dark" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("dark")}> + <MdDarkMode /> + </ActionIcon> + </ActionIconGroup> + </Flex> </> ); } diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 5edf3f7..10e4c16 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -40,7 +40,7 @@ const theme = createTheme({ ReactDOM.createRoot(container).render( <React.StrictMode> - <MantineProvider theme={theme}> + <MantineProvider theme={theme} defaultColorScheme="light"> <BrowserRouter> <AuthProvider> <StreamSocketProvider> diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/livecam.jsx index 43afadf..e380087 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -1,7 +1,7 @@ import { ImageStreamViewer } from "@/components/ImageStreamViewer"; import { useStreamSocket } from "@/context/CameraSocketContext"; import { MdCamera, MdOutlinePlayArrow, MdTableView } from "react-icons/md"; -import { ActionIcon, Table, Text, TextInput } from "@mantine/core"; +import { ActionIcon, Table, Text, TextInput, Tooltip } from "@mantine/core"; import { useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/model/dataset"; @@ -9,13 +9,11 @@ import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { return ( <div className="flex flex-grow p-4"> - <div className="flex flex-grow"> - <div className="flex-grow flex flex-col items-start"> - <MainView /> - </div> - <div className="border-l border-gray-300 min-h-full"></div> - <Inspector /> + <div className="flex-grow flex flex-col items-center"> + <MainView /> </div> + <div className="border-l border-gray-300 dark:border-gray-300/30 min-h-full"></div> + <Inspector /> </div> ); }; @@ -31,27 +29,32 @@ const MainView = () => { <h3>Tanggal: 19 Februari 2024</h3> </span> <ActionIcon.Group> - <ActionIcon - variant={showTable ? "default" : "filled"} - size={"lg"} - onClick={() => setShowTable(false)} - aria-label="Camera" - > - <MdCamera /> - </ActionIcon> - <ActionIcon - variant={showTable ? "filled" : "default"} - size={"lg"} - onClick={() => setShowTable(true)} - aria-label="Table View" - > - <MdTableView /> - </ActionIcon> + <Tooltip label="Tampilan Kamera"> + <ActionIcon + variant={showTable ? "default" : "filled"} + size={"lg"} + onClick={() => setShowTable(false)} + aria-label="Camera" + > + <MdCamera /> + </ActionIcon> + </Tooltip> + <Tooltip label="Tampilan Tabel"> + <ActionIcon + variant={showTable ? "filled" : "default"} + size={"lg"} + onClick={() => setShowTable(true)} + aria-label="Table View" + > + <MdTableView /> + </ActionIcon> + </Tooltip> </ActionIcon.Group> </span> {showTable ? ( <Table.ScrollContainer - className="border-[1px] hover:resize aspect-video" + bd={"1px solid myColor.5"} + className="hover:resize aspect-video" minWidth={720} > <ReportTable data={sampleDataCCTV} /> From 74fb7d02da7a8a0ced63d0c2338978cc9580d8d2 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 2 Aug 2024 07:34:09 +0800 Subject: [PATCH 21/63] feat: reducte tailwind usage --- frontend/src/components/ImageStreamViewer.tsx | 5 +- frontend/src/pages/app/livecam.jsx | 98 +++++++------------ 2 files changed, 39 insertions(+), 64 deletions(-) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index ccbe4bf..b5789a6 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -12,14 +12,12 @@ import { MdOutlineWarningAmber } from "react-icons/md"; export function ImageStreamViewer({ base64Data, error, - minW, }: { /** * Base64 image string, not include `data:image/jpeg;base64,` */ base64Data?: string; error?: string; - minW?: number; }) { const [base64String, setBase64String] = useState<string | undefined>( undefined @@ -31,8 +29,7 @@ export function ImageStreamViewer({ return ( <Box bd={"1px solid myColor.5"} - style={{ minWidth: minW ?? 600, minHeight: ((minW ?? 600) * 9) / 16 }} - className={`resize overflow-auto aspect-video rounded-sm flex justify-center items-center`} + className={`overflow-auto aspect-video w-full rounded-sm flex justify-center items-center`} > {base64String ? ( <img src={base64String} alt="CCTV Stream" /> diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/livecam.jsx index e380087..7b03564 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -1,20 +1,22 @@ import { ImageStreamViewer } from "@/components/ImageStreamViewer"; import { useStreamSocket } from "@/context/CameraSocketContext"; -import { MdCamera, MdOutlinePlayArrow, MdTableView } from "react-icons/md"; -import { ActionIcon, Table, Text, TextInput, Tooltip } from "@mantine/core"; +import { MdCamera, MdTableView } from "react-icons/md"; +import { + ActionIcon, + Card, + Flex, + Table, + Tooltip, +} from "@mantine/core"; import { useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { return ( - <div className="flex flex-grow p-4"> - <div className="flex-grow flex flex-col items-center"> + <Flex direction={"column"} gap={"xs"} p={"xs"}> <MainView /> - </div> - <div className="border-l border-gray-300 dark:border-gray-300/30 min-h-full"></div> - <Inspector /> - </div> + </Flex> ); }; @@ -23,73 +25,49 @@ const MainView = () => { const [showTable, setShowTable] = useState(false); return ( <> - <span className="flex w-full justify-between pr-4"> + <Flex justify={"space-between"} w={"100%"}> <span className="flex flex-col"> <h1 className="text-xl font-bold">Nama Camera</h1> <h3>Tanggal: 19 Februari 2024</h3> </span> - <ActionIcon.Group> - <Tooltip label="Tampilan Kamera"> - <ActionIcon - variant={showTable ? "default" : "filled"} - size={"lg"} - onClick={() => setShowTable(false)} - aria-label="Camera" - > - <MdCamera /> - </ActionIcon> - </Tooltip> - <Tooltip label="Tampilan Tabel"> - <ActionIcon - variant={showTable ? "filled" : "default"} - size={"lg"} - onClick={() => setShowTable(true)} - aria-label="Table View" - > - <MdTableView /> - </ActionIcon> - </Tooltip> - </ActionIcon.Group> - </span> + <Card p={"xs"}> + <ActionIcon.Group> + <Tooltip label="Tampilan Kamera"> + <ActionIcon + variant={showTable ? "default" : "filled"} + size={"lg"} + onClick={() => setShowTable(false)} + aria-label="Camera" + > + <MdCamera /> + </ActionIcon> + </Tooltip> + <Tooltip label="Tampilan Tabel"> + <ActionIcon + variant={showTable ? "filled" : "default"} + size={"lg"} + onClick={() => setShowTable(true)} + aria-label="Table View" + > + <MdTableView /> + </ActionIcon> + </Tooltip> + </ActionIcon.Group> + </Card> + </Flex> {showTable ? ( <Table.ScrollContainer bd={"1px solid myColor.5"} - className="hover:resize aspect-video" - minWidth={720} + className="w-full aspect-video" > <ReportTable data={sampleDataCCTV} /> </Table.ScrollContainer> ) : ( - <ImageStreamViewer base64Data={stream.base64data} minW={720} /> + <ImageStreamViewer base64Data={stream.base64data} minW={500} /> )} <br /> </> ); }; -const Inspector = () => { - return ( - <div className="flex flex-col p-2 gap-2"> - <Text size="xl">Pengaturan</Text> - <RtspInput /> - </div> - ); -}; - -const RtspInput = () => { - return ( - <> - <TextInput - label="RTSP URL" - placeholder="rtsp://urlname" - rightSection={ - <ActionIcon variant="filled"> - <MdOutlinePlayArrow /> - </ActionIcon> - } - /> - </> - ); -}; - export default CameraView; From c667928b0238d9e317f79c1bcb2df93520e04e46 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 2 Aug 2024 07:35:42 +0800 Subject: [PATCH 22/63] feat: move inspector to main layout --- frontend/src/layout/MainLayout.tsx | 65 +++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 25dd9ce..a73c42f 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,15 +1,24 @@ import { Outlet } from "react-router-dom"; import NavbarContent from "../components/sidebar"; import { + Accordion, + ActionIcon, AppShell, Burger, + Card, + Divider, + Flex, Group, + Text, + TextInput, } from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; +import { MdMoreVert, MdOutlinePlayArrow } from "react-icons/md"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); + const [asideMobileOpened] = useDisclosure(); return ( <AppShell navbar={{ @@ -19,8 +28,12 @@ export default function MainLayout() { collapsed: { mobile: !mobileOpened }, }} header={{ height: 100 }} - layout="alt" - + aside={{ + width: 300, + breakpoint: "sm", + collapsed: { mobile: !asideMobileOpened }, + }} + // layout="alt" > <AppShell.Navbar> <NavbarContent /> @@ -39,6 +52,9 @@ export default function MainLayout() { } /> </AppShell.Header> + <AppShell.Aside> + <Inspector /> + </AppShell.Aside> <AppShell.Main className="flex flex-col"> <Outlet /> </AppShell.Main> @@ -50,3 +66,48 @@ export default function MainLayout() { </AppShell> ); } + +const Inspector = () => { + return ( + <div className="flex flex-col p-2 gap-2 w-full"> + <Accordion variant="contained"> + <Accordion.Item value="metadata"> + <Accordion.Control>Metadata</Accordion.Control> + <Accordion.Panel> + <Text size="sm">URL: rtsp://localhost:8888/a301.live</Text> + <RtspInput /> + </Accordion.Panel> + </Accordion.Item> + </Accordion> + <Divider /> + + <Text fw={600}>Daftar Kamera</Text> + <Flex direction={"column"}> + <Card padding={"xs"} radius={"sm"} withBorder> + <Group justify="space-between"> + <Text fw={400} size="sm">Camera 2</Text> + <ActionIcon variant="subtle"> + <MdMoreVert /> + </ActionIcon> + </Group> + </Card> + </Flex> + </div> + ); +}; + +const RtspInput = () => { + return ( + <> + <TextInput + label="RTSP URL" + placeholder="rtsp://urlname" + rightSection={ + <ActionIcon variant="filled"> + <MdOutlinePlayArrow /> + </ActionIcon> + } + /> + </> + ); +}; From cbf8b89381e5297c1b322b0dea06dbb2ba546476 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 2 Aug 2024 07:36:07 +0800 Subject: [PATCH 23/63] feat: add minimal layout for login and register page --- frontend/src/layout/MinimalLayout.tsx | 15 +++++++++++++++ frontend/src/pages/login.tsx | 9 ++++++--- frontend/src/pages/register.tsx | 9 ++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 frontend/src/layout/MinimalLayout.tsx diff --git a/frontend/src/layout/MinimalLayout.tsx b/frontend/src/layout/MinimalLayout.tsx new file mode 100644 index 0000000..13ca3fe --- /dev/null +++ b/frontend/src/layout/MinimalLayout.tsx @@ -0,0 +1,15 @@ +import TitleDashboard from "@/components/titleDashboard"; +import { AppShell } from "@mantine/core"; +import React from "react"; +import { Outlet } from "react-router-dom"; + +export function MinimalLayout({ children }: { children?: React.ReactNode }) { + return ( + <AppShell> + <AppShell.Header> + <TitleDashboard leftSection={<></>} /> + </AppShell.Header> + <AppShell.Main>{children ?? <Outlet />}</AppShell.Main> + </AppShell> + ); +} diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 34c0fe1..aeb91e9 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,3 +1,4 @@ +import { MinimalLayout } from "@/layout/MinimalLayout"; import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; import { useDisclosure } from "@mantine/hooks"; @@ -5,9 +6,11 @@ import { useNavigate } from "react-router-dom"; export function LoginPage() { return ( - <div className="w-full h-svh flex flex-col items-center justify-center"> - <LoginCard /> - </div> + <MinimalLayout> + <div className="w-full h-svh flex flex-col items-center justify-center"> + <LoginCard /> + </div> + </MinimalLayout> ); } diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx index 936f5cb..4d71cea 100644 --- a/frontend/src/pages/register.tsx +++ b/frontend/src/pages/register.tsx @@ -1,3 +1,4 @@ +import { MinimalLayout } from "@/layout/MinimalLayout"; import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; import { useForm } from "@mantine/form"; import { useDisclosure } from "@mantine/hooks"; @@ -5,9 +6,11 @@ import { useNavigate } from "react-router-dom"; export function RegisterPage() { return ( - <div className="w-full h-svh flex flex-col items-center justify-center"> - <LoginCard /> - </div> + <MinimalLayout> + <div className="w-full h-svh flex flex-col items-center justify-center"> + <LoginCard /> + </div> + </MinimalLayout> ); } From 5a4b010a9e7409b81ae91db44d5145bbd0c18377 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 2 Aug 2024 07:53:43 +0800 Subject: [PATCH 24/63] feat: replace react icon with tabler icon to reduce npm size --- frontend/package-lock.json | 57 +++++++++---------- frontend/package.json | 6 +- frontend/src/components/ImageStreamViewer.tsx | 4 +- frontend/src/components/sidebar.tsx | 10 ++-- frontend/src/components/titleDashboard.tsx | 8 +-- frontend/src/layout/MainLayout.tsx | 6 +- frontend/src/pages/app/livecam.jsx | 6 +- 7 files changed, 43 insertions(+), 54 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75f49c3..e92eecc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,16 +13,14 @@ "@mantine/core": "^7.11.2", "@mantine/form": "^7.11.2", "@mantine/hooks": "^7.11.2", + "@tabler/icons-react": "^3.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^5.2.1", "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", "socket.io": "^4.7.5", - "socket.io-client": "^4.7.5", - "sweetalert2": "^11.11.0", - "sweetalert2-react-content": "^5.0.7" + "socket.io-client": "^4.7.5" }, "devDependencies": { "@eslint/compat": "^1.1.1", @@ -1372,6 +1370,30 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "node_modules/@tabler/icons": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-3.11.0.tgz", + "integrity": "sha512-/vZinJNvCYhdAB+RUsyCpanSPuOEKHHIZi4Uu0Bw7ilewHnQhCWUPrT704uHCRli2ROl7spADPmWzAqOganA5A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + } + }, + "node_modules/@tabler/icons-react": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-3.11.0.tgz", + "integrity": "sha512-xHNBi9mns1slvqos+7LkP3ube4CjWrANMbxMaorzwzO9J/+y1sAEG/sN8CV8FmtpYW/9/gDR+OWCjjLLg0RmAw==", + "dependencies": { + "@tabler/icons": "3.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/codecalm" + }, + "peerDependencies": { + "react": ">= 16" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -4847,14 +4869,6 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, - "node_modules/react-icons": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz", - "integrity": "sha512-zdbW5GstTzXaVKvGSyTaBalt7HSfuK5ovrzlpyiWHAFXndXTdd/1hdDHI4xBM1Mn7YriT6aqESucFl9kEXzrdw==", - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5694,25 +5708,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/sweetalert2": { - "version": "11.12.1", - "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.12.1.tgz", - "integrity": "sha512-xV3/YI7Ah6BeP+bXKcrHy1yn6duh8eqlX2TSI9I/rTIzGLYQvnnTa3mOIo5RHUobAjSmacC2IhPApxjvppZaEQ==", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/limonte" - } - }, - "node_modules/sweetalert2-react-content": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/sweetalert2-react-content/-/sweetalert2-react-content-5.0.7.tgz", - "integrity": "sha512-8Fk82Mpk45lFXpJWKIFF/lq8k/dJKDDQGFcuqVosaL/qRdViyAs5+u37LoTGfnOIvf+rfQB3PAXcp1XLLn+0ew==", - "peerDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0", - "sweetalert2": "^11.0.0" - } - }, "node_modules/tabbable": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7d71108..fcc6d28 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,16 +15,14 @@ "@mantine/core": "^7.11.2", "@mantine/form": "^7.11.2", "@mantine/hooks": "^7.11.2", + "@tabler/icons-react": "^3.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-icons": "^5.2.1", "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", "socket.io": "^4.7.5", - "socket.io-client": "^4.7.5", - "sweetalert2": "^11.11.0", - "sweetalert2-react-content": "^5.0.7" + "socket.io-client": "^4.7.5" }, "devDependencies": { "@eslint/compat": "^1.1.1", diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index b5789a6..eb0a5ef 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,6 +1,6 @@ import { Box } from "@mantine/core"; import { useEffect, useState } from "react"; -import { MdOutlineWarningAmber } from "react-icons/md"; +import {IconAlertCircle} from "@tabler/icons-react" // const getBlobUrl = (data: string | null) => { // if (data) { @@ -36,7 +36,7 @@ export function ImageStreamViewer({ ) : ( <p className="flex gap-2 justify-center items-center"> {" "} - <MdOutlineWarningAmber /> {error ?? "Masalah tidak diketahui"} + <IconAlertCircle /> {error ?? "Masalah tidak diketahui"} </p> )} </Box> diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar.tsx index 1b27a2f..7e32ce2 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar.tsx @@ -1,16 +1,14 @@ import { useLocation, useNavigate } from "react-router-dom"; -import { MdMonitor } from "react-icons/md"; -import { HiClipboardDocumentList } from "react-icons/hi2"; import { NavLink, Text } from "@mantine/core"; import { useState } from "react"; -import { HiLogout } from "react-icons/hi"; +import {IconDeviceDesktop, IconBook2,IconLogout} from "@tabler/icons-react" const menuLists = [ - { title: "Tampilan Langsung", path: "/app", icon: <MdMonitor /> }, + { title: "Tampilan Langsung", path: "/app", icon: <IconDeviceDesktop size={20} /> }, { title: "Laporan", path: "/app/laporan", - icon: <HiClipboardDocumentList />, + icon: <IconBook2 size={20} />, }, ]; @@ -60,7 +58,7 @@ export default function NavbarContent() { component="button" label={"Log out"} color="red" - leftSection={<HiLogout />} + leftSection={<IconLogout size={20}/>} onClick={() => { console.log("logout"); }} diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx index 0661030..42ee822 100644 --- a/frontend/src/components/titleDashboard.tsx +++ b/frontend/src/components/titleDashboard.tsx @@ -1,14 +1,12 @@ import { ActionIcon, ActionIconGroup, - Box, Flex, - Group, Stack, Text, useMantineColorScheme, } from "@mantine/core"; -import { MdDarkMode, MdLightMode } from "react-icons/md"; +import {IconMoon, IconSun} from "@tabler/icons-react" export default function TitleDashboard({ leftSection, @@ -35,10 +33,10 @@ export default function TitleDashboard({ </Flex> <ActionIconGroup> <ActionIcon variant={colorScheme ==="light" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("light")}> - <MdLightMode /> + <IconSun size={20} /> </ActionIcon> <ActionIcon variant={colorScheme ==="dark" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("dark")}> - <MdDarkMode /> + <IconMoon size={20} /> </ActionIcon> </ActionIconGroup> </Flex> diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index a73c42f..b7bc1f3 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -14,7 +14,7 @@ import { } from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; -import { MdMoreVert, MdOutlinePlayArrow } from "react-icons/md"; +import {IconDotsVertical, IconPlayerPlay} from "@tabler/icons-react" export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); @@ -87,7 +87,7 @@ const Inspector = () => { <Group justify="space-between"> <Text fw={400} size="sm">Camera 2</Text> <ActionIcon variant="subtle"> - <MdMoreVert /> + <IconDotsVertical /> </ActionIcon> </Group> </Card> @@ -104,7 +104,7 @@ const RtspInput = () => { placeholder="rtsp://urlname" rightSection={ <ActionIcon variant="filled"> - <MdOutlinePlayArrow /> + <IconPlayerPlay size={16} /> </ActionIcon> } /> diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/livecam.jsx index 7b03564..3f7eb3f 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -1,6 +1,6 @@ import { ImageStreamViewer } from "@/components/ImageStreamViewer"; import { useStreamSocket } from "@/context/CameraSocketContext"; -import { MdCamera, MdTableView } from "react-icons/md"; +import {IconCamera, IconTable} from "@tabler/icons-react" import { ActionIcon, Card, @@ -39,7 +39,7 @@ const MainView = () => { onClick={() => setShowTable(false)} aria-label="Camera" > - <MdCamera /> + <IconCamera size={20} /> </ActionIcon> </Tooltip> <Tooltip label="Tampilan Tabel"> @@ -49,7 +49,7 @@ const MainView = () => { onClick={() => setShowTable(true)} aria-label="Table View" > - <MdTableView /> + <IconTable size={20} /> </ActionIcon> </Tooltip> </ActionIcon.Group> From 6e4f37b8efb5291881999f5c6a06221b43bf9cbf Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Mon, 5 Aug 2024 07:24:36 +0800 Subject: [PATCH 25/63] feat: add notification and motals manager --- frontend/package-lock.json | 63 ++++++++++++++++++++++++++++++++++++-- frontend/package.json | 2 ++ frontend/src/main.tsx | 20 +++++++----- 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e92eecc..641facf 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -13,6 +13,8 @@ "@mantine/core": "^7.11.2", "@mantine/form": "^7.11.2", "@mantine/hooks": "^7.11.2", + "@mantine/modals": "^7.11.2", + "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1104,6 +1106,40 @@ "react": "^18.2.0" } }, + "node_modules/@mantine/modals": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/modals/-/modals-7.11.2.tgz", + "integrity": "sha512-AsVyGP5+F9jkQdQ9J0qzaK8q7n9bPcpswAjScpzIgEfUBC4RCvf63bmh9Yp1OEgxl1xkNdzGUYGVau0SQqHasA==", + "peerDependencies": { + "@mantine/core": "7.11.2", + "@mantine/hooks": "7.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/notifications": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/notifications/-/notifications-7.11.2.tgz", + "integrity": "sha512-KB/6mp3mU3LvcFlfMc5zK5mWcrAO+zAGeiBb1oUjNFXBECCn9xizqqUeT0YbWBHL7wysq9IThDJxLwUBnQt+8Q==", + "dependencies": { + "@mantine/store": "7.11.2", + "react-transition-group": "4.4.5" + }, + "peerDependencies": { + "@mantine/core": "7.11.2", + "@mantine/hooks": "7.11.2", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } + }, + "node_modules/@mantine/store": { + "version": "7.11.2", + "resolved": "https://registry.npmjs.org/@mantine/store/-/store-7.11.2.tgz", + "integrity": "sha512-FfkmnOnCivOjqwNaTZeV4TgDANUs7fP+0uSgzp0GuvwTuDfQNVafPBRzPkjKuz5ug+Nn9l6WwjfJ6LBDv9U0LQ==", + "peerDependencies": { + "react": "^18.2.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2232,8 +2268,7 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -2391,6 +2426,15 @@ "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/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5023,6 +5067,21 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "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/react-use-websocket": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.8.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index fcc6d28..bcfe2f1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -15,6 +15,8 @@ "@mantine/core": "^7.11.2", "@mantine/form": "^7.11.2", "@mantine/hooks": "^7.11.2", + "@mantine/modals": "^7.11.2", + "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 10e4c16..b6074c7 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,6 +5,7 @@ import "@fontsource/poppins"; import "@fontsource-variable/karla"; import "@/index.css"; import "@mantine/core/styles.css"; +import "@mantine/notifications/styles.css"; import { createTheme, MantineColorsTuple, @@ -13,6 +14,8 @@ import { import { BrowserRouter } from "react-router-dom"; import { AuthProvider } from "./context/AuthContext"; import { StreamSocketProvider } from "./context/CameraSocketContext"; +import { ModalsProvider } from "@mantine/modals"; +import { Notifications } from "@mantine/notifications"; const container = document.getElementById("root") as ReactDOM.Container; @@ -41,13 +44,16 @@ const theme = createTheme({ ReactDOM.createRoot(container).render( <React.StrictMode> <MantineProvider theme={theme} defaultColorScheme="light"> - <BrowserRouter> - <AuthProvider> - <StreamSocketProvider> - <App /> - </StreamSocketProvider> - </AuthProvider> - </BrowserRouter> + <Notifications /> + <ModalsProvider> + <BrowserRouter> + <AuthProvider> + <StreamSocketProvider> + <App /> + </StreamSocketProvider> + </AuthProvider> + </BrowserRouter> + </ModalsProvider> </MantineProvider> </React.StrictMode> ); From 60b2c4a04478ce216c7413b7d63c9061d1dba4a7 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 6 Aug 2024 15:16:16 +0800 Subject: [PATCH 26/63] feat: add "add camera" button --- frontend/src/layout/MainLayout.tsx | 30 ++++++++++++++++++++++++------ frontend/src/pages/app/livecam.jsx | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index b7bc1f3..3fa7b42 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -5,6 +5,7 @@ import { ActionIcon, AppShell, Burger, + Button, Card, Divider, Flex, @@ -14,7 +15,11 @@ import { } from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; -import {IconDotsVertical, IconPlayerPlay} from "@tabler/icons-react" +import { + IconDotsVertical, + IconPlayerPlay, + IconPlus, +} from "@tabler/icons-react"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); @@ -22,7 +27,7 @@ export default function MainLayout() { return ( <AppShell navbar={{ - width: 200, + width: 300, breakpoint: "sm", collapsed: { mobile: !mobileOpened }, @@ -58,11 +63,11 @@ export default function MainLayout() { <AppShell.Main className="flex flex-col"> <Outlet /> </AppShell.Main> - <AppShell.Footer zIndex={300}> + {/* <AppShell.Footer zIndex={300}> <Group w={"100%"} h={"100%"} bg={"myColor"} px={8}> <p>this is footer</p> </Group> - </AppShell.Footer> + </AppShell.Footer> */} </AppShell> ); } @@ -82,10 +87,23 @@ const Inspector = () => { <Divider /> <Text fw={600}>Daftar Kamera</Text> - <Flex direction={"column"}> + <Flex direction={"column"} gap={"md"}> + <Button + variant="outline" + style={{ borderStyle: "dashed", cursor: "pointer" }} + onClick={() => {}} + radius={"sm"} + > + <Flex align={"center"} justify={"center"}> + <IconPlus size={18} /> + <Text size="sm">Add Camera</Text> + </Flex> + </Button> <Card padding={"xs"} radius={"sm"} withBorder> <Group justify="space-between"> - <Text fw={400} size="sm">Camera 2</Text> + <Text fw={400} size="sm"> + Camera 2 + </Text> <ActionIcon variant="subtle"> <IconDotsVertical /> </ActionIcon> diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/livecam.jsx index 3f7eb3f..1028a46 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/livecam.jsx @@ -14,7 +14,7 @@ import { sampleDataCCTV } from "@/model/dataset"; const CameraView = () => { return ( - <Flex direction={"column"} gap={"xs"} p={"xs"}> + <Flex direction={"column"} gap={"xs"} p={"xs"} mah={"100vh"}> <MainView /> </Flex> ); From e18c8d06ef6c276a9ce2149902de3a3a8350b6f6 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 6 Aug 2024 15:57:37 +0800 Subject: [PATCH 27/63] feat: update login and header design --- frontend/src/components/Icon.tsx | 25 ++++++ frontend/src/components/titleDashboard.tsx | 28 ++++--- frontend/src/layout/MainLayout.tsx | 2 +- frontend/src/pages/login.tsx | 88 +++++++++++++--------- 4 files changed, 97 insertions(+), 46 deletions(-) create mode 100644 frontend/src/components/Icon.tsx diff --git a/frontend/src/components/Icon.tsx b/frontend/src/components/Icon.tsx new file mode 100644 index 0000000..17b2ef6 --- /dev/null +++ b/frontend/src/components/Icon.tsx @@ -0,0 +1,25 @@ +import React from "react"; + +type AppIconProps = React.ComponentPropsWithoutRef<"div"> & { + size?: number; +}; +export function AppIcon({ size, ...props }: AppIconProps) { + const styles = { + backgroundColor: "white", + borderRadius: "100%", + aspectRatio: 1 / 1, + display: "flex", + justifyItems: "center", + alignItems: "center", + height: `${size ?? 24}px`, + width: `${size ?? 24}px`, + } as React.CSSProperties; + return ( + <div {...props}> + <div style={styles}> + {/* dont forget add `/` like `/logo.png` to avoid missing asset */} + <img src="/logo.png" /> + </div> + </div> + ); +} diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx index 42ee822..1a9fc58 100644 --- a/frontend/src/components/titleDashboard.tsx +++ b/frontend/src/components/titleDashboard.tsx @@ -6,24 +6,26 @@ import { Text, useMantineColorScheme, } from "@mantine/core"; -import {IconMoon, IconSun} from "@tabler/icons-react" +import { IconMoon, IconSun } from "@tabler/icons-react"; +import { AppIcon } from "./Icon"; export default function TitleDashboard({ leftSection, }: { leftSection: React.ReactNode; }) { - - const {colorScheme, setColorScheme } = useMantineColorScheme({keepTransitions:true}) + const { colorScheme, setColorScheme } = useMantineColorScheme({ + keepTransitions: true, + }); return ( <> <Flex h={"100%"} align={"center"} px={"md"} pr={48}> {leftSection} <Flex align={"center"} className="flex-grow"> - <div className=" items-end"> - {/* dont forget add `/` like `/logo.png` to avoid missing asset */} - <img src="/logo.png" className="w-24" /> - </div> + <AppIcon + size={64} + style={{padding:"16px"}} + /> <Stack gap={"xs"} className="text-[#66ABB1] overflow-clip"> <h1 className=" text-2xl font-extrabold">BYE-CHEATING</h1> <Text visibleFrom="md" fw={"normal"} size="xs"> @@ -32,10 +34,18 @@ export default function TitleDashboard({ </Stack> </Flex> <ActionIconGroup> - <ActionIcon variant={colorScheme ==="light" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("light")}> + <ActionIcon + variant={colorScheme === "light" ? "filled" : "light"} + size={"xl"} + onClick={() => setColorScheme("light")} + > <IconSun size={20} /> </ActionIcon> - <ActionIcon variant={colorScheme ==="dark" ? "filled" : "light"} size={"xl"} onClick={()=> setColorScheme("dark")}> + <ActionIcon + variant={colorScheme === "dark" ? "filled" : "light"} + size={"xl"} + onClick={() => setColorScheme("dark")} + > <IconMoon size={20} /> </ActionIcon> </ActionIconGroup> diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 3fa7b42..e85d549 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -29,7 +29,7 @@ export default function MainLayout() { navbar={{ width: 300, - breakpoint: "sm", + breakpoint: "md", collapsed: { mobile: !mobileOpened }, }} header={{ height: 100 }} diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index aeb91e9..792ff6f 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -1,5 +1,15 @@ import { MinimalLayout } from "@/layout/MinimalLayout"; -import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; +import { + Anchor, + Button, + Divider, + Group, + Paper, + PasswordInput, + Stack, + Text, + TextInput, +} from "@mantine/core"; import { useForm } from "@mantine/form"; import { useDisclosure } from "@mantine/hooks"; import { useNavigate } from "react-router-dom"; @@ -20,7 +30,6 @@ function LoginCard() { useDisclosure(false); const form = useForm({ - mode: "uncontrolled", initialValues: { email: "", password: "", @@ -35,41 +44,48 @@ function LoginCard() { }); return ( - <div className="border-2 rounded-md p-4 flex flex-col gap-4 w-96"> - <Flex justify={"center"}> - <h1 className="text-2xl font-bold">Login</h1> - </Flex> - <form - className="flex flex-col gap-2 min-h-96" - onSubmit={form.onSubmit((values) => console.log(values))} - > - <TextInput - withAsterisk - label="email" - placeholder="your@email.com" - key={form.key("email")} - {...form.getInputProps("email")} - /> - <PasswordInput - withAsterisk - label="password" - visible={passwordVisible} - onVisibilityChange={togglePasswordVisibility} - key={form.key("password")} - {...form.getInputProps("password")} - /> - <div className="flex-grow"></div> - <Divider /> - <Flex direction={"column"} gap={"4px"}> - <Button type="submit">Login</Button> - <Button - onClick={() => navigate("/register", { replace: true })} - variant="outline" + <Paper radius={"md"} p={"xl"} withBorder> + <Text size="lg" w={300} fw={500}> + Selamat datang di Bye Cheating! + </Text> + <Group grow></Group> + <Divider my={"md"} /> + <form onSubmit={form.onSubmit(() => {})}> + <Stack> + <TextInput + required + label="email" + placeholder="nama@email.com" + onChange={(event) => + form.setFieldValue("email", event.currentTarget.value) + } + value={form.values.email} + error={form.errors.email} + /> + <PasswordInput + required + label="Password" + placeholder="Your password" + onChange={(e) => + form.setFieldValue("password", e.currentTarget.value) + } + value={form.values.password} + error={form.errors.password} + /> + </Stack> + <Group justify="space-between" mt={"xl"}> + <Anchor + component="button" + type="button" + variant="text" + onClick={() => navigate("/register")} + size="xs" > - Create new account - </Button> - </Flex> + Tidak memiliki akun? Register + </Anchor> + <Button type="submit">Login</Button> + </Group> </form> - </div> + </Paper> ); } From 5039afb0213223882175d90fc7a4ccf23c0dc12c Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Fri, 16 Aug 2024 08:52:25 +0800 Subject: [PATCH 28/63] feat: update register UI, setup localization just in case we need it --- frontend/package-lock.json | 65 +++++++++++++++++ frontend/package.json | 2 + frontend/react-18next.d.ts | 12 ++++ frontend/src/lib/i18n.ts | 25 +++++++ frontend/src/locales/en.json | 4 ++ frontend/src/locales/id.json | 14 ++++ frontend/src/main.tsx | 28 ++++---- frontend/src/pages/login.tsx | 3 - frontend/src/pages/register.tsx | 122 ++++++++++++++++++++------------ frontend/tsconfig.json | 2 +- 10 files changed, 214 insertions(+), 63 deletions(-) create mode 100644 frontend/react-18next.d.ts create mode 100644 frontend/src/lib/i18n.ts create mode 100644 frontend/src/locales/en.json create mode 100644 frontend/src/locales/id.json diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 641facf..1ae223e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,8 +16,10 @@ "@mantine/modals": "^7.11.2", "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", + "i18next": "^23.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^15.0.1", "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", @@ -3441,6 +3443,38 @@ "node": ">= 0.4" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/i18next": { + "version": "23.12.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.2.tgz", + "integrity": "sha512-XIeh5V+bi8SJSWGL3jqbTEBW5oD6rbP5L+E7dVQh1MNTxxYef0x15rhJVcRb7oiuq4jLtgy2SD8eFlf6P2cmqg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -4913,6 +4947,28 @@ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" }, + "node_modules/react-i18next": { + "version": "15.0.1", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.1.tgz", + "integrity": "sha512-NwxLqNM6CLbeGA9xPsjits0EnXdKgCRSS6cgkgOdNcPXqL+1fYNl8fBg1wmnnHvFy812Bt4IWTPE9zjoPmFj3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.8", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -6349,6 +6405,15 @@ } } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index bcfe2f1..533b7ff 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,8 +18,10 @@ "@mantine/modals": "^7.11.2", "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", + "i18next": "^23.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-i18next": "^15.0.1", "react-player": "^2.16.0", "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", diff --git a/frontend/react-18next.d.ts b/frontend/react-18next.d.ts new file mode 100644 index 0000000..4ce6b3d --- /dev/null +++ b/frontend/react-18next.d.ts @@ -0,0 +1,12 @@ +import "i18next" + +import ns1ID from "./src/locales/id.json" + +declare module "i18next" { + interface CustomTypeOptions { + defaultNS:"ns1", + resources: { + ns1: typeof ns1ID + } + } +} \ No newline at end of file diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts new file mode 100644 index 0000000..6a057aa --- /dev/null +++ b/frontend/src/lib/i18n.ts @@ -0,0 +1,25 @@ +import i18n from "i18next"; +import { initReactI18next } from "react-i18next"; + +import translationID from "@/locales/id.json"; +import translationEN from "@/locales/en.json"; + +const locales = { + id: { + ns1: translationID, + }, + en: { + ns1: translationEN, + }, +}; + +i18n.use(initReactI18next).init({ + lng: "id", + resources: locales, + defaultNS: "ns1", + ns: ["ns1", "ns2"], + interpolation: { + escapeValue: false, + }, +}); +export default i18n; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json new file mode 100644 index 0000000..03e1a15 --- /dev/null +++ b/frontend/src/locales/en.json @@ -0,0 +1,4 @@ +{ + "login": "Login", + "register": "Register" +} \ No newline at end of file diff --git a/frontend/src/locales/id.json b/frontend/src/locales/id.json new file mode 100644 index 0000000..b254b4e --- /dev/null +++ b/frontend/src/locales/id.json @@ -0,0 +1,14 @@ +{ + "welcome": { + "short": "Selamat Datang di Bye Cheating!" + }, + "login": "Masuk", + "to-login-suggestion": "Sudah punya akun, Masuk", + "register": "Daftar", + "to-register-suggestion": "Tidak punya akun, Daftar!", + "textfield": { + "email": "Email", + "password": "Password", + "name": "Nama" + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b6074c7..8aa4ad2 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,6 +6,7 @@ import "@fontsource-variable/karla"; import "@/index.css"; import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; +import i18n from "@/lib/i18n"; import { createTheme, MantineColorsTuple, @@ -16,6 +17,7 @@ import { AuthProvider } from "./context/AuthContext"; import { StreamSocketProvider } from "./context/CameraSocketContext"; import { ModalsProvider } from "@mantine/modals"; import { Notifications } from "@mantine/notifications"; +import { I18nextProvider } from "react-i18next"; const container = document.getElementById("root") as ReactDOM.Container; @@ -43,17 +45,19 @@ const theme = createTheme({ ReactDOM.createRoot(container).render( <React.StrictMode> - <MantineProvider theme={theme} defaultColorScheme="light"> - <Notifications /> - <ModalsProvider> - <BrowserRouter> - <AuthProvider> - <StreamSocketProvider> - <App /> - </StreamSocketProvider> - </AuthProvider> - </BrowserRouter> - </ModalsProvider> - </MantineProvider> + <I18nextProvider i18n={i18n}> + <MantineProvider theme={theme} defaultColorScheme="light"> + <Notifications /> + <ModalsProvider> + <BrowserRouter> + <AuthProvider> + <StreamSocketProvider> + <App /> + </StreamSocketProvider> + </AuthProvider> + </BrowserRouter> + </ModalsProvider> + </MantineProvider> + </I18nextProvider> </React.StrictMode> ); diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx index 792ff6f..e1072d7 100644 --- a/frontend/src/pages/login.tsx +++ b/frontend/src/pages/login.tsx @@ -11,7 +11,6 @@ import { TextInput, } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { useDisclosure } from "@mantine/hooks"; import { useNavigate } from "react-router-dom"; export function LoginPage() { @@ -26,8 +25,6 @@ export function LoginPage() { function LoginCard() { const navigate = useNavigate(); - const [passwordVisible, { toggle: togglePasswordVisibility }] = - useDisclosure(false); const form = useForm({ initialValues: { diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/register.tsx index 4d71cea..03534a7 100644 --- a/frontend/src/pages/register.tsx +++ b/frontend/src/pages/register.tsx @@ -1,7 +1,16 @@ import { MinimalLayout } from "@/layout/MinimalLayout"; -import { Button, Divider, Flex, PasswordInput, TextInput } from "@mantine/core"; +import { + Anchor, + Button, + Divider, + Text, + Group, + Paper, + PasswordInput, + Stack, + TextInput, +} from "@mantine/core"; import { useForm } from "@mantine/form"; -import { useDisclosure } from "@mantine/hooks"; import { useNavigate } from "react-router-dom"; export function RegisterPage() { @@ -16,15 +25,13 @@ export function RegisterPage() { function LoginCard() { const navigate = useNavigate(); - const [passwordVisible, { toggle: togglePasswordVisibility }] = - useDisclosure(false); const form = useForm({ - mode: "uncontrolled", initialValues: { email: "", name: "", password: "", + repeatedPassword: "", }, validate: { email: (value) => (/^\S+@\S+$/.test(value) ? null : "Invalid email"), @@ -33,53 +40,74 @@ function LoginCard() { /^(?=.*[A-Z])[A-Za-z\d]{8,}$/.test(value) ? null : "At least 8 chars and contain at least 1 uppercase", + repeatedPassword: (value,e) => + value == e.password ? null : "Password harus sama", }, }); return ( - <div className="border-2 rounded-md p-4 flex flex-col gap-4 w-96"> - <Flex justify={"center"}> - <h1 className="text-2xl font-bold">Register</h1> - </Flex> - <form - className="flex flex-col gap-2 min-h-96" - onSubmit={form.onSubmit((values) => console.log(values))} - > - <TextInput - withAsterisk - label="Email" - placeholder="your@email.com" - key={form.key("email")} - {...form.getInputProps("email")} - /> - <TextInput - withAsterisk - label="Name" - placeholder="your name" - key={form.key("name")} - {...form.getInputProps("name")} - /> - <PasswordInput - withAsterisk - label="password" - visible={passwordVisible} - onVisibilityChange={togglePasswordVisibility} - key={form.key("password")} - {...form.getInputProps("password")} - /> - <div className="flex-grow"></div> - - <Divider /> - <Flex direction={"column"} gap={"4px"}> - <Button type="submit">Register</Button> - <Button - onClick={() => navigate("/login", { replace: true })} - variant="outline" + <Paper radius={"md"} p={"xl"} withBorder> + <Text size="lg" w={300} fw={500}> + Selamat Datang di Bye Cheating! + </Text> + <Group grow></Group> + <Divider my={"md"} /> + <form onSubmit={form.onSubmit(() => {})}> + <Stack> + <TextInput + required + label={"Email"} + placeholder="nama@email.com" + onChange={(event) => + form.setFieldValue("email", event.currentTarget.value) + } + value={form.values.email} + error={form.errors.email} + /> + <TextInput + required + label={"Nama"} + placeholder="Nama anda" + onChange={(e) => { + form.setFieldValue("name", e.currentTarget.value); + }} + value={form.values.name} + error={form.errors.name} + /> + <PasswordInput + required + label={"Password"} + placeholder="Password anda" + onChange={(e) => + form.setFieldValue("password", e.currentTarget.value) + } + value={form.values.password} + error={form.errors.password} + /> + <PasswordInput + required + label={"Ulangi Password"} + placeholder="Password anda" + onChange={(e) => + form.setFieldValue("repeatedPassword", e.currentTarget.value) + } + value={form.values.repeatedPassword} + error={form.errors.repeatedPassword} + /> + </Stack> + <Group justify="space-between" mt={"xl"}> + <Anchor + component="button" + type="button" + variant="text" + onClick={() => navigate("/login")} + size="xs" > - Back to Login - </Button> - </Flex> + Sudah punya akun? Login + </Anchor> + <Button type="submit">Register</Button> + </Group> </form> - </div> + </Paper> ); } diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e30cb5a..51b7f93 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -25,6 +25,6 @@ "@/*": ["./src/*"] } }, - "include": ["./src", "eslint.config.js"], + "include": ["./src", "eslint.config.js", "react-18next.d.ts"], "references": [{ "path": "./tsconfig.node.json" }] } From ff4d5beaae73e1ddec6e506bf50c5eb27d9de778 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Mon, 19 Aug 2024 10:25:57 +0800 Subject: [PATCH 29/63] feat: add error screen and addd account card --- frontend/src/App.tsx | 45 ++++++++++++++++-- .../SidebarContent.tsx} | 47 ++++++++++--------- frontend/src/components/sidebar/UserInfo.tsx | 21 +++++++++ frontend/src/components/ui/ErrorScreen.tsx | 25 ++++++++++ frontend/src/layout/MainLayout.tsx | 4 +- 5 files changed, 116 insertions(+), 26 deletions(-) rename frontend/src/components/{sidebar.tsx => sidebar/SidebarContent.tsx} (55%) create mode 100644 frontend/src/components/sidebar/UserInfo.tsx create mode 100644 frontend/src/components/ui/ErrorScreen.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7f1ed35..3aa6e97 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { Outlet, Route, Routes } from "react-router-dom"; +import { Outlet, Route, Routes, useNavigate } from "react-router-dom"; import MainLayout from "@/layout/MainLayout"; import { default as CameraView, @@ -7,8 +7,11 @@ import { import Laporan from "@/pages/app/report"; import { LoginPage } from "@/pages/login"; import { RegisterPage } from "./pages/register"; +import { ErrorScreen } from "./components/ui/ErrorScreen"; +import { Box, Button } from "@mantine/core"; function App() { + const navigate = useNavigate(); return ( <Routes> <Route path="/" element={<Outlet />}> @@ -20,10 +23,46 @@ function App() { <Route path="laporan" element={<Laporan />} /> <Route path="websoket" element={<WebSocketDemo />} /> <Route path="test" element={<div>test view</div>} /> - <Route path="*" element={<div>404 not found</div>} /> + <Route + path="*" + element={ + <ErrorScreen + text="404 Not Found" + bottomSlot={ + <Button + onClick={() => { + window.history?.length && window.history.length > 1 + ? navigate(-1) + : navigate("/app"); + }} + variant="subtle" + > + Back to previous page + </Button> + } + /> + } + /> </Route> </Route> - <Route path="*" element={<div>404 not found</div>} /> + <Route + path="*" + element={ + <Box w={"100vw"} h={"100vh"}> + <ErrorScreen + text="404 Not Found" + bottomSlot={ + <Button + onClick={() => navigate("/", { replace: true })} + variant="subtle" + > + back to home + </Button> + } + /> + </Box> + } + /> </Routes> ); } diff --git a/frontend/src/components/sidebar.tsx b/frontend/src/components/sidebar/SidebarContent.tsx similarity index 55% rename from frontend/src/components/sidebar.tsx rename to frontend/src/components/sidebar/SidebarContent.tsx index 7e32ce2..871ea47 100644 --- a/frontend/src/components/sidebar.tsx +++ b/frontend/src/components/sidebar/SidebarContent.tsx @@ -1,10 +1,15 @@ import { useLocation, useNavigate } from "react-router-dom"; -import { NavLink, Text } from "@mantine/core"; +import { Divider, NavLink, Text } from "@mantine/core"; import { useState } from "react"; -import {IconDeviceDesktop, IconBook2,IconLogout} from "@tabler/icons-react" +import { IconDeviceDesktop, IconBook2, IconLogout } from "@tabler/icons-react"; +import { UserCardInfo } from "./UserInfo"; const menuLists = [ - { title: "Tampilan Langsung", path: "/app", icon: <IconDeviceDesktop size={20} /> }, + { + title: "Tampilan Langsung", + path: "/app", + icon: <IconDeviceDesktop size={20} />, + }, { title: "Laporan", path: "/app/laporan", @@ -12,22 +17,22 @@ const menuLists = [ }, ]; -function TitleSidebar() { - return ( - <div className="flex min-h-6 items-center justify-center p-4"> - <img - src="/LogoBgWhite.png" - className={`float-left block h-12 w-12 duration-500 ${ - !open && "hidden" - }`} - /> - <div className="p-1" /> - <Text variant="text">Dashboard</Text> - </div> - ); -} +// function TitleSidebar() { +// return ( +// <div className="flex min-h-6 items-center justify-center p-4"> +// <img +// src="/LogoBgWhite.png" +// className={`float-left block h-12 w-12 duration-500 ${ +// !open && "hidden" +// }`} +// /> +// <div className="p-1" /> +// <Text variant="text">Dashboard</Text> +// </div> +// ); +// } -export default function NavbarContent() { +export default function SidebarContent() { const location = useLocation(); const navigate = useNavigate(); const [active, setActive] = useState( @@ -36,11 +41,11 @@ export default function NavbarContent() { return ( <> - <TitleSidebar /> + <UserCardInfo /> + <Divider my={""} /> {menuLists.map((val, idx) => ( <NavLink variant="filled" - key={val.title} component={"button"} label={val.title} @@ -58,7 +63,7 @@ export default function NavbarContent() { component="button" label={"Log out"} color="red" - leftSection={<IconLogout size={20}/>} + leftSection={<IconLogout size={20} />} onClick={() => { console.log("logout"); }} diff --git a/frontend/src/components/sidebar/UserInfo.tsx b/frontend/src/components/sidebar/UserInfo.tsx new file mode 100644 index 0000000..21dc4f7 --- /dev/null +++ b/frontend/src/components/sidebar/UserInfo.tsx @@ -0,0 +1,21 @@ +import { Avatar, Group, Text, UnstyledButton } from "@mantine/core"; + + + +export function UserCardInfo() { + return ( + <UnstyledButton style={{}} onClick={() => {}}> + <Group wrap="nowrap" p={"xs"}> + <Avatar size={48} radius={"md"} /> + <div> + <Text fz={"xs"} tt={"uppercase"} fw={"bold"} c={"dimmed"}> + my@email.com + </Text> + <Text fz={"sm"} fw={"normal"}> + Agung Adhinata + </Text> + </div> + </Group> + </UnstyledButton> + ); +} diff --git a/frontend/src/components/ui/ErrorScreen.tsx b/frontend/src/components/ui/ErrorScreen.tsx new file mode 100644 index 0000000..e0d7aa6 --- /dev/null +++ b/frontend/src/components/ui/ErrorScreen.tsx @@ -0,0 +1,25 @@ +import { Center, Stack, Text } from "@mantine/core"; +import { IconAlertCircle } from "@tabler/icons-react"; + +interface ErrorScreenProps { + topSlot?: JSX.Element; + text: string; + bottomSlot?: JSX.Element; +} + +/** + * Display error screen + * + * @returns + */ +export function ErrorScreen({ topSlot, bottomSlot, text }: ErrorScreenProps) { + return ( + <Center miw={"100%"} mih={"100%"} style={{ flexGrow: "1" }}> + <Stack align="center"> + {topSlot ? topSlot : <IconAlertCircle />} + <Text>{text}</Text> + {bottomSlot ? bottomSlot : null} + </Stack> + </Center> + ); +} diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index e85d549..35236ee 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,5 +1,5 @@ import { Outlet } from "react-router-dom"; -import NavbarContent from "../components/sidebar"; +import SidebarContent from "@/components/sidebar/SidebarContent"; import { Accordion, ActionIcon, @@ -41,7 +41,7 @@ export default function MainLayout() { // layout="alt" > <AppShell.Navbar> - <NavbarContent /> + <SidebarContent /> </AppShell.Navbar> <AppShell.Header> <TitleDashboard From 8e67501253fabd44dedbce1b53230b1153a4bd22 Mon Sep 17 00:00:00 2001 From: GitButler <gitbutler@gitbutler.com> Date: Tue, 3 Sep 2024 16:20:13 +0800 Subject: [PATCH 30/63] GitButler Integration Commit This is an integration commit for the virtual branches that GitButler is tracking. Due to GitButler managing multiple virtual branches, you cannot switch back and forth between git branches and virtual branches easily. If you switch to another branch, GitButler will need to be reinitialized. If you commit on this branch, GitButler will throw it away. Here are the branches that are currently applied: - dev (refs/gitbutler/dev) branch head: ff4d5beaae73e1ddec6e506bf50c5eb27d9de778 - frontend/src/pages/app/users.tsx - frontend/src/pages/app/report.jsx - frontend/src/context/AuthContext.tsx - frontend/src/App.tsx - frontend/src/components/main/AddCameraModal.tsx - frontend/src/constant.ts - frontend/src/components/sidebar/SidebarContent.tsx - frontend/src/layout/MainLayout.tsx For more information about what we're doing here, check out our docs: https://docs.gitbutler.com/features/virtual-branches/integration-branch From 76b1c6f0d33e75a3313a010e1e44d333bd0d5447 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 3 Sep 2024 17:38:28 +0800 Subject: [PATCH 31/63] feat: add cam toggle between raw & ML view --- frontend/src/components/ImageStreamViewer.tsx | 9 ++++-- frontend/src/components/LiveCamComponent.tsx | 29 +++++++++++++++++++ .../app/{livecam.jsx => LiveCamPage.jsx} | 7 +++-- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 frontend/src/components/LiveCamComponent.tsx rename frontend/src/pages/app/{livecam.jsx => LiveCamPage.jsx} (91%) diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index eb0a5ef..65358de 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -1,6 +1,6 @@ -import { Box } from "@mantine/core"; +import { Box, Text } from "@mantine/core"; import { useEffect, useState } from "react"; -import {IconAlertCircle} from "@tabler/icons-react" +import { IconAlertCircle } from "@tabler/icons-react"; // const getBlobUrl = (data: string | null) => { // if (data) { @@ -12,12 +12,14 @@ import {IconAlertCircle} from "@tabler/icons-react" export function ImageStreamViewer({ base64Data, error, + title }: { /** * Base64 image string, not include `data:image/jpeg;base64,` */ base64Data?: string; error?: string; + title?: string; }) { const [base64String, setBase64String] = useState<string | undefined>( undefined @@ -29,7 +31,7 @@ export function ImageStreamViewer({ return ( <Box bd={"1px solid myColor.5"} - className={`overflow-auto aspect-video w-full rounded-sm flex justify-center items-center`} + className={`overflow-auto relative aspect-video w-full rounded-sm flex justify-center items-center`} > {base64String ? ( <img src={base64String} alt="CCTV Stream" /> @@ -39,6 +41,7 @@ export function ImageStreamViewer({ <IconAlertCircle /> {error ?? "Masalah tidak diketahui"} </p> )} + <Text style={{position:"absolute", top:1, left:1}} m={10} c={"dimmed"}>{title}</Text> </Box> ); } diff --git a/frontend/src/components/LiveCamComponent.tsx b/frontend/src/components/LiveCamComponent.tsx new file mode 100644 index 0000000..0ff0376 --- /dev/null +++ b/frontend/src/components/LiveCamComponent.tsx @@ -0,0 +1,29 @@ +import { Affix, Button, Flex } from "@mantine/core"; +import { ImageStreamViewer } from "./ImageStreamViewer"; +import { useState } from "react"; + +type LiveCamProps = { + rawCamData?: string; + mlCamData?: string; +}; + +export function LiveCamComponent(props: LiveCamProps) { + const [visible, setVisible] = useState(false); + return ( + <Flex style={{ position: "relative" }}> + {visible ? ( + <ImageStreamViewer title="ML View" base64Data={props.mlCamData} /> + ) : ( + <ImageStreamViewer title="RAW View" base64Data={props.rawCamData} /> + )} + <Button + style={{ position: "absolute", right:0}} + m={10} + onClick={() => setVisible((prev) => !prev)} + variant={visible ? "filled" : "light"} + > + toggle ML + </Button> + </Flex> + ); +} diff --git a/frontend/src/pages/app/livecam.jsx b/frontend/src/pages/app/LiveCamPage.jsx similarity index 91% rename from frontend/src/pages/app/livecam.jsx rename to frontend/src/pages/app/LiveCamPage.jsx index 1028a46..7c3d5a8 100644 --- a/frontend/src/pages/app/livecam.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -11,8 +11,9 @@ import { import { useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/model/dataset"; +import { LiveCamComponent } from "@/components/LiveCamComponent"; -const CameraView = () => { +const LiveCamPage = () => { return ( <Flex direction={"column"} gap={"xs"} p={"xs"} mah={"100vh"}> <MainView /> @@ -63,11 +64,11 @@ const MainView = () => { <ReportTable data={sampleDataCCTV} /> </Table.ScrollContainer> ) : ( - <ImageStreamViewer base64Data={stream.base64data} minW={500} /> + <LiveCamComponent mlCamData="" rawCamData={stream.base64data} /> )} <br /> </> ); }; -export default CameraView; +export default LiveCamPage; From c4c0eab7124743759549db86957a6e27d65be72c Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 3 Sep 2024 17:38:56 +0800 Subject: [PATCH 32/63] feat: add "add camera" button --- .../src/components/main/AddCameraModal.tsx | 34 +++++++++++++++++++ frontend/src/layout/MainLayout.tsx | 12 +++++-- 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/main/AddCameraModal.tsx diff --git a/frontend/src/components/main/AddCameraModal.tsx b/frontend/src/components/main/AddCameraModal.tsx new file mode 100644 index 0000000..6e301af --- /dev/null +++ b/frontend/src/components/main/AddCameraModal.tsx @@ -0,0 +1,34 @@ +import { Button, Modal, Stack, TextInput } from "@mantine/core"; +import { modals } from "@mantine/modals"; + +interface AddCameraModalProps { + opened: boolean; + onClose: () => void; +} +interface AddCameraModalContentProps { + onSubmit: () => void; +} +export function AddCameraModalContent(props: AddCameraModalContentProps) { + return ( + <form + onSubmit={(e) => { + e.preventDefault(); + props.onSubmit(); + }} + > + <Stack> + <TextInput name="URL Kamera" placeholder="RTSP URL" /> + <Button type="submit">Simpan</Button> + </Stack> + </form> + ); +} + +export function AddCameraModal(props: AddCameraModalProps) { + return ( + <Modal title="Tambah Kamera" opened={props.opened} onClose={props.onClose}> + <AddCameraModalContent onSubmit={() => modals.closeAll()} /> + </Modal> + ); +} +// export const AddCameraModal.Content: JSX.Element= AddCameraModalContent diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 35236ee..014439e 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -20,6 +20,8 @@ import { IconPlayerPlay, IconPlus, } from "@tabler/icons-react"; +import { modals } from "@mantine/modals"; +import { AddCameraModalContent } from "@/components/main/AddCameraModal"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); @@ -80,7 +82,6 @@ const Inspector = () => { <Accordion.Control>Metadata</Accordion.Control> <Accordion.Panel> <Text size="sm">URL: rtsp://localhost:8888/a301.live</Text> - <RtspInput /> </Accordion.Panel> </Accordion.Item> </Accordion> @@ -91,7 +92,14 @@ const Inspector = () => { <Button variant="outline" style={{ borderStyle: "dashed", cursor: "pointer" }} - onClick={() => {}} + onClick={() => { + modals.open({ + title: "Tambah Kamera", + children: ( + <AddCameraModalContent onSubmit={()=>{}} /> + ) + }) + }} radius={"sm"} > <Flex align={"center"} justify={"center"}> From e9088d3e82bd869241a99c1b5471bfdd08074a82 Mon Sep 17 00:00:00 2001 From: Agung Adhinata <adhi0asta@gmail.com> Date: Tue, 3 Sep 2024 17:39:11 +0800 Subject: [PATCH 33/63] feat: add user page --- frontend/src/pages/app/users.tsx | 60 ++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 frontend/src/pages/app/users.tsx diff --git a/frontend/src/pages/app/users.tsx b/frontend/src/pages/app/users.tsx new file mode 100644 index 0000000..19b57b3 --- /dev/null +++ b/frontend/src/pages/app/users.tsx @@ -0,0 +1,60 @@ +import { ActionIcon, Center, Flex, Table, Text, Title } from "@mantine/core"; +import { IconEdit } from "@tabler/icons-react"; + +const sample:UserData[] = []; + +type UserData = { + id: string; + username: string; + email: string; +}; + +type UserListProps = { + data: UserData[]; +}; + +function UserLists(props: UserListProps) { + return ( + <Table withTableBorder> + <Table.Thead> + <Table.Tr> + <Table.Th>Username</Table.Th> + <Table.Th>Email</Table.Th> + <Table.Th>Aksi</Table.Th> + </Table.Tr> + </Table.Thead> + <Table.Tbody> + {props.data.length > 1 ? ( + props.data.map((element) => { + return ( + <Table.Tr key={element.email}> + <Table.Td>{element.username}</Table.Td> + <Table.Td>{element.email}</Table.Td> + <Table.Td> + <ActionIcon variant="transparent"> + <IconEdit /> + </ActionIcon> + </Table.Td> + </Table.Tr> + ); + }) + ) : ( + <Center> + <Text>Pengguna kosong</Text> + </Center> + )} + </Table.Tbody> + </Table> + ); +} + +export default function UsersPage() { + return ( + <> + <Flex direction={"column"} gap={"xs"} p={"xs"} mah={"100vh"}> + <Title order={2}>Akun Pengguna + + + + ); +} From 3a763079026d36d41a02d9fab95afaa5e00a3956 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 3 Sep 2024 17:39:41 +0800 Subject: [PATCH 34/63] feat: update auth context --- frontend/src/context/AuthContext.tsx | 50 +++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 09c9602..ff418d5 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,11 +1,19 @@ -import React, { createContext, useContext, useMemo, useState } from "react"; +import React, { createContext, useContext, useMemo } from "react"; import { useLocalStorage } from "@/hooks/useLocalStorage"; import { useNavigate } from "react-router-dom"; +import { BASE_URL } from "@/constant"; +import { notifications } from "@mantine/notifications"; -interface IAuthContext { - login: (email: string, password: string) => void; +type IAuthContext = { + login: (email: string, password: string) => Promise; logout: () => void; -} + register: ( + email: string, + name: string, + password: string, + repeatedPassword: string + ) => Promise; +}; const KEY_ACCESS_TOKEN = "storage_access_token"; const KEY_REFRESH_TOKEN = "storage_refresh_token"; @@ -16,20 +24,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const [accessToken, setAccessToken] = useLocalStorage(KEY_ACCESS_TOKEN, ""); const [refreshToken, setRefreshToken] = useLocalStorage( KEY_REFRESH_TOKEN, - "", + "" ); const navigate = useNavigate(); - const [loggedIn, setLoggedIn] = useState(false); const data: IAuthContext = useMemo( () => ({ async login(email, password) { //TODO: fetch login, save tokens, and navigate to `/app` - const result = await fetch("http://localhost:8889/v1/camera", { + const result = await fetch(`${BASE_URL}/v1/login`, { method: "GET", keepalive: true, body: JSON.stringify({ email, password }), - }); + }).catch(e=> console.log(e)); + if(!result) return console.log(result.statusText); }, logout() { @@ -37,8 +45,32 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setRefreshToken(""); navigate("/", { replace: true }); }, + async register( + email: string, + name: string, + password: string, + repeatedPassword: string + ) { + const result = await fetch(`${BASE_URL}/v1/register`, { + method: "POST", + body: JSON.stringify({ + email: email, + name: name, + password: password, + repeatedPassword: repeatedPassword, + }), + }).catch((e) => console.log(e)); + + if (!result) return; + + console.log(result.statusText); + notifications.show({ + message: result.statusText, + title: "Register Status", + }); + }, }), - [accessToken, refreshToken], + [accessToken, refreshToken] ); return {children}; From 021b539ca63545bf55bf9ef556937a725cca02d5 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 3 Sep 2024 17:40:00 +0800 Subject: [PATCH 35/63] feat: setup constant --- frontend/src/constant.ts | 1 + 1 file changed, 1 insertion(+) create mode 100644 frontend/src/constant.ts diff --git a/frontend/src/constant.ts b/frontend/src/constant.ts new file mode 100644 index 0000000..6b66f04 --- /dev/null +++ b/frontend/src/constant.ts @@ -0,0 +1 @@ +export const BASE_URL = "https://localhost:5500" \ No newline at end of file From d294b119ee98e15ab90e045be0e00d420ab814a7 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 3 Sep 2024 17:40:12 +0800 Subject: [PATCH 36/63] refactor --- frontend/src/App.tsx | 15 +++++++-------- .../src/components/sidebar/SidebarContent.tsx | 2 +- frontend/src/pages/app/report.jsx | 2 +- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3aa6e97..84af9a1 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,14 +1,12 @@ import { Outlet, Route, Routes, useNavigate } from "react-router-dom"; import MainLayout from "@/layout/MainLayout"; -import { - default as CameraView, - default as WebSocketDemo, -} from "@/pages/app/livecam"; -import Laporan from "@/pages/app/report"; +import ReportPage from "@/pages/app/report"; import { LoginPage } from "@/pages/login"; import { RegisterPage } from "./pages/register"; import { ErrorScreen } from "./components/ui/ErrorScreen"; import { Box, Button } from "@mantine/core"; +import UsersPage from "./pages/app/users"; +import LiveCamPage from "./pages/app/LiveCamPage"; function App() { const navigate = useNavigate(); @@ -19,9 +17,10 @@ function App() { } /> } /> }> - } /> - } /> - } /> + } /> + }/> + } /> + {/* } /> */} test view
    } />
    From f9b4ef883b3ad8c6f14f6cf3881ad73e6bbdddba Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 3 Sep 2024 17:40:24 +0800 Subject: [PATCH 37/63] feat: setup GrpcContext --- frontend/src/context/GrpcContext.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 frontend/src/context/GrpcContext.tsx diff --git a/frontend/src/context/GrpcContext.tsx b/frontend/src/context/GrpcContext.tsx new file mode 100644 index 0000000..56bd1e5 --- /dev/null +++ b/frontend/src/context/GrpcContext.tsx @@ -0,0 +1,8 @@ + + +export interface IGrpcContext { + setStreamCollector:(listener: (data: string) => void) => void; + switchSteamChannel: (streamId: string) => void; + startStream : () => void; + stopStream: () => void; +} \ No newline at end of file From 108c0fc20749dd5269f36c516ccb3a701ecadac8 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 3 Sep 2024 17:45:11 +0800 Subject: [PATCH 38/63] feat: rename all routes --- frontend/src/App.tsx | 8 ++++---- frontend/src/pages/{login.tsx => LoginPage.tsx} | 0 frontend/src/pages/{register.tsx => RegisterPage.tsx} | 0 frontend/src/pages/app/{users.tsx => UsersPage.tsx} | 0 4 files changed, 4 insertions(+), 4 deletions(-) rename frontend/src/pages/{login.tsx => LoginPage.tsx} (100%) rename frontend/src/pages/{register.tsx => RegisterPage.tsx} (100%) rename frontend/src/pages/app/{users.tsx => UsersPage.tsx} (100%) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 84af9a1..0c46d8d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,11 +1,11 @@ import { Outlet, Route, Routes, useNavigate } from "react-router-dom"; import MainLayout from "@/layout/MainLayout"; import ReportPage from "@/pages/app/report"; -import { LoginPage } from "@/pages/login"; -import { RegisterPage } from "./pages/register"; +import { LoginPage } from "@/pages/LoginPage.tsx"; +import { RegisterPage } from "./pages/RegisterPage.tsx"; import { ErrorScreen } from "./components/ui/ErrorScreen"; import { Box, Button } from "@mantine/core"; -import UsersPage from "./pages/app/users"; +import UsersPage from "./pages/app/UsersPage.tsx"; import LiveCamPage from "./pages/app/LiveCamPage"; function App() { @@ -18,7 +18,7 @@ function App() { } /> }> } /> - }/> + } /> } /> {/* } /> */} test view
    } /> diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/LoginPage.tsx similarity index 100% rename from frontend/src/pages/login.tsx rename to frontend/src/pages/LoginPage.tsx diff --git a/frontend/src/pages/register.tsx b/frontend/src/pages/RegisterPage.tsx similarity index 100% rename from frontend/src/pages/register.tsx rename to frontend/src/pages/RegisterPage.tsx diff --git a/frontend/src/pages/app/users.tsx b/frontend/src/pages/app/UsersPage.tsx similarity index 100% rename from frontend/src/pages/app/users.tsx rename to frontend/src/pages/app/UsersPage.tsx From b3117a2371a02fcd42e2f3ee3b8effd97d608eb7 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Wed, 4 Sep 2024 07:15:00 +0800 Subject: [PATCH 39/63] feat: migrate from react-router to tanstack-router --- frontend/package-lock.json | 482 ++++++++++++------ frontend/package.json | 4 +- frontend/src/App.tsx | 69 --- .../src/components/sidebar/SidebarContent.tsx | 5 +- frontend/src/context/AuthContext.tsx | 4 +- frontend/src/layout/MainLayout.tsx | 36 +- frontend/src/layout/MinimalLayout.tsx | 2 +- frontend/src/main.tsx | 53 +- frontend/src/pages/LoginPage.tsx | 4 +- frontend/src/pages/RegisterPage.tsx | 4 +- frontend/src/pages/app/LiveCamPage.jsx | 1 - frontend/src/routeTree.gen.ts | 173 +++++++ frontend/src/routes/__root.tsx | 29 ++ frontend/src/routes/app.tsx | 24 + frontend/src/routes/app/index.tsx | 6 + frontend/src/routes/app/profile.tsx | 5 + frontend/src/routes/app/users.tsx | 6 + frontend/src/routes/index.tsx | 5 + frontend/src/routes/login.tsx | 5 + frontend/src/routes/register.tsx | 5 + frontend/src/theme.tsx | 24 + frontend/vite.config.ts | 4 +- 22 files changed, 655 insertions(+), 295 deletions(-) create mode 100644 frontend/src/routeTree.gen.ts create mode 100644 frontend/src/routes/__root.tsx create mode 100644 frontend/src/routes/app.tsx create mode 100644 frontend/src/routes/app/index.tsx create mode 100644 frontend/src/routes/app/profile.tsx create mode 100644 frontend/src/routes/app/users.tsx create mode 100644 frontend/src/routes/index.tsx create mode 100644 frontend/src/routes/login.tsx create mode 100644 frontend/src/routes/register.tsx create mode 100644 frontend/src/theme.tsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 1ae223e..7224604 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,12 +16,12 @@ "@mantine/modals": "^7.11.2", "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", + "@tanstack/react-router": "^1.52.5", "i18next": "^23.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.0.1", "react-player": "^2.16.0", - "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5" @@ -29,6 +29,8 @@ "devDependencies": { "@eslint/compat": "^1.1.1", "@eslint/js": "^9.7.0", + "@tanstack/router-devtools": "^1.52.5", + "@tanstack/router-plugin": "^1.52.0", "@types/eslint__js": "^8.42.3", "@types/node": "^20.14.10", "@types/react": "^18.3.3", @@ -88,30 +90,30 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", - "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", - "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-compilation-targets": "^7.24.7", - "@babel/helper-module-transforms": "^7.24.7", - "@babel/helpers": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/template": "^7.24.7", - "@babel/traverse": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -127,12 +129,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", - "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.24.7", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -142,14 +144,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", - "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.24.7", - "@babel/helper-validator-option": "^7.24.7", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -157,43 +159,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", - "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", - "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", - "dev": true, - "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", - "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-module-imports": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", @@ -208,16 +173,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", - "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.24.7", "@babel/helper-module-imports": "^7.24.7", "@babel/helper-simple-access": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/helper-validator-identifier": "^7.24.7" + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -227,9 +191,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", - "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", "dev": true, "engines": { "node": ">=6.9.0" @@ -248,22 +212,10 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", - "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.24.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", - "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -279,22 +231,22 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", - "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", - "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { - "@babel/template": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -316,10 +268,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -327,6 +282,36 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx-self": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", @@ -369,33 +354,30 @@ } }, "node_modules/@babel/template": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", - "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7" + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", - "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.24.7", - "@babel/helper-environment-visitor": "^7.24.7", - "@babel/helper-function-name": "^7.24.7", - "@babel/helper-hoist-variables": "^7.24.7", - "@babel/helper-split-export-declaration": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/types": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -413,12 +395,12 @@ } }, "node_modules/@babel/types": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", - "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-string-parser": "^7.24.8", "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, @@ -1187,14 +1169,6 @@ "node": ">=14" } }, - "node_modules/@remix-run/router": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.17.0.tgz", - "integrity": "sha512-2D6XaHEVvkCn682XBnipbJjgZUU7xjLtA4dGJRBVUKpEaDYOZMENZoZjAOSb7qirxt5RupjzZxz4fK2FO+EFPw==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -1432,6 +1406,153 @@ "react": ">= 16" } }, + "node_modules/@tanstack/history": { + "version": "1.51.7", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.51.7.tgz", + "integrity": "sha512-y25aH3NDbdUp5Gk6Fnb77LsHTT2JrzVgI44ZiyEOf8i2j14Ma3oJ80fCw7rT/iV4xa4IN2Yex9flAsZQdh1i4A==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.52.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.52.5.tgz", + "integrity": "sha512-rGQW92c4bV6DKmrpHvLZlkAiTrp/iG+gNCtBd5V4z0mtmscaRrEwVSC2NPnQ1xDyDBvisauklsU6ZTRc1VfibA==", + "dependencies": { + "@tanstack/history": "1.51.7", + "@tanstack/react-store": "^0.5.5", + "tiny-invariant": "^1.3.3", + "tiny-warning": "^1.0.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.5.5.tgz", + "integrity": "sha512-1orYXGatBqXCYKuroFwV8Ll/6aDa5E3pU6RR4h7RvRk7TmxF1+zLCsWALZaeijXkySNMGmvawSbUXRypivg2XA==", + "dependencies": { + "@tanstack/store": "0.5.5", + "use-sync-external-store": "^1.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + } + }, + "node_modules/@tanstack/router-devtools": { + "version": "1.52.5", + "resolved": "https://registry.npmjs.org/@tanstack/router-devtools/-/router-devtools-1.52.5.tgz", + "integrity": "sha512-hxakXDsRnksfXf5kH79+uEMnj/mVlK9L1KphvajCNH8AoWjEaiU7mbtu71yQoJfjtOHYglQA4kY/9rd5K5pnBw==", + "dev": true, + "dependencies": { + "clsx": "^2.1.1", + "goober": "^2.1.14" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-router": "^1.52.5", + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.52.0.tgz", + "integrity": "sha512-p1q+pGt5kh9a2d0I3inoRCEaqL1NXPOKarcvNrwn8wP4K6IXXSipZiXIsC4bribVdzBlBwhiyCkDjAUVV0l0xg==", + "dev": true, + "dependencies": { + "prettier": "^3.3.3", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-plugin": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.52.0.tgz", + "integrity": "sha512-CfZL2MeNquohnSuUUvfYNl3QGl1jD1JH/3lTnlx8TY4JMdz1tWuDhNfuJuPh32mNAT0ueFzgUnrEHDEjMRxq7Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.25.2", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.3", + "@babel/types": "^7.25.2", + "@tanstack/router-generator": "^1.52.0", + "@types/babel__core": "^7.20.5", + "@types/babel__generator": "^7.6.8", + "@types/babel__template": "^7.4.4", + "@types/babel__traverse": "^7.20.6", + "babel-dead-code-elimination": "^1.0.6", + "chokidar": "^3.6.0", + "unplugin": "^1.12.2", + "zod": "^3.23.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": ">=1.0.0", + "vite": ">=5.0.0", + "webpack": ">=5.92.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@tanstack/store": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.5.5.tgz", + "integrity": "sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1990,6 +2111,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.6.tgz", + "integrity": "sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3344,6 +3477,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "dev": true, + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -4792,9 +4934,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -5055,36 +5197,6 @@ } } }, - "node_modules/react-router": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.24.0.tgz", - "integrity": "sha512-sQrgJ5bXk7vbcC4BxQxeNa5UmboFm35we1AFK0VvQaz9g0LzxEIuLOhHIoZ8rnu9BO21ishGeL9no1WB76W/eg==", - "dependencies": { - "@remix-run/router": "1.17.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.24.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.24.0.tgz", - "integrity": "sha512-960sKuau6/yEwS8e+NVEidYQb1hNjAYM327gjEyXlc6r3Skf2vtwuJ2l7lssdegD2YjoKG5l8MsVyeTDlVeY8g==", - "dependencies": { - "@remix-run/router": "1.17.0", - "react-router": "6.24.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -5909,6 +6021,16 @@ "node": ">=0.8" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6219,6 +6341,20 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unplugin": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.12.3.tgz", + "integrity": "sha512-my8DH0/T/Kx33KO+6QXAqdeMYgyy0GktlOpdQjpagfHKw5DrD0ctPr7SHUyOT3g4ZVpzCQGt/qcpuoKJ/pniHA==", + "dev": true, + "dependencies": { + "acorn": "^8.12.1", + "webpack-sources": "^3.2.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", @@ -6336,6 +6472,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6414,6 +6558,21 @@ "node": ">=0.10.0" } }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6707,6 +6866,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/package.json b/frontend/package.json index 533b7ff..3d21f05 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,12 +18,12 @@ "@mantine/modals": "^7.11.2", "@mantine/notifications": "^7.11.2", "@tabler/icons-react": "^3.11.0", + "@tanstack/react-router": "^1.52.5", "i18next": "^23.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.0.1", "react-player": "^2.16.0", - "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5" @@ -31,6 +31,8 @@ "devDependencies": { "@eslint/compat": "^1.1.1", "@eslint/js": "^9.7.0", + "@tanstack/router-devtools": "^1.52.5", + "@tanstack/router-plugin": "^1.52.0", "@types/eslint__js": "^8.42.3", "@types/node": "^20.14.10", "@types/react": "^18.3.3", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0c46d8d..e69de29 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,69 +0,0 @@ -import { Outlet, Route, Routes, useNavigate } from "react-router-dom"; -import MainLayout from "@/layout/MainLayout"; -import ReportPage from "@/pages/app/report"; -import { LoginPage } from "@/pages/LoginPage.tsx"; -import { RegisterPage } from "./pages/RegisterPage.tsx"; -import { ErrorScreen } from "./components/ui/ErrorScreen"; -import { Box, Button } from "@mantine/core"; -import UsersPage from "./pages/app/UsersPage.tsx"; -import LiveCamPage from "./pages/app/LiveCamPage"; - -function App() { - const navigate = useNavigate(); - return ( - - }> - } /> - } /> - } /> - }> - } /> - } /> - } /> - {/* } /> */} - test view
    } /> - { - window.history?.length && window.history.length > 1 - ? navigate(-1) - : navigate("/app"); - }} - variant="subtle" - > - Back to previous page - - } - /> - } - /> - - - - navigate("/", { replace: true })} - variant="subtle" - > - back to home - - } - /> - - } - /> - - ); -} - -export default App; diff --git a/frontend/src/components/sidebar/SidebarContent.tsx b/frontend/src/components/sidebar/SidebarContent.tsx index fc01140..9852a9e 100644 --- a/frontend/src/components/sidebar/SidebarContent.tsx +++ b/frontend/src/components/sidebar/SidebarContent.tsx @@ -1,8 +1,8 @@ -import { useLocation, useNavigate } from "react-router-dom"; import { Divider, NavLink } from "@mantine/core"; import { useState } from "react"; import { IconDeviceDesktop, IconBook2, IconLogout } from "@tabler/icons-react"; import { UserCardInfo } from "./UserInfo"; +import { useNavigate } from "@tanstack/react-router"; const menuLists = [ { @@ -33,7 +33,6 @@ const menuLists = [ // } export default function SidebarContent() { - const location = useLocation(); const navigate = useNavigate(); const [active, setActive] = useState( menuLists.findIndex((item) => item.path == (location.pathname ?? "/app")) @@ -52,7 +51,7 @@ export default function SidebarContent() { leftSection={val.icon} active={active == idx} onClick={() => { - navigate(val.path); + navigate({to: val.path}); setActive(idx); }} /> diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index ff418d5..69d59e0 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,8 +1,8 @@ import React, { createContext, useContext, useMemo } from "react"; import { useLocalStorage } from "@/hooks/useLocalStorage"; -import { useNavigate } from "react-router-dom"; import { BASE_URL } from "@/constant"; import { notifications } from "@mantine/notifications"; +import { useNavigate } from "@tanstack/react-router"; type IAuthContext = { login: (email: string, password: string) => Promise; @@ -43,7 +43,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { logout() { setAccessToken(""); setRefreshToken(""); - navigate("/", { replace: true }); + navigate({to: "/", replace: true}); }, async register( email: string, diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 014439e..7f80a5c 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,4 +1,3 @@ -import { Outlet } from "react-router-dom"; import SidebarContent from "@/components/sidebar/SidebarContent"; import { Accordion, @@ -11,17 +10,16 @@ import { Flex, Group, Text, - TextInput, } from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; import { IconDotsVertical, - IconPlayerPlay, IconPlus, } from "@tabler/icons-react"; import { modals } from "@mantine/modals"; import { AddCameraModalContent } from "@/components/main/AddCameraModal"; +import { Outlet } from "@tanstack/react-router"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); @@ -63,7 +61,7 @@ export default function MainLayout() { - + {/* @@ -122,18 +120,18 @@ const Inspector = () => { ); }; -const RtspInput = () => { - return ( - <> - - - - } - /> - - ); -}; +// const RtspInput = () => { +// return ( +// <> +// +// +// +// } +// /> +// +// ); +// }; diff --git a/frontend/src/layout/MinimalLayout.tsx b/frontend/src/layout/MinimalLayout.tsx index 13ca3fe..ce5a5fc 100644 --- a/frontend/src/layout/MinimalLayout.tsx +++ b/frontend/src/layout/MinimalLayout.tsx @@ -1,7 +1,7 @@ import TitleDashboard from "@/components/titleDashboard"; import { AppShell } from "@mantine/core"; +import { Outlet } from "@tanstack/react-router"; import React from "react"; -import { Outlet } from "react-router-dom"; export function MinimalLayout({ children }: { children?: React.ReactNode }) { return ( diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 8aa4ad2..363355d 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,61 +1,40 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; import "@fontsource/poppins"; import "@fontsource-variable/karla"; import "@/index.css"; import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; import i18n from "@/lib/i18n"; -import { - createTheme, - MantineColorsTuple, - MantineProvider, -} from "@mantine/core"; -import { BrowserRouter } from "react-router-dom"; -import { AuthProvider } from "./context/AuthContext"; +import { MantineProvider } from "@mantine/core"; import { StreamSocketProvider } from "./context/CameraSocketContext"; import { ModalsProvider } from "@mantine/modals"; import { Notifications } from "@mantine/notifications"; import { I18nextProvider } from "react-i18next"; +import { appTheme } from "./theme"; +import { createRouter, RouterProvider } from "@tanstack/react-router"; -const container = document.getElementById("root") as ReactDOM.Container; +import { routeTree } from "./routeTree.gen"; -const myColor: MantineColorsTuple = [ - "#fff4e2", - "#ffe9cc", - "#ffd09c", - "#fdb766", - "#fca13a", - "#fb931d", - "#fc8c0c", - "#e17900", - "#c86a00", - "#ae5a00", -]; +const router = createRouter({ routeTree }); +// Register the router instance for type safety +declare module "@tanstack/react-router" { + interface Register { + router: typeof router; + } +} -const theme = createTheme({ - colors: { - myColor, - }, - fontFamily: "Poppins,Karla, sans-serif", - // headings: {fontFamily:"Poppins, sans-serif"}, - primaryColor: "myColor", -}); +const container = document.getElementById("root") as ReactDOM.Container; ReactDOM.createRoot(container).render( - + - - - - - - - + + + diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index e1072d7..0d8d2a6 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -11,7 +11,7 @@ import { TextInput, } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { useNavigate } from "react-router-dom"; +import { useNavigate } from "@tanstack/react-router"; export function LoginPage() { return ( @@ -75,7 +75,7 @@ function LoginCard() { component="button" type="button" variant="text" - onClick={() => navigate("/register")} + onClick={() => navigate({ to: "/register" })} size="xs" > Tidak memiliki akun? Register diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx index 03534a7..26731d3 100644 --- a/frontend/src/pages/RegisterPage.tsx +++ b/frontend/src/pages/RegisterPage.tsx @@ -11,7 +11,7 @@ import { TextInput, } from "@mantine/core"; import { useForm } from "@mantine/form"; -import { useNavigate } from "react-router-dom"; +import { useNavigate } from "@tanstack/react-router"; export function RegisterPage() { return ( @@ -100,7 +100,7 @@ function LoginCard() { component="button" type="button" variant="text" - onClick={() => navigate("/login")} + onClick={() => navigate({to: "/login"})} size="xs" > Sudah punya akun? Login diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index 7c3d5a8..eaf6a15 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -1,4 +1,3 @@ -import { ImageStreamViewer } from "@/components/ImageStreamViewer"; import { useStreamSocket } from "@/context/CameraSocketContext"; import {IconCamera, IconTable} from "@tabler/icons-react" import { diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts new file mode 100644 index 0000000..a03919b --- /dev/null +++ b/frontend/src/routeTree.gen.ts @@ -0,0 +1,173 @@ +/* prettier-ignore-start */ + +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file is auto-generated by TanStack Router + +// Import Routes + +import { Route as rootRoute } from './routes/__root' +import { Route as RegisterImport } from './routes/register' +import { Route as LoginImport } from './routes/login' +import { Route as AppImport } from './routes/app' +import { Route as IndexImport } from './routes/index' +import { Route as AppIndexImport } from './routes/app/index' +import { Route as AppUsersImport } from './routes/app/users' +import { Route as AppProfileImport } from './routes/app/profile' + +// Create/Update Routes + +const RegisterRoute = RegisterImport.update({ + path: '/register', + getParentRoute: () => rootRoute, +} as any) + +const LoginRoute = LoginImport.update({ + path: '/login', + getParentRoute: () => rootRoute, +} as any) + +const AppRoute = AppImport.update({ + path: '/app', + getParentRoute: () => rootRoute, +} as any) + +const IndexRoute = IndexImport.update({ + path: '/', + getParentRoute: () => rootRoute, +} as any) + +const AppIndexRoute = AppIndexImport.update({ + path: '/', + getParentRoute: () => AppRoute, +} as any) + +const AppUsersRoute = AppUsersImport.update({ + path: '/users', + getParentRoute: () => AppRoute, +} as any) + +const AppProfileRoute = AppProfileImport.update({ + path: '/profile', + getParentRoute: () => AppRoute, +} as any) + +// Populate the FileRoutesByPath interface + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexImport + parentRoute: typeof rootRoute + } + '/app': { + id: '/app' + path: '/app' + fullPath: '/app' + preLoaderRoute: typeof AppImport + parentRoute: typeof rootRoute + } + '/login': { + id: '/login' + path: '/login' + fullPath: '/login' + preLoaderRoute: typeof LoginImport + parentRoute: typeof rootRoute + } + '/register': { + id: '/register' + path: '/register' + fullPath: '/register' + preLoaderRoute: typeof RegisterImport + parentRoute: typeof rootRoute + } + '/app/profile': { + id: '/app/profile' + path: '/profile' + fullPath: '/app/profile' + preLoaderRoute: typeof AppProfileImport + parentRoute: typeof AppImport + } + '/app/users': { + id: '/app/users' + path: '/users' + fullPath: '/app/users' + preLoaderRoute: typeof AppUsersImport + parentRoute: typeof AppImport + } + '/app/': { + id: '/app/' + path: '/' + fullPath: '/app/' + preLoaderRoute: typeof AppIndexImport + parentRoute: typeof AppImport + } + } +} + +// Create and export the route tree + +export const routeTree = rootRoute.addChildren({ + IndexRoute, + AppRoute: AppRoute.addChildren({ + AppProfileRoute, + AppUsersRoute, + AppIndexRoute, + }), + LoginRoute, + RegisterRoute, +}) + +/* prettier-ignore-end */ + +/* ROUTE_MANIFEST_START +{ + "routes": { + "__root__": { + "filePath": "__root.tsx", + "children": [ + "/", + "/app", + "/login", + "/register" + ] + }, + "/": { + "filePath": "index.tsx" + }, + "/app": { + "filePath": "app.tsx", + "children": [ + "/app/profile", + "/app/users", + "/app/" + ] + }, + "/login": { + "filePath": "login.tsx" + }, + "/register": { + "filePath": "register.tsx" + }, + "/app/profile": { + "filePath": "app/profile.tsx", + "parent": "/app" + }, + "/app/users": { + "filePath": "app/users.tsx", + "parent": "/app" + }, + "/app/": { + "filePath": "app/index.tsx", + "parent": "/app" + } + } +} +ROUTE_MANIFEST_END */ diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx new file mode 100644 index 0000000..88906bb --- /dev/null +++ b/frontend/src/routes/__root.tsx @@ -0,0 +1,29 @@ +import { ErrorScreen } from "@/components/ui/ErrorScreen"; +import { Box, Button } from "@mantine/core"; +import { + createRootRoute, + Outlet, + useRouter, +} from "@tanstack/react-router"; +import { TanStackRouterDevtools } from "@tanstack/router-devtools"; +export const Route = createRootRoute({ + component: () => ( + <> + + + + ), + notFoundComponent: () => { + const router = useRouter(); + return ( + + router.history.back()}>Back + } + /> + + ); + }, +}); diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx new file mode 100644 index 0000000..333ae86 --- /dev/null +++ b/frontend/src/routes/app.tsx @@ -0,0 +1,24 @@ +import { ErrorScreen } from "@/components/ui/ErrorScreen"; +import MainLayout from "@/layout/MainLayout"; +import { Button, Center } from "@mantine/core"; +import { createFileRoute, useRouter } from "@tanstack/react-router"; + +export const Route = createFileRoute("/app")({ + component: () => ( + + ), + notFoundComponent: () => { + const router = useRouter() + return ( +
    + router.history.back()}>Back + } + /> + +
    + ) + } +}); diff --git a/frontend/src/routes/app/index.tsx b/frontend/src/routes/app/index.tsx new file mode 100644 index 0000000..adf788b --- /dev/null +++ b/frontend/src/routes/app/index.tsx @@ -0,0 +1,6 @@ +import LiveCamPage from '@/pages/app/LiveCamPage' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/app/')({ + component: () => +}) \ No newline at end of file diff --git a/frontend/src/routes/app/profile.tsx b/frontend/src/routes/app/profile.tsx new file mode 100644 index 0000000..3f438a2 --- /dev/null +++ b/frontend/src/routes/app/profile.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/app/profile')({ + component: () =>
    Hello /app/profile!
    +}) \ No newline at end of file diff --git a/frontend/src/routes/app/users.tsx b/frontend/src/routes/app/users.tsx new file mode 100644 index 0000000..25e8058 --- /dev/null +++ b/frontend/src/routes/app/users.tsx @@ -0,0 +1,6 @@ +import UsersPage from '@/pages/app/UsersPage' +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/app/users')({ + component: () => +}) \ No newline at end of file diff --git a/frontend/src/routes/index.tsx b/frontend/src/routes/index.tsx new file mode 100644 index 0000000..33b95c9 --- /dev/null +++ b/frontend/src/routes/index.tsx @@ -0,0 +1,5 @@ +import { LoginPage } from "@/pages/LoginPage" +import {createFileRoute} from "@tanstack/react-router" +export const Route = createFileRoute("/")({ + component: LoginPage +}) \ No newline at end of file diff --git a/frontend/src/routes/login.tsx b/frontend/src/routes/login.tsx new file mode 100644 index 0000000..2073eb5 --- /dev/null +++ b/frontend/src/routes/login.tsx @@ -0,0 +1,5 @@ +import { LoginPage } from "@/pages/LoginPage" +import {createFileRoute} from "@tanstack/react-router" +export const Route = createFileRoute("/login")({ + component: LoginPage +}) \ No newline at end of file diff --git a/frontend/src/routes/register.tsx b/frontend/src/routes/register.tsx new file mode 100644 index 0000000..47a9d84 --- /dev/null +++ b/frontend/src/routes/register.tsx @@ -0,0 +1,5 @@ +import { RegisterPage } from "@/pages/RegisterPage" +import {createFileRoute} from "@tanstack/react-router" +export const Route = createFileRoute("/register")({ + component: RegisterPage +}) \ No newline at end of file diff --git a/frontend/src/theme.tsx b/frontend/src/theme.tsx new file mode 100644 index 0000000..c456a91 --- /dev/null +++ b/frontend/src/theme.tsx @@ -0,0 +1,24 @@ +import { createTheme, MantineColorsTuple } from "@mantine/core"; + +const myColor: MantineColorsTuple = [ + "#fff4e2", + "#ffe9cc", + "#ffd09c", + "#fdb766", + "#fca13a", + "#fb931d", + "#fc8c0c", + "#e17900", + "#c86a00", + "#ae5a00", + ]; + + export const appTheme = createTheme({ + colors: { + myColor, + }, + fontFamily: "Poppins,Karla, sans-serif", + // headings: {fontFamily:"Poppins, sans-serif"}, + primaryColor: "myColor", + }); + \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 62f7471..a7df413 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,9 +1,11 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import path from "node:path"; +import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; + // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [TanStackRouterVite(), react()], resolve: { alias: { "@": path.resolve(__dirname, "./src"), From e2ec64f7d9bfa3765040c6058c99c53ba4a79562 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Thu, 5 Sep 2024 11:50:07 +0800 Subject: [PATCH 40/63] feat" rename icon to headericon --- frontend/src/components/{Icon.tsx => HeaderIcon.tsx} | 0 frontend/src/components/titleDashboard.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename frontend/src/components/{Icon.tsx => HeaderIcon.tsx} (100%) diff --git a/frontend/src/components/Icon.tsx b/frontend/src/components/HeaderIcon.tsx similarity index 100% rename from frontend/src/components/Icon.tsx rename to frontend/src/components/HeaderIcon.tsx diff --git a/frontend/src/components/titleDashboard.tsx b/frontend/src/components/titleDashboard.tsx index 1a9fc58..18536ee 100644 --- a/frontend/src/components/titleDashboard.tsx +++ b/frontend/src/components/titleDashboard.tsx @@ -7,7 +7,7 @@ import { useMantineColorScheme, } from "@mantine/core"; import { IconMoon, IconSun } from "@tabler/icons-react"; -import { AppIcon } from "./Icon"; +import { AppIcon } from "./HeaderIcon"; export default function TitleDashboard({ leftSection, From cb6760f38c88551baa7dcc9907f64923ed2f7752 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Thu, 5 Sep 2024 11:50:33 +0800 Subject: [PATCH 41/63] feat: add profile page --- .../src/components/sidebar/SidebarContent.tsx | 32 ++++++------------- frontend/src/components/sidebar/UserInfo.tsx | 8 +++-- frontend/src/pages/app/ProfilePage.tsx | 17 ++++++++++ frontend/src/routes/app/profile.tsx | 3 +- 4 files changed, 35 insertions(+), 25 deletions(-) create mode 100644 frontend/src/pages/app/ProfilePage.tsx diff --git a/frontend/src/components/sidebar/SidebarContent.tsx b/frontend/src/components/sidebar/SidebarContent.tsx index 9852a9e..601c2cd 100644 --- a/frontend/src/components/sidebar/SidebarContent.tsx +++ b/frontend/src/components/sidebar/SidebarContent.tsx @@ -1,19 +1,19 @@ import { Divider, NavLink } from "@mantine/core"; import { useState } from "react"; -import { IconDeviceDesktop, IconBook2, IconLogout } from "@tabler/icons-react"; +import { IconDeviceDesktop, IconUsers } from "@tabler/icons-react"; import { UserCardInfo } from "./UserInfo"; -import { useNavigate } from "@tanstack/react-router"; +import { useNavigate, useRouterState } from "@tanstack/react-router"; const menuLists = [ { - title: "Tampilan Langsung", + title: "Livecam", path: "/app", icon: , }, { - title: "Laporan", - path: "/app/laporan", - icon: , + title: "Pengguna", + path: "/app/users", + icon: , }, ]; @@ -22,9 +22,7 @@ const menuLists = [ //
    // //
    // Dashboard @@ -34,13 +32,14 @@ const menuLists = [ export default function SidebarContent() { const navigate = useNavigate(); + const router = useRouterState() const [active, setActive] = useState( menuLists.findIndex((item) => item.path == (location.pathname ?? "/app")) ); return ( <> - + navigate({to: "/app/profile"})} /> {menuLists.map((val, idx) => ( { navigate({to: val.path}); setActive(idx); }} /> ))} - } - onClick={() => { - console.log("logout"); - }} - /> ); } diff --git a/frontend/src/components/sidebar/UserInfo.tsx b/frontend/src/components/sidebar/UserInfo.tsx index 21dc4f7..c394322 100644 --- a/frontend/src/components/sidebar/UserInfo.tsx +++ b/frontend/src/components/sidebar/UserInfo.tsx @@ -2,9 +2,13 @@ import { Avatar, Group, Text, UnstyledButton } from "@mantine/core"; -export function UserCardInfo() { +export function UserCardInfo({active, onClick}:{active: boolean, onClick: () => void}) { return ( - {}}> +
    diff --git a/frontend/src/pages/app/ProfilePage.tsx b/frontend/src/pages/app/ProfilePage.tsx new file mode 100644 index 0000000..ecca5b5 --- /dev/null +++ b/frontend/src/pages/app/ProfilePage.tsx @@ -0,0 +1,17 @@ +import { Avatar, Button, Divider, Flex, Text, Title } from "@mantine/core"; +import { IconLogout } from "@tabler/icons-react"; + + +export function ProfilePage() { + return ( + + Profile + + Full Name + Full email + + + + + ) +} \ No newline at end of file diff --git a/frontend/src/routes/app/profile.tsx b/frontend/src/routes/app/profile.tsx index 3f438a2..84f072d 100644 --- a/frontend/src/routes/app/profile.tsx +++ b/frontend/src/routes/app/profile.tsx @@ -1,5 +1,6 @@ +import { ProfilePage } from '@/pages/app/ProfilePage' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/app/profile')({ - component: () =>
    Hello /app/profile!
    + component: () => }) \ No newline at end of file From 9a1d261ec8e1782e6e21387024c3a05a5dd50c8d Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Thu, 5 Sep 2024 14:45:34 +0800 Subject: [PATCH 42/63] refactor --- frontend/src/components/{sidebar => ui}/UserInfo.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename frontend/src/components/{sidebar => ui}/UserInfo.tsx (100%) diff --git a/frontend/src/components/sidebar/UserInfo.tsx b/frontend/src/components/ui/UserInfo.tsx similarity index 100% rename from frontend/src/components/sidebar/UserInfo.tsx rename to frontend/src/components/ui/UserInfo.tsx From 3655a93fb144f83488a3123cdb896352a4413c56 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Thu, 5 Sep 2024 14:46:47 +0800 Subject: [PATCH 43/63] feat: make aside inspector swappable in based on page --- .../layout/AsideCameraInspector.tsx | 50 ++++++ .../{sidebar => layout}/SidebarContent.tsx | 2 +- frontend/src/context/AsideContext.tsx | 37 +++++ frontend/src/layout/MainLayout.tsx | 142 ++++++------------ frontend/src/pages/app/LiveCamPage.jsx | 37 +++-- frontend/src/pages/app/ProfilePage.tsx | 6 + frontend/src/routes/app.tsx | 12 +- 7 files changed, 161 insertions(+), 125 deletions(-) create mode 100644 frontend/src/components/layout/AsideCameraInspector.tsx rename frontend/src/components/{sidebar => layout}/SidebarContent.tsx (96%) create mode 100644 frontend/src/context/AsideContext.tsx diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/AsideCameraInspector.tsx new file mode 100644 index 0000000..fe7cfe5 --- /dev/null +++ b/frontend/src/components/layout/AsideCameraInspector.tsx @@ -0,0 +1,50 @@ +import { Accordion, Divider, Flex, Button, Card, Group, ActionIcon, Text } from "@mantine/core"; +import { modals } from "@mantine/modals"; +import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; +import { AddCameraModalContent } from "../main/AddCameraModal"; + +export function AsideCameraInspector() { + return ( +
    + + + Metadata + + URL: rtsp://localhost:8888/a301.live + + + + + + Daftar Kamera + + + + + + Camera 2 + + + + + + + +
    + ); + } \ No newline at end of file diff --git a/frontend/src/components/sidebar/SidebarContent.tsx b/frontend/src/components/layout/SidebarContent.tsx similarity index 96% rename from frontend/src/components/sidebar/SidebarContent.tsx rename to frontend/src/components/layout/SidebarContent.tsx index 601c2cd..b21d365 100644 --- a/frontend/src/components/sidebar/SidebarContent.tsx +++ b/frontend/src/components/layout/SidebarContent.tsx @@ -1,7 +1,7 @@ import { Divider, NavLink } from "@mantine/core"; import { useState } from "react"; import { IconDeviceDesktop, IconUsers } from "@tabler/icons-react"; -import { UserCardInfo } from "./UserInfo"; +import { UserCardInfo } from "@/components/ui/UserInfo"; import { useNavigate, useRouterState } from "@tanstack/react-router"; const menuLists = [ diff --git a/frontend/src/context/AsideContext.tsx b/frontend/src/context/AsideContext.tsx new file mode 100644 index 0000000..4f8cae6 --- /dev/null +++ b/frontend/src/context/AsideContext.tsx @@ -0,0 +1,37 @@ +import { Center, Text } from "@mantine/core"; +import React, { createContext, useContext, useState } from "react"; + +interface IAsideContext { + asideComponent: React.ReactNode; + setAsideComponent: (component?: React.ReactNode) => void; +} + +export const asideContextNullable = createContext(undefined); + +export const useAside = () => + useContext(asideContextNullable) as IAsideContext; + +export function AsideContextProvider({ children }: { children: JSX.Element }) { + const [aside, setAside] = useState( +
    + Empty +
    + ); + function setAsideComponent(component?: React.ReactNode) { + setAside( + component ?? ( +
    + Empty +
    + ) + ); + } + return ( + + {children} + + ); +} + diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index 7f80a5c..e1c49ad 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -1,124 +1,66 @@ -import SidebarContent from "@/components/sidebar/SidebarContent"; +import SidebarContent from "@/components/layout/SidebarContent"; import { - Accordion, - ActionIcon, AppShell, Burger, - Button, - Card, - Divider, - Flex, - Group, - Text, } from "@mantine/core"; import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; -import { - IconDotsVertical, - IconPlus, -} from "@tabler/icons-react"; -import { modals } from "@mantine/modals"; -import { AddCameraModalContent } from "@/components/main/AddCameraModal"; import { Outlet } from "@tanstack/react-router"; +import { useAside } from "@/context/AsideContext"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); const [asideMobileOpened] = useDisclosure(); + const {asideComponent} = useAside() return ( - - - - - - - - - } - /> - - - - - - - - {/* + breakpoint: "md", + collapsed: { mobile: !mobileOpened }, + }} + header={{ height: 100 }} + aside={{ + width: 300, + breakpoint: "sm", + collapsed: { mobile: !asideMobileOpened }, + }} + // layout="alt" + > + + + + + + + + } + /> + + + {asideComponent} + + + + + {/*

    this is footer

    */} -
    + ); } -const Inspector = () => { - return ( -
    - - - Metadata - - URL: rtsp://localhost:8888/a301.live - - - - - Daftar Kamera - - - - - - Camera 2 - - - - - - - -
    - ); -}; // const RtspInput = () => { // return ( diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index eaf6a15..8ee3793 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -1,26 +1,15 @@ import { useStreamSocket } from "@/context/CameraSocketContext"; -import {IconCamera, IconTable} from "@tabler/icons-react" -import { - ActionIcon, - Card, - Flex, - Table, - Tooltip, -} from "@mantine/core"; -import { useState } from "react"; +import { IconCamera, IconTable } from "@tabler/icons-react"; +import { ActionIcon, Card, Flex, Table, Tooltip } from "@mantine/core"; +import { useEffect, useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/model/dataset"; import { LiveCamComponent } from "@/components/LiveCamComponent"; +import { useAside } from "@/context/AsideContext"; +import { AsideCameraInspector } from "@/components/layout/AsideCameraInspector"; -const LiveCamPage = () => { - return ( - - - - ); -}; -const MainView = () => { +function MainView () { const stream = useStreamSocket(); const [showTable, setShowTable] = useState(false); return ( @@ -49,7 +38,7 @@ const MainView = () => { onClick={() => setShowTable(true)} aria-label="Table View" > - + @@ -70,4 +59,14 @@ const MainView = () => { ); }; -export default LiveCamPage; +export default function LiveCamPage () { + const { setAsideComponent } = useAside(); + useEffect(() => { + setAsideComponent(AsideCameraInspector); + }, []); + return ( + + + + ); +}; diff --git a/frontend/src/pages/app/ProfilePage.tsx b/frontend/src/pages/app/ProfilePage.tsx index ecca5b5..4187028 100644 --- a/frontend/src/pages/app/ProfilePage.tsx +++ b/frontend/src/pages/app/ProfilePage.tsx @@ -1,8 +1,14 @@ +import { useAside } from "@/context/AsideContext"; import { Avatar, Button, Divider, Flex, Text, Title } from "@mantine/core"; import { IconLogout } from "@tabler/icons-react"; +import { useEffect } from "react"; export function ProfilePage() { + const { setAsideComponent } = useAside(); + useEffect(() => { + setAsideComponent(); + }, []); return ( Profile diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index 333ae86..fe7eac8 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -1,14 +1,17 @@ import { ErrorScreen } from "@/components/ui/ErrorScreen"; +import { AsideContextProvider } from "@/context/AsideContext"; import MainLayout from "@/layout/MainLayout"; import { Button, Center } from "@mantine/core"; import { createFileRoute, useRouter } from "@tanstack/react-router"; export const Route = createFileRoute("/app")({ component: () => ( - + + + ), notFoundComponent: () => { - const router = useRouter() + const router = useRouter(); return (
    router.history.back()}>Back } /> -
    - ) - } + ); + }, }); From 69bc7d3f7b8637188a79493f21bb2342c43f5f93 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Thu, 5 Sep 2024 14:47:12 +0800 Subject: [PATCH 44/63] feat: setup interface --- frontend/src/services/GrpcService.tsx | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 frontend/src/services/GrpcService.tsx diff --git a/frontend/src/services/GrpcService.tsx b/frontend/src/services/GrpcService.tsx new file mode 100644 index 0000000..d0f0d63 --- /dev/null +++ b/frontend/src/services/GrpcService.tsx @@ -0,0 +1,28 @@ +interface IGrpcService { + login(email: string, password: string): string | null | undefined; + logout(): void; + + // authenticated user only + changeCamChannel(id: string): void; + + adminAddCamera(name: string, url: string): void; + adminEditCamera(id: string, name?: string, url?: string):void + + adminAddUser( + email: string, + name: string, + password: string + ): string | null | undefined; + adminEditUser( + id: string, + email?: string, + name?: string, + password?: string + ): string | null | undefined; + + adminDeleteUser(id: string, email: string): void; +} + +class GrpcServices { + constructor() {} +} From f34ac8ae0e837d2eaf64b0d1494e98981937e028 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 12:15:39 +0800 Subject: [PATCH 45/63] feat: update ui --- frontend/src/components/LiveCamComponent.tsx | 2 +- frontend/src/main.tsx | 2 +- frontend/src/pages/LoginPage.tsx | 4 +- frontend/src/pages/app/ProfilePage.tsx | 42 ++++++++++++-------- frontend/src/pages/app/UsersPage.tsx | 10 ++++- frontend/src/routes/__root.tsx | 2 +- 6 files changed, 39 insertions(+), 23 deletions(-) diff --git a/frontend/src/components/LiveCamComponent.tsx b/frontend/src/components/LiveCamComponent.tsx index 0ff0376..78cb625 100644 --- a/frontend/src/components/LiveCamComponent.tsx +++ b/frontend/src/components/LiveCamComponent.tsx @@ -1,4 +1,4 @@ -import { Affix, Button, Flex } from "@mantine/core"; +import { Button, Flex } from "@mantine/core"; import { ImageStreamViewer } from "./ImageStreamViewer"; import { useState } from "react"; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 363355d..ff5f546 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -33,7 +33,7 @@ ReactDOM.createRoot(container).render( - + diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 0d8d2a6..567780a 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -71,7 +71,7 @@ function LoginCard() { /> - Tidak memiliki akun? Register - + */} diff --git a/frontend/src/pages/app/ProfilePage.tsx b/frontend/src/pages/app/ProfilePage.tsx index 4187028..6328ad6 100644 --- a/frontend/src/pages/app/ProfilePage.tsx +++ b/frontend/src/pages/app/ProfilePage.tsx @@ -2,22 +2,32 @@ import { useAside } from "@/context/AsideContext"; import { Avatar, Button, Divider, Flex, Text, Title } from "@mantine/core"; import { IconLogout } from "@tabler/icons-react"; import { useEffect } from "react"; - +import { useNavigate } from "@tanstack/react-router"; export function ProfilePage() { - const { setAsideComponent } = useAside(); - useEffect(() => { - setAsideComponent(); - }, []); - return ( - - Profile - - Full Name - Full email - + const navigate = useNavigate(); + const { setAsideComponent } = useAside(); + useEffect(() => { + setAsideComponent(); + }, []); + return ( + + Profile + + Full Name + Full email + - - - ) -} \ No newline at end of file + + + ); +} diff --git a/frontend/src/pages/app/UsersPage.tsx b/frontend/src/pages/app/UsersPage.tsx index 19b57b3..a076fbe 100644 --- a/frontend/src/pages/app/UsersPage.tsx +++ b/frontend/src/pages/app/UsersPage.tsx @@ -1,7 +1,13 @@ import { ActionIcon, Center, Flex, Table, Text, Title } from "@mantine/core"; import { IconEdit } from "@tabler/icons-react"; -const sample:UserData[] = []; +const sample:UserData[] = [ + { + id:"3", + username: "guest 1", + email: "affinitas89@gmail.com" + } +]; type UserData = { id: string; @@ -24,7 +30,7 @@ function UserLists(props: UserListProps) { - {props.data.length > 1 ? ( + {props.data.length >= 1 ? ( props.data.map((element) => { return ( diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index 88906bb..de37bda 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -10,7 +10,7 @@ export const Route = createRootRoute({ component: () => ( <> - + {/* */} ), notFoundComponent: () => { From 0b58dd667e22b07c570e3bd3864f46e238eae9f9 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 12:21:54 +0800 Subject: [PATCH 46/63] feat: move ui --- frontend/src/components/ReportTable.tsx | 2 +- .../{ => components}/context/AsideContext.tsx | 0 .../{ => components}/context/AuthContext.tsx | 0 .../context/CameraSocketContext.tsx | 0 .../{ => components}/context/GrpcContext.tsx | 0 .../src/{ => components}/model/dataset.ts | 0 frontend/src/layout/MainLayout.tsx | 2 +- frontend/src/lib/i18n.ts | 25 ----------------- frontend/src/locales/en.json | 4 --- frontend/src/locales/id.json | 14 ---------- frontend/src/main.tsx | 2 +- frontend/src/pages/app/LiveCamPage.jsx | 6 ++-- frontend/src/pages/app/ProfilePage.tsx | 2 +- frontend/src/pages/app/report.jsx | 2 +- frontend/src/routes/app.tsx | 2 +- frontend/src/services/GrpcService.tsx | 28 ------------------- 16 files changed, 9 insertions(+), 80 deletions(-) rename frontend/src/{ => components}/context/AsideContext.tsx (100%) rename frontend/src/{ => components}/context/AuthContext.tsx (100%) rename frontend/src/{ => components}/context/CameraSocketContext.tsx (100%) rename frontend/src/{ => components}/context/GrpcContext.tsx (100%) rename frontend/src/{ => components}/model/dataset.ts (100%) delete mode 100644 frontend/src/lib/i18n.ts delete mode 100644 frontend/src/locales/en.json delete mode 100644 frontend/src/locales/id.json delete mode 100644 frontend/src/services/GrpcService.tsx diff --git a/frontend/src/components/ReportTable.tsx b/frontend/src/components/ReportTable.tsx index 06ab83f..16a81ae 100644 --- a/frontend/src/components/ReportTable.tsx +++ b/frontend/src/components/ReportTable.tsx @@ -1,4 +1,4 @@ -import { DetectionData } from "@/model/dataset"; +import { DetectionData } from "@/components/model/dataset"; import { Table } from "@mantine/core"; export function ReportTable({ data }: { data: DetectionData[] }) { diff --git a/frontend/src/context/AsideContext.tsx b/frontend/src/components/context/AsideContext.tsx similarity index 100% rename from frontend/src/context/AsideContext.tsx rename to frontend/src/components/context/AsideContext.tsx diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/components/context/AuthContext.tsx similarity index 100% rename from frontend/src/context/AuthContext.tsx rename to frontend/src/components/context/AuthContext.tsx diff --git a/frontend/src/context/CameraSocketContext.tsx b/frontend/src/components/context/CameraSocketContext.tsx similarity index 100% rename from frontend/src/context/CameraSocketContext.tsx rename to frontend/src/components/context/CameraSocketContext.tsx diff --git a/frontend/src/context/GrpcContext.tsx b/frontend/src/components/context/GrpcContext.tsx similarity index 100% rename from frontend/src/context/GrpcContext.tsx rename to frontend/src/components/context/GrpcContext.tsx diff --git a/frontend/src/model/dataset.ts b/frontend/src/components/model/dataset.ts similarity index 100% rename from frontend/src/model/dataset.ts rename to frontend/src/components/model/dataset.ts diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/layout/MainLayout.tsx index e1c49ad..04efa36 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/layout/MainLayout.tsx @@ -6,7 +6,7 @@ import { import TitleDashboard from "@/components/titleDashboard"; import { useDisclosure } from "@mantine/hooks"; import { Outlet } from "@tanstack/react-router"; -import { useAside } from "@/context/AsideContext"; +import { useAside } from "@/components/context/AsideContext"; export default function MainLayout() { const [mobileOpened, { toggle: toggleMobile }] = useDisclosure(); diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts deleted file mode 100644 index 6a057aa..0000000 --- a/frontend/src/lib/i18n.ts +++ /dev/null @@ -1,25 +0,0 @@ -import i18n from "i18next"; -import { initReactI18next } from "react-i18next"; - -import translationID from "@/locales/id.json"; -import translationEN from "@/locales/en.json"; - -const locales = { - id: { - ns1: translationID, - }, - en: { - ns1: translationEN, - }, -}; - -i18n.use(initReactI18next).init({ - lng: "id", - resources: locales, - defaultNS: "ns1", - ns: ["ns1", "ns2"], - interpolation: { - escapeValue: false, - }, -}); -export default i18n; diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json deleted file mode 100644 index 03e1a15..0000000 --- a/frontend/src/locales/en.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "login": "Login", - "register": "Register" -} \ No newline at end of file diff --git a/frontend/src/locales/id.json b/frontend/src/locales/id.json deleted file mode 100644 index b254b4e..0000000 --- a/frontend/src/locales/id.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "welcome": { - "short": "Selamat Datang di Bye Cheating!" - }, - "login": "Masuk", - "to-login-suggestion": "Sudah punya akun, Masuk", - "register": "Daftar", - "to-register-suggestion": "Tidak punya akun, Daftar!", - "textfield": { - "email": "Email", - "password": "Password", - "name": "Nama" - } -} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index ff5f546..63d73ec 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -7,7 +7,7 @@ import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; import i18n from "@/lib/i18n"; import { MantineProvider } from "@mantine/core"; -import { StreamSocketProvider } from "./context/CameraSocketContext"; +import { StreamSocketProvider } from "./components/context/CameraSocketContext"; import { ModalsProvider } from "@mantine/modals"; import { Notifications } from "@mantine/notifications"; import { I18nextProvider } from "react-i18next"; diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index 8ee3793..ba91533 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -1,11 +1,11 @@ -import { useStreamSocket } from "@/context/CameraSocketContext"; +import { useStreamSocket } from "@/components/context/CameraSocketContext"; import { IconCamera, IconTable } from "@tabler/icons-react"; import { ActionIcon, Card, Flex, Table, Tooltip } from "@mantine/core"; import { useEffect, useState } from "react"; import { ReportTable } from "@/components/ReportTable"; -import { sampleDataCCTV } from "@/model/dataset"; +import { sampleDataCCTV } from "@/components/model/dataset"; import { LiveCamComponent } from "@/components/LiveCamComponent"; -import { useAside } from "@/context/AsideContext"; +import { useAside } from "@/components/context/AsideContext"; import { AsideCameraInspector } from "@/components/layout/AsideCameraInspector"; diff --git a/frontend/src/pages/app/ProfilePage.tsx b/frontend/src/pages/app/ProfilePage.tsx index 6328ad6..30ba184 100644 --- a/frontend/src/pages/app/ProfilePage.tsx +++ b/frontend/src/pages/app/ProfilePage.tsx @@ -1,4 +1,4 @@ -import { useAside } from "@/context/AsideContext"; +import { useAside } from "@/components/context/AsideContext"; import { Avatar, Button, Divider, Flex, Text, Title } from "@mantine/core"; import { IconLogout } from "@tabler/icons-react"; import { useEffect } from "react"; diff --git a/frontend/src/pages/app/report.jsx b/frontend/src/pages/app/report.jsx index 8e5ac10..91ce237 100644 --- a/frontend/src/pages/app/report.jsx +++ b/frontend/src/pages/app/report.jsx @@ -1,6 +1,6 @@ import { Divider, Table } from "@mantine/core"; import { ReportTable } from "@/components/ReportTable"; -import { sampleDataCCTV } from "@/model/dataset"; +import { sampleDataCCTV } from "@/components/model/dataset"; export default function ReportPage() { return ( diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index fe7eac8..375ebde 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -1,5 +1,5 @@ import { ErrorScreen } from "@/components/ui/ErrorScreen"; -import { AsideContextProvider } from "@/context/AsideContext"; +import { AsideContextProvider } from "@/components/context/AsideContext"; import MainLayout from "@/layout/MainLayout"; import { Button, Center } from "@mantine/core"; import { createFileRoute, useRouter } from "@tanstack/react-router"; diff --git a/frontend/src/services/GrpcService.tsx b/frontend/src/services/GrpcService.tsx deleted file mode 100644 index d0f0d63..0000000 --- a/frontend/src/services/GrpcService.tsx +++ /dev/null @@ -1,28 +0,0 @@ -interface IGrpcService { - login(email: string, password: string): string | null | undefined; - logout(): void; - - // authenticated user only - changeCamChannel(id: string): void; - - adminAddCamera(name: string, url: string): void; - adminEditCamera(id: string, name?: string, url?: string):void - - adminAddUser( - email: string, - name: string, - password: string - ): string | null | undefined; - adminEditUser( - id: string, - email?: string, - name?: string, - password?: string - ): string | null | undefined; - - adminDeleteUser(id: string, email: string): void; -} - -class GrpcServices { - constructor() {} -} From 6ac66a902061f7fccf3a70eb9e7f7eba31eef979 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 12:23:52 +0800 Subject: [PATCH 47/63] cleanup --- frontend/src/main.tsx | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 63d73ec..95d50ff 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -5,12 +5,10 @@ import "@fontsource-variable/karla"; import "@/index.css"; import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; -import i18n from "@/lib/i18n"; import { MantineProvider } from "@mantine/core"; import { StreamSocketProvider } from "./components/context/CameraSocketContext"; import { ModalsProvider } from "@mantine/modals"; import { Notifications } from "@mantine/notifications"; -import { I18nextProvider } from "react-i18next"; import { appTheme } from "./theme"; import { createRouter, RouterProvider } from "@tanstack/react-router"; @@ -28,15 +26,13 @@ const container = document.getElementById("root") as ReactDOM.Container; ReactDOM.createRoot(container).render( - - - - - - - - - - + + + + + + + + ); From 71f3909b4972c803c5ad79516fb391935e7b9a57 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 12:29:58 +0800 Subject: [PATCH 48/63] remove register --- frontend/src/routes/register.tsx | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 frontend/src/routes/register.tsx diff --git a/frontend/src/routes/register.tsx b/frontend/src/routes/register.tsx deleted file mode 100644 index 47a9d84..0000000 --- a/frontend/src/routes/register.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { RegisterPage } from "@/pages/RegisterPage" -import {createFileRoute} from "@tanstack/react-router" -export const Route = createFileRoute("/register")({ - component: RegisterPage -}) \ No newline at end of file From 67d118555d45bce832c0fc7945e44b166dc0fe52 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 12:29:58 +0800 Subject: [PATCH 49/63] remove register --- frontend/src/pages/RegisterPage.tsx | 113 ---------------------------- frontend/src/routes/register.tsx | 5 -- 2 files changed, 118 deletions(-) delete mode 100644 frontend/src/pages/RegisterPage.tsx delete mode 100644 frontend/src/routes/register.tsx diff --git a/frontend/src/pages/RegisterPage.tsx b/frontend/src/pages/RegisterPage.tsx deleted file mode 100644 index 26731d3..0000000 --- a/frontend/src/pages/RegisterPage.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import { MinimalLayout } from "@/layout/MinimalLayout"; -import { - Anchor, - Button, - Divider, - Text, - Group, - Paper, - PasswordInput, - Stack, - TextInput, -} from "@mantine/core"; -import { useForm } from "@mantine/form"; -import { useNavigate } from "@tanstack/react-router"; - -export function RegisterPage() { - return ( - -
    - -
    -
    - ); -} - -function LoginCard() { - const navigate = useNavigate(); - - const form = useForm({ - initialValues: { - email: "", - name: "", - password: "", - repeatedPassword: "", - }, - validate: { - email: (value) => (/^\S+@\S+$/.test(value) ? null : "Invalid email"), - name: (value) => (/^\S+$/.test(value) ? null : "Field is required"), - password: (value) => - /^(?=.*[A-Z])[A-Za-z\d]{8,}$/.test(value) - ? null - : "At least 8 chars and contain at least 1 uppercase", - repeatedPassword: (value,e) => - value == e.password ? null : "Password harus sama", - }, - }); - - return ( - - - Selamat Datang di Bye Cheating! - - - -
    {})}> - - - form.setFieldValue("email", event.currentTarget.value) - } - value={form.values.email} - error={form.errors.email} - /> - { - form.setFieldValue("name", e.currentTarget.value); - }} - value={form.values.name} - error={form.errors.name} - /> - - form.setFieldValue("password", e.currentTarget.value) - } - value={form.values.password} - error={form.errors.password} - /> - - form.setFieldValue("repeatedPassword", e.currentTarget.value) - } - value={form.values.repeatedPassword} - error={form.errors.repeatedPassword} - /> - - - navigate({to: "/login"})} - size="xs" - > - Sudah punya akun? Login - - - -
    -
    - ); -} diff --git a/frontend/src/routes/register.tsx b/frontend/src/routes/register.tsx deleted file mode 100644 index 47a9d84..0000000 --- a/frontend/src/routes/register.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { RegisterPage } from "@/pages/RegisterPage" -import {createFileRoute} from "@tanstack/react-router" -export const Route = createFileRoute("/register")({ - component: RegisterPage -}) \ No newline at end of file From ae9798a7f8e8bf9957d748c330ef0646644b0cfa Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 22:24:20 +0800 Subject: [PATCH 50/63] feat: create user form --- frontend/src/pages/app/UsersPage.tsx | 75 ++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/app/UsersPage.tsx b/frontend/src/pages/app/UsersPage.tsx index a076fbe..88ac741 100644 --- a/frontend/src/pages/app/UsersPage.tsx +++ b/frontend/src/pages/app/UsersPage.tsx @@ -1,12 +1,26 @@ -import { ActionIcon, Center, Flex, Table, Text, Title } from "@mantine/core"; +import { useAside } from "@/components/context/AsideContext"; +import { + ActionIcon, + Box, + Button, + Center, + Flex, + Stack, + Table, + Text, + TextInput, + Title, +} from "@mantine/core"; +import { useForm } from "@mantine/form"; import { IconEdit } from "@tabler/icons-react"; +import { useEffect } from "react"; -const sample:UserData[] = [ +const sample: UserData[] = [ { - id:"3", + id: "3", username: "guest 1", - email: "affinitas89@gmail.com" - } + email: "affinitas89@gmail.com", + }, ]; type UserData = { @@ -54,7 +68,58 @@ function UserLists(props: UserListProps) { ); } +function UserFormInspector(): JSX.Element { + const form = useForm({ + mode: "controlled", + initialValues: { + email: "", + name: "", + password: "", + avatar: "", + }, + validate: { + email: (val) => (/^\S+@\S+$/.test(val) ? null : "Invalid email"), + password: (val) => (val.length > 7 ? null : "Invalid password"), + name: (val) => (val.length > 0 ? null : "Name must not be empty"), + }, + }); + return ( + + User Form +
    console.log(val))}> + + + + + + +
    + ); +} + export default function UsersPage() { + const { setAsideComponent } = useAside(); + useEffect(() => { + setAsideComponent(); + }, []); return ( <> From c11be2bd3808f13373a73e329de617abadd3c414 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Sun, 29 Sep 2024 22:24:40 +0800 Subject: [PATCH 51/63] temp: remove ws implementation --- .../context/CameraSocketContext.tsx | 73 ++++--------------- 1 file changed, 13 insertions(+), 60 deletions(-) diff --git a/frontend/src/components/context/CameraSocketContext.tsx b/frontend/src/components/context/CameraSocketContext.tsx index ff0a248..850ed0a 100644 --- a/frontend/src/components/context/CameraSocketContext.tsx +++ b/frontend/src/components/context/CameraSocketContext.tsx @@ -1,7 +1,9 @@ +import { time } from "console"; +import { Timeout } from "node_modules/@tanstack/react-router/dist/esm/utils"; import { createContext, useContext, useEffect, useRef, useState } from "react"; import { Socket } from "socket.io"; import { io } from "socket.io-client"; - +const BASE_URL = "http://localhost:7000" /** interface of `StreamSocketContext` */ @@ -11,12 +13,6 @@ export interface IStreamSocketContext { */ base64data: string | undefined; - /** - * change websocket url, changing this won't stop the current running websocket. - * @param url - websocket url, ex: `ws://localhost/v1/test` - * @returns - */ - setWebsocketURL: (url: string) => void; /** * start new connection to websocket source, and don't create new instance if existing instance is active. * @@ -32,14 +28,14 @@ export interface IStreamSocketContext { } const StreamSocketContext = createContext( - undefined, + undefined ); export const useStreamSocket = () => { const context = useContext(StreamSocketContext); if (context === undefined) { throw new Error( - "useStreamSocket must be used inside of `` component", + "useStreamSocket must be used inside of `` component" ); } return context as IStreamSocketContext; @@ -51,66 +47,23 @@ export const useStreamSocket = () => { */ export function StreamSocketProvider({ children }: { children: JSX.Element }) { const initialized = useRef(false); - const [currentUrl, setCurrentUrl] = useState("http://localhost:7000"); - const [wsInstance, setWsInstance] = useState( - undefined, - ); - - const [socketInstance, setSocketInstance] = useState( - undefined, - ); + // const [currentUrl, setCurrentUrl] = useState(BASE_URL); + const [mytimer, setMytimer] = useState(undefined); const [base64Stream, setBase64Stream] = useState(""); const data: IStreamSocketContext = { base64data: base64Stream, - setWebsocketURL(url) { - setCurrentUrl(url); - }, start() { - if (socketInstance) { - return; - } - - setSocketInstance((prevVal) => { - if (prevVal) { - return prevVal; - } - const socket = io(currentUrl); - socket.on("connect", () => { - console.log(`socket established: ${socket.id}`); + const timer = setInterval(() => { + fetch(`${BASE_URL}/camera`).then((value) => { + setBase64Stream(value.body as unknown as string); }); - socket.on("disconnect", () => { - console.log(`socket closed: ${socket.id}`); - }); - socket.on("data", (val) => { - console.log(`data: ${val}`); - setBase64Stream(val) - }); - return socket as unknown as Socket; - }); - - // if (wsInstance) { - // this.stop(); - // } - // const ws = new WebSocket(currentUrl); - // ws.onopen = (ev) => { - // console.log(`connection established`) - // } - // ws.onmessage = (ev) => { - // setBase64Stream(ev.data); - // }; - // ws.onclose = (ev) => { - // console.log("WS: close from"); - // ev.stopPropagation(); - // }; - // setWsInstance(ws); + }, 500); + setMytimer(timer); }, stop() { - wsInstance?.close(); - socketInstance?.disconnect(true); - setWsInstance(undefined); - setSocketInstance(undefined); + clearInterval(mytimer) }, }; From 2226770a865853f2d9991856e58f7192b39b3128 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 4 Oct 2024 14:21:49 +0800 Subject: [PATCH 52/63] feat: change GRPC to ImageStreamContext --- .../src/components/context/GrpcContext.tsx | 8 ---- .../components/context/ImageStreamContext.tsx | 41 +++++++++++++++++++ 2 files changed, 41 insertions(+), 8 deletions(-) delete mode 100644 frontend/src/components/context/GrpcContext.tsx create mode 100644 frontend/src/components/context/ImageStreamContext.tsx diff --git a/frontend/src/components/context/GrpcContext.tsx b/frontend/src/components/context/GrpcContext.tsx deleted file mode 100644 index 56bd1e5..0000000 --- a/frontend/src/components/context/GrpcContext.tsx +++ /dev/null @@ -1,8 +0,0 @@ - - -export interface IGrpcContext { - setStreamCollector:(listener: (data: string) => void) => void; - switchSteamChannel: (streamId: string) => void; - startStream : () => void; - stopStream: () => void; -} \ No newline at end of file diff --git a/frontend/src/components/context/ImageStreamContext.tsx b/frontend/src/components/context/ImageStreamContext.tsx new file mode 100644 index 0000000..7b6c222 --- /dev/null +++ b/frontend/src/components/context/ImageStreamContext.tsx @@ -0,0 +1,41 @@ +import { createContext, useState } from "react"; + +export interface IImageStreamContext { + camName: string; + rawUrl: string; + mlUrl: string; + setStreamUrl: (name: string, url: string) => void; +} + +const imageStreamContext = createContext( + undefined +); + +export function ImageStreamContext({ + children, +}: { + children: React.ReactNode; +}) { + const [rawUrl, setRawUrl] = useState(""); + const [camName, setCamName] = useState(""); + const [mlUrl, setMlUrl] = useState(""); + + function setStreamUrl(name: string, url: string) { + setRawUrl(url); + setMlUrl(url); + setCamName(name); + } + + return ( + + {children} + + ); +} From 4fdbf670a23249e4258d74a83a05aac737aef85a Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Tue, 8 Oct 2024 09:19:46 +0800 Subject: [PATCH 53/63] feat: add camera list extension --- .../layout/AsideCameraInspector.tsx | 134 ++++++++++++------ .../src/components/main/AddCameraModal.tsx | 11 +- frontend/src/pages/app/LiveCamPage.jsx | 2 +- 3 files changed, 98 insertions(+), 49 deletions(-) diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/AsideCameraInspector.tsx index fe7cfe5..adb4fd7 100644 --- a/frontend/src/components/layout/AsideCameraInspector.tsx +++ b/frontend/src/components/layout/AsideCameraInspector.tsx @@ -1,50 +1,94 @@ -import { Accordion, Divider, Flex, Button, Card, Group, ActionIcon, Text } from "@mantine/core"; +import { + Accordion, + Divider, + Flex, + Button, + Card, + Group, + ActionIcon, + Text, + Center, +} from "@mantine/core"; import { modals } from "@mantine/modals"; import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; import { AddCameraModalContent } from "../main/AddCameraModal"; +import { useState } from "react"; + +interface CameraListItem { + name: string; + url: string; +} + +function CameraItem({ name, url }: { name: string; url: string }) { + return ( + + + + {name} + + + + + + + ); +} export function AsideCameraInspector() { - return ( -
    - - - Metadata - - URL: rtsp://localhost:8888/a301.live - - - - - - Daftar Kamera - - - - - - Camera 2 - - - - - - - -
    - ); - } \ No newline at end of file + const [cameras, setCameras] = useState([]); + return ( +
    + + + Metadata + + URL: rtsp://localhost:8888/a301.live + + + + + + Daftar Kamera + + + {cameras.length > 0 ? ( + cameras.map((val, i) => ( + + + + {val.name} + + + + + + + )) + ) : ( +
    kamera kosong
    + )} +
    +
    + ); +} diff --git a/frontend/src/components/main/AddCameraModal.tsx b/frontend/src/components/main/AddCameraModal.tsx index 6e301af..fac1a04 100644 --- a/frontend/src/components/main/AddCameraModal.tsx +++ b/frontend/src/components/main/AddCameraModal.tsx @@ -1,23 +1,27 @@ import { Button, Modal, Stack, TextInput } from "@mantine/core"; import { modals } from "@mantine/modals"; +import { useState } from "react"; interface AddCameraModalProps { opened: boolean; onClose: () => void; } interface AddCameraModalContentProps { - onSubmit: () => void; + onSubmit: (name: string, url: string) => void; } export function AddCameraModalContent(props: AddCameraModalContentProps) { + const [name, setName] = useState("") + const [url, setUrl] = useState("") return (
    { e.preventDefault(); - props.onSubmit(); + props.onSubmit(name, url); }} > - + setName(e.target.value)} placeholder="Tambahkan nama kamera" /> + setUrl(e.target.value)} placeholder="RTSP URL" />
    @@ -32,3 +36,4 @@ export function AddCameraModal(props: AddCameraModalProps) { ); } // export const AddCameraModal.Content: JSX.Element= AddCameraModalContent + \ No newline at end of file diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index ba91533..ec20dd5 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -62,7 +62,7 @@ function MainView () { export default function LiveCamPage () { const { setAsideComponent } = useAside(); useEffect(() => { - setAsideComponent(AsideCameraInspector); + setAsideComponent(); }, []); return ( From 9231b602e36a73e2020f0e58b838ab55c25ca785 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 11 Oct 2024 00:21:39 +0800 Subject: [PATCH 54/63] feat: create separation at camera list feature --- .../layout/AsideCameraInspector.tsx | 86 ++++++++++++++----- .../src/components/main/AddCameraModal.tsx | 28 ++++-- frontend/src/hooks/useCameraManagement.tsx | 50 +++++++++++ frontend/src/main.tsx | 3 - frontend/src/pages/app/LiveCamPage.jsx | 6 +- frontend/src/routeTree.gen.ts | 20 +---- 6 files changed, 140 insertions(+), 53 deletions(-) create mode 100644 frontend/src/hooks/useCameraManagement.tsx diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/AsideCameraInspector.tsx index adb4fd7..d5582fb 100644 --- a/frontend/src/components/layout/AsideCameraInspector.tsx +++ b/frontend/src/components/layout/AsideCameraInspector.tsx @@ -8,34 +8,79 @@ import { ActionIcon, Text, Center, + Menu, + rem, } from "@mantine/core"; import { modals } from "@mantine/modals"; -import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; -import { AddCameraModalContent } from "../main/AddCameraModal"; +import { + IconPlus, + IconDotsVertical, + IconTrash, + IconCamera, +} from "@tabler/icons-react"; +import { AddCameraModal, AddCameraModalContent } from "../main/AddCameraModal"; import { useState } from "react"; +import { useCameraManagement } from "@/hooks/useCameraManagement"; interface CameraListItem { name: string; url: string; } -function CameraItem({ name, url }: { name: string; url: string }) { +function CameraItem({ + name, + url, + onCameraRemoved +}: { + name: string; + url: string; + onCameraRemoved: (url: string) => void; +}) { return ( - + { + console.log(`url ${url} is selected`); + }} + withBorder + > - - {name} - - - - + + + + {name} + + + + + + + + + + { + onCameraRemoved(url); + }} + leftSection={ + + } + > + Delete Camera + + + ); } export function AsideCameraInspector() { - const [cameras, setCameras] = useState([]); + const { cameras, addCamera, removeCamera } = useCameraManagement(); return (
    @@ -59,7 +104,8 @@ export function AsideCameraInspector() { children: ( { - setCameras((prev) => [...prev, { name, url }]); + addCamera(name, url); + modals.closeAll(); }} /> ), @@ -74,16 +120,12 @@ export function AsideCameraInspector() { {cameras.length > 0 ? ( cameras.map((val, i) => ( - - - - {val.name} - - - - - - + removeCamera(val.url)} + name={val.name} + url={val.url} + /> )) ) : (
    kamera kosong
    diff --git a/frontend/src/components/main/AddCameraModal.tsx b/frontend/src/components/main/AddCameraModal.tsx index fac1a04..30f6d9c 100644 --- a/frontend/src/components/main/AddCameraModal.tsx +++ b/frontend/src/components/main/AddCameraModal.tsx @@ -5,13 +5,14 @@ import { useState } from "react"; interface AddCameraModalProps { opened: boolean; onClose: () => void; + onSubmit: (name: string, url: string) => void; } interface AddCameraModalContentProps { onSubmit: (name: string, url: string) => void; } export function AddCameraModalContent(props: AddCameraModalContentProps) { - const [name, setName] = useState("") - const [url, setUrl] = useState("") + const [name, setName] = useState(""); + const [url, setUrl] = useState(""); return (
    { @@ -20,8 +21,18 @@ export function AddCameraModalContent(props: AddCameraModalContentProps) { }} > - setName(e.target.value)} placeholder="Tambahkan nama kamera" /> - setUrl(e.target.value)} placeholder="RTSP URL" /> + setName(e.target.value)} + placeholder="Tambahkan nama kamera" + /> + setUrl(e.target.value)} + placeholder="RTSP URL" + />
    @@ -31,9 +42,14 @@ export function AddCameraModalContent(props: AddCameraModalContentProps) { export function AddCameraModal(props: AddCameraModalProps) { return ( - modals.closeAll()} /> + { + props.onSubmit(name, url); + modals.closeAll(); + + }} + /> ); } // export const AddCameraModal.Content: JSX.Element= AddCameraModalContent - \ No newline at end of file diff --git a/frontend/src/hooks/useCameraManagement.tsx b/frontend/src/hooks/useCameraManagement.tsx new file mode 100644 index 0000000..6841843 --- /dev/null +++ b/frontend/src/hooks/useCameraManagement.tsx @@ -0,0 +1,50 @@ +import { notifications } from "@mantine/notifications"; +import { useState } from "react"; + +interface CameraProps { + name: string; + url: string; +} + +export const useCameraManagement = () => { + const [cameras, setCameras] = useState([]); + + function addCamera(name: string, url: string) { + //check if name and url is not empty + if (!name || !url) { + notifications.show({ + title: "Error", + message: "Name and URL cannot be empty", + color: "red", + }); + return; + } + //check if camera already exists + if (cameras.some((cam) => cam.name === name)) { + notifications.show({ + title: "Error", + message: "Camera with the same name already exists", + color: "red", + }); + return + } + + setCameras([...cameras, { name, url }]); + notifications.show({ title: "Camera added", message: name }); + } + function removeCamera(url: string) { + const removedCameras = cameras.filter((cam) => cam.url === url); + if (removedCameras.length === 0) { + notifications.show({ + title: "Error", + message: "Camera not found", + color: "red", + }); + return; + } + setCameras(cameras.filter((cam) => cam.url !== url)); + notifications.show({ title: "Camera removed", message: url }); + } + + return { cameras, addCamera, removeCamera }; +}; diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 95d50ff..5c5f78e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -6,7 +6,6 @@ import "@/index.css"; import "@mantine/core/styles.css"; import "@mantine/notifications/styles.css"; import { MantineProvider } from "@mantine/core"; -import { StreamSocketProvider } from "./components/context/CameraSocketContext"; import { ModalsProvider } from "@mantine/modals"; import { Notifications } from "@mantine/notifications"; import { appTheme } from "./theme"; @@ -29,9 +28,7 @@ ReactDOM.createRoot(container).render( - - diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index ec20dd5..e5703ea 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -1,4 +1,4 @@ -import { useStreamSocket } from "@/components/context/CameraSocketContext"; +// import { useStreamSocket } from "@/components/context/CameraSocketContext"; import { IconCamera, IconTable } from "@tabler/icons-react"; import { ActionIcon, Card, Flex, Table, Tooltip } from "@mantine/core"; import { useEffect, useState } from "react"; @@ -10,7 +10,7 @@ import { AsideCameraInspector } from "@/components/layout/AsideCameraInspector"; function MainView () { - const stream = useStreamSocket(); + // const stream = useStreamSocket(); const [showTable, setShowTable] = useState(false); return ( <> @@ -52,7 +52,7 @@ function MainView () { ) : ( - + )}
    diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index a03919b..3efbf94 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -11,7 +11,6 @@ // Import Routes import { Route as rootRoute } from './routes/__root' -import { Route as RegisterImport } from './routes/register' import { Route as LoginImport } from './routes/login' import { Route as AppImport } from './routes/app' import { Route as IndexImport } from './routes/index' @@ -21,11 +20,6 @@ import { Route as AppProfileImport } from './routes/app/profile' // Create/Update Routes -const RegisterRoute = RegisterImport.update({ - path: '/register', - getParentRoute: () => rootRoute, -} as any) - const LoginRoute = LoginImport.update({ path: '/login', getParentRoute: () => rootRoute, @@ -81,13 +75,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof LoginImport parentRoute: typeof rootRoute } - '/register': { - id: '/register' - path: '/register' - fullPath: '/register' - preLoaderRoute: typeof RegisterImport - parentRoute: typeof rootRoute - } '/app/profile': { id: '/app/profile' path: '/profile' @@ -122,7 +109,6 @@ export const routeTree = rootRoute.addChildren({ AppIndexRoute, }), LoginRoute, - RegisterRoute, }) /* prettier-ignore-end */ @@ -135,8 +121,7 @@ export const routeTree = rootRoute.addChildren({ "children": [ "/", "/app", - "/login", - "/register" + "/login" ] }, "/": { @@ -153,9 +138,6 @@ export const routeTree = rootRoute.addChildren({ "/login": { "filePath": "login.tsx" }, - "/register": { - "filePath": "register.tsx" - }, "/app/profile": { "filePath": "app/profile.tsx", "parent": "/app" From 9f9fd58510e7916eef01296379abd230fb491f95 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 11 Oct 2024 00:22:42 +0800 Subject: [PATCH 55/63] feat: refactor --- .../layout/AsideCameraInspector.tsx | 136 ------------------ .../{ => components}/layout/MainLayout.tsx | 0 .../{ => components}/layout/MinimalLayout.tsx | 0 .../src/components/layout/SidebarContent.tsx | 60 -------- frontend/src/pages/LoginPage.tsx | 2 +- frontend/src/routes/app.tsx | 2 +- 6 files changed, 2 insertions(+), 198 deletions(-) delete mode 100644 frontend/src/components/layout/AsideCameraInspector.tsx rename frontend/src/{ => components}/layout/MainLayout.tsx (100%) rename frontend/src/{ => components}/layout/MinimalLayout.tsx (100%) delete mode 100644 frontend/src/components/layout/SidebarContent.tsx diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/AsideCameraInspector.tsx deleted file mode 100644 index d5582fb..0000000 --- a/frontend/src/components/layout/AsideCameraInspector.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import { - Accordion, - Divider, - Flex, - Button, - Card, - Group, - ActionIcon, - Text, - Center, - Menu, - rem, -} from "@mantine/core"; -import { modals } from "@mantine/modals"; -import { - IconPlus, - IconDotsVertical, - IconTrash, - IconCamera, -} from "@tabler/icons-react"; -import { AddCameraModal, AddCameraModalContent } from "../main/AddCameraModal"; -import { useState } from "react"; -import { useCameraManagement } from "@/hooks/useCameraManagement"; - -interface CameraListItem { - name: string; - url: string; -} - -function CameraItem({ - name, - url, - onCameraRemoved -}: { - name: string; - url: string; - onCameraRemoved: (url: string) => void; -}) { - return ( - { - console.log(`url ${url} is selected`); - }} - withBorder - > - - - - - {name} - - - - - - - - - - { - onCameraRemoved(url); - }} - leftSection={ - - } - > - Delete Camera - - - - - - ); -} - -export function AsideCameraInspector() { - const { cameras, addCamera, removeCamera } = useCameraManagement(); - return ( -
    - - - Metadata - - URL: rtsp://localhost:8888/a301.live - - - - - - Daftar Kamera - - - {cameras.length > 0 ? ( - cameras.map((val, i) => ( - removeCamera(val.url)} - name={val.name} - url={val.url} - /> - )) - ) : ( -
    kamera kosong
    - )} -
    -
    - ); -} diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/components/layout/MainLayout.tsx similarity index 100% rename from frontend/src/layout/MainLayout.tsx rename to frontend/src/components/layout/MainLayout.tsx diff --git a/frontend/src/layout/MinimalLayout.tsx b/frontend/src/components/layout/MinimalLayout.tsx similarity index 100% rename from frontend/src/layout/MinimalLayout.tsx rename to frontend/src/components/layout/MinimalLayout.tsx diff --git a/frontend/src/components/layout/SidebarContent.tsx b/frontend/src/components/layout/SidebarContent.tsx deleted file mode 100644 index b21d365..0000000 --- a/frontend/src/components/layout/SidebarContent.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Divider, NavLink } from "@mantine/core"; -import { useState } from "react"; -import { IconDeviceDesktop, IconUsers } from "@tabler/icons-react"; -import { UserCardInfo } from "@/components/ui/UserInfo"; -import { useNavigate, useRouterState } from "@tanstack/react-router"; - -const menuLists = [ - { - title: "Livecam", - path: "/app", - icon: , - }, - { - title: "Pengguna", - path: "/app/users", - icon: , - }, -]; - -// function TitleSidebar() { -// return ( -//
    -// -//
    -// Dashboard -//
    -// ); -// } - -export default function SidebarContent() { - const navigate = useNavigate(); - const router = useRouterState() - const [active, setActive] = useState( - menuLists.findIndex((item) => item.path == (location.pathname ?? "/app")) - ); - - return ( - <> - navigate({to: "/app/profile"})} /> - - {menuLists.map((val, idx) => ( - { - navigate({to: val.path}); - setActive(idx); - }} - /> - ))} - - ); -} diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 567780a..e71b5d2 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { MinimalLayout } from "@/layout/MinimalLayout"; +import { MinimalLayout } from "@/components/layout/MinimalLayout"; import { Anchor, Button, diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index 375ebde..69fe15a 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -1,6 +1,6 @@ import { ErrorScreen } from "@/components/ui/ErrorScreen"; import { AsideContextProvider } from "@/components/context/AsideContext"; -import MainLayout from "@/layout/MainLayout"; +import MainLayout from "@/components/layout/MainLayout"; import { Button, Center } from "@mantine/core"; import { createFileRoute, useRouter } from "@tanstack/react-router"; From 155053e14f94bc1b71c5389be8174671aa34a232 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 11 Oct 2024 00:21:39 +0800 Subject: [PATCH 56/63] test --- .../layout/AsideCameraInspector.tsx | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 frontend/src/components/layout/AsideCameraInspector.tsx diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/AsideCameraInspector.tsx new file mode 100644 index 0000000..d5582fb --- /dev/null +++ b/frontend/src/components/layout/AsideCameraInspector.tsx @@ -0,0 +1,136 @@ +import { + Accordion, + Divider, + Flex, + Button, + Card, + Group, + ActionIcon, + Text, + Center, + Menu, + rem, +} from "@mantine/core"; +import { modals } from "@mantine/modals"; +import { + IconPlus, + IconDotsVertical, + IconTrash, + IconCamera, +} from "@tabler/icons-react"; +import { AddCameraModal, AddCameraModalContent } from "../main/AddCameraModal"; +import { useState } from "react"; +import { useCameraManagement } from "@/hooks/useCameraManagement"; + +interface CameraListItem { + name: string; + url: string; +} + +function CameraItem({ + name, + url, + onCameraRemoved +}: { + name: string; + url: string; + onCameraRemoved: (url: string) => void; +}) { + return ( + { + console.log(`url ${url} is selected`); + }} + withBorder + > + + + + + {name} + + + + + + + + + + { + onCameraRemoved(url); + }} + leftSection={ + + } + > + Delete Camera + + + + + + ); +} + +export function AsideCameraInspector() { + const { cameras, addCamera, removeCamera } = useCameraManagement(); + return ( +
    + + + Metadata + + URL: rtsp://localhost:8888/a301.live + + + + + + Daftar Kamera + + + {cameras.length > 0 ? ( + cameras.map((val, i) => ( + removeCamera(val.url)} + name={val.name} + url={val.url} + /> + )) + ) : ( +
    kamera kosong
    + )} +
    +
    + ); +} From 532436f6326b220fc038c1253af4b3b2e9a4b7d8 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 11 Oct 2024 00:29:23 +0800 Subject: [PATCH 57/63] feat: refactor layout component --- frontend/src/{ => components}/layout/MainLayout.tsx | 2 +- frontend/src/{ => components}/layout/MinimalLayout.tsx | 0 .../src/components/layout/{ => part}/AsideCameraInspector.tsx | 2 +- frontend/src/components/layout/{ => part}/SidebarContent.tsx | 0 frontend/src/pages/LoginPage.tsx | 2 +- frontend/src/pages/app/LiveCamPage.jsx | 2 +- frontend/src/routes/app.tsx | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename frontend/src/{ => components}/layout/MainLayout.tsx (96%) rename frontend/src/{ => components}/layout/MinimalLayout.tsx (100%) rename frontend/src/components/layout/{ => part}/AsideCameraInspector.tsx (97%) rename frontend/src/components/layout/{ => part}/SidebarContent.tsx (100%) diff --git a/frontend/src/layout/MainLayout.tsx b/frontend/src/components/layout/MainLayout.tsx similarity index 96% rename from frontend/src/layout/MainLayout.tsx rename to frontend/src/components/layout/MainLayout.tsx index 04efa36..8ac61d6 100644 --- a/frontend/src/layout/MainLayout.tsx +++ b/frontend/src/components/layout/MainLayout.tsx @@ -1,4 +1,4 @@ -import SidebarContent from "@/components/layout/SidebarContent"; +import SidebarContent from "@/components/layout/part/SidebarContent"; import { AppShell, Burger, diff --git a/frontend/src/layout/MinimalLayout.tsx b/frontend/src/components/layout/MinimalLayout.tsx similarity index 100% rename from frontend/src/layout/MinimalLayout.tsx rename to frontend/src/components/layout/MinimalLayout.tsx diff --git a/frontend/src/components/layout/AsideCameraInspector.tsx b/frontend/src/components/layout/part/AsideCameraInspector.tsx similarity index 97% rename from frontend/src/components/layout/AsideCameraInspector.tsx rename to frontend/src/components/layout/part/AsideCameraInspector.tsx index adb4fd7..968a6d4 100644 --- a/frontend/src/components/layout/AsideCameraInspector.tsx +++ b/frontend/src/components/layout/part/AsideCameraInspector.tsx @@ -11,7 +11,7 @@ import { } from "@mantine/core"; import { modals } from "@mantine/modals"; import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; -import { AddCameraModalContent } from "../main/AddCameraModal"; +import { AddCameraModalContent } from "../../main/AddCameraModal"; import { useState } from "react"; interface CameraListItem { diff --git a/frontend/src/components/layout/SidebarContent.tsx b/frontend/src/components/layout/part/SidebarContent.tsx similarity index 100% rename from frontend/src/components/layout/SidebarContent.tsx rename to frontend/src/components/layout/part/SidebarContent.tsx diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index 567780a..e71b5d2 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -1,4 +1,4 @@ -import { MinimalLayout } from "@/layout/MinimalLayout"; +import { MinimalLayout } from "@/components/layout/MinimalLayout"; import { Anchor, Button, diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.jsx index ec20dd5..472a38f 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.jsx @@ -6,7 +6,7 @@ import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/components/model/dataset"; import { LiveCamComponent } from "@/components/LiveCamComponent"; import { useAside } from "@/components/context/AsideContext"; -import { AsideCameraInspector } from "@/components/layout/AsideCameraInspector"; +import { AsideCameraInspector } from "@/components/layout/part/AsideCameraInspector"; function MainView () { diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index 375ebde..69fe15a 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -1,6 +1,6 @@ import { ErrorScreen } from "@/components/ui/ErrorScreen"; import { AsideContextProvider } from "@/components/context/AsideContext"; -import MainLayout from "@/layout/MainLayout"; +import MainLayout from "@/components/layout/MainLayout"; import { Button, Center } from "@mantine/core"; import { createFileRoute, useRouter } from "@tanstack/react-router"; From 77c4fb5ade1a5761658bba49f76a50ecc09132f2 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Fri, 11 Oct 2024 00:43:55 +0800 Subject: [PATCH 58/63] feat: restore removed feature --- .../layout/part/AsideCameraInspector.tsx | 26 +++++++++++++++---- frontend/src/index.css | 5 ++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/layout/part/AsideCameraInspector.tsx b/frontend/src/components/layout/part/AsideCameraInspector.tsx index 968a6d4..72febd0 100644 --- a/frontend/src/components/layout/part/AsideCameraInspector.tsx +++ b/frontend/src/components/layout/part/AsideCameraInspector.tsx @@ -8,11 +8,13 @@ import { ActionIcon, Text, Center, + Menu, } from "@mantine/core"; import { modals } from "@mantine/modals"; import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; import { AddCameraModalContent } from "../../main/AddCameraModal"; import { useState } from "react"; +import { useCameraManagement } from "@/hooks/useCameraManagement"; interface CameraListItem { name: string; @@ -35,7 +37,7 @@ function CameraItem({ name, url }: { name: string; url: string }) { } export function AsideCameraInspector() { - const [cameras, setCameras] = useState([]); + const { cameras, addCamera, removeCamera } = useCameraManagement(); return (
    @@ -59,7 +61,8 @@ export function AsideCameraInspector() { children: ( { - setCameras((prev) => [...prev, { name, url }]); + addCamera(name, url); + modals.closeAll(); }} /> ), @@ -79,9 +82,22 @@ export function AsideCameraInspector() { {val.name} - - - + + + + + + + + { + removeCamera(val.url); + }} + > + Delete Camera + + + )) diff --git a/frontend/src/index.css b/frontend/src/index.css index 2eb7be1..b5f5875 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -4,3 +4,8 @@ } @tailwind components; @tailwind utilities; + + +body { + overflow: hidden; +} \ No newline at end of file From 10c3ae794a5e7bed297f3b7e4e6f5a9157d8793b Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Mon, 14 Oct 2024 09:59:00 +0800 Subject: [PATCH 59/63] feat --- .../layout/part/AsideCameraInspector.tsx | 11 +- frontend/src/constant.ts | 3 +- frontend/src/hooks/useAuthManager.tsx | 65 +++++++++++ frontend/src/hooks/useCameraManagement.tsx | 107 ++++++++++++------ frontend/src/pages/LoginPage.tsx | 15 ++- frontend/src/pages/app/ProfilePage.tsx | 4 +- 6 files changed, 159 insertions(+), 46 deletions(-) create mode 100644 frontend/src/hooks/useAuthManager.tsx diff --git a/frontend/src/components/layout/part/AsideCameraInspector.tsx b/frontend/src/components/layout/part/AsideCameraInspector.tsx index 72febd0..beb4a44 100644 --- a/frontend/src/components/layout/part/AsideCameraInspector.tsx +++ b/frontend/src/components/layout/part/AsideCameraInspector.tsx @@ -13,7 +13,7 @@ import { import { modals } from "@mantine/modals"; import { IconPlus, IconDotsVertical } from "@tabler/icons-react"; import { AddCameraModalContent } from "../../main/AddCameraModal"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useCameraManagement } from "@/hooks/useCameraManagement"; interface CameraListItem { @@ -37,7 +37,10 @@ function CameraItem({ name, url }: { name: string; url: string }) { } export function AsideCameraInspector() { - const { cameras, addCamera, removeCamera } = useCameraManagement(); + const { cameras, addCamera, removeCamera,syncCameras } = useCameraManagement(); + useEffect(()=> { + syncCameras() + },[]) return (
    @@ -80,7 +83,7 @@ export function AsideCameraInspector() { - {val.name} + {val.title} @@ -91,7 +94,7 @@ export function AsideCameraInspector() { { - removeCamera(val.url); + removeCamera(val.id); }} > Delete Camera diff --git a/frontend/src/constant.ts b/frontend/src/constant.ts index 6b66f04..abd37af 100644 --- a/frontend/src/constant.ts +++ b/frontend/src/constant.ts @@ -1 +1,2 @@ -export const BASE_URL = "https://localhost:5500" \ No newline at end of file +export const BASE_URL = "http://localhost:8881" +export const STREAM_URL = "http://localhost:8882" \ No newline at end of file diff --git a/frontend/src/hooks/useAuthManager.tsx b/frontend/src/hooks/useAuthManager.tsx new file mode 100644 index 0000000..34ad52f --- /dev/null +++ b/frontend/src/hooks/useAuthManager.tsx @@ -0,0 +1,65 @@ +import { BASE_URL } from "@/constant"; +import { notifications } from "@mantine/notifications"; +import { useNavigate } from "@tanstack/react-router"; + +const KEY = "key"; +const USER = "user"; + +export const useAuthManager = () => { + const navigate = useNavigate(); + + async function logout() { + localStorage.removeItem(KEY); + localStorage.removeItem(USER); + notifications.show({ + title: "Logout successful", + message: "Goodbye", + color: "green", + }); + navigate({ to: "/login", replace: true }); + } + async function getAccessToken() { + const data = localStorage.getItem(KEY); + const key = JSON.parse(data as string).accessToken; + if (key && typeof key === "string") { + return key as string; + } + return +} + + async function submitLogin(email: string, password: string) { + const body = { + email: email, + password: password, + }; + + const response = await fetch(`${BASE_URL}/authentication/signin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (response.status === 200 || response.status === 201) { + const data = await response.json(); + localStorage.setItem(KEY, JSON.stringify(data.result.key)); + notifications.show({ + title: "Login successful", + message: "Welcome back", + color: "green", + }); + delete data.result.key; + localStorage.setItem(USER, JSON.stringify(data.result)); + return navigate({ to: "/app", replace: true }); + } + notifications.show({ + title: "Login failed", + message: "Please check your credentials", + color: "red", + }); + console.error("Login failed", response.statusText); + return; + } + return { submitLogin, logout, getAccessToken}; +}; diff --git a/frontend/src/hooks/useCameraManagement.tsx b/frontend/src/hooks/useCameraManagement.tsx index 6841843..81487d8 100644 --- a/frontend/src/hooks/useCameraManagement.tsx +++ b/frontend/src/hooks/useCameraManagement.tsx @@ -1,50 +1,89 @@ import { notifications } from "@mantine/notifications"; import { useState } from "react"; +import { useAuthManager } from "./useAuthManager"; +import { BASE_URL } from "@/constant"; interface CameraProps { - name: string; - url: string; + streamId: string; + id: string; + title: string; + description: string; } export const useCameraManagement = () => { const [cameras, setCameras] = useState([]); - - function addCamera(name: string, url: string) { - //check if name and url is not empty - if (!name || !url) { - notifications.show({ - title: "Error", - message: "Name and URL cannot be empty", - color: "red", + const { getAccessToken } = useAuthManager(); + async function syncCameras() { + const response = await fetch(`${BASE_URL}/report?stream=True`, { + headers: { + Authorization: `Bearer ${await getAccessToken()}`, + }, + method: "GET", + }); + try { + const data = await response.json(); + const lists = data.result; + const cameras: CameraProps[] = lists.map((list: any) => { + return { + title: list.title, + description: list.description, + id: list.id, + streamId: list.streamId, + } as CameraProps; }); + setCameras(cameras); return; + } catch (e) { + console.error(e); } - //check if camera already exists - if (cameras.some((cam) => cam.name === name)) { - notifications.show({ - title: "Error", - message: "Camera with the same name already exists", - color: "red", - }); - return - } + } - setCameras([...cameras, { name, url }]); - notifications.show({ title: "Camera added", message: name }); + function addCamera(title: string, id: string) { + //check if name and url is not empty + // if (!title || !id) { + // notifications.show({ + // title: "Error", + // message: "Name and URL cannot be empty", + // color: "red", + // }); + // return; + // } + // //check if camera already exists + // if (cameras.some((cam) => cam.title === title)) { + // notifications.show({ + // title: "Error", + // message: "Camera with the same name already exists", + // color: "red", + // }); + // return; + // } + + // setCameras([...cameras, { title, id, description: "", streamId: "" }]); + // notifications.show({ title: "Camera added", message: title }); + notifications.show({ + title: "Error", + message: "This feature is not implemented yet", + color: "red", + }); } - function removeCamera(url: string) { - const removedCameras = cameras.filter((cam) => cam.url === url); - if (removedCameras.length === 0) { - notifications.show({ - title: "Error", - message: "Camera not found", - color: "red", - }); - return; - } - setCameras(cameras.filter((cam) => cam.url !== url)); - notifications.show({ title: "Camera removed", message: url }); + function removeCamera(id: string) { + // const removedCameras = cameras.filter((cam) => cam.id === id); + // if (removedCameras.length === 0) { + // notifications.show({ + // title: "Error", + // message: "Camera not found", + // color: "red", + // }); + // return; + // } + // setCameras(cameras.filter((cam) => cam.id !== id)); + // notifications.show({ title: "Camera removed", message: id }); + notifications.show({ + title: "Error", + message: "This feature is not implemented yet", + color: "red", + }); } - return { cameras, addCamera, removeCamera }; + return { cameras, addCamera, removeCamera, syncCameras }; }; diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/pages/LoginPage.tsx index e71b5d2..44ba685 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/pages/LoginPage.tsx @@ -1,6 +1,6 @@ import { MinimalLayout } from "@/components/layout/MinimalLayout"; +import { useAuthManager } from "@/hooks/useAuthManager"; import { - Anchor, Button, Divider, Group, @@ -26,6 +26,8 @@ export function LoginPage() { function LoginCard() { const navigate = useNavigate(); + const {submitLogin} = useAuthManager(); + const form = useForm({ initialValues: { email: "", @@ -33,10 +35,7 @@ function LoginCard() { }, validate: { email: (value) => (/^\S+@\S+$/.test(value) ? null : "Invalid email"), - password: (value) => - /^(?=.*[A-Z])[A-Za-z\d]{8,}$/.test(value) - ? null - : "At least 8 chars and contain at least 1 uppercase", + password: (value) => value.length >= 6 ? null : "Password is too short", }, }); @@ -47,12 +46,15 @@ function LoginCard() { -
    {})}> + { + submitLogin(form.values.email, form.values.password) + })}> form.setFieldValue("email", event.currentTarget.value) } @@ -61,6 +63,7 @@ function LoginCard() { /> diff --git a/frontend/src/pages/app/ProfilePage.tsx b/frontend/src/pages/app/ProfilePage.tsx index 30ba184..26ae19a 100644 --- a/frontend/src/pages/app/ProfilePage.tsx +++ b/frontend/src/pages/app/ProfilePage.tsx @@ -3,9 +3,11 @@ import { Avatar, Button, Divider, Flex, Text, Title } from "@mantine/core"; import { IconLogout } from "@tabler/icons-react"; import { useEffect } from "react"; import { useNavigate } from "@tanstack/react-router"; +import { useAuthManager } from "@/hooks/useAuthManager"; export function ProfilePage() { const navigate = useNavigate(); + const { logout } = useAuthManager(); const { setAsideComponent } = useAside(); useEffect(() => { setAsideComponent(); @@ -20,7 +22,7 @@ export function ProfilePage() { {cameras.length > 0 ? ( cameras.map((val, i) => ( - + { + setStreamUrl(val.title,val.streamId,"") + }}> {val.title} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 5c5f78e..cac0664 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -12,6 +12,7 @@ import { appTheme } from "./theme"; import { createRouter, RouterProvider } from "@tanstack/react-router"; import { routeTree } from "./routeTree.gen"; +import { ImageStreamContextProvider } from "./components/context/ImageStreamContext"; const router = createRouter({ routeTree }); // Register the router instance for type safety @@ -28,7 +29,7 @@ ReactDOM.createRoot(container).render( - + diff --git a/frontend/src/pages/app/LiveCamPage.jsx b/frontend/src/pages/app/LiveCamPage.tsx similarity index 82% rename from frontend/src/pages/app/LiveCamPage.jsx rename to frontend/src/pages/app/LiveCamPage.tsx index b8ba7bb..1c29c94 100644 --- a/frontend/src/pages/app/LiveCamPage.jsx +++ b/frontend/src/pages/app/LiveCamPage.tsx @@ -1,17 +1,20 @@ // import { useStreamSocket } from "@/components/context/CameraSocketContext"; import { IconCamera, IconTable } from "@tabler/icons-react"; import { ActionIcon, Card, Flex, Table, Tooltip } from "@mantine/core"; -import { useEffect, useState } from "react"; +import { useContext, useEffect, useState } from "react"; import { ReportTable } from "@/components/ReportTable"; import { sampleDataCCTV } from "@/components/model/dataset"; import { LiveCamComponent } from "@/components/LiveCamComponent"; import { useAside } from "@/components/context/AsideContext"; import { AsideCameraInspector } from "@/components/layout/part/AsideCameraInspector"; +import { imageStreamContext } from "@/components/context/ImageStreamContext"; - -function MainView () { +function MainView() { // const stream = useStreamSocket(); const [showTable, setShowTable] = useState(false); + const { mlUrl, rawUrl } = useContext( + imageStreamContext + ) as import("@/components/context/ImageStreamContext").IImageStreamContext; return ( <> @@ -52,21 +55,21 @@ function MainView () { ) : ( - + )}
    ); -}; +} -export default function LiveCamPage () { +export default function LiveCamPage() { const { setAsideComponent } = useAside(); useEffect(() => { - setAsideComponent(); + setAsideComponent(); }, []); return ( ); -}; +} diff --git a/frontend/src/routes/app.tsx b/frontend/src/routes/app.tsx index 69fe15a..0bf73d0 100644 --- a/frontend/src/routes/app.tsx +++ b/frontend/src/routes/app.tsx @@ -3,12 +3,15 @@ import { AsideContextProvider } from "@/components/context/AsideContext"; import MainLayout from "@/components/layout/MainLayout"; import { Button, Center } from "@mantine/core"; import { createFileRoute, useRouter } from "@tanstack/react-router"; +import { ImageStreamContextProvider } from "@/components/context/ImageStreamContext"; export const Route = createFileRoute("/app")({ component: () => ( - - - + + + + + ), notFoundComponent: () => { const router = useRouter(); From ce848c5965f6d5c5439e1a8f83c17da5685bc238 Mon Sep 17 00:00:00 2001 From: Agung Adhinata Date: Mon, 14 Oct 2024 22:54:21 +0800 Subject: [PATCH 61/63] feat: cleanup --- frontend/src/components/ImageStreamViewer.tsx | 26 +++--- frontend/src/components/LiveCamComponent.tsx | 4 +- .../src/components/context/AuthContext.tsx | 79 ------------------- .../components/context/ImageStreamContext.tsx | 8 +- .../layout/part/AsideCameraInspector.tsx | 2 +- frontend/src/hooks/useAuthManager.tsx | 8 +- frontend/src/hooks/useCameraManagement.tsx | 2 + frontend/src/hooks/useLocalStorage.tsx | 30 ------- frontend/src/main.tsx | 1 - 9 files changed, 21 insertions(+), 139 deletions(-) delete mode 100644 frontend/src/components/context/AuthContext.tsx delete mode 100644 frontend/src/hooks/useLocalStorage.tsx diff --git a/frontend/src/components/ImageStreamViewer.tsx b/frontend/src/components/ImageStreamViewer.tsx index 65358de..26847d6 100644 --- a/frontend/src/components/ImageStreamViewer.tsx +++ b/frontend/src/components/ImageStreamViewer.tsx @@ -2,39 +2,31 @@ import { Box, Text } from "@mantine/core"; import { useEffect, useState } from "react"; import { IconAlertCircle } from "@tabler/icons-react"; -// const getBlobUrl = (data: string | null) => { -// if (data) { -// return `data:image/jpeg;base64, ${data}`; -// } -// return; -// }; -//todo: create error handling like result, error, and loading. + export function ImageStreamViewer({ - base64Data, + source: source, error, title }: { - /** - * Base64 image string, not include `data:image/jpeg;base64,` - */ - base64Data?: string; + + source?: string; error?: string; title?: string; }) { - const [base64String, setBase64String] = useState( + const [imageSource, setImageSource] = useState( undefined ); useEffect(() => { - setBase64String(base64Data ?? ""); - }, [base64Data]); + setImageSource(source ?? ""); + }, [source]); return ( - {base64String ? ( - CCTV Stream + {imageSource ? ( + CCTV Stream ) : (

    {" "} diff --git a/frontend/src/components/LiveCamComponent.tsx b/frontend/src/components/LiveCamComponent.tsx index 78cb625..54851a6 100644 --- a/frontend/src/components/LiveCamComponent.tsx +++ b/frontend/src/components/LiveCamComponent.tsx @@ -12,9 +12,9 @@ export function LiveCamComponent(props: LiveCamProps) { return ( {visible ? ( - + ) : ( - + )} + + + + + + + {cameras.length > 0 ? ( cameras.map((val, i) => ( - { - setStreamUrl(val.title,val.streamId,val.streamId) - }}> - - - {val.title} - -

    - - - - - - - { - removeCamera(val.id); - }} - > - Delete Camera - - - -
    -
    + { + console.log("Selected URL:", url); + }} + url={val.url} + key={i} + streamId={val.id} + actionButtons={ + { + e.stopPropagation(); + setStreamUrl(val.url, val.id); + }} + > + + + } + /> )) ) : ( -
    kamera kosong
    +
    + kamera kosong +
    )}
    diff --git a/frontend/src/components/main/AddCameraModal.tsx b/frontend/src/components/main/AddCameraModal.tsx index 30f6d9c..6a9d202 100644 --- a/frontend/src/components/main/AddCameraModal.tsx +++ b/frontend/src/components/main/AddCameraModal.tsx @@ -5,28 +5,21 @@ import { useState } from "react"; interface AddCameraModalProps { opened: boolean; onClose: () => void; - onSubmit: (name: string, url: string) => void; + onSubmit: (url: string) => void; } interface AddCameraModalContentProps { - onSubmit: (name: string, url: string) => void; + onSubmit: (url: string) => void; } export function AddCameraModalContent(props: AddCameraModalContentProps) { - const [name, setName] = useState(""); const [url, setUrl] = useState(""); return ( { e.preventDefault(); - props.onSubmit(name, url); + props.onSubmit( url); }} > - setName(e.target.value)} - placeholder="Tambahkan nama kamera" - /> { - props.onSubmit(name, url); + onSubmit={(url) => { + props.onSubmit(url); modals.closeAll(); - }} /> diff --git a/frontend/src/constant.ts b/frontend/src/constant.ts index abd37af..7b0a354 100644 --- a/frontend/src/constant.ts +++ b/frontend/src/constant.ts @@ -1,2 +1,3 @@ -export const BASE_URL = "http://localhost:8881" -export const STREAM_URL = "http://localhost:8882" \ No newline at end of file + +export const BASE_URL = "http://localhost:8080" +export const STREAM_URL = "http://localhost:8080" \ No newline at end of file diff --git a/frontend/src/hooks/useAuthManager.tsx b/frontend/src/hooks/useAuthManager.tsx index 50e34c5..3ff8574 100644 --- a/frontend/src/hooks/useAuthManager.tsx +++ b/frontend/src/hooks/useAuthManager.tsx @@ -2,15 +2,15 @@ import { BASE_URL } from "@/constant"; import { notifications } from "@mantine/notifications"; import { useNavigate } from "@tanstack/react-router"; -const KEY = "key"; -const USER = "user"; +const ACCESS_TOKEN = "accessToken"; +const REFRESH_TOKEN = "refreshToken"; export const useAuthManager = () => { const navigate = useNavigate(); async function logout() { - localStorage.removeItem(KEY); - localStorage.removeItem(USER); + localStorage.removeItem(ACCESS_TOKEN); + localStorage.removeItem(REFRESH_TOKEN); notifications.show({ title: "Logout successful", message: "Goodbye", @@ -19,12 +19,43 @@ export const useAuthManager = () => { navigate({ to: "/login", replace: true }); } async function getAccessToken() { - const data = localStorage.getItem(KEY); - const key = JSON.parse(data as string).accessToken; - if (key && typeof key === "string") { - return key as string; + const data = localStorage.getItem(ACCESS_TOKEN); + if (!data) { + console.error("No access token found"); + return null; + } + return data; + } + + async function refreshToken() { + // refresh token if the access token is expired + const response = await fetch(`${BASE_URL}/user/token`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + token: localStorage.getItem(REFRESH_TOKEN), + }), + }).catch((e) => console.error("Error refreshing token", e)); + if (!response) { + return; + } + if (!response.ok) { + notifications.show({ + title: "Refresh token failed", + message: "Please login again", + color: "red", + }); + console.error("Refresh token failed", response.statusText); + navigate({ to: "/login", replace: true }); + return; + } + const data = await response.json(); + if (data && data.result && data.result.token) { + localStorage.setItem(ACCESS_TOKEN, data.result.token); + } - return; } async function submitLogin(email: string, password: string) { @@ -33,33 +64,56 @@ export const useAuthManager = () => { password: password, }; - const response = await fetch(`${BASE_URL}/authentication/signin`, { + const response = await fetch(`${BASE_URL}/user/signin`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(body), + }).catch((error) => { + console.error("Failed to login", error); + notifications.show({ + title: "Login failed", + message: "Please check your network connection", + color: "red", + }); + return null; }); + if (!response) { + return; + } - if (response.status === 200 || response.status === 201) { - const data = await response.json(); - localStorage.setItem(KEY, JSON.stringify(data.result.key)); + // error check first + if (!response.ok) { + // chekck if body message is available + const errorBody = await response.json(); + if (errorBody && errorBody.message) { + notifications.show({ + title: "Login failed", + message: errorBody.message, + color: "red", + }); + console.error("Login failed", errorBody.message); + return; + } notifications.show({ - title: "Login successful", - message: "Welcome back", - color: "green", + title: "Login failed", + message: "Please check your credentials", + color: "red", }); - delete data.result.key; - localStorage.setItem(USER, JSON.stringify(data.result)); - return navigate({ to: "/app", replace: true }); + console.error("Login failed", response.statusText); + return; } + + const data = await response.json(); + localStorage.setItem(ACCESS_TOKEN, data.result.accessToken); + localStorage.setItem(REFRESH_TOKEN, data.result.refreshToken); notifications.show({ - title: "Login failed", - message: "Please check your credentials", - color: "red", + title: "Login successful", + message: "Welcome back", + color: "green", }); - console.error("Login failed", response.statusText); - return; + navigate({ to: "/app", replace: true }); } - return { submitLogin, logout, getAccessToken }; + return { submitLogin, logout, getAccessToken, refreshToken }; }; diff --git a/frontend/src/hooks/useCameraManagement.tsx b/frontend/src/hooks/useCameraManagement.tsx index 385991b..e7d0f8d 100644 --- a/frontend/src/hooks/useCameraManagement.tsx +++ b/frontend/src/hooks/useCameraManagement.tsx @@ -3,89 +3,196 @@ import { useState } from "react"; import { useAuthManager } from "./useAuthManager"; import { BASE_URL } from "@/constant"; -interface CameraProps { - streamId: string; - reportId: string; +interface StreamProps { id: string; - title: string; - description: string; + url: string; + userId: string; + updatedDate: string; + createdDate: string; + streamId: string; } export const useCameraManagement = () => { - const [cameras, setCameras] = useState([]); - const { getAccessToken } = useAuthManager(); - async function syncCameras() { - const response = await fetch(`${BASE_URL}/report?stream=True`, { + const [cameras, setCameras] = useState([]); + const { getAccessToken, refreshToken } = useAuthManager(); + + async function addCamera(streamUrl: string) { + if (!streamUrl || streamUrl.trim() === "") { + notifications.show({ + title: "Error", + message: "Stream URL cannot be empty", + color: "red", + }); + return; + } + const response = await fetch(`${BASE_URL}/stream`, { + method: "POST", headers: { + "Content-Type": "application/json", Authorization: `Bearer ${await getAccessToken()}`, }, - method: "GET", + body: JSON.stringify({ + url: streamUrl, + }), + }).catch((error) => { + console.error("Failed to add camera", error); }); - try { - const data = await response.json(); - const lists = data.result; - const cameras: CameraProps[] = lists.map((list: any) => { - return { - title: list.title, - description: list.description, - id: list.id, - streamId: list.streamId, - reportId: list.reportId, - } as CameraProps; + if (!response) { + return; + } + + if (!response.ok) { + if (response.body) { + const errorData = await response.json(); + notifications.show({ + title: "Error", + message: errorData.message || "Failed to add camera", + color: "red", + }); + console.error("Failed to add camera", errorData); + return; + } + notifications.show({ + title: "Error", + message: "Failed to add camera", + color: "red", }); - setCameras(cameras); + console.error("Failed to add camera", response.statusText); return; - } catch (e) { - console.error(e); + } + const data = await response.json(); + if (data && data.result) { + const newCamera: StreamProps = { + id: data.result.id, + streamId: data.result.streamId, + url: data.result.url, + userId: data.result.userId, + updatedDate: data.result.updatedDate, + createdDate: data.result.createdDate ?? new Date().toISOString(), + }; + setCameras((prev) => [...prev, newCamera]); + notifications.show({ + title: "Camera added", + message: `Camera ${newCamera.id} added successfully`, + color: "green", + }); } } - - function addCamera(title: string, id: string) { - //check if name and url is not empty - // if (!title || !id) { - // notifications.show({ - // title: "Error", - // message: "Name and URL cannot be empty", - // color: "red", - // }); - // return; - // } - // //check if camera already exists - // if (cameras.some((cam) => cam.title === title)) { - // notifications.show({ - // title: "Error", - // message: "Camera with the same name already exists", - // color: "red", - // }); - // return; - // } - - // setCameras([...cameras, { title, id, description: "", streamId: "" }]); - // notifications.show({ title: "Camera added", message: title }); - notifications.show({ - title: "Error", - message: "This feature is not implemented yet", - color: "red", + async function editStream(id: string, newUrl: string) { + if (!id || !newUrl || newUrl.trim() === "") { + notifications.show({ + title: "Error", + message: "Stream ID and URL cannot be empty", + color: "red", + }); + return; + } + const response = await fetch(`${BASE_URL}/stream`, { + method: "PATCH", + body: JSON.stringify({ + id: id, + url: newUrl, + }), + }).catch((error) => { + console.error("Failed to edit camera", error); }); + if (!response) { + return; + } + + if (!response.ok) { + if (response.body) { + const errorData = await response.json(); + notifications.show({ + title: "Error", + message: errorData.message || "Failed to edit camera", + color: "red", + }); + console.error("Failed to edit camera", errorData); + return; + } + notifications.show({ + title: "Error", + message: "Failed to edit camera", + color: "red", + }); + console.error("Failed to edit camera", response.statusText); + return; + } + const data = await response.json(); + if (data && data.result) { + const updatedCamera: StreamProps = { + id: data.result.id, + streamId: data.result.streamId, + url: data.result.url, + userId: data.result.userId, + updatedDate: data.result.updatedDate, + createdDate: data.result.createdDate ?? new Date().toISOString(), + }; + setCameras((prev) => + prev.map((camera) => (camera.id === id ? updatedCamera : camera)) + ); + notifications.show({ + title: "Camera updated", + message: `Camera ${updatedCamera.id} updated successfully`, + color: "green", + }); + } } - function removeCamera(id: string) { - // const removedCameras = cameras.filter((cam) => cam.id === id); - // if (removedCameras.length === 0) { - // notifications.show({ - // title: "Error", - // message: "Camera not found", - // color: "red", - // }); - // return; - // } - // setCameras(cameras.filter((cam) => cam.id !== id)); - // notifications.show({ title: "Camera removed", message: id }); - notifications.show({ - title: "Error", - message: "This feature is not implemented yet", - color: "red", + + // Fetch cameras from the server + // the body result should be in the format: + // { + // "result": [] with StreamProps[] + // } + async function fetchCameras() { + await refreshToken(); + const token = await getAccessToken(); + console.log("Fetching cameras with token:", token); + if (!token) { + notifications.show({ + title: "Error", + message: "You are not authenticated", + color: "red", + }); + return; + } + const response = await fetch(`${BASE_URL}/stream`, { + method: "GET", + headers: { + Authorization: `Bearer ${await getAccessToken()}`, + }, + }).catch((error) => { + console.error("Failed to fetch cameras", error); }); + if (!response) { + return; + } + if (!response.ok) { + notifications.show({ + title: "Error", + message: "Failed to fetch cameras", + color: "red", + }); + console.error("Failed to fetch cameras", response.statusText); + return; + } + + const data = await response.json(); + if (data && data.result) { + //set items as any + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const fetchedCameras: StreamProps[] = data.result.map((item: any) => ({ + id: item.id, + streamId: item.streamId, + url: item.url, + userId: item.userId, + updatedDate: item.updatedDate, + createdDate: item.createdDate ?? new Date().toISOString(), + })); + setCameras(fetchedCameras); + } } - return { cameras, addCamera, removeCamera, syncCameras }; + return { cameras, addCamera, editStream, fetchCameras }; }; diff --git a/frontend/src/pages/app/LiveCamPage.tsx b/frontend/src/pages/app/LiveCamPage.tsx index 1c29c94..5a56281 100644 --- a/frontend/src/pages/app/LiveCamPage.tsx +++ b/frontend/src/pages/app/LiveCamPage.tsx @@ -12,14 +12,14 @@ import { imageStreamContext } from "@/components/context/ImageStreamContext"; function MainView() { // const stream = useStreamSocket(); const [showTable, setShowTable] = useState(false); - const { mlUrl, rawUrl } = useContext( + const { mlUrl, rawUrl, camName } = useContext( imageStreamContext ) as import("@/components/context/ImageStreamContext").IImageStreamContext; return ( <> -

    Nama Camera

    +

    {camName ?? "Default"}

    Tanggal: 19 Februari 2024

    @@ -49,6 +49,7 @@ function MainView() {
    {showTable ? (