diff --git a/apps/edr-passenger-web/portal/public/README.md b/apps/edr-passenger-web/portal/public/README.md new file mode 100644 index 000000000..7ea9c3ab4 --- /dev/null +++ b/apps/edr-passenger-web/portal/public/README.md @@ -0,0 +1,12 @@ +# Banner Image + +Place your banner image as `banner.jpg` in this directory. + +## Recommended Specifications: +- **Filename**: `banner.jpg` (or `banner.png`) +- **Dimensions**: 1920x1080px or higher +- **Aspect Ratio**: 16:9 or similar +- **Content**: Railway/train themed image, Ethio-Djibouti Railway scenery +- **Format**: JPG or PNG + +The image will be used as a background on the login page with a green overlay. diff --git a/apps/edr-passenger-web/portal/public/banner.jpg b/apps/edr-passenger-web/portal/public/banner.jpg new file mode 100644 index 000000000..09c6add92 Binary files /dev/null and b/apps/edr-passenger-web/portal/public/banner.jpg differ diff --git a/apps/edr-passenger-web/portal/src/app/about/page.tsx b/apps/edr-passenger-web/portal/src/app/about/page.tsx new file mode 100644 index 000000000..00430aadc --- /dev/null +++ b/apps/edr-passenger-web/portal/src/app/about/page.tsx @@ -0,0 +1,411 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { getTranslation, Language, useLanguage } from '@/lib/i18n'; +import Link from 'next/link'; +import { Target, Globe, Leaf, Users } from 'lucide-react'; + +const styles = ` + .about-hero { + padding: 60px 20px; + background: linear-gradient(to bottom right, rgb(20, 113, 76), transparent); + text-align: center; + color: #111827; + } + + .dark .about-hero { + color: #f3f4f6; + } + + .about-hero h1 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 16px; + color: #111827; + } + + .dark .about-hero h1 { + color: #f3f4f6; + } + + .about-hero p { + font-size: 1.125rem; + color: #6b7280; + } + + .dark .about-hero p { + color: #9ca3af; + } + + .values-grid { + max-width: 80rem; + margin: 0 auto; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 32px; + padding: 60px 20px; + background-color: white; + } + + .dark .values-grid { + background-color: #111827; + } + + .value-card { + background: white; + border: 1px solid #e5e7eb; + border-radius: 18px; + padding: 24px; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + } + + .dark .value-card { + background: #1f2937; + border-color: #374151; + } + + .value-card:hover { + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + } + + .value-icon { + width: 48px; + height: 48px; + background: rgb(20, 113, 76); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; + } + + .value-card h3 { + font-size: 1.25rem; + font-weight: 700; + margin-bottom: 12px; + color: #111827; + } + + .dark .value-card h3 { + color: #f3f4f6; + } + + .value-card p { + font-size: 0.875rem; + color: #6b7280; + } + + .dark .value-card p { + color: #9ca3af; + } + + .stats-section { + padding: 60px 20px; + background-color: #f9fafb; + } + + .dark .stats-section { + background-color: #0f1117; + } + + .stats-container { + max-width: 80rem; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + gap: 32px; + text-align: center; + } + + .stat { + padding: 20px; + } + + .stat-number { + font-size: 2rem; + font-weight: 700; + color: rgb(20, 113, 76); + margin-bottom: 8px; + } + + .stat-label { + font-size: 0.875rem; + color: #6b7280; + } + + .dark .stat-label { + color: #9ca3af; + } + + .timeline-section { + padding: 60px 20px; + background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%); + } + + .dark .timeline-section { + background: linear-gradient(135deg, #111827 0%, #0f1117 100%); + } + + .timeline-title { + text-align: center; + font-size: 2rem; + font-weight: 700; + margin-bottom: 48px; + color: #111827; + } + + .dark .timeline-title { + color: #f3f4f6; + } + + .timeline { + max-width: 48rem; + margin: 0 auto; + position: relative; + } + + .timeline::before { + content: ''; + position: absolute; + left: 8px; + top: 0; + bottom: 0; + width: 2px; + background: linear-gradient(180deg, rgb(20, 113, 76), rgb(20, 113, 76) 50%, transparent); + } + + .timeline-item { + display: flex; + margin-bottom: 40px; + position: relative; + padding-left: 56px; + animation: slideInLeft 0.6s ease-out forwards; + opacity: 0; + } + + .timeline-item:nth-child(1) { animation-delay: 0.1s; } + .timeline-item:nth-child(2) { animation-delay: 0.2s; } + .timeline-item:nth-child(3) { animation-delay: 0.3s; } + .timeline-item:nth-child(4) { animation-delay: 0.4s; } + .timeline-item:nth-child(5) { animation-delay: 0.5s; } + + @keyframes slideInLeft { + from { + opacity: 0; + transform: translateX(-20px); + } + to { + opacity: 1; + transform: translateX(0); + } + } + + .timeline-dot { + position: absolute; + left: -4px; + top: 8px; + width: 24px; + height: 24px; + background: white; + border-radius: 50%; + border: 3px solid rgb(20, 113, 76); + box-shadow: 0 0 0 2px rgb(20, 113, 76), 0 4px 12px rgba(20, 113, 76, 0.3); + transition: all 0.3s ease; + } + + .timeline-item:hover .timeline-dot { + box-shadow: 0 0 0 2px rgb(20, 113, 76), 0 8px 24px rgba(20, 113, 76, 0.5); + transform: scale(1.15); + } + + .dark .timeline-dot { + background: #1f2937; + } + + .timeline-content { + background: white; + border-radius: 12px; + padding: 20px 24px; + border: 2px solid transparent; + border-left: 4px solid rgb(20, 113, 76); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + transition: all 0.3s ease; + flex: 1; + } + + .timeline-item:hover .timeline-content { + border-color: rgb(20, 113, 76); + box-shadow: 0 8px 24px rgba(20, 113, 76, 0.15); + transform: translateY(-4px); + } + + .dark .timeline-content { + background: #1f2937; + border-left-color: rgb(20, 113, 76); + } + + .timeline-year { + font-weight: 700; + color: rgb(20, 113, 76); + font-size: 1.125rem; + display: flex; + align-items: center; + gap: 8px; + } + + .timeline-year::before { + content: '📅'; + } + + .timeline-event { + color: #6b7280; + margin-top: 8px; + font-size: 0.95rem; + font-weight: 500; + } + + .dark .timeline-event { + color: #d1d5db; + } + + .cta-blue { + background-color: rgb(20, 113, 76); + color: white; + padding: 60px 20px; + text-align: center; + } + + .cta-blue h2 { + font-size: 2rem; + font-weight: 700; + margin-bottom: 16px; + } + + .cta-blue p { + font-size: 1.125rem; + margin-bottom: 32px; + max-width: 42rem; + margin-left: auto; + margin-right: auto; + } + + .button-white { + display: inline-block; + padding: 16px 32px; + background-color: white; + color: rgb(20, 113, 76); + font-weight: 700; + border-radius: 12px; + text-decoration: none; + transition: all 0.2s; + } + + .button-white:hover { + transform: scale(1.05); + } + + @media (max-width: 768px) { + .values-grid { + grid-template-columns: 1fr; + } + + .about-hero h1 { + font-size: 1.875rem; + } + } +`; + +export default function About() { + const [lang, setLang] = useState('en'); + const { getLang } = useLanguage(); + const t = (key: string) => getTranslation(lang, key); + + useEffect(() => { + setLang(getLang()); + const handleLanguageChange = (e: any) => setLang(e.detail); + window.addEventListener('languageChange', handleLanguageChange); + return () => window.removeEventListener('languageChange', handleLanguageChange); + }, [getLang]); + + const values = [ + { icon: Target, title: t('about.mission'), desc: t('about.missionText') }, + { icon: Globe, title: t('about.network'), desc: t('about.networkText') }, + { icon: Users, title: t('about.comfort'), desc: t('about.comfortText') }, + { icon: Leaf, title: t('about.eco'), desc: t('about.ecoText') }, + ]; + + return ( + <> + +
+
+

{t('about.title')}

+

{t('about.subtitle')}

+
+ +
+ {values.map((value, idx) => { + const Icon = value.icon; + return ( +
+
+ +
+

{value.title}

+

{value.desc}

+
+ ); + })} +
+ +
+
+
+
21
+
Railway Stations
+
+
+
360+
+
Comfortable Seats
+
+
+
3
+
Seat Classes
+
+
+
24/7
+
Customer Support
+
+
+
+ +
+

Our Journey

+
+ {[ + { year: '2020', event: 'EDR Platform Launched' }, + { year: '2021', event: 'Reached 10,000+ Passengers' }, + { year: '2022', event: 'Introduced Multi-Currency Support' }, + { year: '2023', event: 'Launched Loyalty Program' }, + { year: '2024', event: 'Age-Based Pricing & Verifayda Integration' }, + ].map((item, idx) => ( +
+
+
+
{item.year}
+
{item.event}
+
+
+ ))} +
+
+ +
+

Join Our Community

+

Be part of the modern railway revolution in East Africa.

+ Book Your First Journey +
+
+ + ); +} diff --git a/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx index e4a829320..8addae4de 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/confirmation/page.tsx @@ -82,7 +82,7 @@ export default function ConfirmationPage() { return (
-
+
{/* Success Header */}
diff --git a/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx index bad056215..79649c4b0 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/passengers/page.tsx @@ -343,7 +343,7 @@ export default function PassengersPage() { return (
-
+

Passenger details

diff --git a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx index e74c5b550..34f1fada1 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/payment/page.tsx @@ -160,7 +160,7 @@ export default function PaymentPage() { return (
-
+

Complete payment

Booking reference: {pnr} diff --git a/apps/edr-passenger-web/portal/src/app/booking/results/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/results/page.tsx index c3054f23b..c8161bb34 100644 --- a/apps/edr-passenger-web/portal/src/app/booking/results/page.tsx +++ b/apps/edr-passenger-web/portal/src/app/booking/results/page.tsx @@ -155,7 +155,7 @@ export default function ResultsPage() { return (

-
+
+ + {/* Passenger Dropdown Menu */} + {isPassengerOpen && ( + <> +
setIsPassengerOpen(false)} /> +
+ {/* Adults */} +
+
+
+
Adults
+
≥5 years
+
+
+ + {adultCount || 1} + +
+
+
+ + {/* Children */} +
+
+
+
Children
+
<5 years • First free
+
+
+ + {childCount || 0} + +
+
+
+
+ + )} +
+ + {/* Nationality */}
- -
-
-
-
Adults
-
≥5 years
-
-
- - {adultCount || 1} - -
-
-
-
-
Children
-
<5 years • First child free
-
-
- - {childCount || 0} - -
-
-
+ + +
+ + {/* Promo Code */} +
+ +
-
- - + {/* Third Row: Search Button */} +
+
- -
+ {/* Popular Routes */}

Popular Routes

@@ -330,7 +356,7 @@ export default function SearchPage() {
{route.from}
- +
{route.to}
@@ -341,29 +367,6 @@ export default function SearchPage() {
-
-
-
- -
-

Modern Fleet

-

Comfortable trains with modern amenities

-
-
-
- -
-

21 Stations

-

Connecting Ethiopia and Djibouti

-
-
-
- -
-

Easy Booking

-

Book tickets in just a few clicks

-
-
diff --git a/apps/edr-passenger-web/portal/src/app/contact/page.tsx b/apps/edr-passenger-web/portal/src/app/contact/page.tsx new file mode 100644 index 000000000..f2c0f4296 --- /dev/null +++ b/apps/edr-passenger-web/portal/src/app/contact/page.tsx @@ -0,0 +1,375 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { getTranslation, Language, useLanguage } from '@/lib/i18n'; +import { Phone, Mail, MapPin, Send, Loader } from 'lucide-react'; + +const styles = ` + .contact-hero { + padding: 60px 20px; + background: linear-gradient(to bottom right, rgb(20, 113, 76), transparent); + text-align: center; + color: #111827; + } + + .dark .contact-hero { + color: #f3f4f6; + } + + .contact-hero h1 { + font-size: 2.5rem; + font-weight: 700; + margin-bottom: 16px; + color: #111827; + } + + .dark .contact-hero h1 { + color: #f3f4f6; + } + + .contact-hero p { + font-size: 1.125rem; + color: #6b7280; + } + + .dark .contact-hero p { + color: #9ca3af; + } + + .contact-grid { + max-width: 80rem; + margin: 0 auto; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 24px; + padding: 60px 20px; + background-color: white; + } + + .dark .contact-grid { + background-color: #111827; + } + + .contact-card { + background: white; + border: 2px solid #f3f4f6; + border-radius: 18px; + padding: 24px; + cursor: pointer; + text-align: center; + transition: all 0.2s; + } + + .dark .contact-card { + background: #1f2937; + border-color: #374151; + } + + .contact-card:hover { + border-color: rgb(20, 113, 76); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + } + + .contact-icon { + width: 48px; + height: 48px; + background: rgb(20, 113, 76); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + margin: 0 auto 16px; + } + + .contact-card h3 { + font-weight: 700; + margin-bottom: 8px; + color: #111827; + } + + .dark .contact-card h3 { + color: #f3f4f6; + } + + .contact-card p { + font-size: 0.875rem; + color: #6b7280; + } + + .dark .contact-card p { + color: #9ca3af; + } + + .contact-card a { + color: #6b7280; + text-decoration: none; + } + + .contact-card a:hover { + color: rgb(20, 113, 76); + } + + .form-section { + padding: 60px 20px; + background-color: #f9fafb; + } + + .dark .form-section { + background-color: #0f1117; + } + + .form-container { + max-width: 42rem; + margin: 0 auto; + background: white; + border-radius: 18px; + padding: 32px; + border: 1px solid #e5e7eb; + } + + .dark .form-container { + background: #1f2937; + border-color: #374151; + } + + .form-container h2 { + font-size: 1.5rem; + font-weight: 700; + margin-bottom: 24px; + color: #111827; + } + + .dark .form-container h2 { + color: #f3f4f6; + } + + .form-group { + margin-bottom: 20px; + } + + .form-group label { + display: block; + font-size: 0.875rem; + font-weight: 500; + color: #374151; + margin-bottom: 8px; + } + + .dark .form-group label { + color: #d1d5db; + } + + .form-group input, + .form-group textarea { + width: 100%; + padding: 12px 16px; + border: 2px solid #e5e7eb; + border-radius: 12px; + font-size: 1rem; + font-family: inherit; + transition: all 0.2s; + box-sizing: border-box; + background: white; + color: #111827; + } + + .dark .form-group input, + .dark .form-group textarea { + background: #111827; + color: #f3f4f6; + border-color: #374151; + } + + .form-group input:focus, + .form-group textarea:focus { + outline: none; + border-color: rgb(20, 113, 76); + box-shadow: 0 0 0 3px rgba(20, 113, 76, 0.1); + } + + .form-submit { + width: 100%; + padding: 14px 20px; + background-color: rgb(20, 113, 76); + color: white; + border: none; + border-radius: 12px; + font-weight: 700; + cursor: pointer; + transition: all 0.2s; + display: flex; + align-items: center; + justify-content: center; + gap: 8px; + margin-top: 8px; + } + + .form-submit:hover { + background-color: rgb(16, 89, 60); + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); + } + + .form-submit:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .alert { + padding: 12px 16px; + border-radius: 8px; + margin-bottom: 16px; + font-size: 0.875rem; + } + + .alert-success { + background-color: #dbeafe; + color: #1e40af; + } + + .dark .alert-success { + background-color: rgba(20, 113, 76, 0.1); + color: #a7f3d0; + } + + .alert-error { + background-color: #fee2e2; + color: #991b1b; + } + + .dark .alert-error { + background-color: rgba(239, 68, 68, 0.1); + color: #fca5a5; + } +`; + +export default function Contact() { + const [lang, setLang] = useState('en'); + const [formData, setFormData] = useState({ name: '', email: '', subject: '', message: '' }); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null); + const { getLang } = useLanguage(); + const t = (key: string) => getTranslation(lang, key); + + useEffect(() => { + setLang(getLang()); + const handleLanguageChange = (e: any) => setLang(e.detail); + window.addEventListener('languageChange', handleLanguageChange); + return () => window.removeEventListener('languageChange', handleLanguageChange); + }, [getLang]); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + await new Promise(resolve => setTimeout(resolve, 1500)); + setMessage({ type: 'success', text: t('contact.success') }); + setFormData({ name: '', email: '', subject: '', message: '' }); + } catch (error) { + setMessage({ type: 'error', text: t('contact.error') }); + } finally { + setLoading(false); + } + }; + + const contactInfo = [ + { icon: Phone, title: t('contact.phone'), value: '+251 911 000 000', link: 'tel:+251911000000' }, + { icon: Mail, title: t('contact.email'), value: 'support@edr.et', link: 'mailto:support@edr.et' }, + { icon: MapPin, title: t('contact.address'), value: 'Addis Ababa, Ethiopia', link: '#' }, + ]; + + return ( + <> + +
+
+

{t('contact.title')}

+

{t('contact.subtitle')}

+
+ +
+ {contactInfo.map((info, idx) => { + const Icon = info.icon; + return ( + +
+ +
+

{info.title}

+

{info.value}

+
+ ); + })} +
+ +
+
+

{t('contact.form')}

+ + {message && ( +
+ {message.text} +
+ )} + +
+
+ + setFormData({ ...formData, name: e.target.value })} + /> +
+ +
+ + setFormData({ ...formData, email: e.target.value })} + /> +
+ +
+ + setFormData({ ...formData, subject: e.target.value })} + /> +
+ +
+ +