From 761f93e2a7e29045fb8b5e0975198ee519c1ae59 Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Wed, 29 Jul 2026 10:16:09 +0100 Subject: [PATCH] feat: overhaul frontend styling --- frontend/index.html | 27 +- frontend/package-lock.json | 670 +----------------- frontend/package.json | 4 +- frontend/src/App.css | 42 -- frontend/src/App.tsx | 124 +++- frontend/src/assets/react.svg | 1 - frontend/src/components/Box.tsx | 14 - frontend/src/components/Dropdown.tsx | 56 -- frontend/src/components/Selector.tsx | 16 - frontend/src/components/Sidebar.tsx | 151 +++- frontend/src/components/ThemeProvider.tsx | 26 + frontend/src/components/Topbar.tsx | 150 ++-- .../src/components/buttons/BlueButton.tsx | 14 - .../src/components/buttons/MenuButton.tsx | 15 - .../src/components/buttons/SidebarButton.tsx | 23 - frontend/src/components/ui/Badge.tsx | 45 ++ frontend/src/components/ui/Button.tsx | 63 ++ frontend/src/components/ui/Card.tsx | 47 ++ frontend/src/components/ui/EmptyState.tsx | 31 + frontend/src/components/ui/Field.tsx | 42 ++ frontend/src/components/ui/IconButton.tsx | 27 + frontend/src/components/ui/Menu.tsx | 95 +++ frontend/src/components/ui/PageHeader.tsx | 23 + frontend/src/components/ui/Progress.tsx | 31 + frontend/src/components/ui/Skeleton.tsx | 7 + frontend/src/components/ui/Spinner.tsx | 26 + frontend/src/components/ui/ThemeToggle.tsx | 31 + .../src/components/video/ClipRangeSlider.tsx | 42 -- frontend/src/components/video/ConfigBox.tsx | 59 -- frontend/src/components/video/ExportPanel.tsx | 156 ++++ .../src/components/video/ExportWidget.tsx | 53 -- frontend/src/components/video/MetadataBox.tsx | 22 - .../src/components/video/PlaybackSlider.tsx | 37 - frontend/src/components/video/Playbar.tsx | 74 -- .../src/components/video/PreviewControls.tsx | 113 +++ frontend/src/components/video/TrimRange.tsx | 68 ++ frontend/src/components/video/VideoCard.tsx | 219 +++--- frontend/src/index.css | 367 +++++++++- frontend/src/layouts/MainLayout.tsx | 111 ++- frontend/src/lib/theme.ts | 25 + frontend/src/pages/ClipEdit.tsx | 254 +++---- frontend/src/pages/ClipUpload.tsx | 146 +++- frontend/src/pages/EditClip.tsx | 108 --- frontend/src/pages/EditMedia.tsx | 127 ++++ frontend/src/pages/EditVod.tsx | 108 --- frontend/src/pages/Home.tsx | 100 +-- frontend/src/pages/MediaLibrary.tsx | 161 +++++ frontend/src/pages/MediaPlayer.tsx | 199 ++++-- frontend/src/pages/MyClips.tsx | 57 -- frontend/src/pages/MyVods.tsx | 60 -- frontend/src/pages/NotFound.tsx | 18 + frontend/src/pages/Profile.tsx | 159 +++-- frontend/src/pages/StreamControl.tsx | 306 +++++--- frontend/vite.config.ts | 3 +- 54 files changed, 2758 insertions(+), 2195 deletions(-) delete mode 100644 frontend/src/App.css delete mode 100644 frontend/src/assets/react.svg delete mode 100644 frontend/src/components/Box.tsx delete mode 100644 frontend/src/components/Dropdown.tsx delete mode 100644 frontend/src/components/Selector.tsx create mode 100644 frontend/src/components/ThemeProvider.tsx delete mode 100644 frontend/src/components/buttons/BlueButton.tsx delete mode 100644 frontend/src/components/buttons/MenuButton.tsx delete mode 100644 frontend/src/components/buttons/SidebarButton.tsx create mode 100644 frontend/src/components/ui/Badge.tsx create mode 100644 frontend/src/components/ui/Button.tsx create mode 100644 frontend/src/components/ui/Card.tsx create mode 100644 frontend/src/components/ui/EmptyState.tsx create mode 100644 frontend/src/components/ui/Field.tsx create mode 100644 frontend/src/components/ui/IconButton.tsx create mode 100644 frontend/src/components/ui/Menu.tsx create mode 100644 frontend/src/components/ui/PageHeader.tsx create mode 100644 frontend/src/components/ui/Progress.tsx create mode 100644 frontend/src/components/ui/Skeleton.tsx create mode 100644 frontend/src/components/ui/Spinner.tsx create mode 100644 frontend/src/components/ui/ThemeToggle.tsx delete mode 100644 frontend/src/components/video/ClipRangeSlider.tsx delete mode 100644 frontend/src/components/video/ConfigBox.tsx create mode 100644 frontend/src/components/video/ExportPanel.tsx delete mode 100644 frontend/src/components/video/ExportWidget.tsx delete mode 100644 frontend/src/components/video/MetadataBox.tsx delete mode 100644 frontend/src/components/video/PlaybackSlider.tsx delete mode 100644 frontend/src/components/video/Playbar.tsx create mode 100644 frontend/src/components/video/PreviewControls.tsx create mode 100644 frontend/src/components/video/TrimRange.tsx create mode 100644 frontend/src/lib/theme.ts delete mode 100644 frontend/src/pages/EditClip.tsx create mode 100644 frontend/src/pages/EditMedia.tsx delete mode 100644 frontend/src/pages/EditVod.tsx create mode 100644 frontend/src/pages/MediaLibrary.tsx delete mode 100644 frontend/src/pages/MyClips.tsx delete mode 100644 frontend/src/pages/MyVods.tsx create mode 100644 frontend/src/pages/NotFound.tsx diff --git a/frontend/index.html b/frontend/index.html index 9552ecd..5d9773a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,32 @@ - Vite + React + TS + + + + VoD System + + + + + +
diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e0c31a4..efa2840 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,13 +7,11 @@ "": { "name": "frontend", "version": "0.0.0", - "hasInstallScript": true, "dependencies": { "@react-oauth/google": "^0.12.2", "axios": "^1.11.0", "clsx": "^2.1.1", "dotenv": "^16.5.0", - "flowbite-react": "^0.11.8", "js-cookie": "^3.0.5", "lucide-react": "^0.511.0", "path": "^0.12.7", @@ -921,68 +919,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.9", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz", - "integrity": "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.9" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", - "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.8.0", - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/dom/node_modules/@floating-ui/core": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", - "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.12" - } - }, - "node_modules/@floating-ui/react": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.3.tgz", - "integrity": "sha512-CLHnes3ixIFFKVQDdICjel8muhFLOBdQH7fgtHNPY8UbCNqbeKZ262G7K66lGQOUQWWnYocf7ZbUsLJgGfsLHg==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.9", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=17.0.0", - "react-dom": ">=17.0.0" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", - "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.8.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", - "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", @@ -1049,12 +985,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@iarna/toml": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz", - "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==", - "license": "ISC" - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1105,41 +1035,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@react-oauth/google": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@react-oauth/google/-/google-0.12.2.tgz", @@ -1249,9 +1144,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1266,9 +1158,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1283,9 +1172,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1300,9 +1186,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1317,9 +1200,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1334,9 +1214,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1351,9 +1228,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1368,9 +1242,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1385,9 +1256,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1402,9 +1270,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1419,9 +1284,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1436,9 +1298,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1453,9 +1312,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1679,9 +1535,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1699,9 +1552,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1719,9 +1569,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1739,9 +1586,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2419,81 +2263,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", - "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", - "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.26.0", - "@typescript-eslint/visitor-keys": "8.26.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.0.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", - "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.65.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", @@ -2643,23 +2412,6 @@ "node": ">=10" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.26.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", - "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.26.0", - "eslint-visitor-keys": "^4.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -2756,24 +2508,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-timsort": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", - "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", - "license": "MIT" - }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2796,6 +2530,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { @@ -2822,18 +2557,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/browserslist": { "version": "4.28.7", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", @@ -2929,27 +2652,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", - "license": "MIT" - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2991,22 +2693,6 @@ "node": ">= 0.8" } }, - "node_modules/comment-json": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", - "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", - "license": "MIT", - "dependencies": { - "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3045,12 +2731,6 @@ "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3073,18 +2753,6 @@ "dev": true, "license": "MIT" }, - "node_modules/debounce": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-2.2.0.tgz", - "integrity": "sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -3109,15 +2777,6 @@ "dev": true, "license": "MIT" }, - "node_modules/deepmerge-ts": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.4.tgz", - "integrity": "sha512-fxqo6nHGQ9zOVgI4KXqtWXJR/yCLtC7aXIVq+6jc8tHPFUxlFmuUcm2kC4vztQ+LJxQ3gER/XAWearGYQ8niGA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3398,6 +3057,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3424,19 +3084,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -3490,34 +3137,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -3532,15 +3151,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3554,18 +3164,6 @@ "node": ">=16.0.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3604,36 +3202,6 @@ "dev": true, "license": "ISC" }, - "node_modules/flowbite-react": { - "version": "0.11.9", - "resolved": "https://registry.npmjs.org/flowbite-react/-/flowbite-react-0.11.9.tgz", - "integrity": "sha512-S8KwzbR2PcVv+iJDpIp62NrIjSkKIm6ZrwdssK0k4mDU8SvYSuecKstATrIxdHAIL3/FiT4VGNuCoTUSj4OcEQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "1.6.9", - "@floating-ui/react": "0.27.3", - "@iarna/toml": "2.2.5", - "@typescript-eslint/typescript-estree": "8.26.0", - "chokidar": "4.0.3", - "classnames": "2.5.1", - "comment-json": "4.2.5", - "debounce": "2.2.0", - "deepmerge-ts": "7.1.4", - "klona": "2.0.6", - "package-manager-detector": "0.2.9", - "recast": "0.23.11", - "tailwind-merge-v2": "npm:tailwind-merge@2.6.0", - "tailwind-merge-v3": "npm:tailwind-merge@3.0.1" - }, - "bin": { - "flowbite-react": "dist/cli/bin.js" - }, - "peerDependencies": { - "react": "^18 || ^19", - "react-dom": "^18 || ^19", - "tailwindcss": "^3 || ^4" - } - }, "node_modules/follow-redirects": { "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", @@ -3796,15 +3364,6 @@ "node": ">=8" } }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3904,6 +3463,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3913,6 +3473,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3921,15 +3482,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4040,15 +3592,6 @@ "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==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4206,9 +3749,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4230,9 +3770,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4254,9 +3791,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4278,9 +3812,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4397,28 +3928,6 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4545,12 +4054,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-manager-detector": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.9.tgz", - "integrity": "sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==", - "license": "MIT" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4601,18 +4104,6 @@ "dev": true, "license": "ISC" }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/postcss": { "version": "8.5.23", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", @@ -4680,26 +4171,6 @@ "node": ">=6" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -4792,44 +4263,6 @@ "react-dom": ">=18" } }, - "node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -4840,16 +4273,6 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", @@ -4895,29 +4318,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -4963,15 +4363,6 @@ "node": ">=8" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5008,38 +4399,11 @@ "node": ">=8" } }, - "node_modules/tabbable": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", - "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", - "license": "MIT" - }, - "node_modules/tailwind-merge-v2": { - "name": "tailwind-merge", - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwind-merge-v3": { - "name": "tailwind-merge", - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.0.1.tgz", - "integrity": "sha512-AvzE8FmSoXC7nC+oU5GlQJbip2UO7tmOhOfQyOmPhrStOGXHU08j8mZEHZ4BmCqY5dWTCo4ClWkNyRNx1wpT0g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, "node_modules/tailwindcss": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", + "dev": true, "license": "MIT" }, "node_modules/tapable": { @@ -5056,12 +4420,6 @@ "url": "https://opencollective.com/webpack" } }, - "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==", - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -5110,22 +4468,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -5138,7 +4485,9 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" + "dev": true, + "license": "0BSD", + "optional": true }, "node_modules/type-check": { "version": "0.4.0", @@ -5157,6 +4506,7 @@ "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/frontend/package.json b/frontend/package.json index 1799591..894d736 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,15 +8,13 @@ "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", - "preview": "vite preview", - "postinstall": "flowbite-react patch" + "preview": "vite preview" }, "dependencies": { "@react-oauth/google": "^0.12.2", "axios": "^1.11.0", "clsx": "^2.1.1", "dotenv": "^16.5.0", - "flowbite-react": "^0.11.8", "js-cookie": "^3.0.5", "lucide-react": "^0.511.0", "path": "^0.12.7", diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index b9d355d..0000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,42 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: filter 300ms; -} -.logo:hover { - filter: drop-shadow(0 0 2em #646cffaa); -} -.logo.react:hover { - filter: drop-shadow(0 0 2em #61dafbaa); -} - -@keyframes logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} - -@media (prefers-reduced-motion: no-preference) { - a:nth-of-type(2) .logo { - animation: logo-spin infinite 20s linear; - } -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 17b39f5..856cb17 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,48 +1,98 @@ - // App.jsx -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import MainLayout from './layouts/MainLayout'; -import ClipUpload from './pages/ClipUpload'; -import ClipEdit from './pages/ClipEdit'; -import Home from './pages/Home'; -import {useEffect} from "react"; -import MyClips from './pages/MyClips'; -import MyVods from './pages/MyVods'; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import { GoogleOAuthProvider } from "@react-oauth/google"; +import { Film, Video } from "lucide-react"; + +import ThemeProvider from "./components/ThemeProvider.tsx"; +import MainLayout from "./layouts/MainLayout.tsx"; + +import Home from "./pages/Home.tsx"; +import ClipUpload from "./pages/ClipUpload.tsx"; +import ClipEdit from "./pages/ClipEdit.tsx"; +import MediaLibrary from "./pages/MediaLibrary.tsx"; import MediaPlayer from "./pages/MediaPlayer.tsx"; -import { getVodById, getVodMediaUrl } from "./utils/api/vods.ts"; -import { getClipById, getClipMediaUrl } from "./utils/api/clips.ts"; -import EditClip from "./pages/EditClip.tsx"; -import EditVod from "./pages/EditVod.tsx"; +import EditMedia from "./pages/EditMedia.tsx"; import Profile from "./pages/Profile.tsx"; import StreamControl from "./pages/StreamControl.tsx"; -import { GoogleOAuthProvider } from '@react-oauth/google'; +import NotFound from "./pages/NotFound.tsx"; +import { getClips, getClipById, getClipMediaUrl, patchClip, deleteClip } from "./utils/api/clips.ts"; +import { getVods, getVodById, getVodMediaUrl, patchVod, deleteVod } from "./utils/api/vods.ts"; function App() { - useEffect(() => { - document.title = "VoD System"; - }, []); - return ( - - - }> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - + + + + }> + } /> + } /> + } /> + + `/clips/${id}/edit`} + playerPath="/video" + mediaApiPath="/api/v1/clips" + emptyIcon={} + emptyTitle="No clips yet" + emptyDescription="Upload a video or clip a moment from a live stream to get started." + /> + } + /> + `/vods/${id}/edit`} + playerPath="/vod" + mediaApiPath="/api/v1/vods" + emptyIcon={ + + + ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/assets/react.svg b/frontend/src/assets/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/frontend/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/src/components/Box.tsx b/frontend/src/components/Box.tsx deleted file mode 100644 index 31a3872..0000000 --- a/frontend/src/components/Box.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import clsx from "clsx"; - -type Props = { - children: React.ReactNode; - className?: string; -}; - -const Box = ({ children, className }: Props) => ( -
- {children} -
-); - -export default Box; diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx deleted file mode 100644 index db384e0..0000000 --- a/frontend/src/components/Dropdown.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useState, useEffect, useRef } from "react"; -import clsx from "clsx"; - -type DropdownItemProps = { - item: string; - onClick: (item: string) => void; - className?: string; -}; - -const DropdownItem = ({ item, onClick, className }: DropdownItemProps) => ( -
  • onClick(item)} - > - {item} -
  • -); - -type DropdownProps = { - label: React.ReactNode; - children: React.ReactNode; - className?: string; -}; - -const Dropdown = ({ label, children, className }: DropdownProps) => { - const [isOpen, setIsOpen] = useState(false); - const ref = useRef(null); - - useEffect(() => { - const handleClickOutside = (e: MouseEvent) => { - if (ref.current && !ref.current.contains(e.target as Node)) { - setIsOpen(false); - } - }; - document.addEventListener('mousedown', handleClickOutside); - return () => document.removeEventListener('mousedown', handleClickOutside); - }, []); - - return ( -
    - - {isOpen && ( -
      - {children} -
    - )} -
    - ); -}; - -export { Dropdown, DropdownItem }; diff --git a/frontend/src/components/Selector.tsx b/frontend/src/components/Selector.tsx deleted file mode 100644 index 8eab14f..0000000 --- a/frontend/src/components/Selector.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from "react"; - -type Props = { - children: React.ReactNode; - label: string; -}; - -const Selector = ({ children, label }: Props) => ( -
    - -
    - {children} -
    -); - -export default Selector; diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index bb33d56..6d116d1 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,52 +1,143 @@ import clsx from "clsx"; -import SidebarButton from "./buttons/SidebarButton.tsx"; -import { Plus, Film, Home, User as UserIcon, Video, Radio } from 'lucide-react'; -import type {User} from "../utils/types.ts"; +import { Film, Home, LogOut, Plus, Radio, User as UserIcon, Video } from "lucide-react"; +import { Link, NavLink, useNavigate } from "react-router-dom"; +import type { ReactNode } from "react"; +import type { User } from "../utils/types.ts"; +import Menu, { MenuItem, MenuSeparator } from "./ui/Menu.tsx"; +import { LiveDot } from "./ui/Badge.tsx"; -type props = { +type Props = { user: User | null; - className?: string -} + isStreaming: boolean; + onLogout: () => void; + /** Closes the mobile drawer after a navigation. */ + onNavigate: () => void; + className?: string; +}; -const Divider = () =>
    ; +const SidebarLink = ({ to, icon, label, end = false, trailing, onNavigate }: { + to: string; + icon: ReactNode; + label: string; + end?: boolean; + trailing?: ReactNode; + onNavigate: () => void; +}) => ( + + {({ isActive }) => ( + + + {icon} + + {label} + {trailing && {trailing}} + + )} + +); + +const SectionLabel = ({ children }: { children: ReactNode }) => ( +

    + {children} +

    +); + +const Sidebar = ({ user, isStreaming, onLogout, onNavigate, className }: Props) => { + const navigate = useNavigate(); -const Sidebar = ({user, className}: props) => { return ( - ); }; diff --git a/frontend/src/components/ThemeProvider.tsx b/frontend/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..7a24d39 --- /dev/null +++ b/frontend/src/components/ThemeProvider.tsx @@ -0,0 +1,26 @@ +import { useCallback, useMemo, useState, type ReactNode } from "react"; +import { readInitialTheme, THEME_STORAGE_KEY, ThemeContext, type Theme } from "../lib/theme.ts"; + +const ThemeProvider = ({ children }: { children: ReactNode }) => { + const [theme, setThemeState] = useState(readInitialTheme); + + const setTheme = useCallback((next: Theme) => { + setThemeState(next); + document.documentElement.classList.toggle("dark", next === "dark"); + try { + localStorage.setItem(THEME_STORAGE_KEY, next); + } catch { + /* Private mode or blocked storage — the theme just won't persist. */ + } + }, []); + + const value = useMemo(() => ({ + theme, + setTheme, + toggleTheme: () => setTheme(theme === "dark" ? "light" : "dark"), + }), [theme, setTheme]); + + return {children}; +}; + +export default ThemeProvider; diff --git a/frontend/src/components/Topbar.tsx b/frontend/src/components/Topbar.tsx index ccf897f..28c558c 100644 --- a/frontend/src/components/Topbar.tsx +++ b/frontend/src/components/Topbar.tsx @@ -1,103 +1,69 @@ -import { Menu, X } from 'lucide-react'; -import { login, logout } from "../utils/api/users.ts"; -import { Dropdown, DropdownItem } from "./Dropdown.tsx"; -import { GoogleLogin } from '@react-oauth/google'; - -import type { User } from "../utils/types.ts"; -import type { CredentialResponse } from '@react-oauth/google'; - -import MenuButton from "./buttons/MenuButton.tsx"; import clsx from "clsx"; -import {useNavigate} from "react-router-dom"; +import { PanelLeft, PanelLeftClose } from "lucide-react"; +import { GoogleLogin } from "@react-oauth/google"; +import type { CredentialResponse } from "@react-oauth/google"; +import { Link } from "react-router-dom"; +import IconButton from "./ui/IconButton.tsx"; +import ThemeToggle from "./ui/ThemeToggle.tsx"; +import Badge, { LiveDot } from "./ui/Badge.tsx"; +import { useTheme } from "../lib/theme.ts"; +import type { User } from "../utils/types.ts"; -type props = { - sidebarToggled: boolean; - setSidebarToggled: (toggled: boolean) => void; +type Props = { user: User | null; - fetchUser: () => void; isStreaming: boolean; + sidebarOpen: boolean; + onToggleSidebar: () => void; + onLogin: (response: CredentialResponse) => void; className?: string; -} +}; -const Topbar = ({ - sidebarToggled, - setSidebarToggled, - user, - fetchUser, - isStreaming, - className}: props) => { - - const navigate = useNavigate(); - - const handleLogin = (response: CredentialResponse) => { - if (!response.credential) { - console.error("No credential received from Google login."); - return; - } - - login(response.credential) - .then(() => { - fetchUser(); - navigate(0); - }) - .catch((error) => { - console.error("Login failed:", error); - }); - } - - const handleLogout = () => { - logout() - .then(() => { - fetchUser(); - navigate("/"); - }) - .catch((error) => { - console.error("Logout failed:", error); - }); - } +const Topbar = ({ user, isStreaming, sidebarOpen, onToggleSidebar, onLogin, className }: Props) => { + const { theme } = useTheme(); return ( -
    - setSidebarToggled(!sidebarToggled)}> - {sidebarToggled ? : } - - - { user ? ( -
    - - - - {user.name} - {isStreaming && ( - - - - - )} - - }> - navigate("/profile")} /> - handleLogout()} - className={"text-red-500 font-medium"} /> - -
    - ) : - ( - handleLogin(credentialResponse)} /> +
    - ) -} + > + + {sidebarOpen ? : } + -export default Topbar; \ No newline at end of file +
    + + {user && isStreaming && ( + + + + Live now + + + )} + + + + {!user && ( +
    + console.error("Google login failed")} + /> +
    + )} +
    + ); +}; + +export default Topbar; diff --git a/frontend/src/components/buttons/BlueButton.tsx b/frontend/src/components/buttons/BlueButton.tsx deleted file mode 100644 index 4dce83e..0000000 --- a/frontend/src/components/buttons/BlueButton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -type Props = React.ButtonHTMLAttributes; - -const BlueButton: React.FC = ({ className = "", ...props }) => { - return ( - +); + +export default Button; diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx new file mode 100644 index 0000000..9feff93 --- /dev/null +++ b/frontend/src/components/ui/Card.tsx @@ -0,0 +1,47 @@ +import clsx from "clsx"; +import type { HTMLAttributes, ReactNode } from "react"; + +type Props = HTMLAttributes & { + children: ReactNode; + /** `flat` drops the shadow — for cards sitting inside another surface. */ + tone?: "raised" | "flat"; +}; + +const Card = ({ children, tone = "raised", className, ...props }: Props) => ( +
    + {children} +
    +); + +type HeaderProps = { + icon?: ReactNode; + title: string; + description?: string; + action?: ReactNode; + className?: string; +}; + +/** Icon + title + optional supporting copy, the standard card lead-in. */ +export const CardHeader = ({ icon, title, description, action, className }: HeaderProps) => ( +
    + {icon && ( +
    + {icon} +
    + )} +
    +

    {title}

    + {description &&

    {description}

    } +
    + {action} +
    +); + +export default Card; diff --git a/frontend/src/components/ui/EmptyState.tsx b/frontend/src/components/ui/EmptyState.tsx new file mode 100644 index 0000000..f94fe9e --- /dev/null +++ b/frontend/src/components/ui/EmptyState.tsx @@ -0,0 +1,31 @@ +import type { ReactNode } from "react"; +import clsx from "clsx"; + +type Props = { + icon?: ReactNode; + title: string; + description?: string; + action?: ReactNode; + className?: string; +}; + +const EmptyState = ({ icon, title, description, action, className }: Props) => ( +
    + {icon && ( +
    + {icon} +
    + )} +

    {title}

    + {description &&

    {description}

    } + {action &&
    {action}
    } +
    +); + +export default EmptyState; diff --git a/frontend/src/components/ui/Field.tsx b/frontend/src/components/ui/Field.tsx new file mode 100644 index 0000000..5b04184 --- /dev/null +++ b/frontend/src/components/ui/Field.tsx @@ -0,0 +1,42 @@ +import clsx from "clsx"; +import { useId, type InputHTMLAttributes, type ReactNode, type SelectHTMLAttributes, type TextareaHTMLAttributes } from "react"; + +type FieldProps = { + label?: string; + hint?: string; + error?: string | null; + children: (id: string) => ReactNode; + className?: string; +}; + +/** Label + control + hint/error, wired together with a generated id. */ +export const Field = ({ label, hint, error, children, className }: FieldProps) => { + const id = useId(); + return ( +
    + {label && ( + + )} + {children(id)} + {error ? ( +

    {error}

    + ) : hint ? ( +

    {hint}

    + ) : null} +
    + ); +}; + +export const Input = ({ className, ...props }: InputHTMLAttributes) => ( + +); + +export const Textarea = ({ className, ...props }: TextareaHTMLAttributes) => ( +