diff --git a/apps/portal/src/app/features/auth/components/AuthShell.tsx b/apps/portal/src/app/features/auth/components/AuthShell.tsx new file mode 100644 index 000000000..15e993f20 --- /dev/null +++ b/apps/portal/src/app/features/auth/components/AuthShell.tsx @@ -0,0 +1,164 @@ +import type { ReactNode } from 'react'; +import { + Box, + Center, + Flex, + Group, + Stack, + Text, + ThemeIcon, + Title, + rem, + useMantineTheme, +} from '@mantine/core'; +import { IconAnchor, IconCheck } from '@tabler/icons-react'; + +const FEATURES = [ + 'Submit applications online, 24/7', + 'Real-time status tracking & alerts', + 'Available in English & አማርኛ', +]; + +/** + * EMA brand mark — mirrors the logo used in the portal header + * (gradient tile + anchor glyph). Use `variant="white"` on dark backgrounds. + */ +export function BrandMark({ + size = 44, + variant = 'gradient', +}: { + size?: number; + variant?: 'gradient' | 'white'; +}) { + return ( + + + + ); +} + +interface AuthShellProps { + children: ReactNode; + brandTitle?: string; + brandSubtitle?: string; +} + +/** + * Two-pane auth layout: a branded gradient panel (hidden below `md`) and the + * form pane. Standalone (rendered outside the portal chrome and i18n provider), + * so copy here is plain English to match the other auth pages. + */ +export function AuthShell({ + children, + brandTitle = 'Maritime licensing, made simple.', + brandSubtitle = 'Apply for vessel and seafarer licenses, upload documents, and track every application in one secure portal.', +}: AuthShellProps) { + const theme = useMantineTheme(); + const heroGradient = theme.other.heroGradient as string; + + return ( + + {/* ---- Brand panel ---------------------------------------------- */} + + {/* Decorative blurred orbs for depth */} + + + + + + +
+ + EMA Portal + + + Ethiopian Maritime Authority + +
+
+ + + + + {brandTitle} + + + {brandSubtitle} + + + + + {FEATURES.map((feature) => ( + +
+ +
+ + {feature} + +
+ ))} +
+
+ + + © 2026 Ethiopian Maritime Authority + +
+
+ + {/* ---- Form panel ----------------------------------------------- */} +
+ + + + + EMA Portal + + + {children} + +
+
+ ); +} diff --git a/apps/portal/src/app/features/auth/pages/LoginPage.tsx b/apps/portal/src/app/features/auth/pages/LoginPage.tsx index 5419d9d1e..78b62e3cd 100644 --- a/apps/portal/src/app/features/auth/pages/LoginPage.tsx +++ b/apps/portal/src/app/features/auth/pages/LoginPage.tsx @@ -1,15 +1,22 @@ import { useState } from 'react'; import { - Paper, - TextInput, - PasswordInput, - Button, - Stack, - Title, - Center, - Text, Anchor, + Button, + Checkbox, + Divider, + Group, + PasswordInput, + Stack, + Text, + TextInput, + Title, } from '@mantine/core'; +import { + IconArrowRight, + IconDeviceMobile, + IconLock, + IconMail, +} from '@tabler/icons-react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -19,6 +26,7 @@ import { loginSuccess, setUser } from '../store/auth.slice'; import type { LoginPayload, AuthUser } from '../types/auth.types'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; +import { AuthShell } from '../components/AuthShell'; const schema = z.object({ email: z.string().email({ message: 'Enter a valid email' }), @@ -31,6 +39,7 @@ export function LoginPage() { const navigate = useNavigate(); const dispatch = useAppDispatch(); const [isLoading, setIsLoading] = useState(false); + const [rememberMe, setRememberMe] = useState(true); const [loginTrigger] = useApiMutation(); const [meTrigger] = useApiMutation(); @@ -73,37 +82,83 @@ export function LoginPage() { }; return ( -
- - - Sign in to Portal -
- - - - - -
- - Don't have an account?{' '} - - Sign up - + + +
+ + Welcome back + + + Sign in to access your maritime licensing dashboard. - - -
+ + +
+ + } + error={errors.email?.message} + {...register('email')} + /> + } + error={errors.password?.message} + {...register('password')} + /> + + + setRememberMe(e.currentTarget.checked)} + /> + notify.info('Password reset is coming soon.')} + > + Forgot password? + + + + + +
+ + + + + + + Don't have an account?{' '} + + Create one + + + + ); } diff --git a/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx b/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx index c786d40fe..965737c38 100644 --- a/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx +++ b/apps/portal/src/app/features/auth/pages/OTPVerificationPage.tsx @@ -1,18 +1,21 @@ import { - Paper, - TextInput, + Anchor, Button, + Group, Stack, - Title, - Center, Text, + TextInput, + ThemeIcon, + Title, } from '@mantine/core'; +import { IconArrowRight, IconShieldLock } from '@tabler/icons-react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useNavigate, useLocation } from 'react-router-dom'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; +import { AuthShell } from '../components/AuthShell'; const schema = z.object({ verificationCode: z.string().min(1, { message: 'Verification code is required' }), @@ -72,38 +75,63 @@ export function OTPVerificationPage() { }; return ( -
- - - Verify your email - - A verification code has been sent to {email || 'your email'}. Enter it - below to complete your registration. + + + + + + +
+ + Verify your account + + + A verification code has been sent to{' '} + + {email || 'your email'} + + . Enter it below to continue. -
- - - - -
- + + + + + + Didn't receive a code? + + - Send OTP again - - - -
+ Resend code + + + + ); } diff --git a/apps/portal/src/app/features/auth/pages/SignupPage.tsx b/apps/portal/src/app/features/auth/pages/SignupPage.tsx index 5edd9cbac..d12d1ad6b 100644 --- a/apps/portal/src/app/features/auth/pages/SignupPage.tsx +++ b/apps/portal/src/app/features/auth/pages/SignupPage.tsx @@ -1,14 +1,24 @@ +import { useState } from 'react'; import { - Paper, - TextInput, - PasswordInput, - Button, - Stack, - Title, - Center, - Text, Anchor, + Button, + Checkbox, + Group, + PasswordInput, + SimpleGrid, + Stack, + Text, + TextInput, + Title, } from '@mantine/core'; +import { + IconArrowRight, + IconAt, + IconDeviceMobile, + IconLock, + IconMail, + IconUser, +} from '@tabler/icons-react'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; @@ -17,6 +27,7 @@ import { useAppDispatch } from '../../../store/hooks'; import { loginSuccess } from '../store/auth.slice'; import { useApiMutation } from '@ema-platform/api'; import { notify } from '@ema-platform/ui'; +import { AuthShell } from '../components/AuthShell'; const schema = z .object({ @@ -52,6 +63,7 @@ interface SignupPayload { export function SignupPage() { const navigate = useNavigate(); const dispatch = useAppDispatch(); + const [agreed, setAgreed] = useState(false); const [signupTrigger, { isLoading: loading }] = useApiMutation<{ token: string; refreshToken: string; @@ -103,67 +115,119 @@ export function SignupPage() { }; return ( -
- - - Create an account -
- + + +
+ + Create account + + + It only takes a minute to get started. + +
+ + + + } error={errors.nameEn?.message} {...register('nameEn')} /> } error={errors.nameAm?.message} {...register('nameAm')} /> + + + } error={errors.email?.message} {...register('email')} /> } error={errors.username?.message} {...register('username')} /> - + + + } + error={errors.phoneNumber?.message} + {...register('phoneNumber')} + /> + + } error={errors.password?.message} {...register('password')} /> } error={errors.confirmPassword?.message} {...register('confirmPassword')} /> - - - - - Already have an account?{' '} - - Sign in - - -
-
-
+ + + setAgreed(e.currentTarget.checked)} + label={ + + I agree to the{' '} + e.preventDefault()} + > + Terms & Privacy Policy + + + } + /> + + + + + + + Already have an account?{' '} + + Sign in + + + + ); } diff --git a/ema-portal.pen b/ema-portal.pen new file mode 100644 index 000000000..f5fc5d4aa --- /dev/null +++ b/ema-portal.pen @@ -0,0 +1,562 @@ +{ + "version": "2.13", + "children": [ + { + "type": "frame", + "id": "YKsGt", + "x": 0, + "y": 0, + "name": "Welcome to Pencil", + "clip": true, + "width": 1000, + "height": 1458, + "layout": "none", + "children": [ + { + "type": "text", + "id": "Lmqy1", + "x": 38, + "y": 297, + "name": "Start here", + "fill": "#ff8303ff", + "textGrowth": "fixed-width-height", + "width": 923, + "height": 307, + "content": "Ask agent to \ndesign something", + "lineHeight": 1.399999976158142, + "textAlign": "center", + "fontFamily": "Noto Sans", + "fontSize": 100, + "fontWeight": "500", + "letterSpacing": -4 + }, + { + "type": "path", + "id": "AmD9T", + "x": 267.8010819142764, + "y": 632, + "rotation": -45, + "geometry": "M7 0l0 14m7-7l-7 7-7-7", + "width": 366, + "height": 366, + "stroke": "#ff8302ff", + "strokeWidth": 50, + "strokeLinejoin": "round", + "strokeLinecap": "round" + } + ] + } + ], + "themes": { + "Mode": [ + "Light", + "Dark" + ] + }, + "variables": { + "--sidebar": { + "type": "color", + "value": [ + { + "value": "#E7E8E5" + }, + { + "value": "#18181b", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-foreground": { + "type": "color", + "value": [ + { + "value": "#666666" + }, + { + "value": "#fafafa", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-primary": { + "type": "color", + "value": [ + { + "value": "#18181b" + }, + { + "value": "#18181b", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-primary-foreground": { + "type": "color", + "value": [ + { + "value": "#fafafa" + }, + { + "value": "#fafafa", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-border": { + "type": "color", + "value": [ + { + "value": "#CBCCC9" + }, + { + "value": "#ffffff1a", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-accent": { + "type": "color", + "value": [ + { + "value": "#CBCCC9" + }, + { + "value": "#2a2a30", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-accent-foreground": { + "type": "color", + "value": [ + { + "value": "#18181b" + }, + { + "value": "#fafafa", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--sidebar-ring": { + "type": "color", + "value": [ + { + "value": "#71717a" + }, + { + "value": "#71717a", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--background": { + "type": "color", + "value": [ + { + "value": "#F2F3F0" + }, + { + "value": "#111111", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#FFFFFF", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--card": { + "type": "color", + "value": [ + { + "value": "#FFFFFF" + }, + { + "value": "#1A1A1A", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--card-foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#FFFFFF", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--popover": { + "type": "color", + "value": [ + { + "value": "#FFFFFF" + }, + { + "value": "#1A1A1A", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--popover-foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#FFFFFF", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--primary": { + "type": "color", + "value": [ + { + "value": "#FF8400" + }, + { + "value": "#FF8400", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--primary-foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#111111", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--secondary": { + "type": "color", + "value": [ + { + "value": "#E7E8E5" + }, + { + "value": "#2E2E2E", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--secondary-foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#FFFFFF", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--muted": { + "type": "color", + "value": [ + { + "value": "#F2F3F0" + }, + { + "value": "#2E2E2E", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--muted-foreground": { + "type": "color", + "value": [ + { + "value": "#666666" + }, + { + "value": "#B8B9B6", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--accent": { + "type": "color", + "value": [ + { + "value": "#F2F3F0" + }, + { + "value": "#111111", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--accent-foreground": { + "type": "color", + "value": [ + { + "value": "#111111" + }, + { + "value": "#F2F3F0", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--destructive": { + "type": "color", + "value": [ + { + "value": "#D93C15" + }, + { + "value": "#FF5C33", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--border": { + "type": "color", + "value": [ + { + "value": "#CBCCC9" + }, + { + "value": "#2E2E2E", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--input": { + "type": "color", + "value": [ + { + "value": "#CBCCC9" + }, + { + "value": "#2E2E2E", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--ring": { + "type": "color", + "value": [ + { + "value": "#666666" + }, + { + "value": "#666666", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--white": { + "type": "color", + "value": "#FFFFFF" + }, + "--black": { + "type": "color", + "value": "#000000" + }, + "--font-primary": { + "type": "string", + "value": "JetBrains Mono" + }, + "--font-secondary": { + "type": "string", + "value": "Geist" + }, + "--radius-none": { + "type": "number", + "value": 0 + }, + "--radius-pill": { + "type": "number", + "value": 999 + }, + "--color-success": { + "type": "color", + "value": [ + { + "value": "#DFE6E1" + }, + { + "value": "#222924", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-success-foreground": { + "type": "color", + "value": [ + { + "value": "#004D1A" + }, + { + "value": "#B6FFCE", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-warning": { + "type": "color", + "value": [ + { + "value": "#E9E3D8" + }, + { + "value": "#291C0F", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-warning-foreground": { + "type": "color", + "value": [ + { + "value": "#804200" + }, + { + "value": "#FF8400", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-error": { + "type": "color", + "value": [ + { + "value": "#E5DCDA" + }, + { + "value": "#24100B", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-error-foreground": { + "type": "color", + "value": [ + { + "value": "#8C1C00" + }, + { + "value": "#FF5C33", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-info": { + "type": "color", + "value": [ + { + "value": "#DFDFE6" + }, + { + "value": "#222229", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--color-info-foreground": { + "type": "color", + "value": [ + { + "value": "#000066" + }, + { + "value": "#B2B2FF", + "theme": { + "Mode": "Dark" + } + } + ] + }, + "--radius-m": { + "type": "number", + "value": 16 + } + } +} \ No newline at end of file