forked from tpd94/CDRM-Project
		
	UI overhaul
This commit is contained in:
		
							parent
							
								
									cc3b37db1d
								
							
						
					
					
						commit
						f83d22c09e
					
				@ -4,5 +4,6 @@
 | 
			
		||||
    "semi": true,
 | 
			
		||||
    "singleQuote": 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",
 | 
			
		||||
                "react": "^19.1.0",
 | 
			
		||||
                "react-dom": "^19.1.0",
 | 
			
		||||
                "react-icons": "^5.5.0",
 | 
			
		||||
                "react-router-dom": "^7.7.0",
 | 
			
		||||
                "shaka-player": "^4.15.8",
 | 
			
		||||
                "sonner": "^2.0.6",
 | 
			
		||||
                "tailwindcss": "^4.1.11"
 | 
			
		||||
            },
 | 
			
		||||
            "devDependencies": {
 | 
			
		||||
@ -22,10 +24,12 @@
 | 
			
		||||
                "@types/react-dom": "^19.1.6",
 | 
			
		||||
                "@vitejs/plugin-react": "^4.7.0",
 | 
			
		||||
                "@vitejs/plugin-react-swc": "^3.11.0",
 | 
			
		||||
                "daisyui": "^5.0.46",
 | 
			
		||||
                "eslint": "^9.31.0",
 | 
			
		||||
                "eslint-plugin-react-hooks": "^5.2.0",
 | 
			
		||||
                "eslint-plugin-react-refresh": "^0.4.20",
 | 
			
		||||
                "globals": "^16.3.0",
 | 
			
		||||
                "prettier-plugin-tailwindcss": "^0.6.14",
 | 
			
		||||
                "vite": "^7.0.5"
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
@ -2137,6 +2141,16 @@
 | 
			
		||||
            "dev": true,
 | 
			
		||||
            "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": {
 | 
			
		||||
            "version": "4.4.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
 | 
			
		||||
@ -3479,6 +3493,110 @@
 | 
			
		||||
                "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": {
 | 
			
		||||
            "version": "1.1.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
 | 
			
		||||
@ -3516,6 +3634,15 @@
 | 
			
		||||
                "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": {
 | 
			
		||||
            "version": "0.17.0",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
 | 
			
		||||
@ -3667,6 +3794,16 @@
 | 
			
		||||
                "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": {
 | 
			
		||||
            "version": "1.2.1",
 | 
			
		||||
            "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
 | 
			
		||||
 | 
			
		||||
@ -14,8 +14,10 @@
 | 
			
		||||
        "axios": "^1.10.0",
 | 
			
		||||
        "react": "^19.1.0",
 | 
			
		||||
        "react-dom": "^19.1.0",
 | 
			
		||||
        "react-icons": "^5.5.0",
 | 
			
		||||
        "react-router-dom": "^7.7.0",
 | 
			
		||||
        "shaka-player": "^4.15.8",
 | 
			
		||||
        "sonner": "^2.0.6",
 | 
			
		||||
        "tailwindcss": "^4.1.11"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
@ -24,10 +26,12 @@
 | 
			
		||||
        "@types/react-dom": "^19.1.6",
 | 
			
		||||
        "@vitejs/plugin-react": "^4.7.0",
 | 
			
		||||
        "@vitejs/plugin-react-swc": "^3.11.0",
 | 
			
		||||
        "daisyui": "^5.0.46",
 | 
			
		||||
        "eslint": "^9.31.0",
 | 
			
		||||
        "eslint-plugin-react-hooks": "^5.2.0",
 | 
			
		||||
        "eslint-plugin-react-refresh": "^0.4.20",
 | 
			
		||||
        "globals": "^16.3.0",
 | 
			
		||||
        "prettier-plugin-tailwindcss": "^0.6.14",
 | 
			
		||||
        "vite": "^7.0.5"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,38 +1,12 @@
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
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 { Route, Routes } from "react-router-dom";
 | 
			
		||||
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() {
 | 
			
		||||
    const [isMenuOpen, setIsMenuOpen] = useState(false); // Track if the menu is open
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div id="appcontainer" className="flex flex-row w-full h-full bg-black">
 | 
			
		||||
            {/* The SideMenu should be visible when isMenuOpen is true */}
 | 
			
		||||
            <SideMenu isMenuOpen={isMenuOpen} setIsMenuOpen={setIsMenuOpen} />
 | 
			
		||||
 | 
			
		||||
            <div
 | 
			
		||||
                id="navbarcontainer"
 | 
			
		||||
                className="hidden lg:flex lg:w-2xs bg-gray-950/55 border-r border-white/5 shrink-0"
 | 
			
		||||
            >
 | 
			
		||||
                <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 />} />
 | 
			
		||||
@ -40,9 +14,6 @@ function App() {
 | 
			
		||||
            <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 { NavLink } from "react-router-dom";
 | 
			
		||||
import homeIcon from "../assets/icons/home.svg";
 | 
			
		||||
import cacheIcon from "../assets/icons/cache.svg";
 | 
			
		||||
import apiIcon from "../assets/icons/api.svg";
 | 
			
		||||
import testPlayerIcon from "../assets/icons/testplayer.svg";
 | 
			
		||||
import accountIcon from "../assets/icons/account.svg";
 | 
			
		||||
import discordIcon from "../assets/icons/discord.svg";
 | 
			
		||||
import telegramIcon from "../assets/icons/telegram.svg";
 | 
			
		||||
import giteaIcon from "../assets/icons/gitea.svg";
 | 
			
		||||
import { FaDiscord } from "react-icons/fa";
 | 
			
		||||
import { FaTelegram } from "react-icons/fa";
 | 
			
		||||
import { SiGitea } from "react-icons/si";
 | 
			
		||||
import { FaHome } from "react-icons/fa";
 | 
			
		||||
import { FaDatabase } from "react-icons/fa";
 | 
			
		||||
import { IoCodeSlashSharp } from "react-icons/io5";
 | 
			
		||||
import { FaVideo } from "react-icons/fa";
 | 
			
		||||
import { RiAccountCircleFill } from "react-icons/ri";
 | 
			
		||||
 | 
			
		||||
function NavBar() {
 | 
			
		||||
    const [externalLinks, setExternalLinks] = useState({
 | 
			
		||||
@ -23,152 +23,156 @@ function NavBar() {
 | 
			
		||||
            .catch((error) => console.error("Error fetching links:", error));
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const MenuItem = ({ to, children }) => {
 | 
			
		||||
        return (
 | 
			
		||||
        <div className="flex flex-col w-full h-full bg-white/1">
 | 
			
		||||
            {/* Header */}
 | 
			
		||||
            <div>
 | 
			
		||||
                <p className="text-white text-2xl font-bold p-3 text-center mb-5">
 | 
			
		||||
                    <a href="/">CDRM-Project</a>
 | 
			
		||||
                </p>
 | 
			
		||||
            </div>
 | 
			
		||||
            <li>
 | 
			
		||||
                <NavLink to={to} className={({ isActive }) => (isActive ? "menu-active" : "")}>
 | 
			
		||||
                    {children}
 | 
			
		||||
                </NavLink>
 | 
			
		||||
            </li>
 | 
			
		||||
        );
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
            {/* Scrollable navigation area */}
 | 
			
		||||
            <div className="overflow-y-auto grow flex flex-col">
 | 
			
		||||
                {/* Main NavLinks */}
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    to="/"
 | 
			
		||||
                    className={({ isActive }) =>
 | 
			
		||||
                        `flex flex-row p-3 border-l-3 ${
 | 
			
		||||
                            isActive
 | 
			
		||||
                                ? "border-l-sky-500/50 bg-black/50"
 | 
			
		||||
                                : "hover:border-l-sky-500/50 hover:bg-white/5"
 | 
			
		||||
                        }`
 | 
			
		||||
                    }
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className="navbar sticky top-0 z-300 bg-slate-700 shadow-sm text-white">
 | 
			
		||||
                <div className="navbar-start">
 | 
			
		||||
                    <div className="dropdown">
 | 
			
		||||
                        <div tabIndex={0} role="button" className="btn btn-ghost lg:hidden">
 | 
			
		||||
                            <svg
 | 
			
		||||
                                xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                                className="h-5 w-5"
 | 
			
		||||
                                fill="none"
 | 
			
		||||
                                viewBox="0 0 24 24"
 | 
			
		||||
                                stroke="currentColor"
 | 
			
		||||
                            >
 | 
			
		||||
                    <button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
 | 
			
		||||
                        <img src={homeIcon} alt="Home" className="w-1/2 cursor-pointer" />
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
 | 
			
		||||
                                {" "}
 | 
			
		||||
                                <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
 | 
			
		||||
                    </p>
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    to="/cache"
 | 
			
		||||
                    className={({ isActive }) =>
 | 
			
		||||
                        `flex flex-row p-3 border-l-3 ${
 | 
			
		||||
                            isActive
 | 
			
		||||
                                ? "border-l-emerald-500/50 bg-black/50"
 | 
			
		||||
                                : "hover:border-l-emerald-500/50 hover:bg-white/5"
 | 
			
		||||
                        }`
 | 
			
		||||
                    }
 | 
			
		||||
                >
 | 
			
		||||
                    <button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
 | 
			
		||||
                        <img src={cacheIcon} alt="Cache" className="w-1/2 cursor-pointer" />
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
 | 
			
		||||
                            </MenuItem>
 | 
			
		||||
                            <MenuItem to="/cache">
 | 
			
		||||
                                <FaDatabase alt="Cache" width={20} height={20} />
 | 
			
		||||
                                Cache
 | 
			
		||||
                    </p>
 | 
			
		||||
                </NavLink>
 | 
			
		||||
 | 
			
		||||
                <NavLink
 | 
			
		||||
                    to="/api"
 | 
			
		||||
                    className={({ isActive }) =>
 | 
			
		||||
                        `flex flex-row p-3 border-l-3 ${
 | 
			
		||||
                            isActive
 | 
			
		||||
                                ? "border-l-indigo-500/50 bg-black/50"
 | 
			
		||||
                                : "hover:border-l-indigo-500/50 hover:bg-white/5"
 | 
			
		||||
                        }`
 | 
			
		||||
                    }
 | 
			
		||||
                >
 | 
			
		||||
                    <button className="w-1/3 p-3 flex flex-col items-center justify-center cursor-pointer">
 | 
			
		||||
                        <img src={apiIcon} alt="API" className="w-1/2 cursor-pointer" />
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <p className="grow text-white md:text-2xl font-bold flex items-center justify-start">
 | 
			
		||||
                            </MenuItem>
 | 
			
		||||
                            <MenuItem to="/api">
 | 
			
		||||
                                <IoCodeSlashSharp alt="API" width={20} height={20} />
 | 
			
		||||
                                API
 | 
			
		||||
                    </p>
 | 
			
		||||
                </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">
 | 
			
		||||
                            </MenuItem>
 | 
			
		||||
                            <MenuItem to="/testplayer">
 | 
			
		||||
                                <FaVideo alt="Test Player" width={20} height={20} />
 | 
			
		||||
                                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">
 | 
			
		||||
                            </MenuItem>
 | 
			
		||||
                            <MenuItem to="/account">
 | 
			
		||||
                                <RiAccountCircleFill alt="My Account" width={20} height={20} />
 | 
			
		||||
                                My Account
 | 
			
		||||
                        </p>
 | 
			
		||||
                    </NavLink>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
                            </MenuItem>
 | 
			
		||||
 | 
			
		||||
            {/* External links at very bottom */}
 | 
			
		||||
            <div className="flex flex-row w-full h-16 bg-black/25">
 | 
			
		||||
                            <div className="divider">Social links</div>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <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"
 | 
			
		||||
                    />
 | 
			
		||||
                                    <FaDiscord alt="Discord" width={20} height={20} />
 | 
			
		||||
                                    Discord
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <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"
 | 
			
		||||
                    />
 | 
			
		||||
                                    <FaTelegram alt="Telegram" width={20} height={20} />
 | 
			
		||||
                                    Telegram
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <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" />
 | 
			
		||||
                                    <SiGitea alt="Gitea" width={20} height={20} />
 | 
			
		||||
                                    Gitea
 | 
			
		||||
                                </a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                        </ul>
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <a className="btn btn-ghost text-xl">CDRM-Project</a>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="navbar-center hidden lg:flex">
 | 
			
		||||
                    <ul className="menu menu-horizontal px-1">
 | 
			
		||||
                        <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>
 | 
			
		||||
                    </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>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -10,6 +14,11 @@ if (
 | 
			
		||||
    fullHost += `:${port}`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const handleCopy = (text) => {
 | 
			
		||||
    navigator.clipboard.writeText(text);
 | 
			
		||||
    toast.success("Copied to clipboard");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function API() {
 | 
			
		||||
    const [deviceInfo, setDeviceInfo] = useState({
 | 
			
		||||
        device_type: "",
 | 
			
		||||
@ -61,13 +70,7 @@ function API() {
 | 
			
		||||
        document.title = "API | CDRM-Project";
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <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
 | 
			
		||||
    const decryptRequest = `import requests
 | 
			
		||||
 | 
			
		||||
print(requests.post(
 | 
			
		||||
    url='${fullHost}/api/decrypt',
 | 
			
		||||
@ -83,55 +86,154 @@ print(requests.post(
 | 
			
		||||
            'Accept-Language': 'en-US,en;q=0.5',
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
).json()['message'])`}
 | 
			
		||||
                    </pre>
 | 
			
		||||
                </div>
 | 
			
		||||
            </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
 | 
			
		||||
).json()['message'])`;
 | 
			
		||||
 | 
			
		||||
    const searchRequest = `import requests
 | 
			
		||||
 | 
			
		||||
print(requests.post(
 | 
			
		||||
    url='${fullHost}/api/cache/search',
 | 
			
		||||
    json={
 | 
			
		||||
        'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
 | 
			
		||||
    }
 | 
			
		||||
).json())`}
 | 
			
		||||
).json())`;
 | 
			
		||||
 | 
			
		||||
    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>
 | 
			
		||||
            </details>
 | 
			
		||||
            <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">
 | 
			
		||||
                            </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> '{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}
 | 
			
		||||
                                    <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>
 | 
			
		||||
            </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">
 | 
			
		||||
                        </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> {prDeviceInfo.security_level}
 | 
			
		||||
                        <br />
 | 
			
		||||
                        <strong>Host:</strong> {fullHost}/remotecdm/playready
 | 
			
		||||
                        <br />
 | 
			
		||||
                        <strong>Secret:</strong> '{prDeviceInfo.secret}'<br />
 | 
			
		||||
                        <strong>Device Name:</strong> {prDeviceInfo.device_name}
 | 
			
		||||
                                    <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>
 | 
			
		||||
            </details>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </Container>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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 MyAccount from "./MyAccount"; // <-- Import the MyAccount component
 | 
			
		||||
 | 
			
		||||
function Account() {
 | 
			
		||||
    const [isLoggedIn, setIsLoggedIn] = useState(null); // null = loading state
 | 
			
		||||
    const [isLoggedIn, setIsLoggedIn] = useState(null);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        fetch("/login/status", {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            credentials: "include", // Sends cookies with request
 | 
			
		||||
            credentials: "include",
 | 
			
		||||
        })
 | 
			
		||||
            .then((res) => res.json())
 | 
			
		||||
            .then((data) => {
 | 
			
		||||
@ -19,19 +22,25 @@ function Account() {
 | 
			
		||||
                }
 | 
			
		||||
            })
 | 
			
		||||
            .catch((err) => {
 | 
			
		||||
                toast.error(`Error checking login status. Reason: ${err.message}`);
 | 
			
		||||
                console.error("Error checking login status:", err);
 | 
			
		||||
                setIsLoggedIn(false); // Assume not logged in on error
 | 
			
		||||
                setIsLoggedIn(false);
 | 
			
		||||
            });
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    if (isLoggedIn === null) {
 | 
			
		||||
        return <div>Loading...</div>; // Optional loading UI
 | 
			
		||||
        return <div>Loading...</div>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div id="accountpage" className="w-full h-full flex">
 | 
			
		||||
        <>
 | 
			
		||||
            <NavBar />
 | 
			
		||||
            <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 { FaDownload } from "react-icons/fa";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import Container from "../Container";
 | 
			
		||||
import NavBar from "../NavBar";
 | 
			
		||||
 | 
			
		||||
function Cache() {
 | 
			
		||||
    const [searchQuery, setSearchQuery] = 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);
 | 
			
		||||
 | 
			
		||||
    // Fetch the key count when the component mounts
 | 
			
		||||
@ -23,32 +30,40 @@ function Cache() {
 | 
			
		||||
 | 
			
		||||
    const handleInputChange = (event) => {
 | 
			
		||||
        const query = event.target.value;
 | 
			
		||||
        setSearchQuery(query); // Update the search query
 | 
			
		||||
        setSearchQuery(query);
 | 
			
		||||
 | 
			
		||||
        // Clear the previous timeout
 | 
			
		||||
        if (debounceTimeout.current) {
 | 
			
		||||
            clearTimeout(debounceTimeout.current);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set a new timeout to send the API call after 1 second of no typing
 | 
			
		||||
        debounceTimeout.current = setTimeout(() => {
 | 
			
		||||
        if (query.trim() !== "") {
 | 
			
		||||
                sendApiCall(query); // Only call the API if the query is not empty
 | 
			
		||||
            setLoading(true); // Show spinner immediately
 | 
			
		||||
            debounceTimeout.current = setTimeout(() => {
 | 
			
		||||
                sendApiCall(query);
 | 
			
		||||
            }, 1000);
 | 
			
		||||
        } else {
 | 
			
		||||
                setCacheData([]); // Clear results if query is empty
 | 
			
		||||
            setHasSearched(false); // Reset state when input is cleared
 | 
			
		||||
            setCacheData([]);
 | 
			
		||||
        }
 | 
			
		||||
        }, 1000); // 1 second delay
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const sendApiCall = (text) => {
 | 
			
		||||
        setLoading(true);
 | 
			
		||||
        fetch("/api/cache/search", {
 | 
			
		||||
            method: "POST",
 | 
			
		||||
            headers: { "Content-Type": "application/json" },
 | 
			
		||||
            body: JSON.stringify({ input: text }),
 | 
			
		||||
        })
 | 
			
		||||
            .then((response) => response.json())
 | 
			
		||||
            .then((data) => setCacheData(data)) // Update cache data with the results
 | 
			
		||||
            .catch((error) => console.error("Error:", error));
 | 
			
		||||
            .then((data) => {
 | 
			
		||||
                setCacheData(data);
 | 
			
		||||
                setHasSearched(true);
 | 
			
		||||
            })
 | 
			
		||||
            .catch((error) => {
 | 
			
		||||
                toast.error(`Error: ${error.message}`);
 | 
			
		||||
                console.error("Error:", error);
 | 
			
		||||
            })
 | 
			
		||||
            .finally(() => setLoading(false));
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
@ -56,51 +71,71 @@ function Cache() {
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    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 />
 | 
			
		||||
            <Container>
 | 
			
		||||
                <div className="my-4 flex w-full flex-col items-center justify-center gap-2 lg:flex-row">
 | 
			
		||||
                    <fieldset className="fieldset w-full max-w-2xl">
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                            value={searchQuery}
 | 
			
		||||
                            onChange={handleInputChange}
 | 
			
		||||
                    placeholder={`Search ${keyCount} keys...`} // Dynamic placeholder
 | 
			
		||||
                    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"
 | 
			
		||||
                            placeholder={`Search ${keyCount} keys...`}
 | 
			
		||||
                            className="input w-full max-w-2xl font-mono"
 | 
			
		||||
                        />
 | 
			
		||||
                <a
 | 
			
		||||
                    href="/api/cache/download"
 | 
			
		||||
                    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>
 | 
			
		||||
                    <button
 | 
			
		||||
                        className="btn btn-success"
 | 
			
		||||
                        onClick={() => {
 | 
			
		||||
                            window.location.href = "/api/cache/download";
 | 
			
		||||
                        }}
 | 
			
		||||
                    >
 | 
			
		||||
                    Download Cache
 | 
			
		||||
                </a>
 | 
			
		||||
                        <FaDownload />
 | 
			
		||||
                        Download keys cache
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
            <div className="w-full grow p-4 border-2 border-emerald-500/50 rounded-2xl mt-5 overflow-y-auto">
 | 
			
		||||
                <table className="min-w-full text-white">
 | 
			
		||||
 | 
			
		||||
                {loading ? (
 | 
			
		||||
                    <div className="flex justify-center py-16">
 | 
			
		||||
                        <span className="loading loading-spinner loading-md me-2"></span>
 | 
			
		||||
                        Searching...
 | 
			
		||||
                    </div>
 | 
			
		||||
                ) : cacheData.length > 0 ? (
 | 
			
		||||
                    <div className="my-4 flex justify-center">
 | 
			
		||||
                        <div className="overflow-x-auto">
 | 
			
		||||
                            <table className="table">
 | 
			
		||||
                                <thead>
 | 
			
		||||
                                    <tr>
 | 
			
		||||
                            <th className="p-2 border border-black">PSSH</th>
 | 
			
		||||
                            <th className="p-2 border border-black">KID</th>
 | 
			
		||||
                            <th className="p-2 border border-black">Key</th>
 | 
			
		||||
                                        <th></th>
 | 
			
		||||
                                        <th className="text-center">PSSH</th>
 | 
			
		||||
                                        <th className="text-center">key ID:key pair</th>
 | 
			
		||||
                                    </tr>
 | 
			
		||||
                                </thead>
 | 
			
		||||
                                <tbody>
 | 
			
		||||
                        {cacheData.length > 0 ? (
 | 
			
		||||
                            cacheData.map((item, index) => (
 | 
			
		||||
                                    {cacheData.map((item, index) => (
 | 
			
		||||
                                        <tr key={index}>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.PSSH}</td>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.KID}</td>
 | 
			
		||||
                                    <td className="p-2 border border-black">{item.Key}</td>
 | 
			
		||||
                                </tr>
 | 
			
		||||
                            ))
 | 
			
		||||
                        ) : (
 | 
			
		||||
                            <tr>
 | 
			
		||||
                                <td colSpan="3" className="p-2 border border-black text-center">
 | 
			
		||||
                                    No data found
 | 
			
		||||
                                            <th>{index + 1}</th>
 | 
			
		||||
                                            <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 NavBar from "../NavBar";
 | 
			
		||||
import Container from "../Container";
 | 
			
		||||
import { toast } from "sonner";
 | 
			
		||||
import { IoInformationCircleOutline } from "react-icons/io5";
 | 
			
		||||
 | 
			
		||||
function HomePage() {
 | 
			
		||||
    const [pssh, setPssh] = useState("");
 | 
			
		||||
@ -58,7 +62,7 @@ function HomePage() {
 | 
			
		||||
            })
 | 
			
		||||
            .catch((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);
 | 
			
		||||
            });
 | 
			
		||||
    };
 | 
			
		||||
@ -67,14 +71,15 @@ function HomePage() {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        if (messageRef.current) {
 | 
			
		||||
            const textToCopy = messageRef.current.innerText; // Grab the plain text (with visual line breaks)
 | 
			
		||||
            toast.success("Copied to clipboard");
 | 
			
		||||
            navigator.clipboard.writeText(textToCopy).catch((err) => {
 | 
			
		||||
                alert("Failed to copy!");
 | 
			
		||||
                toast.error(`Failed to copy. Reason: ${err.message}`);
 | 
			
		||||
                console.error(err);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleFetchPaste = () => {
 | 
			
		||||
    const handleFetchPaste = (event) => {
 | 
			
		||||
        event.preventDefault();
 | 
			
		||||
        readTextFromClipboard()
 | 
			
		||||
            .then(() => {
 | 
			
		||||
@ -84,7 +89,8 @@ function HomePage() {
 | 
			
		||||
                setData(document.getElementById("data").value);
 | 
			
		||||
            })
 | 
			
		||||
            .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,75 +139,87 @@ function HomePage() {
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className="flex flex-col w-full overflow-y-auto p-4 min-h-full">
 | 
			
		||||
                <form className="flex flex-col w-full h-full bg-black/5 p-4 overflow-y-auto">
 | 
			
		||||
                    <label htmlFor="pssh" className="text-white w-8/10 self-center">
 | 
			
		||||
                        PSSH:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
            <NavBar />
 | 
			
		||||
            <Container>
 | 
			
		||||
                <div className="mx-auto flex w-full max-w-2xl flex-col justify-center">
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">PSSH*</legend>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                        id="pssh"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
                            className="input w-full font-mono"
 | 
			
		||||
                            placeholder="Enter PSSH here"
 | 
			
		||||
                            value={pssh}
 | 
			
		||||
                            onChange={(e) => setPssh(e.target.value)}
 | 
			
		||||
                            required
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="licurl" className="text-white w-8/10 self-center">
 | 
			
		||||
                        License URL:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
                        <p className="label text-red-500">* Required</p>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">License URL*</legend>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                        id="licurl"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
                            className="input w-full font-mono"
 | 
			
		||||
                            placeholder="Enter License URL here"
 | 
			
		||||
                            value={licurl}
 | 
			
		||||
                            onChange={(e) => setLicurl(e.target.value)}
 | 
			
		||||
                            required
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="proxy" className="text-white w-8/10 self-center">
 | 
			
		||||
                        Proxy:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
                        <p className="label text-red-500">* Required</p>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">Proxy</legend>
 | 
			
		||||
                        <input
 | 
			
		||||
                            type="text"
 | 
			
		||||
                        id="proxy"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white p-1"
 | 
			
		||||
                            className="input w-full font-mono"
 | 
			
		||||
                            placeholder="Enter Proxy here (https://example.com:8080)"
 | 
			
		||||
                            value={proxy}
 | 
			
		||||
                            onChange={(e) => setProxy(e.target.value)}
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="headers" className="text-white w-8/10 self-center">
 | 
			
		||||
                        Headers:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">
 | 
			
		||||
                            Headers*
 | 
			
		||||
                            <div
 | 
			
		||||
                                className="tooltip"
 | 
			
		||||
                                data-tip="You can use https://curlconverter.com/python/ to paste the header values here"
 | 
			
		||||
                            >
 | 
			
		||||
                                <IoInformationCircleOutline className="h-5 w-5" />
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </legend>
 | 
			
		||||
                        <textarea
 | 
			
		||||
                        id="headers"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
                            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'}"
 | 
			
		||||
                            value={headers}
 | 
			
		||||
                            onChange={(e) => setHeaders(e.target.value)}
 | 
			
		||||
                            required
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="cookies" className="text-white w-8/10 self-center">
 | 
			
		||||
                        Cookies:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
                        <p className="label text-red-500">* Required</p>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">Cookies</legend>
 | 
			
		||||
                        <textarea
 | 
			
		||||
                        id="cookies"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
                            className="textarea h-48 w-full font-mono"
 | 
			
		||||
                            placeholder="Enter cookies here (JSON format)"
 | 
			
		||||
                            value={cookies}
 | 
			
		||||
                            onChange={(e) => setCookies(e.target.value)}
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="data" className="text-white w-8/10 self-center">
 | 
			
		||||
                        Data:{" "}
 | 
			
		||||
                    </label>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">Data</legend>
 | 
			
		||||
                        <textarea
 | 
			
		||||
                        id="data"
 | 
			
		||||
                        className="w-8/10 border-2 border-sky-500/25 rounded-xl self-center m-2 text-white p-1 h-48"
 | 
			
		||||
                            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 */}
 | 
			
		||||
                    {devices.length > 0 && (
 | 
			
		||||
                        <>
 | 
			
		||||
                            <label htmlFor="device" className="text-white w-8/10 self-center">
 | 
			
		||||
                                Select Device:
 | 
			
		||||
                            </label>
 | 
			
		||||
                            <fieldset className="fieldset">
 | 
			
		||||
                                <legend className="fieldset-legend text-base">Select device</legend>
 | 
			
		||||
                                <select
 | 
			
		||||
                                id="device"
 | 
			
		||||
                                className="w-8/10 border-2 border-sky-500/25 rounded-xl h-10 self-center m-2 text-white bg-black p-1"
 | 
			
		||||
                                    className="select w-full"
 | 
			
		||||
                                    value={selectedDevice}
 | 
			
		||||
                                    onChange={(e) => setSelectedDevice(e.target.value)}
 | 
			
		||||
                                >
 | 
			
		||||
@ -211,60 +229,60 @@ function HomePage() {
 | 
			
		||||
                                        </option>
 | 
			
		||||
                                    ))}
 | 
			
		||||
                                </select>
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                        </>
 | 
			
		||||
                    )}
 | 
			
		||||
 | 
			
		||||
                    <div className="flex flex-col lg:flex-row w-full self-center mt-5 items-center lg:justify-around lg:items-stretch">
 | 
			
		||||
                    <div className="mx-auto my-4 flex w-full flex-col items-center justify-center gap-2 lg:flex-row">
 | 
			
		||||
                        <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}
 | 
			
		||||
                            disabled={pssh === "" || licurl === "" || headers === ""}
 | 
			
		||||
                        >
 | 
			
		||||
                            Submit
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <button
 | 
			
		||||
                            type="button"
 | 
			
		||||
                            className="btn btn-info btn-wide"
 | 
			
		||||
                            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
 | 
			
		||||
                        </button>
 | 
			
		||||
                        <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}
 | 
			
		||||
                        >
 | 
			
		||||
                            Reset
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </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>
 | 
			
		||||
                    <>
 | 
			
		||||
                        <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 pt-10 break-words overflow-y-auto"
 | 
			
		||||
                                        className="w-full grow overflow-y-auto font-mono break-words"
 | 
			
		||||
                                        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"
 | 
			
		||||
                                    <div
 | 
			
		||||
                                        className="card-actions mt-4 justify-end"
 | 
			
		||||
                                        onClick={handleCopy}
 | 
			
		||||
                                    >
 | 
			
		||||
                            Copy Results
 | 
			
		||||
                        </button>
 | 
			
		||||
                                        <button className="btn btn-success">Copy results</button>
 | 
			
		||||
                                    </div>
 | 
			
		||||
                                </div>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
            </Container>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
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() {
 | 
			
		||||
    const [wvList, setWvList] = useState([]);
 | 
			
		||||
@ -21,6 +23,7 @@ function MyAccount() {
 | 
			
		||||
            setUsername(response.data.Styled_Username || "");
 | 
			
		||||
            setApiKey(response.data.API_Key || "");
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            toast.error(`Failed to fetch user info. Reason: ${err.message}`);
 | 
			
		||||
            console.error("Failed to fetch user info", err);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@ -43,7 +46,7 @@ function MyAccount() {
 | 
			
		||||
            (cdmType === "PR" && extension !== "prd") ||
 | 
			
		||||
            (cdmType === "WV" && extension !== "wvd")
 | 
			
		||||
        ) {
 | 
			
		||||
            alert(`Please upload a .${cdmType === "PR" ? "prd" : "wvd"} file.`);
 | 
			
		||||
            toast.error(`Please upload a .${cdmType === "PR" ? "prd" : "wvd"} file.`);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -55,9 +58,10 @@ function MyAccount() {
 | 
			
		||||
            await axios.post(`/upload/${cdmType}`, formData);
 | 
			
		||||
            await fetchUserInfo(); // Refresh list after upload
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            toast.error(`Upload failed. Reason: ${err.message}`);
 | 
			
		||||
            console.error("Upload failed", err);
 | 
			
		||||
            alert("Upload failed");
 | 
			
		||||
        } finally {
 | 
			
		||||
            toast.success(`${cdmType} CDM uploaded successfully`);
 | 
			
		||||
            setUploading(false);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@ -66,17 +70,18 @@ function MyAccount() {
 | 
			
		||||
    const handleLogout = async () => {
 | 
			
		||||
        try {
 | 
			
		||||
            await axios.post("/logout");
 | 
			
		||||
            toast.success("Logged out successfully. Reloading page...");
 | 
			
		||||
            window.location.reload();
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            toast.error(`Logout failed. Reason: ${error.message}`);
 | 
			
		||||
            console.error("Logout failed:", error);
 | 
			
		||||
            alert("Logout failed!");
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Handle change password
 | 
			
		||||
    const handleChangePassword = async () => {
 | 
			
		||||
        if (passwordError || password === "") {
 | 
			
		||||
            alert("Please enter a valid password.");
 | 
			
		||||
            toast.error("Please enter a valid password");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -86,16 +91,16 @@ function MyAccount() {
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            if (response.data.message === "True") {
 | 
			
		||||
                alert("Password changed successfully.");
 | 
			
		||||
                toast.success("Password changed successfully");
 | 
			
		||||
                setPassword("");
 | 
			
		||||
            } else {
 | 
			
		||||
                alert("Failed to change password.");
 | 
			
		||||
                toast.error("Failed to change password");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            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 {
 | 
			
		||||
                alert("Error occurred while changing password.");
 | 
			
		||||
                toast.error("Error occurred while changing password");
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
@ -103,7 +108,7 @@ function MyAccount() {
 | 
			
		||||
    // Handle change API key
 | 
			
		||||
    const handleChangeApiKey = async () => {
 | 
			
		||||
        if (apiKeyError || newApiKey === "") {
 | 
			
		||||
            alert("Please enter a valid API key.");
 | 
			
		||||
            toast.error("Please enter a valid API key");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -112,128 +117,144 @@ function MyAccount() {
 | 
			
		||||
                new_api_key: newApiKey,
 | 
			
		||||
            });
 | 
			
		||||
            if (response.data.message === "True") {
 | 
			
		||||
                alert("API key changed successfully.");
 | 
			
		||||
                toast.success("API key changed successfully");
 | 
			
		||||
                setApiKey(newApiKey);
 | 
			
		||||
                setNewApiKey("");
 | 
			
		||||
            } else {
 | 
			
		||||
                alert("Failed to change API key.");
 | 
			
		||||
                toast.error("Failed to change API key");
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            alert("Error occurred while changing API key.");
 | 
			
		||||
            toast.error("Error occurred while changing API key");
 | 
			
		||||
            console.error(error);
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            id="myaccount"
 | 
			
		||||
            className="flex flex-col lg:flex-row gap-4 w-full min-h-full overflow-y-auto p-4"
 | 
			
		||||
        >
 | 
			
		||||
            <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">
 | 
			
		||||
                <h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 w-full text-center mb-2">
 | 
			
		||||
                    {username ? `${username}` : "My Account"}
 | 
			
		||||
                </h1>
 | 
			
		||||
        <>
 | 
			
		||||
            <Container>
 | 
			
		||||
                <div className="flex flex-col gap-4 p-4 lg:flex-row">
 | 
			
		||||
                    {/* Left Panel - Account Settings */}
 | 
			
		||||
                    <div className="w-full lg:w-96">
 | 
			
		||||
                        <div className="card bg-base-200 shadow-xl">
 | 
			
		||||
                            <div className="card-body">
 | 
			
		||||
                                <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="w-full flex flex-col items-center">
 | 
			
		||||
                    <label htmlFor="apiKey" className="text-white font-semibold mb-1">
 | 
			
		||||
                                <div className="divider"></div>
 | 
			
		||||
 | 
			
		||||
                                <fieldset className="fieldset">
 | 
			
		||||
                                    <legend className="fieldset-legend text-base" htmlFor="apiKey">
 | 
			
		||||
                                        API Key
 | 
			
		||||
                    </label>
 | 
			
		||||
                                    </legend>
 | 
			
		||||
                                    <input
 | 
			
		||||
                        id="apiKey"
 | 
			
		||||
                                        name="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"
 | 
			
		||||
                                        className="input input-bordered text-center"
 | 
			
		||||
                                    />
 | 
			
		||||
 | 
			
		||||
                    {/* New API Key Section */}
 | 
			
		||||
                    <label htmlFor="newApiKey" className="text-white font-semibold mt-4 mb-1">
 | 
			
		||||
                                    <legend
 | 
			
		||||
                                        className="fieldset-legend text-base"
 | 
			
		||||
                                        htmlFor="newApiKey"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        New API Key
 | 
			
		||||
                    </label>
 | 
			
		||||
                                    </legend>
 | 
			
		||||
                                    <input
 | 
			
		||||
                        id="newApiKey"
 | 
			
		||||
                                        name="newApiKey"
 | 
			
		||||
                                        type="text"
 | 
			
		||||
                                        value={newApiKey}
 | 
			
		||||
                                        onChange={(e) => {
 | 
			
		||||
                                            const value = e.target.value;
 | 
			
		||||
                            const isValid = /^[^\s]+$/.test(value); // No spaces
 | 
			
		||||
                                            const isValid = /^[^\s]+$/.test(value);
 | 
			
		||||
                                            if (!isValid) {
 | 
			
		||||
                                setApiKeyError("API key must not contain spaces.");
 | 
			
		||||
                                                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"
 | 
			
		||||
                                        className="input input-bordered"
 | 
			
		||||
                                    />
 | 
			
		||||
                    {apiKeyError && <p className="text-red-500 text-sm mb-3">{apiKeyError}</p>}
 | 
			
		||||
                                    {apiKeyError && (
 | 
			
		||||
                                        <p className="label text-error">{apiKeyError}</p>
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    <button
 | 
			
		||||
                        className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
 | 
			
		||||
                                        className="btn btn-primary btn-block mt-2"
 | 
			
		||||
                                        onClick={handleChangeApiKey}
 | 
			
		||||
                                    >
 | 
			
		||||
                        Change API Key
 | 
			
		||||
                                        Change API key
 | 
			
		||||
                                    </button>
 | 
			
		||||
                                </fieldset>
 | 
			
		||||
 | 
			
		||||
                    {/* Change Password Section */}
 | 
			
		||||
                    <label htmlFor="password" className="text-white font-semibold mt-4 mb-1">
 | 
			
		||||
                        Change Password
 | 
			
		||||
                    </label>
 | 
			
		||||
                                <fieldset className="fieldset">
 | 
			
		||||
                                    <legend
 | 
			
		||||
                                        className="fieldset-legend text-base"
 | 
			
		||||
                                        htmlFor="passwordChange"
 | 
			
		||||
                                    >
 | 
			
		||||
                                        Change password
 | 
			
		||||
                                    </legend>
 | 
			
		||||
                                    <input
 | 
			
		||||
                        id="password"
 | 
			
		||||
                                        name="passwordChange"
 | 
			
		||||
                                        type="password"
 | 
			
		||||
                                        value={password}
 | 
			
		||||
                                        onChange={(e) => {
 | 
			
		||||
                                            const value = e.target.value;
 | 
			
		||||
                                            const isValid =
 | 
			
		||||
                                /^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(value);
 | 
			
		||||
                            if (!isValid) {
 | 
			
		||||
                                setPasswordError(
 | 
			
		||||
                                    "Password must not contain spaces or invalid characters."
 | 
			
		||||
                                                /^[A-Za-z0-9!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?`~]*$/.test(
 | 
			
		||||
                                                    value
 | 
			
		||||
                                                );
 | 
			
		||||
                                            if (!isValid) {
 | 
			
		||||
                                                setPasswordError("Invalid password characters");
 | 
			
		||||
                                            } else {
 | 
			
		||||
                                                setPasswordError("");
 | 
			
		||||
                                            }
 | 
			
		||||
                                            setPassword(value);
 | 
			
		||||
                                        }}
 | 
			
		||||
                        placeholder="New Password"
 | 
			
		||||
                        className="w-full p-2 mb-1 rounded bg-gray-800 text-white border border-gray-600 text-center"
 | 
			
		||||
                                        placeholder="New password"
 | 
			
		||||
                                        className="input input-bordered"
 | 
			
		||||
                                    />
 | 
			
		||||
                    {passwordError && <p className="text-red-500 text-sm mb-3">{passwordError}</p>}
 | 
			
		||||
                                    {passwordError && (
 | 
			
		||||
                                        <p className="label text-error">{passwordError}</p>
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    <button
 | 
			
		||||
                        className="w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
 | 
			
		||||
                                        className="btn btn-secondary btn-block mt-2"
 | 
			
		||||
                                        onClick={handleChangePassword}
 | 
			
		||||
                                    >
 | 
			
		||||
                        Change Password
 | 
			
		||||
                                        Change password
 | 
			
		||||
                                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                                </fieldset>
 | 
			
		||||
 | 
			
		||||
                <button
 | 
			
		||||
                    onClick={handleLogout}
 | 
			
		||||
                    className="mt-auto w-full h-12 bg-yellow-500/50 rounded-2xl text-2xl text-white"
 | 
			
		||||
                >
 | 
			
		||||
                                <div className="divider"></div>
 | 
			
		||||
 | 
			
		||||
                                <button className="btn btn-error mt-auto" onClick={handleLogout}>
 | 
			
		||||
                                    Log out
 | 
			
		||||
                                </button>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
 | 
			
		||||
            <div className="flex flex-col w-full lg:ml-2 mt-2 lg:mt-0">
 | 
			
		||||
                {/* 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">
 | 
			
		||||
                    <h1 className="bg-black text-2xl font-bold text-white border-b-2 border-white p-2">
 | 
			
		||||
                        Widevine CDMs
 | 
			
		||||
                    </h1>
 | 
			
		||||
                    <div className="flex flex-col w-full grow p-2 bg-white/5 rounded-2xl mt-2 text-white text-left">
 | 
			
		||||
                    {/* Right Panel - CDM Uploads */}
 | 
			
		||||
                    <div className="flex w-full flex-col gap-4">
 | 
			
		||||
                        {/* Widevine CDM */}
 | 
			
		||||
                        <div className="card bg-base-200 shadow-xl">
 | 
			
		||||
                            <div className="card-body">
 | 
			
		||||
                                <h2 className="card-title">Widevine CDMs</h2>
 | 
			
		||||
                                <div className="divider"></div>
 | 
			
		||||
                                <div className="max-h-60 space-y-2 overflow-y-auto">
 | 
			
		||||
                                    {wvList.length === 0 ? (
 | 
			
		||||
                            <div className="text-white text-center font-bold">
 | 
			
		||||
                                        <div className="text-center text-sm">
 | 
			
		||||
                                            No Widevine CDMs uploaded.
 | 
			
		||||
                                        </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"
 | 
			
		||||
                                                className={`rounded px-2 py-1 text-sm ${
 | 
			
		||||
                                                    i % 2 === 0 ? "bg-base-100" : "bg-base-300"
 | 
			
		||||
                                                }`}
 | 
			
		||||
                                            >
 | 
			
		||||
                                                {filename}
 | 
			
		||||
@ -241,7 +262,7 @@ function MyAccount() {
 | 
			
		||||
                                        ))
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </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">
 | 
			
		||||
                                <label className="btn btn-accent mt-4">
 | 
			
		||||
                                    {uploading ? "Uploading..." : "Upload CDM"}
 | 
			
		||||
                                    <input
 | 
			
		||||
                                        type="file"
 | 
			
		||||
@ -251,23 +272,24 @@ function MyAccount() {
 | 
			
		||||
                                    />
 | 
			
		||||
                                </label>
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                {/* Playready Section */}
 | 
			
		||||
                <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">
 | 
			
		||||
                    <h1 className="text-2xl font-bold text-white border-b-2 border-white p-2 bg-black">
 | 
			
		||||
                        Playready CDMs
 | 
			
		||||
                    </h1>
 | 
			
		||||
                    <div className="flex flex-col w-full bg-white/5 grow rounded-2xl mt-2 text-white text-left p-2">
 | 
			
		||||
                        {/* 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-white text-center font-bold">
 | 
			
		||||
                                No Playready CDMs uploaded.
 | 
			
		||||
                                        <div className="text-center text-sm">
 | 
			
		||||
                                            No PlayReady CDMs uploaded.
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    ) : (
 | 
			
		||||
                                        prList.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"
 | 
			
		||||
                                                className={`rounded px-2 py-1 text-sm ${
 | 
			
		||||
                                                    i % 2 === 0 ? "bg-base-100" : "bg-base-300"
 | 
			
		||||
                                                }`}
 | 
			
		||||
                                            >
 | 
			
		||||
                                                {filename}
 | 
			
		||||
@ -275,7 +297,7 @@ function MyAccount() {
 | 
			
		||||
                                        ))
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </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">
 | 
			
		||||
                                <label className="btn btn-accent mt-4">
 | 
			
		||||
                                    {uploading ? "Uploading..." : "Upload CDM"}
 | 
			
		||||
                                    <input
 | 
			
		||||
                                        type="file"
 | 
			
		||||
@ -287,6 +309,9 @@ function MyAccount() {
 | 
			
		||||
                            </div>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </Container>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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() {
 | 
			
		||||
    const [username, setUsername] = useState("");
 | 
			
		||||
    const [password, setPassword] = useState("");
 | 
			
		||||
    const [status, setStatus] = useState("");
 | 
			
		||||
 | 
			
		||||
    // Validation functions
 | 
			
		||||
    const validateUsername = (name) => /^[A-Za-z0-9_-]+$/.test(name);
 | 
			
		||||
    const validatePassword = (pass) => /^\S+$/.test(pass); // No spaces
 | 
			
		||||
    const [confirmPassword, setConfirmPassword] = useState("");
 | 
			
		||||
    const [tab, setTab] = useState("login"); // 'login' or 'register'
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        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)) {
 | 
			
		||||
            setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
 | 
			
		||||
            toast.error("Invalid username. Use only letters, numbers, hyphens, or underscores.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!validatePassword(password)) {
 | 
			
		||||
            setStatus("Invalid password. Spaces are not allowed.");
 | 
			
		||||
            toast.error("Invalid password. Spaces are not allowed.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (tab === "register") {
 | 
			
		||||
            if (password !== confirmPassword) {
 | 
			
		||||
                toast.error("Passwords do not match.");
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            try {
 | 
			
		||||
            const response = await fetch("/register", {
 | 
			
		||||
                const res = await fetch("/register", {
 | 
			
		||||
                    method: "POST",
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        "Content-Type": "application/json",
 | 
			
		||||
                    },
 | 
			
		||||
                    body: JSON.stringify({ username, password }),
 | 
			
		||||
                });
 | 
			
		||||
            const data = await response.json();
 | 
			
		||||
                const data = await res.json();
 | 
			
		||||
                if (data.message) {
 | 
			
		||||
                setStatus(data.message);
 | 
			
		||||
            } else if (data.error) {
 | 
			
		||||
                setStatus(data.error);
 | 
			
		||||
                    toast.success(data.message);
 | 
			
		||||
                } else {
 | 
			
		||||
                    toast.error(data.error || "Unknown error");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
            setStatus("An error occurred while registering.");
 | 
			
		||||
                toast.error(`Register error: ${err.message}`);
 | 
			
		||||
            }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleLogin = async () => {
 | 
			
		||||
        if (!validateUsername(username)) {
 | 
			
		||||
            setStatus("Invalid username. Use only letters, numbers, hyphens, or underscores.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!validatePassword(password)) {
 | 
			
		||||
            setStatus("Invalid password. Spaces are not allowed.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        } else {
 | 
			
		||||
            try {
 | 
			
		||||
            const response = await fetch("/login", {
 | 
			
		||||
                const res = await fetch("/login", {
 | 
			
		||||
                    method: "POST",
 | 
			
		||||
                    headers: {
 | 
			
		||||
                        "Content-Type": "application/json",
 | 
			
		||||
                    },
 | 
			
		||||
                credentials: "include", // Important to send cookies
 | 
			
		||||
                    credentials: "include",
 | 
			
		||||
                    body: JSON.stringify({ username, password }),
 | 
			
		||||
                });
 | 
			
		||||
            const data = await response.json();
 | 
			
		||||
                const data = await res.json();
 | 
			
		||||
                if (data.message) {
 | 
			
		||||
                // Successful login - reload the page to trigger Account check
 | 
			
		||||
                    window.location.reload();
 | 
			
		||||
            } else if (data.error) {
 | 
			
		||||
                setStatus(data.error);
 | 
			
		||||
                } else {
 | 
			
		||||
                    toast.error(data.error || "Login failed");
 | 
			
		||||
                }
 | 
			
		||||
            } catch (err) {
 | 
			
		||||
            setStatus("An error occurred while logging in.");
 | 
			
		||||
                toast.error(`Login error: ${err.message}`);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="flex flex-col w-full h-full items-center justify-center p-4">
 | 
			
		||||
            <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="flex flex-col w-full">
 | 
			
		||||
                    <label htmlFor="username" className="text-lg font-bold mb-2 text-white">
 | 
			
		||||
                        Username:
 | 
			
		||||
                    </label>
 | 
			
		||||
        <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="mx-auto">
 | 
			
		||||
                {/* Tabs */}
 | 
			
		||||
                <div className="tabs tabs-box justify-center">
 | 
			
		||||
                    <button
 | 
			
		||||
                        className={`tab ${tab === "login" ? "tab-active" : ""}`}
 | 
			
		||||
                        onClick={() => setTab("login")}
 | 
			
		||||
                    >
 | 
			
		||||
                        <IoIosLogIn className="h-6 w-6 me-1"/>
 | 
			
		||||
                        Sign in
 | 
			
		||||
                    </button>
 | 
			
		||||
                    <button
 | 
			
		||||
                        className={`tab ${tab === "register" ? "tab-active" : ""}`}
 | 
			
		||||
                        onClick={() => setTab("register")}
 | 
			
		||||
                    >
 | 
			
		||||
                        <PiUserCirclePlus className="h-6 w-6 me-1" />
 | 
			
		||||
                        Register
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                <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)}
 | 
			
		||||
                        placeholder="Username"
 | 
			
		||||
                        className="mb-4 p-2 border border-gray-300 rounded text-white bg-transparent"
 | 
			
		||||
                            className="input w-full"
 | 
			
		||||
                            placeholder="Enter your username"
 | 
			
		||||
                            required
 | 
			
		||||
                        />
 | 
			
		||||
                    <label htmlFor="password" className="text-lg font-bold mb-2 text-white">
 | 
			
		||||
                        Password:
 | 
			
		||||
                    </label>
 | 
			
		||||
                    </fieldset>
 | 
			
		||||
 | 
			
		||||
                    <fieldset className="fieldset">
 | 
			
		||||
                        <legend className="fieldset-legend text-base">Password</legend>
 | 
			
		||||
                        <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"
 | 
			
		||||
                            className="input w-full"
 | 
			
		||||
                            placeholder="Enter your password"
 | 
			
		||||
                            required
 | 
			
		||||
                        />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="flex flex-col lg:flex-row w-8/10 items-center lg:justify-between mt-4">
 | 
			
		||||
                    <button
 | 
			
		||||
                        onClick={handleLogin}
 | 
			
		||||
                        className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
 | 
			
		||||
                    >
 | 
			
		||||
                        Login
 | 
			
		||||
                    </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>
 | 
			
		||||
                    <button
 | 
			
		||||
                        onClick={handleRegister}
 | 
			
		||||
                        className="bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded mt-4 w-1/3"
 | 
			
		||||
                    >
 | 
			
		||||
                        Register
 | 
			
		||||
                    </button>
 | 
			
		||||
                </div>
 | 
			
		||||
                {status && <p className="text-sm text-white mt-4 p-4">{status}</p>}
 | 
			
		||||
                </form>
 | 
			
		||||
            </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 { toast } from "sonner";
 | 
			
		||||
import Container from "../Container";
 | 
			
		||||
import NavBar from "../NavBar";
 | 
			
		||||
 | 
			
		||||
function TestPlayer() {
 | 
			
		||||
    const [mpdUrl, setMpdUrl] = useState(""); // State to hold the MPD URL
 | 
			
		||||
    const [kids, setKids] = useState(""); // State to hold KIDs (separated by line breaks)
 | 
			
		||||
    const [keys, setKeys] = useState(""); // State to hold Keys (separated by line breaks)
 | 
			
		||||
    const [headers, setHeaders] = useState(""); // State to hold request headers
 | 
			
		||||
    const [mpdUrl, setMpdUrl] = useState("");
 | 
			
		||||
    const [headers, setHeaders] = useState("");
 | 
			
		||||
    const [keyPairs, setKeyPairs] = useState(""); // "kid:key" per line
 | 
			
		||||
 | 
			
		||||
    const videoRef = useRef(null); // Ref for the video element
 | 
			
		||||
    const playerRef = useRef(null); // Ref for Shaka Player instance
 | 
			
		||||
    const videoRef = useRef(null);
 | 
			
		||||
    const playerRef = useRef(null);
 | 
			
		||||
 | 
			
		||||
    // Function to update the MPD URL state
 | 
			
		||||
    const handleInputChange = (event) => {
 | 
			
		||||
        setMpdUrl(event.target.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Function to update KIDs and Keys
 | 
			
		||||
    const handleKidsChange = (event) => {
 | 
			
		||||
        setKids(event.target.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleKeysChange = (event) => {
 | 
			
		||||
        setKeys(event.target.value);
 | 
			
		||||
    const handleKeyPairsChange = (event) => {
 | 
			
		||||
        setKeyPairs(event.target.value);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const handleHeadersChange = (event) => {
 | 
			
		||||
@ -29,32 +25,38 @@ function TestPlayer() {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Function to initialize Shaka Player
 | 
			
		||||
    const initializePlayer = () => {
 | 
			
		||||
        if (videoRef.current) {
 | 
			
		||||
            // Initialize Shaka Player only if it's not already initialized
 | 
			
		||||
            if (!playerRef.current) {
 | 
			
		||||
                const player = new shaka.Player(videoRef.current);
 | 
			
		||||
    const initializePlayer = async () => {
 | 
			
		||||
        if (videoRef.current && !playerRef.current) {
 | 
			
		||||
            const player = new shaka.Player(); // no mediaElement
 | 
			
		||||
            await player.attach(videoRef.current); // attach later
 | 
			
		||||
            playerRef.current = player;
 | 
			
		||||
 | 
			
		||||
                // Add error listener
 | 
			
		||||
            player.addEventListener("error", (event) => {
 | 
			
		||||
                toast.error(`Error code ${event.detail.code}: ${event.detail.message}`);
 | 
			
		||||
                console.error("Error code", event.detail.code, "object", event.detail);
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Function to handle submit and configure player with DRM keys and headers
 | 
			
		||||
    const handleSubmit = () => {
 | 
			
		||||
        if (mpdUrl && kids && keys) {
 | 
			
		||||
            // Split the KIDs and Keys by new lines
 | 
			
		||||
            const kidsArray = kids.split("\n").map((k) => k.trim());
 | 
			
		||||
            const keysArray = keys.split("\n").map((k) => k.trim());
 | 
			
		||||
        if (mpdUrl && keyPairs) {
 | 
			
		||||
            // Parse KID:KEY pairs
 | 
			
		||||
            const lines = keyPairs
 | 
			
		||||
                .split("\n")
 | 
			
		||||
                .map((line) => line.trim())
 | 
			
		||||
                .filter(Boolean);
 | 
			
		||||
            const clearKeys = {};
 | 
			
		||||
 | 
			
		||||
            if (kidsArray.length !== keysArray.length) {
 | 
			
		||||
                console.error("The number of KIDs and Keys must be the same.");
 | 
			
		||||
            for (const line of lines) {
 | 
			
		||||
                const [kid, key] = line.split(":").map((part) => part.trim());
 | 
			
		||||
                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
 | 
			
		||||
            const player = new shaka.Player(videoRef.current);
 | 
			
		||||
@ -62,15 +64,10 @@ function TestPlayer() {
 | 
			
		||||
            // Widevine DRM configuration with the provided KIDs and Keys
 | 
			
		||||
            const config = {
 | 
			
		||||
                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);
 | 
			
		||||
 | 
			
		||||
            // Configure the player with ClearKey DRM and custom headers
 | 
			
		||||
@ -81,12 +78,15 @@ function TestPlayer() {
 | 
			
		||||
                .load(mpdUrl)
 | 
			
		||||
                .then(() => {
 | 
			
		||||
                    console.log("Video loaded");
 | 
			
		||||
                    toast.success("Video successfully loaded");
 | 
			
		||||
                })
 | 
			
		||||
                .catch((error) => {
 | 
			
		||||
                    toast.error(`Error loading the video. Reason: ${error.message}`);
 | 
			
		||||
                    console.error("Error loading the video", error);
 | 
			
		||||
                });
 | 
			
		||||
        } 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 (
 | 
			
		||||
        <div className="flex flex-col items-center w-full p-4">
 | 
			
		||||
            <div className="w-full flex flex-col">
 | 
			
		||||
                <video ref={videoRef} width="100%" height="auto" controls className="h-96" />
 | 
			
		||||
        <>
 | 
			
		||||
            <NavBar />
 | 
			
		||||
            <Container>
 | 
			
		||||
                <div className="flex w-full flex-col items-center justify-center py-8">
 | 
			
		||||
                    <div className="flex w-full flex-col items-center lg:flex-row lg:items-start lg:gap-4">
 | 
			
		||||
                        {/* Video Section */}
 | 
			
		||||
                        <div className="w-full lg:w-1/2">
 | 
			
		||||
                            <video
 | 
			
		||||
                                ref={videoRef}
 | 
			
		||||
                                width="100%"
 | 
			
		||||
                                height="auto"
 | 
			
		||||
                                controls
 | 
			
		||||
                                className="aspect-video max-h-96 w-full"
 | 
			
		||||
                            />
 | 
			
		||||
                        </div>
 | 
			
		||||
 | 
			
		||||
                        {/* Inputs Section */}
 | 
			
		||||
                        <div className="mt-4 flex w-full flex-col items-center lg:mt-0 lg:w-1/2">
 | 
			
		||||
                            <fieldset className="fieldset w-full">
 | 
			
		||||
                                <legend className="fieldset-legend text-base">Manifest URL*</legend>
 | 
			
		||||
                                <input
 | 
			
		||||
                                    type="text"
 | 
			
		||||
                                    value={mpdUrl}
 | 
			
		||||
                                    onChange={handleInputChange}
 | 
			
		||||
                    placeholder="MPD URL"
 | 
			
		||||
                    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"
 | 
			
		||||
                                    placeholder="Enter manifest URL here"
 | 
			
		||||
                                    className="input w-full font-mono"
 | 
			
		||||
                                />
 | 
			
		||||
                                <p className="label text-red-500">* Required</p>
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                            <fieldset className="fieldset w-full">
 | 
			
		||||
                                <legend className="fieldset-legend text-base">Key pairs*</legend>
 | 
			
		||||
                                <textarea
 | 
			
		||||
                    placeholder="KIDs (one per line)"
 | 
			
		||||
                    value={kids}
 | 
			
		||||
                    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"
 | 
			
		||||
                />
 | 
			
		||||
                <textarea
 | 
			
		||||
                    placeholder="Keys (one per line)"
 | 
			
		||||
                    value={keys}
 | 
			
		||||
                    onChange={handleKeysChange}
 | 
			
		||||
                    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"
 | 
			
		||||
                                    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="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"
 | 
			
		||||
                                    className="textarea w-full font-mono"
 | 
			
		||||
                                />
 | 
			
		||||
                <button onClick={handleSubmit} className="mt-4 p-2 bg-blue-500 text-white rounded">
 | 
			
		||||
                            </fieldset>
 | 
			
		||||
                            <button
 | 
			
		||||
                                onClick={handleSubmit}
 | 
			
		||||
                                className="btn btn-primary btn-wide my-4"
 | 
			
		||||
                            >
 | 
			
		||||
                                Submit
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </div>
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </Container>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,31 @@
 | 
			
		||||
@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 {
 | 
			
		||||
    display: none;
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,22 @@
 | 
			
		||||
import { StrictMode } from "react";
 | 
			
		||||
import { createRoot } from "react-dom/client";
 | 
			
		||||
import { BrowserRouter } from "react-router-dom";
 | 
			
		||||
import "./index.css";
 | 
			
		||||
import { Toaster } from "sonner";
 | 
			
		||||
import App from "./App.jsx";
 | 
			
		||||
import "./assets/fonts/font-face.css";
 | 
			
		||||
import "./index.css";
 | 
			
		||||
 | 
			
		||||
createRoot(document.getElementById("root")).render(
 | 
			
		||||
    <StrictMode>
 | 
			
		||||
        <BrowserRouter>
 | 
			
		||||
            <App />
 | 
			
		||||
            <Toaster
 | 
			
		||||
                richColors
 | 
			
		||||
                className="flex justify-center"
 | 
			
		||||
                position="bottom-center"
 | 
			
		||||
                duration="7000"
 | 
			
		||||
                theme="dark"
 | 
			
		||||
            />
 | 
			
		||||
        </BrowserRouter>
 | 
			
		||||
    </StrictMode>
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user