Y
Belaichi.exe

SpinMyDay

←  BACK_TO_HOME.EXE

[ CASE STUDY : AI · FULL-STACK ]

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.

Role
Full-Stack Developer
Stack
Next.js 15 · GPT-4o · TypeScript
Type
AI Web Application
Status
● Live

// REAL-WORLD VALUE

<30s

AI generation time
Full 6-slot itinerary

2 min

Total user time
Form input to complete plan

30–60

Minutes saved
vs. manual travel research

6

Named time slots
Morning through night

// THE PROBLEM

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.

What makes it different

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.

Shareability as growth

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.

// ARCHITECTURE

Data Flow: Form to Itinerary

01

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.

02

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.

03

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.

04

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.

05

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.

06

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 })

// WHY THE ARCHITECTURE WORKS

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.

// TECHNICAL STACK

Built With Purpose

Next.js 15.2
TypeScript 5
OpenAI GPT-4o
OpenWeatherMap
Google Places API
Tailwind CSS 3.4
Radix UI + shadcn
react-hook-form + zod
date-fns 4
next-themes

// ENGINEERING WHY

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])

// HARDEST PROBLEMS

Challenges Solved

⚠ Challenge

LLMs frequently wrap JSON responses in conversational preamble, breaking JSON.parse() and surfacing raw errors to users.

✓ Solution

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.

⚠ Challenge

The model would acknowledge weather in a generic, boilerplate way — “it might rain, so consider an indoor option” — regardless of actual conditions.

✓ Solution

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.

⚠ Challenge

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.

✓ Solution

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.

⚠ Challenge

Instagram does not support direct URL sharing from web applications — the standard Web Share API and deep-link approach both fail silently.

✓ Solution

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

Security-Conscious by Default

Zero key exposure

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.

API proxying

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.

No data persistence

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.

Early key validation

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.

// ROADMAP

What’s Next

HIGH

Geocoding for custom locations

Resolve user-typed city names to coordinates so the weather API works for all destinations, not just pre-seeded ones.

HIGH

Streaming AI responses

Stream GPT-4o output progressively — reducing perceived time-to-first-content from 3–8 seconds to near-instant.

HIGH

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.

MED

Saved itineraries + user accounts

Persist plans with Supabase and Clerk authentication so users can revisit and share past trips.

MED

PDF / calendar export

Allow users to export their itinerary as a PDF or iCal file for offline use and calendar integration.

LOW

i18n + analytics dashboard

Internationalization for non-English speakers, and an admin view tracking popular destinations and average generation time.

// NEED AN AI-POWERED PRODUCT BUILT?

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.

GET IN TOUCH

© 2026 YASSIR BELAICHI
[ SYSTEM STATUS: ONLINE ]
BUILT WITH UNITY + PASSION

Scroll to Top