From dec1dd902cc80c27213f97a6f83a9681f46aa7f1 Mon Sep 17 00:00:00 2001 From: Zak Bearman Date: Sun, 6 Sep 2026 19:52:39 +1200 Subject: [PATCH] Unify page layouts, compact headers and shared UI styling --- frontend/UI.md | 20 ++ frontend/app/account.css | 9 - frontend/app/admin/invites/page.tsx | 4 +- frontend/app/admin/requests-all/page.tsx | 5 - frontend/app/admin/system/page.tsx | 9 +- frontend/app/changelog/page.tsx | 15 +- frontend/app/feedback/page.tsx | 14 +- frontend/app/forgot-password/page.tsx | 20 +- frontend/app/globals.css | 7 +- frontend/app/how-it-works/page.tsx | 11 +- frontend/app/layout.tsx | 1 + frontend/app/login/page.tsx | 14 +- .../app/new-requests/NewRequestClient.tsx | 15 +- frontend/app/not-found.tsx | 11 ++ frontend/app/page.tsx | 11 +- frontend/app/portal/PortalClient.tsx | 24 +-- frontend/app/profile/invites/page.tsx | 12 +- frontend/app/profile/page.tsx | 9 +- frontend/app/requests/[id]/page.tsx | 34 ++-- frontend/app/reset-password/page.tsx | 19 +- frontend/app/signup/page.tsx | 27 ++- frontend/app/ui/AdminShell.tsx | 10 +- frontend/app/ui/ApplicationChrome.tsx | 2 +- frontend/app/ui/AuthLayout.tsx | 21 +++ frontend/app/ui/PageHeading.tsx | 26 +++ frontend/app/workspace.css | 174 ++++++++++++++++++ scripts/review_layout_ui.cjs | 135 ++++++++++++++ 27 files changed, 480 insertions(+), 179 deletions(-) create mode 100644 frontend/UI.md create mode 100644 frontend/app/not-found.tsx create mode 100644 frontend/app/ui/AuthLayout.tsx create mode 100644 frontend/app/ui/PageHeading.tsx create mode 100644 frontend/app/workspace.css create mode 100644 scripts/review_layout_ui.cjs diff --git a/frontend/UI.md b/frontend/UI.md new file mode 100644 index 0000000..ef25e12 --- /dev/null +++ b/frontend/UI.md @@ -0,0 +1,20 @@ +# Shared workspace layout + +- Use `app/ui/PageHeading.tsx` for page titles. Keep the heading flat, with a short description and optional actions. Only record IDs belong in the optional eyebrow. +- Admin pages use `AdminShell`, which supplies the same heading and settings navigation. +- Authentication screens use `AuthLayout`; they do not render the signed-in navigation. +- `app/workspace.css` owns page width, gutters, title sizes and shared spacing. Feature styles own the content inside those pages. Do not add new page-specific hero panels or outer width overrides. +- Keep primary actions, secondary controls and destructive actions visually distinct. Do not fade or uppercase every span inside a button: cards also use buttons, often with nested text. +- Keep technical IDs and pipeline labels monospace. Use sentence case for ordinary labels and descriptions. +- Preserve the six-stage request pipeline: three columns on desktop, two on tablet, one on narrow screens. Issue reports remain a right-hand column on desktop and stack on smaller screens. +- Desktop navigation stays at the top; mobile navigation stays at the bottom. Dialogs must remain clear of both. + +## Browser checks + +Build the frontend before reviewing. The scripts in `scripts/` run using Node and Playwright: + +- `review_layout_ui.cjs`: page alignment, consistent headings, overflow, redirects, pipeline layout, invite tabs, and fixture-only recovery forms. +- `review_account_ui.cjs`: fixture-only login and profile interaction checks. +- `review_settings_ui.cjs`: settings state, region-only saves, secret preservation, responsive controls and issue dialog placement. + +The layout/settings reviews accept `REVIEW_BASE`, `REVIEW_LIVE_BASE`, `REVIEW_PLAYWRIGHT`, and `REVIEW_DIR`. Provide an authorised short-lived session through `REVIEW_SESSION` as `{ "name": "cookie-name", "token": "..." }` in the process environment, never in a committed file. Live writes are blocked; submission checks use fixtures. Screenshots may contain account information and must stay outside the repository. diff --git a/frontend/app/account.css b/frontend/app/account.css index 652f6ed..3dd3a2c 100644 --- a/frontend/app/account.css +++ b/frontend/app/account.css @@ -1,7 +1,4 @@ /* Top-navigation workspace and streamlined account screens. */ -.page > main:not(.auth-screen):not(.auth-card):not(.login-page) { width: calc(100% - 64px); max-width: 1440px; margin: 32px auto 0; padding: 0; border: 0 !important; background: transparent !important; } -.page > .site-banner, .page > .user-view-banner { width: calc(100% - 64px); max-width: 1440px; margin: 16px auto 0; } -.admin-shell.admin-shell--top-nav { display: block; width: calc(100% - 64px); max-width: 1440px; margin: 24px auto 0; } .admin-shell--top-nav > .admin-card { width: 100%; max-width: none; padding: 24px 0; } .settings-top-navigation { display: flex; justify-content: space-between; align-items: center; gap: 20px; padding: 0 0 20px; border-bottom: 1px solid var(--ops-line-soft); } .settings-top-navigation a { color: var(--ops-muted); font-size: 13px; text-decoration: none; } @@ -11,10 +8,7 @@ .admin-supplemental { margin-top: 28px; border-top: 1px solid var(--ops-line-soft); padding-top: 20px; } .admin-supplemental > summary { cursor: pointer; color: var(--ops-muted); font-size: 13px; margin-bottom: 18px; } .admin-supplemental .admin-rail-stack { display: block; max-width: 960px; } -.page > main.account-page { max-width: 920px !important; margin-top: 42px; } -.account-heading { display: flex; align-items: center; justify-content: space-between; gap: 24px; margin-bottom: 32px; } .account-eyebrow { font: 10px "JetBrains Mono", monospace; color: var(--ops-faint); } -.account-heading h1 { font-size: clamp(30px, 4vw, 40px); line-height: 1.2; margin: 7px 0 0; color: var(--ops-text); } .account-identity { display: flex; align-items: center; gap: 12px; min-width: 0; } .account-identity > div { display: grid; gap: 4px; min-width: 0; } .account-identity strong { font-size: 14px; overflow-wrap: anywhere; } @@ -92,9 +86,6 @@ button.account-secondary { min-height: 44px; padding: 11px 16px; border: 1px sol @keyframes account-appear { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } } @media (prefers-reduced-motion: reduce) { .account-panel { animation: none; } } @media (max-width: 680px) { - .page > main:not(.auth-screen):not(.auth-card):not(.login-page), .page > .site-banner, .page > .user-view-banner, .admin-shell.admin-shell--top-nav { width: calc(100% - 32px); } - .page > main.account-page { margin-top: 28px; } - .account-heading { align-items: flex-start; gap: 16px; margin-bottom: 24px; } .account-identity strong { max-width: 130px; } .account-avatar { display: none; } .account-panel { padding: 22px 20px; } diff --git a/frontend/app/admin/invites/page.tsx b/frontend/app/admin/invites/page.tsx index 00437a0..e2746ea 100644 --- a/frontend/app/admin/invites/page.tsx +++ b/frontend/app/admin/invites/page.tsx @@ -1188,8 +1188,8 @@ export default function AdminInviteManagementPage() { return (
diff --git a/frontend/app/admin/requests-all/page.tsx b/frontend/app/admin/requests-all/page.tsx index b3a2d9c..e00f9d9 100644 --- a/frontend/app/admin/requests-all/page.tsx +++ b/frontend/app/admin/requests-all/page.tsx @@ -109,11 +109,6 @@ export default function AdminRequestsAllPage() { router.push('/admin')}> - Back to settings - - } >
diff --git a/frontend/app/admin/system/page.tsx b/frontend/app/admin/system/page.tsx index 9d75e81..8530c90 100644 --- a/frontend/app/admin/system/page.tsx +++ b/frontend/app/admin/system/page.tsx @@ -116,14 +116,9 @@ export default function AdminSystemGuidePage() { return ( router.push('/admin')}> - Back to settings - - } >
diff --git a/frontend/app/changelog/page.tsx b/frontend/app/changelog/page.tsx index 8c79508..2c937d1 100644 --- a/frontend/app/changelog/page.tsx +++ b/frontend/app/changelog/page.tsx @@ -1,5 +1,7 @@ 'use client' +import PageHeading from '../ui/PageHeading' + import { useEffect, useMemo, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth' @@ -106,14 +108,9 @@ export default function ChangelogPage() { }, [groups, loading]) return ( -
-
-
-

Changelog

-

Latest updates and release notes.

-
- {content} -
-
+
+ + {content} +
) } diff --git a/frontend/app/feedback/page.tsx b/frontend/app/feedback/page.tsx index ed76558..724e16e 100644 --- a/frontend/app/feedback/page.tsx +++ b/frontend/app/feedback/page.tsx @@ -1,5 +1,7 @@ 'use client' +import PageHeading from '../ui/PageHeading' + import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetchOrThrow, getApiBase, getToken, UnauthorizedError } from '../lib/auth' @@ -78,16 +80,10 @@ export default function FeedbackPage() { } return ( -
-
-

Send feedback

-

Help us improve Magent

-

- Found a problem or have an idea? Send it here and we will see it right away. -

-
+
+ -
+ diff --git a/frontend/app/forgot-password/page.tsx b/frontend/app/forgot-password/page.tsx index 024dd8d..be2c353 100644 --- a/frontend/app/forgot-password/page.tsx +++ b/frontend/app/forgot-password/page.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' -import BrandingLogo from '../ui/BrandingLogo' +import AuthLayout from '../ui/AuthLayout' import { getApiBase } from '../lib/auth' export default function ForgotPasswordPage() { @@ -46,14 +46,8 @@ export default function ForgotPasswordPage() { } return ( -
- -

Forgot password

-

- Enter the username or email you use for Jellyfin or Magent. If the account is eligible, a reset link - will be emailed to you. -

- + + - {error &&
{error}
} - {status &&
{status}
} + {error &&
{error}
} + {status &&
{status}
}
-
@@ -74,6 +68,6 @@ export default function ForgotPasswordPage() { Back to sign in -
+ ) } diff --git a/frontend/app/globals.css b/frontend/app/globals.css index cc6a91b..02c1c00 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -512,10 +512,9 @@ button { } button span { - font-size: 12px; - text-transform: uppercase; - opacity: 0.8; - text-align: center; + font-size: inherit; + text-transform: inherit; + text-align: inherit; } .filters { diff --git a/frontend/app/how-it-works/page.tsx b/frontend/app/how-it-works/page.tsx index fe8bc45..ce1dd95 100644 --- a/frontend/app/how-it-works/page.tsx +++ b/frontend/app/how-it-works/page.tsx @@ -1,16 +1,11 @@ 'use client' +import PageHeading from '../ui/PageHeading' + export default function HowItWorksPage() { return (
-
-

How it works

-

How Magent works for users

-

- Use Magent to find a request, watch it move through the pipeline, and know when it is - ready without constantly refreshing the page. -

-
+

What Magent is for

diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 6d57f58..5ef0371 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -2,6 +2,7 @@ import './globals.css' import './ops-redesign.css' import './admin/config.css' import './account.css' +import './workspace.css' import type { ReactNode } from 'react' import BrandingFavicon from './ui/BrandingFavicon' import ApplicationChrome from './ui/ApplicationChrome' diff --git a/frontend/app/login/page.tsx b/frontend/app/login/page.tsx index d0740d5..3077065 100644 --- a/frontend/app/login/page.tsx +++ b/frontend/app/login/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, type FormEvent } from 'react' import { getApiBase, setToken } from '../lib/auth' -import MagentMark from '../ui/MagentMark' +import AuthLayout from '../ui/AuthLayout' type LoginMode = 'jellyfin' | 'local' type LoginOptions = { showJellyfinLogin: boolean; showLocalLogin: boolean; showForgotPassword: boolean; showSignupLink: boolean } @@ -77,10 +77,9 @@ export default function LoginPage() { } return ( -
-
-
MagentBeta
-

Welcome back.

Sign in to your media workspace.

+ Have an invite? Create an account + }> {banner &&

{banner.message}

} {optionsReady && options.showJellyfinLogin && options.showLocalLogin &&
@@ -102,9 +101,6 @@ export default function LoginPage() { )} - {optionsReady && options.showSignupLink && } -
-

Grizzlyflix · Request. Watch. Enjoy.

-
+ ) } diff --git a/frontend/app/new-requests/NewRequestClient.tsx b/frontend/app/new-requests/NewRequestClient.tsx index bfb85e6..20e3d7e 100644 --- a/frontend/app/new-requests/NewRequestClient.tsx +++ b/frontend/app/new-requests/NewRequestClient.tsx @@ -1,5 +1,7 @@ 'use client' +import PageHeading from '../ui/PageHeading' + import { useEffect, useRef, useState } from 'react' import { useRouter } from 'next/navigation' import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth' @@ -318,18 +320,7 @@ export default function NewRequestClient() { return (
-
-
- New requests -

Find something worth watching.

-

Choose what you want, find the right title, then tailor the request before it goes to Seerr.

-
-
- Seerr