Files
edr-platform/apps/edr-passenger-web/portal/README.md
2026-06-02 20:48:14 +03:00

9.2 KiB

EDR Passenger Portal

Modern Next.js 14 web application for the Ethio-Djibouti Railway passenger booking system.

Features

Complete Booking Flow

  1. Search - Find trains by route, date, and passenger count
  2. Results - View available schedules with pricing
  3. Auth Check - Sign in or continue as guest
  4. Passengers - Collect passenger details with Fayda verification
  5. Seats - Select seats with visual seat map
  6. Review - Confirm booking details and fare breakdown
  7. Payment - Choose payment method and process payment
  8. Confirmation - View PNR, tickets with QR codes

Key Capabilities

  • Fayda 2.0 Integration - Ethiopian national ID verification
  • Age-Based Pricing - First child travels free
  • Multi-Currency Support - ETB, DJF, USD display
  • Seat Hold System - 2-hour seat reservation
  • Guest Booking - Book without account, optional registration
  • QR Code Tickets - Digital tickets with QR codes
  • Responsive Design - Mobile-first, works on all devices

Tech Stack

  • Framework: Next.js 14 with App Router
  • Styling: Tailwind CSS
  • State Management:
    • TanStack Query (React Query) for server state
    • Zustand for client state (booking flow, auth, payment)
  • Forms: React Hook Form with Zod validation
  • API Client: Axios with interceptors
  • Date Handling: date-fns
  • QR Codes: qrcode.react

Getting Started

Prerequisites

  • Node.js >= 20.x
  • pnpm >= 9.x
  • EDR Passenger API running on port 3002

Installation

# Install dependencies
pnpm install

# Create environment file
cp .env.example .env.local

# Update .env.local with API URL
NEXT_PUBLIC_API_URL=http://localhost:3002

Development

# Run development server
pnpm dev

# Access at http://localhost:5174

Build

# Build for production
pnpm build

# Start production server
pnpm start

Project Structure

src/
├── app/                      # Next.js App Router pages
│   ├── booking/
│   │   ├── search/          # Search trains
│   │   ├── results/         # Search results
│   │   ├── auth-check/      # Login or guest
│   │   ├── passengers/      # Passenger details + Fayda
│   │   ├── seats/           # Seat selection
│   │   ├── review/          # Booking review
│   │   ├── payment/         # Payment processing
│   │   └── confirmation/    # Booking confirmation
│   ├── login/               # Login page
│   ├── layout.tsx           # Root layout
│   ├── page.tsx             # Home (redirects to search)
│   ├── providers.tsx        # React Query provider
│   └── globals.css          # Global styles
├── components/              # Reusable components
├── lib/                     # Core utilities
│   ├── api-client.ts       # Axios client with interceptors
│   ├── auth-store.ts       # Auth state (Zustand)
│   ├── booking-store.ts    # Booking flow state (Zustand)
│   └── payment-store.ts    # Payment state (Zustand)
├── types/                   # TypeScript types
│   └── index.ts
└── hooks/                   # Custom React hooks

State Management

Booking Store (Zustand)

Persists booking flow state across pages:

  • Search criteria
  • Selected schedule
  • Passenger details
  • Seat hold information
  • Booking ID and PNR
  • Payment method

Auth Store (Zustand)

Manages user authentication:

  • User profile
  • JWT token
  • Login/logout/register
  • Persisted to localStorage

Payment Store (Zustand)

Tracks payment flow:

  • Payment intent ID
  • Payment status
  • Selected currency

API Integration

Endpoints Used

Endpoint Method Purpose
/stations GET Fetch all stations
/search POST Search available trains
/passengers/verify-fayda POST Verify Ethiopian national ID
/seatmap/{scheduleId} GET Get coaches and seats
/seatmap/{scheduleId}/hold POST Hold seats (2 hours)
/bookings/create POST Create booking + generate PNR
/bookings/{id}/confirm PATCH Confirm booking after payment
/payments/intent POST Create payment intent
/auth/login POST User login
/auth/register POST User registration

Booking Flow

  • User selects origin, destination, date, passengers
  • Validates form with Zod schema
  • Stores criteria in booking store
  • Navigates to results

2. Results

  • Fetches schedules from API
  • Displays available trains with pricing
  • User selects a schedule
  • Stores selection and navigates to auth check

3. Auth Check

  • Checks if user is authenticated
  • Offers "Sign In" or "Continue as Guest"
  • Authenticated users can use saved profiles

4. Passengers

  • Collects details for each passenger
  • Ethiopian nationals: Fayda verification
    • Calls /passengers/verify-fayda
    • Auto-fills name and DOB on success
    • Allows manual entry on failure
  • Non-Ethiopians: Passport details
  • Optional account creation checkbox
  • Stores passenger data in booking store

5. Seats

  • Fetches coaches and seat map
  • Visual seat selection (4-column grid)
  • Color-coded seat status:
    • Green: Available
    • Blue: Selected
    • Yellow: Held by others
    • Gray: Booked/Blocked
  • Calls /seatmap/{scheduleId}/hold on selection
  • Stores hold ID and expiry (2 hours)
  • Option to skip (auto-assign)

6. Review

  • Displays trip summary
  • Lists all passengers
  • Shows fare breakdown
  • Displays seat hold countdown timer
  • Calls /bookings/create on confirm
  • Generates 6-character PNR
  • Navigates to payment

7. Payment

  • Displays PNR prominently
  • Payment method selection:
    • Telebirr
    • CBE Birr
    • eBirr
    • Card
    • Wallet
  • Shows order summary
  • Calls /payments/intent
  • Processes payment (simulated for now)

8. Confirmation

  • Calls /bookings/{id}/confirm
  • Displays success message
  • Shows PNR with copy button
  • Generates QR codes for each ticket
  • Lists all passenger tickets
  • Download and share options
  • "Book Another Trip" button clears state

Form Validation

All forms use React Hook Form + Zod:

// Example: Search form validation
const searchSchema = z.object({
  originStationId: z.string().min(1, 'Please select origin'),
  destinationStationId: z.string().min(1, 'Please select destination'),
  departureDate: z.string().min(1, 'Please select date'),
  adultCount: z.number().min(1).max(9),
  childCount: z.number().min(0).max(9),
  nationality: z.enum(['ETHIOPIAN', 'DJIBOUTIAN', 'OTHER']),
}).refine((data) => data.originStationId !== data.destinationStationId, {
  message: 'Origin and destination must be different',
  path: ['destinationStationId'],
});

Styling

Tailwind Utility Classes

Custom component classes in globals.css:

.btn-primary     /* Primary action button */
.btn-secondary   /* Secondary action button */
.input-field     /* Form input styling */
.card            /* Card container */

Theme Colors

Primary brand color: rgb(20, 113, 76) (EDR green)

Shades available: 50, 100, 200, 300, 400, 500, 600, 700, 800, 900

Error Handling

  • Network errors: Retry button with exponential backoff
  • Validation errors: Inline field-level messages
  • API errors: User-friendly error messages
  • Seat hold expiry: Alert and re-selection option
  • 401 Unauthorized: Auto-redirect to login

Accessibility

  • Semantic HTML elements
  • ARIA labels on interactive elements
  • Keyboard navigation support
  • Color contrast WCAG AA compliant
  • Screen reader announcements for validation errors

Mobile Responsiveness

  • Mobile-first design approach
  • Responsive grid layouts (md: breakpoint)
  • Touch-friendly button sizes
  • Scrollable seat maps on small screens
  • Optimized forms for mobile input

Testing Checklist

  • Search form validation
  • Results display and selection
  • Guest vs authenticated flow
  • Fayda verification (Ethiopian)
  • Passport form (non-Ethiopian)
  • Seat selection and hold
  • Hold countdown timer
  • PNR generation
  • Payment method selection
  • Confirmation with QR codes
  • Mobile responsiveness
  • Error states
  • Back navigation

Environment Variables

NEXT_PUBLIC_API_URL=http://localhost:3002  # Passenger API URL

Known Limitations

  1. Payment processing is simulated (no real provider integration yet)
  2. Ticket PDF download not implemented (placeholder button)
  3. Share booking feature not implemented (placeholder button)
  4. Seat hold release on expiry requires manual refresh
  5. No internationalization (English only)

Future Enhancements

  • Real payment provider integration (Stripe, Telebirr, etc.)
  • PDF ticket generation and download
  • Email/SMS sharing functionality
  • Real-time seat availability updates (WebSocket)
  • Booking history page
  • User profile management
  • Saved passenger profiles
  • Multi-language support (Amharic, Arabic)
  • Accessibility improvements
  • Analytics tracking

Contributing

Follow the EDR Platform standards in CLAUDE.md:

  • TypeScript strict mode
  • Conventional commits
  • ESLint + Prettier
  • pnpm only (no npm/yarn)

License

Proprietary - Ethio-Djibouti Railway Platform

Support

For issues or questions, contact the EDR Platform team.