From 174bc1a7bd409f7548ccffacadab538a512aa9d3 Mon Sep 17 00:00:00 2001 From: Marshal Date: Wed, 19 Aug 2026 17:07:48 +0000 Subject: [PATCH 1/3] Implement new feature for user authentication and improve error handling --- .../src/pages/EDRFreightLandingPage.tsx | 1763 ++++++++++++----- 1 file changed, 1315 insertions(+), 448 deletions(-) diff --git a/apps/edr-freight-web/portal/src/pages/EDRFreightLandingPage.tsx b/apps/edr-freight-web/portal/src/pages/EDRFreightLandingPage.tsx index b6f4787d6..59e6f2ad6 100644 --- a/apps/edr-freight-web/portal/src/pages/EDRFreightLandingPage.tsx +++ b/apps/edr-freight-web/portal/src/pages/EDRFreightLandingPage.tsx @@ -1,139 +1,571 @@ +import { useEffect, useRef, useState } from "react"; import { Link } from "react-router-dom"; import { ArrowRight, - BarChart3, + BellRing, + Briefcase, + Building2, + ChartColumn, + Check, CheckCircle2, Clock3, - Globe2, + FileCheck, + FileText, Mail, + Map as MapIcon, MapPin, Menu, + Package, + PackageSearch, Phone, - ShieldCheck, - Train, + Radar, + Receipt, + Route as RouteIcon, + Satellite, + Ship, + Timer, + TrainFront, + TrendingDown, + TrendingUp, Truck, - Users, } from "lucide-react"; +/* ------------------------------------------------------------------ */ +/* Motion helpers */ +/* ------------------------------------------------------------------ */ + +/** Reveals children once they scroll into view. No animation library needed. */ +function useReveal() { + const ref = useRef(null); + const [shown, setShown] = useState(false); + + useEffect(() => { + const node = ref.current; + if (!node) return; + if (typeof IntersectionObserver === "undefined") { + setShown(true); + return; + } + + const observer = new IntersectionObserver( + (entries) => { + if (entries.some((entry) => entry.isIntersecting)) { + setShown(true); + observer.disconnect(); + } + }, + { rootMargin: "0px 0px -12% 0px", threshold: 0.15 }, + ); + + observer.observe(node); + return () => observer.disconnect(); + }, []); + + return { ref, shown }; +} + +function Reveal({ + children, + delay = 0, + className = "", +}: { + children: React.ReactNode; + delay?: number; + className?: string; +}) { + const { ref, shown } = useReveal(); + + return ( +
+ {children} +
+ ); +} + +/** Counts up to `value` when scrolled into view. Keeps prefix/suffix intact. */ +function CountUp({ + value, + decimals = 0, + prefix = "", + suffix = "", + duration = 1400, +}: { + value: number; + decimals?: number; + prefix?: string; + suffix?: string; + duration?: number; +}) { + const { ref, shown } = useReveal(); + const [display, setDisplay] = useState(0); + + useEffect(() => { + if (!shown) return; + if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) { + setDisplay(value); + return; + } + + let frame = 0; + const start = performance.now(); + + const tick = (now: number) => { + const progress = Math.min(1, (now - start) / duration); + // easeOutCubic + setDisplay(value * (1 - Math.pow(1 - progress, 3))); + if (progress < 1) frame = requestAnimationFrame(tick); + }; + + frame = requestAnimationFrame(tick); + return () => cancelAnimationFrame(frame); + }, [shown, value, duration]); + + return ( + + {prefix} + {display.toFixed(decimals)} + {suffix} + + ); +} + +/* ------------------------------------------------------------------ */ +/* Content */ +/* ------------------------------------------------------------------ */ + +const navLinks = [ + { label: "Features", href: "#features" }, + { label: "Live ops", href: "#showcase" }, + { label: "Corridors", href: "#corridors" }, + { label: "How it works", href: "#how" }, + { label: "Contact", href: "#contact" }, +]; + +const heroTrust = [ + "Telebirr & CBE Birr payments", + "Fayda ID verified", + "Customs-ready documents", +]; + +const trackingStops = [ + { name: "Mojo Dry Port", time: "Loaded · 06:40", state: "done" }, + { name: "Adama", time: "Departed · 08:15", state: "done" }, + { name: "Dire Dawa", time: "ETA 14:30", state: "current" }, + { name: "Dewele (Border)", time: "ETA 18:05", state: "idle" }, + { name: "Djibouti Port", time: "ETA 22:40", state: "idle" }, +] as const; + const stats = [ { - label: "Active Corridors", - value: "24+", - icon: Globe2, + icon: RouteIcon, + value: , + label: "Addis Ababa → Djibouti · electrified", + trend: "Standard gauge", + down: false, + chart: [20, 24, 28, 30, 34, 36, 40, 44], }, { - label: "Monthly Shipments", - value: "12K+", - icon: Truck, + icon: Package, + value: , + label: "Monthly consignments booked", + trend: "+18% vs last month", + down: false, + chart: [16, 22, 19, 26, 30, 28, 36, 44], }, { - label: "Fleet Coverage", - value: "16 Trains", - icon: Train, + icon: TrainFront, + value: , + label: "Train sets in active rotation", + trend: "14 running now", + down: false, + fleet: true, }, { - label: "On-time Delivery", - value: "100%", - icon: Clock3, + icon: Timer, + value: , + label: "Booking to confirmed wagon", + trend: "-62% since launch", + down: true, + chart: [44, 40, 36, 30, 26, 20, 16, 12], }, ]; +const stations = [ + { name: "Sebeta", hub: false }, + { name: "Addis Ababa", hub: true, note: "Km 0" }, + { name: "Mojo", hub: true }, + { name: "Adama", hub: false }, + { name: "Awash", hub: false }, + { name: "Mieso", hub: false }, + { name: "Dire Dawa", hub: true }, + { name: "Dewele", hub: true, note: "Customs", border: true }, + { name: "Ali Sabieh", hub: false }, + { name: "Djibouti Port", hub: true, note: "Km 752" }, +]; + +const lanes = [ + { from: "Mojo Dry Port", to: "Djibouti Port", transit: "~16h", kind: "Containers" }, + { from: "Addis Ababa", to: "Djibouti Port", transit: "~19h", kind: "Mixed cargo" }, + { from: "Djibouti Port", to: "Mojo Dry Port", transit: "~16h", kind: "Imports" }, + { from: "Dire Dawa", to: "Djibouti Port", transit: "~8h", kind: "Bulk" }, +]; + +const steps = [ + { + num: "01", + title: "Verify & register", + body: "Sign up with your company TIN, verify with Fayda ID, and get trade access approved by EDR.", + }, + { + num: "02", + title: "Book & pay", + body: "Pick origin, destination, cargo type and schedule. Pay with Telebirr, CBE Birr or contract credit.", + }, + { + num: "03", + title: "Load & clear", + body: "Wagon allocation, loading confirmation, interchange document and customs stamps — all digital.", + }, + { + num: "04", + title: "Track & settle", + body: "Follow the train live, get gate notifications, and download the final invoice when the cargo is released.", + }, +]; + +const audiences = [ + { + icon: Building2, + title: "Shippers & importers", + body: "Book, pay, track and download documents from one dashboard.", + items: ["Consignment booking", "Live tracking", "Invoices & receipts"], + }, + { + icon: Ship, + title: "Shipping lines", + body: "Manage container inventory, bookings and interchange with the railway.", + items: ["Container management", "Booking completion", "Interchange documents"], + }, + { + icon: Briefcase, + title: "Transit agents", + body: "Handle customs declarations, stamps and signatures on behalf of clients.", + items: ["Digital stamps", "E-signatures", "Compliance checks"], + }, + { + icon: TrainFront, + title: "EDR operations", + body: "Train sets, locomotives, wagons, schedules, maintenance and incidents.", + items: ["Train builder", "Fleet & fuel", "Incident reporting"], + }, +]; + +/* ------------------------------------------------------------------ */ +/* Feature previews */ +/* ------------------------------------------------------------------ */ + +function Chip({ + children, + tone = "green", + icon: Icon, +}: { + children: React.ReactNode; + tone?: "green" | "amber" | "muted"; + icon?: React.ElementType; +}) { + const tones = { + green: "bg-emerald-50 text-emerald-700", + amber: "bg-amber-50 text-amber-700", + muted: "bg-slate-100 text-slate-500", + }; + + return ( + + {Icon ? : null} + {children} + + ); +} + +function BookingPreview() { + return ( +
+
+
+

CN-2026-08421

+

+ Mojo → Djibouti · 40ft × 8 +

+
+ Confirmed +
+ +
+ + Depart Thu 21 Aug · TS-14 + + ETB 184,200 +
+
+ ); +} + +function GpsPreview() { + return ( +
+
+ +
+ +
+ {["Mojo", "Adama", "Awash", "Dire Dawa", "Djibouti"].map((s) => ( + {s} + ))} +
+ +
+ Loco 3421 · 64 km/h + + ETA 14:30 + +
+
+ ); +} + +function DocsPreview() { + const docs = [ + { name: "Interchange document", status: "Signed" }, + { name: "Customs declaration", status: "Stamped" }, + { name: "Commercial invoice", status: "Pending" }, + ]; + + return ( +
+ {docs.map((doc) => { + const pending = doc.status === "Pending"; + + return ( +
+ + {doc.name} + + {doc.status} + +
+ ); + })} +
+ ); +} + +function BillingPreview() { + return ( +
+
+
+

INV-1042 · Aug

+

+ ETB 1,284,600 +

+
+ Paid +
+ +
+ {["Telebirr", "CBE Birr", "eBirr", "Card"].map((method) => ( + + {method} + + ))} +
+
+ ); +} + +function MilePreview() { + const legs = [ + { icon: Truck, name: "Truck" }, + { icon: TrainFront, name: "Rail" }, + { icon: Truck, name: "Truck" }, + ]; + + return ( +
+
+ {legs.map((leg, index) => { + const Icon = leg.icon; + const isRail = index === 1; + + return ( +
+
+ + + + {leg.name} +
+ + {index < legs.length - 1 ? ( + + ) : null} +
+ ); + })} +
+ +
+ Warehouse, Gerji + Consignee, Djibouti +
+
+ ); +} + +function AnalyticsPreview() { + const bars = [22, 30, 26, 36, 40, 34, 46, 52, 48, 56]; + + return ( +
+
+ {bars.map((height, index) => ( + = 8 ? "bg-edr-primary" : "bg-emerald-200" + }`} + style={{ + height: `${height}%`, + animationDelay: `${index * 60}ms`, + }} + /> + ))} +
+ +
+ + Corridor throughput · TEU/week + + +12% +
+
+ ); +} + const features = [ { - title: "Real-time Shipment Tracking", - description: - "Track consignments across Addis Ababa, Dire Dawa, Djibouti, Mojo, and all major freight corridors.", + icon: PackageSearch, + title: "Consignment booking", + body: "Book containers or bulk cargo, pick a train schedule, and get a confirmed wagon allocation in minutes.", + preview: , + }, + { + icon: Radar, + title: "Live GPS tracking", + body: "Locomotive telemetry and wagon-level position across every station from Mojo to Djibouti Port.", + preview: , + }, + { + icon: FileCheck, + title: "Customs & documents", + body: "Interchange documents, stamps and e-signatures generated and shared with transit agents automatically.", + preview: , + }, + { + icon: Receipt, + title: "Billing & payments", + body: "Contract rates, invoices and settlement via Telebirr, CBE Birr, eBirr or card — with full audit trail.", + preview: , + }, + { icon: Truck, + title: "First & last mile", + body: "Request trucking to and from the railhead and see it on the same timeline as the rail leg.", + preview: , }, { - title: "Rail Freight Operations", - description: - "Monitor train schedules, operational performance, maintenance, and corridor activity.", - icon: Train, - }, - { - title: "Smart Analytics Dashboard", - description: - "Visualize operational insights, shipment trends, and corridor performance in real time.", - icon: BarChart3, - }, - { - title: "Enterprise-grade Security", - description: - "Secure portal access, billing workflows, document management, and customer operations.", - icon: ShieldCheck, + icon: ChartColumn, + title: "Operations analytics", + body: "Corridor throughput, on-time performance, fleet utilisation and incident reporting for operators.", + preview: , }, ]; -const corridors = [ - "Addis Ababa → Djibouti", - "Dire Dawa → Djibouti", - "Adama → Dire Dawa", - "Mojo → Djibouti", - "Awash → Holhol", - "Mieso → Aysha", -]; +/* ------------------------------------------------------------------ */ +/* Page */ +/* ------------------------------------------------------------------ */ export default function EDRFreightLandingPage() { return (
+ + {/* Navbar */} -
+
-
-
- -
+ + + + -
-

EDR Freight

- -

+ + EDR Freight + Rail Logistics Platform -

-
-
+ + + -
{/* Hero */} -
-
+
+
+
-
+
-
- - Ethiopia–Djibouti Railway Freight Platform -
+ + + + Live on the Addis Ababa – Djibouti corridor + + -

- Smarter Railway Freight Logistics For Modern Operations -

+ +

+ Move cargo by rail. + Track every wagon. +

+
-

- EDR Freight enables logistics companies and railway operators to - manage shipments, monitor freight corridors, optimize train - operations, and streamline enterprise logistics workflows. -

+ +

+ EDR Freight is the booking, tracking and billing platform for the + Ethio-Djibouti Railway. Book consignments, schedule train sets, + clear customs documents and settle invoices — in one portal. +

+
-
- - Get Started - - - - - Login - -
- -
- {[ - "Real-time Tracking", - "Railway Analytics", - "Secure Operations", - "Multi-corridor Freight", - ].map((item) => ( -
+
+ - - {item} -
- ))} -
+ Book a shipment + + + + + See how it works + +
+ + + +
+ {heroTrust.map((item) => ( + + + {item} + + ))} +
+
- {/* Hero Dashboard Card */} -
-
-
-
-

- Freight Operations + {/* Live tracking card */} + +

+
+
+

CN-2026-08417

+

+ Mojo Dry Port → Djibouti Port · 40ft × 12

- -

Live Statistics

-
- -
+ + + In transit +
-
- {stats.map((item) => { - const Icon = item.icon; +
    + {trackingStops.map((stop, index) => { + const isLast = index === trackingStops.length - 1; + const done = stop.state === "done"; + const current = stop.state === "current"; return ( -
    -
    - -
    +
  1. + + + {!isLast ? ( + + ) : null} + -

    {item.value}

    - -

    - {item.label} -

    -
  2. + + + {stop.name} + + + {stop.time} + + + ); })} -
+ -
-
-
-

- Corridor Performance +

+ {[ + ["Train", "TS-14 · 2 locos"], + ["Wagons", "12 / 12 loaded"], + ["Next gate", "Dewele customs"], + ].map(([key, value]) => ( +
+

+ {key}

- -

- 99% -

+

{value}

- -
- Operational -
-
- -
-
-
+ ))}
+ +
+
-
-
-
- -
+ {/* Stats */} +
+
+ {stats.map((stat, index) => { + const Icon = stat.icon; + const TrendIcon = stat.down ? TrendingDown : TrendingUp; -
-

16 Trains Active

+ return ( + +
+
+ + + -

- Across all freight corridors + {stat.trend} +

+ +

+ {stat.value}

+

+ {stat.label} +

+ + {stat.fleet ? ( +
+ {Array.from({ length: 16 }).map((_, unit) => ( + + ))} +
+ ) : ( +
+ {stat.chart!.map((height, bar) => ( + = stat.chart!.length - 3 + ? "bg-edr-primary" + : "bg-emerald-200" + }`} + style={{ + height: `${(height / 48) * 100}%`, + animationDelay: `${bar * 60}ms`, + }} + /> + ))} +
+ )}
-
-
-
+ + ); + })}
{/* Features */} -
-
-
-
- Platform Features -
- -

- Everything needed for freight operations -

- -

- Centralized railway freight operations with live shipment - visibility, operational monitoring, customer management, and - intelligent logistics insights. +

+
+ +

+ PLATFORM

-
+

+ Everything between the dry port and the quay +

+ -
- {features.map((feature) => { - const Icon = feature.icon; + +

+ One portal for shippers, shipping lines, transit agents and railway + operations. Built around the real workflow of the corridor, not a + generic TMS. +

+
+
- return ( -
-
- +
+ {features.map((feature, index) => { + const Icon = feature.icon; + + return ( + +
+
+ {feature.preview}
-

{feature.title}

+
+
+ + + +

{feature.title}

+
-

- {feature.description} -

-
- ); - })} -
+

+ {feature.body} +

+ + + Learn more + + +
+ + + ); + })} +
+
+ + {/* Showcase */} +
+
+ +
+ Freight train hauling containers along the corridor + +
+ +
+ + + TS-14 · Awash → Mieso · 68 km/h + +
+ +
+ {[ + ["98.4%", "On-time"], + ["312", "Wagons moving"], + ["16h 20m", "Avg transit"], + ].map(([value, key]) => ( +
+

{value}

+

{key}

+
+ ))} +
+
+ + + +

+ LIVE OPERATIONS +

+

+ See the whole train, not just a tracking number +

+

+ GPS from every locomotive, wagon-level loading status, gate events at + Dewele customs and ETA that updates as the train moves. Shippers and + operators look at the same map. +

+ +
    + {[ + { icon: Satellite, text: "Locomotive telemetry every 30 seconds" }, + { icon: BellRing, text: "SMS & in-app alerts on gate and border events" }, + { icon: MapIcon, text: "Shareable tracking link for your consignee" }, + ].map((point) => { + const Icon = point.icon; + + return ( +
  • + + + + {point.text} +
  • + ); + })} +
+
{/* Corridors */} -
+
-
-
-
- Freight Corridors -
+ +

+ NETWORK +

+

+ One line. Two countries. Every station on the timeline. +

+

+ Standard-gauge, fully electrified. Book any origin–destination pair + along the corridor and see wagon position station by station. +

+
-

- Connected logistics infrastructure -

+ {/* Animated corridor */} + +
+ + {stations.map((station) => ( + + ))} +
-

- Efficiently move freight across strategic Ethiopia–Djibouti - railway corridors with operational visibility and optimized - transport coordination. -

- -
- {corridors.map((corridor) => ( -
+ {stations.map((station) => ( + + -
- - {corridor} -
- ))} -
+ {station.name} + + {station.note ? ( + + {station.note} + + ) : null} + + ))}
+
-
-
-
-

- Operational Insights +

+ {lanes.map((lane, index) => ( + +
+

+ {lane.from} + + {lane.to} +

+

+ {lane.transit} transit · {lane.kind}

- -

- Freight Performance -

- -
- -
-
- -
- {[ - { - label: "Bookings Processed", - value: "9,842", - progress: "92%", - }, - { - label: "On-time Shipments", - value: "99%", - progress: "99%", - }, - { - label: "Customer Satisfaction", - value: "99%", - progress: "99%", - }, - ].map((item) => ( -
-
- {item.label} - - - {item.value} - -
- -
-
-
-
- ))} -
- -
-
-
- -
- -
-

- Enterprise-ready Platform -

- -

- Designed for large-scale freight and railway operations. -

-
-
-
-
+ + ))}
+ {/* How it works */} +
+
+ +

+ HOW IT WORKS +

+

+ From booking to delivered in four steps +

+

+ No phone calls, no paper manifests. Every party — shipper, shipping + line, transit agent and station — works from the same record. +

+ + + Create a free account + + +
+ +
+ {steps.map((step, index) => ( + +
+ + {step.num} + + +
+

{step.title}

+

+ {step.body} +

+
+
+
+ ))} +
+
+
+ + {/* Audiences */} +
+ +

+ Built for everyone on the corridor +

+
+ +
+ {audiences.map((audience, index) => { + const Icon = audience.icon; + + return ( + +
+ +

{audience.title}

+

+ {audience.body} +

+ +
    + {audience.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+
+
+ ); + })} +
+
+ {/* Contact */} -
-
-
-
-
- Contact Us -
+
+
+ +

+ CONTACT +

+

+ Let’s move freight smarter +

+

+ Contact EDR Freight for partnership opportunities, enterprise + onboarding, or logistics support. +

-

- Let’s move freight smarter -

+
+ {[ + { icon: Mail, label: "Email", value: "support@edrfreight.com" }, + { icon: Phone, label: "Phone", value: "+251 11 000 0000" }, + { icon: MapPin, label: "Head Office", value: "Addis Ababa, Ethiopia" }, + ].map((row) => { + const Icon = row.icon; -

- Contact EDR Freight for partnership opportunities, enterprise - onboarding, or logistics support. -

- -
-
-
- + return ( +
+ + + +
+

{row.label}

+

{row.value}

+
- -
-

Email

-

- support@edrfreight.com -

-
-
- -
-
- -
- -
-

Phone

-

+251 11 000 0000

-
-
- -
-
- -
- -
-

Head Office

-

- Addis Ababa, Ethiopia -

-
-
-
+ ); + })}
+ - {/* Contact Form */} -
+ +

Send us a message

-

We’ll get back to you as soon as possible.

@@ -504,117 +1129,359 @@ export default function EDRFreightLandingPage() {