# Frontend PD - Paper Dynasty Web App ## Overview Vue 3 + Nuxt 3 frontend for the PD (Paper Dynasty) league. Provides real-time game interface with WebSocket communication to the game backend. ## Technology Stack - **Framework**: Nuxt 3 (Vue 3 Composition API) - **Language**: TypeScript (strict mode) - **Styling**: Tailwind CSS - **State Management**: Pinia - **WebSocket**: Socket.io-client - **HTTP Client**: Axios (or Nuxt's built-in fetch) - **Auth**: Discord OAuth with JWT ## League-Specific Characteristics ### PD League - **Player Data**: Detailed model with scouting data, ratings, probabilities - **Focus**: Advanced analytics and detailed player evaluation - **Branding**: Green primary color (#16a34a) - **API**: PD-specific REST API for team/player data with analytics ## Project Structure ``` frontend-pd/ ├── assets/ │ ├── css/ │ │ └── tailwind.css # Tailwind imports │ └── images/ # PD branding assets │ ├── components/ │ ├── Branding/ # PD-specific branding │ │ ├── Header.vue │ │ ├── Footer.vue │ │ └── Logo.vue │ └── League/ # PD-specific features │ ├── PlayerCardDetailed.vue # Player cards with scouting │ └── ScoutingPanel.vue # Scouting data display │ ├── composables/ │ ├── useAuth.ts # Authentication state │ ├── useWebSocket.ts # WebSocket connection │ ├── useGameState.ts # Game state management │ └── useLeagueConfig.ts # PD-specific config │ ├── layouts/ │ ├── default.vue # Standard layout │ ├── game.vue # Game view layout │ └── auth.vue # Auth pages layout │ ├── pages/ │ ├── index.vue # Home/dashboard │ ├── games/ │ │ ├── [id].vue # Game view │ │ ├── create.vue # Create new game │ │ └── history.vue # Completed games │ ├── auth/ │ │ ├── login.vue │ │ └── callback.vue # Discord OAuth callback │ └── spectate/ │ └── [id].vue # Spectator view │ ├── plugins/ │ ├── socket.client.ts # Socket.io plugin │ └── auth.ts # Auth plugin │ ├── store/ # Pinia stores │ ├── auth.ts # Authentication state │ ├── game.ts # Current game state │ ├── games.ts # Games list │ └── ui.ts # UI state (modals, toasts) │ ├── types/ │ ├── game.ts # Game-related types │ ├── player.ts # PD player types (with scouting) │ ├── api.ts # API response types │ └── websocket.ts # WebSocket event types │ ├── utils/ │ ├── api.ts # API client │ ├── formatters.ts # Data formatting utilities │ └── validators.ts # Input validation │ ├── middleware/ │ ├── auth.ts # Auth guard │ └── game-access.ts # Game access validation │ ├── public/ # Static assets ├── app.vue # Root component ├── nuxt.config.ts # Nuxt configuration ├── tailwind.config.js # Tailwind configuration ├── tsconfig.json # TypeScript configuration └── package.json ``` ## Shared Components Many components are shared between SBA and PD frontends. These will be located in a shared component library: **Shared**: - Game board visualization - Play-by-play feed - Dice roll animations - Decision input forms - WebSocket connection status **PD-Specific**: - PD branding (header, footer, colors) - Detailed player card display (with scouting data) - Scouting data panels - Advanced analytics views ## Development Workflow ### Daily Development ```bash # Install dependencies (first time) npm install # Run dev server with hot-reload npm run dev # Frontend available at http://localhost:3001 ``` ### Building ```bash # Build for production npm run build # Preview production build npm run preview # Generate static site (if needed) npm run generate ``` ### Code Quality ```bash # Type checking npm run type-check # Linting npm run lint # Fix linting issues npm run lint:fix ``` ## Coding Standards ### Vue/TypeScript Style - **Composition API**: Use ` ``` ### Composable Pattern ```typescript // composables/useGameActions.ts export const useGameActions = (gameId: string) => { const { socket } = useWebSocket() const gameStore = useGameStore() const setDefense = (positioning: string) => { if (!socket.value?.connected) { throw new Error('Not connected') } socket.value.emit('set_defense', { game_id: gameId, positioning }) } return { setDefense, // ... other actions } } ``` ### Store Pattern (Pinia) ```typescript // store/game.ts export const useGameStore = defineStore('game', () => { // State const gameState = ref(null) const loading = ref(false) // Computed const currentInning = computed(() => gameState.value?.inning ?? 1) // Actions const setGameState = (state: GameState) => { gameState.value = state } return { // State gameState, loading, // Computed currentInning, // Actions setGameState, } }) ``` ## Configuration ### Environment Variables Copy `.env.example` to `.env` and configure: ```bash # API endpoints NUXT_PUBLIC_API_URL=http://localhost:8000 NUXT_PUBLIC_WS_URL=http://localhost:8000 # Discord OAuth (public client ID - safe to expose) NUXT_PUBLIC_DISCORD_CLIENT_ID=your-client-id NUXT_PUBLIC_DISCORD_REDIRECT_URI=http://localhost:3001/auth/callback # Vite dev server allowed hosts (comma-separated) # Add your domain for external access NUXT_ALLOWED_HOSTS=localhost,127.0.0.1 ``` **Note**: `NUXT_ALLOWED_HOSTS` is read in `nuxt.config.ts` to configure Vite's `allowedHosts`. Required when accessing dev server through a reverse proxy or external domain. ### Nuxt Config ```typescript // nuxt.config.ts export default defineNuxtConfig({ modules: ['@nuxtjs/tailwindcss', '@pinia/nuxt'], runtimeConfig: { public: { leagueId: 'pd', leagueName: 'Paper Dynasty', apiUrl: process.env.NUXT_PUBLIC_API_URL || 'http://localhost:8000', // ... other config } }, css: ['~/assets/css/tailwind.css'], typescript: { strict: true, typeCheck: true } }) ``` ### Tailwind Config (PD Theme) ```javascript // tailwind.config.js module.exports = { theme: { extend: { colors: { primary: { DEFAULT: '#16a34a', // PD Green 50: '#f0fdf4', 100: '#dcfce7', // ... other shades }, secondary: { DEFAULT: '#ea580c', // PD Orange // ... other shades } } } } } ``` ## WebSocket Integration ### Connection Management ```typescript // composables/useWebSocket.ts const { $socket } = useNuxtApp() const authStore = useAuthStore() onMounted(() => { if (authStore.token) { $socket.connect(authStore.token) } }) onUnmounted(() => { $socket.disconnect() }) ``` ### Event Handling ```typescript // composables/useGameEvents.ts export const useGameEvents = () => { const { socket } = useWebSocket() const gameStore = useGameStore() onMounted(() => { socket.value?.on('game_state_update', (data: GameState) => { gameStore.setGameState(data) }) socket.value?.on('play_completed', (data: PlayOutcome) => { gameStore.handlePlayCompleted(data) }) }) onUnmounted(() => { socket.value?.off('game_state_update') socket.value?.off('play_completed') }) } ``` ## Type Definitions ### PD Player Type (with Scouting Data) ```typescript // types/player.ts export interface PdPlayer { id: number name: string image: string scouting_data: { power: number contact: number speed: number fielding: number // ... other scouting metrics } ratings: { overall: number batting: number pitching?: number // ... other ratings } probabilities: { single: number double: number triple: number homerun: number walk: number strikeout: number // ... other probabilities } } export interface Lineup { id: number game_id: string card_id: number position: string batting_order?: number is_starter: boolean is_active: boolean player: PdPlayer } ``` ### Game State Type ```typescript // types/game.ts export interface GameState { game_id: string status: 'pending' | 'active' | 'completed' inning: number half: 'top' | 'bottom' outs: number balls: number strikes: number home_score: number away_score: number runners: { first: number | null second: number | null third: number | null } current_batter: PdPlayer | null current_pitcher: PdPlayer | null } ``` ## PD-Specific Features ### Scouting Data Display ```vue Scouting Report {{ formatLabel(key) }} {{ value }} ``` ### Advanced Player Card ```vue {{ player.name }} Overall: {{ player.ratings.overall }} Batting: {{ player.ratings.batting }} Probabilities {{ outcome }}: {{ (prob * 100).toFixed(1) }}% ``` ## Mobile-First Design ### Responsive Breakpoints - **xs**: 375px (Small phones) - **sm**: 640px (Large phones) - **md**: 768px (Tablets) - **lg**: 1024px (Desktop) ### Mobile Layout Principles - Single column layout on mobile - Bottom sheet for decision inputs - Sticky scoreboard at top - Touch-friendly buttons (44x44px minimum) - Swipe gestures for navigation - Collapsible scouting data on mobile ### Example Responsive Component ```vue ``` ## Common Tasks ### Adding a New Page 1. Create file in `pages/` directory 2. Use `