Modernize admin UI with React and update templates

This commit is contained in:
2025-05-24 10:51:55 +03:30
parent d35a93e4bc
commit 0f96344543
9 changed files with 153 additions and 250 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

+23 -151
View File
@@ -1,156 +1,28 @@
{{ define "admin/index.html" }}
<!DOCTYPE html>
<html>
<head>
<title>Media Approval</title>
<style>
body {
font-family: sans-serif;
background-color: #f9f9f9;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
max-width: 300px;
position: relative;
}
.card img {
max-width: 100%;
border-radius: 4px;
margin-bottom: 15px;
}
button {
margin: 5px;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
color: white;
}
.approve { background-color: #4CAF50; }
.reject { background-color: #f44336; }
.message {
position: absolute;
top: -25px;
left: 0;
right: 0;
text-align: center;
font-size: 14px;
color: #4CAF50;
display: none;
}
</style>
</head>
<body>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Admin Dashboard</title>
<meta name="description" content="Admin Dashboard for Media Review" />
<meta name="author" content="Mahdium" />
<div class="card" id="media-card">
<div class="message" id="status-msg"></div>
<img id="preview" src="" alt="Media Preview" onerror="loadRemote()" />
<div>
<button class="approve" onclick="sendAction('approve')">Approve</button>
<button class="reject" onclick="sendAction('reject')">Reject</button>
</div>
</div>
<meta property="og:title" content="Admin Dashboard" />
<meta property="og:description" content="Admin Dashboard for Media Review" />
<meta property="og:type" content="website" />
<script>
let currentMedia = null;
let nextMedia = null;
let preloadedImage = new Image();
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@mahdium86" />
<script type="module" crossorigin src="/admin/assets/index-CWPfp0-e.js"></script>
<link rel="stylesheet" crossorigin href="/admin/assets/index-DShmOgsI.css">
</head>
window.onload = async () => {
const token = getToken();
if (!token) {
alert("No token found. Please login first.");
window.location.href = "login";
return;
}
currentMedia = await fetchMedia(token);
showMedia(currentMedia);
preloadNextMedia(token);
};
function getToken() {
const match = document.cookie.match(/(^| )token=([^;]+)/);
return match ? match[2] : null;
}
function showMedia(media) {
const preview = document.getElementById('preview');
preview.src = media.preview_url;
}
function loadRemote() {
if (currentMedia?.remote_url) {
document.getElementById('preview').src = currentMedia.remote_url;
}
}
async function sendAction(action) {
if (!currentMedia?.id) return;
const token = getToken();
const endpoint = `api/${action}`;
await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
},
body: JSON.stringify({ mediaId: currentMedia.id })
});
showMessage(action.charAt(0).toUpperCase() + action.slice(1) + " successful");
// Show next media
if (nextMedia) {
currentMedia = nextMedia;
showMedia(currentMedia);
preloadNextMedia(token);
} else {
document.getElementById('media-card').innerHTML = 'No more media.';
}
}
function showMessage(text) {
const msgDiv = document.getElementById('status-msg');
msgDiv.innerText = text;
msgDiv.style.display = 'block';
setTimeout(() => {
msgDiv.style.display = 'none';
}, 500);
}
async function fetchMedia(token) {
const res = await fetch('api/getmedia', {
method: 'GET',
headers: { 'Authorization': 'Bearer ' + token }
});
return await res.json();
}
async function preloadNextMedia(token) {
try {
nextMedia = await fetchMedia(token);
preloadedImage.src = nextMedia.preview_url;
preloadedImage.onerror = () => {
preloadedImage.src = nextMedia.remote_url;
};
} catch (err) {
console.error("Failed to preload next media:", err);
nextMedia = null;
}
}
</script>
</body>
<body>
<div id="root"></div>
<!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
<script src="https://cdn.gpteng.co/gptengineer.js" type="module"></script>
</body>
</html>
{{ end }}
-92
View File
@@ -1,92 +0,0 @@
{{ define "admin/login.html" }}
<!DOCTYPE html>
<html>
<head>
<title>Simple Login Page</title>
<style>
body {
font-family: sans-serif;
background-color: #f0f0f0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.login-container {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
input[type="password"] {
padding: 10px;
margin: 10px 0;
border: 1px solid #ccc;
border-radius: 4px;
width: 200px;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #3e8e41;
}
#message {
margin-top: 10px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="login-container">
<h2>Login</h2>
<input type="password" id="password" placeholder="Password"><br>
<button onclick="login()">Login</button>
<div id="message"></div>
</div>
<script>
function login() {
const password = document.getElementById('password').value;
fetch('api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ "Password": password })
})
.then(response => response.json())
.then(data => {
if (data.message === "Login successful") {
document.getElementById('message').innerText = data.message;
saveToken(data.token);
setTimeout(() => {
window.location.href = "/admin/";
}, 1000);
} else {
document.getElementById('message').innerText = "Login failed.";
}
})
.catch(error => {
console.error('Error:', error);
document.getElementById('message').innerText = "An error occurred.";
});
}
function saveToken(token) {
document.cookie = "token=" + token + "; path=/";
}
</script>
</body>
</html>
{{ end }}
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB