Create-CC-Shop/public/admin.html
2025-12-26 13:37:00 +01:00

486 lines
17 KiB
HTML

<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Admin - Shop Item Management</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
:root {
--gh-bg: #0d1117;
--gh-panel: #161b22;
--gh-border: #30363d;
--gh-text: #c9d1d9;
--gh-muted: #8b949e;
--gh-accent: #58a6ff;
--gh-green: #238636;
--gh-red: #f85149;
--gh-radius: 8px;
--gh-shadow: 0 4px 32px #01040960;
}
body {
background: var(--gh-bg);
color: var(--gh-text);
font-family: "Segoe UI", "Liberation Sans", Arial, sans-serif;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
h1 {
color: var(--gh-accent);
font-size: 2.2rem;
font-weight: 700;
margin: 40px 0 32px 0;
letter-spacing: -1px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 8px;
color: var(--gh-muted);
font-size: 1em;
font-weight: 600;
}
input,
select {
background: #0d1117;
color: var(--gh-text);
border: 1px solid var(--gh-border);
border-radius: var(--gh-radius);
padding: 10px 12px;
width: 100%;
box-sizing: border-box;
font-size: 1.05em;
outline: none;
transition:
border 0.2s,
box-shadow 0.2s;
}
input:focus,
select:focus {
border: 1.5px solid var(--gh-accent);
box-shadow: 0 0 0 3px #1f6feb33;
}
button {
font-weight: 600;
margin-right: 12px;
padding: 10px 20px;
border: none;
border-radius: var(--gh-radius);
cursor: pointer;
font-size: 1em;
box-shadow: none;
transition:
background 0.2s,
box-shadow 0.2s;
outline: none;
}
button.add {
background: var(--gh-green);
color: #fff;
}
button.add:hover,
button.add:focus {
background: #2ea043;
box-shadow: 0 2px 8px #2ea04333;
}
button.edit {
background: var(--gh-accent);
color: #fff;
}
button.edit:hover,
button.edit:focus {
background: #1f6feb;
box-shadow: 0 2px 8px #1f6feb33;
}
button.delete {
background: var(--gh-red);
color: #fff;
}
button.delete:hover,
button.delete:focus {
background: #da3633;
box-shadow: 0 2px 8px #da363333;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
form {
background: var(--gh-panel);
border: 1px solid var(--gh-border);
border-radius: var(--gh-radius);
padding: 32px;
box-shadow: var(--gh-shadow);
max-width: 600px;
width: 100%;
box-sizing: border-box;
}
table {
width: 100%;
border-collapse: separate; /* Use separate to enable border-radius on table */
border-spacing: 0;
margin-top: 32px;
background: var(--gh-panel);
border: 1px solid var(--gh-border);
border-radius: var(--gh-radius);
overflow: hidden; /* Ensures rounded corners apply to content */
box-shadow: var(--gh-shadow);
max-width: 600px;
}
th,
td {
padding: 15px 20px;
border-bottom: 1px solid var(--gh-border);
text-align: left;
}
th {
background: #21262d;
color: var(--gh-text);
font-weight: 600;
}
tbody tr:last-child td {
border-bottom: none;
}
tr.item-disabled {
background: #0d1117;
color: var(--gh-muted);
}
.actions {
display: flex;
gap: 8px;
}
#msg {
margin-top: 24px;
font-size: 1.1em;
color: var(--gh-green);
text-align: center;
}
#msg.error {
color: var(--gh-red);
}
.edit-row input,
.edit-row select {
width: 100px; /* Adjust width for inline edit fields */
margin-right: 0;
}
#admin-locked {
color: var(--gh-red);
font-size: 1.5em;
margin-top: 80px;
text-align: center;
font-weight: 600;
}
small {
color: var(--gh-muted);
font-size: 0.9em;
display: block;
margin-top: 4px;
}
/* Responsive adjustments */
@media (max-width: 768px) {
h1 {
font-size: 1.8rem;
margin: 24px 0;
}
form,
table {
max-width: 95vw;
margin-left: auto;
margin-right: auto;
}
th,
td {
padding: 10px 12px;
font-size: 0.9em;
}
.actions button {
padding: 6px 10px;
font-size: 0.85em;
margin-right: 4px;
}
.edit-row input,
.edit-row select {
width: 70px;
}
}
@media (max-width: 480px) {
th:nth-child(2),
td:nth-child(2) {
display: none; /* Hide price column on very small screens */
}
}
</style>
</head>
<body>
<h1>Admin - Shop Item Management</h1>
<div id="admin-locked" style="display: none">
Access denied. Admins only.
</div>
<form id="addForm" autocomplete="off">
<div class="form-group">
<label for="itemName">Item Name</label>
<input id="itemName" name="itemName" type="text" required />
</div>
<div class="form-group">
<label for="itemPrice">Price</label>
<input
id="itemPrice"
name="itemPrice"
type="number"
step="any"
required
/>
</div>
<div class="form-group">
<label for="itemQuantity">Quantity</label>
<input
id="itemQuantity"
name="itemQuantity"
type="number"
step="any"
required
/>
</div>
<div class="form-group">
<label for="itemEnabled">Enabled</label>
<select id="itemEnabled" name="itemEnabled">
<option value="1" selected>Yes</option>
<option value="0">No</option>
</select>
</div>
<button type="submit" class="add">Add Item</button>
</form>
<div id="msg"></div>
<table id="itemsTable">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Enabled</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<!-- Items will be loaded here -->
</tbody>
</table>
<script>
// --- Admin lock logic ---
function getLoggedInUser() {
try {
return JSON.parse(localStorage.getItem("user"));
} catch {
return null;
}
}
function checkAdmin() {
const user = getLoggedInUser();
if (!user || user.username !== "ZareMate") {
document.getElementById("admin-locked").style.display = "";
document.getElementById("addForm").style.display = "none";
document.getElementById("itemsTable").style.display =
"none";
document.getElementById("msg").style.display = "none";
return false;
}
document.getElementById("admin-locked").style.display = "none";
document.getElementById("addForm").style.display = "";
document.getElementById("itemsTable").style.display = "";
document.getElementById("msg").style.display = "";
return true;
}
if (!checkAdmin()) {
// Stop script execution if not admin
throw new Error("Not admin");
}
const msgEl = document.getElementById("msg");
const tableBody = document.querySelector("#itemsTable tbody");
let items = [];
let editingId = null;
function showMsg(msg, isError = false) {
msgEl.textContent = msg;
msgEl.className = isError ? "error" : "";
}
async function fetchItems() {
showMsg("");
const res = await fetch("/api/shop/items/all");
const data = await res.json();
if (data.ok) {
items = data.items;
renderTable();
} else {
showMsg(
"Failed to load items: " +
(data.error || "Unknown error"),
true,
);
}
}
function renderTable() {
tableBody.innerHTML = "";
items.forEach((item) => {
if (editingId === item.id) {
// Edit row
const tr = document.createElement("tr");
tr.className = item.enabled
? ""
: "item-disabled edit-row";
tr.innerHTML = `
<td><input type="text" value="${item.name}" id="edit-name-${item.id}" /></td>
<td><input type="number" step="any" value="${item.price}" id="edit-price-${item.id}" style="width:80px;" /></td>
<td><input type="number" step="any" value="${item.quantity}" id="edit-quantity-${item.id}" style="width:80px;" /></td>
<td>
<select id="edit-enabled-${item.id}">
<option value="1" ${item.enabled ? "selected" : ""}>Yes</option>
<option value="0" ${!item.enabled ? "selected" : ""}>No</option>
</select>
</td>
<td class="actions">
<button class="edit" onclick="saveEdit(${item.id})">Save</button>
<button onclick="cancelEdit()">Cancel</button>
</td>
`;
tableBody.appendChild(tr);
} else {
// Normal row
const tr = document.createElement("tr");
tr.className = item.enabled ? "" : "item-disabled";
tr.innerHTML = `
<td>${item.name}</td>
<td>${item.price}</td>
<td>${item.enabled ? "Yes" : "No"}</td>
<td class="actions">
<button class="edit" onclick="startEdit(${item.id})">Edit</button>
<button class="delete" onclick="deleteItem(${item.id})">Delete</button>
</td>
`;
tableBody.appendChild(tr);
}
});
}
// Add item
document.getElementById("addForm").onsubmit = async function (e) {
e.preventDefault();
showMsg("");
const name = document.getElementById("itemName").value.trim();
const price = parseFloat(
document.getElementById("itemPrice").value,
);
const quantity = parseFloat(
document.getElementById("itemQuantity").value,
);
const enabled = parseInt(
document.getElementById("itemEnabled").value,
10,
);
if (!name || isNaN(price)) {
showMsg("Please provide valid name and price.", true);
return;
}
const res = await fetch("/api/shop/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, price, enabled, quantity }),
});
const data = await res.json();
if (data.ok) {
showMsg("Item added!");
document.getElementById("addForm").reset();
fetchItems();
} else {
showMsg(
"Failed to add item: " +
(data.error || "Unknown error"),
true,
);
}
};
// Edit helpers
window.startEdit = function (id) {
editingId = id;
renderTable();
};
window.cancelEdit = function () {
editingId = null;
renderTable();
};
window.saveEdit = async function (id) {
showMsg("");
const name = document
.getElementById(`edit-name-${id}`)
.value.trim();
const price = parseFloat(
document.getElementById(`edit-price-${id}`).value,
);
const enabled = parseInt(
document.getElementById(`edit-enabled-${id}`).value,
10,
);
const quantity = parseInt(
document.getElementById(`edit-quantity-${id}`).value,
10,
);
if (!name || isNaN(price) || isNaN(quantity)) {
showMsg("Please provide valid name and price.", true);
return;
}
const res = await fetch(`/api/shop/items/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, price, enabled, quantity }),
});
const data = await res.json();
if (data.ok) {
showMsg("Item updated!");
editingId = null;
fetchItems();
} else {
showMsg(
"Failed to update item: " +
(data.error || "Unknown error"),
true,
);
}
};
// Delete item
window.deleteItem = async function (id) {
if (!confirm("Are you sure you want to delete this item?"))
return;
showMsg("");
const res = await fetch(`/api/shop/items/${id}`, {
method: "DELETE",
});
const data = await res.json();
if (data.ok) {
showMsg("Item deleted!");
fetchItems();
} else {
showMsg(
"Failed to delete item: " +
(data.error || "Unknown error"),
true,
);
}
};
// Initial load
if (checkAdmin()) {
fetchItems();
}
</script>
</body>
</html>