";
} else {
data.computers.forEach((comp) => {
const div = document.createElement("div");
div.style.display = "flex";
div.style.alignItems = "center";
div.style.marginBottom = "6px";
div.innerHTML = `Computer #${comp.id}
`;
listEl.appendChild(div);
});
}
}
window.removeComputer = async function (id) {
const user = getLoggedInUser();
const msgEl = document.getElementById("computersMsg");
msgEl.textContent = "";
if (!user) return;
const res = await fetch("/api/computers/" + id, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ linked_user: null }),
});
const data = await res.json();
if (data.ok) {
msgEl.style.color = "#23a559";
msgEl.textContent = "Computer unlinked.";
loadComputersTab();
} else {
msgEl.style.color = "#ff5555";
msgEl.textContent = "Error: " + (data.error || "Unknown error");
}
};
document.getElementById("loginBtn").onclick = openLoginModal;
document.getElementById("logoutBtn").onclick = logout;
document.getElementById("loginForm").onsubmit = async function (e) {
e.preventDefault();
const username = document.getElementById("loginUsername").value.trim();
const password = document.getElementById("loginPassword").value;
if (!username || !password) return;
const hash = await sha256(password);
// Fetch user info to verify login
let res = await fetch("/api/users/" + encodeURIComponent(username));
let data = await res.json();
if (!data.ok || !data.user) {
document.getElementById("loginMsg").textContent = "User not found";
return;
}
if (data.user.password !== hash) {
document.getElementById("loginMsg").textContent = "Incorrect password";
return;
}
setLoggedInUser({
username: data.user.username,
adresses: data.user.adresses,
});
closeLoginModal();
updateBalance();
cartPosition();
};
// --- Cart logic ---
function getCart() {
try {
return JSON.parse(localStorage.getItem("cart")) || [];
} catch {
return [];
}
}
function setCart(cart) {
localStorage.setItem("cart", JSON.stringify(cart));
updateCartUI();
}
function addToCart(item, qty = 1) {
let cart = getCart();
const idx = cart.findIndex((i) => i.id === item.id);
if (idx >= 0) {
cart[idx].qty += qty * item.quantity || 1;
} else {
cart.push({ ...item, qty: qty * item.quantity || 1 });
}
setCart(cart);
}
function removeFromCart(id) {
let cart = getCart().filter((i) => i.id !== id);
setCart(cart);
}
function updateCartUI() {
const cart = getCart();
const count = cart.reduce((a, i) => a + i.qty, 0);
document.getElementById("cartCount").textContent = count;
document.getElementById("cartCount").style.display = count ? "" : "none";
}
function openCart() {
renderCart();
document.getElementById("cartWindow").style.display = "flex";
}
function closeCart() {
document.getElementById("cartWindow").style.display = "none";
}
document.getElementById("cartBtn").addEventListener("click", () => {
openCart();
});
async function getItemSellQuantity(id) {
const res = await fetch("/api/shop/items");
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Failed to load");
const item = data.items.find((i) => i.id === id);
return item ? item.quantity : 1;
}
function renderCart() {
const cart = getCart();
const cartItems = document.getElementById("cartItems");
const cartTotal = document.getElementById("cartTotal");
if (!cart.length) {
cartItems.innerHTML = "
Your cart is empty.
";
cartTotal.textContent = "";
document.getElementById("checkoutBtn").disabled = true;
return;
}
cartItems.innerHTML = "";
let total = 0;
Promise.all(
cart.map(async (item) => {
const sellQuantity = await getItemSellQuantity(item.id);
const orderAmount = item.qty / sellQuantity;
const price = item.price * orderAmount;
const div = document.createElement("div");
div.style.display = "flex";
div.style.justifyContent = "space-between";
div.style.alignItems = "center";
div.style.marginBottom = "8px";
div.innerHTML = `
${formatItemId(item.name)} × ${item.qty}${price.toFixed(2)} $
`;
cartItems.appendChild(div);
return price;
}),
).then((prices) => {
total = prices.reduce((sum, p) => sum + p, 0);
cartTotal.textContent = "Total: " + total.toFixed(2) + " $";
document.getElementById("checkoutBtn").disabled = false;
});
}
// --- Address selection and checkout ---
function openAddressModal() {
const user = getLoggedInUser();
if (!user || !Array.isArray(user.adresses) || !user.adresses.length) {
document.getElementById("addressMsg").textContent =
"No addresses found for your account.";
document.getElementById("addressList").innerHTML = "";
document.getElementById("addressModal").style.display = "flex";
return;
}
document.getElementById("addressMsg").textContent = "";
const list = document.getElementById("addressList");
list.innerHTML = "";
user.adresses.forEach((addr, idx) => {
const id = "addr_" + idx;
const label = document.createElement("label");
label.innerHTML = ` ${addr}`;
list.appendChild(label);
});
// add separator for other user addresses
const separator = document.createElement("hr");
separator.style.margin = "10px 0";
list.appendChild(separator);
getAllAddresses().then((addresses) => {
const otherAddr = addresses.filter((addr) => !user.adresses.includes(addr));
otherAddr.forEach((addr, idx) => {
const id = "addr_" + idx;
const label = document.createElement("label");
label.innerHTML = ` ${addr}`;
list.appendChild(label);
});
});
document.getElementById("addressModal").style.display = "flex";
}
function closeAddressModal() {
document.getElementById("addressModal").style.display = "none";
}
document.getElementById("checkoutBtn").onclick = function () {
const user = getLoggedInUser();
if (!user) {
closeCart();
openLoginModal();
return;
}
openAddressModal();
};
document.getElementById("addressForm").onsubmit = function (e) {
e.preventDefault();
const addr = document.querySelector('input[name="address"]:checked');
if (!addr) {
document.getElementById("addressMsg").textContent =
"Please select an address.";
return;
}
// from client send post to API
const user = getLoggedInUser();
const cart = getCart();
const items = cart.map((item) => ({
id: item.name,
quantity: item.qty,
sellQuantity: getItemSellQuantity(item.name),
}));
const request = {
userId: user.username,
addressId: addr.value,
items: items,
};
console.log(request);
fetch("/api/shop/items/buy", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(request),
})
.then((response) => response.json())
.then((data) => {
// Conditional check
if (data.ok === true) {
console.log("Purchase successful!");
showMsg("Order placed for address: " + addr.value);
updateBalance();
} else {
console.log("Purchase failed or message did not match:", data);
showMsg("Order failed");
}
})
.catch((error) => {
console.error("Error during fetch:", error);
});
closeAddressModal();
closeCart();
setCart([]);
updateCartUI();
};
//get Item stock
async function getItemStock(itemId) {
try {
const res = await fetch(`/api/item/${itemId}`);
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Failed to load");
return data.item.count;
} catch (error) {
console.error("Error during fetch:", error);
return null;
}
}
function getCartItemCount(itemId) {
const cart = getCart();
const item = cart.find((item) => item.id === itemId);
return item ? item.count : 0;
}
//check if item can be added to cart (if item_stock > cart_item_count)
async function canAddToCart(itemId) {
const item_stock = (await getItemStock(itemId)) || 0;
const cart_item_count = getCartItemCount(itemId);
return item_stock > cart_item_count;
}
function convertStockToStackPlusRest(stock, quantity) {
const stackSize = 64;
const stacks = Math.floor(stock / stackSize);
const rest = stock % stackSize;
return { stacks, rest };
}
// --- Shop items logic ---
async function fetchItems() {
const tableBody = document.querySelector("#items-list tbody");
if (!tableBody) return;
// Clear table and show loader
tableBody.innerHTML = "
Loading items...
";
try {
const res = await fetch("/api/shop/items");
const data = await res.json();
if (!data.ok) throw new Error(data.error || "Failed to load");
if (!data.items.length) {
tableBody.innerHTML =
"