Files
emaui/libs/auth/src/lib/components/AuthShell.tsx
2026-08-14 15:03:02 +03:00

195 lines
5.5 KiB
TypeScript

import type { ReactNode } from 'react';
import {
Box,
Center,
Flex,
Group,
Stack,
Text,
Title,
rem,
useComputedColorScheme,
useMantineTheme,
type BoxProps,
} from '@mantine/core';
import { IconCheck } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { ColorSchemeToggle, LanguageSwitcher } from '@ema-platform/ui';
import { useAuthConfig } from '../AuthConfig';
export function BrandMark({ size = 44, ...boxProps }: BoxProps & { size?: number }) {
const { logoUrl } = useAuthConfig();
return (
<Box
component="img"
src={logoUrl}
alt="EMA"
w={size}
h={size}
style={{ display: 'block', objectFit: 'contain', flexShrink: 0 }}
{...boxProps}
/>
);
}
interface AuthShellProps {
children: ReactNode;
brandTitle?: string;
brandSubtitle?: string;
}
export function AuthShell({ children, brandTitle, brandSubtitle }: AuthShellProps) {
const { t } = useTranslation();
const theme = useMantineTheme();
const { logoUrl } = useAuthConfig();
const heroGradient = theme.other.heroGradient as string;
const computed = useComputedColorScheme('light');
const isDark = computed === 'dark';
const resolvedBrandTitle =
brandTitle ?? t('authShell.brandTitle', 'Maritime licensing, made simple.');
const resolvedBrandSubtitle =
brandSubtitle ??
t(
'authShell.brandSubtitle',
'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.',
);
const FEATURES = [
t('authShell.feature1', 'Submit applications online, 24/7'),
t('authShell.feature2', 'Real-time status tracking & alerts'),
t('authShell.feature3', 'Available in English & አማርኛ'),
];
return (
<Box
mih="100vh"
style={{
background: 'var(--mantine-color-body)',
position: 'relative',
}}
p="md"
>
{/* Language switcher & Theme toggle — top-right corner */}
<Group
gap="xs"
style={{
position: 'absolute',
top: rem(16),
right: rem(16),
zIndex: 10,
}}
>
<LanguageSwitcher supportedLanguages={['en', 'am']} />
<ColorSchemeToggle />
</Group>
<Flex
mih="100vh"
align="center"
justify="center"
>
<Box
mx="auto"
maw={1280}
w="100%"
p={12}
style={{
borderRadius: rem(16),
background: 'var(--mantine-color-body)',
boxShadow: isDark
? '0 20px 60px rgba(0,0,0,0.5)'
: '0 20px 60px rgba(0,0,0,0.15)',
overflow: 'hidden',
}}
>
<Flex direction={{ base: 'column', lg: 'row' }}>
<Box
w={{ base: '100%', lg: '50%' }}
px={48}
py={48}
style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}
>
{children}
</Box>
<Box
visibleFrom="lg"
w="50%"
p="lg"
pos="relative"
style={{ background: heroGradient, overflow: 'hidden' }}
>
<Box
pos="absolute"
top={-80}
right={-40}
w={240}
h={240}
style={{ borderRadius: '50%', background: 'rgba(255,255,255,0.10)' }}
/>
<Box
pos="absolute"
bottom={-80}
left={-60}
w={220}
h={220}
style={{ borderRadius: '50%', background: 'rgba(255,255,255,0.08)' }}
/>
<Stack
gap="lg"
pos="relative"
style={{ zIndex: 1 }}
h="100%"
justify="center"
>
<Center>
<Box
component="img"
src={logoUrl}
alt="EMA Portal"
style={{ display: 'block', maxWidth: '60%', height: 'auto' }}
/>
</Center>
<Stack gap={6}>
<Title order={2} c="white" fz={rem(28)} lh={1.2} fw={700}>
{resolvedBrandTitle}
</Title>
<Text fz="sm" lh={1.6} style={{ color: 'rgba(255,255,255,0.85)' }}>
{resolvedBrandSubtitle}
</Text>
</Stack>
<Stack gap="sm">
{FEATURES.map((feature) => (
<Group key={feature} gap="sm" wrap="nowrap">
<Center
w={22}
h={22}
style={{
borderRadius: '50%',
background: 'rgba(255,255,255,0.2)',
flexShrink: 0,
}}
>
<IconCheck size={12} color="white" stroke={2.4} />
</Center>
<Text c="white" fz="sm" fw={500}>
{feature}
</Text>
</Group>
))}
</Stack>
<Text fz="xs" style={{ color: 'rgba(255,255,255,0.7)' }}>
{t('authShell.copyright', '© 2026 Ethiopian Maritime Authority')}
</Text>
</Stack>
</Box>
</Flex>
</Box>
</Flex>
</Box>
);
}