Add ApiClient.findStationByExtId and toStation helper; use it in useOriginStationWalk. Clamp OSRM route/step durations to a minimum walking speed (1.25 m/s) and update tests to mock the new API call.
⏱️ TimeToLeave
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 (
.icsfiles or Google Calendar), checks real-time train/bus departures (HAFAS & WienerLinien), and provides a live "Leave Status" based on real-time delays.
🚀 How It Works
- Sync Your Calendar: Import your
.icsfile, provide a calendar URL, or connect your Google Calendar via OAuth 2.0. The app extracts your upcoming events and destinations. - 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.
- Journey Calculation: The app queries the HAFAS protocol and WienerLinien APIs to find the best public transport connections to your event destination.
- 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.
- Leave Status: You get a clear status:
Leave now,On time,Delayed +X min, orDeparture missed.
🧱 Project Structure
This project uses a monorepo setup (npm workspaces) to manage multiple, interconnected parts:
| 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. |
🛠 Development & Running the Application
Prerequisites
- Node.js (version 20.x or higher)
- npm (version 9.x or higher)
Installation
-
Clone the repository:
git clone <repository-url> cd TimeToLeave -
Install dependencies:
npm install -
Environment variables:
- For
apps/webandapps/mobile, copy.env.exampleto.envin each app directory and update the backend API URL and any required keys. - Google Calendar integration requires
GOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET, andGOOGLE_REDIRECT_URIenvironment variables.
- For
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
EventsProviderandReminderSettingsProvider) - Routing: Next.js built-in routing for
/(event list) and/calendarviews. - Calendar Integration:
- Import
.icsfiles 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
- Import
- 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.
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
);
🛡️ Testing & Quality Assurance
The project provides comprehensive scripts for maintaining code quality:
- Linting: Use
npm run lintto catch stylistic and structural errors via ESLint 9. - Type Checking: Use
npm run typecheckto 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.
📂 File Structure
├── 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
Built for developers who bike to the train and hate missing their connections.
