SpinMyDay
A full-stack AI travel planning application that generates weather-aware, hyper-personalized day-trip itineraries in under 30 seconds — designed to feel like a consumer product, not a developer demo.
Planning a Day Trip Shouldn’t Take All Day
Planning a one-day trip in an unfamiliar city typically means cross-referencing review platforms, checking weather, matching activities to budget, and accounting for group dynamics — a process that can take 30–60 minutes and still produces generic results.
SpinMyDay is designed to compress this into a short conversational form and a single AI call. The key differentiator over static guides or open-ended AI chat is the structured, opinionated output: a fixed 6-slot itinerary that feels curated rather than a list of suggestions to sift through.
Weather is a first-class input — real temperature and conditions are injected into the AI prompt before generation, changing both what is recommended and how each activity is described.
Deep-link sharing encodes the full itinerary in the URL with no backend storage — users become distribution channels, pulling new visitors directly into the product experience.
Data Flow: Form to Itinerary
User completes 8-step wizard
Age, travel type, mood, budget, dietary needs, and destination collected one decision at a time — reducing cognitive load and improving completion rate.
Server Action invoked — no HTTP round-trip
generateItinerary() fires as a Next.js Server Action. API keys never leave the server. No client bundle exposure.
Live weather fetched by coordinate
OpenWeatherMap is called with lat/lng pairs (more reliable than city-name lookups). Temperature, humidity, wind, and conditions are parsed and normalised.
Structured prompt assembled — weather embedded
User profile + real weather data are composed into a schema-locked prompt. GPT-4o is instructed to embed temperature values and practical weather advice in every activity description.
GPT-4o returns JSON — extracted robustly
Model called at temperature: 0.7. A regex fallback /\{[\s\S]*\}/ isolates valid JSON even when the model adds surrounding prose — preventing hard parse failures.
Timeline rendered to client
Six activity cards displayed with name, description, neighborhood, budget range, weather note, and direct Google Maps link. Each card supports “Spin Again” regeneration.
Dual API Architecture
SERVER ACTION (primary)
- Called directly from React — no HTTP overhead
- Used by the main form flow
- Keys in
process.env— never exposed - Returns structured
{ success, itinerary, loc }
REST ROUTE /api/plan (secondary)
- HTTP POST — useful for testing and external integrations
- Parallel implementation of identical logic
- Enables future migration to a standalone backend
- Returns
NextResponse.json({ itinerary })
System Strengths
Stateless design
No session affinity required — the application is designed to scale horizontally without coordination overhead.
Server-side AI
All external API calls happen server-side. No credentials, prompts, or third-party responses are ever passed through the client bundle.
Structured outputs
A schema-locked JSON contract between the prompt and the UI means the display layer is stable and predictable regardless of model variation.
Weather as a first-class input
Real conditions are injected before prompt assembly — making the itinerary genuinely executable on the day it is generated, not just plausible in general.
Built With Purpose
Key Decisions
Server Actions over client fetch()
The primary generation call is a Server Action — this keeps API keys strictly server-side, eliminates an unnecessary HTTP round-trip, and allows the form to call the function as a typed async call with structured return values instead of raw HTTP status codes.
Structured JSON schema prompt
Rather than free-text output, the prompt defines an explicit JSON contract the model is expected to conform to. This makes UI rendering stable and deterministic. A regex fallback handles the cases where the model adds conversational framing around its JSON output.
Weather fetched before prompt assembly
Most travel AI tools pass weather as an optional afterthought. Here, real numerical data — temperature, humidity, wind — is fetched first and embedded directly into the prompt, with the model explicitly instructed to reference conditions in every activity description.
Coordinate-based location resolution
Destinations are stored as { lat, lng } pairs rather than city name strings. The coordinate endpoint of OpenWeatherMap is globally consistent and avoids ambiguity issues that city-name lookups introduce for non-English or similarly-named locations.
TypeScript — lib/actions.ts (simplified)
// Step 1: Fetch live weather by coordinate — always before the prompt const weatherData = await fetch( `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lng}&units=metric&appid=${API_KEY}` ) // Step 2: Compose a schema-locked prompt with weather as first-class data const prompt = ` Create a personalized day trip for a ${age}-year-old ${gender}, traveling ${travelType}. Mood: ${mood} | Budget: ${budget} | Location: ${locationName} CURRENT WEATHER: - Condition: ${weatherCondition} - Temperature: ${temp}°C (feels like ${feelsLike}°C) - Wind: ${windSpeed} km/h | Humidity: ${humidity}% INSTRUCTION: Every activity MUST reference the weather with practical advice. Return JSON: { morning, midday, lunch, afternoon, evening, night } Each slot: { time, activity, description, type, address, budget, weatherNote } ` // Step 3: Call model, extract JSON — regex fallback handles model prose wrapping const raw = await openaiChatCompletion(prompt) const match = raw.match(/\{[\s\S]*\}/) const itinerary = JSON.parse(match[0])
Challenges Solved
LLMs frequently wrap JSON responses in conversational preamble, breaking JSON.parse() and surfacing raw errors to users.
Two-layer strategy: the system prompt explicitly instructs the model to return only JSON, and a regex extractor isolates the JSON block even when prose is added. If parsing still fails, a structured error code is returned — not a raw exception.
The model would acknowledge weather in a generic, boilerplate way — “it might rain, so consider an indoor option” — regardless of actual conditions.
The prompt was updated to include real numerical values and example descriptions showing the expected integration style. The model now references specific temperatures, practical clothing advice, and condition-specific tradeoffs in each activity.
A progress bar with two distinct behaviors — step tracking during the form, and a 0→100% animation on AI submission — needed clean isolation without leaking timers.
Two separate useEffect hooks, each managing its own interval, handle the two phases independently. Both effects clean up on re-render, preventing memory leaks and competing timer states.
Instagram does not support direct URL sharing from web applications — the standard Web Share API and deep-link approach both fail silently.
Platform is detected (iOS / Android / desktop), a rich caption with emoji and hashtags is copied to the clipboard, then a native URI scheme attempts to open the Instagram app — with a 2-second timeout fallback to the appropriate app store.
Security-Conscious by Default
All three API keys — OpenAI, OpenWeatherMap, Google Places — are accessed exclusively via process.env on the server. None appear in any response body or client bundle.
Weather and Places endpoints are proxied through Next.js API routes. The browser never communicates directly with third-party APIs — enabling server-side rate limiting or caching to be layered in later.
User profile data — age, gender, dietary preferences — is never stored, logged, or transmitted beyond the lifetime of a single request. No database, no accounts, no session storage.
Every server route checks for required API keys before making any external call, returning a structured api_key_missing error code rather than surfacing raw environment failures.
What’s Next
Geocoding for custom locations
Resolve user-typed city names to coordinates so the weather API works for all destinations, not just pre-seeded ones.
Streaming AI responses
Stream GPT-4o output progressively — reducing perceived time-to-first-content from 3–8 seconds to near-instant.
True per-slot Spin Again
Replace the local alternatives pool with a live API call that regenerates only the specific time slot, not the full itinerary.
Saved itineraries + user accounts
Persist plans with Supabase and Clerk authentication so users can revisit and share past trips.
PDF / calendar export
Allow users to export their itinerary as a PDF or iCal file for offline use and calendar integration.
i18n + analytics dashboard
Internationalization for non-English speakers, and an admin view tracking popular destinations and average generation time.
Let’s build something intelligent.
If you’re looking for full-stack AI integration done properly — structured outputs, secure architecture, and a product-quality experience — let’s talk.
[ SYSTEM STATUS: ONLINE ]
BUILT WITH UNITY + PASSION