109 lines
6.3 KiB
JavaScript
109 lines
6.3 KiB
JavaScript
import { useState, useRef, useEffect } from 'react';
|
|
import { useAuth } from '../contexts/AuthContext.jsx';
|
|
import Avatar from './Avatar.jsx';
|
|
|
|
function useTheme() {
|
|
const [dark, setDark] = useState(() => localStorage.getItem('rosterchirp-theme') === 'dark');
|
|
useEffect(() => {
|
|
document.documentElement.setAttribute('data-theme', dark ? 'dark' : 'light');
|
|
localStorage.setItem('rosterchirp-theme', dark ? 'dark' : 'light');
|
|
}, [dark]);
|
|
return [dark, setDark];
|
|
}
|
|
|
|
export default function UserFooter({ onProfile, onHelp, onAbout, mobileCompact=false }) {
|
|
const { user, logout } = useAuth();
|
|
const [showMenu, setShowMenu] = useState(false);
|
|
const [dark, setDark] = useTheme();
|
|
const menuRef = useRef(null);
|
|
const btnRef = useRef(null);
|
|
|
|
useEffect(() => {
|
|
if (!showMenu) return;
|
|
const handler = (e) => {
|
|
if (menuRef.current && !menuRef.current.contains(e.target) &&
|
|
btnRef.current && !btnRef.current.contains(e.target)) {
|
|
setShowMenu(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handler);
|
|
return () => document.removeEventListener('mousedown', handler);
|
|
}, [showMenu]);
|
|
|
|
const handleLogout = async () => { await logout(); };
|
|
|
|
if (mobileCompact) return (
|
|
<div style={{ position:'relative' }}>
|
|
<button ref={btnRef} onClick={() => setShowMenu(!showMenu)} style={{ background:'none',border:'none',cursor:'pointer',padding:2,display:'flex',alignItems:'center' }}>
|
|
<Avatar user={user} size="sm" />
|
|
</button>
|
|
{showMenu && (
|
|
<div ref={menuRef} style={{ position:'absolute',right:0,top:'calc(100% + 4px)',background:'var(--surface)',border:'1px solid var(--border)',borderRadius:'var(--radius)',boxShadow:'0 4px 16px rgba(0,0,0,0.15)',minWidth:180,zIndex:200 }}>
|
|
<div style={{ padding:'10px 14px',borderBottom:'1px solid var(--border)',fontSize:13,fontWeight:600 }}>{user?.display_name||user?.name}</div>
|
|
{[['Profile',()=>{setShowMenu(false);onProfile?.();}],['Help',()=>{setShowMenu(false);onHelp?.();}],['About',()=>{setShowMenu(false);onAbout?.();}]].map(([label,action])=>(
|
|
<button key={label} onClick={action} style={{ display:'block',width:'100%',padding:'11px 14px',textAlign:'left',fontSize:14,background:'none',border:'none',cursor:'pointer',color:'var(--text-primary)' }}
|
|
onMouseEnter={e=>e.currentTarget.style.background='var(--background)'} onMouseLeave={e=>e.currentTarget.style.background=''}>{label}</button>
|
|
))}
|
|
<div style={{ borderTop:'1px solid var(--border)' }}>
|
|
<button onClick={handleLogout} style={{ display:'block',width:'100%',padding:'11px 14px',textAlign:'left',fontSize:14,background:'none',border:'none',cursor:'pointer',color:'var(--error)' }}>Sign out</button>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<div className="sidebar-footer">
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 4 }}>
|
|
<button ref={btnRef} className="user-footer-btn" style={{ flex: 1 }} onClick={() => setShowMenu(!showMenu)}>
|
|
<Avatar user={user} size="sm" />
|
|
<div className="flex-col flex-1 overflow-hidden" style={{ textAlign: 'left' }}>
|
|
<span className="font-medium text-sm truncate">{user?.display_name || user?.name}</span>
|
|
<span className="text-xs truncate" style={{ color: 'var(--text-secondary)' }}>{user?.role}</span>
|
|
</div>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/>
|
|
</svg>
|
|
</button>
|
|
<button className="btn-icon" onClick={() => setDark(d => !d)} title={dark ? 'Light mode' : 'Dark mode'} style={{ flexShrink: 0, padding: 8 }}>
|
|
{dark ? (
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<circle cx="12" cy="12" r="5"/>
|
|
<line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/>
|
|
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/>
|
|
<line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/>
|
|
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/>
|
|
</svg>
|
|
) : (
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>
|
|
</svg>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{showMenu && (
|
|
<div ref={menuRef} className="footer-menu">
|
|
<button className="footer-menu-item" onClick={() => { setShowMenu(false); onProfile?.(); }}>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
|
Profile
|
|
</button>
|
|
<button className="footer-menu-item" onClick={() => { setShowMenu(false); onHelp?.(); }}>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>
|
|
Help
|
|
</button>
|
|
<button className="footer-menu-item" onClick={() => { setShowMenu(false); onAbout?.(); }}>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
About
|
|
</button>
|
|
<hr className="divider" style={{ margin: '4px 0' }} />
|
|
<button className="footer-menu-item danger" onClick={handleLogout}>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|