Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
80 lines
2.5 KiB
JavaScript
80 lines
2.5 KiB
JavaScript
/**
|
|
* UserMenu — avatar button + flyout menu
|
|
*
|
|
* RBAC-gated items based on sw.can() / sw.isAdmin / sw.isTeamAdmin().
|
|
* Surfaces mount this wherever they want; the shell does not own placement.
|
|
*
|
|
* Props:
|
|
* placement — Menu direction: 'down-right' (default) | 'down-left'
|
|
* onAction — Override handler; if omitted, uses default routing
|
|
*/
|
|
const { html } = window;
|
|
const { useState, useRef, useMemo } = hooks;
|
|
|
|
import { Avatar } from '../primitives/avatar.js';
|
|
import { Menu } from '../primitives/menu.js';
|
|
|
|
export function UserMenu({ placement = 'down-right', onAction }) {
|
|
const [open, setOpen] = useState(false);
|
|
const btnRef = useRef(null);
|
|
|
|
const sw = window.sw;
|
|
const user = sw?.auth?.user;
|
|
const authenticated = sw?.auth?.isAuthenticated;
|
|
|
|
const items = useMemo(() => {
|
|
const list = [
|
|
{ label: 'Settings', action: 'settings', icon: '\u2699\ufe0f' },
|
|
];
|
|
|
|
// Admin — requires admin role
|
|
if (!authenticated || sw?.isAdmin) {
|
|
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) {
|
|
list.push({ label: 'Team Admin', action: 'team-admin', icon: '\ud83d\udc65' });
|
|
}
|
|
|
|
// Debug — admin only
|
|
if (!authenticated || sw?.isAdmin) {
|
|
list.push({ label: 'Debug', action: 'debug', icon: '\ud83d\udc1b' });
|
|
}
|
|
|
|
list.push({ divider: true });
|
|
list.push({ label: 'Sign Out', action: 'sign-out' });
|
|
|
|
return list;
|
|
}, [user, authenticated]);
|
|
|
|
function handleSelect(action) {
|
|
setOpen(false);
|
|
if (onAction) return onAction(action);
|
|
if (action === 'sign-out') {
|
|
sw?.auth?.logout();
|
|
} else {
|
|
sw?.emit('shell.navigate', { target: action });
|
|
}
|
|
}
|
|
|
|
const displayName = user?.display_name || user?.username || 'Demo User';
|
|
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)}
|
|
/>
|
|
`;
|
|
}
|