This commit is contained in:
Frank Harris 2026-06-17 18:00:53 -05:00
parent cc7bbafb63
commit 3948dde2e7
6 changed files with 114 additions and 66 deletions

View file

@ -131,25 +131,12 @@ textarea {
.primary-nav { .primary-nav {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: center;
gap: 10px;
min-width: 0;
}
.nav-group {
display: inline-flex;
align-items: center; align-items: center;
flex-wrap: wrap;
justify-content: center; justify-content: center;
gap: 6px; gap: 6px;
min-width: 0; min-width: 0;
} }
.nav-group + .nav-group {
padding-left: 10px;
border-left: 1px solid var(--line);
}
.nav-link { .nav-link {
padding: 9px 10px; padding: 9px 10px;
border-radius: 6px; border-radius: 6px;
@ -163,6 +150,60 @@ textarea {
background: var(--accent-soft); background: var(--accent-soft);
} }
.account-menu {
position: relative;
}
.account-menu summary {
list-style: none;
cursor: pointer;
}
.account-menu summary::-webkit-details-marker {
display: none;
}
.account-menu summary::after {
content: "";
display: inline-block;
width: 0;
height: 0;
margin-left: 7px;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid currentColor;
transform: translateY(1px);
}
.account-menu-panel {
position: absolute;
right: 0;
top: calc(100% + 10px);
min-width: 210px;
padding: 8px;
border: 1px solid var(--line);
border-radius: var(--radius);
background: rgba(8, 18, 33, 0.98);
box-shadow: var(--shadow);
display: grid;
gap: 4px;
}
.account-menu-link {
display: flex;
align-items: center;
min-height: 40px;
padding: 8px 10px;
border-radius: 6px;
color: var(--muted);
}
.account-menu-link:hover,
.account-menu-link.is-active {
color: var(--text);
background: var(--accent-soft);
}
.header-actions { .header-actions {
display: flex; display: flex;
gap: 8px; gap: 8px;
@ -723,6 +764,7 @@ textarea {
.primary-nav { .primary-nav {
width: 100%; width: 100%;
flex-wrap: wrap; flex-wrap: wrap;
row-gap: 8px;
} }
.header-actions { .header-actions {
@ -786,29 +828,36 @@ textarea {
padding-top: 12px; padding-top: 12px;
} }
.nav-group,
.nav-group + .nav-group {
display: grid;
grid-template-columns: 1fr;
justify-items: stretch;
gap: 4px;
padding-left: 0;
padding-top: 8px;
border-left: 0;
border-top: 1px solid var(--line);
}
.nav-group:first-child {
border-top: 0;
padding-top: 0;
}
.nav-link { .nav-link {
min-height: 44px; min-height: 44px;
display: flex; display: flex;
align-items: center; align-items: center;
} }
.account-menu {
border-top: 1px solid var(--line);
padding-top: 8px;
}
.account-menu summary {
width: 100%;
}
.account-menu-panel {
position: static;
min-width: 0;
margin-top: 6px;
padding: 4px 0 0 12px;
border: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.account-menu-link {
min-height: 42px;
}
.header-actions { .header-actions {
grid-column: 1 / -1; grid-column: 1 / -1;
display: none; display: none;

View file

@ -2,6 +2,7 @@ document.addEventListener('DOMContentLoaded', () => {
const toggle = document.querySelector('[data-nav-toggle]'); const toggle = document.querySelector('[data-nav-toggle]');
const menu = document.querySelector('[data-nav-menu]'); const menu = document.querySelector('[data-nav-menu]');
const actions = document.querySelector('[data-header-actions]'); const actions = document.querySelector('[data-header-actions]');
const accountMenu = document.querySelector('[data-account-menu]');
if (!toggle || !menu) { if (!toggle || !menu) {
return; return;
@ -14,5 +15,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (actions) { if (actions) {
actions.classList.toggle('is-open', !expanded); actions.classList.toggle('is-open', !expanded);
} }
if (accountMenu && window.matchMedia('(max-width: 820px)').matches) {
accountMenu.open = !expanded;
}
}); });
}); });

View file

@ -436,15 +436,14 @@ function website_table_exists(string $tableName): bool
return false; return false;
} }
$stmt = $db->prepare('SHOW TABLES LIKE ?'); $escapedTableName = $db->real_escape_string($tableName);
if (!$stmt) { $result = @$db->query("SHOW TABLES LIKE '{$escapedTableName}'");
if (!$result instanceof mysqli_result) {
return false; return false;
} }
$stmt->bind_param('s', $tableName);
$stmt->execute(); $exists = $result->num_rows > 0;
$result = $stmt->get_result(); $result->free();
$exists = $result instanceof mysqli_result && $result->num_rows > 0;
$stmt->close();
return $exists; return $exists;
} }

View file

@ -5,7 +5,7 @@ declare(strict_types=1);
$activePage = $activePage ?? ''; $activePage = $activePage ?? '';
$currentUser = website_current_user(); $currentUser = website_current_user();
$cartCount = website_cart_count(); $cartCount = website_cart_count();
$publicLinks = [ $primaryLinks = [
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')], ['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')], ['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.php')],
['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')], ['key' => 'pricing', 'label' => 'Pricing', 'href' => website_url('pricing.php')],
@ -19,18 +19,22 @@ if ($currentUser) {
['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('my_orders.php')], ['key' => 'orders', 'label' => 'My Orders', 'href' => website_url('my_orders.php')],
['key' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')], ['key' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')],
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()], ['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
['key' => 'logout', 'label' => 'Logout', 'href' => website_url('logout.php')],
]; ];
if (website_current_user_is_staff()) {
$accountLinks[] = ['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')];
}
$accountLinks[] = ['key' => 'logout', 'label' => 'Logout', 'href' => website_url('logout.php')];
$accountLabel = 'Account';
} else { } else {
$accountLinks = [ $accountLinks = [
['key' => 'account', 'label' => 'Login', 'href' => website_login_url()], ['key' => 'account', 'label' => 'Login', 'href' => website_login_url()],
['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()], ['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()],
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()], ['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
]; ];
$accountLabel = 'Account';
} }
$staffLinks = ($currentUser && website_current_user_is_staff()) ? [ $accountActiveKeys = array_column($accountLinks, 'key');
['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')], $accountIsActive = in_array($activePage, $accountActiveKeys, true);
] : [];
?> ?>
<header class="site-header"> <header class="site-header">
<div class="container header-shell"> <div class="container header-shell">
@ -50,29 +54,21 @@ $staffLinks = ($currentUser && website_current_user_is_staff()) ? [
</button> </button>
<nav class="primary-nav" id="primary-nav" data-nav-menu aria-label="Primary navigation"> <nav class="primary-nav" id="primary-nav" data-nav-menu aria-label="Primary navigation">
<div class="nav-group nav-group-public"> <?php foreach ($primaryLinks as $link): ?>
<?php foreach ($publicLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>"> <a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?> <?= website_escape($link['label']) ?>
</a> </a>
<?php endforeach; ?> <?php endforeach; ?>
</div> <details class="account-menu" data-account-menu>
<div class="nav-group nav-group-account" aria-label="Account navigation"> <summary class="nav-link<?= $accountIsActive ? ' is-active' : '' ?>"><?= website_escape($accountLabel) ?></summary>
<?php foreach ($accountLinks as $link): ?> <div class="account-menu-panel">
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>"> <?php foreach ($accountLinks as $link): ?>
<?= website_escape($link['label']) ?> <a class="account-menu-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
</a> <?= website_escape($link['label']) ?>
<?php endforeach; ?> </a>
</div> <?php endforeach; ?>
<?php if (!empty($staffLinks)): ?> </div>
<div class="nav-group nav-group-staff" aria-label="Staff navigation"> </details>
<?php foreach ($staffLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</nav> </nav>
<div class="header-actions" data-header-actions> <div class="header-actions" data-header-actions>

View file

@ -77,12 +77,11 @@ Website footer account links are state-aware:
- logged in: `My Account`, `Order a Server`, `Control Panel`, `My Servers`, `Log Out` - logged in: `My Account`, `Order a Server`, `Control Panel`, `My Servers`, `Log Out`
- staff-only links appear only for Panel admin users and still enforce website staff authorization server-side - staff-only links appear only for Panel admin users and still enforce website staff authorization server-side
The shared header groups navigation by purpose: The shared header uses one responsive navigation bar:
- public: Home, Game Servers, Pricing, Locations, Documentation, Support - primary links: Home, Game Servers, Pricing, Locations, Documentation, Support
- account: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Logout - account menu: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Staff Dashboard/Logout
- staff: Staff Dashboard, only for authorized website staff - action buttons: Custom Projects and Control Panel
- actions: Custom Projects and Control Panel
Control Panel links point directly to the configured Panel domain. `My Servers` Control Panel links point directly to the configured Panel domain. `My Servers`
opens a website customer page that summarizes website orders and links to the opens a website customer page that summarizes website orders and links to the

View file

@ -254,6 +254,7 @@ Check:
- expected catalog columns exist or can be added by the idempotent migration runner - expected catalog columns exist or can be added by the idempotent migration runner
- `remote_servers` schema is the current Panel schema - `remote_servers` schema is the current Panel schema
- the page is not assuming `remote_servers.enabled` exists - the page is not assuming `remote_servers.enabled` exists
- `website_table_exists()` is not using `SHOW TABLES LIKE ?`; MySQL does not support binding the table pattern in that statement through mysqli prepared placeholders
The current helper treats missing `remote_servers.enabled` as enabled for display and lets staff assign locations from current Panel remote-server rows. The current helper treats missing `remote_servers.enabled` as enabled for display and lets staff assign locations from current Panel remote-server rows.