diff --git a/CLAUDE.md b/CLAUDE.md index 2920e89..8bc6759 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,48 +8,196 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -VIGEOGUESSR is a Vietnamese geography guessing game built with Next.js 14. Players view 360ยฐ panoramic street images from Vietnamese cities and guess the location on an interactive map. The game uses Mapillary Graph API for panoramic images and calculates distance-based scoring. +VIGEOGUESSR is a Vietnamese geography guessing game built with Next.js 15 and React 19. Players view 360ยฐ panoramic street images from Vietnamese cities and guess the location on an interactive map. The game uses Mapillary Graph API for panoramic images and calculates distance-based scoring. -## Development Commands +## ๐Ÿ› ๏ธ Development Environment -- `npm run dev` - Start development server on localhost:3000 -- `npm run build` - Build for production -- `npm run start` - Start production server -- `npm run lint` - Run ESLint to check code quality +- **Language**: JavaScript (with jsconfig.json for IDE support) +- **Framework**: Next.js 15 (App Router) +- **React**: React 19 +- **Styling**: Tailwind CSS (migrating from custom CSS) +- **Component Library**: shadcn/ui (for generic UI components) +- **Specialized Libraries**: MapLibre GL JS + MapillaryJS (for mapping/street view) +- **Data Fetching**: React Query (TanStack) for API calls +- **Testing**: Jest + React Testing Library +- **Linting**: ESLint +- **Package Manager**: pnpm (preferred) -## Project Structure (Standard Next.js) +## ๐Ÿ“‚ Project Structure ``` src/ -โ”œโ”€โ”€ app/ # Next.js App Router pages -โ”‚ โ”œโ”€โ”€ api/ # Server-side API routes -โ”‚ โ”‚ โ””โ”€โ”€ mapillary/ # Mapillary API endpoint -โ”‚ โ”œโ”€โ”€ game/ # Game page -โ”‚ โ”œโ”€โ”€ globals.css # Global styles -โ”‚ โ”œโ”€โ”€ layout.js # Root layout -โ”‚ โ””โ”€โ”€ page.js # Home page -โ”œโ”€โ”€ components/ # React components -โ”‚ โ”œโ”€โ”€ ui/ # Reusable UI components -โ”‚ โ”‚ โ”œโ”€โ”€ Header.js # Navigation header -โ”‚ โ”‚ โ”œโ”€โ”€ DonateModal.js # Donation modal -โ”‚ โ”‚ โ””โ”€โ”€ index.js # Component exports -โ”‚ โ””โ”€โ”€ features/ # Game-specific components -โ”‚ โ”œโ”€โ”€ GameMap.js # Interactive Leaflet map -โ”‚ โ”œโ”€โ”€ PanoViewer.js # 360ยฐ image viewer -โ”‚ โ”œโ”€โ”€ ResultModal.js # Game result display -โ”‚ โ””โ”€โ”€ index.js # Component exports -โ”œโ”€โ”€ constants/ # Application constants -โ”‚ โ””โ”€โ”€ locations.js # Vietnamese city data -โ”œโ”€โ”€ utils/ # Utility functions -โ”‚ โ”œโ”€โ”€ mapillary.js # Mapillary API integration -โ”‚ โ”œโ”€โ”€ distance.js # Distance calculations -โ”‚ โ””โ”€โ”€ index.js # Utility exports -โ””โ”€โ”€ types/ # TypeScript definitions (ready for TS migration) +โ”œโ”€โ”€ app/ # Next.js App Router +โ”‚ โ”œโ”€โ”€ layout.js +โ”‚ โ”œโ”€โ”€ page.js +โ”‚ โ”œโ”€โ”€ game/ +โ”‚ โ””โ”€โ”€ api/ # Server-side API routes +โ”œโ”€โ”€ components/ # UI components (shadcn/ui + game-specific) +โ”‚ โ”œโ”€โ”€ GameMap.js # MapLibre GL JS integration +โ”‚ โ”œโ”€โ”€ PanoViewer.js # MapillaryJS integration +โ”‚ โ”œโ”€โ”€ ResultModal.js # Result display with map + street view +โ”‚ โ””โ”€โ”€ ui/ # shadcn/ui components +โ”œโ”€โ”€ hooks/ # Custom React hooks +โ”‚ โ”œโ”€โ”€ useMapillary.js # Image fetching with React Query +โ”‚ โ””โ”€โ”€ useScoring.js # Scoring API with React Query +โ”œโ”€โ”€ lib/ # Utilities and API wrappers +โ”œโ”€โ”€ constants/ # Game constants (cities, locations) +โ”œโ”€โ”€ utils/ # Utility functions +โ”œโ”€โ”€ styles/ # Tailwind customizations +โ”œโ”€โ”€ tests/ # Test files +โ””โ”€โ”€ public/ ``` -## Architecture +## ๐Ÿ“ฆ Installation & Setup -### Architectural Design Philosophy +- Tailwind CSS with PostCSS configuration +- shadcn/ui: `npx shadcn-ui@latest init` +- React Query: `` in app/layout.js +- MapLibre GL JS: `pnpm add maplibre-gl` +- MapillaryJS: `pnpm add mapillary-js` + +## โš™๏ธ Development Commands + +- **Dev server**: `pnpm dev` - Start development server on localhost:3000 +- **Build**: `pnpm build` - Build for production +- **Start**: `pnpm start` - Start production server +- **Lint**: `pnpm lint` - Run ESLint to check code quality +- **Test**: `pnpm test` - Run Jest + RTL tests +- **Format**: `pnpm format` - Format code with Prettier + +## ๐Ÿง  React Query Architecture + +### API Data Fetching (Option A - Recommended) + +```javascript +// hooks/useMapillary.js +const { data: imageData, isLoading } = useQuery({ + queryKey: ["mapillary", city], + queryFn: () => + fetch(`/api/mapillary?location=${city}`).then((res) => res.json()), + staleTime: 5 * 60 * 1000, // 5 minutes +}); + +// hooks/useScoring.js +const scoreMutation = useMutation({ + mutationFn: (scoreData) => + fetch("/api/scoring", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(scoreData), + }).then((res) => res.json()), +}); +``` + +**Benefits:** + +- Automatic caching of API responses +- Built-in loading/error states +- Retry logic for failed requests +- Server APIs unchanged + +## ๐ŸŽจ Styling Architecture + +### Tailwind + Specialized Libraries + +```javascript +// GameMap component with MapLibre GL JS +import "maplibre-gl/dist/maplibre-gl.css"; + +export default function GameMap() { + return ( +
+
+
+ ); +} +``` + +**Integration Strategy:** + +- Keep required CSS imports for MapLibre GL JS and MapillaryJS +- Wrap components with Tailwind utility classes +- Create custom Tailwind components for consistent styling +- Remove custom CSS in favor of Tailwind utilities + +## ๐Ÿงฉ Component Guidelines + +### shadcn/ui Integration + +- **Replace**: Header, DonateModal, UsernameModal โ†’ Button, Dialog, Input components +- **Keep**: GameMap, PanoViewer, ResultModal (specialized functionality) +- **Mixing**: Game components use shadcn/ui internally for buttons, forms, etc. + +### Game-Specific Components + +```javascript +// PanoViewer.js - Keep (MapillaryJS integration) +// GameMap.js - Keep (MapLibre GL JS integration) +// ResultModal.js - Keep but use shadcn/ui Button, Dialog internally +``` + +## ๐Ÿงช Testing Strategy + +### Jest + React Testing Library + +```javascript +// tests/GameMap.test.js +import { render, screen } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import GameMap from "@/components/GameMap"; + +// Mock MapLibre GL JS +jest.mock("maplibre-gl", () => ({ + Map: jest.fn(() => ({ + on: jest.fn(), + remove: jest.fn(), + })), +})); + +test("renders game map container", () => { + const queryClient = new QueryClient(); + render( + + + + ); + + expect(screen.getByRole("application")).toBeInTheDocument(); +}); +``` + +### Testing Considerations + +- **Mock specialized libraries**: MapLibre GL JS, MapillaryJS +- **Test React Query hooks**: Use `QueryClient` wrapper +- **Focus on user interactions**: Button clicks, form submissions +- **Skip complex map/street view rendering**: Test props and callbacks + +## ๐Ÿ“ Code Style Standards + +- **JavaScript**: Use ES6+ features, prefer arrow functions +- **Imports**: Group React โ†’ Next.js โ†’ libraries โ†’ local components +- **Components**: Always destructure props, use meaningful names +- **Styling**: Prefer Tailwind utilities over custom CSS +- **API**: Keep server logic in `/api` routes, client logic in hooks + +## ๐Ÿ” Migration Guidelines + +### From Custom CSS to Tailwind + +1. Replace custom CSS classes with Tailwind utilities +2. Keep required CSS imports for MapLibre/MapillaryJS +3. Use Tailwind components for consistent styling +4. Remove `globals.css` custom styles gradually + +### Adding shadcn/ui Components + +1. Install: `npx shadcn-ui@latest add button dialog input` +2. Replace existing UI components with shadcn/ui equivalents +3. Maintain game-specific component functionality +4. Use shadcn/ui components within specialized components + +## Architecture **Multi-Library Approach: Each Tool for Its Optimal Purpose** @@ -58,11 +206,12 @@ This project uses a carefully chosen combination of specialized libraries rather ### Core Game Flow **Multi-User Gameplay Experience:** + 1. **User Entry**: Home page checks localStorage for cached username, displays popup if not found 2. **City Selection**: User selects from available Vietnamese cities on home page 3. **Game Initialization**: Navigate to game scene with selected city parameter 4. **Image Loading**: Server provides random panoramic image from city bounds via `src/app/api/mapillary/route.js` -5. **Gameplay**: +5. **Gameplay**: - Display raw panoramic image without location metadata - Show city overview map for location guessing - User submits guess location with username @@ -72,137 +221,42 @@ This project uses a carefully chosen combination of specialized libraries rather - 100m-200m = 3 points - 200m-500m = 2 points - 500m-1km = 1 point - - >1km = 0 points -7. **Results**: + - > 1km = 0 points +7. **Results**: - Show guess location, actual location, distance, and points - Enable MapillaryJS navigation around actual location - "Next" button continues to next round **Technical Implementation:** + - **Image Fetching**: `src/utils/mapillary.js:getRandomMapillaryImage()` fetches random panoramic images via server-side API -- **Display**: `src/components/features/PanoViewer.js` renders raw 360ยฐ images using MapillaryJS npm module +- **Display**: `src/components/features/PanoViewer.js` renders raw 360ยฐ images using MapillaryJS npm module - **Interaction**: `src/components/features/GameMap.js` provides MapLibre GL JS-based interactive map for location guessing - **Scoring**: Enhanced distance calculation with point system in `src/utils/distance.js:calculateDistance()` - **Session Management**: Per-user session tracking for concurrent multi-player support ### Key Components + - **GameMap**: MapLibre GL JS integration with click-based location selection, automatic centering based on city choice, and native coordinate synchronization - **PanoViewer**: MapillaryJS npm module integration with precise coordinate extraction, error handling and fallback display - **ResultModal**: MapLibre GL JS result map showing true location (green), guess location (red), and connecting line with distance calculation - **Game Page**: Main game orchestration with state management for loading, guessing, and results ### Data Structure + - City boundaries defined in `src/constants/locations.js:boundingBoxVN` object with coordinates and delta values for API queries - Location codes: HN (Hanoi), TPHCM (Ho Chi Minh City), HP (Hai Phong), ND (Nam Dinh), DN (Da Nang), DL (Dalat), DHLA (Duc Hoa) ### External Dependencies + - **MapLibre GL JS**: Interactive map rendering (npm module with CSS import) - provides native coordinate synchronization with MapillaryJS - **MapillaryJS**: 360ยฐ street imagery display (npm module with CSS import) - **Mapillary Graph API**: Street view image source with server-side access token security ### State Management + Game state managed at page level with React hooks: + - Image loading states and error handling - Location selection and guess submission - Distance calculation and result display - -### Why This Architecture: Decision Rationale - -#### **1. MapLibre GL JS for Geographic Selection (NOT Leaflet or MapillaryJS Earth Mode)** - -**Decision**: Use MapLibre GL JS for the guessing map interface - -**Why MapLibre GL JS?** -- โœ… **Native MapillaryJS Coordination**: Built-in coordinate synchronization with MapillaryJS for precise coordinate matching -- โœ… **No API Keys Required**: Fully open source, no licensing restrictions -- โœ… **WebGL Performance**: Hardware-accelerated rendering for smooth interactions -- โœ… **Precise Coordinate System**: Uses same coordinate system as MapillaryJS, eliminating mismatches -- โœ… **Familiar UX**: Traditional map interface users expect for location guessing -- โœ… **Complete Geographic Coverage**: Full city-wide map coverage with various tile sources - -**Why NOT Leaflet?** -- โŒ **Coordinate Mismatches**: Different coordinate systems cause precision issues with MapillaryJS -- โŒ **No Native Sync**: Lacks built-in coordinate synchronization with MapillaryJS -- โŒ **Limited Performance**: Canvas-based rendering vs WebGL acceleration - -**Why NOT MapillaryJS Earth Mode?** -- โŒ **Limited Coverage**: Earth mode only works where street imagery exists, not city-wide -- โŒ **Complex UX**: Bird's eye view is designed for navigation around imagery, not geographic selection - -#### **2. MapillaryJS for Street Imagery (NOT Generic Image Viewers)** - -**Decision**: Use MapillaryJS for 360ยฐ street imagery display - -**Why MapillaryJS?** -- โœ… **Native Integration**: Built specifically for Mapillary's street imagery ecosystem -- โœ… **WebGL Performance**: Hardware-accelerated rendering for smooth 360ยฐ interaction -- โœ… **Built-in Controls**: Native pan, zoom, and navigation controls -- โœ… **Future Extensibility**: Supports advanced features like spatial data visualization - -**Why NOT Photo Sphere Viewer or alternatives?** -- โŒ **Generic Solution**: Not optimized for street imagery data format -- โŒ **Performance**: Less efficient rendering without Mapillary-specific optimizations -- โŒ **Limited Features**: Lacks integration with Mapillary's advanced capabilities - -#### **3. Server-Side API (NOT Client-Side MapillaryJS Data Provider)** - -**Decision**: Use server-side API for geographic image discovery - -**Why Server-Side API?** -- โœ… **Security**: API keys remain server-side, preventing exposure -- โœ… **Geographic Discovery**: MapillaryJS is designed for navigation between known IDs, not location-based discovery -- โœ… **Controlled Access**: Server can implement rate limiting and caching -- โœ… **Separation of Concerns**: Clean separation between data fetching and display - -**Why NOT MapillaryJS Data Provider?** -- โŒ **Wrong Use Case**: MapillaryJS data providers are for navigation, not geographic bounds queries -- โŒ **Security Risk**: Would require exposing API keys client-side -- โŒ **Complex Implementation**: Would require understanding internal cell systems - -### Important Implementation Notes -- **MapLibre GL JS + MapillaryJS integration solves coordinate precision**: Native coordinate synchronization eliminates mismatches -- **Precise coordinate flow**: MapillaryJS `getPosition()` provides exact coordinates, MapLibre GL JS uses same coordinate system -- **Coordinate synchronization**: PanoViewer extracts precise coordinates via `onPreciseLocationLoad` callback -- Components use refs for DOM manipulation and cleanup -- Server-side API route (`/api/mapillary`) handles Mapillary Graph API calls for geographic image discovery with secure token storage -- Client-side utility includes retry logic for failed server requests -- Once image ID is obtained, MapillaryJS handles the 360ยฐ street imagery display with precise coordinate extraction -- Image fallback system handles panoramic vs regular image display - -### Recent Changes (Completed) - -**Multi-User Gameplay Implementation (Latest):** -- **Username System**: Added localStorage-cached username with popup modal for first-time users -- **City Selection Interface**: Enhanced home page with username-aware city selection -- **Per-User Scoring API**: Created `/api/scoring` endpoint with session tracking for concurrent multi-player support -- **Distance-Based Point System**: Implemented 0-5 point scale based on accuracy (0-50m=5pts, 50m-100m=4pts, 100m-200m=3pts, 200m-500m=2pts, 500m-1km=1pt, >1km=0pts) -- **Raw Image Display**: Modified PanoViewer to disable navigation controls during gameplay, preventing location information exposure -- **Enhanced Result Modal**: Added points display, side-by-side map/street view layout, and enabled MapillaryJS navigation in result phase -- **Game Flow Integration**: Updated game page to handle username parameters, server-side scoring, and fallback error handling - -### Recent Changes (Previous) -- **Structure**: Migrated to standard Next.js `src/` directory structure -- **Code Organization**: Separated utilities (`src/utils/`), constants (`src/constants/`), UI components (`src/components/ui/`), and feature components (`src/components/features/`) -- **Import Optimization**: Updated all import paths and added index files for cleaner imports -- **Configuration**: Updated `jsconfig.json` path aliases to work with new src structure -- **Cleanup**: Removed 16 unused assets and redundant npm dependencies -- **MapillaryJS Migration & Security Enhancement**: Complete refactor from Photo Sphere Viewer to MapillaryJS npm module with server-side API - - Installed MapillaryJS v4.1.2 as npm dependency and removed photo-sphere-viewer/three.js dependencies - - Created server-side API route `src/app/api/mapillary/route.js` for secure Mapillary Graph API calls - - Moved API key to environment variables (`MAPILLARY_ACCESS_TOKEN`, `NEXT_PUBLIC_MAPILLARY_ACCESS_TOKEN`) - - Refactored `src/components/features/PanoViewer.js` to use imported MapillaryJS module with CSS import - - Updated `src/utils/mapillary.js` to call server API instead of direct external API calls - - Removed CDN imports from `src/app/layout.js` in favor of npm module - - Enhanced error handling and retry logic for server-side requests - - Maintained fallback image display for error handling -- **MapLibre GL JS Migration**: Replaced Leaflet with MapLibre GL JS for native MapillaryJS coordinate synchronization - - Installed MapLibre GL JS v5.6.1 as npm dependency and removed Leaflet dependencies - - Removed Leaflet CDN imports from `src/app/layout.js` - - Completely refactored `src/components/features/GameMap.js` to use MapLibre GL JS with WebGL rendering - - Implemented precise coordinate synchronization using MapillaryJS `getPosition()` method - - Enhanced `src/components/features/PanoViewer.js` with `onPreciseLocationLoad` callback for coordinate extraction - - Updated game page to use precise MapillaryJS coordinates instead of server-provided coordinates - - Migrated `src/components/features/ResultModal.js` from Leaflet to MapLibre GL JS with GeoJSON markers and styling - - Solved coordinate mismatch issues between street imagery and map interface - - Fixed empty result page issue by ensuring all map components use MapLibre GL JS -- Project now follows Next.js 14 best practices, uses native MapillaryJS coordination, and is ready for TypeScript migration \ No newline at end of file