486 lines
17 KiB
HTML
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>
|