Gamified Travel

TokyoQuest

A full-stack Next.js app that turns Tokyo sightseeing into a quest game — sign in with Google, complete quests with proof media, earn EXP, and unlock story chapters by leveling up.

  • Next.js
  • TypeScript
  • Prisma
  • PostgreSQL
  • NextAuth.js
  • Supabase Storage
  • Tailwind CSS
  • Zod
Visual Placeholder

Gamified Travel

Overview

Project Context

A recruiter-friendly breakdown of the product goal, audience, and engineering direction.

Overview

What it is

A full-stack web app that frames Tokyo sightseeing as a quest game: users sign in, browse quests, save favorites, submit proof media to complete quests, earn EXP, and unlock story chapters as they level up.

Who it is for

Tokyo visitors and local explorers who prefer mission-based discovery over a static list of places.

Why it matters

Adding progress, completion proof, and a narrative layer turns one-off sightseeing into something repeatable and worth coming back to.

Problem

What needed to be solved

Most Tokyo travel apps are flat lists of places with no sense of progress or storytelling. Users have no reason to come back, and exploration feels detached from any larger arc.

  • Generic 'best of' lists don't create momentum across a multi-day trip.
  • Saving a place gives no signal of progress once you've actually been there.
  • Without a structured completion step, there's no record of what users actually did.

Solution

Product Approach

Model the experience as quest → save → complete → level → unlock, then keep the integrity-critical pieces (auth, completion validation, level/story progression) on the server.

Main User Flow

  1. 01Sign in with Google.
  2. 02Browse paginated quests on /home and save favorites.
  3. 03Open a quest detail page; review status, comments, and the completion UI.
  4. 04Submit photo or video proof through the in-app camera/upload flow.
  5. 05Receive EXP, see the level-up modal, and unlock the next story chapter.

Key Decisions

  • Use NextAuth (Google + Prisma adapter) so identity and session live next to the data model.
  • Run quest completion as one server route that validates media, blocks duplicates, awards EXP, and updates story progress atomically.
  • Gate story chapters by level via StoryProgress.unlockedLevels rather than client checks.
  • Push everything sensitive — quest CRUD, image uploads, completion review — behind isStaff admin endpoints.

Architecture

Technical Design

A high-level system view that explains the app layers and data flow without hiding the engineering decisions.

Architecture and technical design

1

Frontend

Next.js 15 App Router with TypeScript, Tailwind CSS, and Chakra UI. Server components handle data loading and access control; client components drive quest cards, the camera/upload flow, the story timeline + reader modal, and the level-up modal.

2

Backend

Next.js Route Handlers under src/app/api/ for quests, saves, completions, reviews, stories, blogs, and admin operations. Zod validation, rate limiting, security headers (next.config.ts), and middleware-based auth redirects sit in front of every protected route.

3

Database

PostgreSQL via Prisma. The schema models User, Quest, Tag, QuestCompletion, SavedQuest, Review, Blog, BlogContent, StoryChapter, StoryProgress, and StoryAnswer. A DTO layer (src/lib/dto.ts) limits what the client receives.

4

Auth

NextAuth.js with the Google provider, Prisma adapter, JWT sessions, and secure-cookie options. Middleware redirects unauthenticated users away from protected paths; admin APIs additionally enforce isStaff.

5

Deploy

Targets a Vercel-style Next.js deployment (next.config.ts uses standalone output and @vercel/speed-insights). Live URL at tokyoquest.jp is referenced in code but not verified from the repo alone.

Data Flow

  1. 1

    User submits a Zod-validated request with their NextAuth session.

  2. 2

    Middleware enforces auth and applies security headers.

  3. 3

    Route handler validates input (and media for completions) and runs Prisma calls.

  4. 4

    Completion logic awards EXP, recomputes level, and updates story progress.

  5. 5

    Pages map results through DTOs before rendering the client UI.

Features

Key Product Capabilities

Google Sign-In

NextAuth with the Google provider, Prisma adapter, JWT sessions, and secure-cookie options.

Paginated Quest Browsing

Authenticated /home feed with pagination, saved-state decoration, and tag-based category pages.

Save / Unsave Quests

Optimistic UI on quest cards backed by a SavedQuest join model so users can plan a trip before completing anything.

Quest Completion With Proof Media

Accepts Base64 image/video data, enforces type/size checks, prevents duplicate completions, awards 100 EXP, and recalculates level.

Level-Driven Story Unlocks

Story chapters unlock by user level, with read-progress tracking and persisted riddle answers (StoryProgress / StoryAnswer).

Staff-Only Admin Dashboard

Internal tools for managing completions, quests, tags, blog content, and Supabase-backed image uploads, gated by isStaff.

Key features

Engineering

Challenges And Tradeoffs

Atomic Quest Completion

Problem

A single user action has to validate media, block duplicate completions, grant EXP, recalculate level, and update story progress — and none of those steps can be left half-finished.

Resolution

Centralize the flow in src/app/api/quests/[id]/complete/route.ts with Zod-validated payloads, server-side type/size checks on Base64 media, and a level-system module (src/lib/level-system.ts) that owns thresholds and unit-tested level math.

Keeping Client Payloads Honest

Problem

Prisma models include fields the client should never see (timestamps on internal joins, raw user metadata, admin-only flags), and exposing them by accident is the easiest way to leak data.

Resolution

Route every page through src/lib/dto.ts so home, profile, and quest detail pages render from explicit DTOs instead of raw Prisma results, and isolate admin reads behind isStaff-checked endpoints.

A Story System That Reacts To Gameplay

Problem

Stories aren't a separate feature — they have to react to quest completions in real time without becoming a tangle of cross-cutting writes.

Resolution

Treat StoryProgress.unlockedLevels as the single source of truth, update it from the completion route, and let the story APIs and StoryReader gate chapters and persist read state / riddle answers off that one field.

Engineering challenges

Impact / Results

  • Demonstrates an end-to-end gamified product loop — auth → browse → save → complete → level → unlock — on one Next.js + Prisma stack.
  • Pushes integrity-critical logic (completion, level math, story unlocks, admin CRUD) into server routes and a tested level-system module rather than client code.
  • Note: live deployment status, real-user usage, and production performance are unconfirmed from the repo alone — see open questions below.

What I Would Improve

  • Verify and publish the live URL (tokyoquest.jp is referenced in code but not confirmed live).
  • Add a test script to package.json and expand coverage beyond the level-system unit tests (completion route, story unlocks, admin guards).
  • Consider replacing or hardening Base64 media uploads with direct-to-storage uploads to Supabase Storage for larger files.
  • Decide whether the admin route name (/miasanmia_admin) should be generalized before a public portfolio link.

Impact and future improvements