# TimeToLeave TimeToLeave is a departure planner for calendar-driven travel. It imports events with locations, resolves the closest public-transport station, checks live ÖBB HAFAS and Wiener Linien data, and shows when to leave by train or bike. ![TimeToLeave Logo](apps/web/public/timetoleave_logo.png) ![Platform](https://img.shields.io/badge/platform-Web_%26_Mobile-blue) ![Next.js](https://img.shields.io/badge/Next.js-16.2-green) ![React Native](https://img.shields.io/badge/React%20Native-0.81-blue) ![Expo](https://img.shields.io/badge/Expo-54-black) ![TypeScript](https://img.shields.io/badge/TypeScript-5-blue) ## What It Does 1. Imports events from ICS URLs, ICS files, Google Calendar on web, or native device calendars on mobile. 2. Stores events locally in browser `localStorage` or mobile `AsyncStorage`. 3. Uses browser or device geolocation, a saved station, or the default Mödling origin. 4. Geocodes event destinations and resolves nearby stations through HAFAS `LocMatch`. 5. Searches ÖBB HAFAS journeys, enriches train data with the ÖBB GTFS fallback when available, and shows real-time delays and cancellations. 6. Calculates bike and final walking routes through OSRM. 7. Shows a live leave-by countdown and optional browser/mobile notifications. ## Repository Layout | Path | Purpose | | --- | --- | | `apps/web/` | Next.js 16 App Router web UI plus backend proxy routes for HAFAS, geocoding, routing, calendar parsing, Google Calendar, and Wiener Linien. | | `apps/mobile/` | Expo 54 / React Native 0.81 mobile app with native calendar, location, notification, and local storage integrations. | | `packages/core/` | Shared types, defaults, HAFAS time parsing, journey parsing/scoring, countdown, formatting, and status utilities. | | `packages/api-client/` | Shared client for calling the web app's `/api/*` backend routes from web hooks and the mobile app. | | `docs/` | Architecture, development, API, user, and codebase reference documentation. | ## Prerequisites - Node.js 20 or newer - npm 9 or newer - For mobile native builds: Expo/EAS prerequisites plus Android Studio or Xcode as needed ## Setup ```bash npm install cp .env.example .env ``` The web app reads environment variables from the workspace process. For deployment, configure the same values in the hosting environment. Important variables: | Variable | Purpose | | --- | --- | | `HAFAS_URL` | ÖBB HAFAS endpoint. Defaults to `https://fahrplan.oebb.at/bin/mgate.exe`. | | `NOMINATIM_URL` and `NOMINATIM_USER_AGENT` | Geocoding endpoint and required user agent. | | `OSRM_URL` | Routing endpoint used for bike and foot profiles. | | `WIENER_LINIEN_API_URL` | Wiener Linien live data base URL. | | `OEBB_GTFS_URL` | Optional ÖBB GTFS ZIP used to enrich HAFAS train metadata. | | `CORS_ALLOWED_ORIGINS` | Comma-separated origins allowed to call `/api/*`. | | `DEPLOYMENT_URL` | Public base URL used by Google OAuth redirects. | | `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI` | Required for Google Calendar sync on web. | | `EXPO_PUBLIC_API_BASE_URL` | Mobile backend URL. Set this for device builds so the app can reach the deployed web backend. | ## Development | Command | Description | | --- | --- | | `npm run dev` | Start the Next.js web app on `http://localhost:3000`. | | `npm run dev:mobile` | Start the Expo development server. | | `npm run build` | Build the web app. | | `npm run start` | Start the built web app. | | `npm run test` | Run web Vitest and mobile Jest suites. | | `npm run lint` | Run ESLint across web, mobile, core, and api-client workspaces. | | `npm run typecheck` | Run TypeScript checks across all workspaces. | ## Web App Current user-facing routes: | Route | Description | | --- | --- | | `/` | Departure desk. Shows the next upcoming event, leave-by status, transport mode selector, train journeys, bike route, final walk, and nearby Wiener Linien departures. | | `/calendar` | Calendar import and management view with URL, file, and Google Calendar tabs plus batch destination editing. | The add/edit event UI is a modal component, not a standalone page route. ## Backend Proxy Routes All backend routes live under `apps/web/src/app/api/` and are protected by strict CORS plus per-IP rate limiting in `apps/web/src/proxy.ts`. | Endpoint | Methods | Purpose | | --- | --- | --- | | `/api/health` | `GET` | Returns `{ ok, ts, version }`. | | `/api/hafas` | `GET`, `POST` | Convenience journey search or validated HAFAS relay for `TripSearch` and `LocMatch`. | | `/api/geocode` | `GET` | Forward geocoding through Nominatim. | | `/api/bike-route` | `GET` | OSRM bicycle route between two coordinates. | | `/api/walk-route` | `GET` | OSRM foot route between two coordinates. | | `/api/calendar` | `GET` | Fetch and parse an allowed remote ICS URL. | | `/api/calendar/parse` | `POST` | Parse uploaded/raw ICS text. | | `/api/calendar/google` | `GET` | Fetch Google Calendar events using OAuth cookies. | | `/api/auth/google` | `GET` | Start Google OAuth. | | `/api/auth/google/callback` | `GET` | Complete Google OAuth and store token cookie. | | `/api/auth/google/status` | `GET` | Report Google configuration and connection state. | | `/api/auth/google/disconnect` | `POST` | Delete the Google token cookie. | | `/api/wienerlinien/stops` | `GET` | Find nearby Wiener Linien stops. | | `/api/wienerlinien/monitor` | `GET` | Fetch and flatten live stop departures. | ## Mobile App The mobile app includes event list, add/edit event, event detail, calendar import, and settings screens. It supports: - Native calendar sync for the next 30 days. - Calendar-source selection, including CalDAV/DAVx, Apple, Google, Exchange, subscribed, and local calendars when exposed by the device. - Saved origin station with current-location lookup. - Train, bike, walking, and Wiener Linien live sections on event detail. - Local notifications scheduled from stored event/settings data. - Dark/light theme toggle. ## Documentation Start with [docs/README.md](docs/README.md), then use: - [Architecture](docs/ARCHITECTURE.md) - [Development Guide](docs/DEVELOPMENT.md) - [Core & API Client Reference](docs/API_REFERENCE.md) - [User Guide](docs/USER_GUIDE.md) - [Codebase Function Guide](docs/CODEBASE_FUNCTION_GUIDE.md) ## Important Implementation Notes - HAFAS date/time values are Vienna-local strings. Use `parseHafasTime()` and `hafasDateTime()` from `@timetoleave/core`; avoid ad hoc `Date` parsing for HAFAS payloads. - Remote calendar URLs are restricted to known calendar providers and private/reserved hosts are blocked. - Mobile devices must use a reachable `EXPO_PUBLIC_API_BASE_URL`; same-origin empty base URLs only work in the web app. - This repo uses Next.js 16. Before changing Next.js routing, middleware/proxy, or framework conventions, read the relevant guide in `node_modules/next/dist/docs/`.