Add Docker support with multi-stage build and standalone output

This commit is contained in:
2026-05-10 00:42:39 +02:00
parent 6af44b5c13
commit 7c345785a7
5 changed files with 243 additions and 8 deletions
+136 -7
View File
@@ -1,21 +1,150 @@
# TimeToLeave - Next.js Rewrite
# TimeToLeave
This is a rewrite of the TimeToLeave application using Next.js App Router with TypeScript.
> Know when to leave the house so you're never late for your next meeting.
## Development
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
```bash
npm install
```
### Development
```bash
npm run dev
```
## Building
The app will be available at [http://localhost:3000](http://localhost:3000).
### Build & Start
```bash
npm run build
npm start
```
## Testing
### Docker Deployment
Build and run with Docker Compose:
```bash
npm test
```
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:
```env
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
```bash
npm test # Run tests once
npm run test:watch # Run tests in watch mode
```
### Linting & Type Checking
```bash
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](https://nextjs.org/) (App Router) |
| UI | [React 19](https://react.dev/) + [Tailwind CSS v4](https://tailwindcss.com/) |
| Language | [TypeScript 5](https://www.typescriptlang.org/) |
| Train Data | [ÖBB HAFAS API](https://fahrplan.oebb.at/) |
| Geocoding | [Nominatim](https://nominatim.openstreetmap.org/) |
| Bicycle Routing | [OSRM](https://project-osrm.org/) |
| Calendar | [node-ical](https://www.npmjs.com/package/node-ical) |
| Dates | [date-fns](https://date-fns.org/) |
| Testing | [Vitest](https://vitest.dev/) + [React Testing Library](https://testing-library.com/) |
## How It Works
1. **Add events** — Manually enter events or import from an ICS calendar URL.
2. **Pick an origin** — Use your current location or search for a departure station.
3. **Get journey info** — The app queries ÖBB for real-time train schedules and optionally calculates bicycle routes via OSRM.
4. **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