Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c45dd0065 |
@@ -300,7 +300,7 @@ export default function SettingsPage({ section }: SettingsPageProps) {
|
||||
requests_data_source: 'Pick where Magent should read requests from.',
|
||||
log_level: 'How much detail is written to the activity log.',
|
||||
log_file: 'Where the activity log is stored.',
|
||||
site_build_number: 'Build number shown in the footer (auto-set from releases).',
|
||||
site_build_number: 'Build number shown in the account menu (auto-set from releases).',
|
||||
site_banner_enabled: 'Enable a sitewide banner for announcements.',
|
||||
site_banner_message: 'Short banner message for maintenance or updates.',
|
||||
site_banner_tone: 'Visual tone for the banner.',
|
||||
|
||||
@@ -175,30 +175,35 @@ body {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.signed-in {
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--ink-muted);
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
border: 1px dashed var(--border);
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signed-in-menu {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.avatar-button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: linear-gradient(130deg, rgba(28, 107, 255, 0.35), rgba(17, 214, 198, 0.25));
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 10px 20px rgba(28, 107, 255, 0.25);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signed-in-dropdown {
|
||||
position: absolute;
|
||||
top: calc(100% + 8px);
|
||||
right: 0;
|
||||
min-width: 180px;
|
||||
background: rgba(14, 20, 32, 0.95);
|
||||
width: min(260px, 90vw);
|
||||
background: rgba(14, 20, 32, 0.96);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
@@ -206,17 +211,50 @@ body {
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
.signed-in-dropdown a {
|
||||
.signed-in-header {
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
padding: 8px 10px 6px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.signed-in-actions {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 8px 4px 4px;
|
||||
}
|
||||
|
||||
.signed-in-actions a,
|
||||
.signed-in-signout {
|
||||
display: block;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.signed-in-dropdown a:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
.signed-in-signout {
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.signed-in-actions a:hover,
|
||||
.signed-in-signout:hover {
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
.signed-in-build {
|
||||
margin-top: 6px;
|
||||
padding: 6px 10px 8px;
|
||||
font-size: 11px;
|
||||
color: var(--ink-muted);
|
||||
text-align: left;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
@@ -1446,21 +1484,18 @@ button span {
|
||||
}
|
||||
|
||||
.signed-in-menu {
|
||||
width: 100%;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.signed-in {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
.avatar-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.signed-in-dropdown {
|
||||
position: static;
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
right: 0;
|
||||
left: auto;
|
||||
width: min(260px, 92vw);
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
|
||||
@@ -29,8 +29,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
|
||||
</a>
|
||||
</div>
|
||||
<div className="header-right">
|
||||
<HeaderIdentity />
|
||||
<ThemeToggle />
|
||||
<HeaderIdentity />
|
||||
</div>
|
||||
<div className="header-nav">
|
||||
<HeaderActions />
|
||||
|
||||
@@ -32,14 +32,6 @@ export default function HeaderActions() {
|
||||
void load()
|
||||
}, [])
|
||||
|
||||
const signOut = () => {
|
||||
clearToken()
|
||||
setSignedIn(false)
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
if (!signedIn) {
|
||||
return null
|
||||
}
|
||||
@@ -49,12 +41,7 @@ export default function HeaderActions() {
|
||||
<a className="header-cta header-cta--left" href="/feedback">Send feedback</a>
|
||||
<a href="/">Requests</a>
|
||||
<a href="/how-it-works">How it works</a>
|
||||
<a href="/changelog">Changelog</a>
|
||||
<a href="/profile">My profile</a>
|
||||
{role === 'admin' && <a href="/admin">Settings</a>}
|
||||
<button type="button" className="header-link" onClick={signOut}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,13 +4,15 @@ import { useEffect, useState } from 'react'
|
||||
import { authFetch, clearToken, getApiBase, getToken } from '../lib/auth'
|
||||
|
||||
export default function HeaderIdentity() {
|
||||
const [identity, setIdentity] = useState<string | null>(null)
|
||||
const [identity, setIdentity] = useState<{ username: string; role?: string } | null>(null)
|
||||
const [buildNumber, setBuildNumber] = useState<string | null>(null)
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const token = getToken()
|
||||
if (!token) {
|
||||
setIdentity(null)
|
||||
setBuildNumber(null)
|
||||
return
|
||||
}
|
||||
const load = async () => {
|
||||
@@ -24,7 +26,14 @@ export default function HeaderIdentity() {
|
||||
}
|
||||
const data = await response.json()
|
||||
if (data?.username) {
|
||||
setIdentity(`${data.username}${data.role ? ` (${data.role})` : ''}`)
|
||||
setIdentity({ username: data.username, role: data.role })
|
||||
}
|
||||
const siteResponse = await fetch(`${baseUrl}/site/public`)
|
||||
if (siteResponse.ok) {
|
||||
const siteInfo = await siteResponse.json()
|
||||
if (siteInfo?.buildNumber) {
|
||||
setBuildNumber(siteInfo.buildNumber)
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@@ -38,14 +47,42 @@ export default function HeaderIdentity() {
|
||||
return null
|
||||
}
|
||||
|
||||
const label = `${identity.username}${identity.role ? ` (${identity.role})` : ''}`
|
||||
const initial = identity.username.slice(0, 1).toUpperCase()
|
||||
const signOut = () => {
|
||||
clearToken()
|
||||
if (typeof window !== 'undefined') {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="signed-in-menu">
|
||||
<button type="button" className="signed-in" onClick={() => setOpen((prev) => !prev)}>
|
||||
Signed in as {identity}
|
||||
<button
|
||||
type="button"
|
||||
className="avatar-button"
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open}
|
||||
title={label}
|
||||
>
|
||||
{initial}
|
||||
</button>
|
||||
{open && (
|
||||
<div className="signed-in-dropdown">
|
||||
<a href="/profile">My profile</a>
|
||||
<div className="signed-in-header">Signed in as {label}</div>
|
||||
<div className="signed-in-actions">
|
||||
<a href="/profile" onClick={() => setOpen(false)}>
|
||||
My profile
|
||||
</a>
|
||||
<a href="/changelog" onClick={() => setOpen(false)}>
|
||||
Changelog
|
||||
</a>
|
||||
<button type="button" className="signed-in-signout" onClick={signOut}>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
{buildNumber ? <div className="signed-in-build">Build {buildNumber}</div> : null}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -57,9 +57,6 @@ export default function SiteStatus() {
|
||||
{banner?.enabled && banner.message ? (
|
||||
<div className={`site-banner site-banner--${tone}`}>{banner.message}</div>
|
||||
) : null}
|
||||
{info?.buildNumber ? (
|
||||
<div className="site-version">Build {info.buildNumber}</div>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user