← All projects

Waypoint

ongoing

A shared trip planner that puts flights, stays, and spending on one map.

Why it exists

Every trip I've planned with other people ends up split across a spreadsheet, a maps app full of unlabeled pins, and a group chat doing the arithmetic on who owes what. Waypoint puts all three in one workspace, with the map and the data actually wired to each other instead of living in separate tabs.

Features

A map and a table that point at the same place

Every flight, stay, and itinerary stop can carry a map pin. Clicking a pin highlights its row, and clicking a row highlights its pin: the two views of a trip stay in sync instead of drifting apart.

One trip, everything in it

Flights, accommodations, a day-by-day itinerary, and shared expenses all hang off a single trip. An expense can attach to a specific itinerary stop, so "who paid for what" is never separated from "when did that happen."

One access code, not an account system

A small group sharing one trip doesn't need individual logins. Everyone who has the shared code gets the same workspace, with per-IP rate limiting on the sign-in form rather than a full user directory to maintain.

A Telegram bot for while you are actually traveling

Ask what's today, what's next, or log an expense by chat, without opening the app or hunting for wifi to load a full page. The bot reads and writes the same trip data the web workspace does.

Why I built it

Every group trip I’ve been part of ends up run out of three places that don’t talk to each other: a spreadsheet for the budget, a maps app full of pins nobody labeled properly, and a group chat doing the actual coordination. By the time you’re standing at a checkpoint asking “wait, is our flight pin the departure or the connection,” the tools have already failed you. I wanted one place where the map and the itinerary are the same object, not two things I have to keep mentally in sync myself.

What I worked on

The trip is the one root everything hangs off: flights, accommodations, itinerary days and their stops, and expenses, each scoped to a trip and cascading cleanly on delete. The part I spent the most time on is a single LocationPin model that a flight, a stay, or an itinerary stop can each attach to, at most one at a time, through a nullable unique foreign key. That one design choice is what lets the map and the data tables cross-reference each other: clicking a pin highlights its row and clicking a row highlights its pin, sharing one piece of state instead of two views built separately and hoping they agree.

Everything mutates through Server Actions, not a separate API layer. Every action checks the session first, then scopes its write by the trip and record id together, never trusting an id alone. There’s no client-side data-fetching library sitting on top: an edit runs its action, revalidates the page, and the server component reloads the trip fresh. One fewer layer that could go stale.

Since this is a workspace for a small group traveling together, not a multi-tenant product, it doesn’t have individual accounts. One shared access code, bcrypt-checked, gets everyone into the same trip, with per-IP rate limiting on the sign-in form standing in for the account lockouts a real user system would otherwise need. A companion Telegram bot reads and writes the same data for the moments a full page load isn’t worth it: what’s today, what’s next, or logging an expense from the back of a taxi.

Not everything is closed out yet. A few Prisma calls, like deleting a row that’s already gone, don’t have their own error handling and would currently surface as a raw 500 rather than a clean message, and the access-code hashing has no explicit length guard even though bcrypt quietly truncates past 72 bytes. Both are logged as known gaps rather than shipped as if they were already handled.