Update CLAUDE.md

This commit is contained in:
tiennm99 committed 2025-08-03 14:31:56 +07:00
1 parent 7b6e71dfbb
commit 730e9a157a
1 file changed
+192 -138
+192 -138
View File
@@ -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: `<QueryClientProvider>` 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 (
<div className="w-full h-96 rounded-lg border border-gray-200 overflow-hidden">
<div ref={mapRef} className="w-full h-full" />
</div>
);
}
```
**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(
<QueryClientProvider client={queryClient}>
<GameMap choiceLocation="HN" onLocationSelect={jest.fn()} />
</QueryClientProvider>
);
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