Skip to content
gCascone01Public

About

Resources

Stars

3 stars

Watchers

1 watching

Forks

Repository files navigation

PackrOn

AI travel planner for road trips and city trips. Fill in a short wizard, Gemini returns a full itinerary (stops, times, map coordinates, lodging, fuel/tolls), then you edit, save, and share it.

Stack

  • Next.js 16 (App Router), React 19, TypeScript
  • Tailwind 4 + shadcn (base-nova)
  • Leaflet maps (leaflet + react-leaflet)
  • Google Gemini (@google/genai, primary gemini-3.1-flash-lite with automatic fallback)
  • Supabase Auth + Postgres (@supabase/ssr + @supabase/supabase-js) — login, saved trips, passkeys
  • @react-pdf/renderer for client-side PDF export (dynamically imported on click)
  • Italian and English via URL prefixes (/it, /en)
  • Light/dark theme (cookie-persisted, OS-seeded on first visit)

Features

  • Road trips: multi-stop driving itineraries with distances, fuel costs, tolls, overnight stays. Optional "visit origin" toggle includes sightseeing in the origin city.
  • City trips: walkable/public-transport itineraries within a single city.
  • Editable results: reorder/remove stops (distances recompute live), replace a stop from 3 Gemini alternatives with undo toast + pinnable previous choice.
  • Lazy stop details: on-demand description (Gemini) + real photo (Wikipedia, freely licensed) in a modal.
  • Export to PDF: a printable, offline A4 itinerary (days, stops, substops, costs, toll breakdown, plus a black & white route map with territory outlines + numbered stops for road trips) generated client-side with @react-pdf/renderer, saved as PackrOn - <Title>.pdf.
  • Sharing: every generated trip is stored in Supabase and shared with its public UUID route (/{locale}/trip/{id}). Guest trips can be claimed by signing up.
  • Accounts & saved trips: email/password auth, passkeys (WebAuthn), username profile, saved-trip library with edit-overwrite, account deletion.
  • SEO: locale sitemap with hreflang, canonical URLs, Open Graph/Twitter cards, JSON-LD, robots rules.

Local setup

npm install
cp .env.example .env.local
# set GEMINI_API_KEY + Supabase vars (see below)
npm run dev

Open http://localhost:3000. Middleware redirects / to /it or /en from the packron-locale cookie or Accept-Language.

Scripts

Command Purpose
npm run dev Dev server
npm run build Production build
npm run typecheck tsc --noEmit
npm test Unit tests (Vitest)

Environment

Variable Required Notes
GEMINI_API_KEY Yes, for generation Also used for stop alternatives + descriptions
GEMINI_MODEL No Default gemini-3.1-flash-lite (fallback is built-in)
NEXT_PUBLIC_CARTO_BASEMAPS_KEY No Optional CARTO Voyager tiles; without it, maps use public OpenStreetMap tiles. Restrict the key to your site referrers
NEXT_PUBLIC_SUPABASE_URL Yes, for auth/saved trips Supabase project URL
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY Yes, for auth/saved trips Public sb_publishable_… key (legacy NEXT_PUBLIC_SUPABASE_ANON_KEY still works as fallback). Never add a secret/service-role key
NEXT_PUBLIC_SITE_URL Required in prod Canonical URL used for sitemap, canonical URLs, and social metadata
NEXT_PUBLIC_BOOKING_AFFILIATE_ID No Omitted from Booking URLs until set to a real aid
NEXT_PUBLIC_GETYOURGUIDE_PARTNER_ID No Optional partner_id on GetYourGuide searches
Without the Supabase vars the app still builds and runs, but auth UI explains setup and trip generation/storage returns 503.

Supabase setup

  1. Create a project, then run all migrations in the SQL editor (in order):
    • supabase/migrations/20260911000000_create_saved_trips.sql (table + RLS)
    • supabase/migrations/20260912000000_delete_own_account.sql (self-service account deletion)
    • supabase/migrations/20261002000000_unify_trip_storage.sql (guest rows + public trip reads)
  2. Set NEXT_PUBLIC_SUPABASE_URL + key, and NEXT_PUBLIC_SITE_URL in prod.
  3. Dashboard → Authentication → Providers → Email: disable email confirmation so signup returns an active session immediately. Set the Site URL to the production URL; this app does not send signup confirmation links.
  4. Optional, for passkeys: Authentication → Passkeys → enable, RP display name PackrOn, RP ID = bare production domain. RP ID binds credentials, so localhost needs its own RP ID/project for testing.

Routes

Pages (all under /it + /en):

  • / — planner
  • /how-it-works — how it works (legacy /come-funziona 302-redirects here)
  • /examples — example itineraries (legacy /esempi 302-redirects here)
  • /login, /signup — public auth entry points (in sitemap)
  • /trips, /trips/[id] — private saved-trip library (never indexed)
  • /trip/[id] — public itinerary by database UUID (never indexed)
  • /account — private profile, passkeys, delete account (never indexed)

API:

  • POST /api/generate-trip — Gemini itinerary (required-fields check only; feasibility via Gemini impossible_trip flag)
  • POST /api/suggest-stops — three nearby alternatives (Gemini, mock fallback)
  • POST /api/describe-stop — lazy description + Wikipedia photo
  • GET /api/reverse-geocode — Nominatim, then BigDataCloud
  • GET /api/trips, POST /api/trips — list / save (auth required)
  • GET /api/trips/[id] — public trip read; PUT (auto-save edits) / PATCH (favourite flag) / DELETE remain owner-only
  • POST /api/trips/claim — attach a guest trip to the authenticated user
  • DELETE /api/account — delete own account (confirmation by typing username)
  • GET /auth/callback — Supabase PKCE code exchange (not locale-prefixed)

Sharing vs saved trips

  • Every generated itinerary is inserted into saved_trips; authenticated generations include the server-derived user ID and guest generations keep user_id null. The trip UUID route is the same public URL used by the share action.
  • Edits in the trip view auto-save over the same row; the star button only toggles the is_favorite flag (run supabase/migrations/20261005000000_trip_favorites.sql). My Trips lists every computed trip, with favourites in a separate top section.
  • Guest trip IDs are also retained in browser local storage for the Recent Trips list. The guest can claim an orphaned row by creating an account while viewing it; the claim endpoint accepts only null-owner rows and derives ownership from the session.

Key files

  • lib/gemini.ts, lib/gemini-prompt.ts, lib/gemini-schema.ts — model call + fallback, prompt rules, response schema
  • lib/geocode.ts, lib/geo.ts — required-field check, haversine distances, live recompute
  • lib/trips.ts — saved-trip validation and row summaries
  • lib/supabase/ — browser/server clients, session refresh
  • lib/i18n.ts — all user-facing strings (it/en, MessageKey type-safe)
  • components/planner.tsx, components/result/ — wizard + editable result view
  • components/auth/, components/trips/ — login, account, saved-trip UI

Architecture decisions and rationale live in AGENTS.md (single source of truth for contributors).

public/logo.png is part of the repo (it may be hidden from some workspace scans by ignore rules).

About

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages