>({});
+
+ const requiredDocs = useMemo(
+ () => (licenseType ? REQUIRED_DOCUMENTS[licenseType] : []),
+ [licenseType],
+ );
+ const attached = requiredDocs.filter((d) => uploaded[d]).length;
+
+ const next = () => setActive((c) => Math.min(c + 1, 3));
+ const prev = () => setActive((c) => Math.max(c - 1, 0));
+
+ const canContinue = () => {
+ if (active === 0) return !!licenseType && !!fullName.trim();
+ if (active === 1) return !!subjectName.trim();
+ return true;
+ };
+
+ const handleSubmit = () => {
+ if (!licenseType) return;
+ submit({
+ requestType,
+ licenseType,
+ applicantName: fullName.trim(),
+ subjectName: subjectName.trim() || fullName.trim(),
+ notes: notes.trim() || undefined,
+ documents: requiredDocs.filter((d) => uploaded[d]),
+ });
+ notify.success('Your application has been submitted to EMA.');
+ navigate('/v2/applications');
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {/* ---- Form card ------------------------------------------- */}
+
+
+ {active === 0 && (
+
+
+ }
+ data={LICENSE_OPTIONS}
+ value={licenseType}
+ onChange={(v) => setLicenseType(v as LicenseType)}
+ searchable
+ />
+
+ }
+ data={CATEGORIES}
+ value={category}
+ onChange={setCategory}
+ />
+ }
+ data={REGIONS}
+ value={region}
+ onChange={setRegion}
+ searchable
+ />
+
+ }
+ placeholder="Abebe Bekele Tadesse"
+ value={fullName}
+ onChange={(e) => setFullName(e.currentTarget.value)}
+ />
+
+ }
+ placeholder="ET-1234567"
+ value={idNumber}
+ onChange={(e) => setIdNumber(e.currentTarget.value)}
+ />
+ }
+ placeholder="+251 911 234 567"
+ value={phone}
+ onChange={(e) => setPhone(e.currentTarget.value)}
+ />
+
+
+
+ )}
+
+ {active === 1 && (
+
+
+
+ )}
+
+ {active === 2 && (
+
+
+ }>
+ All required documents must be provided before the review can be completed.
+
+ {requiredDocs.map((d) => (
+
+ setUploaded((s) => ({ ...s, [d]: !s[d] }))
+ }
+ />
+ ))}
+
+ )}
+
+ {active === 3 && (
+
+
+
+
+
+
+
+
+
+
+ {attached < requiredDocs.length && (
+
+ Some required documents are still missing. You can still submit, but
+ review may be delayed.
+
+ )}
+
+ )}
+
+
+ }
+ onClick={prev}
+ disabled={active === 0}
+ >
+ Back
+
+ {active < 3 ? (
+ }
+ onClick={next}
+ disabled={!canContinue()}
+ >
+ Continue
+
+ ) : (
+ }
+ onClick={handleSubmit}
+ >
+ Submit application
+
+ )}
+
+
+
+
+ {/* ---- Summary + help -------------------------------------- */}
+
+
+
+
+ Application Summary
+
+
+
+
+
+
+
+ {licenseType ? LICENSE_TYPE_LABELS[licenseType] : 'Select a license'}
+
+
+ {category ?? '—'} · {REQUEST_TYPE_LABELS[requestType]}
+
+
+
+
+
+
+
+
+
+
+
+ Total payable
+
+ {licenseType ? `ETB ${SERVICE_FEE[licenseType].toLocaleString()}` : '—'}
+
+
+
+
+
+
+
+
+
+
+ Need help?
+
+
+
+ Our support team can guide you through the documents required for this
+ license.
+
+ }
+ onClick={() => navigate('/support')}
+ >
+ Contact support
+
+
+
+
+
+
+ );
+}
+
+function SectionHead({ title, subtitle }: { title: string; subtitle: string }) {
+ return (
+
+
{title}
+
+ {subtitle}
+
+
+ );
+}
+
+function SummaryRow({ label, value }: { label: string; value: string }) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
+
+function ReviewItem({ label, value }: { label: string; value: string }) {
+ return (
+
+
+ {label}
+
+
+ {value}
+
+
+ );
+}
+
+function Dropzone() {
+ return (
+
+
+ Supporting document
+
+
notify.info('File upload — coming soon.')}
+ >
+
+
+
+
+
+ Drag & drop files here, or click to browse
+
+
+ PDF, JPG or PNG — up to 10 MB
+
+
+
+
+ );
+}
diff --git a/apps/portal/src/app/v2/pages/DashboardV2.tsx b/apps/portal/src/app/v2/pages/DashboardV2.tsx
new file mode 100644
index 000000000..77cff8f1c
--- /dev/null
+++ b/apps/portal/src/app/v2/pages/DashboardV2.tsx
@@ -0,0 +1,303 @@
+import {
+ Box,
+ Button,
+ Card,
+ Center,
+ Grid,
+ Group,
+ Paper,
+ SimpleGrid,
+ Stack,
+ Table,
+ Text,
+ ThemeIcon,
+ Title,
+ UnstyledButton,
+ useMantineTheme,
+} from '@mantine/core';
+import {
+ IconArrowRight,
+ IconAward,
+ IconChevronRight,
+ IconClockHour4,
+ IconCircleCheck,
+ IconFileText,
+ IconLifebuoy,
+ IconShip,
+ IconSquareRoundedPlus,
+ IconUpload,
+} from '@tabler/icons-react';
+import type { Icon } from '@tabler/icons-react';
+import { useNavigate } from 'react-router-dom';
+import { notify } from '@ema-platform/ui';
+import { useLicenses } from '../../features/licenses/hooks/useLicenses';
+import { useLicenseLabels } from '../../features/licenses/hooks/useLicenseLabels';
+import { StatusDonut } from '../../features/dashboard/components/StatusDonut';
+import type { ApplicationStatus } from '../../features/licenses/types/license.types';
+import { StatusPill } from '../components/ui';
+
+const OPEN_STATUSES: ApplicationStatus[] = [
+ 'SUBMITTED',
+ 'UNDER_REVIEW',
+ 'INFO_REQUESTED',
+ 'PAYMENT_PENDING',
+];
+
+export function DashboardV2() {
+ const navigate = useNavigate();
+ const theme = useMantineTheme();
+ const { applications, licenses } = useLicenses();
+ const { licenseType, formatDate } = useLicenseLabels();
+
+ const activeLicenses = licenses.filter((l) => l.status === 'ACTIVE').length;
+ const pending = applications.filter((a) => OPEN_STATUSES.includes(a.status)).length;
+ const approved = applications.filter(
+ (a) => a.status === 'APPROVED' || a.status === 'ISSUED',
+ ).length;
+ const documents = applications.reduce((sum, a) => sum + a.documents.length, 0);
+
+ const firstName = (licenses[0]?.holderName ?? 'there').split(' ')[0];
+ const recent = applications.slice(0, 6);
+
+ return (
+
+ {/* ---- Hero banner ------------------------------------------- */}
+
+
+
+
+
+ Welcome back, {firstName} 👋
+
+
+ You have {pending} applications in review and {activeLicenses} active
+ licence{activeLicenses === 1 ? '' : 's'}. Start a new application or check
+ your status below.
+
+
+ }
+ onClick={() => navigate('/v2/apply')}
+ >
+ Apply for a license
+
+
+
+
+
+
+
+
+ {/* ---- Stat cards -------------------------------------------- */}
+
+
+
+
+
+
+
+ {/* ---- Lower row --------------------------------------------- */}
+
+
+
+
+ Recent Applications
+ }
+ onClick={() => navigate('/v2/applications')}
+ >
+ View all
+
+
+
+
+
+
+ Application
+ Type
+ Submitted
+ Status
+
+
+
+
+ {recent.map((app) => (
+ navigate('/v2/applications')}
+ >
+
+
+ {app.referenceNo}
+
+
+
+
+ {licenseType(app.licenseType)}
+
+
+
+
+ {formatDate(app.submittedAt)}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ Application Status
+
+
+
+
+
+
+ Quick actions
+
+
+ navigate('/v2/apply')}
+ />
+ notify.info('Document upload — coming soon.')}
+ />
+ navigate('/support')}
+ />
+
+
+
+
+
+
+ );
+}
+
+function StatCard({
+ icon: CardIcon,
+ color,
+ value,
+ label,
+ trend,
+ trendColor = 'gray',
+}: {
+ icon: Icon;
+ color: string;
+ value: number;
+ label: string;
+ trend: string;
+ trendColor?: string;
+}) {
+ return (
+
+
+
+
+
+
+
+ {trend}
+
+
+
+ {value}
+
+
+ {label}
+
+
+
+ );
+}
+
+function QuickAction({
+ icon: ActionIconCmp,
+ color,
+ label,
+ onClick,
+}: {
+ icon: Icon;
+ color: string;
+ label: string;
+ onClick: () => void;
+}) {
+ return (
+
+
+
+
+
+
+
+ {label}
+
+
+
+
+
+ );
+}