800 lines
26 KiB
JavaScript
800 lines
26 KiB
JavaScript
// --- Utility functions ---
|
||
function formatItemId(id) {
|
||
if (!id && id !== 0) return "(unknown)";
|
||
id = String(id);
|
||
|
||
// Remove any NBT/variant suffixes that might follow a space or bracket e.g. "minecraft:stone{...}"
|
||
// Keep it simple: strip anything starting with '{' or '[' or ' ' after id body.
|
||
id = id.split("{")[0].split("[")[0].trim();
|
||
|
||
// If there is a namespace, take the part after colon
|
||
const parts = id.split(":");
|
||
let name = parts.length > 1 ? parts[1] : parts[0];
|
||
|
||
// Normalize common separators
|
||
name = name.replace(/[-.]/g, "_");
|
||
|
||
// Replace multiple underscores with single space
|
||
name = name.replace(/_+/g, " ").trim();
|
||
|
||
// If name contains uppercase letters (already pretty), keep spacing but fix underscores
|
||
if (/[A-Z]/.test(name)) {
|
||
// Replace underscores and keep casing; ensure single spaces
|
||
name = name.replace(/_+/g, " ").trim();
|
||
return name;
|
||
}
|
||
|
||
// Lowercase everything then title-case just the first word for a natural look:
|
||
// "sugar cane" -> "Sugar cane", "redstone_dust" -> "Redstone dust"
|
||
name = name.toLowerCase().split(" ").filter(Boolean);
|
||
|
||
if (name.length === 0) return id;
|
||
|
||
// Title-case only first word, leave rest lowercase (matches examples like "Sugar cane")
|
||
const first = name[0];
|
||
const rest = name.slice(1);
|
||
const firstCap = first.charAt(0).toUpperCase() + first.slice(1);
|
||
const result = [firstCap].concat(rest).join(" ");
|
||
|
||
return result;
|
||
}
|
||
|
||
// /api/users output is {{"ok":true,"users":[{"username":"ZareMate","password":"74b669f042afd3fccff6d3915f436b5bf3ad98836650ecfacf5062c7229115cb","balance":12858,"adresses":["PKP Centralny: Poczta - ZareMate","Factory 1: Manual"]},{"username":"qawe","password":"cd9ecb80ec16c7d73f7713f5e25a5d96b68bfe4debcfefc214f5351ee325e09f","balance":7027.52,"adresses":["Jajomyje 17: qawe"]},{"username":"skybloczek1","password":"937824fc6a52074b0a173319ec94c5e6c5a82cf8e63d9f401b15219c7e33248a","balance":22,"adresses":["Duchnice: skypa"]}]}}
|
||
async function getAllAddresses() {
|
||
try {
|
||
const response = await fetch("/api/users");
|
||
const data = await response.json();
|
||
|
||
const addresses = data.users.map((user) => user.adresses).flat();
|
||
console.log(addresses);
|
||
|
||
return addresses;
|
||
} catch (error) {
|
||
console.error("Error fetching addresses:", error);
|
||
throw error;
|
||
}
|
||
}
|
||
|
||
// get id from name
|
||
function getId(name) {
|
||
// Implement id retrieval logic here
|
||
// name: Sugar cane -> minecraft:sugar_cane
|
||
// name: Redstone dust -> minecraft:redstone_dust
|
||
const parts = name.split(" ");
|
||
const first = parts[0].toLowerCase();
|
||
const rest = parts.slice(1).map((word) => word.toLowerCase());
|
||
const id = [first].concat(rest).join("_");
|
||
return `minecraft:${id}`;
|
||
}
|
||
|
||
function getImage(id) {
|
||
// Implement image retrieval logic here
|
||
// id: mod:item_id
|
||
// /static/textures/mod/item_id.png
|
||
const parts = id.split(":");
|
||
const mod = parts[0];
|
||
const item = parts[1];
|
||
return `/static/textures/${mod}/${item}.png`;
|
||
}
|
||
|
||
function sha256(str) {
|
||
// Returns a promise that resolves to the hex digest
|
||
const encoder = new TextEncoder();
|
||
const data = encoder.encode(str);
|
||
return crypto.subtle.digest("SHA-256", data).then((buf) =>
|
||
Array.from(new Uint8Array(buf))
|
||
.map((b) => b.toString(16).padStart(2, "0"))
|
||
.join(""),
|
||
);
|
||
}
|
||
|
||
function openTransferModal() {
|
||
const LocalUser = getLoggedInUser();
|
||
if (!LocalUser) {
|
||
document.getElementById("transferMsg").textContent =
|
||
"Please log in to transfer balance.";
|
||
document.getElementById("transferModal").style.display = "flex";
|
||
return;
|
||
}
|
||
const transferToList = document.getElementById("transferToList");
|
||
transferToList.innerHTML = "";
|
||
Promise.all([fetch("/api/users").then((res) => res.json())]).then(
|
||
([users]) => {
|
||
users.users.forEach((user) => {
|
||
if (user.username !== LocalUser.username) {
|
||
const input = document.createElement("input");
|
||
input.type = "radio";
|
||
input.name = "transferTo";
|
||
input.id = user.username;
|
||
const label = document.createElement("label");
|
||
label.htmlFor = user.username;
|
||
label.textContent = user.username;
|
||
transferToList.appendChild(input);
|
||
transferToList.appendChild(label);
|
||
transferToList.appendChild(document.createElement("br"));
|
||
}
|
||
});
|
||
},
|
||
);
|
||
document.getElementById("transferMsg").textContent = "";
|
||
document.getElementById("transferModal").style.display = "flex";
|
||
}
|
||
|
||
function closeTransferModal() {
|
||
document.getElementById("transferModal").style.display = "none";
|
||
}
|
||
|
||
// on onclick transferForm make POST request to /api/users/transfer
|
||
function submitTransferForm() {
|
||
const from = getLoggedInUser().username;
|
||
const to = document.querySelector("input[name='transferTo']:checked").id;
|
||
const amount = document.getElementById("transferAmount").value;
|
||
fetch("/api/users/transfer", {
|
||
method: "POST",
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
body: JSON.stringify({ from, to, amount }),
|
||
})
|
||
.then((res) => res.json())
|
||
.then((data) => {
|
||
if (data.ok) {
|
||
updateBalance();
|
||
closeTransferModal();
|
||
} else {
|
||
document.getElementById("transferMsg").textContent = data.error;
|
||
}
|
||
});
|
||
}
|
||
|
||
function updateBalance() {
|
||
const user = getLoggedInUser();
|
||
if (!user) {
|
||
document.getElementById("balanceAmount").textContent = 0;
|
||
document.getElementById("balanceBtn").style.display = "none";
|
||
return;
|
||
}
|
||
fetch("/api/users/" + encodeURIComponent(user.username))
|
||
.then((res) => res.json())
|
||
.then((data) => {
|
||
if (data.ok) {
|
||
document.getElementById("balanceAmount").textContent =
|
||
data.user.balance + " $";
|
||
document.getElementById("balanceBtn").style.display = "block";
|
||
} else {
|
||
document.getElementById("balanceAmount").textContent = 0;
|
||
document.getElementById("balanceBtn").style.display = "none";
|
||
}
|
||
});
|
||
}
|
||
|
||
document.getElementById("balanceBtn").addEventListener("click", () => {
|
||
openTransferModal();
|
||
});
|
||
document.getElementById("transferBtn").addEventListener("click", () => {
|
||
submitTransferForm();
|
||
});
|
||
|
||
// Helper to fetch user info (fresh)
|
||
async function fetchUserInfo(username) {
|
||
const res = await fetch("/api/users/" + encodeURIComponent(username));
|
||
const data = await res.json();
|
||
return data.ok ? data.user : null;
|
||
}
|
||
|
||
function showMsg(msg, error = false) {
|
||
const msgEl = document.getElementById("msg");
|
||
msgEl.textContent = msg;
|
||
msgEl.className = error ? "error" : "";
|
||
// after 2 seconds clear message
|
||
setTimeout(() => {
|
||
msgEl.textContent = "";
|
||
msgEl.className = "";
|
||
}, 2000);
|
||
}
|
||
|
||
// --- Login logic ---
|
||
function openLoginModal() {
|
||
document.getElementById("loginModal").style.display = "flex";
|
||
document.getElementById("loginMsg").textContent = "";
|
||
}
|
||
function closeLoginModal() {
|
||
document.getElementById("loginModal").style.display = "none";
|
||
}
|
||
function updateLoginUI() {
|
||
const user = getLoggedInUser();
|
||
document.getElementById("loginBtn").style.display = user ? "none" : "";
|
||
document.getElementById("logoutBtn").style.display = user ? "" : "none";
|
||
document.getElementById("accountBtn").style.display = user ? "" : "none";
|
||
updateBalance();
|
||
}
|
||
|
||
function cartPosition() {
|
||
const user = getLoggedInUser();
|
||
if (user) {
|
||
document.querySelector(".cart-btn").style.right = "315px";
|
||
} else {
|
||
document.querySelector(".cart-btn").style.right = "120px";
|
||
}
|
||
}
|
||
|
||
function getLoggedInUser() {
|
||
try {
|
||
return JSON.parse(localStorage.getItem("user"));
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
function setLoggedInUser(user) {
|
||
localStorage.setItem("user", JSON.stringify(user));
|
||
updateLoginUI();
|
||
cartPosition();
|
||
updateBalance();
|
||
}
|
||
function logout() {
|
||
localStorage.removeItem("user");
|
||
updateLoginUI();
|
||
cartPosition();
|
||
updateBalance();
|
||
showMsg("Logged out.");
|
||
}
|
||
|
||
// --- Account Modal logic ---
|
||
document.getElementById("accountBtn").onclick = openAccountModal;
|
||
function openAccountModal() {
|
||
showAccountTab("Password");
|
||
loadAddressesTab();
|
||
loadComputersTab();
|
||
document.getElementById("accountModal").style.display = "flex";
|
||
}
|
||
function closeAccountModal() {
|
||
document.getElementById("accountModal").style.display = "none";
|
||
}
|
||
function showAccountTab(tab) {
|
||
["Password", "Addresses", "Computers"].forEach((t) => {
|
||
document.getElementById("accountTab" + t).style.display =
|
||
t === tab ? "" : "none";
|
||
document.getElementById("tab" + t).style.background =
|
||
t === tab ? "#23272a" : "";
|
||
});
|
||
}
|
||
document.getElementById("tabPassword").onclick = () =>
|
||
showAccountTab("Password");
|
||
document.getElementById("tabAddresses").onclick = () => {
|
||
showAccountTab("Addresses");
|
||
loadAddressesTab();
|
||
};
|
||
document.getElementById("tabComputers").onclick = () => {
|
||
showAccountTab("Computers");
|
||
loadComputersTab();
|
||
};
|
||
|
||
// --- Change Password ---
|
||
document.getElementById("changePasswordForm").onsubmit = async function (e) {
|
||
e.preventDefault();
|
||
const user = getLoggedInUser();
|
||
const msgEl = document.getElementById("changePasswordMsg");
|
||
msgEl.textContent = "";
|
||
if (!user) {
|
||
msgEl.textContent = "Not logged in.";
|
||
return;
|
||
}
|
||
const current = document.getElementById("currentPassword").value;
|
||
const newpw = document.getElementById("newPassword").value;
|
||
const confirm = document.getElementById("confirmNewPassword").value;
|
||
if (!current || !newpw || !confirm) {
|
||
msgEl.textContent = "Fill all fields.";
|
||
return;
|
||
}
|
||
if (newpw !== confirm) {
|
||
msgEl.textContent = "Passwords do not match.";
|
||
return;
|
||
}
|
||
const userInfo = await fetchUserInfo(user.username);
|
||
if (!userInfo) {
|
||
msgEl.textContent = "User not found.";
|
||
return;
|
||
}
|
||
const currentHash = await sha256(current);
|
||
if (userInfo.password !== currentHash) {
|
||
msgEl.textContent = "Current password incorrect.";
|
||
return;
|
||
}
|
||
const newHash = await sha256(newpw);
|
||
const res = await fetch("/api/users/" + encodeURIComponent(user.username), {
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ password: newHash }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
msgEl.style.color = "#23a559";
|
||
msgEl.textContent = "Password changed!";
|
||
} else {
|
||
msgEl.style.color = "#ff5555";
|
||
msgEl.textContent = "Error: " + (data.error || "Unknown error");
|
||
}
|
||
};
|
||
|
||
// --- Addresses Tab ---
|
||
async function loadAddressesTab() {
|
||
const user = getLoggedInUser();
|
||
const listEl = document.getElementById("addressesList");
|
||
const msgEl = document.getElementById("addressesMsg");
|
||
listEl.innerHTML = "";
|
||
msgEl.textContent = "";
|
||
if (!user) {
|
||
listEl.innerHTML = "Not logged in.";
|
||
return;
|
||
}
|
||
const userInfo = await fetchUserInfo(user.username);
|
||
if (!userInfo || !Array.isArray(userInfo.adresses)) {
|
||
listEl.innerHTML = "No addresses found.";
|
||
return;
|
||
}
|
||
if (!userInfo.adresses.length) {
|
||
listEl.innerHTML = "<div>No addresses.</div>";
|
||
} else {
|
||
userInfo.adresses.forEach((addr, idx) => {
|
||
const div = document.createElement("div");
|
||
div.style.display = "flex";
|
||
div.style.alignItems = "center";
|
||
div.style.marginBottom = "6px";
|
||
div.innerHTML = `<span style="flex:1;">${addr}</span>
|
||
<button style="background:#ff5555;padding:2px 8px;border-radius:4px;border:none;color:#fff;cursor:pointer;" onclick="removeAddress(${idx})">Remove</button>`;
|
||
listEl.appendChild(div);
|
||
});
|
||
}
|
||
}
|
||
window.removeAddress = async function (idx) {
|
||
const user = getLoggedInUser();
|
||
const msgEl = document.getElementById("addressesMsg");
|
||
msgEl.textContent = "";
|
||
if (!user) return;
|
||
const userInfo = await fetchUserInfo(user.username);
|
||
if (!userInfo || !Array.isArray(userInfo.adresses)) return;
|
||
userInfo.adresses.splice(idx, 1);
|
||
const res = await fetch("/api/users/" + encodeURIComponent(user.username), {
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({ adresses: userInfo.adresses }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
msgEl.style.color = "#23a559";
|
||
msgEl.textContent = "Address removed.";
|
||
loadAddressesTab();
|
||
} else {
|
||
msgEl.style.color = "#ff5555";
|
||
msgEl.textContent = "Error: " + (data.error || "Unknown error");
|
||
}
|
||
};
|
||
document.getElementById("addAddressForm").onsubmit = async function (e) {
|
||
e.preventDefault();
|
||
const user = getLoggedInUser();
|
||
const msgEl = document.getElementById("addressesMsg");
|
||
msgEl.textContent = "";
|
||
const newAddr = document.getElementById("newAddress").value.trim();
|
||
if (!user || !newAddr) return;
|
||
const userInfo = await fetchUserInfo(user.username);
|
||
if (!userInfo || !Array.isArray(userInfo.adresses)) return;
|
||
userInfo.adresses.push(newAddr);
|
||
const res = await fetch("/api/users/" + encodeURIComponent(user.username), {
|
||
method: "PUT",
|
||
headers: { "Content-Type": "application/json" },
|
||
body: JSON.stringify({
|
||
adresses: userInfo.adresses,
|
||
}),
|
||
});
|
||
// update local storage
|
||
localStorage.setItem("user", JSON.stringify(userInfo));
|
||
|
||
const data = await res.json();
|
||
if (data.ok) {
|
||
msgEl.style.color = "#23a559";
|
||
msgEl.textContent = "Address added.";
|
||
document.getElementById("newAddress").value = "";
|
||
loadAddressesTab();
|
||
} else {
|
||
msgEl.style.color = "#ff5555";
|
||
msgEl.textContent = "Error: " + (data.error || "Unknown error");
|
||
}
|
||
};
|
||
|
||
// --- Linked Computers Tab ---
|
||
async function loadComputersTab() {
|
||
const user = getLoggedInUser();
|
||
const listEl = document.getElementById("computersList");
|
||
const msgEl = document.getElementById("computersMsg");
|
||
listEl.innerHTML = "";
|
||
msgEl.textContent = "";
|
||
if (!user) {
|
||
listEl.innerHTML = "Not logged in.";
|
||
return;
|
||
}
|
||
const res = await fetch(
|
||
"/api/users/" + encodeURIComponent(user.username) + "/computers",
|
||
);
|
||
const data = await res.json();
|
||
if (!data.ok || !Array.isArray(data.computers)) {
|
||
listEl.innerHTML = "No linked computers found.";
|
||
return;
|
||
}
|
||
if (!data.computers.length) {
|
||
listEl.innerHTML = "<div>No linked computers.</div>";
|
||
} else {
|
||
data.computers.forEach((comp) => {
|
||
const div = document.createElement("div");
|
||
div.style.display = "flex";
|
||
div.style.alignItems = "center";
|
||
div.style.marginBottom = "6px";
|
||
div.innerHTML = `<span style="flex:1;">Computer #${comp.id}</span>
|
||
<button style="background:#ff5555;padding:2px 8px;border-radius:4px;border:none;color:#fff;cursor:pointer;" onclick="removeComputer(${comp.id})">Remove</button>`;
|
||
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 = "<div>Your cart is empty.</div>";
|
||
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 = `
|
||
<img src="${getImage(item.name)}" alt="${item.name}"
|
||
style="width:50px;height:50px;margin-right:8px;image-rendering: pixelated;">
|
||
<span class="item-name">${formatItemId(item.name)} × ${item.qty}</span>
|
||
<span class="item-price">${price.toFixed(2)} $</span>
|
||
<button style="background:#ff5555;padding:2px 8px;border-radius:4px;border:none;color:#fff;cursor:pointer;"
|
||
onclick="removeFromCart(${item.id});renderCart();updateCartUI();">
|
||
Remove
|
||
</button>
|
||
`;
|
||
|
||
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 = `<input type="radio" name="address" value="${addr}" id="${id}" ${idx === 0 ? "checked" : ""}/> ${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 = `<input type="radio" name="address" value="${addr}" id="${id}"/> ${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,
|
||
}));
|
||
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 = "<tr><td colspan='6'>Loading items...</td></tr>";
|
||
|
||
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 =
|
||
"<tr><td colspan='6'>No items available to buy.</td></tr>";
|
||
return;
|
||
}
|
||
|
||
tableBody.innerHTML = ""; // Clear loader
|
||
// sort items alphabetically
|
||
const sortedItems = [...data.items].sort((a, b) =>
|
||
a.name.localeCompare(b.name),
|
||
);
|
||
|
||
const rows = await Promise.all(
|
||
sortedItems.map(async (item) => {
|
||
const item_stock = (await getItemStock(item.name)) || 0;
|
||
const row = document.createElement("tr");
|
||
row.innerHTML = `
|
||
<td><img src="${getImage(item.name)}" alt="${item.name}" style="width:50px;height:50px;image-rendering: pixelated;"></td>
|
||
<td class="item-name">${formatItemId(item.name)}</td>
|
||
<td class="item-quantity">${item.quantity}</td>
|
||
<td class="item-price">${item.price.toFixed(2)} $</td>
|
||
<td class="item-stock">${convertStockToStackPlusRest(item_stock, item.quantity).stacks ? `${convertStockToStackPlusRest(item_stock, item.quantity).stacks} stacks </br> ` : ""}${convertStockToStackPlusRest(item_stock, item.quantity).rest ? `${convertStockToStackPlusRest(item_stock, item.quantity).rest} in stock` : ""} ${!convertStockToStackPlusRest(item_stock, item.quantity).stacks && !convertStockToStackPlusRest(item_stock, item.quantity).rest ? "Out of stock" : ""}
|
||
<td><input type="number" min="1" max="${Math.floor(item_stock / item.quantity)}" id="quantity-input-${item.id}" style="width:40px;" value="1"></td>
|
||
<td><button class="buy-btn" data-id="${item.id}" data-name="${item.name}" data-price="${item.price}" data-quantity="${item.quantity}">Get</button></td>
|
||
`;
|
||
return row;
|
||
}),
|
||
);
|
||
|
||
tableBody.innerHTML = "";
|
||
rows.forEach((row) => tableBody.appendChild(row));
|
||
|
||
function getItemSellQuantity(item) {
|
||
const quantity = parseInt(
|
||
document.getElementById(`quantity-input-${item.id}`).value,
|
||
10,
|
||
);
|
||
return quantity;
|
||
}
|
||
|
||
// Add event listeners for buy buttons
|
||
Array.from(tableBody.getElementsByClassName("buy-btn")).forEach((btn) => {
|
||
btn.onclick = function () {
|
||
const item = {
|
||
id: parseInt(this.dataset.id, 10),
|
||
name: this.dataset.name,
|
||
price: parseFloat(this.dataset.price),
|
||
quantity: parseInt(this.dataset.quantity, 10),
|
||
};
|
||
addToCart(item, getItemSellQuantity(item));
|
||
updateCartUI();
|
||
};
|
||
});
|
||
} catch (err) {
|
||
tableBody.innerHTML =
|
||
"<tr><td colspan='6' style='color:#ff5555;'>Error loading items.</td></tr>";
|
||
}
|
||
}
|
||
|
||
// --- Initialization ---
|
||
updateLoginUI();
|
||
updateCartUI();
|
||
fetchItems();
|
||
updateBalance();
|
||
cartPosition();
|
||
|
||
// Close modals on outside click
|
||
window.onclick = function (event) {
|
||
["loginModal", "cartWindow", "addressModal"].forEach((id) => {
|
||
const el = document.getElementById(id);
|
||
if (event.target === el) el.style.display = "none";
|
||
});
|
||
};
|