142 lines
4.9 KiB
TypeScript
142 lines
4.9 KiB
TypeScript
import { useState, useRef, useEffect } from 'react';
|
|
import { Link } from 'react-router-dom';
|
|
import { useTeam } from '../contexts/TeamContext';
|
|
import { useAuth } from '../contexts/AuthContext';
|
|
import { TeamDetail } from '../types';
|
|
import './TeamSelector.css';
|
|
|
|
export function TeamSelector() {
|
|
const { user } = useAuth();
|
|
const { teams, currentTeam, loading, setCurrentTeam } = useTeam();
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
// Close dropdown when clicking outside
|
|
useEffect(() => {
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
}
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
// Don't show if not authenticated
|
|
if (!user) {
|
|
return null;
|
|
}
|
|
|
|
const handleTeamSelect = (team: TeamDetail) => {
|
|
setCurrentTeam(team);
|
|
setIsOpen(false);
|
|
};
|
|
|
|
const roleColors: Record<string, string> = {
|
|
owner: 'var(--color-success)',
|
|
admin: 'var(--color-primary)',
|
|
member: 'var(--color-text-muted)',
|
|
};
|
|
|
|
return (
|
|
<div className="team-selector" ref={dropdownRef}>
|
|
<button
|
|
className="team-selector-trigger"
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
disabled={loading}
|
|
aria-expanded={isOpen}
|
|
aria-haspopup="listbox"
|
|
>
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
|
|
<circle cx="9" cy="7" r="4"/>
|
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
|
|
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
|
</svg>
|
|
<span className="team-selector-name">
|
|
{loading ? 'Loading...' : currentTeam?.name || 'Select Team'}
|
|
</span>
|
|
<svg
|
|
className={`team-selector-chevron ${isOpen ? 'open' : ''}`}
|
|
width="12"
|
|
height="12"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth="2"
|
|
>
|
|
<polyline points="6 9 12 15 18 9"/>
|
|
</svg>
|
|
</button>
|
|
|
|
{isOpen && (
|
|
<div className="team-selector-dropdown" role="listbox">
|
|
{teams.length === 0 ? (
|
|
<div className="team-selector-empty">
|
|
<p>You're not a member of any teams yet.</p>
|
|
<Link
|
|
to="/teams/new"
|
|
className="team-selector-create-link"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
Create your first team
|
|
</Link>
|
|
</div>
|
|
) : (
|
|
<>
|
|
<ul className="team-selector-list">
|
|
{teams.map(team => (
|
|
<li key={team.id}>
|
|
<button
|
|
className={`team-selector-item ${currentTeam?.id === team.id ? 'selected' : ''}`}
|
|
onClick={() => handleTeamSelect(team)}
|
|
role="option"
|
|
aria-selected={currentTeam?.id === team.id}
|
|
>
|
|
<div className="team-selector-item-info">
|
|
<span className="team-selector-item-name">{team.name}</span>
|
|
<span className="team-selector-item-meta">
|
|
{team.project_count} project{team.project_count !== 1 ? 's' : ''}
|
|
</span>
|
|
</div>
|
|
{team.user_role && (
|
|
<span
|
|
className="team-selector-item-role"
|
|
style={{ color: roleColors[team.user_role] || roleColors.member }}
|
|
>
|
|
{team.user_role}
|
|
</span>
|
|
)}
|
|
{currentTeam?.id === team.id && (
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<polyline points="20 6 9 17 4 12"/>
|
|
</svg>
|
|
)}
|
|
</button>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
<div className="team-selector-footer">
|
|
<Link
|
|
to="/teams"
|
|
className="team-selector-link"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
View all teams
|
|
</Link>
|
|
<Link
|
|
to="/teams/new"
|
|
className="team-selector-link team-selector-link-primary"
|
|
onClick={() => setIsOpen(false)}
|
|
>
|
|
+ New Team
|
|
</Link>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|