diff --git a/cdrm-frontend/.prettierrc.json b/cdrm-frontend/.prettierrc.json
index eb0d496..13040da 100644
--- a/cdrm-frontend/.prettierrc.json
+++ b/cdrm-frontend/.prettierrc.json
@@ -4,5 +4,6 @@
"semi": true,
"singleQuote": false,
"useTabs": false,
- "printWidth": 100
+ "printWidth": 100,
+ "plugins": ["prettier-plugin-tailwindcss"]
}
diff --git a/cdrm-frontend/package-lock.json b/cdrm-frontend/package-lock.json
index 5d0ec6d..caeddcf 100644
--- a/cdrm-frontend/package-lock.json
+++ b/cdrm-frontend/package-lock.json
@@ -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",
diff --git a/cdrm-frontend/package.json b/cdrm-frontend/package.json
index fd98c46..515869a 100644
--- a/cdrm-frontend/package.json
+++ b/cdrm-frontend/package.json
@@ -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"
}
}
diff --git a/cdrm-frontend/src/App.jsx b/cdrm-frontend/src/App.jsx
index ed2c71b..1921f7e 100644
--- a/cdrm-frontend/src/App.jsx
+++ b/cdrm-frontend/src/App.jsx
@@ -1,48 +1,19 @@
-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 (
-
- {/* The SideMenu should be visible when isMenuOpen is true */}
-
-
-
-
-
-
-
-
-
-
-
-
-
- } />
- } />
- } />
- } />
- } />
-
-
-
-
+
+ } />
+ } />
+ } />
+ } />
+ } />
+
);
}
diff --git a/cdrm-frontend/src/assets/fonts/InterVariable-Italic.woff2 b/cdrm-frontend/src/assets/fonts/InterVariable-Italic.woff2
new file mode 100644
index 0000000..b3530f3
Binary files /dev/null and b/cdrm-frontend/src/assets/fonts/InterVariable-Italic.woff2 differ
diff --git a/cdrm-frontend/src/assets/fonts/InterVariable.woff2 b/cdrm-frontend/src/assets/fonts/InterVariable.woff2
new file mode 100644
index 0000000..5a8d3e7
Binary files /dev/null and b/cdrm-frontend/src/assets/fonts/InterVariable.woff2 differ
diff --git a/cdrm-frontend/src/assets/fonts/font-face.css b/cdrm-frontend/src/assets/fonts/font-face.css
new file mode 100644
index 0000000..d12308e
--- /dev/null
+++ b/cdrm-frontend/src/assets/fonts/font-face.css
@@ -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;
+}
diff --git a/cdrm-frontend/src/components/Container.jsx b/cdrm-frontend/src/components/Container.jsx
new file mode 100644
index 0000000..d25cc18
--- /dev/null
+++ b/cdrm-frontend/src/components/Container.jsx
@@ -0,0 +1,9 @@
+const Container = ({ children, className = "", ...props }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Container;
diff --git a/cdrm-frontend/src/components/NavBar.jsx b/cdrm-frontend/src/components/NavBar.jsx
index 88c6034..a8896d9 100644
--- a/cdrm-frontend/src/components/NavBar.jsx
+++ b/cdrm-frontend/src/components/NavBar.jsx
@@ -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 (
+
+ (isActive ? "menu-active" : "")}>
+ {children}
+
+
+ );
+ };
+
return (
-
- {/* Header */}
-
+ <>
+
+
+
-
- {/* External links at very bottom */}
-
-
+ >
);
}
diff --git a/cdrm-frontend/src/components/Pages/API.jsx b/cdrm-frontend/src/components/Pages/API.jsx
index b43bc55..c42a0bb 100644
--- a/cdrm-frontend/src/components/Pages/API.jsx
+++ b/cdrm-frontend/src/components/Pages/API.jsx
@@ -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 (
-
-
- Sending a decryption request
-
-
- {`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'])`}
-
-
-
-
- Sending a search request
-
-
- {`import requests
+).json()['message'])`;
+
+ const searchRequest = `import requests
print(requests.post(
url='${fullHost}/api/cache/search',
json={
'input': 'AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAADsIARIQ62dqu8s0Xpa7z2FmMPGj2hoNd2lkZXZpbmVfdGVzdCIQZmtqM2xqYVNkZmFsa3IzaioCSEQyAA=='
}
-).json())`}
-
+).json())`;
+
+ return (
+ <>
+
+
+
+
+
+
+
+ Sending a decryption request
+
+
+
+ {decryptRequest}
+
+
+
+
+
+
+
+
+
+
+ Sending a search request
+
+
+
+ {searchRequest}
+
+
+
+
+
+
+
+
+
+
+ PyWidevine RemoteCDM info
+
+
+
+ Device Type:{" "}
+
+ {deviceInfo.device_type || "N/A"}
+
+
+
+ System ID:{" "}
+
+ {deviceInfo.system_id || "N/A"}
+
+
+
+ Security Level:{" "}
+
+ {deviceInfo.security_level || "N/A"}
+
+
+
+ Host:{" "}
+ {fullHost}/remotecdm/widevine
+
+
+ Secret:{" "}
+ {deviceInfo.secret || "N/A"}
+
+
+ Device Name:{" "}
+
+ {deviceInfo.device_name || "N/A"}
+
+
+
+
+
+
+
+
+ PyPlayready RemoteCDM info
+
+
+
+ Security Level:{" "}
+
+ {prDeviceInfo.security_level || "N/A"}
+
+
+
+ Host:{" "}
+
+ {fullHost}/remotecdm/playready
+
+
+
+ Secret:{" "}
+
+ {prDeviceInfo.secret || "N/A"}
+
+
+
+ Device Name:{" "}
+
+ {prDeviceInfo.device_name || "N/A"}
+
+
+
+
+
-
-
- PyWidevine RemoteCDM info
-
-
- Device Type: '{deviceInfo.device_type}'
- System ID: {deviceInfo.system_id}
-
- Security Level: {deviceInfo.security_level}
-
- Host: {fullHost}/remotecdm/widevine
-
- Secret: '{deviceInfo.secret}'
- Device Name: {deviceInfo.device_name}
-
-
-
-
- PyPlayready RemoteCDM info
-
-
- Security Level: {prDeviceInfo.security_level}
-
- Host: {fullHost}/remotecdm/playready
-
- Secret: '{prDeviceInfo.secret}'
- Device Name: {prDeviceInfo.device_name}
-
-
-
-
+
+ >
);
}
diff --git a/cdrm-frontend/src/components/Pages/Account.jsx b/cdrm-frontend/src/components/Pages/Account.jsx
index e5c865f..f31d2c6 100644
--- a/cdrm-frontend/src/components/Pages/Account.jsx
+++ b/cdrm-frontend/src/components/Pages/Account.jsx
@@ -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 Loading...
; // Optional loading UI
+ return Loading...
;
}
return (
-
- {isLoggedIn ? : }
-
+ <>
+
+
+
+ {isLoggedIn ? : }
+
+
+ >
);
}
diff --git a/cdrm-frontend/src/components/Pages/Cache.jsx b/cdrm-frontend/src/components/Pages/Cache.jsx
index 9522a4e..ce878b8 100644
--- a/cdrm-frontend/src/components/Pages/Cache.jsx
+++ b/cdrm-frontend/src/components/Pages/Cache.jsx
@@ -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
- } else {
- setCacheData([]); // Clear results if query is empty
- }
- }, 1000); // 1 second delay
+ if (query.trim() !== "") {
+ setLoading(true); // Show spinner immediately
+ debounceTimeout.current = setTimeout(() => {
+ sendApiCall(query);
+ }, 1000);
+ } else {
+ setHasSearched(false); // Reset state when input is cleared
+ setCacheData([]);
+ }
};
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 (
-
-
-
-
-
-
- PSSH |
- KID |
- Key |
-
-
-
- {cacheData.length > 0 ? (
- cacheData.map((item, index) => (
-
- {item.PSSH} |
- {item.KID} |
- {item.Key} |
-
- ))
- ) : (
-
-
- No data found
- |
-
- )}
-
-
-
-
+ <>
+
+
+
+
+
+
+
+ {loading ? (
+
+
+ Searching...
+
+ ) : cacheData.length > 0 ? (
+
+
+
+
+
+ |
+ PSSH |
+ key ID:key pair |
+
+
+
+ {cacheData.map((item, index) => (
+
+ {index + 1} |
+ {item.PSSH} |
+
+ {item.KID}:{item.Key}
+ |
+
+ ))}
+
+
+
+
+ ) : hasSearched ? (
+
+
No data found in the database
+
+ ) : (
+
+
Enter a search query to see results
+
+ )}
+
+ >
);
}
diff --git a/cdrm-frontend/src/components/Pages/HomePage.jsx b/cdrm-frontend/src/components/Pages/HomePage.jsx
index 09a2f29..2704ef9 100644
--- a/cdrm-frontend/src/components/Pages/HomePage.jsx
+++ b/cdrm-frontend/src/components/Pages/HomePage.jsx
@@ -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,138 +139,150 @@ function HomePage() {
return (
<>
-
-