Initial commit: portal

This commit is contained in:
jtricerolph 2026-07-01 12:09:54 +00:00
commit b54081113b
18 changed files with 736 additions and 0 deletions

View file

@ -0,0 +1,89 @@
import { NavLink, useNavigate } from 'react-router-dom'
import type { User } from '../types'
interface Props { user: User; activeSlug?: string }
export function Sidebar({ user, activeSlug }: Props) {
const navigate = useNavigate()
async function logout() {
await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' })
navigate('/login', { replace: true })
}
return (
<aside style={{
width: 'var(--sidebar-w)', flexShrink: 0,
background: 'var(--navy)', borderRight: '1px solid var(--surface-2)',
display: 'flex', flexDirection: 'column', height: '100dvh',
position: 'sticky', top: 0,
}}>
<div style={{ padding: '1.25rem 1rem 1rem', borderBottom: '1px solid var(--surface-2)' }}>
<div style={{ color: 'var(--gold)', fontWeight: 700, fontSize: '0.9rem', letterSpacing: '0.05em' }}>
HNF MANAGE
</div>
<div style={{ color: 'var(--text-muted)', fontSize: '0.75rem', marginTop: '0.2rem' }}>
Hotel Number Four
</div>
</div>
<nav style={{ flex: 1, overflowY: 'auto', padding: '0.5rem 0' }}>
<NavLink to="/" end style={({ isActive }) => navItem(isActive && !activeSlug)}>
🏠 Dashboard
</NavLink>
{user.apps.length > 0 && (
<div style={{ padding: '0.5rem 1rem 0.25rem', fontSize: '0.65rem',
color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
Apps
</div>
)}
{user.apps.map(app => (
<NavLink key={app.slug} to={`/app/${app.slug}`}
style={({ isActive }) => navItem(isActive)}>
<span style={{ fontSize: '1rem' }}>{app.icon}</span>
<span style={{ marginLeft: '0.5rem' }}>{app.name}</span>
</NavLink>
))}
{user.is_admin && (
<>
<div style={{ padding: '0.5rem 1rem 0.25rem', fontSize: '0.65rem',
color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em',
marginTop: '0.5rem' }}>
Admin
</div>
<NavLink to="/admin/users" style={({ isActive }) => navItem(isActive)}>
👥 Users
</NavLink>
<NavLink to="/admin/monitor" style={({ isActive }) => navItem(isActive)}>
📡 Monitor
</NavLink>
</>
)}
</nav>
<div style={{ padding: '0.875rem 1rem', borderTop: '1px solid var(--surface-2)' }}>
<div style={{ fontSize: '0.8rem', fontWeight: 600, marginBottom: '0.1rem' }}>{user.name}</div>
<div style={{ fontSize: '0.7rem', color: 'var(--text-muted)', marginBottom: '0.6rem' }}>{user.email}</div>
<button onClick={logout} style={{
background: 'none', border: '1px solid var(--surface-2)', borderRadius: '5px',
color: 'var(--text-muted)', padding: '0.3rem 0.75rem', fontSize: '0.75rem', width: '100%',
}}>
Sign out
</button>
</div>
</aside>
)
}
function navItem(active: boolean): React.CSSProperties {
return {
display: 'flex', alignItems: 'center', padding: '0.55rem 1rem',
fontSize: '0.875rem', color: active ? 'var(--gold)' : 'var(--text-muted)',
background: active ? 'var(--surface)' : 'transparent',
borderLeft: active ? '3px solid var(--gold)' : '3px solid transparent',
transition: 'color 0.1s, background 0.1s',
}
}