JustPaste
HomeCategoriesAboutDonateContactTerms of UsePrivacy Policy
JustPaste

Free online notepad — write and share instantly

Navigate

  • Home
  • Timeline
  • Categories

Info

  • About
  • Donate
  • Contact

Legal

  • Terms of Use
  • Privacy Policy

© 2026 JustPaste.app. All rights reserved.

Made with ♥ by JustPaste

Untitled Page | JustPaste.app
11 days ago9 views
👨‍💻Programming
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>TrainGo — Admin Portal</title>
  <link rel="stylesheet" href="home.css" />
  <link rel="stylesheet" href="admin.css" />
  <link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet" />
</head>
<body>

  <div class="bg-gradient"></div>
  <div class="bg-grid"></div>

  <!-- ===== TOP BAR ===== -->
  <header class="topbar">
    <div class="topbar__brand">
      <span class="brand__icon">🚆</span>
      <span class="brand__name">TrainGo</span>
      <span class="admin-badge">ADMIN</span>
    </div>
    <div class="topbar__user">
      <span class="user-chip">🛡️ <span id="current-user">admin</span></span>
      <button class="btn-logout" id="logout-btn">Logout</button>
    </div>
  </header>

  <!-- ===== TAB NAV ===== -->
  <nav class="tabs">
    <button class="tab active" data-panel="panel-home">📊 Home</button>
    <button class="tab" data-panel="panel-train">🚂 Register a Train</button>
    <button class="tab" data-panel="panel-profile">👤 Profile</button>
  </nav>

  <main class="content">

    <!-- ========== HOME ========== -->
    <section id="panel-home" class="panel active">
      <h1 class="panel__title">Admin Dashboard</h1>
      <p class="panel__sub">System-wide booking activity and customer records.</p>

      <div class="stat-row">
        <div class="stat-card stat-card--yellow">
          <div class="stat-card__label">Total Customers</div>
          <div class="stat-card__value" id="stat-customers">0</div>
        </div>
        <div class="stat-card stat-card--green">
          <div class="stat-card__label">Active Tickets</div>
          <div class="stat-card__value" id="stat-active">0</div>
        </div>
        <div class="stat-card stat-card--pink">
          <div class="stat-card__label">Registered Trains</div>
          <div class="stat-card__value" id="stat-trains">0</div>
        </div>
      </div>

      <!-- 1.1 Tickets per class -->
      <h2 class="section-head">1.1 — Number of Tickets Booked Per Class</h2>
      <div class="table-wrap">
        <table class="data-table">
          <thead>
            <tr><th>Ticket Category</th><th>Tickets Booked</th><th>Share</th></tr>
          </thead>
          <tbody id="class-agg-body">
            <tr><td colspan="3" class="empty-row">No bookings yet.</td></tr>
          </tbody>
        </table>
      </div>

      <!-- 1.2 Sales per quarter -->
      <h2 class="section-head">1.2 — Sales Done Per Quarter</h2>
      <div class="table-wrap">
        <table class="data-table">
          <thead>
            <tr><th>Quarter</th><th>Bookings</th><th>Tickets</th></tr>
          </thead>
          <tbody id="quarter-agg-body">
            <tr><td colspan="3" class="empty-row">No bookings yet.</td></tr>
          </tbody>
        </table>
      </div>

      <!-- 2. Client table -->
      <h2 class="section-head">2 — Customer Records</h2>
      <div class="table-wrap table-wrap--scroll">
        <table class="data-table">
          <thead>
            <tr>
              <th>2.2.1 ID</th>
              <th>2.2.2 Name</th>
              <th>2.2.3 Mobile</th>
              <th>2.2.4 Tickets Booked</th>
              <th>2.2.5 Action</th>
            </tr>
          </thead>
          <tbody id="customers-body">
            <tr><td colspan="5" class="empty-row">No customers registered yet.</td></tr>
          </tbody>
        </table>
      </div>
    </section>

    <!-- ========== REGISTER A TRAIN ========== -->
    <section id="panel-train" class="panel">
      <h1 class="panel__title">Register a Train</h1>
      <p class="panel__sub">Registered trains become selectable by customers when booking.</p>

      <form id="train-form" novalidate>
        <div class="form-grid">

          <div class="input-group">
            <label for="tr-name">🚂 Name of Train</label>
            <input type="text" id="tr-name" placeholder="e.g. Rajdhani Express" />
            <span class="error-msg" id="tr-name-err"></span>
          </div>

          <div class="input-group">
            <label for="tr-seats">💺 Number of Seats</label>
            <input type="text" id="tr-seats" placeholder="e.g. 480" maxlength="4" inputmode="numeric" />
            <span class="error-msg" id="tr-seats-err"></span>
          </div>

          <div class="input-group">
            <label for="tr-from">🚉 From</label>
            <select id="tr-from"><option value="">Select station</option></select>
            <span class="error-msg" id="tr-from-err"></span>
          </div>

          <div class="input-group">
            <label for="tr-to">🏁 To</label>
            <select id="tr-to"><option value="">Select station</option></select>
            <span class="error-msg" id="tr-to-err"></span>
          </div>

          <div class="input-group">
            <label for="tr-depart">🕐 Departure Time</label>
            <input type="time" id="tr-depart" />
            <span class="error-msg" id="tr-depart-err"></span>
          </div>

          <div class="input-group">
            <label for="tr-duration">⏱️ Duration (hours)</label>
            <input type="text" id="tr-duration" placeholder="e.g. 16.5" maxlength="5" />
            <span class="error-msg" id="tr-duration-err"></span>
          </div>

          <div class="input-group input-group--wide">
            <label for="tr-owner">🏢 Ownership</label>
            <select id="tr-owner">
              <option value="">Select ownership</option>
              <option value="Indian Railways">Indian Railways</option>
              <option value="IRCTC">IRCTC</option>
              <option value="Private Operator">Private Operator</option>
              <option value="State Railway">State Railway</option>
              <option value="Joint Venture">Joint Venture</option>
            </select>
            <span class="error-msg" id="tr-owner-err"></span>
          </div>

        </div>

        <button type="submit" class="btn btn--green">
          <span class="btn__text">Register Train</span>
          <span class="btn__arrow">→</span>
        </button>
      </form>

      <h2 class="section-head">Registered Trains</h2>
      <div class="table-wrap table-wrap--scroll">
        <table class="data-table">
          <thead>
            <tr>
              <th>Train ID</th>
              <th>Name</th>
              <th>Seats</th>
              <th>From</th>
              <th>To</th>
              <th>Departs</th>
              <th>Ownership</th>
              <th>Action</th>
            </tr>
          </thead>
          <tbody id="trains-body">
            <tr><td colspan="8" class="empty-row">No trains registered yet.</td></tr>
          </tbody>
        </table>
      </div>
    </section>

    <!-- ========== PROFILE ========== -->
    <section id="panel-profile" class="panel">
      <h1 class="panel__title">Profile</h1>
      <p class="panel__sub">Update your admin account information.</p>

      <form id="profile-form" novalidate>
        <div class="form-grid">

          <div class="input-group">
            <label for="pf-username">👤 Username</label>
            <input type="text" id="pf-username" placeholder="admin_username" />
            <span class="error-msg" id="pf-username-err"></span>
          </div>

          <div class="input-group">
            <label for="pf-email">✉️ Email Id</label>
            <input type="email" id="pf-email" placeholder="[email protected]" />
            <span class="error-msg" id="pf-email-err"></span>
          </div>

          <div class="input-group">
            <label for="pf-mobile">📱 Mobile Number</label>
            <input type="text" id="pf-mobile" placeholder="10-digit number" maxlength="10" inputmode="numeric" />
            <span class="error-msg" id="pf-mobile-err"></span>
          </div>
        </div>

        <label class="checkbox-row">
          <input type="checkbox" id="pf-toggle-pw" />
          <span>🔐 Update Password</span>
        </label>

        <div class="form-grid pw-section" id="pf-pw-section">
          <div class="input-group">
            <label for="pf-current">Current Password</label>
            <input type="password" id="pf-current" placeholder="••••••••" />
            <span class="error-msg" id="pf-current-err"></span>
          </div>
          <div class="input-group">
            <label for="pf-new">New Password</label>
            <input type="password" id="pf-new" placeholder="••••••••" />
            <span class="error-msg" id="pf-new-err"></span>
          </div>
          <div class="input-group">
            <label for="pf-confirm">Confirm Password</label>
            <input type="password" id="pf-confirm" placeholder="••••••••" />
            <span class="error-msg" id="pf-confirm-err"></span>
          </div>
        </div>

        <button type="submit" class="btn btn--yellow">
          <span class="btn__text">Save Changes</span>
          <span class="btn__arrow">→</span>
        </button>
      </form>

      <h2 class="section-head">Danger Zone</h2>
      <div class="danger-zone">
        <div>
          <strong>Delete this account</strong>
          <p>Permanently removes your admin account. This cannot be undone.</p>
        </div>
        <button class="btn btn--red" id="delete-self-btn">Delete Account</button>
      </div>
    </section>

  </main>

  <!-- ===== GENERIC CONFIRM MODAL ===== -->
  <div class="modal-backdrop" id="confirm-modal">
    <div class="modal">
      <div class="modal__accent"></div>
      <div class="modal__body">
        <h2 class="modal__title" id="confirm-title">Are you sure?</h2>
        <p class="modal__text" id="confirm-text"></p>
        <div class="modal__actions">
          <button class="btn btn--red" id="c
onfirm-yes">Yes, Delete</button>
          <button class="btn btn--outline" id="confirm-no">Cancel</button>
        </div>
      </div>
    </div>
  </div>

  <div id="toast" class="toast" aria-live="polite"></div>
  <script src="store.js"></script>
  <script src="admin.js"></script>
</body>
</html>



/* =====================================================
   TrainGo — Admin Portal (US_UI_003)
   Requires store.js to be loaded first.
   ===================================================== */

   const STATIONS = [
    "New Delhi", "Mumbai Central", "Chennai Central", "Howrah Junction",
    "Bengaluru City", "Hyderabad Deccan", "Pune Junction", "Ahmedabad Junction",
    "Jaipur Junction", "Lucknow Charbagh", "Bhopal Junction", "Patna Junction",
  ];
  
  const toast = document.getElementById("toast");
  const confirmModal = document.getElementById("confirm-modal");
  let pendingAction = null;
  
  /* ===== UI HELPERS ===== */
  function showToast(msg, isError = false) {
    toast.textContent = msg;
    toast.classList.toggle("toast--error", isError);
    toast.classList.add("show");
    setTimeout(() => toast.classList.remove("show"), 3000);
  }
  
  function setError(errId, msg) {
    const el = document.getElementById(errId);
    if (!el) return;
    el.textContent = msg;
    const f = el.parentElement.querySelector("input, select, textarea");
    if (f) { f.classList.remove("valid"); f.classList.add("invalid"); }
  }
  
  function setValid(errId) {
    const el = document.getElementById(errId);
    if (!el) return;
    el.textContent = "";
    const f = el.parentElement.querySelector("input, select, textarea");
    if (f) { f.classList.remove("invalid"); f.classList.add("valid"); }
  }
  
  function clearFormState(form) {
    form.querySelectorAll(".error-msg").forEach((e) => (e.textContent = ""));
    form.querySelectorAll("input, select, textarea")
        .forEach((f) => f.classList.remove("invalid", "valid"));
  }
  
  function escapeHTML(str) {
    const map = {
      "&": "&amp;", "<": "&lt;", ">": "&gt;",
      '"': "&quot;", "'": "&#39;",
    };
    return String(str).replace(/[&<>"']/g, (c) => map[c]);
  }
  
  /* ===== VALIDATORS ===== */
  const validators = {};
  
  function attachLiveValidation(inputId, errId, fn) {
    validators[inputId] = fn;
    const input = document.getElementById(inputId);
    if (!input) return;
    const evt = input.tagName === "SELECT" ? "change" : "input";
    input.addEventListener(evt, () => {
      const msg = fn(input.value.trim());
      msg ? setError(errId, msg) : setValid(errId);
    });
  }
  
  function runValidation(fields) {
    let ok = true;
    fields.forEach(([inputId, errId]) => {
      const input = document.getElementById(inputId);
      const fn = validators[inputId];
      if (!input || !fn) return;
      const msg = fn(input.value.trim());
      if (msg) { setError(errId, msg); ok = false; }
      else { setValid(errId); }
    });
    return ok;
  }
  
  /* ===== TABS ===== */
  function showPanel(id) {
    document.querySelectorAll(".panel").forEach((p) => p.classList.remove("active"));
    document.getElementById(id)?.classList.add("active");
    document.querySelectorAll(".tab").forEach((t) =>
      t.classList.toggle("active", t.dataset.panel === id)
    );
    window.scrollTo({ top: 0, behavior: "smooth" });
  }
  
  document.querySelectorAll(".tab").forEach((tab) => {
    tab.addEventListener("click", () => {
      const id = tab.dataset.panel;
      showPanel(id);
      if (id === "panel-home") renderDashboard();
      if (id === "panel-train") renderTrains();
      if (id === "panel-profile") loadProfile();
    });
  });
  
  /* ===== ACCESS GUARD ===== */
  function guardAdmin() {
    const s = DB.session.get();
  
    if (!s) { window.location.href = "index.html"; return false; }
    if (s.role !== "admin") { window.location.href = "home.html"; return false; }
  
    document.getElementById("current-user").textContent = s.username;
    return true;
  }
  
  document.getElementById("logout-btn").addEventListener("click", () => {
    DB.session.clear();
    window.location.href = "index.html";
  });
  
  /* ===== CONFIRM MODAL ===== */
  function openConfirm(title, text, onYes) {
    document.getElementById("confirm-title").textContent = title;
    document.getElementById("confirm-text").innerHTML = text;
    pendingAction = onYes;
    confirmModal.classList.add("is-open");
  }
  
  function closeConfirm() {
    pendingAction = null;
    confirmModal.classList.remove("is-open");
  }
  
  document.getElementById("confirm-no").addEventListener("click", closeConfirm);
  
  document.getElementById("confirm-yes").addEventListener("click", () => {
    const fn = pendingAction;
    closeConfirm();
    if (typeof fn === "function") fn();
  });
  
  confirmModal.addEventListener("click", (e) => {
    if (e.target === confirmModal) closeConfirm();
  });
  
  document.addEventListener("keydown", (e) => {
    if (e.key === "Escape" && confirmModal.classList.contains("is-open")) closeConfirm();
  });
  
  /* ===== 1 + 2. DASHBOARD ===== */
  function renderDashboard() {
    const customers = DB.users.customers();
    const active = DB.tickets.active();
  
    document.getElementById("stat-customers").textContent = customers.length;
    document.getElementById("stat-active").textContent = active.length;
    document.getElementById("stat-trains").textContent = DB.trains.all().length;
  
    /* 1.1 tickets per class — system wide */
    const byClass = DB.tickets.countByClass();
    const totalSeats = Object.values(byClass).reduce((a, b) => a + b, 0);
    const classBody = document.getElementById("class-agg-body");
  
    classBody.innerHTML = totalSeats === 0
      ? `<tr><td colspan="3" class="empty-row">No bookings yet.</td></tr>`
      : Object.entries(byClass)
          .sort((a, b) => b[1] - a[1])
          .map(([cat, n]) => {
            const pct = Math.round((n / totalSeats) * 100);
            return `<tr>
              <td>${escapeHTML(cat)}</td>
              <td>${n}</td>
              <td><div class="bar-cell">
                <div class="bar" style="width:${Math.max(pct * 1.4, 4)}px"></div>
                <span>${pct}%</span>
              </div></td>
            </tr>`;
          }).join("");
  
    /* 1.2 sales per quarter */
    const byQ = DB.tickets.countByQuarter();
    const qKeys = Object.keys(byQ).sort();
    const qBody = document.getElementById("quarter-agg-body");
  
    qBody.innerHTML = qKeys.length === 0
      ? `<tr><td colspan="3" class="empty-row">No bookings yet.</td></tr>`
      : qKeys.map((q) =>
          `<tr><td>${q}</td><td>${byQ[q].bookings}</td><td>${byQ[q].seats}</td></tr>`
        ).join("");
  
    /* 2. customer records */
    const custBody = document.getElementById("customers-body");
  
    custBody.innerHTML = customers.length === 0
      ? `<tr><td colspan="5" class="empty-row">No customers registered yet.</td></tr>`
      : customers.map((u) => {
          const booked = active
            .filter((t) => t.userId === u.userId || t.userName === u.username)
            .reduce((sum, t) => sum + t.count, 0);
          return `<tr>
            <td><strong>${escapeHTML(u.userId || "—")}</strong></td>
            <td>${escapeHTML(u.username)}</td>
            <td>${escapeHTML(u.mobile || "—")}</td>
            <td>${booked}</td>
            <td><button class="btn-delete" data-user="${escapeHTML(u.username)}">Delete</button></td>
          </tr>`;
        }).join("");
  
    custBody.querySelectorAll(".btn-delete").forEach((btn) => {
      btn.addEventListener("click", () => {
        const uname = btn.dataset.user;
        openConfirm(
          "Delete Customer?",
          `This will permanently remove <strong>${escapeHTML(uname)}</strong> and all their tickets.`,
          () => {
            DB.users.remove(uname);
            renderDashboard();
            showToast(`Customer "${uname}" deleted.`);
          }
        );
      });
    });
  }
  
  /* ===== REGISTER A TRAIN ===== */
  function populateStations() {
    const from = document.getElementById("tr-from");
    const to = document.getElementById("tr-to");
    STATIONS.forEach((s) => {
      from.insertAdjacentHTML("beforeend", `<option value="${s}">${s}</option>`);
      to.insertAdjacentHTML("beforeend", `<option value="${s}">${s}</option>`);
    });
  }
  
  attachLiveValidation("tr-name", "tr-name-err", (v) => {
    if (v === "") return "Train name is required.";
    if (v.length < 3) return "Must be at least 3 characters.";
    return null;
  });
  
  attachLiveValidation("tr-seats", "tr-seats-err", (v) => {
    if (v === "") return "Number of seats is required.";
    if (!/^[0-9]+$/.test(v)) return "Digits only.";
    const n = Number(v);
    if (n < 1 || n > 2000) return "Enter between 1 and 2000.";
    return null;
  });
  
  attachLiveValidation("tr-from", "tr-from-err", (v) =>
    v === "" ? "Origin station is required." : null
  );
  
  attachLiveValidation("tr-to", "tr-to-err", (v) => {
    if (v === "") return "Destination station is required.";
    if (v === document.getElementById("tr-from").value)
      return "Destination must differ from origin.";
    return null;
  });
  
  attachLiveValidation("tr-depart", "tr-depart-err", (v) =>
    v === "" ? "Departure time is required." : null
  );
  
  attachLiveValidation("tr-duration", "tr-duration-err", (v) => {
    if (v === "") return "Duration is required.";
    if (!/^[0-9]+(\.[0-9]+)?$/.test(v)) return "Numbers only (e.g. 16.5).";
    const n = Number(v);
    if (n <= 0 || n > 72) return "Enter between 0 and 72 hours.";
    return null;
  });
  
  attachLiveValidation("tr-owner", "tr-owner-err", (v) =>
    v === "" ? "Ownership is required." : null
  );
  
  const trainForm = document.getElementById("train-form");
  
  trainForm.addEventListener("submit", (e) => {
    e.preventDefault();
  
    const fields = [
      ["tr-name", "tr-name-err"],
      ["tr-seats", "tr-seats-err"],
      ["tr-from", "tr-from-err"],
      ["tr-to", "tr-to-err"],
      ["tr-depart", "tr-depart-err"],
      ["tr-duration", "tr-duration-err"],
      ["tr-owner", "tr-owner-err"],
    ];
  
    if (!runValidation(fields)) {
      showToast("Please fix the highlighted fields.", true);
      return;
    }
  
    const train = DB.trains.add({
      name: document.getElementById("tr-name").value.trim(),
      seats: Number(document.getElementById("tr-seats").value),
      from: document.getElementById("tr-from").value,
      to: document.getElementById("tr-to").value,
      depart: document.getElementById("tr-depart").value,
      duration: Number(document.getElementById("tr-duration").value),
      owner: document.getElementById("tr-owner").value,
    });
  
    if (!train) {
      showToast("Registration failed — storage error.", true);
      return;
    }
  
    trainForm.reset();
    clearFormState(trainForm);
    renderTrains();
    renderDashboard();
    showToast(`✓  Train ${train.id} registered.`);
  });
  
  function renderTrains() {
    const body = document.getElementById("trains-body");
    const trains = DB.trains.all();
  
    body.innerHTML = trains.length === 0
      ? `<tr><td colspan="8" class="empty-row">No trains registered yet.</td></tr>`
      : trains.slice().reverse().map((t) => `
          <tr>
            <td><strong>${escapeHTML(t.id)}</strong></td>
            <td>${escapeHTML(t.name)}</td>
            <td>${t.seats}</td>
            <td>${escapeHTML(t.from)}</td>
            <td>${escapeHTML(t.to)}</td>
            <td>${escapeHTML(t.depart)}</td>
            <td>${escapeHTML(t.owner)}</td>
            <td><button class="btn-delete" data-train="${escapeHTML(t.id)}">Remove</button></td>
          </tr>`).join("");
  
    body.querySelectorAll(".btn-delete").forEach((btn) => {
      btn.addEventListener("click", () => {
        const id = btn.dataset.train;
        openConfirm(
          "Remove Train?",
          `Train <strong>${escapeHTML(id)}</strong> will no longer be bookable.`,
          () => {
            DB.trains.remove(id);
            renderTrains();
            renderDashboard();
            showToast(`Train ${id} removed.`);
          }
        );
      });
    });
  }
  
  /* ===== PROFILE ===== */
  const profileForm = document.getElementById("profile-form");
  const pfToggle = document.getElementById("pf-toggle-pw");
  const pfSection = document.getElementById("pf-pw-section");
  
  pfToggle.addEventListener("change", () => {
    pfSection.classList.toggle("is-open", pfToggle.checked);
    if (!pfToggle.checked) {
      ["pf-current", "pf-new", "pf-confirm"].forEach((id) => {
        const el = document.getElementById(id);
        el.value = "";
        el.classList.remove("invalid", "valid");
        document.getElementById(id + "-err").textContent = "";
      });
    }
  });
  
  function loadProfile() {
    const admin = DB.session.user();
    if (!admin) return;
    document.getElementById("pf-username").value = admin.username || "";
    document.getElementById("pf-email").value = admin.email || "";
    document.getElementById("pf-mobile").value = admin.mobile || "";
  }
  
  attachLiveValidation("pf-username", "pf-username-err", (v) => {
    if (v === "") return "Username is required.";
    if (v.length < 6) return "Must be at least 6 characters.";
    if (!/^[A-Za-z]+$/.test(v)) return "Letters only — no numbers or symbols.";
    return null;
  });
  
  attachLiveValidation("pf-email", "pf-email-err", (v) => {
    if (v === "") return "Email is required.";
    if (!v.includes("@")) return "Must contain an @ sign.";
    if (!/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return "Must include a domain like gmail.com.";
    return null;
  });
  
  attachLiveValidation("pf-mobile", "pf-mobile-err", (v) => {
    if (v === "") return "Mobile number is required.";
    if (/[A-Za-z]/.test(v)) return "Alphabets are not allowed.";
    if (!/^[0-9]{10}$/.test(v)) return "Must be exactly 10 digits.";
    return null;
  });
  
  attachLiveValidation("pf-current", "pf-current-err", (v) =>
    v === "" ? "Current password is required." : null
  );
  
  attachLiveValidation("pf-new", "pf-new-err", (v) => {
    if (v === "") return "New password is required.";
    if (v.length < 8) return "Must be at least 8 characters.";
    if (!/[A-Z]/.test(v)) return "Needs at least 1 uppercase letter.";
    if (!/[0-9]/.test(v)) return "Needs at least 1 number.";
    if (!/[^A-Za-z0-9]/.test(v)) return "Needs at least 1 special character.";
    return null;
  });
  
  attachLiveValidation("pf-confirm", "pf-confirm-err", (v) => {
    if (v === "") return "Please confirm the new password.";
    if (v !== document.getElementById("pf-new").value) return "Passwords do not match.";
    return null;
  });
  
  profileForm.addEventListener("submit", (e) => {
    e.preventDefault();
  
    let fields = [
      ["pf-username", "pf-username-err"],
      ["pf-email", "pf-email-err"],
      ["pf-mobile", "pf-mobile-err"],
    ];
  
    if (pfToggle.checked) {
      fields = fields.concat([
        ["pf-current", "pf-current-err"],
        ["pf-new", "pf-new-err"],
        ["pf-confirm", "pf-confirm-err"],
      ]);
    }
  
    if (!runValidation(fields)) {
      showToast("Please fix the highlighted fields.", true);
      return;
    }
  
    const admin = DB.session.user();
    if (!admin) {
      showToast("Session expired. Please log in again.", true);
      setTimeout(() => (window.location.href = "index.html"), 1200);
      return;
    }
  
    const newUsername = document.getElementById("pf-username").value.trim();
  
    if (newUsername.toLowerCase() !== admin.username.toLowerCase() &&
        DB.users.exists(newUsername)) {
      setError("pf-username-err", "Username already taken.");
      return;
    }
  
    const patch = {
      username: newUsername,
      email: document.getElementById("pf-email").value.trim(),
      mobile: document.getElementById("pf-mobile").value.trim(),
    };
  
    if (pfToggle.checked) {
      if (admin.password !== document.getElementById("pf-current").value) {
        setError("pf-current-err", "Current password is incorrect.");
        showToast("Current password is incorrect.", true);
        return;
      }
      patch.password = document.getElementById("pf-new").value;
    }
  
    // DB.users.update keeps the session and ticket ownership in sync
    const updated = DB.users.update(admin.username, patch);
  
    if (!updated) {
      showToast("Update failed — username may be taken.", true);
      return;
    }
  
    document.getElementById("current-user").textContent = updated.username;
  
    pfToggle.checked = false;
    pfSection.classList.remove("is-open");
    ["pf-current", "pf-new", "pf-confirm"].forEach((id) => {
      document.getElementById(id).value = "";
    });
    clearFormState(profileForm);
  
    showToast("✓  Profile updated successfully.");
  });
  
  /* delete own account */
  document.getElementById("delete-self-btn").addEventListener("click", () => {
    const s = DB.session.get();
    if (!s) return;
  
    openConfirm(
      "Delete Your Account?",
      `Account <strong>${escapeHTML(s.username)}</strong> will be permanently removed and you will be logged out.`,
      () => {
        DB.users.remove(s.username);
        DB.session.clear();
        window.location.href = "index.html";
      }
    );
  });
  
  /* ===== INIT ===== */
  if (guardAdmin()) {
    populateStations();
    loadProfile();
    renderDashboard();
    renderTrains();
  }
← Back to timeline