update documentation
This commit is contained in:
+110
-102
@@ -1,149 +1,157 @@
|
||||
# Core & API Client Reference
|
||||
|
||||
This document provides a detailed reference for the internal packages: `@timetoleave/core` and `@timetoleave/api-client`.
|
||||
This reference covers the two shared packages: `@timetoleave/core` and `@timetoleave/api-client`.
|
||||
|
||||
## 📦 `@timetoleave/core`
|
||||
## `@timetoleave/core`
|
||||
|
||||
The core package contains all domain types, status calculation logic, HAFAS time parsing, and formatting utilities. It has zero runtime dependencies outside of the standard library.
|
||||
The core package contains shared types, defaults, pure utilities, HAFAS parsing, and journey scoring. It has no first-party dependency on either app.
|
||||
|
||||
### Types (`types.ts`)
|
||||
### Main Types
|
||||
|
||||
| Interface | Description |
|
||||
| :--- | :--- |
|
||||
| `Event` | Represents a calendar event with an assigned destination and event time. |
|
||||
| `CalendarEvent` | Raw calendar event data (typically `string` dates before conversion to `Event`). |
|
||||
| `Station` | A transport station with a name, HAFAS `extId`, and optional coordinates. |
|
||||
| `Journey` | A public transport connection including scheduled/real departure/arrival, delay, platform, and cancellation status. |
|
||||
| `BikeRoute` / `WalkRoute` | Routing data including total distance, duration, and step-by-step instructions. |
|
||||
| `CountdownInfo` | Output of the countdown utility containing a label, color code, and urgency flag. |
|
||||
| `ReminderSettings` | User preferences for buffers, walking/bike options, and reminder toggles. |
|
||||
| `WienerLinienDeparture` | Specific types for WienerLinien (Vienna public transport) monitor responses. |
|
||||
| Type | Description |
|
||||
| --- | --- |
|
||||
| `Event` | Locally stored app event with `eventTime: Date`. |
|
||||
| `CalendarEvent` | API-safe calendar event with `eventTime: string`. |
|
||||
| `Station` | HAFAS station identity with optional coordinates. |
|
||||
| `Journey` | Parsed transit journey with scheduled/real departure and arrival, delay, platform, changes, train labels, and cancellation state. |
|
||||
| `BikeRoute`, `BikeStep` | OSRM bicycle route summary and step data. |
|
||||
| `WalkRoute`, `WalkStep` | OSRM foot route summary and step data. |
|
||||
| `CountdownInfo` | Countdown label, color key, and urgency flag. |
|
||||
| `ReminderSettings` | Buffer, notification, walking, and bike visibility settings. |
|
||||
| `GeocodeResult` | Nominatim-style coordinate result. |
|
||||
| `NearbyStop`, `WienerLinien*` | Vienna stop and departure response shapes. |
|
||||
| `CalendarAccountType`, `CalendarSourceInfo`, `SelectableCalendar` | Mobile native-calendar selection metadata. |
|
||||
|
||||
### HAFAS Time Utilities (`hafas-time.ts`)
|
||||
### Defaults
|
||||
|
||||
HAFAS timestamps are strictly tied to the `Europe/Vienna` timezone (CET/CEST). These utilities handle the bi-directional conversion between UTC JavaScript `Date` objects and HAFAS strings, correctly handling Daylight Saving Time (DST) transitions.
|
||||
`packages/core/src/defaults.ts` exports the shared fallback origin:
|
||||
|
||||
- `DEFAULT_ORIGIN_ADDRESS`: `Goethegasse 36, 2340 Moedling`
|
||||
- `DEFAULT_ORIGIN_LAT`: `48.0806926`
|
||||
- `DEFAULT_ORIGIN_LNG`: `16.2908052`
|
||||
- `DEFAULT_ORIGIN_STATION_NAME`: `Mödling Bahnhof`
|
||||
- `DEFAULT_ORIGIN_STATION_EXT_ID`: `1231701`
|
||||
- `DEFAULT_ORIGIN_STATION`: assembled `Station`
|
||||
|
||||
### HAFAS Time Utilities
|
||||
|
||||
HAFAS date/time strings are Vienna-local values. Use these helpers for every HAFAS request/response conversion.
|
||||
|
||||
```typescript
|
||||
import { parseHafasTime, hafasDateTime } from '@timetoleave/core';
|
||||
import { hafasDateTime, parseHafasTime } from "@timetoleave/core";
|
||||
|
||||
// Parse HAFAS date ("YYYYMMDD") and time ("HHMMSS") into a UTC Date object
|
||||
const dateObj = parseHafasTime("20231027", "143000");
|
||||
|
||||
// Convert a UTC Date object back to HAFAS date and time strings
|
||||
const { date, time } = hafasDateTime(dateObj);
|
||||
// Returns: { date: "20231027", time: "143000" }
|
||||
const parsed = parseHafasTime("20260518", "143000");
|
||||
const outbound = hafasDateTime(new Date());
|
||||
```
|
||||
|
||||
> **Note:** `getTimezoneOffsetMinutes` is designed exclusively for full-hour offsets like `Europe/Vienna`. It will produce incorrect results for fractional timezones (e.g., India +05:30).
|
||||
| Function | Description |
|
||||
| --- | --- |
|
||||
| `parseHafasTime(dateStr, timeStr)` | Converts HAFAS `YYYYMMDD` and `HHMMSS` strings into a UTC `Date`, including CET/CEST transition handling. |
|
||||
| `hafasDateTime(date)` | Converts a JavaScript `Date` into HAFAS date/time strings in `Europe/Vienna`. |
|
||||
| `getTimezoneOffsetMinutes(instant, tz)` | Internal helper intended for whole-hour zones such as `Europe/Vienna`. |
|
||||
| `getDateTimeParts(instant, tz)` | Extracts timezone-local date parts through `Intl.DateTimeFormat`. |
|
||||
|
||||
### Countdown & Status Utilities
|
||||
### Journey Parsing and Scoring
|
||||
|
||||
#### `calculateCountdown(targetDate: Date): CountdownInfo`
|
||||
Calculates the time delta between `now` and `targetDate`, returning a human-readable label, a color code, and an urgency boolean.
|
||||
| Function | Description |
|
||||
| --- | --- |
|
||||
| `parseHafasJourneys(json, hafasDate, queryDate)` | Converts HAFAS `outConL` responses into `Journey[]`, including real-time delay, platform, trains, changes, and cancellations. |
|
||||
| `rankJourneys(journeys, targetArrivalTime, finalLegDurationMs?)` | Scores journeys by arrival fit, transfer count, duration, and cancellation penalty. |
|
||||
|
||||
| Time Delta | Label | Color | Urgent |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `<= 0 min` | `Now` | `red` | `true` |
|
||||
| `<= 10 min` | `[X]min` | `orange` | `true` |
|
||||
| `<= 30 min` | `[X]min` | `yellow` | `false` |
|
||||
| `<= 60 min` | `[X]min` | `green` | `false` |
|
||||
| `> 60 min` | `[X]h [Y]min` | `blue` | `false` |
|
||||
### Countdown, Status, and Formatting
|
||||
|
||||
#### `getLeaveStatus(event: Event, journeys: Journey[]): string`
|
||||
Derives a human-readable leave-by status by finding the earliest non-cancelled journey and comparing its real departure time (`rD`) against the current time.
|
||||
| Function | Description |
|
||||
| --- | --- |
|
||||
| `calculateCountdown(targetDate)` | Returns a countdown label and color: red for now/past, orange within 10 minutes, yellow within 30, green within 60, blue beyond 60. |
|
||||
| `getLeaveStatus(event, journeys)` | Returns `No journey data`, `All journeys cancelled`, `Departure missed`, `Delayed +N min`, `Leave now`, or `On time`. |
|
||||
| `StatusUtils.checkServerStatus(url)` | Performs a timeout-bound `HEAD` request and returns boolean availability. |
|
||||
| `formatTime(date)` | Austrian local `HH:mm`. |
|
||||
| `formatDate(date)` | Austrian local date with weekday. |
|
||||
| `formatDateTime(date)` | Austrian local date and time. |
|
||||
| `formatDuration(seconds)` | Human-readable duration such as `1h 05min` or `45min`. |
|
||||
| `formatDistance(meters)` | Meters below 1 km, one-decimal kilometers above. |
|
||||
|
||||
**Possible Returns:**
|
||||
- `"No journey data"`
|
||||
- `"All journeys cancelled"`
|
||||
- `"Departure missed"` (if `rD` is in the past)
|
||||
- `"Delayed +[X] min"` (if delay exceeds 10 minutes)
|
||||
- `"Leave now"` (if departure is within 15 minutes)
|
||||
- `"On time"`
|
||||
## `@timetoleave/api-client`
|
||||
|
||||
### Formatting Utilities (`formatting.ts`)
|
||||
|
||||
All formatting functions default to the `de-AT` locale to match the primary target region.
|
||||
|
||||
- `formatTime(date: Date)` -> `"14:30"`
|
||||
- `formatDate(date: Date)` -> `"Mi., 27. Oktober 2023"`
|
||||
- `formatDateTime(date: Date)` -> `"27. Oktober 2023, 14:30"`
|
||||
- `formatDuration(seconds: number)` -> `"1h 23min"` or `"45min"`
|
||||
- `formatDistance(meters: number)` -> `"1.2km"` or `"800m"`
|
||||
|
||||
---
|
||||
|
||||
## 🌐 `@timetoleave/api-client`
|
||||
|
||||
The API client is a lightweight wrapper around the Web App's Next.js API routes. It handles URL construction, query parameters, and JSON serialization.
|
||||
|
||||
### Initialization
|
||||
`ApiClient` is a small client for the web backend proxy. In the web app, an empty base URL means same-origin. In mobile, set `EXPO_PUBLIC_API_BASE_URL` or pass a deployed backend URL.
|
||||
|
||||
```typescript
|
||||
import { ApiClient } from '@timetoleave/api-client';
|
||||
import { ApiClient } from "@timetoleave/api-client";
|
||||
|
||||
// Initialize with the base URL of the backend proxy
|
||||
const api = new ApiClient('http://localhost:3000');
|
||||
const api = new ApiClient("https://timetoleave.app");
|
||||
```
|
||||
|
||||
### Methods
|
||||
The constructor accepts either a string or a string array. When an array is provided, the client tries the next base URL for network failures and unavailable statuses such as 408, 429, 502, 503, and 504.
|
||||
|
||||
### Health
|
||||
|
||||
#### `getHealth()`
|
||||
```typescript
|
||||
getHealth(): Promise<{ status: 'ok'; uptime: number }>
|
||||
getHealth(): Promise<{ status: "ok"; uptime: number }>
|
||||
```
|
||||
Checks the `/api/health` endpoint to verify backend availability.
|
||||
|
||||
#### `geocode(name: string, countrycodes?: string)`
|
||||
Calls `/api/health`. The current route returns `{ ok, ts, version }`, so callers should keep this method's legacy type in mind until it is aligned with the route payload.
|
||||
|
||||
### Geocoding
|
||||
|
||||
```typescript
|
||||
geocode(name: string, countrycodes?: string): Promise<GeocodeResult[]>
|
||||
```
|
||||
Performs forward geocoding via `/api/geocode`.
|
||||
|
||||
#### `reverseGeocode(lat: number, lng: number)`
|
||||
```typescript
|
||||
reverseGeocode(lat: number, lng: number): Promise<GeocodeResult | null>
|
||||
```
|
||||
Performs reverse geocoding via `/api/geocode/reverse`.
|
||||
|
||||
#### `fetchCalendar(url: string, days?: number)`
|
||||
`geocode()` calls `/api/geocode` and wraps the first result in an array. `reverseGeocode()` calls `/api/geocode/reverse`; the current web app does not define that route, so it returns `null` for non-OK responses.
|
||||
|
||||
### Calendar
|
||||
|
||||
```typescript
|
||||
fetchCalendar(url: string, days?: number): Promise<CalendarEvent[]>
|
||||
```
|
||||
Fetches and parses a remote `.ics` file via `/api/calendar`. The optional `days` parameter limits the fetch to upcoming events.
|
||||
|
||||
#### `parseCalendarIcs(content: string)`
|
||||
```typescript
|
||||
parseCalendarIcs(content: string): Promise<CalendarEvent[]>
|
||||
```
|
||||
Parses raw `.ics` string content via `/api/calendar/parse`.
|
||||
|
||||
#### `searchStation(query: string)`
|
||||
`fetchCalendar()` imports a remote allowed ICS URL through `/api/calendar`. `parseCalendarIcs()` posts raw ICS text to `/api/calendar/parse`.
|
||||
|
||||
Google Calendar sync is currently implemented in the web UI and backend routes, not as a dedicated `ApiClient` method.
|
||||
|
||||
### HAFAS
|
||||
|
||||
```typescript
|
||||
hafasRequest<T = Record<string, unknown>>(body: unknown): Promise<T>
|
||||
searchStation(query: string): Promise<Station[]>
|
||||
findStationByExtId(extId: string): Promise<Station | null>
|
||||
findNearestStationByCoords(lat: number, lng: number): Promise<Station | null>
|
||||
searchJourneys(
|
||||
fromStationExtId: string,
|
||||
toStationExtId: string,
|
||||
date: Date,
|
||||
options?: { arriveBy?: boolean },
|
||||
): Promise<Journey[]>
|
||||
```
|
||||
Searches for stations by name using the HAFAS `LocMatch` method. Returns up to 5 matches with valid `extId`s.
|
||||
|
||||
#### `searchJourneys(fromStationExtId: string, toStationExtId: string, date: Date)`
|
||||
```typescript
|
||||
searchJourneys(from: string, to: string, date: Date): Promise<Journey[]>
|
||||
```
|
||||
Sends a HAFAS `TripSearch` request to find public transport connections between two stations. It automatically converts the `Date` to HAFAS-compatible strings.
|
||||
`hafasRequest()` posts a validated HAFAS body to `/api/hafas`. The server only allows `TripSearch` and `LocMatch`.
|
||||
|
||||
#### `getBikeRoute(fromLat, fromLng, toLat, toLng)` & `getWalkRoute(fromLat, fromLng, toLat, toLng)`
|
||||
```typescript
|
||||
getBikeRoute(...): Promise<BikeRoute>
|
||||
getWalkRoute(...): Promise<WalkRoute>
|
||||
```
|
||||
Retrieves routing data for the "first mile / last mile" segment (e.g., biking from home to the train station).
|
||||
`searchJourneys()` builds a `TripSearch`, converts the requested date with `hafasDateTime()`, and parses the response with `parseHafasJourneys()`. When `arriveBy` is true and no journeys are returned, it retries with a two-hour backward fallback window.
|
||||
|
||||
#### `findNearbyStops(lat: number, lng: number, radius?: number)`
|
||||
```typescript
|
||||
findNearbyStops(lat: number, lng: number, radius: number = 1000): Promise<NearbyStop[]>
|
||||
```
|
||||
Finds public transport stops within a specific radius using the WienerLinien API (`/api/wienerlinien/stops`).
|
||||
### Routing
|
||||
|
||||
#### `hafasRequest<T>(body: unknown)`
|
||||
```typescript
|
||||
hafasRequest<T>(body: unknown): Promise<T>
|
||||
getBikeRoute(fromLat, fromLng, toLat, toLng): Promise<BikeRoute>
|
||||
getWalkRoute(fromLat, fromLng, toLat, toLng): Promise<WalkRoute>
|
||||
```
|
||||
A generic method to send arbitrary HAFAS protocol bodies to `/api/hafas`. Useful for advanced use-cases not covered by the wrapper methods.
|
||||
|
||||
Both methods call OSRM-backed proxy routes and return route summaries plus turn-by-turn steps.
|
||||
|
||||
### Wiener Linien
|
||||
|
||||
```typescript
|
||||
findNearbyStops(lat: number, lng: number, radius?: number): Promise<NearbyStop[]>
|
||||
monitorStops(stopIds: string[]): Promise<WienerLinienDeparture[]>
|
||||
```
|
||||
|
||||
`findNearbyStops()` calls `/api/wienerlinien/stops`. `monitorStops()` sends repeated `stopIds` query parameters to `/api/wienerlinien/monitor` and returns flattened departure rows.
|
||||
|
||||
## Backend Contract Notes
|
||||
|
||||
- `/api/health` currently returns `{ ok, ts, version }`.
|
||||
- `/api/geocode` returns a single `GeocodeResult`, while `ApiClient.geocode()` wraps it in an array for existing callers.
|
||||
- `/api/hafas` POST caps `TripSearch.numF` to 5 in the current implementation.
|
||||
- `/api/calendar/parse` has a small body limit intended for direct uploaded text parsing.
|
||||
- Remote calendar URL import is allow-list based and rejects redirects.
|
||||
|
||||
+64
-46
@@ -1,64 +1,82 @@
|
||||
# TimeToLeave Architecture
|
||||
|
||||
This document outlines the architectural decisions, directory structure, and data flow of the TimeToLeave application.
|
||||
TimeToLeave is an npm workspaces monorepo. The web app is both the browser UI and the backend proxy for external services; the mobile app calls that backend through the shared API client; shared domain logic lives in `packages/core`.
|
||||
|
||||
## 🏗️ Monorepo Structure
|
||||
|
||||
TimeToLeave uses an **npm Workspaces Monorepo** to manage its interconnected components. This allows for seamless code sharing and dependency management between the Web dashboard, the Mobile client, and the shared packages.
|
||||
## Monorepo Structure
|
||||
|
||||
```text
|
||||
TimeToLeave/
|
||||
├── apps/
|
||||
│ ├── web/ # Next.js 16 Web Dashboard & Backend Proxy
|
||||
│ └── mobile/ # React Native 0.81 / Expo 54 Mobile Client
|
||||
│ ├── web/ # Next.js 16 App Router UI and backend proxy
|
||||
│ └── mobile/ # Expo 54 / React Native 0.81 app
|
||||
├── packages/
|
||||
│ ├── api-client/ # Unified API Client for Backend Proxies
|
||||
│ └── core/ # Shared Domain Types, Logic, and Utilities
|
||||
├── docs/ # Comprehensive Documentation
|
||||
└── package.json # Root Workspace Configuration
|
||||
│ ├── api-client/ # Shared client for /api/* backend routes
|
||||
│ └── core/ # Shared types, defaults, parsing, scoring, formatting
|
||||
├── docs/ # Documentation
|
||||
└── package.json # Workspace scripts
|
||||
```
|
||||
|
||||
## 🧩 Component Overview
|
||||
## Components
|
||||
|
||||
### 1. Web Application (`apps/web`)
|
||||
- **Role:** Primary dashboard for desktop users and the **Backend Proxy** for HAFAS/Geocoding APIs.
|
||||
- **Framework:** Next.js 16 (App Router) with React 19.
|
||||
- **Styling:** Tailwind CSS 4.
|
||||
- **State Management:** React Context (`EventsProvider`, `ReminderSettingsProvider`).
|
||||
- **Backend Proxy:** The Next.js API routes (`/api/*`) act as a server-side proxy. This is crucial because the HAFAS protocol and various geocoding APIs require server-side execution to protect API keys, handle CORS, and manage protocol-specific payloads.
|
||||
### Web App: `apps/web`
|
||||
|
||||
### 2. Mobile Application (`apps/mobile`)
|
||||
- **Role:** On-the-go companion app for real-time status checks and native device integration.
|
||||
- **Framework:** React Native 0.81 via Expo 54.
|
||||
- **Navigation:** React Navigation 7 (Native Stack Navigator).
|
||||
- **Native APIs:**
|
||||
- `expo-location`: Geolocation for finding nearby stations and calculating local transit (bike/walk) to the station.
|
||||
- `expo-calendar`: Native integration to read local calendar events directly on the device.
|
||||
- `expo-notifications`: Push notifications to alert the user when it's time to leave.
|
||||
- `@react-native-async-storage/async-storage`: Persisting user settings (buffers, toggles) and cached events locally.
|
||||
- Next.js 16 App Router with React 19 and Tailwind CSS 4.
|
||||
- User-facing routes are `/` and `/calendar`.
|
||||
- Route handlers under `apps/web/src/app/api/**/route.ts` proxy HAFAS, Nominatim, OSRM, Google Calendar, remote ICS, and Wiener Linien calls.
|
||||
- `apps/web/src/proxy.ts` applies strict CORS and per-IP rate limiting to `/api/*`.
|
||||
- Client state is kept in React context and persisted to `localStorage` through `useEventsStore` and `useReminderSettings`.
|
||||
|
||||
### 3. Core Package (`packages/core`)
|
||||
- **Role:** The single source of truth for domain logic and TypeScript types across the entire monorepo.
|
||||
- **Key Modules:**
|
||||
- **Types:** Defines `Event`, `Journey`, `Station`, `BikeRoute`, `CountdownInfo`, and `WienerLinien` specific types.
|
||||
- **HAFAS Time Parsing:** Specialized utilities to parse Vienna-centric (CET/CEST) timestamps. Handles Daylight Saving Time (DST) transitions accurately using `Intl.DateTimeFormat`.
|
||||
- **Countdown & Status Logic:** Algorithms to translate raw journey data into human-readable statuses like *"Leave now"*, *"On time"*, or *"Delayed +12 min"*.
|
||||
- **Formatting:** Standardized formatting for dates, times, distances, and durations.
|
||||
### Mobile App: `apps/mobile`
|
||||
|
||||
### 4. API Client Package (`packages/api-client`)
|
||||
- **Role:** A lightweight HTTP client that wraps the Web App's API routes.
|
||||
- **Usage:** Used by both the Web frontend (for server/client data sync) and the Mobile client to communicate with the backend proxy.
|
||||
- **Features:** Handles URL building, query parameters, and JSON serialization for HAFAS requests, calendar fetching, and routing requests.
|
||||
- Expo 54 / React Native 0.81 with React Navigation 7.
|
||||
- Screens cover event list, add/edit event, event detail, settings, and calendar import.
|
||||
- Uses native APIs through `expo-calendar`, `expo-location`, `expo-notifications`, and `AsyncStorage`.
|
||||
- Calls the web backend through `@timetoleave/api-client`; device builds should set `EXPO_PUBLIC_API_BASE_URL`.
|
||||
|
||||
## 🔄 Data Flow
|
||||
### Core Package: `packages/core`
|
||||
|
||||
1. **Calendar Sync:** The user provides an `.ics` URL or uploads a file. The `ApiClient` sends this to the Web App's `/api/calendar` route, which parses the events and returns standardized `CalendarEvent` objects.
|
||||
2. **Station Search:** The user searches for a station. The `ApiClient` triggers a HAFAS `LocMatch` request via `/api/hafas`.
|
||||
3. **Journey Calculation:** Using the station `extId` and the event time, the `ApiClient` sends a `TripSearch` request to `/api/hafas`. The backend returns real-time journey data (`Journey[]`).
|
||||
4. **Local Routing:** Using `expo-location` (mobile) or browser geolocation (web), the app calculates the bike/walk route from the user's home/location to the departure station via `/api/bike-route` or `/api/walk-route`.
|
||||
5. **Real-Time Status:** The `packages/core` logic continuously compares the `Journey.rD` (real departure) against the current time and local travel duration to update the Leave Status dynamically.
|
||||
- Owns the shared TypeScript model: events, calendar events, stations, journeys, routes, reminder settings, geocoding, and Wiener Linien types.
|
||||
- Provides Vienna-aware HAFAS time conversion through `parseHafasTime()` and `hafasDateTime()`.
|
||||
- Parses HAFAS journey responses, ranks journeys, formats dates/durations/distances, and computes countdown/status labels.
|
||||
- Exports default origin constants for the Mödling fallback origin.
|
||||
|
||||
## ⚠️ Technical Constraints & Guidelines
|
||||
### API Client Package: `packages/api-client`
|
||||
|
||||
- **Next.js Version:** The project uses **Next.js 16.2+**, which includes breaking changes compared to previous versions. Always refer to `node_modules/next/dist/docs/` when modifying Web routing or API conventions.
|
||||
- **HAFAS Timezone:** HAFAS timestamps are strictly tied to `Europe/Vienna`. The `hafas-time.ts` module handles the bi-directional conversion between UTC `Date` objects and Vienna-local HAFAS strings. Never use standard `Date` methods for HAFAS times; always use `parseHafasTime()` and `hafasDateTime()`.
|
||||
- Wraps the web backend routes from browser and mobile code.
|
||||
- Supports base URL failover by accepting either one base URL or an array of base URLs.
|
||||
- Builds HAFAS `TripSearch` and `LocMatch` requests, parses journey responses with `@timetoleave/core`, and includes an arrive-by fallback search window.
|
||||
|
||||
## Data Flow
|
||||
|
||||
1. Events enter through manual entry, remote ICS URL import, local ICS file parsing, Google Calendar on web, or native device calendars on mobile.
|
||||
2. Events are normalized to `Event` or `CalendarEvent` objects and stored locally in the client.
|
||||
3. The app resolves an origin station from saved settings, geolocation, or the shared default origin.
|
||||
4. The event destination is geocoded with Nominatim.
|
||||
5. HAFAS `LocMatch` resolves the nearest destination station.
|
||||
6. HAFAS `TripSearch` fetches live journeys, with HAFAS time conversion handled by `packages/core`.
|
||||
7. OSRM provides optional bike and final-walk routes.
|
||||
8. Wiener Linien endpoints provide nearby stops and live departures around the destination.
|
||||
9. Countdown and leave-by calculations combine event time, selected transport mode, journey arrival, final walk duration, bike route duration, and reminder buffers.
|
||||
|
||||
## External Integrations
|
||||
|
||||
| Service | Used for | Access path |
|
||||
| --- | --- | --- |
|
||||
| ÖBB HAFAS | Station lookup and journey search | `/api/hafas` |
|
||||
| ÖBB GTFS ZIP | Optional train metadata enrichment | `apps/web/src/lib/oebb-gtfs.ts` |
|
||||
| Nominatim | Destination geocoding | `/api/geocode` |
|
||||
| OSRM | Bike and foot routes | `/api/bike-route`, `/api/walk-route` |
|
||||
| Wiener Linien Darwin | Nearby stops and live monitor data | `/api/wienerlinien/*` |
|
||||
| Google Calendar | Web OAuth calendar import | `/api/auth/google/*`, `/api/calendar/google` |
|
||||
| Remote ICS providers | Calendar URL import | `/api/calendar` |
|
||||
|
||||
## Security and Operational Constraints
|
||||
|
||||
- Calendar URL imports validate provider domains, block private/reserved hosts, reject redirects, validate content type, and cap response size.
|
||||
- HAFAS POST bodies are size-limited and only `TripSearch` and `LocMatch` are allowed.
|
||||
- Coordinate APIs validate ranges and reject unrealistically distant route requests.
|
||||
- API routes are rate-limited per client IP.
|
||||
- CORS is allow-list based through `CORS_ALLOWED_ORIGINS`.
|
||||
- Google OAuth tokens are stored in HTTP-only cookies.
|
||||
- HAFAS time values must be treated as `Europe/Vienna` local strings.
|
||||
- This project uses Next.js 16. Before changing framework behavior, routing, route handlers, or proxy/middleware code, read the relevant guide in `node_modules/next/dist/docs/`.
|
||||
|
||||
@@ -87,6 +87,20 @@ HAFAS timestamps are Vienna-local strings, not UTC timestamps. Use these helpers
|
||||
| --- | --- |
|
||||
| `parseHafasJourneys(json, hafasDate, queryDate)` | Shared HAFAS trip parser that converts raw `outConL` connections into `Journey[]`. It parses scheduled and realtime departure/arrival strings with `parseHafasTime`, computes delay, platform, train labels, change count, and cancellation state. |
|
||||
|
||||
### `packages/core/src/journey-scoring.ts`
|
||||
|
||||
| Function | What it does |
|
||||
| --- | --- |
|
||||
| `rankJourneys(journeys, targetArrivalTime, finalLegDurationMs?)` | Scores journeys by arrival fit, transfer count, duration, and cancellation penalty. It favors arrivals close to the target, direct connections, and shorter journeys, while strongly penalizing late or cancelled journeys. |
|
||||
|
||||
### `packages/core/src/defaults.ts`
|
||||
|
||||
| Constant | What it does |
|
||||
| --- | --- |
|
||||
| `DEFAULT_ORIGIN_ADDRESS`, `DEFAULT_ORIGIN_LAT`, `DEFAULT_ORIGIN_LNG` | Shared fallback origin address and coordinates for Goethegasse 36, 2340 Moedling. |
|
||||
| `DEFAULT_ORIGIN_STATION_NAME`, `DEFAULT_ORIGIN_STATION_EXT_ID` | Fallback station name and HAFAS station ID for Mödling Bahnhof. |
|
||||
| `DEFAULT_ORIGIN_STATION` | Pre-assembled `Station` object used when geolocation or saved origin settings are unavailable. |
|
||||
|
||||
### `packages/core/src/index.ts`
|
||||
|
||||
Barrel file that re-exports the core types, countdown utilities, formatting utilities, status utilities, HAFAS time helpers, and the shared HAFAS parser.
|
||||
@@ -112,8 +126,10 @@ This client talks to the web app's backend proxy routes. `baseUrl` defaults to a
|
||||
| `ApiClient.reverseGeocode(lat, lng)` | Calls `/api/geocode/reverse`. Note: the current web app does not define this route, so callers should tolerate `null` or failures. |
|
||||
| `ApiClient.findNearbyStops(lat, lng, radius?)` | Calls `/api/wienerlinien/stops` and returns nearby stops. |
|
||||
| `ApiClient.searchStation(query)` | Uses HAFAS `LocMatch` through `/api/hafas` to search station names. |
|
||||
| `ApiClient.findStationByExtId(extId)` | Uses HAFAS `LocMatch` to resolve one station by external station ID. |
|
||||
| `ApiClient.findNearestStationByCoords(lat, lng)` | Uses HAFAS coordinate `LocMatch` to find the closest station to GPS coordinates. |
|
||||
| `ApiClient.monitorStops(stopIds)` | Calls `/api/wienerlinien/monitor` and returns flattened departure rows. |
|
||||
| `ApiClient.fetchApi(path, init?, params?)` | Private helper that tries configured base URLs and falls through on network errors or unavailable status codes. |
|
||||
|
||||
### `packages/api-client/src/index.ts`
|
||||
|
||||
@@ -133,6 +149,7 @@ Defines environment-backed service URLs and defaults:
|
||||
| `WIENER_LINIEN_API_URL` | Wiener Linien API base. |
|
||||
| `DEFAULT_DAYS` | Default calendar import horizon. |
|
||||
| `DEFAULT_STATION`, `DEFAULT_STATION_NAME`, `DEFAULT_STATION_EXT_ID` | Web fallback origin. The default origin is Goethegasse 36, 2340 Moedling, using Mödling Bahnhof as the nearest transit station fallback. |
|
||||
| `OEBB_GTFS_URL` | ÖBB GTFS ZIP source used by the HAFAS route to enrich train metadata when possible. |
|
||||
| `APP_VERSION` | Health endpoint version string. |
|
||||
|
||||
### `apps/web/src/lib/api-service.ts`
|
||||
|
||||
+95
-83
@@ -1,101 +1,113 @@
|
||||
# Development Guide
|
||||
|
||||
This guide covers setting up the development environment, running the applications, and maintaining code quality.
|
||||
## Prerequisites
|
||||
|
||||
## 🛠 Prerequisites
|
||||
- Node.js 20 or newer
|
||||
- npm 9 or newer
|
||||
- Android Studio or Xcode for native mobile builds
|
||||
- Expo/EAS tooling when building or submitting mobile apps
|
||||
|
||||
- **Node.js:** Version 20.x or higher.
|
||||
- **npm:** Version 9.x or higher.
|
||||
## Install
|
||||
|
||||
## 📥 Installation
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd TimeToLeave
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
*This installs dependencies for the root workspace as well as all apps and packages.*
|
||||
|
||||
3. **Environment Variables:**
|
||||
- Web App: Copy `apps/web/.env.example` to `apps/web/.env` and configure your HAFAS/Geocoding API keys.
|
||||
- Mobile App: Copy `apps/mobile/.env.example` to `apps/mobile/.env` and set the backend API URL.
|
||||
|
||||
## ▶️ Running the Applications
|
||||
|
||||
| Script | Command | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `dev` | `npm run dev` | Starts the Next.js development server (Web) on `http://localhost:3000`. |
|
||||
| `dev:mobile` | `npm run dev:mobile` | Starts the Expo development server (Mobile) and opens the Expo Go simulator. |
|
||||
| `build` | `npm run build` | Builds the production bundle for the Web application. |
|
||||
|
||||
## 🧪 Testing & Quality Assurance
|
||||
|
||||
The project enforces strict typing and linting across all workspaces.
|
||||
|
||||
| Script | Command | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `test` | `npm run test` | Runs **Vitest** for the Web app (using jsdom & React Testing Library) and **Jest** for the Mobile app (using jest-expo). |
|
||||
| `lint` | `npm run lint` | Runs **ESLint 9** across the entire monorepo. |
|
||||
| `typecheck` | `npm run typecheck` | Runs **TypeScript 5** type checking across all workspaces. |
|
||||
|
||||
### Testing Specific Packages
|
||||
If you want to run tests for a specific workspace:
|
||||
```bash
|
||||
cd apps/web && npm test
|
||||
cd apps/mobile && npm test
|
||||
cd packages/core && npm run typecheck
|
||||
npm install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
## 🐳 Docker Support (Web App)
|
||||
The root workspace owns dependency installation. Avoid installing separately inside workspaces unless you are intentionally changing that workspace's dependency list.
|
||||
|
||||
The web application includes a `Dockerfile` and `docker-compose.yml` for containerized development or deployment.
|
||||
## Environment
|
||||
|
||||
| Variable | Default | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `PORT` | `3000` through Next dev defaults | Web server port when the runtime honors it. |
|
||||
| `HAFAS_URL` | `https://fahrplan.oebb.at/bin/mgate.exe` | ÖBB HAFAS endpoint. |
|
||||
| `HAFAS_TIMEOUT_MS` | `10000` | HAFAS request timeout. |
|
||||
| `HAFAS_VER`, `HAFAS_LANG`, `HAFAS_AID`, `HAFAS_CLIENT_*` | ÖBB app-compatible defaults | HAFAS request envelope metadata. |
|
||||
| `NOMINATIM_URL` | `https://nominatim.openstreetmap.org` | Geocoding endpoint. |
|
||||
| `NOMINATIM_USER_AGENT` | `TimeToLeave/2.0` | Required Nominatim user agent. |
|
||||
| `OSRM_URL` | `https://router.project-osrm.org` | Bike and foot routing base URL. |
|
||||
| `WIENER_LINIEN_API_URL` | `https://api.wienerlinien.at/darwin-v2` | Wiener Linien live data base URL. |
|
||||
| `OEBB_GTFS_URL` | ÖBB 2026 GTFS ZIP | Optional HAFAS response enrichment source. |
|
||||
| `CORS_ALLOWED_ORIGINS` | `http://localhost:3000` fallback | Allowed browser origins for `/api/*`. |
|
||||
| `API_RATE_LIMIT_MAX_REQUESTS` | `120` | Per-IP API requests per window. |
|
||||
| `API_RATE_LIMIT_WINDOW_MS` | `60000` | Rate-limit window length. |
|
||||
| `DEPLOYMENT_URL` | local fallback in OAuth code | Public app URL for Google OAuth redirects. |
|
||||
| `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, `GOOGLE_REDIRECT_URI` | none | Enables Google Calendar sync. |
|
||||
| `APP_VERSION` | `0.1.0` | Version returned by `/api/health`. |
|
||||
| `EXPO_PUBLIC_API_BASE_URL` | empty | Mobile backend URL. Required for physical devices unless the empty base URL is intentionally proxied. |
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `npm run dev` | Start the web app with `next dev`. |
|
||||
| `npm run build` | Build the web app. |
|
||||
| `npm run start` | Start the built web app. |
|
||||
| `npm run dev:mobile` | Start the Expo dev server. |
|
||||
| `npm run android` | Run the Expo app on Android from the root script. |
|
||||
| `npm run ios` | Run the Expo app on iOS from the root script. |
|
||||
| `npm run lint` | Run ESLint across web, mobile, core, and api-client. |
|
||||
| `npm run typecheck` | Run TypeScript checks across all workspaces. |
|
||||
| `npm run test` | Run web Vitest tests and mobile Jest tests. |
|
||||
|
||||
Workspace-specific examples:
|
||||
|
||||
```bash
|
||||
npm run test -w apps/web
|
||||
npm run test -w apps/mobile
|
||||
npm run typecheck -w packages/core
|
||||
npm run typecheck -w packages/api-client
|
||||
```
|
||||
|
||||
## Web Routes
|
||||
|
||||
| Route | Description |
|
||||
| --- | --- |
|
||||
| `/` | Departure desk with next upcoming event, train/bike mode selector, live journeys, route sections, reminders, and nearby Wiener Linien departures. |
|
||||
| `/calendar` | Calendar import/management with URL, file, Google OAuth, and batch destination editing. |
|
||||
|
||||
The web add/edit event flow is implemented by `apps/web/src/app/add-event/AddEventModal.tsx`, not a standalone route.
|
||||
|
||||
## API Routes
|
||||
|
||||
| Endpoint | Methods | Notes |
|
||||
| --- | --- | --- |
|
||||
| `/api/health` | `GET` | Returns `{ ok, ts, version }`. |
|
||||
| `/api/hafas` | `GET`, `POST` | GET is a simple trip search. POST validates and forwards `TripSearch` or `LocMatch`. |
|
||||
| `/api/geocode` | `GET` | Requires `name`; optional `countrycodes`. Returns the first result. |
|
||||
| `/api/bike-route` | `GET` | Requires `fromLat`, `fromLng`, `toLat`, `toLng`. |
|
||||
| `/api/walk-route` | `GET` | Same coordinate contract as bike route, using OSRM foot profile. |
|
||||
| `/api/calendar` | `GET` | Requires an allowed remote ICS `url`; optional `days`. |
|
||||
| `/api/calendar/parse` | `POST` | Parses raw ICS text from the request body. |
|
||||
| `/api/calendar/google` | `GET` | Requires Google OAuth token cookie; optional day horizon. |
|
||||
| `/api/auth/google` | `GET` | Starts OAuth. |
|
||||
| `/api/auth/google/callback` | `GET` | Completes OAuth. |
|
||||
| `/api/auth/google/status` | `GET` | Reports configuration and connection state. |
|
||||
| `/api/auth/google/disconnect` | `POST` | Clears OAuth token cookie. |
|
||||
| `/api/wienerlinien/stops` | `GET` | Requires `lat`, `lng`; optional `radius`, capped server-side. |
|
||||
| `/api/wienerlinien/monitor` | `GET` | Accepts repeated `stopIds`, capped server-side. |
|
||||
|
||||
## Docker
|
||||
|
||||
The web app includes Docker support:
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
## 📁 Web App Routing (Next.js 16)
|
||||
## Testing Notes
|
||||
|
||||
The web app uses the Next.js App Router. Key routes include:
|
||||
- Web tests use Vitest, jsdom, and Testing Library.
|
||||
- Mobile tests use Jest, jest-expo, and React Native Testing Library.
|
||||
- Route, geocoding, HAFAS, calendar, routing, reminder, and UI component tests are present across the repo.
|
||||
- Run `npm run typecheck` after changing shared types because both apps consume `packages/core`.
|
||||
|
||||
| Route | Description |
|
||||
| :--- | :--- |
|
||||
| `/` | **Dashboard:** Lists upcoming events and their real-time leave status. |
|
||||
| `/calendar` | **Calendar Sync:** Interface to import `.ics` files or paste calendar URLs. |
|
||||
| `/add-event` | **Manual Entry:** Add a new event manually without a calendar source. |
|
||||
| `/event/[id]` | **Event Details:** Deep dive into a specific event, showing journey options, delays, and local routing. |
|
||||
## Development Rules
|
||||
|
||||
### Backend API Routes
|
||||
The web app acts as a proxy for external APIs. These are located in `apps/web/src/app/api/`:
|
||||
|
||||
| Endpoint | Method | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `/api/health` | `GET` | Health check for the backend proxy. |
|
||||
| `/api/calendar` | `GET` | Fetch and parse remote `.ics` files. |
|
||||
| `/api/calendar/parse` | `POST` | Parse raw `.ics` content. |
|
||||
| `/api/hafas` | `POST` | Generic HAFAS protocol endpoint (TripSearch, LocMatch). |
|
||||
| `/api/geocode` | `GET` | Forward geocoding (Nominatim). |
|
||||
| `/api/geocode/reverse`| `GET` | Reverse geocoding. |
|
||||
| `/api/bike-route` | `GET` | Bicycle routing between coordinates. |
|
||||
| `/api/walk-route` | `GET` | Walking routing between coordinates. |
|
||||
| `/api/wienerlinien/stops`| `GET` | Find nearby WienerLinien stops. |
|
||||
|
||||
## 📱 Mobile App Structure
|
||||
|
||||
The mobile app is organized into the following directories:
|
||||
|
||||
- `src/screens/`: UI screens (`EventListScreen`, `EventDetailScreen`, `AddEventScreen`, `CalendarImportScreen`, `SettingsScreen`).
|
||||
- `src/navigation/`: React Navigation configuration (`AppNavigator.tsx`).
|
||||
- `src/services/`: API integration and data fetching services.
|
||||
- `src/store/`: Local state management and AsyncStorage integration.
|
||||
|
||||
## ⚠️ Development Notes
|
||||
|
||||
1. **Next.js 16 Breaking Changes:** The web app runs on Next.js 16.2+, which introduces breaking changes in routing and API conventions. Always check `node_modules/next/dist/docs/` if you encounter unexpected behavior.
|
||||
2. **HAFAS Timezones:** Never parse HAFAS times using standard `Date` methods. Use `@timetoleave/core` utilities (`parseHafasTime`, `hafasDateTime`) to ensure accurate CET/CEST and DST handling.
|
||||
- Use `@timetoleave/core` for shared behavior.
|
||||
- Use `@timetoleave/api-client` for client-to-backend calls that are shared by web and mobile.
|
||||
- Keep backend validation close to route handlers, then delegate external service behavior to `apps/web/src/lib`.
|
||||
- Never parse HAFAS date/time values with ad hoc `Date` logic; use `parseHafasTime()` and `hafasDateTime()`.
|
||||
- This repo uses Next.js 16. Read the relevant `node_modules/next/dist/docs/` material before changing Next.js framework code.
|
||||
|
||||
+28
-40
@@ -1,59 +1,47 @@
|
||||
# 📚 TimeToLeave Documentation
|
||||
# TimeToLeave Documentation
|
||||
|
||||
Welcome to the official documentation for TimeToLeave, the smart departure planner that syncs with your calendar and monitors real-time public transport to tell you exactly when to leave home.
|
||||
This directory documents the current TimeToLeave monorepo: the Next.js web app and backend proxy, the Expo mobile app, and the shared TypeScript packages.
|
||||
|
||||
## 🗺️ Table of Contents
|
||||
## Contents
|
||||
|
||||
### 1. [Architecture](./ARCHITECTURE.md)
|
||||
Understand the monorepo structure, tech stack, data flow, and component relationships between the Web Dashboard, Mobile Client, and shared packages.
|
||||
| Document | Use it for |
|
||||
| --- | --- |
|
||||
| [Architecture](./ARCHITECTURE.md) | System overview, package responsibilities, data flow, integrations, and operational constraints. |
|
||||
| [Development](./DEVELOPMENT.md) | Setup, environment variables, local commands, route map, quality checks, Docker, and mobile development notes. |
|
||||
| [Core & API Client Reference](./API_REFERENCE.md) | Shared package exports, HAFAS helpers, countdown/status logic, and `ApiClient` methods. |
|
||||
| [User Guide](./USER_GUIDE.md) | How to use the web and mobile apps, import calendars, read leave-by statuses, and manage settings. |
|
||||
| [Codebase Function Guide](./CODEBASE_FUNCTION_GUIDE.md) | File-by-file map of first-party source modules, functions, hooks, and components. |
|
||||
|
||||
### 2. [Core & API Reference](./API_REFERENCE.md)
|
||||
Detailed reference for the internal packages:
|
||||
- **`@timetoleave/core`**: Domain types, HAFAS time parsing, countdown/status logic, and formatting utilities.
|
||||
- **`@timetoleave/api-client`**: The unified HTTP client that wraps the backend proxy routes.
|
||||
|
||||
### 3. [Development Guide](./DEVELOPMENT.md)
|
||||
Everything a developer needs to get started:
|
||||
- Prerequisites and installation instructions.
|
||||
- Running the Web and Mobile apps in development mode.
|
||||
- Testing (Vitest/Jest), Linting (ESLint), and Type Checking (TypeScript).
|
||||
- Backend API endpoints and Next.js 16 routing conventions.
|
||||
|
||||
### 4. [User Guide](./USER_GUIDE.md)
|
||||
A guide for end-users explaining how to sync calendars, view event details, interpret the "Leave Status" color codes, and configure mobile notifications.
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Start
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repository-url>
|
||||
cd TimeToLeave
|
||||
|
||||
# Install dependencies for the entire monorepo
|
||||
npm install
|
||||
|
||||
# Start the Web development server
|
||||
cp .env.example .env
|
||||
npm run dev
|
||||
```
|
||||
|
||||
# Start the Mobile development server
|
||||
For mobile development:
|
||||
|
||||
```bash
|
||||
npm run dev:mobile
|
||||
```
|
||||
|
||||
## 🛡️ Code Quality
|
||||
Set `EXPO_PUBLIC_API_BASE_URL` for device builds so the app can reach the web backend.
|
||||
|
||||
## Quality Checks
|
||||
|
||||
```bash
|
||||
# Run all tests (Web Vitest + Mobile Jest)
|
||||
npm run test
|
||||
|
||||
# Run ESLint across the monorepo
|
||||
npm run lint
|
||||
|
||||
# Run TypeScript type checking
|
||||
npm run typecheck
|
||||
npm run test
|
||||
npm run build
|
||||
```
|
||||
|
||||
---
|
||||
## Current User-Facing Routes
|
||||
|
||||
*Built for developers who bike to the train and hate missing their connections.*
|
||||
| Route | Purpose |
|
||||
| --- | --- |
|
||||
| `/` | Departure desk showing the next upcoming event and live route/departure data. |
|
||||
| `/calendar` | Calendar import, Google Calendar sync, and batch destination review. |
|
||||
|
||||
Add/edit event actions are handled in modal and native-screen flows, not by standalone web page routes.
|
||||
|
||||
+103
-65
@@ -1,93 +1,131 @@
|
||||
# User Guide
|
||||
|
||||
Welcome to the TimeToLeave User Guide! This document explains how to use the TimeToLeave web dashboard and mobile application to plan your departures seamlessly.
|
||||
TimeToLeave helps you answer one practical question: when do I need to leave for my next appointment?
|
||||
|
||||
## 🚀 How TimeToLeave Works
|
||||
The app imports events with locations, finds the nearest usable transport station, checks live train and local transit data, calculates walking or biking time, and shows a live leave-by countdown.
|
||||
|
||||
TimeToLeave acts as your personal departure planner. Instead of manually checking train schedules, you simply sync your calendar. The app calculates the best public transport connections to your upcoming appointments, monitors real-time delays, and tells you exactly when to leave home.
|
||||
## Web Dashboard
|
||||
|
||||
### Core Workflow
|
||||
1. **Sync Your Calendar:** Import your `.ics` file or provide a calendar URL.
|
||||
2. **Set Your Origin:** Define your home station or let the app use your current location.
|
||||
3. **Automatic Planning:** The app queries real-time public transport data (via HAFAS and WienerLinien) to find the best connections.
|
||||
4. **Leave Status:** The dashboard displays a clear status: `Leave now`, `On time`, `Delayed +X min`, or `Departure missed`.
|
||||
### Departure Desk
|
||||
|
||||
---
|
||||
Open `/` to see the next upcoming event. The card shows:
|
||||
|
||||
## 🌐 Web Dashboard
|
||||
- Event title and appointment time.
|
||||
- Destination address or place.
|
||||
- A live countdown badge.
|
||||
- Leave-by, arrive-by, and buffer times.
|
||||
- Train and bike mode selector.
|
||||
- Train journeys with delay, platform, cancellation, arrival, and transfer information.
|
||||
- Optional final walking route from the arrival station to the destination.
|
||||
- Optional door-to-door bike route.
|
||||
- Nearby Wiener Linien stops and live departures when destination coordinates are available.
|
||||
|
||||
The web dashboard is designed for planning and monitoring your upcoming events from a desktop or laptop.
|
||||
The dashboard currently focuses on the next upcoming event. Imported and manually added events are stored locally in the browser.
|
||||
|
||||
### Main Dashboard (`/`)
|
||||
This is the default view when you open the application. It displays a list of your upcoming events sorted by date. Each event card shows:
|
||||
- **Event Title & Time:** The name of the appointment and when it starts.
|
||||
- **Destination Station:** The nearest station to your event.
|
||||
- **Leave Status:** A color-coded indicator of your current standing:
|
||||
- 🔴 **Red:** Time is up ("Now") or you're extremely close to departure.
|
||||
- 🟠 **Orange:** Urgent! You need to leave within 10 minutes.
|
||||
- 🟡 **Yellow:** Moderate urgency (within 30 minutes).
|
||||
- 🟢 **Green:** Relaxed (within 60 minutes).
|
||||
- 🔵 **Blue:** Plenty of time remaining (over 1 hour).
|
||||
### Add or Edit Events
|
||||
|
||||
### Calendar Sync (`/calendar`)
|
||||
Use this page to connect your personal calendar.
|
||||
- **URL Import:** Paste a public `.ics` calendar URL. The app will fetch upcoming events automatically.
|
||||
- **File Import:** Upload a `.ics` file directly from your computer.
|
||||
Use the add/edit event modal from the web interface to create or change local events. Editing is modal-based; there is no separate `/add-event` page.
|
||||
|
||||
### Event Details (`/event/[id]`)
|
||||
Click on any event from the dashboard to view detailed planning information:
|
||||
- **Journey Options:** A list of available trains/buses with scheduled vs. real departure times.
|
||||
- **Delay Information:** Real-time delays are highlighted. Cancelled journeys are clearly marked.
|
||||
- **Local Routing:** See how long it takes to bike or walk from your home to the departure station.
|
||||
### Calendar Page
|
||||
|
||||
### Manual Event Entry (`/add-event`)
|
||||
If you don't have a calendar synced, or you have a one-off appointment, you can manually add an event by specifying the title, destination, and time.
|
||||
Open `/calendar` to import and review events.
|
||||
|
||||
---
|
||||
Available import sources:
|
||||
|
||||
## 📱 Mobile Application
|
||||
| Source | Description |
|
||||
| --- | --- |
|
||||
| URL | Paste an allowed public ICS URL. The backend fetches and parses future events with locations. |
|
||||
| File | Upload a local `.ics` file. The backend parses the file content. |
|
||||
| Google | Connect Google Calendar through OAuth, sync events, and disconnect when needed. |
|
||||
|
||||
The mobile app is perfect for on-the-go checks, leveraging your phone's native capabilities.
|
||||
Imported events are merged into local storage. The calendar view also includes batch destination editing so locations can be corrected before using them for route planning.
|
||||
|
||||
### Event List Screen
|
||||
The home screen mirrors the web dashboard, showing your upcoming events and their real-time status. You can pull-to-refresh to get the latest transit data.
|
||||
## Mobile App
|
||||
|
||||
### Event Detail Screen
|
||||
Tap on an event to see:
|
||||
- The best journey options and real-time platform information.
|
||||
- A countdown timer to your departure.
|
||||
- Step-by-step bike/walk directions to the station.
|
||||
The mobile app includes:
|
||||
|
||||
### Calendar Import Screen
|
||||
Import your calendar directly on the device. The mobile app can read your device's native calendar apps (via `expo-calendar`) if you prefer not to use a remote `.ics` URL.
|
||||
- Event list.
|
||||
- Add/edit event screen.
|
||||
- Event detail screen.
|
||||
- Calendar import screen.
|
||||
- Settings screen.
|
||||
|
||||
### Settings Screen
|
||||
Customize your experience:
|
||||
- **Buffer Time:** Set a default buffer (e.g., arrive 5 minutes early).
|
||||
- **Departure Buffer:** Add extra time for the actual transit journey.
|
||||
- **Toggle Options:** Enable/disable the walking or biking route suggestions based on your preference.
|
||||
- **Notifications:** Configure push notifications so you get an alert exactly when it's time to leave.
|
||||
### Calendar Import
|
||||
|
||||
---
|
||||
The mobile calendar import screen supports:
|
||||
|
||||
## ⏱️ Understanding "Leave Status"
|
||||
- ICS URL import through the configured backend.
|
||||
- Native device-calendar sync for the next 30 days.
|
||||
- Calendar selection before native sync.
|
||||
- Source grouping for CalDAV/DAVx, Apple, Google, Exchange, subscribed, local, CardDAV, ActiveSync, and other calendars when the device reports that metadata.
|
||||
|
||||
The "Leave Status" is the heart of TimeToLeave. It is calculated dynamically by comparing the **real departure time** of your best non-cancelled journey against the **current time**.
|
||||
If no native calendar selection is saved, the sync uses all available calendars.
|
||||
|
||||
### Event Detail
|
||||
|
||||
Tap an event to see:
|
||||
|
||||
- Leave-by and arrive-by times.
|
||||
- Train journeys from the saved origin station to the destination station.
|
||||
- Optional walking route for the final leg.
|
||||
- Optional bike route.
|
||||
- Nearby destination stops and live Wiener Linien departures.
|
||||
|
||||
### Settings
|
||||
|
||||
Use settings to configure:
|
||||
|
||||
- Origin station.
|
||||
- Current-location origin lookup.
|
||||
- Reminder buffer.
|
||||
- Arrival buffer.
|
||||
- Walking option visibility.
|
||||
- Bike option visibility.
|
||||
- Notifications.
|
||||
- Dark/light theme.
|
||||
|
||||
## Leave-By Status
|
||||
|
||||
The countdown and leave-by time are calculated from the selected transport mode.
|
||||
|
||||
For train mode, the app looks for a non-cancelled journey that arrives early enough after accounting for the final walk and arrival buffer. For bike mode, it subtracts the bike route duration from the target arrival time.
|
||||
|
||||
Countdown colors:
|
||||
|
||||
| Color | Meaning |
|
||||
| --- | --- |
|
||||
| Red | Leave time is now or already passed. |
|
||||
| Orange | Leave time is within 10 minutes. |
|
||||
| Yellow | Leave time is within 30 minutes. |
|
||||
| Green | Leave time is within 60 minutes. |
|
||||
| Blue | More than 60 minutes remain. |
|
||||
|
||||
Text statuses can include:
|
||||
|
||||
| Status | Meaning |
|
||||
| :--- | :--- |
|
||||
| **Leave now** | Your train departs within 15 minutes. Head out! |
|
||||
| **On time** | Everything is running smoothly, and you have a comfortable window. |
|
||||
| **Delayed +X min** | Your train is delayed. You can stay home a bit longer! |
|
||||
| **Departure missed** | The best available journey has already departed. A new search may be required. |
|
||||
| **All journeys cancelled** | Unfortunately, all connections for this time slot are cancelled. |
|
||||
| --- | --- |
|
||||
| `Leave now` | The selected departure is close enough that you should go. |
|
||||
| `On time` | The selected connection is currently usable. |
|
||||
| `Delayed +N min` | The selected journey is delayed by more than 10 minutes. |
|
||||
| `Departure missed` | The selected departure has already left. |
|
||||
| `All journeys cancelled` | Every returned journey is cancelled. |
|
||||
| `No journey data` | No usable journey data is available. |
|
||||
|
||||
## 🔒 Privacy & Data
|
||||
## Notifications
|
||||
|
||||
TimeToLeave is designed with privacy in mind:
|
||||
- Calendar data is processed server-side solely for the purpose of event extraction and is not permanently stored beyond the active session.
|
||||
- Geolocation data is used exclusively for calculating routes and finding nearby stations. It is never shared with third parties.
|
||||
Web reminders use browser notifications when permission is granted. Mobile reminders use local Expo notifications.
|
||||
|
||||
---
|
||||
Mobile notifications are scheduled from stored events and settings. Because the mobile store does not retain live journey data, scheduled notification times use a conservative fallback based on event time, arrival buffer, and reminder buffer.
|
||||
|
||||
*Happy traveling! Built for developers who bike to the train and hate missing their connections.*
|
||||
## Data and Privacy
|
||||
|
||||
Event data is stored locally in the browser or on the device. Some features send the minimum required request data to external services:
|
||||
|
||||
- Destination text is sent to Nominatim for geocoding.
|
||||
- Coordinates are sent to OSRM for bike/walk route calculation.
|
||||
- Station IDs and journey times are sent to ÖBB HAFAS.
|
||||
- Coordinates or stop IDs are sent to Wiener Linien for nearby stops and departures.
|
||||
- Google Calendar sync uses Google OAuth and server-side token cookies.
|
||||
- Remote ICS imports fetch the provided calendar URL through the backend.
|
||||
|
||||
See the root [Privacy Policy](../PRIVACY_POLICY.md) for more detail.
|
||||
|
||||
Reference in New Issue
Block a user