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 {
display: flex;
flex-wrap: nowrap;
justify-content: center;
gap: 10px;
min-width: 0;
}
.nav-group {
display: inline-flex;
align-items: center;
flex-wrap: wrap;
justify-content: center;
gap: 6px;
min-width: 0;
}
.nav-group + .nav-group {
padding-left: 10px;
border-left: 1px solid var(--line);
}
.nav-link {
padding: 9px 10px;
border-radius: 6px;
@ -163,6 +150,60 @@ textarea {
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 {
display: flex;
gap: 8px;
@ -723,6 +764,7 @@ textarea {
.primary-nav {
width: 100%;
flex-wrap: wrap;
row-gap: 8px;
}
.header-actions {
@ -786,29 +828,36 @@ textarea {
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 {
min-height: 44px;
display: flex;
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 {
grid-column: 1 / -1;
display: none;

View file

@ -2,6 +2,7 @@ document.addEventListener('DOMContentLoaded', () => {
const toggle = document.querySelector('[data-nav-toggle]');
const menu = document.querySelector('[data-nav-menu]');
const actions = document.querySelector('[data-header-actions]');
const accountMenu = document.querySelector('[data-account-menu]');
if (!toggle || !menu) {
return;
@ -14,5 +15,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (actions) {
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;
}
$stmt = $db->prepare('SHOW TABLES LIKE ?');
if (!$stmt) {
$escapedTableName = $db->real_escape_string($tableName);
$result = @$db->query("SHOW TABLES LIKE '{$escapedTableName}'");
if (!$result instanceof mysqli_result) {
return false;
}
$stmt->bind_param('s', $tableName);
$stmt->execute();
$result = $stmt->get_result();
$exists = $result instanceof mysqli_result && $result->num_rows > 0;
$stmt->close();
$exists = $result->num_rows > 0;
$result->free();
return $exists;
}

View file

@ -5,7 +5,7 @@ declare(strict_types=1);
$activePage = $activePage ?? '';
$currentUser = website_current_user();
$cartCount = website_cart_count();
$publicLinks = [
$primaryLinks = [
['key' => 'home', 'label' => 'Home', 'href' => website_url('index.php')],
['key' => 'servers', 'label' => 'Game Servers', 'href' => website_url('serverlist.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' => 'servers', 'label' => 'My Servers', 'href' => website_url('my_servers.php')],
['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 {
$accountLinks = [
['key' => 'account', 'label' => 'Login', 'href' => website_login_url()],
['key' => 'register', 'label' => 'Create Account', 'href' => website_register_url()],
['key' => 'cart', 'label' => 'Cart' . ($cartCount > 0 ? ' (' . $cartCount . ')' : ''), 'href' => website_cart_url()],
];
$accountLabel = 'Account';
}
$staffLinks = ($currentUser && website_current_user_is_staff()) ? [
['key' => 'staff', 'label' => 'Staff Dashboard', 'href' => website_url('staff.php')],
] : [];
$accountActiveKeys = array_column($accountLinks, 'key');
$accountIsActive = in_array($activePage, $accountActiveKeys, true);
?>
<header class="site-header">
<div class="container header-shell">
@ -50,29 +54,21 @@ $staffLinks = ($currentUser && website_current_user_is_staff()) ? [
</button>
<nav class="primary-nav" id="primary-nav" data-nav-menu aria-label="Primary navigation">
<div class="nav-group nav-group-public">
<?php foreach ($publicLinks as $link): ?>
<?php foreach ($primaryLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
</div>
<div class="nav-group nav-group-account" aria-label="Account navigation">
<details class="account-menu" data-account-menu>
<summary class="nav-link<?= $accountIsActive ? ' is-active' : '' ?>"><?= website_escape($accountLabel) ?></summary>
<div class="account-menu-panel">
<?php foreach ($accountLinks as $link): ?>
<a class="nav-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<a class="account-menu-link<?= $activePage === $link['key'] ? ' is-active' : '' ?>" href="<?= website_escape($link['href']) ?>">
<?= website_escape($link['label']) ?>
</a>
<?php endforeach; ?>
</div>
<?php if (!empty($staffLinks)): ?>
<div class="nav-group nav-group-staff" aria-label="Staff navigation">
<?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; ?>
</details>
</nav>
<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`
- 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
- account: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Logout
- staff: Staff Dashboard, only for authorized website staff
- actions: Custom Projects and Control Panel
- primary links: Home, Game Servers, Pricing, Locations, Documentation, Support
- account menu: Login/Create Account/Cart or My Account/My Orders/My Servers/Cart/Staff Dashboard/Logout
- action buttons: Custom Projects and Control Panel
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

View file

@ -254,6 +254,7 @@ Check:
- expected catalog columns exist or can be added by the idempotent migration runner
- `remote_servers` schema is the current Panel schema
- 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.