- Add auth routes: /auth/login, /auth/callback, /auth/logout - Add OAuth2 flow with Keycloak using HTTP-only cookies - Add web auth dependencies with role checking - Add profile page (read-only) at /web/profile - Update header with user menu (sign in/out, profile) - Filter posts based on user permissions (hide drafts from guests) - Conditionally show/hide create/edit/delete buttons - Add authorization rules documentation to AGENTS.md - Secure post editing/deletion endpoints with auth checks - Add can_edit, can_delete flags to templates
175 lines
6.6 KiB
HTML
175 lines
6.6 KiB
HTML
<header class="site-header" data-testid="site-header">
|
|
<div class="container" data-testid="header-container">
|
|
<a href="/" class="site-logo" data-testid="nav-logo">
|
|
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" data-testid="logo-icon">
|
|
<rect width="32" height="32" rx="6" fill="var(--color-primary)"/>
|
|
<path d="M8 12h16M8 16h12M8 20h8" stroke="white" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
<span data-testid="logo-text">Blog</span>
|
|
</a>
|
|
|
|
{% include "partials/nav.html" %}
|
|
|
|
<div class="header-actions" data-testid="header-actions">
|
|
<button
|
|
type="button"
|
|
class="theme-toggle"
|
|
data-testid="theme-toggle"
|
|
aria-label="Toggle dark mode"
|
|
title="Toggle dark mode"
|
|
>
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" data-testid="theme-light-icon" style="display: none;">
|
|
<path d="M10 2v2M10 16v2M4.22 4.22l1.42 1.42M14.36 14.36l1.42 1.42M2 10h2M16 10h2M4.22 15.78l1.42-1.42M14.36 5.64l1.42-1.42" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<circle cx="10" cy="10" r="3" stroke="currentColor" stroke-width="2"/>
|
|
</svg>
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" data-testid="theme-dark-icon" style="display: none;">
|
|
<path d="M17.293 13.293A8 8 0 116.707 2.707a8.003 8.003 0 0010.586 10.586z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
|
|
{% if user %}
|
|
<div class="user-menu" data-testid="user-menu">
|
|
<button
|
|
type="button"
|
|
class="user-menu-toggle"
|
|
data-testid="user-menu-toggle"
|
|
aria-haspopup="true"
|
|
aria-expanded="false"
|
|
>
|
|
<span class="avatar avatar-sm" data-testid="user-avatar">{{ user.username[0]|upper }}</span>
|
|
<span class="user-name" data-testid="user-name">{{ user.username }}</span>
|
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-left: 0.25rem;">
|
|
<path d="M2 4L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
<div class="user-menu-dropdown" data-testid="user-menu-dropdown">
|
|
<a href="/web/profile" class="user-menu-item" data-testid="user-menu-profile">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<circle cx="8" cy="6" r="3" stroke="currentColor" stroke-width="2"/>
|
|
<path d="M2 14c0-3 3-5 6-5s6 2 6 5" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
|
</svg>
|
|
Profile
|
|
</a>
|
|
{% if can_create %}
|
|
<a href="/web/posts/new" class="user-menu-item" data-testid="user-menu-new-post">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M8 2v12M2 8h12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
New Post
|
|
</a>
|
|
{% endif %}
|
|
<div class="user-menu-divider" data-testid="user-menu-divider"></div>
|
|
<a href="/auth/logout" class="user-menu-item user-menu-item-danger" data-testid="user-menu-logout">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 0.5rem;">
|
|
<path d="M10 12h2a2 2 0 002-2V6a2 2 0 00-2-2h-2M6 12l-3-3m0 0l3-3m-3 3h8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
Sign Out
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<a href="/auth/login" class="btn btn-primary btn-sm" data-testid="btn-login">
|
|
Sign In
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<style>
|
|
.user-menu {
|
|
position: relative;
|
|
}
|
|
|
|
.user-menu-toggle {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.375rem 0.75rem;
|
|
background: transparent;
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 6px;
|
|
color: var(--color-text);
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.user-menu-toggle:hover {
|
|
background-color: var(--color-hover);
|
|
border-color: var(--color-secondary-dark-2);
|
|
}
|
|
|
|
.user-name {
|
|
font-weight: 500;
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.user-menu-dropdown {
|
|
position: absolute;
|
|
top: 100%;
|
|
right: 0;
|
|
margin-top: 0.5rem;
|
|
min-width: 180px;
|
|
background-color: var(--color-box-body);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px var(--color-shadow);
|
|
opacity: 0;
|
|
visibility: hidden;
|
|
transform: translateY(-10px);
|
|
transition: all 0.2s ease;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.user-menu:hover .user-menu-dropdown,
|
|
.user-menu-toggle:focus + .user-menu-dropdown,
|
|
.user-menu-dropdown:hover {
|
|
opacity: 1;
|
|
visibility: visible;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.user-menu-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0.75rem 1rem;
|
|
color: var(--color-text);
|
|
text-decoration: none;
|
|
font-size: 0.875rem;
|
|
transition: background-color 0.2s ease;
|
|
}
|
|
|
|
.user-menu-item:first-child {
|
|
border-radius: 8px 8px 0 0;
|
|
}
|
|
|
|
.user-menu-item:last-child {
|
|
border-radius: 0 0 8px 8px;
|
|
}
|
|
|
|
.user-menu-item:hover {
|
|
background-color: var(--color-hover);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.user-menu-item-danger {
|
|
color: var(--color-red);
|
|
}
|
|
|
|
.user-menu-item-danger:hover {
|
|
background-color: var(--color-error-bg);
|
|
}
|
|
|
|
.user-menu-divider {
|
|
height: 1px;
|
|
background-color: var(--color-border);
|
|
margin: 0.25rem 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.user-name {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|