Skip to main content

Tech Stack

The frontend is built with modern web technologies:

React 19.2.0

Latest React with concurrent features and improved hooks

Vite 7.3.1

Ultra-fast build tool with HMR (Hot Module Replacement)

Axios 1.13.5

HTTP client for API communication

Framer Motion 12.34.3

Animation library for smooth UI transitions

Project Structure

source/Front/Crafter_League_of_Legends/
├── src/
│   ├── App.jsx                 # Main application component
│   ├── main.jsx               # Application entry point
│   ├── components/            # Reusable UI components
│   ├── services/              # API and business logic
│   ├── hooks/                 # Custom React hooks
│   └── constants/             # Configuration and theme
├── package.json               # Dependencies and scripts
├── vite.config.js            # Vite configuration
└── index.html                # HTML entry point

Core Components

App.jsx - Main Application

The root component orchestrates game state and logic:
App.jsx
import { useCallback, useEffect, useState, useMemo } from 'react'
import { storageService } from '../services/storageService';
import { GAME_CONFIG } from '../constants/theme';
import { gameService } from '../services/gameService';

function App() {
  // Core game state
  const [gameData, setGameData] = useState(null);
  const [selectedItems, setSelectedItems] = useState([]);
  const [score, setScore] = useState(storageService.getScore());
  const [timeLeft, setTimeLeft] = useState(GAME_CONFIG.timePerQuestion);
  const [feedback, setFeedback] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  // Dynamic slot calculation based on item components
  const requiredSlots = useMemo(() => {
    return gameData?.correctComponents?.length || 2;
  }, [gameData?.correctComponents?.length]);

  // Load new question from API
  const loadNewQuestion = useCallback(async () => {
    try {
      setIsLoading(true);
      const data = await gameService.getRandomItem();
      setGameData(data);
    } catch (err) {
      console.error('Error loading question:', err);
    } finally {
      setIsLoading(false);
    }
  }, []);

  // ... game logic
}
The component uses React hooks extensively for state management and side effects.

Key Features

Uses React’s built-in useState and useCallback hooks:
  • gameData: Current question and options from backend
  • selectedItems: Items chosen by player
  • score: Current score (persisted to localStorage)
  • timeLeft: Countdown timer
  • feedback: Answer validation result
Automatic timer pause when slots are full:
useEffect(() => {
  if (!gameData || feedback || timeLeft <= 0) return;
  if (selectedItems.length >= requiredSlots) return; // PAUSE

  const timer = setInterval(() => {
    setTimeLeft((prev) => prev > 0 ? prev - 1 : 0);
  }, 1000);

  return () => clearInterval(timer);
}, [gameData, feedback, timeLeft, selectedItems.length, requiredSlots]);
Allows duplicates and manages slot capacity:
const handleItemClick = useCallback((item) => {
  if (feedback) return;

  setSelectedItems((prev) => {
    const isInSlots = prev.some((s) => s.id === item.id);

    if (prev.length < requiredSlots) {
      // Add item if slots available
      return [...prev, item];
    } else if (isInSlots) {
      // Remove last occurrence if slots full
      const lastIdx = prev.map((s) => s.id).lastIndexOf(item.id);
      return prev.filter((_, i) => i !== lastIdx);
    }
    return prev;
  });
}, [feedback, requiredSlots]);
Submits answer and handles scoring:
const handleSubmit = useCallback(async () => {
  if (selectedItems.length !== requiredSlots) return;

  const selectedIds = selectedItems.map((item) => item.id);
  const result = await gameService.validateAnswer(
    gameData.targetItem.id, 
    selectedIds
  );

  if (result.isCorrect) {
    const newScore = score + GAME_CONFIG.pointsPerCorrect;
    setScore(newScore);
    storageService.setScore(newScore);
  } else {
    const newScore = Math.max(0, score - GAME_CONFIG.pointsPerIncorrect);
    setScore(newScore);
  }
}, [selectedItems, gameData, score]);

Services Layer

Game Service

Handles all game-related API calls using Axios:
import axios from 'axios';
import { API_CONFIG } from '../constants/theme';

class GameService {
  constructor() {
    this.api = axios.create({
      baseURL: API_CONFIG.baseURL,
      timeout: 10000,
    });
  }

  async getRandomItem() {
    const response = await this.api.get(API_CONFIG.endpoints.question);
    return response.data;
  }

  async validateAnswer(targetId, selectedIds) {
    const response = await this.api.post(API_CONFIG.endpoints.validate, {
      targetItemId: targetId,
      selectedComponentIds: selectedIds,
    });
    return response.data;
  }
}

export const gameService = new GameService();

Storage Service

Manages localStorage for persistent data:
class StorageService {
  getScore() {
    return parseInt(localStorage.getItem('score') || '0', 10);
  }

  setScore(score) {
    localStorage.setItem('score', score.toString());
  }

  getBestScore() {
    return parseInt(localStorage.getItem('bestScore') || '0', 10);
  }

  setBestScore(score) {
    localStorage.setItem('bestScore', score.toString());
  }

  incrementStreak() {
    const streak = this.getStreak() + 1;
    localStorage.setItem('streak', streak.toString());
  }

  resetStreak() {
    localStorage.setItem('streak', '0');
  }
}

export const storageService = new StorageService();

Configuration System

Theme Constants

Located at constants/theme.js:
// Color palette
export const COLORS = {
  background: '#0A1428',
  accentGold: '#C89B3C',
  magicBlue: '#0BC6E3',
  progressGreen: '#00BFA5',
  panelDark: '#1A2332',
  textMain: '#F0E6D2',
  itemBorder: '#463714',
  successGreen: '#00FF88',
  errorRed: '#FF4444',
};

// Game mechanics
export const GAME_CONFIG = {
  timePerQuestion: 15,       // seconds
  pointsPerCorrect: 100,
  pointsPerIncorrect: 50,
  itemsInCircle: 14,
  circleRadius: 270,
  centralItemSize: 120,
  peripheralItemSize: 64,
  maxSlots: 2,
};

// API endpoints
export const API_CONFIG = {
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:5000/api',
  endpoints: {
    question: '/game/question',
    validate: '/game/validate',
  },
};

Vite Configuration

Minimal configuration in vite.config.js:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'

export default defineConfig({
  plugins: [react()],
  build: {
    outDir: 'dist',
  }
})
react-swc plugin provides faster builds using SWC (Speedy Web Compiler) instead of Babel.

Build Scripts

Defined in package.json:
{
  "scripts": {
    "dev": "vite",              // Start dev server
    "build": "vite build",       // Production build
    "lint": "eslint .",          // Lint code
    "preview": "vite preview"    // Preview production build
  }
}

Development

npm run dev
Starts Vite dev server with:
  • Hot Module Replacement (HMR)
  • Fast refresh for React components
  • Runs on http://localhost:5173

Production Build

npm run build
Creates optimized production bundle:
  • Minified JavaScript and CSS
  • Code splitting
  • Tree shaking
  • Output to dist/ directory

Styling Approach

The application uses inline styles with JavaScript objects for simplicity and co-location.
Example styling pattern:
const styles = {
  app: {
    height: '100vh',
    width: '100vw',
    display: 'flex',
    flexDirection: 'column',
    background: 'linear-gradient(135deg, #0A1428 0%, #1A2332 100%)',
  },
  loadingContainer: {
    display: 'flex',
    justifyContent: 'center',
    alignItems: 'center',
    background: COLORS.background,
  },
};

return <div style={styles.app}>...</div>;

Performance Considerations

Memoization prevents unnecessary re-renders:
const handleItemClick = useCallback((item) => {
  // Logic here
}, [feedback, requiredSlots]);
Avoid recalculating on every render:
const requiredSlots = useMemo(() => {
  return gameData?.correctComponents?.length || 2;
}, [gameData?.correctComponents?.length]);
Vite automatically splits code by route/component for optimal loading.
Images from Data Dragon CDN are cached by the browser and backend.

Next Steps

Backend Architecture

Understand how the Spring Boot backend works

Data Dragon Integration

Learn about API integration and caching

Build docs developers (and LLMs) love