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 { useTranslation } from '@repo/core-i18n';
|
||||||
import { Burger, Group, Select, Text, useCoreAppShell } from '@repo/ui/components';
|
import {
|
||||||
import { Globe } from 'lucide-react';
|
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';
|
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() {
|
export default function HeaderLayout() {
|
||||||
const { i18n } = useTranslation();
|
const { i18n, t } = useTranslation();
|
||||||
const { mobileOpened, toggleMobile } = useCoreAppShell();
|
const { mobileOpened, toggleMobile } = useCoreAppShell();
|
||||||
|
|
||||||
return (
|
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" />
|
<Burger opened={mobileOpened} onClick={toggleMobile} hiddenFrom="sm" size="sm" />
|
||||||
<Text>LOGO</Text>
|
|
||||||
<Select
|
{/* Contextual Title / Breadcrumb Placeholder */}
|
||||||
size="xs"
|
<Text fw={600} size="sm" c="dimmed" visibleFrom="sm" style={{ letterSpacing: '0.5px' }}>
|
||||||
variant="filled"
|
LOGO
|
||||||
leftSection={<Globe size={16} />}
|
</Text>
|
||||||
data={[
|
</Group>
|
||||||
{ value: 'en', label: 'English' },
|
|
||||||
{ value: 'id', label: 'Bahasa Indonesia' },
|
{/* 3. RIGHT SECTION (Actions & Profile) */}
|
||||||
]}
|
<Group wrap="nowrap" gap="xs">
|
||||||
value={i18n.resolvedLanguage || i18n.language}
|
<ActionIcon variant="subtle" color="gray" size="lg" aria-label="Help">
|
||||||
onChange={async (val) => {
|
<HelpCircle size={18} />
|
||||||
if (!val) return;
|
</ActionIcon>
|
||||||
await i18n.changeLanguage(val);
|
|
||||||
await secureStorage.setItem(AppStorageKey.LOCALE, val);
|
<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>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,16 @@
|
|||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"duplicate": "Duplicate",
|
"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",
|
"delete": "Hapus",
|
||||||
"edit": "Ubah",
|
"edit": "Ubah",
|
||||||
"duplicate": "Duplikat",
|
"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