diff --git a/db.js b/db.js
index 02a6f023..c6db502e 100644
--- a/db.js
+++ b/db.js
@@ -1,10 +1,11 @@
const mariadb = require("mariadb");
+const env = require("dotenv").config().parsed;
const pool = mariadb.createPool({
- host: "10.0.0.1", // "mariadb" if in Docker
- user: "minecraft_shop_user",
- password: "UsersBeliev3sNewsp4p3r",
- database: "minecraft-shop",
+ host: env.DB_HOST, // "mariadb" if in Docker
+ user: env.DB_USER,
+ password: env.DB_PASSWORD,
+ database: env.DB_DATABASE,
connectionLimit: 5,
});
diff --git a/package-lock.json b/package-lock.json
index 144501b2..15a1cf75 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"axios": "^1.13.2",
+ "dotenv": "^17.2.3",
"express": "^5.2.1",
"mariadb": "^3.4.5",
"ws": "^8.18.3"
@@ -218,6 +219,18 @@
"node": ">= 0.8"
}
},
+ "node_modules/dotenv": {
+ "version": "17.2.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
+ "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
diff --git a/package.json b/package.json
index 8edf8898..615412a1 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"type": "commonjs",
"dependencies": {
"axios": "^1.13.2",
+ "dotenv": "^17.2.3",
"express": "^5.2.1",
"mariadb": "^3.4.5",
"ws": "^8.18.3"
diff --git a/public/admin.html b/public/admin.html
deleted file mode 100644
index 5dba8217..00000000
--- a/public/admin.html
+++ /dev/null
@@ -1,485 +0,0 @@
-
-
-
-
- Admin - Shop Item Management
-
-
-
-
- Admin - Shop Item Management
-
- Access denied. Admins only.
-
-
-
-
-
-
- | Name |
- Price |
- Enabled |
- Actions |
-
-
-
-
-
-
-
-
-
diff --git a/public/css/index.css b/public/css/index.css
deleted file mode 100644
index 8efd0143..00000000
--- a/public/css/index.css
+++ /dev/null
@@ -1,338 +0,0 @@
-body {
- background: linear-gradient(145deg, #0f0f0f 0%, #1a1a1a 100%);
- color: #fff;
- font-family: "Segoe UI", sans-serif;
- margin: 24px;
- transition: background 0.5s;
-}
-h1 {
- color: #5865f2;
- text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
-}
-#items-list {
- margin-top: 24px;
- background: rgba(255, 255, 255, 0.03);
- border-radius: 12px;
- padding: 16px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
- width: 50%;
- backdrop-filter: blur(8px);
- display: table;
- table-layout: auto;
-}
-.item-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 12px 0;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
-}
-.item-name {
- font-size: 1em;
- font-weight: 600;
- color: #e0e0e0;
- transition: color 0.3s;
-}
-.item-name:hover {
- color: #fff;
-}
-.item-price {
- color: #23a559;
- font-size: 1.1em;
- margin-left: 16px;
- margin-right: 16px;
- transition: color 0.3s;
-}
-.item-price:hover {
- color: #178a43;
-}
-.buy-btn,
-.buy64-btn,
-.buy32-btn {
- background: #5865f2;
- color: #fff;
- border: none;
- border-radius: 6px;
- padding: 8px 20px;
- cursor: pointer;
- font-weight: bold;
- transition: all 0.3s ease;
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
-}
-.buy-btn:hover,
-.buy64-btn:hover,
-.buy32-btn:hover {
- background: #4752c4;
- transform: translateY(-2px);
- box-shadow: 0 6px 16px rgba(88, 101, 242, 0.4);
-}
-#msg {
- margin-top: 18px;
- font-size: 15px;
- color: #23a559;
- transition: color 0.3s;
-}
-#msg.error {
- color: #ff5555;
-}
-.modal,
-.cart-window,
-.address-modal,
-.transfer-modal {
- display: none;
- position: fixed;
- z-index: 1000;
- left: 0;
- top: 0;
- width: 100vw;
- height: 100vh;
- background: rgba(0, 0, 0, 0.7);
- align-items: center;
- justify-content: center;
- backdrop-filter: blur(8px);
-}
-.modal-content,
-.cart-content,
-.address-content,
-.transfer-content {
- background: rgba(255, 255, 255, 0.05);
- color: #fff;
- padding: 40px 32px 32px 32px;
- border-radius: 16px;
- max-width: 500px;
- box-shadow: 0 16px 32px rgba(0, 0, 0, 0.3);
- backdrop-filter: blur(12px);
- border: 1px solid rgba(255, 255, 255, 0.05);
-}
-.modal-content h2,
-.cart-content h2,
-.address-content h2 {
- margin-top: 0;
- color: #5865f2;
- font-size: 1.5em;
- text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
-}
-.close-btn {
- background: #ff5555;
- color: #fff;
- border: none;
- border-radius: 8px;
- padding: 8px 16px;
- cursor: pointer;
- float: right;
- font-weight: bold;
- transition: all 0.3s ease;
- box-shadow: 0 4px 12px rgba(255, 85, 85, 0.3);
-}
-.close-btn:hover {
- background: #c0392b;
- transform: scale(1.1);
-}
-.cart-btn {
- position: fixed;
- top: 24px;
- right: 115px;
- background: #5865f2;
- color: #fff;
- border: none;
- border-radius: 50%;
- width: 45px;
- height: 45px;
- padding-top: 10px;
- padding-left: 10px;
- font-size: 1.6em;
- cursor: pointer;
- z-index: 900;
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
- transition: all 0.3s;
-}
-.cart-btn:hover {
- background: #4752c4;
- transform: scale(1.05);
-}
-.cart-count {
- position: absolute;
- bottom: 25px;
- left: 25px;
- background: #23a559;
- color: #fff;
- border-radius: 50%;
- width: 24px;
- height: 24px;
- font-size: 18px;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: 0 2px 6px rgba(35, 165, 89, 0.3);
- transition: all 0.3s;
-}
-#cartItems {
- margin: 24px 0;
-}
-#cartItems div {
- padding: 12px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.05);
- display: flex;
- justify-content: space-between;
- align-items: center;
- font-size: 1em;
- transition: background 0.3s;
-}
-#cartItems div:hover {
- background: rgba(255, 255, 255, 0.03);
-}
-.item-name {
- font-weight: bold;
- margin-right: 8px;
-}
-.item-price {
- font-weight: bold;
-}
-#cartTotal {
- margin: 24px 0;
- font-size: 1.2em;
- font-weight: bold;
- color: #23a559;
-}
-#checkoutBtn {
- background: #23a559;
- color: #fff;
- border: none;
- border-radius: 8px;
- padding: 14px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.3s ease;
- box-shadow: 0 6px 16px rgba(35, 165, 89, 0.3);
-}
-#checkoutBtn:hover {
- background: #178a43;
- transform: translateY(-2px);
- box-shadow: 0 8px 20px rgba(35, 165, 89, 0.4);
-}
-
-.login-btn {
- position: fixed;
- top: 24px;
- right: 24px;
- background: #5865f2;
- color: #fff;
- border: none;
- border-radius: 28px;
- padding: 10px 24px;
- font-size: 1em;
- cursor: pointer;
- z-index: 900;
- font-weight: bold;
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
- transition: all 0.3s;
-}
-.login-btn:hover {
- background: #4752c4;
- transform: translateY(-2px);
-}
-.logout-btn {
- position: fixed;
- top: 24px;
- right: 24px;
- background: #ff5555;
- color: #fff;
- border: none;
- border-radius: 28px;
- padding: 10px 24px;
- font-size: 1em;
- cursor: pointer;
- z-index: 900;
- font-weight: bold;
- box-shadow: 0 4px 12px rgba(255, 85, 85, 0.3);
- transition: all 0.3s;
-}
-.logout-btn:hover {
- background: #c0392b;
- transform: translateY(-2px);
-}
-.address-list {
- margin: 16px 0;
-}
-.address-list label {
- display: block;
- margin-bottom: 8px;
- color: #ccc;
-}
-.balance-Btn {
- position: fixed;
- top: 24px;
- right: 365px;
- background: #5865f2;
- color: #fff;
- border: none;
- border-radius: 28px;
- padding: 12px 24px;
- font-size: 1em;
- cursor: pointer;
- z-index: 900;
- font-weight: bold;
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
- transition: all 0.3s;
-}
-
-/* Additional modern styles */
-input[type="text"],
-input[type="password"],
-input[type="number"] {
- background: rgba(255, 255, 255, 0.05);
- border: none;
- border-radius: 6px;
- padding: 10px 12px;
- color: #fff;
- width: 100%;
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
- transition: box-shadow 0.3s;
-}
-input[type="text"]:focus,
-input[type="password"]:focus,
-input[type="number"]:focus {
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.4);
-}
-form {
- display: flex;
- flex-direction: column;
- gap: 12px;
-}
-button[type="submit"],
-#transferBtn {
- background: #5865f2;
- color: #fff;
- border: none;
- border-radius: 6px;
- padding: 10px;
- font-weight: bold;
- cursor: pointer;
- transition: all 0.3s;
- box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3);
-}
-button[type="submit"]:hover,
-#transferBtn:hover {
- background: #4752c4;
- transform: translateY(-2px);
-}
-.account-tab.active {
- display: block;
-}
-#accountTabs button {
- background: #1a1a1a;
- border: 1px solid #333;
- border-radius: 6px;
- padding: 8px 12px;
- cursor: pointer;
- transition: all 0.3s;
- color: #fff;
-}
-#accountTabs button:hover {
- background: #2a2a2a;
-}
-#accountTabs button.active {
- background: #5865f2;
- color: #fff;
- border-color: #5865f2;
-}
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index 73774c77..00000000
--- a/public/index.html
+++ /dev/null
@@ -1,266 +0,0 @@
-
-
-
-
- Shop - Buy Items
-
-
-
-
-
-
-
-
-
-
- 0
-
- Shop - Items to Buy
-
-
-
-
- |
- Item Name |
- Amount |
- Price |
- Stock |
- |
- |
-
-
-
-
- | Loading items... |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Your Cart
-
-
-
-
-
-
-
-
-
-
-
Select Address
-
-
-
-
-
-
-
-
-
-
Manage Account
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/public/items.html b/public/items.html
deleted file mode 100644
index 65295b23..00000000
--- a/public/items.html
+++ /dev/null
@@ -1,585 +0,0 @@
-
-
-
-
- Latest Items
-
-
-
-
- Latest Items
-
- Shows the most recently received item lists from connected clients.
- Data comes from /api/items.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- | Source |
- Item |
- Count |
- Last updated |
-
-
-
-
- | Loading… |
-
-
-
-
-
-
- Live view of item lists reported by connected clients.
-
-
-
-
-
diff --git a/public/js/index.js b/public/js/index.js
deleted file mode 100644
index efdee52a..00000000
--- a/public/js/index.js
+++ /dev/null
@@ -1,800 +0,0 @@
-// --- 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 = "No addresses.
";
- } 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 = `${addr}
- `;
- 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 = "No linked computers.
";
- } 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: item.quantity,
- }));
- 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 =
- "| No items available to buy. |
";
- 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 = `
- }) |
- ${formatItemId(item.name)} |
- ${item.quantity} |
- ${item.price.toFixed(2)} $ |
- ${convertStockToStackPlusRest(item_stock, item.quantity).stacks ? `${convertStockToStackPlusRest(item_stock, item.quantity).stacks} stacks ` : ""}${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" : ""}
- | |
- |
- `;
- 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 =
- "| Error loading items. |
";
- }
-}
-
-// --- 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";
- });
-};
diff --git a/public/msg.html b/public/msg.html
deleted file mode 100644
index e4451839..00000000
--- a/public/msg.html
+++ /dev/null
@@ -1,108 +0,0 @@
-
-
-
-
- WebSocket Test UI
-
-
-
- WebSocket Chat
- Not connected
-
-
-
-
-
-
-
diff --git a/public/register.html b/public/register.html
deleted file mode 100644
index b3d4724d..00000000
--- a/public/register.html
+++ /dev/null
@@ -1,187 +0,0 @@
-
-
-
-
- Register New Account
-
-
-
-
- Register New Account
-
-
-
-
-