mirror of
https://github.com/Tria-plc/emaui.git
synced 2026-08-26 19:12:50 +00:00
added internationalization for backoffice
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useState, type ElementType, type ReactNode } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Paper,
|
||||
Text,
|
||||
@@ -64,21 +65,25 @@ const ROLES = [
|
||||
{ name: 'Guest', value: 10, color: '#93c5fd' },
|
||||
];
|
||||
|
||||
const KPIS = [
|
||||
{ label: 'Total Users', value: '9,431', icon: IconUsers, color: 'blue', trend: '+12.5%', up: true },
|
||||
{ label: 'Active Users', value: '7,218', icon: IconUserCheck, color: 'indigo', trend: '+8.2%', up: true },
|
||||
{ label: 'New This Month', value: '642', icon: IconUserPlus, color: 'cyan', trend: '+23.1%', up: true },
|
||||
{ label: 'Pending Approvals', value: '37', icon: IconClockHour4, color: 'orange', trend: '-4.0%', up: false },
|
||||
];
|
||||
function useKpis(t: (key: string) => string) {
|
||||
return [
|
||||
{ label: t('dashboard.kpis.totalUsers'), value: '9,431', icon: IconUsers, color: 'blue', trend: '+12.5%', up: true },
|
||||
{ label: t('dashboard.kpis.activeUsers'), value: '7,218', icon: IconUserCheck, color: 'indigo', trend: '+8.2%', up: true },
|
||||
{ label: t('dashboard.kpis.newThisMonth'), value: '642', icon: IconUserPlus, color: 'cyan', trend: '+23.1%', up: true },
|
||||
{ label: t('dashboard.kpis.pendingApprovals'), value: '37', icon: IconClockHour4, color: 'orange', trend: '-4.0%', up: false },
|
||||
];
|
||||
}
|
||||
|
||||
const QUICK_LINKS = [
|
||||
{ label: 'Add User', desc: 'Create a new account', icon: IconUserPlus, color: 'blue' },
|
||||
{ label: 'Roles & Permissions', desc: 'Manage access levels', icon: IconShieldLock, color: 'indigo' },
|
||||
{ label: 'Invite Members', desc: 'Send email invites', icon: IconMail, color: 'cyan' },
|
||||
{ label: 'Import Users', desc: 'Bulk CSV import', icon: IconUpload, color: 'violet' },
|
||||
{ label: 'Export Data', desc: 'Download reports', icon: IconDownload, color: 'blue' },
|
||||
{ label: 'Audit Log', desc: 'Review activity', icon: IconClipboardList, color: 'grape' },
|
||||
];
|
||||
function useQuickLinks(t: (key: string) => string) {
|
||||
return [
|
||||
{ label: t('dashboard.quickLinks.addUser'), desc: t('dashboard.quickLinks.addUserDesc'), icon: IconUserPlus, color: 'blue' },
|
||||
{ label: t('dashboard.quickLinks.rolesAndPermissions'), desc: t('dashboard.quickLinks.rolesAndPermissionsDesc'), icon: IconShieldLock, color: 'indigo' },
|
||||
{ label: t('dashboard.quickLinks.inviteMembers'), desc: t('dashboard.quickLinks.inviteMembersDesc'), icon: IconMail, color: 'cyan' },
|
||||
{ label: t('dashboard.quickLinks.importUsers'), desc: t('dashboard.quickLinks.importUsersDesc'), icon: IconUpload, color: 'violet' },
|
||||
{ label: t('dashboard.quickLinks.exportData'), desc: t('dashboard.quickLinks.exportDataDesc'), icon: IconDownload, color: 'blue' },
|
||||
{ label: t('dashboard.quickLinks.auditLog'), desc: t('dashboard.quickLinks.auditLogDesc'), icon: IconClipboardList, color: 'grape' },
|
||||
];
|
||||
}
|
||||
|
||||
type UserStatus = 'Active' | 'Pending' | 'Invited';
|
||||
const STATUS_COLOR: Record<UserStatus, string> = {
|
||||
@@ -214,16 +219,19 @@ function QuickLinkTile({
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
export function DashboardPage() {
|
||||
const { t } = useTranslation();
|
||||
const [range, setRange] = useState('week');
|
||||
const kpis = useKpis(t);
|
||||
const quickLinks = useQuickLinks(t);
|
||||
|
||||
return (
|
||||
<Stack gap="xl">
|
||||
{/* header */}
|
||||
<Group justify="space-between" align="flex-end" wrap="wrap">
|
||||
<Stack gap={4}>
|
||||
<Title order={2}>Overview</Title>
|
||||
<Title order={2}>{t('dashboard.title')}</Title>
|
||||
<Text c="dimmed" size="sm">
|
||||
Welcome back — here's what's happening across your platform.
|
||||
{t('dashboard.subtitle')}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Group gap="sm">
|
||||
@@ -232,20 +240,20 @@ export function DashboardPage() {
|
||||
onChange={setRange}
|
||||
radius="md"
|
||||
data={[
|
||||
{ label: 'Today', value: 'today' },
|
||||
{ label: 'This Week', value: 'week' },
|
||||
{ label: 'This Month', value: 'month' },
|
||||
{ label: t('dashboard.timeRange.today'), value: 'today' },
|
||||
{ label: t('dashboard.timeRange.thisWeek'), value: 'week' },
|
||||
{ label: t('dashboard.timeRange.thisMonth'), value: 'month' },
|
||||
]}
|
||||
/>
|
||||
<Button leftSection={<IconDownload size={16} />} radius="md">
|
||||
Export
|
||||
{t('common.export')}
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
{/* KPIs */}
|
||||
<SimpleGrid cols={{ base: 1, sm: 2, lg: 4 }} spacing="lg">
|
||||
{KPIS.map((k) => (
|
||||
{kpis.map((k) => (
|
||||
<StatCard key={k.label} {...k} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
@@ -256,15 +264,15 @@ export function DashboardPage() {
|
||||
<SectionCard>
|
||||
<Group justify="space-between" align="flex-start" mb="lg">
|
||||
<CardHeading
|
||||
title="User Registrations"
|
||||
subtitle="New sign-ups over the last 8 months"
|
||||
title={t('dashboard.charts.userRegistrations')}
|
||||
subtitle={t('dashboard.charts.registrationsSubtitle')}
|
||||
/>
|
||||
<Stack gap={0} align="flex-end">
|
||||
<Text fw={700} c="teal">
|
||||
+18.2%
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
vs previous period
|
||||
{t('dashboard.charts.vsPreviousPeriod')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
@@ -309,7 +317,7 @@ export function DashboardPage() {
|
||||
|
||||
<Grid.Col span={{ base: 12, lg: 4 }}>
|
||||
<SectionCard>
|
||||
<CardHeading title="Users by Role" subtitle="Distribution across access levels" />
|
||||
<CardHeading title={t('dashboard.charts.usersByRole')} subtitle={t('dashboard.charts.roleDistribution')} />
|
||||
<Group mt="lg" justify="center" wrap="nowrap" gap="lg">
|
||||
<Box pos="relative" w={168} h={168} style={{ flexShrink: 0 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
@@ -348,7 +356,7 @@ export function DashboardPage() {
|
||||
9,431
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Total users
|
||||
{t('dashboard.charts.totalUsers')}
|
||||
</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
@@ -376,9 +384,9 @@ export function DashboardPage() {
|
||||
<Grid gutter="lg" align="stretch">
|
||||
<Grid.Col span={{ base: 12, lg: 8 }}>
|
||||
<SectionCard>
|
||||
<CardHeading title="Quick Links" subtitle="Common administrative shortcuts" />
|
||||
<CardHeading title={t('dashboard.quickLinks.title')} subtitle={t('dashboard.quickLinks.subtitle')} />
|
||||
<SimpleGrid cols={{ base: 2, sm: 3 }} spacing="md" mt="lg">
|
||||
{QUICK_LINKS.map((q) => (
|
||||
{quickLinks.map((q) => (
|
||||
<QuickLinkTile key={q.label} {...q} />
|
||||
))}
|
||||
</SimpleGrid>
|
||||
@@ -389,11 +397,11 @@ export function DashboardPage() {
|
||||
<SectionCard>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Text fw={700} fz="lg">
|
||||
Recent Users
|
||||
{t('dashboard.recentUsers.title')}
|
||||
</Text>
|
||||
<Anchor size="sm" fw={600}>
|
||||
<Group gap={4} wrap="nowrap">
|
||||
View all
|
||||
{t('common.viewAll')}
|
||||
<IconArrowRight size={14} />
|
||||
</Group>
|
||||
</Anchor>
|
||||
|
||||
47
apps/backoffice/src/app/i18n/config.ts
Normal file
47
apps/backoffice/src/app/i18n/config.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import i18next from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
import { en } from './locales/en';
|
||||
import { am } from './locales/am';
|
||||
|
||||
export const SUPPORTED_LANGUAGES = ['en', 'am'] as const;
|
||||
export type AppLanguage = (typeof SUPPORTED_LANGUAGES)[number];
|
||||
|
||||
const STORAGE_KEY = 'ema-backoffice-lang';
|
||||
|
||||
function getInitialLanguage(): AppLanguage {
|
||||
const stored =
|
||||
typeof localStorage !== 'undefined'
|
||||
? (localStorage.getItem(STORAGE_KEY) as AppLanguage | null)
|
||||
: null;
|
||||
if (stored && SUPPORTED_LANGUAGES.includes(stored)) return stored;
|
||||
return 'en';
|
||||
}
|
||||
|
||||
export const i18n = i18next.createInstance();
|
||||
|
||||
i18n.use(initReactI18next).init({
|
||||
resources: {
|
||||
en: { translation: en },
|
||||
am: { translation: am },
|
||||
},
|
||||
lng: getInitialLanguage(),
|
||||
fallbackLng: 'en',
|
||||
supportedLngs: [...SUPPORTED_LANGUAGES],
|
||||
interpolation: { escapeValue: false },
|
||||
returnNull: false,
|
||||
});
|
||||
|
||||
i18n.on('languageChanged', (lng) => {
|
||||
if (typeof localStorage !== 'undefined') {
|
||||
localStorage.setItem(STORAGE_KEY, lng);
|
||||
}
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.lang = lng;
|
||||
}
|
||||
});
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
document.documentElement.lang = i18n.language;
|
||||
}
|
||||
|
||||
export default i18n;
|
||||
82
apps/backoffice/src/app/i18n/locales/am.ts
Normal file
82
apps/backoffice/src/app/i18n/locales/am.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Translations } from './en';
|
||||
|
||||
export const am: Translations = {
|
||||
app: {
|
||||
name: 'ኢማ አስተዳደር',
|
||||
shortName: 'ኢማ',
|
||||
authority: 'የኢትዮጵያ ባሕር ጉዳዮች ባለሥልጣን',
|
||||
tagline: 'የቁጥጥር ማዕከል',
|
||||
},
|
||||
|
||||
language: {
|
||||
label: 'ቋንቋ',
|
||||
en: 'English',
|
||||
am: 'አማርኛ',
|
||||
},
|
||||
|
||||
nav: {
|
||||
menu: 'ምናሌ',
|
||||
dashboard: 'ዳሽቦርድ',
|
||||
userManagement: 'የተጠቃሚ አስተዳደር',
|
||||
},
|
||||
|
||||
common: {
|
||||
logout: 'ውጣ',
|
||||
profile: 'መገለጫ',
|
||||
settings: 'ቅንብሮች',
|
||||
export: 'ላክ',
|
||||
viewAll: 'ሁሉንም ይመልከቱ',
|
||||
home: 'መነሻ',
|
||||
notifications: 'ማሳወቂያዎች',
|
||||
administrator: 'አስተዳዳሪ',
|
||||
},
|
||||
|
||||
breadcrumbs: {
|
||||
dashboard: 'ዳሽቦርድ',
|
||||
um: 'የተጠቃሚ አስተዳደር',
|
||||
home: 'መነሻ',
|
||||
},
|
||||
|
||||
dashboard: {
|
||||
title: 'አጠቃላይ እይታ',
|
||||
subtitle: 'እንኳን ደህና መጡ — በመድረክዎ ላይ ያለው እንቅስቃሴ አጠቃላይ እይታ።',
|
||||
kpis: {
|
||||
totalUsers: 'ጠቅላላ ተጠቃሚዎች',
|
||||
activeUsers: 'ንቁ ተጠቃሚዎች',
|
||||
newThisMonth: 'በዚህ ወር አዲስ',
|
||||
pendingApprovals: 'በመጠባበቅ ላይ ያሉ',
|
||||
},
|
||||
charts: {
|
||||
userRegistrations: 'የተጠቃሚ ምዝገባ',
|
||||
registrationsSubtitle: 'ባለፉት 8 ወራት አዲስ ምዝገባ',
|
||||
vsPreviousPeriod: 'ካለፈው ጊዜ ጋር ሲነጻጸር',
|
||||
usersByRole: 'ተጠቃሚዎች በሚና',
|
||||
roleDistribution: 'በተደራሽነት ደረጃ ስርጭት',
|
||||
totalUsers: 'ጠቅላላ ተጠቃሚዎች',
|
||||
},
|
||||
quickLinks: {
|
||||
title: 'ፈጣን አገናኞች',
|
||||
subtitle: 'የተለመዱ የአስተዳደር አቋራጮች',
|
||||
addUser: 'ተጠቃሚ ያክሉ',
|
||||
addUserDesc: 'አዲስ መለያ ይፍጠሩ',
|
||||
rolesAndPermissions: 'ሚናዎች እና ፈቃዶች',
|
||||
rolesAndPermissionsDesc: 'የተደራሽነት ደረጃዎችን ያስተዳድሩ',
|
||||
inviteMembers: 'አባላትን ይጋብዙ',
|
||||
inviteMembersDesc: 'በኢሜይል ግብዣ ይላኩ',
|
||||
importUsers: 'ተጠቃሚዎችን ያስመጡ',
|
||||
importUsersDesc: 'የCSV ጅምላ ማስመጣት',
|
||||
exportData: 'ውሂብ ያላኩ',
|
||||
exportDataDesc: 'ሪፖርቶችን ያውርዱ',
|
||||
auditLog: 'የኦዲት መዝገብ',
|
||||
auditLogDesc: 'እንቅስቃሴን ይገምግሙ',
|
||||
},
|
||||
recentUsers: {
|
||||
title: 'የቅርብ ጊዜ ተጠቃሚዎች',
|
||||
},
|
||||
timeRange: {
|
||||
today: 'ዛሬ',
|
||||
thisWeek: 'በዚህ ሳምንት',
|
||||
thisMonth: 'በዚህ ወር',
|
||||
},
|
||||
},
|
||||
};
|
||||
82
apps/backoffice/src/app/i18n/locales/en.ts
Normal file
82
apps/backoffice/src/app/i18n/locales/en.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
export const en = {
|
||||
app: {
|
||||
name: 'EMA Admin',
|
||||
shortName: 'EMA',
|
||||
authority: 'Ethiopian Maritime Authority',
|
||||
tagline: 'Control Center',
|
||||
},
|
||||
|
||||
language: {
|
||||
label: 'Language',
|
||||
en: 'English',
|
||||
am: 'አማርኛ',
|
||||
},
|
||||
|
||||
nav: {
|
||||
menu: 'MENU',
|
||||
dashboard: 'Dashboard',
|
||||
userManagement: 'User Management',
|
||||
},
|
||||
|
||||
common: {
|
||||
logout: 'Log out',
|
||||
profile: 'Profile',
|
||||
settings: 'Settings',
|
||||
export: 'Export',
|
||||
viewAll: 'View all',
|
||||
home: 'Home',
|
||||
notifications: 'Notifications',
|
||||
administrator: 'Administrator',
|
||||
},
|
||||
|
||||
breadcrumbs: {
|
||||
dashboard: 'Dashboard',
|
||||
um: 'User Management',
|
||||
home: 'Home',
|
||||
},
|
||||
|
||||
dashboard: {
|
||||
title: 'Overview',
|
||||
subtitle: 'Welcome back — here\u2019s what\u2019s happening across your platform.',
|
||||
kpis: {
|
||||
totalUsers: 'Total Users',
|
||||
activeUsers: 'Active Users',
|
||||
newThisMonth: 'New This Month',
|
||||
pendingApprovals: 'Pending Approvals',
|
||||
},
|
||||
charts: {
|
||||
userRegistrations: 'User Registrations',
|
||||
registrationsSubtitle: 'New sign-ups over the last 8 months',
|
||||
vsPreviousPeriod: 'vs previous period',
|
||||
usersByRole: 'Users by Role',
|
||||
roleDistribution: 'Distribution across access levels',
|
||||
totalUsers: 'Total users',
|
||||
},
|
||||
quickLinks: {
|
||||
title: 'Quick Links',
|
||||
subtitle: 'Common administrative shortcuts',
|
||||
addUser: 'Add User',
|
||||
addUserDesc: 'Create a new account',
|
||||
rolesAndPermissions: 'Roles & Permissions',
|
||||
rolesAndPermissionsDesc: 'Manage access levels',
|
||||
inviteMembers: 'Invite Members',
|
||||
inviteMembersDesc: 'Send email invites',
|
||||
importUsers: 'Import Users',
|
||||
importUsersDesc: 'Bulk CSV import',
|
||||
exportData: 'Export Data',
|
||||
exportDataDesc: 'Download reports',
|
||||
auditLog: 'Audit Log',
|
||||
auditLogDesc: 'Review activity',
|
||||
},
|
||||
recentUsers: {
|
||||
title: 'Recent Users',
|
||||
},
|
||||
timeRange: {
|
||||
today: 'Today',
|
||||
thisWeek: 'This Week',
|
||||
thisMonth: 'This Month',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export type Translations = typeof en;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
Group,
|
||||
Text,
|
||||
@@ -29,23 +30,19 @@ interface AppHeaderProps {
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const ROUTE_LABELS: Record<string, string> = {
|
||||
dashboard: 'Dashboard',
|
||||
um: 'User Management',
|
||||
};
|
||||
|
||||
function useBreadcrumbs() {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const segments = location.pathname.split('/').filter(Boolean);
|
||||
|
||||
const crumbs = [
|
||||
{ label: 'Home', path: '/dashboard', icon: <IconHome size={14} /> },
|
||||
{ label: t('breadcrumbs.home'), path: '/dashboard', icon: <IconHome size={14} /> },
|
||||
...segments
|
||||
.filter((seg) => ROUTE_LABELS[seg])
|
||||
.filter((seg) => ['dashboard', 'um'].includes(seg))
|
||||
.map((seg, i) => ({
|
||||
label: ROUTE_LABELS[seg],
|
||||
label: t(`breadcrumbs.${seg}`),
|
||||
path: '/' + segments.slice(0, i + 1).join('/'),
|
||||
icon: null,
|
||||
})),
|
||||
@@ -55,6 +52,7 @@ function useBreadcrumbs() {
|
||||
}
|
||||
|
||||
export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { crumbs } = useBreadcrumbs();
|
||||
@@ -106,7 +104,7 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
<ColorSchemeToggle />
|
||||
|
||||
<Indicator color="red" size={8} offset={6} withBorder>
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label="Notifications">
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label={t('common.notifications')}>
|
||||
<IconBell size={19} />
|
||||
</ActionIcon>
|
||||
</Indicator>
|
||||
@@ -123,7 +121,7 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
Amanuel D.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" visibleFrom="sm">
|
||||
Administrator
|
||||
{t('common.administrator')}
|
||||
</Text>
|
||||
</div>
|
||||
<IconChevronDown size={14} />
|
||||
@@ -131,11 +129,11 @@ export function AppHeader({ onToggle }: AppHeaderProps) {
|
||||
</UnstyledButton>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Item leftSection={<IconUserCircle size={16} />}>Profile</Menu.Item>
|
||||
<Menu.Item leftSection={<IconSettings size={16} />}>Settings</Menu.Item>
|
||||
<Menu.Item leftSection={<IconUserCircle size={16} />}>{t('common.profile')}</Menu.Item>
|
||||
<Menu.Item leftSection={<IconSettings size={16} />}>{t('common.settings')}</Menu.Item>
|
||||
<Menu.Divider />
|
||||
<Menu.Item color="red" leftSection={<IconLogout size={16} />} onClick={handleLogout}>
|
||||
Logout
|
||||
{t('common.logout')}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
AppShell,
|
||||
Box,
|
||||
@@ -20,12 +21,8 @@ import { useNavigate, useLocation } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { logout } from '@ema-platform/auth';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ label: 'Dashboard', icon: IconLayoutDashboard, path: '/dashboard' },
|
||||
{ label: 'User Management', icon: IconUsers, path: '/um/user-management/dashboard' },
|
||||
];
|
||||
|
||||
export function AppSidebar() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const { pathname } = useLocation();
|
||||
@@ -44,10 +41,10 @@ export function AppSidebar() {
|
||||
</ThemeIcon>
|
||||
<div>
|
||||
<Text fw={700} lh={1.1}>
|
||||
EMA Admin
|
||||
{t('app.name')}
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed" lh={1.2}>
|
||||
Control Center
|
||||
{t('app.tagline')}
|
||||
</Text>
|
||||
</div>
|
||||
</Group>
|
||||
@@ -56,20 +53,28 @@ export function AppSidebar() {
|
||||
<AppShell.Section grow component={ScrollArea}>
|
||||
<Stack gap={4}>
|
||||
<Text size="xs" fw={700} c="dimmed" px="xs" mb={4} style={{ letterSpacing: 1 }}>
|
||||
MENU
|
||||
{t('nav.menu')}
|
||||
</Text>
|
||||
{NAV_ITEMS.map(({ label, icon: Icon, path }) => (
|
||||
<NavLink
|
||||
key={path}
|
||||
label={label}
|
||||
leftSection={<Icon size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith(path)}
|
||||
onClick={() => navigate(path)}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
))}
|
||||
<NavLink
|
||||
key="dashboard"
|
||||
label={t('nav.dashboard')}
|
||||
leftSection={<IconLayoutDashboard size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith('/dashboard')}
|
||||
onClick={() => navigate('/dashboard')}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
<NavLink
|
||||
key="um"
|
||||
label={t('nav.userManagement')}
|
||||
leftSection={<IconUsers size={19} stroke={1.6} />}
|
||||
active={pathname.startsWith('/um')}
|
||||
onClick={() => navigate('/um/user-management/dashboard')}
|
||||
variant="light"
|
||||
color="blue"
|
||||
styles={{ root: { borderRadius: rem(10) }, label: { fontWeight: 500 } }}
|
||||
/>
|
||||
</Stack>
|
||||
</AppShell.Section>
|
||||
|
||||
@@ -91,13 +96,13 @@ export function AppSidebar() {
|
||||
Amanuel D.
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
Administrator
|
||||
{t('common.administrator')}
|
||||
</Text>
|
||||
</Box>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
aria-label="Log out"
|
||||
aria-label={t('common.logout')}
|
||||
onClick={handleLogout}
|
||||
>
|
||||
<IconLogout size={18} />
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Menu, ActionIcon } from '@mantine/core';
|
||||
import { IconWorld, IconCheck } from '@tabler/icons-react';
|
||||
|
||||
const LANGUAGES = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'am', label: 'አማርኛ' },
|
||||
];
|
||||
import { i18n, SUPPORTED_LANGUAGES } from '../../i18n/config';
|
||||
|
||||
export function LanguageSwitcher() {
|
||||
const [current, setCurrent] = useState('en');
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Menu shadow="md" width={160} position="bottom-end" withinPortal>
|
||||
<Menu.Target>
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label="Language">
|
||||
<ActionIcon variant="default" size={38} radius="md" aria-label={t('language.label')}>
|
||||
<IconWorld size={19} />
|
||||
</ActionIcon>
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>Language</Menu.Label>
|
||||
{LANGUAGES.map((lng) => (
|
||||
<Menu.Label>{t('language.label')}</Menu.Label>
|
||||
{SUPPORTED_LANGUAGES.map((code) => (
|
||||
<Menu.Item
|
||||
key={lng.code}
|
||||
onClick={() => setCurrent(lng.code)}
|
||||
rightSection={current === lng.code ? <IconCheck size={16} /> : undefined}
|
||||
key={code}
|
||||
onClick={() => i18n.changeLanguage(code)}
|
||||
rightSection={i18n.language === code ? <IconCheck size={16} /> : undefined}
|
||||
>
|
||||
{lng.label}
|
||||
{t(`language.${code}`)}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu.Dropdown>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { Provider } from 'react-redux';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { AuthConfigProvider } from '@ema-platform/auth';
|
||||
import type { ReactNode } from 'react';
|
||||
import { store } from '../store';
|
||||
import { i18n } from '../i18n/config';
|
||||
import { MantineThemeProvider } from './MantineThemeProvider';
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
@@ -24,7 +26,9 @@ export function AppProviders({ children }: { children: ReactNode }) {
|
||||
enableForgotPassword: true,
|
||||
}}
|
||||
>
|
||||
<MantineThemeProvider>{children}</MantineThemeProvider>
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<MantineThemeProvider>{children}</MantineThemeProvider>
|
||||
</I18nextProvider>
|
||||
</AuthConfigProvider>
|
||||
</QueryClientProvider>
|
||||
</Provider>
|
||||
|
||||
@@ -4,6 +4,7 @@ import '@mantine/core/styles.css';
|
||||
import '@mantine/notifications/styles.css';
|
||||
import '@mantine/dates/styles.css';
|
||||
import './styles.css';
|
||||
import './app/i18n/config';
|
||||
import { App } from './app/app';
|
||||
|
||||
window.__USER_MANAGEMENT_BRANDING__ = {
|
||||
|
||||
Reference in New Issue
Block a user