Add user menu with profile page, card and row hovers, and family breadcrumb fixes

This commit is contained in:
Ian Gulliver
2026-08-15 22:36:19 -07:00
parent fe3ba87d4f
commit c1a648e2bb
5 changed files with 144 additions and 8 deletions
+6 -2
View File
@@ -9,11 +9,15 @@
<link rel="stylesheet" href="/static/fonts/fonts.css">
<link rel="stylesheet" href="/static/directory/style.css">
</head>
<body>
<body data-user-email="{{.UserEmail}}">
<aside class="sidebar">
<div class="brand"><img src="/static/brand/icon-192.png" alt=""><span>Helios Who?</span></div>
<nav id="nav"></nav>
<div class="user"><span class="user-avatar">{{.UserInitial}}</span><span>{{.UserName}}</span></div>
<div class="user-menu" id="user-menu" hidden>
<a href="/profile">View Profile</a>
<form method="post" action="/auth/logout"><button>Sign Out</button></form>
</div>
<div class="user" id="user"><span class="user-avatar">{{.UserInitial}}</span><span>{{.UserName}}</span></div>
</aside>
<main id="main"></main>
<script src="/static/directory/app.js"></script>
+54 -1
View File
@@ -359,6 +359,9 @@ function referrerCrumbs() {
if (seg[0] === 'staff') {
return [['Staff', '/staff']];
}
if (seg[0] === 'profile') {
return [['Profile', '/profile']];
}
return null;
}
@@ -583,7 +586,17 @@ function renderFamilyDetail(key) {
return;
}
const shortName = (family.name || '').replace(/ Family$/, '');
main.append(breadcrumbs([['People', '/people'], [shortName, null], ['Family', null]]));
let crumbs = [['People', '/people'], [shortName, null], ['Family', null]];
if (document.referrer && new URL(document.referrer).origin === location.origin) {
const ref = new URL(document.referrer);
const rseg = ref.pathname.split('/').filter(Boolean).map(decodeURIComponent);
if (rseg[0] === 'people' && rseg[1] && byEmail[rseg[1]]) {
crumbs = [['People', '/people'], [byEmail[rseg[1]].fullName, ref.pathname], ['Family', null]];
} else if (rseg[0] === 'profile') {
crumbs = [['Profile', '/profile'], ['Family', null]];
}
}
main.append(breadcrumbs(crumbs));
const content = el('div', 'container detail-content');
const grid = el('div', 'detail-grid');
@@ -1046,6 +1059,30 @@ function renderClassroomDetail(slug) {
renderRoster(classroom.name, classroom.imageUrl, groups);
}
function renderProfile() {
const main = document.querySelector('#main');
main.replaceChildren();
const me = byEmail[document.body.dataset.userEmail];
if (!me) {
main.append(el('div', 'empty', 'Not found.'));
return;
}
const content = el('div', 'container content');
const family = state.model.families[me.familyKey];
if (family) {
content.append(el('h1', 'profile-heading', 'Family Photo'));
content.append(listRow(thumbUrl(family.photoUrl), '', 'Family Photo', family.photoCaption || '', '/families/' + encodeURIComponent(me.familyKey)));
const kids = (family.kidEmails || []).map(e => byEmail[e]).filter(Boolean);
if (kids.length) {
content.append(el('h1', 'profile-heading', 'Students'));
for (const k of kids) {
content.append(listRow(thumbUrl(k.photoUrl), (k.pronouns || '').toUpperCase(), firstName(k.fullName), '', personLink(k)));
}
}
}
main.append(content);
}
function tabParam(fallback) {
return new URLSearchParams(location.search).get('tab') || fallback;
}
@@ -1072,9 +1109,25 @@ function render() {
} else if (seg[0] === 'staff') {
state.q = '';
renderStaffPage();
} else if (seg[0] === 'profile') {
renderProfile();
}
}
const userMenu = document.querySelector('#user-menu');
document.querySelector('#user').addEventListener('click', e => {
e.stopPropagation();
userMenu.hidden = !userMenu.hidden;
});
document.addEventListener('click', () => {
userMenu.hidden = true;
});
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
userMenu.hidden = true;
}
});
async function load() {
const res = await fetch('/api/directory/model');
if (!res.ok) {
+81
View File
@@ -31,6 +31,7 @@ body {
display: flex;
flex-direction: column;
padding: 12px;
position: relative;
}
.brand {
@@ -101,6 +102,49 @@ nav a.active {
font-size: 14px;
font-weight: 500;
color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
cursor: pointer;
}
.user:hover {
background: rgba(255, 255, 255, 0.06);
}
.user-menu {
position: absolute;
left: 12px;
bottom: 62px;
width: 190px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 18px rgba(0, 0, 0, 0.25);
padding: 6px 0;
z-index: 10;
}
.user-menu form {
margin: 0;
}
.user-menu a,
.user-menu button {
display: block;
width: 100%;
padding: 8px 16px;
border: 0;
background: none;
text-align: left;
text-decoration: none;
font: inherit;
font-size: 14px;
font-weight: 500;
color: var(--ink);
cursor: pointer;
}
.user-menu a:hover,
.user-menu button:hover {
background: #f2f4f6;
}
.user-avatar {
@@ -202,6 +246,16 @@ main {
margin: 0;
}
.profile-heading {
font-size: 24px;
font-weight: 700;
margin: 26px 0 10px;
}
.profile-heading:first-child {
margin-top: 4px;
}
.controls {
display: flex;
gap: 10px;
@@ -427,6 +481,22 @@ a.family-card {
text-decoration: none;
}
a.person-card,
a.family-card {
padding: 14px;
margin: -14px;
border-radius: 12px;
}
a.person-card:hover,
a.family-card:hover {
background: #f6f6f7;
}
a.student-card:hover {
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
.detail-top {
display: flex;
align-items: center;
@@ -593,6 +663,17 @@ a.family-card {
font-weight: 600;
}
a.list-row {
padding-left: 14px;
padding-right: 14px;
margin: 0 -14px;
border-radius: 12px;
}
a.list-row:hover {
background: #f6f6f7;
}
.list-tile {
width: 80px;
height: 80px;