Update CHECKLIST.md and REWRITE_PLAN.md to reflect the current post-rewrite status and remaining tasks. - Add input validation to /api/hafas route to enforce request shape and cap results - Fix SSR crash in useBikeRoute by using relative fetch URLs - Wire CalendarPanel to fetch calendar events and merge them into the global events store
TimeToLeave
Know when to leave the house so you're never late for your next meeting.
TimeToLeave is a web app that tells you when to leave for upcoming events based on real-time Austrian train (ÖBB) schedules — and optionally compares train journeys with bicycle routes.
Features
- Real-time train journeys — Query the ÖBB HAFAS API for live departure times, delays, and platform information.
- Calendar import — Import events from any ICS calendar (CalDAV, Google Calendar, etc.).
- Bicycle routing — Compare train journeys with bicycle routes powered by OSRM, including distance, duration, and turn-by-turn directions.
- Train vs bicycle comparison — See both travel modes side by side for each event, so you can decide what works best.
- Calendar month view — Browse events in a month grid, see event indicators on dates, and drill into train/bike details per day.
- Geolocation support — Use your browser's current location as the origin station.
- Countdown & status indicators — Visual countdown timers and live status badges so you know exactly when to go.
Architecture
Built with Next.js App Router (v16), React 19, TypeScript, and Tailwind CSS v4.
src/
├── app/ # Next.js App Router pages & layout
│ ├── api/ # Server-side API routes
│ │ ├── hafas/ # ÖBB train journey search
│ │ ├── calendar/ # ICS calendar parsing
│ │ ├── geocode/ # Nominatim geocoding
│ │ ├── bike-route/ # OSRM bicycle routing
│ │ └── health/ # Server health check
│ ├── ui/ # Shared UI components
│ ├── page.tsx # Home page
│ └── layout.tsx # Root layout
├── hooks/ # Custom React hooks
│ ├── useJourneys.ts # Fetch train journeys per event
│ ├── useBikeRoute.ts # Fetch bicycle routes
│ ├── useCalendar.ts # Calendar import & parsing
│ ├── useEventsStore.tsx # Events state management
│ ├── useGeolocation.ts # Browser geolocation
│ ├── useOriginStation.ts # Origin station selection
│ ├── useClock.ts # Live clock for countdowns
│ └── useServerHealth.ts # API health monitoring
├── lib/ # Shared utilities & clients
│ ├── hafas-client.ts # ÖBB HAFAS API client
│ ├── geocoding-client.ts # Nominatim geocoding client
│ ├── bike-routing-client.ts# OSRM bicycle routing client
│ ├── calendar-utils.ts # ICS parsing utilities
│ ├── countdown-utils.ts # Countdown time calculations
│ ├── formatting.ts # Date/time formatting helpers
│ └── constants.ts # App-wide constants
├── types/ # TypeScript type definitions
└── test/ # Test utilities
Getting Started
Prerequisites
- Node.js 20+
- npm (or yarn / pnpm)
Installation
npm install
Development
npm run dev
The app will be available at http://localhost:3000.
Build & Start
npm run build
npm start
Docker Deployment
Build and run with Docker Compose:
docker compose up --build -d
The app will be available on port 3000 by default. Override the port or any other variable by creating a .env file at the project root:
PORT=8080
HAFAS_URL=https://fahrplan.oebb.at/bin/mgate.exe
NOMINATIM_URL=https://nominatim.openstreetmap.org
NOMINATIM_USER_AGENT=TimeToLeave/2.0
OSRM_URL=https://router.project-osrm.org
The Dockerfile uses a 3-stage build (deps → builder → runner) with output: "standalone", producing a small final image that runs as a non-root user. Stop and remove the container with docker compose down.
Testing
npm test # Run tests once
npm run test:watch # Run tests in watch mode
Linting & Type Checking
npm run lint
npm run typecheck
Environment Variables
The following environment variables can be configured (defaults are provided for development):
| Variable | Description | Default |
|---|---|---|
HAFAS_URL |
ÖBB HAFAS API endpoint | https://fahrplan.oebb.at/bin/mgate.exe |
NOMINATIM_URL |
Nominatim geocoding endpoint | https://nominatim.openstreetmap.org |
NOMINATIM_USER_AGENT |
User-Agent for Nominatim requests | TimeToLeave/2.0 |
OSRM_URL |
OSRM routing endpoint | https://router.project-osrm.org |
Tech Stack
| Category | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19 + Tailwind CSS v4 |
| Language | TypeScript 5 |
| Train Data | ÖBB HAFAS API |
| Geocoding | Nominatim |
| Bicycle Routing | OSRM |
| Calendar | node-ical |
| Dates | date-fns |
| Testing | Vitest + React Testing Library |
How It Works
- Add events — Manually enter events or import from an ICS calendar URL.
- Pick an origin — Use your current location or search for a departure station.
- Get journey info — The app queries ÖBB for real-time train schedules and optionally calculates bicycle routes via OSRM.
- Decide when to leave — Each event shows a countdown, the best available train, and (if enabled) a bicycle alternative with estimated travel time.
License
MIT