355 lines
15 KiB
TypeScript
355 lines
15 KiB
TypeScript
import { Link, router, usePage } from '@inertiajs/react';
|
|
import {
|
|
ChevronDown,
|
|
ArrowUpRight,
|
|
LogOut,
|
|
Menu,
|
|
UserRound,
|
|
X,
|
|
} from 'lucide-react';
|
|
import { useEffect, useId, useRef, useState } from 'react';
|
|
import { useNavigationContext } from '@/components/navigation-context';
|
|
import { TempoSocialLinks, useTempoUi } from '@/components/tempo-brand';
|
|
import {
|
|
TempoLanguageSelector,
|
|
TempoWeather,
|
|
} from '@/components/tempo-header-tools';
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuSub,
|
|
DropdownMenuSubContent,
|
|
DropdownMenuSubTrigger,
|
|
DropdownMenuTrigger,
|
|
} from '@/components/ui/dropdown-menu';
|
|
import {
|
|
portalNavigation,
|
|
currentNavigationGroup,
|
|
currentNavigationItem,
|
|
} from '@/lib/portal-navigation';
|
|
import type { PortalNavItem } from '@/lib/portal-navigation';
|
|
import type { ProfileAccess } from '@/types/profile-access';
|
|
import type { OpportunityManagementAccess } from '@/types/opportunity-management';
|
|
import { logout } from '@/routes';
|
|
|
|
export function PortalGroupedMenu() {
|
|
const { props, url } = usePage<{
|
|
auth: {
|
|
user: { name?: string; email?: string } | null;
|
|
canManagePortal: boolean;
|
|
};
|
|
existingBusinessEnabled?: boolean;
|
|
businessReviewUrl?: string | null;
|
|
educationEnabled?: boolean;
|
|
aiEnabled?: boolean;
|
|
legacyContentEnabled?: boolean;
|
|
profileAccess?: ProfileAccess | null;
|
|
opportunityManagement?: OpportunityManagementAccess | null;
|
|
}>();
|
|
const { t, language, requestCount } = useTempoUi();
|
|
const navigation = useNavigationContext();
|
|
const groups = portalNavigation({
|
|
context: navigation.view
|
|
? { id: navigation.active, ...navigation.view.tools }
|
|
: undefined,
|
|
authenticated: !!props.auth?.user,
|
|
manager: !!props.auth?.canManagePortal,
|
|
businesses: props.existingBusinessEnabled,
|
|
businessReviewUrl: props.businessReviewUrl,
|
|
education: props.educationEnabled,
|
|
ai: props.aiEnabled,
|
|
legacyContent: props.legacyContentEnabled,
|
|
opportunityManagement: props.opportunityManagement,
|
|
profileAccess: props.profileAccess,
|
|
});
|
|
const current = currentNavigationGroup(groups, url);
|
|
const currentItem = currentNavigationItem(groups, url);
|
|
const [loggingOut, setLoggingOut] = useState(false);
|
|
const [expanded, setExpanded] = useState(false);
|
|
const institutionalId = useId();
|
|
const toolsId = useId();
|
|
const toggle = useRef<HTMLButtonElement>(null);
|
|
useEffect(() => router.on('navigate', () => setExpanded(false)), []);
|
|
const items = (children: PortalNavItem[]) =>
|
|
children.map((item) =>
|
|
item.children ? (
|
|
<DropdownMenuSub key={item.id}>
|
|
<DropdownMenuSubTrigger className="tempo-menu-item font-semibold">
|
|
{t(item.label)}
|
|
</DropdownMenuSubTrigger>
|
|
<DropdownMenuSubContent
|
|
collisionPadding={12}
|
|
className="tempo-dropdown max-h-[min(70vh,34rem)] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
|
>
|
|
{items(item.children)}
|
|
</DropdownMenuSubContent>
|
|
</DropdownMenuSub>
|
|
) : (
|
|
<DropdownMenuItem
|
|
key={item.id}
|
|
asChild
|
|
className="tempo-menu-item"
|
|
>
|
|
<Link
|
|
href={item.href!}
|
|
aria-current={
|
|
currentItem === item.id ? 'page' : undefined
|
|
}
|
|
>
|
|
{t(item.label)}
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
),
|
|
);
|
|
const accountGroups = groups.filter(
|
|
(group) =>
|
|
!['home', 'discovery', 'realization', 'support'].includes(group.id),
|
|
);
|
|
return (
|
|
<header
|
|
className="tempo-header"
|
|
lang={language}
|
|
data-expanded={expanded || undefined}
|
|
onKeyDown={(event) => {
|
|
if (
|
|
event.key === 'Escape' &&
|
|
expanded &&
|
|
!event.defaultPrevented
|
|
) {
|
|
setExpanded(false);
|
|
toggle.current?.focus();
|
|
}
|
|
}}
|
|
>
|
|
<div className="tempo-header-inner">
|
|
<Link
|
|
href="/"
|
|
aria-label="Tempo — início"
|
|
className="tempo-logo-link"
|
|
>
|
|
<img
|
|
src="/images/logo/logo-vermelho.png"
|
|
alt="Tempo"
|
|
width="1787"
|
|
height="564"
|
|
className="tempo-header-logo"
|
|
/>
|
|
</Link>
|
|
<nav
|
|
id={institutionalId}
|
|
aria-label="Navegação institucional"
|
|
className="tempo-primary-navigation"
|
|
>
|
|
<ul>
|
|
<li>
|
|
<Link
|
|
href="/"
|
|
className="tempo-nav-trigger"
|
|
aria-current={
|
|
url.split('?')[0] === '/'
|
|
? 'page'
|
|
: undefined
|
|
}
|
|
data-current-group={
|
|
url.split('?')[0] === '/' || undefined
|
|
}
|
|
>
|
|
{t('Início')}
|
|
</Link>
|
|
</li>
|
|
<li>
|
|
<a
|
|
href="https://revista.tempo.co.mz/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
className="tempo-nav-trigger"
|
|
aria-label={`${t('Revista Tempo')} — ${t('Abre num novo separador')}`}
|
|
>
|
|
{t('Revista Tempo')}
|
|
<ArrowUpRight size={14} aria-hidden="true" />
|
|
</a>
|
|
</li>
|
|
{[
|
|
['/sobre', 'Sobre nós'],
|
|
['/contacto', 'Contacto'],
|
|
].map(([href, label]) => (
|
|
<li key={href}>
|
|
<Link
|
|
href={href}
|
|
className="tempo-nav-trigger"
|
|
aria-current={
|
|
url.split('?')[0] === href
|
|
? 'page'
|
|
: undefined
|
|
}
|
|
data-current-group={
|
|
url.split('?')[0] === href || undefined
|
|
}
|
|
>
|
|
{t(label)}
|
|
</Link>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
<div id={toolsId} className="tempo-header-extras">
|
|
<div className="tempo-header-utilities">
|
|
<div className="flex items-center gap-1">
|
|
<TempoWeather />
|
|
<TempoLanguageSelector />
|
|
</div>
|
|
<TempoSocialLinks />
|
|
</div>
|
|
</div>
|
|
<div className="tempo-header-actions">
|
|
{props.auth?.user ? (
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger className="tempo-login-button">
|
|
<UserRound size={16} aria-hidden="true" />
|
|
<span className="max-w-24 truncate">
|
|
{props.auth.user.name || t('A minha área')}
|
|
</span>
|
|
<ChevronDown size={12} aria-hidden="true" />
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
sideOffset={12}
|
|
collisionPadding={12}
|
|
className="tempo-dropdown max-h-[75vh] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
|
>
|
|
<DropdownMenuLabel className="truncate text-xs text-slate-500">
|
|
{props.auth.user.email}
|
|
</DropdownMenuLabel>
|
|
{items(accountGroups)}
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem
|
|
asChild
|
|
className="tempo-menu-item text-red-700"
|
|
>
|
|
<Link
|
|
href={logout()}
|
|
as="button"
|
|
type="button"
|
|
disabled={loggingOut}
|
|
data-test="portal-logout-button"
|
|
onClick={() => router.flushAll()}
|
|
onStart={() => setLoggingOut(true)}
|
|
onFinish={() => setLoggingOut(false)}
|
|
>
|
|
<LogOut size={16} aria-hidden="true" />
|
|
{loggingOut
|
|
? 'A terminar sessão…'
|
|
: t('Terminar sessão')}
|
|
</Link>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
) : (
|
|
<Link href="/login" className="tempo-login-button">
|
|
<UserRound size={15} aria-hidden="true" />
|
|
{t('Entrar')}
|
|
</Link>
|
|
)}
|
|
</div>
|
|
<button
|
|
ref={toggle}
|
|
type="button"
|
|
className="tempo-header-toggle"
|
|
aria-label={
|
|
expanded
|
|
? 'Fechar menu e ferramentas'
|
|
: 'Abrir menu e ferramentas'
|
|
}
|
|
aria-expanded={expanded}
|
|
aria-controls={`${institutionalId} ${toolsId}`}
|
|
onClick={() => setExpanded(!expanded)}
|
|
>
|
|
{expanded ? (
|
|
<X size={22} aria-hidden="true" />
|
|
) : (
|
|
<Menu size={22} aria-hidden="true" />
|
|
)}
|
|
</button>
|
|
</div>
|
|
<div className="tempo-section-bar">
|
|
<div className="tempo-section-inner">
|
|
<Link
|
|
href="/investidor/manifestacoes"
|
|
className="tempo-panel-summary"
|
|
>
|
|
<span>{t('O meu painel')}</span>
|
|
<span
|
|
className="tempo-panel-count"
|
|
aria-live="polite"
|
|
aria-atomic="true"
|
|
>
|
|
{requestCount}{' '}
|
|
{t(
|
|
requestCount === 1
|
|
? 'pedido recebido'
|
|
: 'pedidos recebidos',
|
|
)}
|
|
</span>
|
|
</Link>
|
|
<nav
|
|
aria-label="Áreas do portal"
|
|
className="tempo-section-navigation"
|
|
>
|
|
<ul>
|
|
{groups
|
|
.filter((group) =>
|
|
[
|
|
'discovery',
|
|
'realization',
|
|
'support',
|
|
].includes(group.id),
|
|
)
|
|
.map((group) => (
|
|
<li key={group.id}>
|
|
<DropdownMenu modal={false}>
|
|
<DropdownMenuTrigger
|
|
className="tempo-nav-trigger"
|
|
data-current-group={
|
|
current === group.id ||
|
|
undefined
|
|
}
|
|
>
|
|
{t(group.label)}
|
|
<ChevronDown
|
|
size={14}
|
|
aria-hidden="true"
|
|
/>
|
|
{current === group.id && (
|
|
<span className="sr-only">
|
|
Grupo actual
|
|
</span>
|
|
)}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="start"
|
|
sideOffset={8}
|
|
collisionPadding={12}
|
|
className="tempo-dropdown max-h-[min(75vh,36rem)] w-72 max-w-[calc(100vw-1.5rem)] overflow-y-auto p-2"
|
|
>
|
|
{items(group.children ?? [])}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</nav>
|
|
<Link
|
|
href="/manifestar-interesse"
|
|
className="tempo-interest-button"
|
|
>
|
|
{t('Manifestar interesse')}
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|