feat(nav): give admins a mobile entry point to the admin area #20
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1517,7 +1517,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.88.0"
|
version = "0.89.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "mangalord"
|
name = "mangalord"
|
||||||
version = "0.88.0"
|
version = "0.89.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
default-run = "mangalord"
|
default-run = "mangalord"
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const DESKTOP = { width: 1280, height: 720 } as const;
|
|||||||
|
|
||||||
async function mockSession(
|
async function mockSession(
|
||||||
page: Page,
|
page: Page,
|
||||||
opts: { authed?: boolean; logoutCalls?: { count: number } } = {}
|
opts: { authed?: boolean; admin?: boolean; logoutCalls?: { count: number } } = {}
|
||||||
) {
|
) {
|
||||||
const authed = opts.authed ?? false;
|
const authed = opts.authed ?? false;
|
||||||
await page.route('**/api/v1/auth/config', (route) =>
|
await page.route('**/api/v1/auth/config', (route) =>
|
||||||
@@ -31,7 +31,7 @@ async function mockSession(
|
|||||||
id: 'u1',
|
id: 'u1',
|
||||||
username: 'fabian',
|
username: 'fabian',
|
||||||
created_at: '2026-01-05T00:00:00Z',
|
created_at: '2026-01-05T00:00:00Z',
|
||||||
is_admin: false
|
is_admin: opts.admin ?? false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
: JSON.stringify({
|
: JSON.stringify({
|
||||||
@@ -175,11 +175,27 @@ test.describe('mobile account hub', () => {
|
|||||||
await expect(page.getByTestId('account-row-preferences')).toBeVisible();
|
await expect(page.getByTestId('account-row-preferences')).toBeVisible();
|
||||||
await expect(page.getByTestId('account-row-change-password')).toBeVisible();
|
await expect(page.getByTestId('account-row-change-password')).toBeVisible();
|
||||||
await expect(page.getByTestId('account-row-logout')).toBeVisible();
|
await expect(page.getByTestId('account-row-logout')).toBeVisible();
|
||||||
|
// A non-admin never sees the Admin entry.
|
||||||
|
await expect(page.getByTestId('account-row-admin')).toHaveCount(0);
|
||||||
// Desktop card MUST not render — only the hub view is active
|
// Desktop card MUST not render — only the hub view is active
|
||||||
// on mobile so we don't double-up the password form testids.
|
// on mobile so we don't double-up the password form testids.
|
||||||
await expect(page.getByTestId('account-desktop-card')).toHaveCount(0);
|
await expect(page.getByTestId('account-desktop-card')).toHaveCount(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('phone viewport admin: the account hub offers an Admin row linking to /admin', async ({
|
||||||
|
page
|
||||||
|
}) => {
|
||||||
|
// Admin is reachable from the desktop header but had no mobile entry
|
||||||
|
// point — admins had to type the URL. The Account hub now carries it.
|
||||||
|
await mockSession(page, { authed: true, admin: true });
|
||||||
|
await page.setViewportSize(MOBILE);
|
||||||
|
await page.goto('/profile/account');
|
||||||
|
|
||||||
|
const adminRow = page.getByTestId('account-row-admin');
|
||||||
|
await expect(adminRow).toBeVisible();
|
||||||
|
await expect(adminRow).toHaveAttribute('href', '/admin');
|
||||||
|
});
|
||||||
|
|
||||||
test('phone viewport authed: Change password row opens the bottom sheet', async ({
|
test('phone viewport authed: Change password row opens the bottom sheet', async ({
|
||||||
page
|
page
|
||||||
}) => {
|
}) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mangalord-frontend",
|
"name": "mangalord-frontend",
|
||||||
"version": "0.88.0",
|
"version": "0.89.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -81,7 +81,9 @@
|
|||||||
label: 'Account',
|
label: 'Account',
|
||||||
href: '/profile/account',
|
href: '/profile/account',
|
||||||
icon: User,
|
icon: User,
|
||||||
match: ['/profile', '/profile/preferences']
|
// `/admin` keeps the Account tab highlighted while an admin is in
|
||||||
|
// the admin area — its only mobile entry point is the Account hub.
|
||||||
|
match: ['/profile', '/profile/preferences', '/admin']
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
import SlidersHorizontal from '@lucide/svelte/icons/sliders-horizontal';
|
||||||
import KeyRound from '@lucide/svelte/icons/key-round';
|
import KeyRound from '@lucide/svelte/icons/key-round';
|
||||||
import LogOut from '@lucide/svelte/icons/log-out';
|
import LogOut from '@lucide/svelte/icons/log-out';
|
||||||
|
import Shield from '@lucide/svelte/icons/shield';
|
||||||
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
import ChevronRight from '@lucide/svelte/icons/chevron-right';
|
||||||
|
|
||||||
let currentPassword = $state('');
|
let currentPassword = $state('');
|
||||||
@@ -193,6 +194,18 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if session.user.is_admin}
|
||||||
|
<!-- Admin is reachable from the desktop header; the mobile chrome
|
||||||
|
has no Admin tab, so the Account hub is its entry point. -->
|
||||||
|
<div class="row-group">
|
||||||
|
<a class="row" href="/admin" data-testid="account-row-admin">
|
||||||
|
<Shield size={18} aria-hidden="true" />
|
||||||
|
<span class="row-label">Admin</span>
|
||||||
|
<ChevronRight size={16} aria-hidden="true" class="row-chev" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="row-group">
|
<div class="row-group">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user