Changeset 0.37.14 (#226)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
* extraItems — Additional menu items inserted after surface links
|
||||
*/
|
||||
const { html } = window;
|
||||
const { useState, useRef, useMemo } = hooks;
|
||||
const { useState, useRef, useMemo, useEffect } = hooks;
|
||||
|
||||
import { Avatar } from '../primitives/avatar.js';
|
||||
import { Menu } from '../primitives/menu.js';
|
||||
@@ -27,12 +27,23 @@ function _currentSurface() {
|
||||
|
||||
export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [extSurfaces, setExtSurfaces] = useState([]);
|
||||
const btnRef = useRef(null);
|
||||
|
||||
const sw = window.sw;
|
||||
const user = sw?.auth?.user;
|
||||
const authenticated = sw?.auth?.isAuthenticated;
|
||||
|
||||
// Fetch extension surfaces once on mount
|
||||
useEffect(() => {
|
||||
if (!sw?.api?.surfaces?.list) return;
|
||||
sw.api.surfaces.list().then(data => {
|
||||
const all = data || [];
|
||||
const CORE = new Set(['chat', 'admin', 'notes', 'settings', 'team-admin', 'workflow', 'workflow-landing']);
|
||||
setExtSurfaces(all.filter(s => !CORE.has(s.id)));
|
||||
}).catch(() => {});
|
||||
}, [authenticated]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const current = _currentSurface();
|
||||
const list = [];
|
||||
@@ -44,10 +55,9 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
{ key: 'notes', label: 'Notes', icon: '\ud83d\udcdd' },
|
||||
];
|
||||
|
||||
// Add extension surfaces from page data if available
|
||||
const extSurfaces = window.__EXTENSION_SURFACES__ || [];
|
||||
// Add extension surfaces fetched from API
|
||||
for (const ext of extSurfaces) {
|
||||
surfaces.push({ key: ext.id || ext.route, label: ext.title, icon: '\ud83e\udde9' });
|
||||
surfaces.push({ key: ext.id, label: ext.title, icon: '\ud83e\udde9', route: ext.route });
|
||||
}
|
||||
|
||||
const surfaceItems = surfaces
|
||||
@@ -72,18 +82,18 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
}
|
||||
|
||||
// Admin — requires admin role
|
||||
if ((!authenticated || sw?.isAdmin) && current !== 'admin') {
|
||||
if (authenticated && sw?.isAdmin && current !== 'admin') {
|
||||
list.push({ label: 'Admin', action: 'admin', icon: '\ud83d\udee1\ufe0f' });
|
||||
}
|
||||
|
||||
// Team Admin — requires admin role on at least one team
|
||||
const hasTeamAdmin = sw?.auth?.teams?.some(t => t.my_role === 'admin');
|
||||
if ((!authenticated || hasTeamAdmin) && current !== 'team-admin') {
|
||||
if (authenticated && hasTeamAdmin && current !== 'team-admin') {
|
||||
list.push({ label: 'Team Admin', action: 'team-admin', icon: '\ud83d\udc65' });
|
||||
}
|
||||
|
||||
// Debug — admin only
|
||||
if (!authenticated || sw?.isAdmin) {
|
||||
if (authenticated && sw?.isAdmin) {
|
||||
list.push({ label: 'Debug', action: 'debug', icon: '\ud83d\udc1b' });
|
||||
}
|
||||
|
||||
@@ -91,7 +101,7 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
list.push({ label: 'Sign Out', action: 'sign-out' });
|
||||
|
||||
return list;
|
||||
}, [user, authenticated, extraItems]);
|
||||
}, [user, authenticated, extraItems, extSurfaces]);
|
||||
|
||||
function handleSelect(action) {
|
||||
setOpen(false);
|
||||
@@ -101,7 +111,9 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
const BASE = window.__BASE__ || '';
|
||||
switch (action) {
|
||||
case 'sign-out':
|
||||
window.sw?.auth?.logout();
|
||||
window.sw?.auth?.logout().then(() => {
|
||||
location.href = BASE + '/login';
|
||||
});
|
||||
break;
|
||||
case 'debug':
|
||||
const dbg = document.getElementById('debugModal');
|
||||
@@ -110,8 +122,12 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
case 'chat':
|
||||
location.href = BASE + '/';
|
||||
break;
|
||||
default:
|
||||
location.href = BASE + '/' + action;
|
||||
default: {
|
||||
// Extension surfaces use /s/{id} routes
|
||||
const ext = extSurfaces.find(s => s.id === action);
|
||||
location.href = BASE + (ext?.route || '/' + action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,17 +135,20 @@ export function UserMenu({ placement = 'down-right', onAction, extraItems }) {
|
||||
const avatar = user?.avatar || null;
|
||||
|
||||
return html`
|
||||
<button class="sw-user-menu__trigger" ref=${btnRef}
|
||||
onClick=${() => setOpen(!open)} aria-label="User menu">
|
||||
<${Avatar} src=${avatar} name=${displayName} size="sm" />
|
||||
</button>
|
||||
<${Menu}
|
||||
open=${open}
|
||||
anchor=${btnRef.current}
|
||||
direction=${placement}
|
||||
items=${items}
|
||||
onSelect=${handleSelect}
|
||||
onClose=${() => setOpen(false)}
|
||||
/>
|
||||
<div class="user-menu-wrap">
|
||||
<button class="user-btn" ref=${btnRef}
|
||||
onClick=${() => setOpen(!open)} aria-label="User menu">
|
||||
<${Avatar} src=${avatar} name=${displayName} size="sm" />
|
||||
<span class="sb-label">${displayName}</span>
|
||||
</button>
|
||||
<${Menu}
|
||||
open=${open}
|
||||
anchor=${btnRef.current}
|
||||
direction=${placement}
|
||||
items=${items}
|
||||
onSelect=${handleSelect}
|
||||
onClose=${() => setOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user