forked from tpd94/CDRM-Project
UI overhaul
This commit is contained in:
parent
cc3b37db1d
commit
f83d22c09e
@ -4,5 +4,6 @@
|
|||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"printWidth": 100
|
"printWidth": 100,
|
||||||
|
"plugins": ["prettier-plugin-tailwindcss"]
|
||||||
}
|
}
|
||||||
|
137
cdrm-frontend/package-lock.json
generated
137
cdrm-frontend/package-lock.json
generated
@ -12,8 +12,10 @@
|
|||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.7.0",
|
"react-router-dom": "^7.7.0",
|
||||||
"shaka-player": "^4.15.8",
|
"shaka-player": "^4.15.8",
|
||||||
|
"sonner": "^2.0.6",
|
||||||
"tailwindcss": "^4.1.11"
|
"tailwindcss": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -22,10 +24,12 @@
|
|||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.11.0",
|
"@vitejs/plugin-react-swc": "^3.11.0",
|
||||||
|
"daisyui": "^5.0.46",
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"vite": "^7.0.5"
|
"vite": "^7.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2137,6 +2141,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/daisyui": {
|
||||||
|
"version": "5.0.46",
|
||||||
|
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.0.46.tgz",
|
||||||
|
"integrity": "sha512-vMDZK1tI/bOb2Mc3Mk5WpquBG3ZqBz1YKZ0xDlvpOvey60dOS4/5Qhdowq1HndbQl7PgDLDYysxAjjUjwR7/eQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.4.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
|
||||||
@ -3479,6 +3493,110 @@
|
|||||||
"node": ">= 0.8.0"
|
"node": ">= 0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
|
||||||
|
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": 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.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz",
|
||||||
|
"integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.21.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "*",
|
||||||
|
"@prettier/plugin-hermes": "*",
|
||||||
|
"@prettier/plugin-oxc": "*",
|
||||||
|
"@prettier/plugin-pug": "*",
|
||||||
|
"@shopify/prettier-plugin-liquid": "*",
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "*",
|
||||||
|
"@zackad/prettier-plugin-twig": "*",
|
||||||
|
"prettier": "^3.0",
|
||||||
|
"prettier-plugin-astro": "*",
|
||||||
|
"prettier-plugin-css-order": "*",
|
||||||
|
"prettier-plugin-import-sort": "*",
|
||||||
|
"prettier-plugin-jsdoc": "*",
|
||||||
|
"prettier-plugin-marko": "*",
|
||||||
|
"prettier-plugin-multiline-arrays": "*",
|
||||||
|
"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-hermes": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-oxc": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@prettier/plugin-pug": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@shopify/prettier-plugin-liquid": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@trivago/prettier-plugin-sort-imports": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@zackad/prettier-plugin-twig": {
|
||||||
|
"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-multiline-arrays": {
|
||||||
|
"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/proxy-from-env": {
|
"node_modules/proxy-from-env": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
@ -3516,6 +3634,15 @@
|
|||||||
"react": "^19.1.0"
|
"react": "^19.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-icons": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.17.0",
|
"version": "0.17.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
|
||||||
@ -3667,6 +3794,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sonner": {
|
||||||
|
"version": "2.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.6.tgz",
|
||||||
|
"integrity": "sha512-yHFhk8T/DK3YxjFQXIrcHT1rGEeTLliVzWbO0xN8GberVun2RiBnxAjXAYpZrqwEVHBG9asI/Li8TAAhN9m59Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
@ -14,8 +14,10 @@
|
|||||||
"axios": "^1.10.0",
|
"axios": "^1.10.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-icons": "^5.5.0",
|
||||||
"react-router-dom": "^7.7.0",
|
"react-router-dom": "^7.7.0",
|
||||||
"shaka-player": "^4.15.8",
|
"shaka-player": "^4.15.8",
|
||||||
|
"sonner": "^2.0.6",
|
||||||
"tailwindcss": "^4.1.11"
|
"tailwindcss": "^4.1.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -24,10 +26,12 @@
|
|||||||
"@types/react-dom": "^19.1.6",
|
"@types/react-dom": "^19.1.6",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.11.0",
|
"@vitejs/plugin-react-swc": "^3.11.0",
|
||||||
|
"daisyui": "^5.0.46",
|
||||||
"eslint": "^9.31.0",
|
"eslint": "^9.31.0",
|
||||||
"eslint-plugin-react-hooks": "^5.2.0",
|
"eslint-plugin-react-hooks": "^5.2.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.20",
|
"eslint-plugin-react-refresh": "^0.4.20",
|
||||||
"globals": "^16.3.0",
|
"globals": "^16.3.0",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
"vite": "^7.0.5"
|
"vite": "^7.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,48 +1,19 @@
|
|||||||
import { useState } from "react";
|
import { Route, Routes } from "react-router-dom";
|
||||||
import Home from "./components/Pages/HomePage";
|
|
||||||
import Cache from "./components/Pages/Cache";
|
|
||||||
import API from "./components/Pages/API";
|
|
||||||
import TestPlayer from "./components/Pages/TestPlayer";
|
|
||||||
import NavBar from "./components/NavBar";
|
|
||||||
import NavBarMain from "./components/NavBarMain";
|
|
||||||
import SideMenu from "./components/SideMenu"; // Add this import
|
|
||||||
import Account from "./components/Pages/Account";
|
import Account from "./components/Pages/Account";
|
||||||
import { Routes, Route } from "react-router-dom";
|
import API from "./components/Pages/API";
|
||||||
|
import Cache from "./components/Pages/Cache";
|
||||||
|
import Home from "./components/Pages/HomePage";
|
||||||
|
import TestPlayer from "./components/Pages/TestPlayer";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false); // Track if the menu is open
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="appcontainer" className="flex flex-row w-full h-full bg-black">
|
<Routes>
|
||||||
{/* The SideMenu should be visible when isMenuOpen is true */}
|
<Route path="/" element={<Home />} />
|
||||||
<SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
|
<Route path="/cache" element={<Cache />} />
|
||||||
|
<Route path="/api" element={<API />} />
|
||||||
<div
|
<Route path="/testplayer" element={<TestPlayer />} />
|
||||||
id="navbarcontainer"
|
<Route path="/account" element={<Account />} />
|
||||||
className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shrink-0"
|
</Routes>
|
||||||
>
|
|
||||||
<NavBar />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="maincontainer" className="w-full lg:w-5/6 bg-gray-950/50 flex flex-col grow">
|
|
||||||
<div
|
|
||||||
id="navbarmaincontainer"
|
|
||||||
className="w-full lg:hidden h-16 bg-gray-950/10 border-b border-white/5 sticky top-0 z-10"
|
|
||||||
>
|
|
||||||
<NavBarMain setIsMenuOpen={setIsMenuOpen} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="maincontentcontainer" className="w-full grow overflow-y-auto">
|
|
||||||
<Routes>
|
|
||||||
<Route path="/" element={<Home />} />
|
|
||||||
<Route path="/cache" element={<Cache />} />
|
|
||||||
<Route path="/api" element={<API />} />
|
|
||||||
<Route path="/testplayer" element={<TestPlayer />} />
|
|
||||||
<Route path="/account" element={<Account />} />
|
|
||||||
</Routes>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BIN
cdrm-frontend/src/assets/fonts/InterVariable-Italic.woff2
Normal file
BIN
cdrm-frontend/src/assets/fonts/InterVariable-Italic.woff2
Normal file
Binary file not shown.
BIN
cdrm-frontend/src/assets/fonts/InterVariable.woff2
Normal file
BIN
cdrm-frontend/src/assets/fonts/InterVariable.woff2
Normal file
Binary file not shown.
15
cdrm-frontend/src/assets/fonts/font-face.css
Normal file
15
cdrm-frontend/src/assets/fonts/font-face.css
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
src: url("./InterVariable.woff2");
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Inter;
|
||||||
|
src: url("./InterVariable-Italic.woff2");
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
9
cdrm-frontend/src/components/Container.jsx
Normal file
9
cdrm-frontend/src/components/Container.jsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const Container = ({ children, className = "", ...props }) => {
|
||||||
|
return (
|
||||||
|
<main className={`container mx-auto p-4 mb-5 ${className}`} {...props}>
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Container;
|
@ -1,13 +1,13 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink } from "react-router-dom";
|
||||||
import homeIcon from "../assets/icons/home.svg";
|
import { FaDiscord } from "react-icons/fa";
|
||||||
import cacheIcon from "../assets/icons/cache.svg";
|
import { FaTelegram } from "react-icons/fa";
|
||||||
import apiIcon from "../assets/icons/api.svg";
|
import { SiGitea } from "react-icons/si";
|
||||||
import testPlayerIcon from "../assets/icons/testplayer.svg";
|
import { FaHome } from "react-icons/fa";
|
||||||
import accountIcon from "../assets/icons/account.svg";
|
import { FaDatabase } from "react-icons/fa";
|
||||||
import discordIcon from "../assets/icons/discord.svg";
|
import { IoCodeSlashSharp } from "react-icons/io5";
|
||||||
import telegramIcon from "../assets/icons/telegram.svg";
|
import { FaVideo } from "react-icons/fa";
|
||||||
import giteaIcon from "../assets/icons/gitea.svg";
|
import { RiAccountCircleFill } from "react-icons/ri";
|
||||||
|
|
||||||
function NavBar() {
|
function NavBar() {
|
||||||
const [externalLinks, setExternalLinks] = useState({
|
const [externalLinks, setExternalLinks] = useState({
|
||||||
@ -23,152 +23,156 @@ function NavBar() {
|
|||||||
.catch((error) => console.error("Error fetching links:", error));
|
.catch((error) => console.error("Error fetching links:", error));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const MenuItem = ({ to, children }) => {
|
||||||
|
return (
|
||||||
|
<li>
|
||||||
|
<NavLink to={to} className={({ isActive }) => (isActive ? "menu-active" : "")}>
|
||||||
|
{children}
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full bg-white/1">
|
<>
|
||||||
{/* Header */}
|
<div className="navbar sticky top-0 z-300 bg-slate-700 shadow-sm text-white">
|
||||||
<div>
|
<div className="navbar-start">
|
||||||
<p className="text-white text-2xl font-bold p-3 text-center mb-5">
|
<div className="dropdown">
|
||||||
<a href="/">CDRM-Project</a>
|
<div tabIndex={0} role="button" className="btn btn-ghost lg:hidden">
|
||||||
</p>
|
<svg
|
||||||
</div>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4 6h16M4 12h8m-8 6h16"
|
||||||
|
/>{" "}
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<ul
|
||||||
|
tabIndex={0}
|
||||||
|
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow"
|
||||||
|
>
|
||||||
|
<MenuItem to="/">
|
||||||
|
<FaHome alt="Home" width={20} height={20} />
|
||||||
|
Home
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem to="/cache">
|
||||||
|
<FaDatabase alt="Cache" width={20} height={20} />
|
||||||
|
Cache
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem to="/api">
|
||||||
|
<IoCodeSlashSharp alt="API" width={20} height={20} />
|
||||||
|
API
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem to="/testplayer">
|
||||||
|
<FaVideo alt="Test Player" width={20} height={20} />
|
||||||
|
Test Player
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem to="/account">
|
||||||
|
<RiAccountCircleFill alt="My Account" width={20} height={20} />
|
||||||
|
My Account
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
{/* Scrollable navigation area */}
|
<div className="divider">Social links</div>
|
||||||
<div className="overflow-y-auto grow flex flex-col">
|
<li>
|
||||||
{/* Main NavLinks */}
|
<a
|
||||||
<NavLink
|
href={externalLinks.discord}
|
||||||
to="/"
|
target="_blank"
|
||||||
className={({ isActive }) =>
|
rel="noopener noreferrer"
|
||||||
`flex flex-row p-3 border-l-3 ${
|
>
|
||||||
isActive
|
<FaDiscord alt="Discord" width={20} height={20} />
|
||||||
? "border-l-sky-500/50 bg-black/50"
|
Discord
|
||||||
: "hover:border-l-sky-500/50 hover:bg-white/5"
|
</a>
|
||||||
}`
|
</li>
|
||||||
}
|
<li>
|
||||||
>
|
<a
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
href={externalLinks.telegram}
|
||||||
<img src={homeIcon} alt="Home" className="w-1/2 cursor-pointer" />
|
target="_blank"
|
||||||
</button>
|
rel="noopener noreferrer"
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
>
|
||||||
Home
|
<FaTelegram alt="Telegram" width={20} height={20} />
|
||||||
</p>
|
Telegram
|
||||||
</NavLink>
|
</a>
|
||||||
|
</li>
|
||||||
<NavLink
|
<li>
|
||||||
to="/cache"
|
<a
|
||||||
className={({ isActive }) =>
|
href={externalLinks.gitea}
|
||||||
`flex flex-row p-3 border-l-3 ${
|
target="_blank"
|
||||||
isActive
|
rel="noopener noreferrer"
|
||||||
? "border-l-emerald-500/50 bg-black/50"
|
>
|
||||||
: "hover:border-l-emerald-500/50 hover:bg-white/5"
|
<SiGitea alt="Gitea" width={20} height={20} />
|
||||||
}`
|
Gitea
|
||||||
}
|
</a>
|
||||||
>
|
</li>
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
</ul>
|
||||||
<img src={cacheIcon} alt="Cache" className="w-1/2 cursor-pointer" />
|
</div>
|
||||||
</button>
|
<a className="btn btn-ghost text-xl">CDRM-Project</a>
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
</div>
|
||||||
Cache
|
<div className="navbar-center hidden lg:flex">
|
||||||
</p>
|
<ul className="menu menu-horizontal px-1">
|
||||||
</NavLink>
|
<MenuItem to="/">
|
||||||
|
<FaHome alt="Home" width={20} height={20} />
|
||||||
<NavLink
|
Home
|
||||||
to="/api"
|
</MenuItem>
|
||||||
className={({ isActive }) =>
|
<MenuItem to="/cache">
|
||||||
`flex flex-row p-3 border-l-3 ${
|
<FaDatabase alt="Cache" width={20} height={20} />
|
||||||
isActive
|
Cache
|
||||||
? "border-l-indigo-500/50 bg-black/50"
|
</MenuItem>
|
||||||
: "hover:border-l-indigo-500/50 hover:bg-white/5"
|
<MenuItem to="/api">
|
||||||
}`
|
<IoCodeSlashSharp alt="API" width={20} height={20} />
|
||||||
}
|
API
|
||||||
>
|
</MenuItem>
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
<MenuItem to="/testplayer">
|
||||||
<img src={apiIcon} alt="API" className="w-1/2 cursor-pointer" />
|
<FaVideo alt="Test Player" width={20} height={20} />
|
||||||
</button>
|
Test Player
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
</MenuItem>
|
||||||
API
|
<MenuItem to="/account">
|
||||||
</p>
|
<RiAccountCircleFill alt="My Account" width={20} height={20} />
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
<NavLink
|
|
||||||
to="/testplayer"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row p-3 border-l-3 ${
|
|
||||||
isActive
|
|
||||||
? "border-l-rose-500/50 bg-black/50"
|
|
||||||
: "hover:border-l-rose-500/50 hover:bg-white/5"
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
|
||||||
<img
|
|
||||||
src={testPlayerIcon}
|
|
||||||
alt="Test Player"
|
|
||||||
className="w-1/2 cursor-pointer"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
|
||||||
Test Player
|
|
||||||
</p>
|
|
||||||
</NavLink>
|
|
||||||
|
|
||||||
{/* Account link at bottom of scrollable area */}
|
|
||||||
<div className="mt-auto">
|
|
||||||
<NavLink
|
|
||||||
to="/account"
|
|
||||||
className={({ isActive }) =>
|
|
||||||
`flex flex-row p-3 border-l-3 ${
|
|
||||||
isActive
|
|
||||||
? "border-l-yellow-500/50 bg-black/50"
|
|
||||||
: "hover:border-l-yellow-500/50 hover:bg-white/5"
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
|
|
||||||
<img src={accountIcon} alt="Account" className="w-1/2 cursor-pointer" />
|
|
||||||
</button>
|
|
||||||
<p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
|
|
||||||
My Account
|
My Account
|
||||||
</p>
|
</MenuItem>
|
||||||
</NavLink>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div className="navbar-end hidden lg:flex">
|
||||||
|
<a
|
||||||
|
className="btn btn-ghost hover:text-indigo-400"
|
||||||
|
href={externalLinks.discord}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="tooltip tooltip-bottom" data-tip="CDRM Discord">
|
||||||
|
<FaDiscord className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="btn btn-ghost hover:text-sky-400"
|
||||||
|
href={externalLinks.telegram}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="tooltip tooltip-bottom" data-tip="CDRM Telegram">
|
||||||
|
<FaTelegram className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="btn btn-ghost hover:text-lime-400"
|
||||||
|
href={externalLinks.gitea}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<div className="tooltip tooltip-left" data-tip="CDRM Gitea">
|
||||||
|
<SiGitea className="h-6 w-6" />
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
{/* External links at very bottom */}
|
|
||||||
<div className="flex flex-row w-full h-16 bg-black/25">
|
|
||||||
<a
|
|
||||||
href={externalLinks.discord}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-950 group"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={discordIcon}
|
|
||||||
alt="Discord"
|
|
||||||
className="w-1/2 group-hover:animate-bounce"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={externalLinks.telegram}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-blue-400 group"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src={telegramIcon}
|
|
||||||
alt="Telegram"
|
|
||||||
className="w-1/2 group-hover:animate-bounce"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
href={externalLinks.gitea}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer hover:bg-green-700 group"
|
|
||||||
>
|
|
||||||
<img src={giteaIcon} alt="Gitea" className="w-1/2 group-hover:animate-bounce" />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
import Container from "../Container";
|
||||||
|
import { FaCopy } from "react-icons/fa";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const { protocol, hostname, port } = window.location;
|
const { protocol, hostname, port } = window.location;
|
||||||
|
|
||||||
@ -10,6 +14,11 @@ if (
|
|||||||
fullHost += `:${port}`;
|
fullHost += `:${port}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleCopy = (text) => {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
toast.success("Copied to clipboard");
|
||||||
|
};
|
||||||
|
|
||||||
function API() {
|
function API() {
|
||||||
const [deviceInfo, setDeviceInfo] = useState({
|
const [deviceInfo, setDeviceInfo] = useState({
|
||||||
device_type: "",
|
device_type: "",
|
||||||
@ -61,13 +70,7 @@ function API() {
|
|||||||
document.title = "API | CDRM-Project";
|
document.title = "API | CDRM-Project";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const decryptRequest = `import requests
|
||||||
<div className="flex flex-col w-full overflow-y-auto p-4 text-white">
|
|
||||||
<details open className="w-full list-none">
|
|
||||||
<summary className="text-2xl">Sending a decryption request</summary>
|
|
||||||
<div className="mt-5 p-5 rounded-lg border-2 border-indigo-500/50">
|
|
||||||
<pre className="rounded-lg font-mono whitespace-pre-wrap text-white overflow-auto">
|
|
||||||
{`import requests
|
|
||||||
|
|
||||||
print(requests.post(
|
print(requests.post(
|
||||||
url='${fullHost}/api/decrypt',
|
url='${fullHost}/api/decrypt',
|
||||||
@ -83,55 +86,154 @@ print(requests.post(
|
|||||||
'Accept-Language': 'en-US,en;q=0.5',
|
'Accept-Language': 'en-US,en;q=0.5',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
).json()['message'])`}
|
).json()['message'])`;
|
||||||
</pre>
|
|
||||||
</div>
|
const searchRequest = `import requests
|
||||||
</details>
|
|
||||||
<details open className="w-full list-none mt-5">
|
|
||||||
<summary className="text-2xl">Sending a search request</summary>
|
|
||||||
<div className="mt-5 border-2 border-indigo-500/50 p-5 rounded-lg">
|
|
||||||
<pre className="rounded-lg font-mono whitespace-pre text-white overflow-x-auto max-w-full p-5">
|
|
||||||
{`import requests
|
|
||||||
|
|
||||||
print(requests.post(
|
print(requests.post(
|
||||||
url='${fullHost}/api/cache/search',
|
url='${fullHost}/api/cache/search',
|
||||||
json={
|
json={
|
||||||
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
|
||||||
}
|
}
|
||||||
).json())`}
|
).json())`;
|
||||||
</pre>
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<NavBar />
|
||||||
|
<Container>
|
||||||
|
<div className="mx-auto flex w-full max-w-2xl flex-col justify-center py-8">
|
||||||
|
<div className="join join-vertical w-full max-w-2xl">
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
className="collapse-arrow join-item collapse border border-gray-600"
|
||||||
|
>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<div className="collapse-title text-lg font-semibold">
|
||||||
|
Sending a decryption request
|
||||||
|
</div>
|
||||||
|
<div className="collapse-content text-slate-300">
|
||||||
|
<pre className="my-4 overflow-auto rounded-lg font-mono break-all whitespace-pre-wrap">
|
||||||
|
{decryptRequest}
|
||||||
|
</pre>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => handleCopy(decryptRequest)}
|
||||||
|
>
|
||||||
|
<FaCopy /> Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
className="collapse-arrow join-item collapse border border-gray-600"
|
||||||
|
>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<div className="collapse-title text-lg font-semibold">
|
||||||
|
Sending a search request
|
||||||
|
</div>
|
||||||
|
<div className="collapse-content text-slate-300">
|
||||||
|
<pre className="my-4 overflow-auto rounded-lg font-mono break-all whitespace-pre-wrap">
|
||||||
|
{searchRequest}
|
||||||
|
</pre>
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-primary"
|
||||||
|
onClick={() => handleCopy(searchRequest)}
|
||||||
|
>
|
||||||
|
<FaCopy /> Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
className="collapse-arrow join-item collapse border border-gray-600"
|
||||||
|
>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<div className="collapse-title text-lg font-semibold">
|
||||||
|
PyWidevine RemoteCDM info
|
||||||
|
</div>
|
||||||
|
<div className="collapse-content text-slate-300">
|
||||||
|
<p>
|
||||||
|
<strong>Device Type:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{deviceInfo.device_type || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>System ID:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{deviceInfo.system_id || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Security Level:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{deviceInfo.security_level || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Host:</strong>{" "}
|
||||||
|
<span className="font-mono">{fullHost}/remotecdm/widevine</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Secret:</strong>{" "}
|
||||||
|
<span className="font-mono">{deviceInfo.secret || "N/A"}</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Device Name:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{deviceInfo.device_name || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
tabIndex={0}
|
||||||
|
className="collapse-arrow join-item collapse border border-gray-600"
|
||||||
|
>
|
||||||
|
<input type="checkbox" defaultChecked />
|
||||||
|
<div className="collapse-title text-lg font-semibold">
|
||||||
|
PyPlayready RemoteCDM info
|
||||||
|
</div>
|
||||||
|
<div className="collapse-content text-slate-300">
|
||||||
|
<p>
|
||||||
|
<strong>Security Level:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{prDeviceInfo.security_level || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Host:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{fullHost}/remotecdm/playready
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Secret:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{prDeviceInfo.secret || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Device Name:</strong>{" "}
|
||||||
|
<span className="font-mono">
|
||||||
|
{prDeviceInfo.device_name || "N/A"}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</Container>
|
||||||
<details open className="w-full list-none mt-5">
|
</>
|
||||||
<summary className="text-2xl">PyWidevine RemoteCDM info</summary>
|
|
||||||
<div className="mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto">
|
|
||||||
<p>
|
|
||||||
<strong>Device Type:</strong> '{deviceInfo.device_type}'<br />
|
|
||||||
<strong>System ID:</strong> {deviceInfo.system_id}
|
|
||||||
<br />
|
|
||||||
<strong>Security Level:</strong> {deviceInfo.security_level}
|
|
||||||
<br />
|
|
||||||
<strong>Host:</strong> {fullHost}/remotecdm/widevine
|
|
||||||
<br />
|
|
||||||
<strong>Secret:</strong> '{deviceInfo.secret}'<br />
|
|
||||||
<strong>Device Name:</strong> {deviceInfo.device_name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
<details open className="w-full list-none mt-5">
|
|
||||||
<summary className="text-2xl">PyPlayready RemoteCDM info</summary>
|
|
||||||
<div className="mt-5 border-2 border-indigo-500/50 p-5 rounded-lg overflow-x-auto">
|
|
||||||
<p>
|
|
||||||
<strong>Security Level:</strong> {prDeviceInfo.security_level}
|
|
||||||
<br />
|
|
||||||
<strong>Host:</strong> {fullHost}/remotecdm/playready
|
|
||||||
<br />
|
|
||||||
<strong>Secret:</strong> '{prDeviceInfo.secret}'<br />
|
|
||||||
<strong>Device Name:</strong> {prDeviceInfo.device_name}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Container from "../Container";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
import MyAccount from "./MyAccount";
|
||||||
import Register from "./Register";
|
import Register from "./Register";
|
||||||
import MyAccount from "./MyAccount"; // <-- Import the MyAccount component
|
|
||||||
|
|
||||||
function Account() {
|
function Account() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(null); // null = loading state
|
const [isLoggedIn, setIsLoggedIn] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/login/status", {
|
fetch("/login/status", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
credentials: "include", // Sends cookies with request
|
credentials: "include",
|
||||||
})
|
})
|
||||||
.then((res) => res.json())
|
.then((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -19,19 +22,25 @@ function Account() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
toast.error(`Error checking login status. Reason: ${err.message}`);
|
||||||
console.error("Error checking login status:", err);
|
console.error("Error checking login status:", err);
|
||||||
setIsLoggedIn(false); // Assume not logged in on error
|
setIsLoggedIn(false);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (isLoggedIn === null) {
|
if (isLoggedIn === null) {
|
||||||
return <div>Loading...</div>; // Optional loading UI
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="accountpage" className="w-full h-full flex">
|
<>
|
||||||
{isLoggedIn ? <MyAccount /> : <Register />}
|
<NavBar />
|
||||||
</div>
|
<Container>
|
||||||
|
<div id="accountpage" className="flex h-full w-full">
|
||||||
|
{isLoggedIn ? <MyAccount /> : <Register />}
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { FaDownload } from "react-icons/fa";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Container from "../Container";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
|
||||||
function Cache() {
|
function Cache() {
|
||||||
const [searchQuery, setSearchQuery] = useState("");
|
const [searchQuery, setSearchQuery] = useState("");
|
||||||
const [cacheData, setCacheData] = useState([]);
|
const [cacheData, setCacheData] = useState([]);
|
||||||
const [keyCount, setKeyCount] = useState(0); // New state to store the key count
|
const [keyCount, setKeyCount] = useState(0);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [hasSearched, setHasSearched] = useState(false);
|
||||||
|
|
||||||
const debounceTimeout = useRef(null);
|
const debounceTimeout = useRef(null);
|
||||||
|
|
||||||
// Fetch the key count when the component mounts
|
// Fetch the key count when the component mounts
|
||||||
@ -23,32 +30,40 @@ function Cache() {
|
|||||||
|
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
const query = event.target.value;
|
const query = event.target.value;
|
||||||
setSearchQuery(query); // Update the search query
|
setSearchQuery(query);
|
||||||
|
|
||||||
// Clear the previous timeout
|
|
||||||
if (debounceTimeout.current) {
|
if (debounceTimeout.current) {
|
||||||
clearTimeout(debounceTimeout.current);
|
clearTimeout(debounceTimeout.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a new timeout to send the API call after 1 second of no typing
|
if (query.trim() !== "") {
|
||||||
debounceTimeout.current = setTimeout(() => {
|
setLoading(true); // Show spinner immediately
|
||||||
if (query.trim() !== "") {
|
debounceTimeout.current = setTimeout(() => {
|
||||||
sendApiCall(query); // Only call the API if the query is not empty
|
sendApiCall(query);
|
||||||
} else {
|
}, 1000);
|
||||||
setCacheData([]); // Clear results if query is empty
|
} else {
|
||||||
}
|
setHasSearched(false); // Reset state when input is cleared
|
||||||
}, 1000); // 1 second delay
|
setCacheData([]);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendApiCall = (text) => {
|
const sendApiCall = (text) => {
|
||||||
|
setLoading(true);
|
||||||
fetch("/api/cache/search", {
|
fetch("/api/cache/search", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ input: text }),
|
body: JSON.stringify({ input: text }),
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => setCacheData(data)) // Update cache data with the results
|
.then((data) => {
|
||||||
.catch((error) => console.error("Error:", error));
|
setCacheData(data);
|
||||||
|
setHasSearched(true);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error(`Error: ${error.message}`);
|
||||||
|
console.error("Error:", error);
|
||||||
|
})
|
||||||
|
.finally(() => setLoading(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -56,51 +71,71 @@ function Cache() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full overflow-y-auto p-4">
|
<>
|
||||||
<div className="flex flex-col lg:flex-row w-full lg:h-12 items-center">
|
<NavBar />
|
||||||
<input
|
<Container>
|
||||||
type="text"
|
<div className="my-4 flex w-full flex-col items-center justify-center gap-2 lg:flex-row">
|
||||||
value={searchQuery}
|
<fieldset className="fieldset w-full max-w-2xl">
|
||||||
onChange={handleInputChange}
|
<input
|
||||||
placeholder={`Search ${keyCount} keys...`} // Dynamic placeholder
|
type="text"
|
||||||
className="lg:grow w-full border-2 border-emerald-500/25 rounded-xl h-10 self-center m-2 text-white p-1 focus:outline-none focus:ring-2 focus:ring-emerald-500/50 transition-all duration-200 ease-in-out"
|
value={searchQuery}
|
||||||
/>
|
onChange={handleInputChange}
|
||||||
<a
|
placeholder={`Search ${keyCount} keys...`}
|
||||||
href="/api/cache/download"
|
className="input w-full max-w-2xl font-mono"
|
||||||
className="bg-emerald-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-10 truncate w-full text-center flex items-center justify-center m-2"
|
/>
|
||||||
>
|
</fieldset>
|
||||||
Download Cache
|
<button
|
||||||
</a>
|
className="btn btn-success"
|
||||||
</div>
|
onClick={() => {
|
||||||
<div className="w-full grow p-4 border-2 border-emerald-500/50 rounded-2xl mt-5 overflow-y-auto">
|
window.location.href = "/api/cache/download";
|
||||||
<table className="min-w-full text-white">
|
}}
|
||||||
<thead>
|
>
|
||||||
<tr>
|
<FaDownload />
|
||||||
<th className="p-2 border border-black">PSSH</th>
|
Download keys cache
|
||||||
<th className="p-2 border border-black">KID</th>
|
</button>
|
||||||
<th className="p-2 border border-black">Key</th>
|
</div>
|
||||||
</tr>
|
|
||||||
</thead>
|
{loading ? (
|
||||||
<tbody>
|
<div className="flex justify-center py-16">
|
||||||
{cacheData.length > 0 ? (
|
<span className="loading loading-spinner loading-md me-2"></span>
|
||||||
cacheData.map((item, index) => (
|
Searching...
|
||||||
<tr key={index}>
|
</div>
|
||||||
<td className="p-2 border border-black">{item.PSSH}</td>
|
) : cacheData.length > 0 ? (
|
||||||
<td className="p-2 border border-black">{item.KID}</td>
|
<div className="my-4 flex justify-center">
|
||||||
<td className="p-2 border border-black">{item.Key}</td>
|
<div className="overflow-x-auto">
|
||||||
</tr>
|
<table className="table">
|
||||||
))
|
<thead>
|
||||||
) : (
|
<tr>
|
||||||
<tr>
|
<th></th>
|
||||||
<td colSpan="3" className="p-2 border border-black text-center">
|
<th className="text-center">PSSH</th>
|
||||||
No data found
|
<th className="text-center">key ID:key pair</th>
|
||||||
</td>
|
</tr>
|
||||||
</tr>
|
</thead>
|
||||||
)}
|
<tbody>
|
||||||
</tbody>
|
{cacheData.map((item, index) => (
|
||||||
</table>
|
<tr key={index}>
|
||||||
</div>
|
<th>{index + 1}</th>
|
||||||
</div>
|
<td className="font-mono">{item.PSSH}</td>
|
||||||
|
<td className="font-mono">
|
||||||
|
{item.KID}:{item.Key}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : hasSearched ? (
|
||||||
|
<div className="flex justify-center py-16">
|
||||||
|
<div className="text-center">No data found in the database</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex justify-center py-16">
|
||||||
|
<div className="text-center">Enter a search query to see results</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { readTextFromClipboard } from "../Functions/ParseChallenge";
|
import { readTextFromClipboard } from "../Functions/ParseChallenge";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
import Container from "../Container";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { IoInformationCircleOutline } from "react-icons/io5";
|
||||||
|
|
||||||
function HomePage() {
|
function HomePage() {
|
||||||
const [pssh, setPssh] = useState("");
|
const [pssh, setPssh] = useState("");
|
||||||
@ -58,7 +62,7 @@ function HomePage() {
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error during decryption request:", error);
|
console.error("Error during decryption request:", error);
|
||||||
setMessage("Error: Unable to process request.");
|
setMessage(`Error: Unable to process request. Reason: ${error.message}`);
|
||||||
setIsVisible(true);
|
setIsVisible(true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -67,14 +71,15 @@ function HomePage() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (messageRef.current) {
|
if (messageRef.current) {
|
||||||
const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
|
const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
|
||||||
|
toast.success("Copied to clipboard");
|
||||||
navigator.clipboard.writeText(textToCopy).catch((err) => {
|
navigator.clipboard.writeText(textToCopy).catch((err) => {
|
||||||
alert("Failed to copy!");
|
toast.error(`Failed to copy. Reason: ${err.message}`);
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFetchPaste = () => {
|
const handleFetchPaste = (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
readTextFromClipboard()
|
readTextFromClipboard()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -84,7 +89,8 @@ function HomePage() {
|
|||||||
setData(document.getElementById("data").value);
|
setData(document.getElementById("data").value);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
alert("Failed to paste from fetch!");
|
toast.error(`Failed to paste from fetch. Reason: ${err.message}`);
|
||||||
|
console.error("Failed to paste from fetch:", err);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,138 +139,150 @@ function HomePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
|
<NavBar />
|
||||||
<form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
|
<Container>
|
||||||
<label htmlFor="pssh" className="text-white w-8/10 self-center">
|
<div className="mx-auto flex w-full max-w-2xl flex-col justify-center">
|
||||||
PSSH:{" "}
|
<fieldset className="fieldset">
|
||||||
</label>
|
<legend className="fieldset-legend text-base">PSSH*</legend>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="pssh"
|
className="input w-full font-mono"
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
placeholder="Enter PSSH here"
|
||||||
value={pssh}
|
value={pssh}
|
||||||
onChange={(e) => setPssh(e.target.value)}
|
onChange={(e) => setPssh(e.target.value)}
|
||||||
/>
|
required
|
||||||
<label htmlFor="licurl" className="text-white w-8/10 self-center">
|
/>
|
||||||
License URL:{" "}
|
<p className="label text-red-500">* Required</p>
|
||||||
</label>
|
</fieldset>
|
||||||
<input
|
<fieldset className="fieldset">
|
||||||
type="text"
|
<legend className="fieldset-legend text-base">License URL*</legend>
|
||||||
id="licurl"
|
<input
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
type="text"
|
||||||
value={licurl}
|
className="input w-full font-mono"
|
||||||
onChange={(e) => setLicurl(e.target.value)}
|
placeholder="Enter License URL here"
|
||||||
/>
|
value={licurl}
|
||||||
<label htmlFor="proxy" className="text-white w-8/10 self-center">
|
onChange={(e) => setLicurl(e.target.value)}
|
||||||
Proxy:{" "}
|
required
|
||||||
</label>
|
/>
|
||||||
<input
|
<p className="label text-red-500">* Required</p>
|
||||||
type="text"
|
</fieldset>
|
||||||
id="proxy"
|
<fieldset className="fieldset">
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
|
<legend className="fieldset-legend text-base">Proxy</legend>
|
||||||
value={proxy}
|
<input
|
||||||
onChange={(e) => setProxy(e.target.value)}
|
type="text"
|
||||||
/>
|
className="input w-full font-mono"
|
||||||
<label htmlFor="headers" className="text-white w-8/10 self-center">
|
placeholder="Enter Proxy here (https://example.com:8080)"
|
||||||
Headers:{" "}
|
value={proxy}
|
||||||
</label>
|
onChange={(e) => setProxy(e.target.value)}
|
||||||
<textarea
|
/>
|
||||||
id="headers"
|
</fieldset>
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
<fieldset className="fieldset">
|
||||||
value={headers}
|
<legend className="fieldset-legend text-base">
|
||||||
onChange={(e) => setHeaders(e.target.value)}
|
Headers*
|
||||||
/>
|
<div
|
||||||
<label htmlFor="cookies" className="text-white w-8/10 self-center">
|
className="tooltip"
|
||||||
Cookies:{" "}
|
data-tip="You can use https://curlconverter.com/python/ to paste the header values here"
|
||||||
</label>
|
>
|
||||||
<textarea
|
<IoInformationCircleOutline className="h-5 w-5" />
|
||||||
id="cookies"
|
</div>
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
</legend>
|
||||||
value={cookies}
|
<textarea
|
||||||
onChange={(e) => setCookies(e.target.value)}
|
className="textarea h-48 w-full font-mono"
|
||||||
/>
|
placeholder="Enter headers here (JSON format). E.g. {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'}"
|
||||||
<label htmlFor="data" className="text-white w-8/10 self-center">
|
value={headers}
|
||||||
Data:{" "}
|
onChange={(e) => setHeaders(e.target.value)}
|
||||||
</label>
|
required
|
||||||
<textarea
|
/>
|
||||||
id="data"
|
<p className="label text-red-500">* Required</p>
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
|
</fieldset>
|
||||||
value={data}
|
<fieldset className="fieldset">
|
||||||
onChange={(e) => setData(e.target.value)}
|
<legend className="fieldset-legend text-base">Cookies</legend>
|
||||||
/>
|
<textarea
|
||||||
|
className="textarea h-48 w-full font-mono"
|
||||||
|
placeholder="Enter cookies here (JSON format)"
|
||||||
|
value={cookies}
|
||||||
|
onChange={(e) => setCookies(e.target.value)}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset className="fieldset">
|
||||||
|
<legend className="fieldset-legend text-base">Data</legend>
|
||||||
|
<textarea
|
||||||
|
className="textarea h-48 w-full font-mono"
|
||||||
|
placeholder="Enter data here (JSON format)"
|
||||||
|
value={data}
|
||||||
|
onChange={(e) => setData(e.target.value)}
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
{/* Device Selection Dropdown, only show if logged in */}
|
{/* Device Selection Dropdown, only show if logged in */}
|
||||||
{devices.length > 0 && (
|
{devices.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<label htmlFor="device" className="text-white w-8/10 self-center">
|
<fieldset className="fieldset">
|
||||||
Select Device:
|
<legend className="fieldset-legend text-base">Select device</legend>
|
||||||
</label>
|
<select
|
||||||
<select
|
className="select w-full"
|
||||||
id="device"
|
value={selectedDevice}
|
||||||
className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white bg-black p-1"
|
onChange={(e) => setSelectedDevice(e.target.value)}
|
||||||
value={selectedDevice}
|
>
|
||||||
onChange={(e) => setSelectedDevice(e.target.value)}
|
{devices.map((device, index) => (
|
||||||
>
|
<option key={index} value={device}>
|
||||||
{devices.map((device, index) => (
|
{device}
|
||||||
<option key={index} value={device}>
|
</option>
|
||||||
{device}
|
))}
|
||||||
</option>
|
</select>
|
||||||
))}
|
</fieldset>
|
||||||
</select>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
<div className="mx-auto my-4 flex w-full flex-col items-center justify-center gap-2 lg:flex-row">
|
||||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-sky-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
className="btn btn-primary btn-wide"
|
||||||
onClick={handleSubmitButton}
|
onClick={handleSubmitButton}
|
||||||
|
disabled={pssh === "" || licurl === "" || headers === ""}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-info btn-wide"
|
||||||
onClick={handleFetchPaste}
|
onClick={handleFetchPaste}
|
||||||
className="bg-yellow-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0"
|
|
||||||
>
|
>
|
||||||
Paste from fetch
|
Paste from fetch
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="bg-red-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate mt-5 w-1/2 lg:mt-0"
|
className="btn btn-error btn-wide"
|
||||||
onClick={handleReset}
|
onClick={handleReset}
|
||||||
>
|
>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isVisible && (
|
|
||||||
<div
|
|
||||||
id="main_content"
|
|
||||||
className="flex-col w-full h-full p-10 items-center justify-center self-center"
|
|
||||||
>
|
|
||||||
<div className="flex flex-col w-full h-full overflow-y-auto items-center">
|
|
||||||
<div className="w-8/10 grow p-4 text-white text-bold text-center text-xl md:text-3xl border-2 border-sky-500/25 rounded-xl bg-black/5">
|
|
||||||
<p className="w-full border-b-2 border-white/75 pb-2">Results:</p>
|
|
||||||
<p
|
|
||||||
className="w-full grow pt-10 break-words overflow-y-auto"
|
|
||||||
ref={messageRef}
|
|
||||||
dangerouslySetInnerHTML={{ __html: message }}
|
|
||||||
/>
|
|
||||||
<div ref={bottomRef} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
|
|
||||||
<button
|
|
||||||
className="bg-green-500/50 rounded-xl text-white text-bold text-xl p-1 lg:w-1/5 lg:h-12 truncate w-1/2"
|
|
||||||
onClick={handleCopy}
|
|
||||||
>
|
|
||||||
Copy Results
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
{isVisible && (
|
||||||
|
<>
|
||||||
|
<div className="mx-auto my-4 flex w-full max-w-2xl flex-col justify-center">
|
||||||
|
<div className="card bg-base-100 card-lg border border-gray-500 shadow-sm">
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">Result</h2>
|
||||||
|
<div className="divider"></div>
|
||||||
|
<p
|
||||||
|
className="w-full grow overflow-y-auto font-mono break-words"
|
||||||
|
ref={messageRef}
|
||||||
|
dangerouslySetInnerHTML={{ __html: message }}
|
||||||
|
/>
|
||||||
|
<div ref={bottomRef} />
|
||||||
|
<div
|
||||||
|
className="card-actions mt-4 justify-end"
|
||||||
|
onClick={handleCopy}
|
||||||
|
>
|
||||||
|
<button className="btn btn-success">Copy results</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Container>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Container from "../Container";
|
||||||
|
|
||||||
function MyAccount() {
|
function MyAccount() {
|
||||||
const [wvList, setWvList] = useState([]);
|
const [wvList, setWvList] = useState([]);
|
||||||
@ -21,6 +23,7 @@ function MyAccount() {
|
|||||||
setUsername(response.data.Styled_Username || "");
|
setUsername(response.data.Styled_Username || "");
|
||||||
setApiKey(response.data.API_Key || "");
|
setApiKey(response.data.API_Key || "");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
toast.error(`Failed to fetch user info. Reason: ${err.message}`);
|
||||||
console.error("Failed to fetch user info", err);
|
console.error("Failed to fetch user info", err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -43,7 +46,7 @@ function MyAccount() {
|
|||||||
(cdmType === "PR" && extension !== "prd") ||
|
(cdmType === "PR" && extension !== "prd") ||
|
||||||
(cdmType === "WV" && extension !== "wvd")
|
(cdmType === "WV" && extension !== "wvd")
|
||||||
) {
|
) {
|
||||||
alert(`Please upload a .${cdmType === "PR" ? "prd" : "wvd"} file.`);
|
toast.error(`Please upload a .${cdmType === "PR" ? "prd" : "wvd"} file.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,9 +58,10 @@ function MyAccount() {
|
|||||||
await axios.post(`/upload/${cdmType}`, formData);
|
await axios.post(`/upload/${cdmType}`, formData);
|
||||||
await fetchUserInfo(); // Refresh list after upload
|
await fetchUserInfo(); // Refresh list after upload
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
toast.error(`Upload failed. Reason: ${err.message}`);
|
||||||
console.error("Upload failed", err);
|
console.error("Upload failed", err);
|
||||||
alert("Upload failed");
|
|
||||||
} finally {
|
} finally {
|
||||||
|
toast.success(`${cdmType} CDM uploaded successfully`);
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -66,17 +70,18 @@ function MyAccount() {
|
|||||||
const handleLogout = async () => {
|
const handleLogout = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.post("/logout");
|
await axios.post("/logout");
|
||||||
|
toast.success("Logged out successfully. Reloading page...");
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
toast.error(`Logout failed. Reason: ${error.message}`);
|
||||||
console.error("Logout failed:", error);
|
console.error("Logout failed:", error);
|
||||||
alert("Logout failed!");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle change password
|
// Handle change password
|
||||||
const handleChangePassword = async () => {
|
const handleChangePassword = async () => {
|
||||||
if (passwordError || password === "") {
|
if (passwordError || password === "") {
|
||||||
alert("Please enter a valid password.");
|
toast.error("Please enter a valid password");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,16 +91,16 @@ function MyAccount() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.data.message === "True") {
|
if (response.data.message === "True") {
|
||||||
alert("Password changed successfully.");
|
toast.success("Password changed successfully");
|
||||||
setPassword("");
|
setPassword("");
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to change password.");
|
toast.error("Failed to change password");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.response && error.response.data?.message === "Invalid password format") {
|
if (error.response && error.response.data?.message === "Invalid password format") {
|
||||||
alert("Password format is invalid. Please try again.");
|
toast.error("Password format is invalid. Please try again.");
|
||||||
} else {
|
} else {
|
||||||
alert("Error occurred while changing password.");
|
toast.error("Error occurred while changing password");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -103,7 +108,7 @@ function MyAccount() {
|
|||||||
// Handle change API key
|
// Handle change API key
|
||||||
const handleChangeApiKey = async () => {
|
const handleChangeApiKey = async () => {
|
||||||
if (apiKeyError || newApiKey === "") {
|
if (apiKeyError || newApiKey === "") {
|
||||||
alert("Please enter a valid API key.");
|
toast.error("Please enter a valid API key");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,181 +117,201 @@ function MyAccount() {
|
|||||||
new_api_key: newApiKey,
|
new_api_key: newApiKey,
|
||||||
});
|
});
|
||||||
if (response.data.message === "True") {
|
if (response.data.message === "True") {
|
||||||
alert("API key changed successfully.");
|
toast.success("API key changed successfully");
|
||||||
setApiKey(newApiKey);
|
setApiKey(newApiKey);
|
||||||
setNewApiKey("");
|
setNewApiKey("");
|
||||||
} else {
|
} else {
|
||||||
alert("Failed to change API key.");
|
toast.error("Failed to change API key");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert("Error occurred while changing API key.");
|
toast.error("Error occurred while changing API key");
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<>
|
||||||
id="myaccount"
|
<Container>
|
||||||
className="flex flex-col lg:flex-row gap-4 w-full min-h-full overflow-y-auto p-4"
|
<div className="flex flex-col gap-4 p-4 lg:flex-row">
|
||||||
>
|
{/* Left Panel - Account Settings */}
|
||||||
<div className="flex-col w-full min-h-164 lg:h-full lg:w-96 border-2 border-yellow-500/50 rounded-2xl p-4 flex items-center overflow-y-auto">
|
<div className="w-full lg:w-96">
|
||||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
|
<div className="card bg-base-200 shadow-xl">
|
||||||
{username ? `${username}` : "My Account"}
|
<div className="card-body">
|
||||||
</h1>
|
<p className="text-center text-sm">Username:</p>
|
||||||
|
<h2 className="card-title justify-center text-center font-bold">
|
||||||
|
{username}
|
||||||
|
</h2>
|
||||||
|
|
||||||
{/* API Key Section */}
|
<div className="divider"></div>
|
||||||
<div className="w-full flex flex-col items-center">
|
|
||||||
<label htmlFor="apiKey" className="text-white font-semibold mb-1">
|
|
||||||
API Key
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="apiKey"
|
|
||||||
type="text"
|
|
||||||
value={apiKey}
|
|
||||||
readOnly
|
|
||||||
className="w-full p-2 mb-4 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* New API Key Section */}
|
<fieldset className="fieldset">
|
||||||
<label htmlFor="newApiKey" className="text-white font-semibold mt-4 mb-1">
|
<legend className="fieldset-legend text-base" htmlFor="apiKey">
|
||||||
New API Key
|
API Key
|
||||||
</label>
|
</legend>
|
||||||
<input
|
<input
|
||||||
id="newApiKey"
|
name="apiKey"
|
||||||
type="text"
|
type="text"
|
||||||
value={newApiKey}
|
value={apiKey}
|
||||||
onChange={(e) => {
|
readOnly
|
||||||
const value = e.target.value;
|
className="input input-bordered text-center"
|
||||||
const isValid = /^[^\s]+$/.test(value); // No spaces
|
/>
|
||||||
if (!isValid) {
|
|
||||||
setApiKeyError("API key must not contain spaces.");
|
|
||||||
} else {
|
|
||||||
setApiKeyError("");
|
|
||||||
}
|
|
||||||
setNewApiKey(value);
|
|
||||||
}}
|
|
||||||
placeholder="Enter new API key"
|
|
||||||
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
|
||||||
/>
|
|
||||||
{apiKeyError && <p className="text-red-500 text-sm mb-3">{apiKeyError}</p>}
|
|
||||||
<button
|
|
||||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
|
||||||
onClick={handleChangeApiKey}
|
|
||||||
>
|
|
||||||
Change API Key
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{/* Change Password Section */}
|
<legend
|
||||||
<label htmlFor="password" className="text-white font-semibold mt-4 mb-1">
|
className="fieldset-legend text-base"
|
||||||
Change Password
|
htmlFor="newApiKey"
|
||||||
</label>
|
>
|
||||||
<input
|
New API Key
|
||||||
id="password"
|
</legend>
|
||||||
type="password"
|
<input
|
||||||
value={password}
|
name="newApiKey"
|
||||||
onChange={(e) => {
|
type="text"
|
||||||
const value = e.target.value;
|
value={newApiKey}
|
||||||
const isValid =
|
onChange={(e) => {
|
||||||
/^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(value);
|
const value = e.target.value;
|
||||||
if (!isValid) {
|
const isValid = /^[^\s]+$/.test(value);
|
||||||
setPasswordError(
|
if (!isValid) {
|
||||||
"Password must not contain spaces or invalid characters."
|
setApiKeyError("API key must not contain spaces");
|
||||||
);
|
} else {
|
||||||
} else {
|
setApiKeyError("");
|
||||||
setPasswordError("");
|
}
|
||||||
}
|
setNewApiKey(value);
|
||||||
setPassword(value);
|
}}
|
||||||
}}
|
placeholder="Enter new API key"
|
||||||
placeholder="New Password"
|
className="input input-bordered"
|
||||||
className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
|
/>
|
||||||
/>
|
{apiKeyError && (
|
||||||
{passwordError && <p className="text-red-500 text-sm mb-3">{passwordError}</p>}
|
<p className="label text-error">{apiKeyError}</p>
|
||||||
<button
|
)}
|
||||||
className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
<button
|
||||||
onClick={handleChangePassword}
|
className="btn btn-primary btn-block mt-2"
|
||||||
>
|
onClick={handleChangeApiKey}
|
||||||
Change Password
|
>
|
||||||
</button>
|
Change API key
|
||||||
</div>
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<button
|
<fieldset className="fieldset">
|
||||||
onClick={handleLogout}
|
<legend
|
||||||
className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
|
className="fieldset-legend text-base"
|
||||||
>
|
htmlFor="passwordChange"
|
||||||
Log out
|
>
|
||||||
</button>
|
Change password
|
||||||
</div>
|
</legend>
|
||||||
|
<input
|
||||||
|
name="passwordChange"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
const isValid =
|
||||||
|
/^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(
|
||||||
|
value
|
||||||
|
);
|
||||||
|
if (!isValid) {
|
||||||
|
setPasswordError("Invalid password characters");
|
||||||
|
} else {
|
||||||
|
setPasswordError("");
|
||||||
|
}
|
||||||
|
setPassword(value);
|
||||||
|
}}
|
||||||
|
placeholder="New password"
|
||||||
|
className="input input-bordered"
|
||||||
|
/>
|
||||||
|
{passwordError && (
|
||||||
|
<p className="label text-error">{passwordError}</p>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="btn btn-secondary btn-block mt-2"
|
||||||
|
onClick={handleChangePassword}
|
||||||
|
>
|
||||||
|
Change password
|
||||||
|
</button>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<div className="flex flex-col w-full lg:ml-2 mt-2 lg:mt-0">
|
<div className="divider"></div>
|
||||||
{/* Widevine Section */}
|
|
||||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl lg:p-4 p-2 overflow-y-auto">
|
<button className="btn btn-error mt-auto" onClick={handleLogout}>
|
||||||
<h1 className="bg-black text-2xl font-bold text-white border-b-2 border-white p-2">
|
Log out
|
||||||
Widevine CDMs
|
</button>
|
||||||
</h1>
|
|
||||||
<div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
|
|
||||||
{wvList.length === 0 ? (
|
|
||||||
<div className="text-white text-center font-bold">
|
|
||||||
No Widevine CDMs uploaded.
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
wvList.map((filename, i) => (
|
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className={`text-center font-bold text-white p-2 rounded ${
|
|
||||||
i % 2 === 0 ? "bg-black/30" : "bg-black/60"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{filename}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
|
||||||
{uploading ? "Uploading..." : "Upload CDM"}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".wvd"
|
|
||||||
hidden
|
|
||||||
onChange={(e) => handleUpload(e, "WV")}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Playready Section */}
|
{/* Right Panel - CDM Uploads */}
|
||||||
<div className="border-2 border-yellow-500/50 flex flex-col w-full min-h-1/2 text-center rounded-2xl p-2 mt-2 lg:mt-2 overflow-y-auto">
|
<div className="flex w-full flex-col gap-4">
|
||||||
<h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 bg-black">
|
{/* Widevine CDM */}
|
||||||
Playready CDMs
|
<div className="card bg-base-200 shadow-xl">
|
||||||
</h1>
|
<div className="card-body">
|
||||||
<div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
|
<h2 className="card-title">Widevine CDMs</h2>
|
||||||
{prList.length === 0 ? (
|
<div className="divider"></div>
|
||||||
<div className="text-white text-center font-bold">
|
<div className="max-h-60 space-y-2 overflow-y-auto">
|
||||||
No Playready CDMs uploaded.
|
{wvList.length === 0 ? (
|
||||||
</div>
|
<div className="text-center text-sm">
|
||||||
) : (
|
No Widevine CDMs uploaded.
|
||||||
prList.map((filename, i) => (
|
</div>
|
||||||
<div
|
) : (
|
||||||
key={i}
|
wvList.map((filename, i) => (
|
||||||
className={`text-center font-bold text-white p-2 rounded ${
|
<div
|
||||||
i % 2 === 0 ? "bg-black/30" : "bg-black/60"
|
key={i}
|
||||||
}`}
|
className={`rounded px-2 py-1 text-sm ${
|
||||||
>
|
i % 2 === 0 ? "bg-base-100" : "bg-base-300"
|
||||||
{filename}
|
}`}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))
|
<label className="btn btn-accent mt-4">
|
||||||
)}
|
{uploading ? "Uploading..." : "Upload CDM"}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".wvd"
|
||||||
|
hidden
|
||||||
|
onChange={(e) => handleUpload(e, "WV")}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* PlayReady CDM */}
|
||||||
|
<div className="card bg-base-200 shadow-xl">
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">PlayReady CDMs</h2>
|
||||||
|
<div className="divider"></div>
|
||||||
|
<div className="max-h-60 space-y-2 overflow-y-auto">
|
||||||
|
{prList.length === 0 ? (
|
||||||
|
<div className="text-center text-sm">
|
||||||
|
No PlayReady CDMs uploaded.
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
prList.map((filename, i) => (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`rounded px-2 py-1 text-sm ${
|
||||||
|
i % 2 === 0 ? "bg-base-100" : "bg-base-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{filename}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<label className="btn btn-accent mt-4">
|
||||||
|
{uploading ? "Uploading..." : "Upload CDM"}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".prd"
|
||||||
|
hidden
|
||||||
|
onChange={(e) => handleUpload(e, "PR")}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label className="bg-yellow-500 text-white w-full min-h-16 lg:min-h-16 mt-4 rounded-2xl flex items-center justify-center cursor-pointer">
|
|
||||||
{uploading ? "Uploading..." : "Upload CDM"}
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
accept=".prd"
|
|
||||||
hidden
|
|
||||||
onChange={(e) => handleUpload(e, "PR")}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</Container>
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,118 +1,146 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { IoIosLogIn } from "react-icons/io";
|
||||||
|
import { PiUserCirclePlus } from "react-icons/pi";
|
||||||
|
|
||||||
function Register() {
|
function Register() {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [status, setStatus] = useState("");
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
|
const [tab, setTab] = useState("login"); // 'login' or 'register'
|
||||||
// Validation functions
|
|
||||||
const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
|
|
||||||
const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = "Register | CDRM-Project";
|
document.title = "Register | CDRM-Project";
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleRegister = async () => {
|
const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
|
||||||
|
const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
|
||||||
|
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
if (!validateUsername(username)) {
|
if (!validateUsername(username)) {
|
||||||
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
toast.error("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!validatePassword(password)) {
|
if (!validatePassword(password)) {
|
||||||
setStatus("Invalid password. Spaces are not allowed.");
|
toast.error("Invalid password. Spaces are not allowed.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (tab === "register") {
|
||||||
const response = await fetch("/register", {
|
if (password !== confirmPassword) {
|
||||||
method: "POST",
|
toast.error("Passwords do not match.");
|
||||||
headers: {
|
return;
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ username, password }),
|
|
||||||
});
|
|
||||||
const data = await response.json();
|
|
||||||
if (data.message) {
|
|
||||||
setStatus(data.message);
|
|
||||||
} else if (data.error) {
|
|
||||||
setStatus(data.error);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
try {
|
||||||
setStatus("An error occurred while registering.");
|
const res = await fetch("/register", {
|
||||||
}
|
method: "POST",
|
||||||
};
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
const handleLogin = async () => {
|
},
|
||||||
if (!validateUsername(username)) {
|
body: JSON.stringify({ username, password }),
|
||||||
setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
|
});
|
||||||
return;
|
const data = await res.json();
|
||||||
}
|
if (data.message) {
|
||||||
if (!validatePassword(password)) {
|
toast.success(data.message);
|
||||||
setStatus("Invalid password. Spaces are not allowed.");
|
} else {
|
||||||
return;
|
toast.error(data.error || "Unknown error");
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
try {
|
toast.error(`Register error: ${err.message}`);
|
||||||
const response = await fetch("/login", {
|
}
|
||||||
method: "POST",
|
} else {
|
||||||
headers: {
|
try {
|
||||||
"Content-Type": "application/json",
|
const res = await fetch("/login", {
|
||||||
},
|
method: "POST",
|
||||||
credentials: "include", // Important to send cookies
|
headers: {
|
||||||
body: JSON.stringify({ username, password }),
|
"Content-Type": "application/json",
|
||||||
});
|
},
|
||||||
const data = await response.json();
|
credentials: "include",
|
||||||
if (data.message) {
|
body: JSON.stringify({ username, password }),
|
||||||
// Successful login - reload the page to trigger Account check
|
});
|
||||||
window.location.reload();
|
const data = await res.json();
|
||||||
} else if (data.error) {
|
if (data.message) {
|
||||||
setStatus(data.error);
|
window.location.reload();
|
||||||
|
} else {
|
||||||
|
toast.error(data.error || "Login failed");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error(`Login error: ${err.message}`);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
setStatus("An error occurred while logging in.");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-full items-center justify-center p-4">
|
<div className="mx-auto flex min-h-full w-full max-w-xl flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
<div className="flex flex-col w-full h-full lg:w-1/2 lg:h-96 border-2 border-yellow-500/50 rounded-2xl p-4 overflow-x-auto justify-center items-center">
|
<div className="mx-auto">
|
||||||
<div className="flex flex-col w-full">
|
{/* Tabs */}
|
||||||
<label htmlFor="username" className="text-lg font-bold mb-2 text-white">
|
<div className="tabs tabs-box justify-center">
|
||||||
Username:
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
value={username}
|
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
|
||||||
placeholder="Username"
|
|
||||||
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
|
||||||
/>
|
|
||||||
<label htmlFor="password" className="text-lg font-bold mb-2 text-white">
|
|
||||||
Password:
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
value={password}
|
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
|
||||||
placeholder="Password"
|
|
||||||
className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col lg:flex-row w-8/10 items-center lg:justify-between mt-4">
|
|
||||||
<button
|
<button
|
||||||
onClick={handleLogin}
|
className={`tab ${tab === "login" ? "tab-active" : ""}`}
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
onClick={() => setTab("login")}
|
||||||
>
|
>
|
||||||
Login
|
<IoIosLogIn className="h-6 w-6 me-1"/>
|
||||||
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={handleRegister}
|
className={`tab ${tab === "register" ? "tab-active" : ""}`}
|
||||||
className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
|
onClick={() => setTab("register")}
|
||||||
>
|
>
|
||||||
|
<PiUserCirclePlus className="h-6 w-6 me-1" />
|
||||||
Register
|
Register
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{status && <p className="text-sm text-white mt-4 p-4">{status}</p>}
|
<h2 className="mt-10 text-center text-2xl font-bold tracking-tight">
|
||||||
|
{tab === "login" ? "Sign in" : "Register"}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mx-auto mt-10 w-full max-w-xl">
|
||||||
|
<form className="space-y-6" onSubmit={handleSubmit}>
|
||||||
|
<fieldset className="fieldset">
|
||||||
|
<legend className="fieldset-legend text-base">Username</legend>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your username"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset className="fieldset">
|
||||||
|
<legend className="fieldset-legend text-base">Password</legend>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
{tab === "register" && (
|
||||||
|
<fieldset className="fieldset">
|
||||||
|
<legend className="fieldset-legend text-base">Confirm password</legend>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
className="input w-full"
|
||||||
|
placeholder="Confirm your password"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<button type="submit" className="btn btn-primary btn-block">
|
||||||
|
{tab === "login" ? "Sign in" : "Register"}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,27 +1,23 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import shaka from "shaka-player";
|
import shaka from "shaka-player";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import Container from "../Container";
|
||||||
|
import NavBar from "../NavBar";
|
||||||
|
|
||||||
function TestPlayer() {
|
function TestPlayer() {
|
||||||
const [mpdUrl, setMpdUrl] = useState(""); // State to hold the MPD URL
|
const [mpdUrl, setMpdUrl] = useState("");
|
||||||
const [kids, setKids] = useState(""); // State to hold KIDs (separated by line breaks)
|
const [headers, setHeaders] = useState("");
|
||||||
const [keys, setKeys] = useState(""); // State to hold Keys (separated by line breaks)
|
const [keyPairs, setKeyPairs] = useState(""); // "kid:key" per line
|
||||||
const [headers, setHeaders] = useState(""); // State to hold request headers
|
|
||||||
|
|
||||||
const videoRef = useRef(null); // Ref for the video element
|
const videoRef = useRef(null);
|
||||||
const playerRef = useRef(null); // Ref for Shaka Player instance
|
const playerRef = useRef(null);
|
||||||
|
|
||||||
// Function to update the MPD URL state
|
|
||||||
const handleInputChange = (event) => {
|
const handleInputChange = (event) => {
|
||||||
setMpdUrl(event.target.value);
|
setMpdUrl(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to update KIDs and Keys
|
const handleKeyPairsChange = (event) => {
|
||||||
const handleKidsChange = (event) => {
|
setKeyPairs(event.target.value);
|
||||||
setKids(event.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeysChange = (event) => {
|
|
||||||
setKeys(event.target.value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleHeadersChange = (event) => {
|
const handleHeadersChange = (event) => {
|
||||||
@ -29,31 +25,37 @@ function TestPlayer() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Function to initialize Shaka Player
|
// Function to initialize Shaka Player
|
||||||
const initializePlayer = () => {
|
const initializePlayer = async () => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current && !playerRef.current) {
|
||||||
// Initialize Shaka Player only if it's not already initialized
|
const player = new shaka.Player(); // no mediaElement
|
||||||
if (!playerRef.current) {
|
await player.attach(videoRef.current); // attach later
|
||||||
const player = new shaka.Player(videoRef.current);
|
playerRef.current = player;
|
||||||
playerRef.current = player;
|
|
||||||
|
|
||||||
// Add error listener
|
player.addEventListener("error", (event) => {
|
||||||
player.addEventListener("error", (event) => {
|
toast.error(`Error code ${event.detail.code}: ${event.detail.message}`);
|
||||||
console.error("Error code", event.detail.code, "object", event.detail);
|
console.error("Error code", event.detail.code, "object", event.detail);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to handle submit and configure player with DRM keys and headers
|
// Function to handle submit and configure player with DRM keys and headers
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
if (mpdUrl && kids && keys) {
|
if (mpdUrl && keyPairs) {
|
||||||
// Split the KIDs and Keys by new lines
|
// Parse KID:KEY pairs
|
||||||
const kidsArray = kids.split("\n").map((k) => k.trim());
|
const lines = keyPairs
|
||||||
const keysArray = keys.split("\n").map((k) => k.trim());
|
.split("\n")
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
const clearKeys = {};
|
||||||
|
|
||||||
if (kidsArray.length !== keysArray.length) {
|
for (const line of lines) {
|
||||||
console.error("The number of KIDs and Keys must be the same.");
|
const [kid, key] = line.split(":").map((part) => part.trim());
|
||||||
return;
|
if (!kid || !key) {
|
||||||
|
toast.error(`Invalid line (expected format keyId:key) at line "${line}"`);
|
||||||
|
console.error(`Invalid line (expected format keyId:key) at line "${line}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
clearKeys[kid] = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Shaka Player only when the submit button is pressed
|
// Initialize Shaka Player only when the submit button is pressed
|
||||||
@ -62,15 +64,10 @@ function TestPlayer() {
|
|||||||
// Widevine DRM configuration with the provided KIDs and Keys
|
// Widevine DRM configuration with the provided KIDs and Keys
|
||||||
const config = {
|
const config = {
|
||||||
drm: {
|
drm: {
|
||||||
clearKeys: {},
|
clearKeys: clearKeys,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map KIDs to Keys
|
|
||||||
kidsArray.forEach((kid, index) => {
|
|
||||||
config.drm.clearKeys[kid] = keysArray[index];
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Configuring player with the following DRM config and headers:", config);
|
console.log("Configuring player with the following DRM config and headers:", config);
|
||||||
|
|
||||||
// Configure the player with ClearKey DRM and custom headers
|
// Configure the player with ClearKey DRM and custom headers
|
||||||
@ -81,12 +78,15 @@ function TestPlayer() {
|
|||||||
.load(mpdUrl)
|
.load(mpdUrl)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("Video loaded");
|
console.log("Video loaded");
|
||||||
|
toast.success("Video successfully loaded");
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
toast.error(`Error loading the video. Reason: ${error.message}`);
|
||||||
console.error("Error loading the video", error);
|
console.error("Error loading the video", error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.error("MPD URL, KIDs, and Keys are required.");
|
toast.error("Manifest URL and key pairs are required");
|
||||||
|
console.error("Manifest URL and key pairs are required.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -113,39 +113,65 @@ function TestPlayer() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center w-full p-4">
|
<>
|
||||||
<div className="w-full flex flex-col">
|
<NavBar />
|
||||||
<video ref={videoRef} width="100%" height="auto" controls className="h-96" />
|
<Container>
|
||||||
<input
|
<div className="flex w-full flex-col items-center justify-center py-8">
|
||||||
type="text"
|
<div className="flex w-full flex-col items-center lg:flex-row lg:items-start lg:gap-4">
|
||||||
value={mpdUrl}
|
{/* Video Section */}
|
||||||
onChange={handleInputChange}
|
<div className="w-full lg:w-1/2">
|
||||||
placeholder="MPD URL"
|
<video
|
||||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
ref={videoRef}
|
||||||
/>
|
width="100%"
|
||||||
<textarea
|
height="auto"
|
||||||
placeholder="KIDs (one per line)"
|
controls
|
||||||
value={kids}
|
className="aspect-video max-h-96 w-full"
|
||||||
onChange={handleKidsChange}
|
/>
|
||||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
</div>
|
||||||
/>
|
|
||||||
<textarea
|
{/* Inputs Section */}
|
||||||
placeholder="Keys (one per line)"
|
<div className="mt-4 flex w-full flex-col items-center lg:mt-0 lg:w-1/2">
|
||||||
value={keys}
|
<fieldset className="fieldset w-full">
|
||||||
onChange={handleKeysChange}
|
<legend className="fieldset-legend text-base">Manifest URL*</legend>
|
||||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
<input
|
||||||
/>
|
type="text"
|
||||||
<textarea
|
value={mpdUrl}
|
||||||
placeholder="Headers (one per line)"
|
onChange={handleInputChange}
|
||||||
value={headers}
|
placeholder="Enter manifest URL here"
|
||||||
onChange={handleHeadersChange}
|
className="input w-full font-mono"
|
||||||
className="border-2 border-rose-700/50 mt-2 text-white p-1 overflow-y-auto rounded transition-all ease-in-out focus:outline-none focus:ring-2 focus:ring-rose-700/50 duration-200"
|
/>
|
||||||
/>
|
<p className="label text-red-500">* Required</p>
|
||||||
<button onClick={handleSubmit} className="mt-4 p-2 bg-blue-500 text-white rounded">
|
</fieldset>
|
||||||
Submit
|
<fieldset className="fieldset w-full">
|
||||||
</button>
|
<legend className="fieldset-legend text-base">Key pairs*</legend>
|
||||||
</div>
|
<textarea
|
||||||
</div>
|
placeholder="keyId:key pair (one per line)"
|
||||||
|
value={keyPairs}
|
||||||
|
onChange={handleKeyPairsChange}
|
||||||
|
className="textarea w-full font-mono"
|
||||||
|
/>
|
||||||
|
<p className="label text-red-500">* Required</p>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset className="fieldset w-full">
|
||||||
|
<legend className="fieldset-legend text-base">Headers</legend>
|
||||||
|
<textarea
|
||||||
|
placeholder="Headers (one per line)"
|
||||||
|
value={headers}
|
||||||
|
onChange={handleHeadersChange}
|
||||||
|
className="textarea w-full font-mono"
|
||||||
|
/>
|
||||||
|
</fieldset>
|
||||||
|
<button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
className="btn btn-primary btn-wide my-4"
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,31 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
@plugin "daisyui";
|
||||||
|
|
||||||
|
@plugin "daisyui/theme" {
|
||||||
|
name: "dim";
|
||||||
|
default: true;
|
||||||
|
--radius-selector: 0.5rem;
|
||||||
|
--radius-field: 0.5rem;
|
||||||
|
--radius-box: 0.5rem;
|
||||||
|
--size-selector: 0.25rem;
|
||||||
|
--size-field: 0.25rem;
|
||||||
|
--depth: 0;
|
||||||
|
--noise: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-sans:
|
||||||
|
"Inter", system-ui, -apple-system, Roboto, "Segoe UI", "Helvetica Neue", "Noto Sans",
|
||||||
|
Oxygen, Ubuntu, Cantarell, "Open Sans", Arial, sans-serif;
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Force Sonner toast to use Inter first */
|
||||||
|
[data-sonner-toast],
|
||||||
|
.sonner-toast,
|
||||||
|
:where([data-sonner-toast]) :where([data-title]) :where([data-description]) {
|
||||||
|
font-family: var(--font-sans) !important;
|
||||||
|
}
|
||||||
|
|
||||||
details summary::-webkit-details-marker {
|
details summary::-webkit-details-marker {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import "./index.css";
|
import { Toaster } from "sonner";
|
||||||
import App from "./App.jsx";
|
import App from "./App.jsx";
|
||||||
|
import "./assets/fonts/font-face.css";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
createRoot(document.getElementById("root")).render(
|
createRoot(document.getElementById("root")).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<App />
|
<App />
|
||||||
|
<Toaster
|
||||||
|
richColors
|
||||||
|
className="flex justify-center"
|
||||||
|
position="bottom-center"
|
||||||
|
duration="7000"
|
||||||
|
theme="dark"
|
||||||
|
/>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user