update documentation
This commit is contained in:
@@ -1,162 +1,128 @@
|
||||
# ⏱️ TimeToLeave
|
||||
# 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** is a smart departure planner that tells you exactly when to leave home to catch your public transport for upcoming appointments. It syncs with your personal calendar (`.ics` files or Google Calendar), checks real-time train/bus departures (HAFAS & WienerLinien), and provides a live "Leave Status" based on real-time delays.
|
||||
    
|
||||
|
||||
   
|
||||
## What It Does
|
||||
|
||||
## 🚀 How It Works
|
||||
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.
|
||||
|
||||
1. **Sync Your Calendar:** Import your `.ics` file, provide a calendar URL, or connect your Google Calendar via OAuth 2.0. The app extracts your upcoming events and destinations.
|
||||
2. **Set Your Origin:** Define your home station or let the app use your current geolocation. The app can also find the nearest station to your location via HAFAS LocMatch.
|
||||
3. **Journey Calculation:** The app queries the HAFAS protocol and WienerLinien APIs to find the best public transport connections to your event destination.
|
||||
4. **Real-Time Monitoring:** It monitors your train's real-time departure time, accounts for delays, and adds your local travel time (e.g., biking or walking to the station) to calculate a dynamic countdown.
|
||||
5. **Leave Status:** You get a clear status: `Leave now`, `On time`, `Delayed +X min`, or `Departure missed`.
|
||||
## Repository Layout
|
||||
|
||||
## 🧱 Project Structure
|
||||
| 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. |
|
||||
|
||||
This project uses a monorepo setup (npm workspaces) to manage multiple, interconnected parts:
|
||||
## Prerequisites
|
||||
|
||||
| Directory | Description |
|
||||
| :--- | :--- |
|
||||
| `apps/web/` | The main web dashboard built with **Next.js 16**, React 19, and Tailwind CSS 4. |
|
||||
| `apps/mobile/` | The on-the-go mobile client built with **React Native 0.81** and **Expo 54**. |
|
||||
| `packages/core/` | Shared domain logic, types (`Event`, `Journey`, `Station`), countdown utilities, and status calculators. |
|
||||
| `packages/api-client/` | A lightweight client that handles API proxies for HAFAS requests, calendar parsing, geocoding, bike routing, and Google Calendar sync. |
|
||||
- Node.js 20 or newer
|
||||
- npm 9 or newer
|
||||
- For mobile native builds: Expo/EAS prerequisites plus Android Studio or Xcode as needed
|
||||
|
||||
## 🛠 Development & Running the Application
|
||||
## Setup
|
||||
|
||||
### Prerequisites
|
||||
|
||||
* Node.js (version 20.x or higher)
|
||||
* npm (version 9.x or higher)
|
||||
|
||||
### Installation
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd TimeToLeave
|
||||
```
|
||||
|
||||
2. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
3. **Environment variables:**
|
||||
* For `apps/web` and `apps/mobile`, copy `.env.example` to `.env` in each app directory and update the backend API URL and any required keys.
|
||||
* Google Calendar integration requires `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET`, and `GOOGLE_REDIRECT_URI` environment variables.
|
||||
|
||||
### Available Scripts
|
||||
|
||||
| Script | Command | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `dev` | `npm run dev` | Starts the Next.js development server for the Web dashboard. |
|
||||
| `dev:mobile` | `npm run dev:mobile` | Starts the Expo development server for the Mobile client. |
|
||||
| `build` | `npm run build` | Builds the production bundle for the Web application. |
|
||||
| `test` | `npm run test` | Runs Vitest for the Web app and Jest for the Mobile app. |
|
||||
| `lint` | `npm run lint` | Runs ESLint across both web and mobile clients. |
|
||||
| `typecheck` | `npm run typecheck` | Runs TypeScript type checking across all workspaces. |
|
||||
|
||||
## 📝 Key Features & Tech Stack
|
||||
|
||||
### Web Application (`apps/web`)
|
||||
* **Framework:** Next.js 16.2.6 (App Router)
|
||||
* **UI:** React 19.1.0 with Tailwind CSS 4
|
||||
* **State Management:** React Context (via `EventsProvider` and `ReminderSettingsProvider`)
|
||||
* **Routing:** Next.js built-in routing for `/` (event list) and `/calendar` views.
|
||||
* **Calendar Integration:**
|
||||
* Import `.ics` files or provide calendar URLs
|
||||
* **Google Calendar sync** via full OAuth 2.0 flow (token exchange, refresh, and status checks)
|
||||
* Batch edit panel for managing event destinations
|
||||
* Edit support in the AddEventModal for modifying existing events
|
||||
* **Dark/Light Theme:** Built-in theme toggle
|
||||
|
||||
### Mobile Application (`apps/mobile`)
|
||||
* **Framework:** React Native 0.81 via Expo 54
|
||||
* **Navigation:** React Navigation 7 (Native Stack)
|
||||
* **Theme:** Dark theme by default
|
||||
* **Device APIs:**
|
||||
* `expo-location`: For geocoding your current position.
|
||||
* `expo-calendar`: For native calendar event integration.
|
||||
* `expo-notifications`: For native push notifications when it's time to leave.
|
||||
* `@react-native-async-storage/async-storage`: For persisting settings and local state.
|
||||
* **Station Selection:** Async station search with error handling and nearest-station detection via HAFAS LocMatch.
|
||||
|
||||
### Core Logic (`packages/core`)
|
||||
* **Countdown Utilities:** Calculates time-deltas and assigns color codes (Red/Orange/Yellow/Green/Blue) based on urgency.
|
||||
* **HAFAS Time Parsing:** Highly accurate timezone-aware parsing for HAFAS timestamps, specifically handling `Europe/Vienna` (CET/CEST) and DST transitions.
|
||||
* **WienerLinien Support:** Native types and handling for Vienna public transport departures.
|
||||
* **Leave Status:** Derives human-readable statuses (`Leave now`, `Delayed +10 min`, etc.) by comparing the best non-cancelled journey's real departure time against the current time.
|
||||
|
||||
## 📄 API Client Usage
|
||||
|
||||
The `@timetoleave/api-client` package provides a clean interface to interact with your backend proxy, which handles the heavy lifting of HAFAS protocol communication, calendar parsing, and Google Calendar sync.
|
||||
|
||||
```typescript
|
||||
import { ApiClient } from "@timetoleave/api-client";
|
||||
|
||||
// Initialize with your backend URL
|
||||
const api = new ApiClient("http://localhost:3000");
|
||||
|
||||
// 1. Sync your calendar (via .ics URL)
|
||||
const events = await api.fetchCalendar("https://example.com/calendar.ics", 7);
|
||||
|
||||
// 2. Sync Google Calendar (after OAuth flow)
|
||||
const googleEvents = await api.fetchGoogleCalendarEvents();
|
||||
|
||||
// 3. Search for a station via the HAFAS LocMatch endpoint
|
||||
const stationResult = await api.hafasRequest({
|
||||
svcReqL: [
|
||||
{
|
||||
meth: "LocMatch",
|
||||
req: { searchTxt: "Wien Mitte", maxMatches: 5 },
|
||||
},
|
||||
],
|
||||
});
|
||||
const stations = stationResult?.svcReqL?.[0]?.res?.locL ?? [];
|
||||
|
||||
// 4. Find the nearest station to your current location
|
||||
const nearestStation = await api.findNearestStation(48.2082, 16.3738);
|
||||
|
||||
// 5. Find journeys between stations for a specific date
|
||||
const journeys = await api.searchJourneys(
|
||||
stations[0].extId, // From
|
||||
"dest:extId", // To
|
||||
new Date() // Date
|
||||
);
|
||||
|
||||
// 6. Get a bike route from your current location to the station
|
||||
const bikeRoute = await api.getBikeRoute(
|
||||
48.2082, 16.3738, // From lat/lng
|
||||
48.1850, 16.3780 // To lat/lng
|
||||
);
|
||||
```bash
|
||||
npm install
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
## 🛡️ Testing & Quality Assurance
|
||||
The web app reads environment variables from the workspace process. For deployment, configure the same values in the hosting environment.
|
||||
|
||||
The project provides comprehensive scripts for maintaining code quality:
|
||||
Important variables:
|
||||
|
||||
* **Linting:** Use `npm run lint` to catch stylistic and structural errors via ESLint 9.
|
||||
* **Type Checking:** Use `npm run typecheck` to ensure strict type safety across the codebase via TypeScript 5.
|
||||
* **Testing:**
|
||||
* The web application uses **Vitest** (v4.1.5) with **jsdom** and **@testing-library/react**.
|
||||
* The mobile application uses **Jest** (v29.7.0) with **jest-expo** and **react-test-renderer**.
|
||||
| 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. |
|
||||
|
||||
## 📂 File Structure
|
||||
## Development
|
||||
|
||||
```text
|
||||
├── apps/
|
||||
│ ├── mobile/ # Mobile application using React Native and Expo
|
||||
│ └── web/ # Web application using Next.js and Tailwind CSS
|
||||
├── packages/
|
||||
│ ├── api-client/ # API client for HAFAS, Calendar, Google Calendar, and Routing proxies
|
||||
│ └── core/ # Shared domain types, countdowns, and HAFAS time utilities
|
||||
├── node_modules/ # Third-party dependencies
|
||||
└── README.md # The file you're reading now
|
||||
```
|
||||
| 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. |
|
||||
|
||||
---
|
||||
*Built for developers who bike to the train and hate missing their connections.*
|
||||
## 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/`.
|
||||
|
||||
Reference in New Issue
Block a user