From 7596b119668ff2474b47629dab6e9c5f1d19cf48 Mon Sep 17 00:00:00 2001
From: mfwadejr <125498560+mfwadejr@users.noreply.github.com>
Date: Sat, 29 Aug 2026 21:07:18 -0400
Subject: [PATCH] Rebrand interface as vBoxStock
---
public/app.js | 7 ++++---
public/index.html | 4 ++--
public/site.webmanifest | 12 ++++++++++++
public/theme.css | 1 +
4 files changed, 19 insertions(+), 5 deletions(-)
create mode 100644 public/site.webmanifest
diff --git a/public/app.js b/public/app.js
index 1f4f6af..46ae44b 100644
--- a/public/app.js
+++ b/public/app.js
@@ -96,6 +96,7 @@ async function load(){ state.products=await api("/api/products"); render(); }
async function renderAdmin(){
const panel=$("#adminPanel"); panel.innerHTML='
Loading backups…
';
try{const [backups,users,audit]=await Promise.all([api("/api/admin/backups"),api("/api/admin/users"),api("/api/admin/audit")]);panel.innerHTML=`User accounts
Create administrators or read-only accounts. New and reset passwords must be changed at next login.
${users.map(u=>`
${esc(u.username)}${u.role==="admin"?"Admin":"Read-Only"} · ${u.enabled?"Enabled":"Disabled"}${u.mustChangePassword?" · Password change required":""}${u.lastLoginAt?` · Last login ${fmtDateTime(u.lastLoginAt)}`:""}
`).join("")}
Database backups
Create snapshots inside /data/backups, download an off-server copy, or restore a previous database.
Restore replaces the active database and all user accounts. Your current password is required. Everyone will be signed out afterward.
${backups.length?backups.map(b=>`
${esc(b.name)}${fmtDateTime(b.createdAt)} · ${(b.size/1024).toFixed(1)} KB
`).join(""):"
No local backups yet.
"}
Audit log
The latest 250 security and data-changing events. Audit entries cannot be edited or deleted.
${audit.map(a=>`
${esc(a.username)}${esc(a.action.replaceAll("_"," "))}${fmtDateTime(a.createdAt)}${a.target?` · ${esc(a.target)}`:""}${a.details?` · ${esc(a.details)}`:""}${a.ipAddress?` · ${esc(a.ipAddress)}`:""}`).join("")||"
No audit events yet.
"}
`;
+ panel.querySelector(".admin-page").insertAdjacentHTML("afterbegin",'
vBoxStockAdministration
Manage access, protect your data, and review account activity.
');
$("#createUserForm").onsubmit=async e=>{e.preventDefault();try{await api("/api/admin/users",{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});toast("User created.");renderAdmin();}catch(error){toast(error.message);}};
document.querySelectorAll("[data-role-user]").forEach(b=>b.onclick=async()=>{try{await api(`/api/admin/users/${b.dataset.roleUser}`,{method:"PATCH",body:JSON.stringify({role:b.dataset.role==="admin"?"readonly":"admin"})});toast("User role updated.");renderAdmin();}catch(e){toast(e.message);}});
document.querySelectorAll("[data-toggle-user]").forEach(b=>b.onclick=async()=>{try{await api(`/api/admin/users/${b.dataset.toggleUser}`,{method:"PATCH",body:JSON.stringify({enabled:b.dataset.enabled!=="true"})});toast("User status updated.");renderAdmin();}catch(e){toast(e.message);}});
@@ -109,16 +110,16 @@ async function renderAdmin(){
}
async function restoreUpload(file,password){const panel=$("#adminPanel");try{storageStatus("saving","Restoring database");const response=await fetch("/api/admin/restore-upload",{method:"POST",headers:{"content-type":"application/octet-stream","x-confirm-password":password},body:file});const data=await response.json();if(!response.ok)throw new Error(data.error||"Restore failed");panel.innerHTML='Restore complete
The container is restarting. Everyone has been signed out.
';}catch(e){storageStatus("critical","Database error");toast(e.message);}}
function showLogin(message=""){
- state.user=null;document.body.className="auth-required";$("#authBody").innerHTML=`Sign in
Use your Stockroom account to continue.
${message?`${esc(message)}
`:""}`;
+ state.user=null;document.body.className="auth-required";$("#authBody").innerHTML=`Sign in
Use your vBoxStock account to continue.
${message?`${esc(message)}
`:""}`;
$("#loginForm").onsubmit=async e=>{e.preventDefault();const button=e.currentTarget.querySelector("button");button.disabled=true;try{const user=await api("/api/auth/login",{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});user.mustChangePassword?showPasswordChange(user):await enterApp(user);}catch(error){showLogin(error.message);}};
}
function showPasswordChange(user){
- state.user=user;document.body.className="auth-required";$("#authBody").innerHTML=`Change your password
Your temporary password must be replaced before you can use Stockroom.
`;
+ state.user=user;document.body.className="auth-required";$("#authBody").innerHTML=`Change your password
Your temporary password must be replaced before you can use vBoxStock.
`;
$("#passwordForm").onsubmit=async e=>{e.preventDefault();try{await api("/api/auth/change-password",{method:"POST",body:JSON.stringify(Object.fromEntries(new FormData(e.currentTarget)))});await enterApp({...user,mustChangePassword:false});}catch(error){toast(error.message);}};$("#changeLogout").onclick=logout;
}
async function enterApp(user){state.user=user;document.body.className=user.role==="admin"?"role-admin":"role-readonly";const account=$("#currentUser"),menu=$(".user-menu");account.innerHTML=`Logged in as${esc(user.username)}${user.role==="admin"?"Admin":"Read-Only"}`;account.style.width="104px";account.style.textAlign="right";menu.style.marginLeft="auto";menu.style.minWidth="280px";menu.style.justifyContent="flex-end";const adminTab=document.querySelector('[data-tab="admin"]');adminTab.hidden=user.role!=="admin";adminTab.style.display=user.role==="admin"?"":"none";document.querySelectorAll(".edit-only").forEach(el=>{el.hidden=user.role!=="admin";el.style.display=user.role==="admin"?"":"none";});if(user.role!=="admin"&&state.tab==="admin")state.tab="available";await load();}
async function logout(){try{await api("/api/auth/logout",{method:"POST",body:"{}"});}catch{}showLogin();}
-async function initialize(){try{const response=await fetch("/api/auth/me"),data=await response.json();if(!response.ok)return showLogin();data.mustChangePassword?showPasswordChange(data):await enterApp(data);}catch{showLogin("Unable to connect to Stockroom.");}}
+async function initialize(){try{const response=await fetch("/api/auth/me"),data=await response.json();if(!response.ok)return showLogin();data.mustChangePassword?showPasswordChange(data):await enterApp(data);}catch{showLogin("Unable to connect to vBoxStock.");}}
document.querySelectorAll("[data-tab]").forEach(b=>b.onclick=()=>{state.tab=b.dataset.tab;state.page=1;render();});
$("#search").oninput=e=>{state.query=e.target.value;state.page=1;render();};
$("[data-open=receive]").onclick=receiveForm; $("[data-open=sell]").onclick=()=>sellForm();
diff --git a/public/index.html b/public/index.html
index 882173b..7d60926 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,5 +1,5 @@
-vSeeBox Stockroom
-
+
vBoxStock
+
vBoxStock
Inventory Overview
Available products0Ready to sellSold this month0No sales recordedInventory value$0Based on purchase cost
diff --git a/public/site.webmanifest b/public/site.webmanifest
new file mode 100644
index 0000000..3c1e9da
--- /dev/null
+++ b/public/site.webmanifest
@@ -0,0 +1,12 @@
+{
+ "name": "vBoxStock",
+ "short_name": "vBoxStock",
+ "description": "Inventory, sales, and customer tracking for independent vSeeBox resellers.",
+ "start_url": "/",
+ "display": "standalone",
+ "background_color": "#f5f7fb",
+ "theme_color": "#2563eb",
+ "icons": [
+ { "src": "/assets/vboxstock-icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" }
+ ]
+}
diff --git a/public/theme.css b/public/theme.css
index bc8dad8..7ca7041 100644
--- a/public/theme.css
+++ b/public/theme.css
@@ -31,6 +31,7 @@ dialog{max-height:min(90dvh,900px);overflow:auto}dialog::backdrop{background:#05
#currentUser{background:var(--surface-soft);border:1px solid var(--border-strong);border-radius:9px;padding:6px 10px;line-height:1.15}.theme-control{display:grid;grid-template-columns:auto 1fr;align-items:center;gap:7px;margin:0 0 0 4px;padding-left:12px;border-left:1px solid var(--border-strong);color:var(--muted);font-size:11px}.user-menu select{width:92px;border:1px solid var(--border-strong);border-radius:9px;padding:9px 26px 9px 9px;font-size:12px;font-weight:650}
#pagination button{background:var(--surface-soft);border-color:var(--border-strong);color:var(--text)}#pagination button:not(:disabled):hover{background:var(--blue-soft);color:var(--blue-text)}#pagination button:disabled{background:var(--surface-soft);color:var(--text);opacity:1;cursor:not-allowed}
button,.button,input,select,textarea{-webkit-tap-highlight-color:transparent}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:3px solid color-mix(in srgb,var(--blue) 45%,transparent);outline-offset:2px}
+.brand{font-weight:750;white-space:nowrap}.brand img{width:42px;height:42px;object-fit:contain;filter:drop-shadow(0 2px 4px var(--shadow))}.auth-brand img{width:62px;height:62px}.admin-intro{display:flex;align-items:center;gap:14px;padding:4px 0 2px}.admin-intro img{width:54px;height:54px;object-fit:contain}.admin-intro h2{margin:0}.admin-intro p{margin:4px 0 0}.admin-kicker{display:block;color:var(--blue-text);font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{scroll-behavior:auto!important;animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}
@media (pointer:coarse){button,.button,.upload-backup,.user-menu select{min-height:44px}}
@media (max-width:1050px){