feat: enhance HeaderLayout with user profile menu and notifications, update translations for common terms
This commit is contained in:
@@ -1,31 +1,162 @@
|
||||
import { useTranslation } from '@repo/core-i18n';
|
||||
import { Burger, Group, Select, Text, useCoreAppShell } from '@repo/ui/components';
|
||||
import { Globe } from 'lucide-react';
|
||||
import {
|
||||
Burger,
|
||||
Group,
|
||||
Select,
|
||||
Text,
|
||||
useCoreAppShell,
|
||||
ActionIcon,
|
||||
Indicator,
|
||||
Menu,
|
||||
Avatar,
|
||||
UnstyledButton,
|
||||
Box,
|
||||
} from '@repo/ui/components';
|
||||
import { Globe, Bell, HelpCircle, Settings, User, Briefcase, LogOut, ChevronDown } from 'lucide-react';
|
||||
import { AppStorageKey, secureStorage } from '../../../../core/storage/local';
|
||||
|
||||
// Dummy user data for preview
|
||||
const USER = {
|
||||
name: 'Jane Doe',
|
||||
email: 'jane.doe@enterprise.com',
|
||||
role: 'System Administrator',
|
||||
// avatar: null as string | null, // Simulated null for testing fallback
|
||||
avatar: 'https://i.pravatar.cc/150?u=jane.doe',
|
||||
};
|
||||
|
||||
const getInitials = (name: string): string => {
|
||||
const parts = name.trim().split(/\s+/);
|
||||
if (parts.length === 0) return '';
|
||||
if (parts.length === 1) return parts[0].substring(0, 2).toUpperCase();
|
||||
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
||||
};
|
||||
|
||||
export default function HeaderLayout() {
|
||||
const { i18n } = useTranslation();
|
||||
const { i18n, t } = useTranslation();
|
||||
const { mobileOpened, toggleMobile } = useCoreAppShell();
|
||||
|
||||
return (
|
||||
<Group h="100%" px="md" justify="space-between">
|
||||
<Group>
|
||||
<Group
|
||||
h="100%"
|
||||
px="md"
|
||||
justify="space-between"
|
||||
bg="var(--mantine-color-body)"
|
||||
style={{ borderBottom: '1px solid var(--mantine-color-default-border)' }}
|
||||
wrap="nowrap"
|
||||
>
|
||||
{/* 1. LEFT SECTION (Navigation & Context) */}
|
||||
<Group wrap="nowrap" gap="sm">
|
||||
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||
<Text>LOGO</Text>
|
||||
<Select
|
||||
size="xs"
|
||||
variant="filled"
|
||||
leftSection={<Globe size={16} />}
|
||||
data={[
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'id', label: 'Bahasa Indonesia' },
|
||||
]}
|
||||
value={i18n.resolvedLanguage || i18n.language}
|
||||
onChange={async (val) => {
|
||||
if (!val) return;
|
||||
await i18n.changeLanguage(val);
|
||||
await secureStorage.setItem(AppStorageKey.LOCALE, val);
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Contextual Title / Breadcrumb Placeholder */}
|
||||
<Text fw={600} size="sm" c="dimmed" visibleFrom="sm" style={{ letterSpacing: '0.5px' }}>
|
||||
LOGO
|
||||
</Text>
|
||||
</Group>
|
||||
|
||||
{/* 3. RIGHT SECTION (Actions & Profile) */}
|
||||
<Group wrap="nowrap" gap="xs">
|
||||
<ActionIcon variant="subtle" color="gray" size="lg" aria-label="Help">
|
||||
<HelpCircle size={18} />
|
||||
</ActionIcon>
|
||||
|
||||
<Indicator inline size={9} offset={4} color="red" withBorder>
|
||||
<ActionIcon variant="subtle" color="gray" size="lg" aria-label="Notifications">
|
||||
<Bell size={18} />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
|
||||
<Menu shadow="md" width={260} position="bottom-end" radius="md" withArrow>
|
||||
<Menu.Target>
|
||||
<UnstyledButton
|
||||
style={{
|
||||
padding: '4px',
|
||||
borderRadius: 'var(--mantine-radius-xl)',
|
||||
transition: 'background-color 150ms ease',
|
||||
}}
|
||||
>
|
||||
<Group gap="sm" wrap="nowrap">
|
||||
<Avatar src={USER.avatar} size={28} radius="xl" color="blue">
|
||||
{getInitials(USER.name)}
|
||||
</Avatar>
|
||||
|
||||
<Box visibleFrom="sm" style={{ textAlign: 'left' }}>
|
||||
<Text size="sm" fw={600} lh={1.1} maw={140} truncate="end">
|
||||
{USER.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" maw={140} truncate="end">
|
||||
{USER.role}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<ChevronDown size={14} style={{ opacity: 0.5 }} className="mantine-visible-from-sm" />
|
||||
</Group>
|
||||
</UnstyledButton>
|
||||
</Menu.Target>
|
||||
|
||||
<Menu.Dropdown>
|
||||
<Box p="xs" hiddenFrom="sm">
|
||||
<Text size="sm" fw={600} truncate="end">
|
||||
{USER.name}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" truncate="end">
|
||||
{USER.email}
|
||||
</Text>
|
||||
</Box>
|
||||
<Menu.Divider hiddenFrom="sm" />
|
||||
|
||||
<Box px="sm">
|
||||
<Menu.Label>{t('common:preferences', { ns: 'common' })}</Menu.Label>
|
||||
<Group px="sm" justify="space-between" wrap="nowrap">
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<Globe size={14} style={{ opacity: 0.8 }} />
|
||||
<Text size="sm">{t('common:language', { ns: 'common' })}</Text>
|
||||
</Group>
|
||||
<Select
|
||||
size="xs"
|
||||
variant="unstyled"
|
||||
w={110}
|
||||
data={[
|
||||
{ value: 'en', label: t('common:english', { ns: 'common' }) },
|
||||
{ value: 'id', label: t('common:indonesian', { ns: 'common' }) },
|
||||
]}
|
||||
value={i18n.resolvedLanguage || i18n.language}
|
||||
onChange={async (val) => {
|
||||
if (!val) return;
|
||||
await i18n.changeLanguage(val);
|
||||
await secureStorage.setItem(AppStorageKey.LOCALE, val);
|
||||
}}
|
||||
styles={{ input: { textAlign: 'right' } }}
|
||||
/>
|
||||
</Group>
|
||||
</Box>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
<Box px="sm">
|
||||
<Menu.Label>{t('common:application', { ns: 'common' })}</Menu.Label>
|
||||
<Menu.Item leftSection={<Settings size={14} />}>
|
||||
{t('common:accountSettings', { ns: 'common' })}
|
||||
</Menu.Item>
|
||||
<Menu.Item leftSection={<User size={14} />}>{t('common:myProfile', { ns: 'common' })}</Menu.Item>
|
||||
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Label>{t('common:workspace', { ns: 'common' })}</Menu.Label>
|
||||
<Menu.Item leftSection={<Briefcase size={14} />}>
|
||||
{t('common:switchWorkspace', { ns: 'common' })}
|
||||
</Menu.Item>
|
||||
</Box>
|
||||
|
||||
<Box px="sm">
|
||||
<Menu.Divider />
|
||||
|
||||
<Menu.Item color="red" leftSection={<LogOut size={14} />}>
|
||||
{t('common:signOut', { ns: 'common' })}
|
||||
</Menu.Item>
|
||||
</Box>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
</Group>
|
||||
</Group>
|
||||
);
|
||||
|
||||
@@ -9,6 +9,16 @@
|
||||
"delete": "Delete",
|
||||
"edit": "Edit",
|
||||
"duplicate": "Duplicate",
|
||||
"view": "View"
|
||||
"view": "View",
|
||||
"preferences": "Preferences",
|
||||
"language": "Language",
|
||||
"application": "Application",
|
||||
"accountSettings": "Account Settings",
|
||||
"myProfile": "My Profile",
|
||||
"workspace": "Workspace",
|
||||
"switchWorkspace": "Switch Workspace",
|
||||
"signOut": "Sign Out",
|
||||
"english": "English",
|
||||
"indonesian": "Indonesia"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,16 @@
|
||||
"delete": "Hapus",
|
||||
"edit": "Ubah",
|
||||
"duplicate": "Duplikat",
|
||||
"view": "Lihat"
|
||||
"view": "Lihat",
|
||||
"preferences": "Preferensi",
|
||||
"language": "Bahasa",
|
||||
"application": "Aplikasi",
|
||||
"accountSettings": "Pengaturan Akun",
|
||||
"myProfile": "Profil Saya",
|
||||
"workspace": "Ruang Kerja",
|
||||
"switchWorkspace": "Ganti Ruang Kerja",
|
||||
"signOut": "Keluar",
|
||||
"english": "Inggris",
|
||||
"indonesian": "Indonesia"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user