strat-gameplay-webapp/.claude/plans/010-shared-components.md
Cal Corum e0c12467b0 CLAUDE: Improve UX with single-click OAuth, enhanced games list, and layout fix
Frontend UX improvements:
- Single-click Discord OAuth from home page (no intermediate /auth page)
- Auto-redirect authenticated users from home to /games
- Fixed Nuxt layout system - app.vue now wraps NuxtPage with NuxtLayout
- Games page now has proper card container with shadow/border styling
- Layout header includes working logout with API cookie clearing

Games list enhancements:
- Display team names (lname) instead of just team IDs
- Show current score for each team
- Show inning indicator (Top/Bot X) for active games
- Responsive header with wrapped buttons on mobile

Backend improvements:
- Added team caching to SbaApiClient (1-hour TTL)
- Enhanced GameListItem with team names, scores, inning data
- Games endpoint now enriches response with SBA API team data

Docker optimizations:
- Optimized Dockerfile using --chown flag on COPY (faster than chown -R)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-05 16:14:00 -06:00

452 lines
10 KiB
Markdown

# Plan 010: Create Shared Component Library
**Priority**: MEDIUM
**Effort**: 1-2 weeks
**Status**: NOT STARTED
**Risk Level**: LOW - Code organization
---
## Problem Statement
SBA and PD frontends have duplicate component implementations. When a component is updated in one frontend, it must be manually synchronized to the other.
Current state:
- `frontend-sba/components/` - Full implementation (~20 components)
- `frontend-pd/components/` - Minimal (mostly stubs)
## Impact
- **DRY Violation**: Same code in two places
- **Maintenance**: Updates require changes in both places
- **Consistency**: Risk of drift between implementations
- **Testing**: Need to test same component twice
## Proposed Structure
```
strat-gameplay-webapp/
├── packages/
│ └── shared-ui/ # Shared component library
│ ├── package.json
│ ├── nuxt.config.ts # Nuxt module config
│ ├── components/
│ │ ├── Game/
│ │ ├── Decisions/
│ │ ├── Gameplay/
│ │ ├── Substitutions/
│ │ └── UI/
│ ├── composables/
│ ├── types/
│ └── styles/
├── frontend-sba/ # SBA-specific
│ └── nuxt.config.ts # Extends shared-ui
└── frontend-pd/ # PD-specific
└── nuxt.config.ts # Extends shared-ui
```
## Implementation Steps
### Step 1: Create Package Structure (1 hour)
```bash
cd /mnt/NV2/Development/strat-gameplay-webapp
# Create shared package
mkdir -p packages/shared-ui/{components,composables,types,styles}
# Initialize package
cd packages/shared-ui
```
Create `packages/shared-ui/package.json`:
```json
{
"name": "@strat-gameplay/shared-ui",
"version": "0.1.0",
"private": true,
"type": "module",
"exports": {
".": {
"import": "./index.ts"
},
"./components/*": {
"import": "./components/*"
},
"./composables/*": {
"import": "./composables/*"
},
"./types/*": {
"import": "./types/*"
}
},
"dependencies": {
"vue": "^3.4.0"
},
"devDependencies": {
"@nuxt/kit": "^3.14.0",
"typescript": "^5.3.0"
},
"peerDependencies": {
"nuxt": "^3.14.0"
}
}
```
### Step 2: Create Nuxt Module (2 hours)
Create `packages/shared-ui/nuxt.config.ts`:
```typescript
import { defineNuxtModule, addComponentsDir, addImportsDir } from '@nuxt/kit'
import { fileURLToPath } from 'url'
import { dirname, join } from 'path'
export default defineNuxtModule({
meta: {
name: '@strat-gameplay/shared-ui',
configKey: 'sharedUi',
},
defaults: {
prefix: '', // No prefix for components
},
setup(options, nuxt) {
const runtimeDir = dirname(fileURLToPath(import.meta.url))
// Add components
addComponentsDir({
path: join(runtimeDir, 'components'),
prefix: options.prefix,
pathPrefix: false,
})
// Add composables
addImportsDir(join(runtimeDir, 'composables'))
// Add types
nuxt.hook('prepare:types', ({ tsConfig }) => {
tsConfig.include = tsConfig.include || []
tsConfig.include.push(join(runtimeDir, 'types/**/*.d.ts'))
})
},
})
```
Create `packages/shared-ui/index.ts`:
```typescript
// Export module
export { default } from './nuxt.config'
// Export types
export * from './types/game'
export * from './types/player'
export * from './types/websocket'
// Export composables
export { useWebSocket } from './composables/useWebSocket'
export { useGameActions } from './composables/useGameActions'
```
### Step 3: Move Shared Components (4 hours)
Identify components that are league-agnostic:
**Fully Shared (move to packages/shared-ui):**
- `components/UI/` - All UI primitives
- `components/Game/ScoreBoard.vue` - League-agnostic
- `components/Game/GameBoard.vue` - League-agnostic
- `components/Game/CurrentSituation.vue` - League-agnostic
- `components/Gameplay/DiceRoller.vue` - League-agnostic
- `components/Gameplay/PlayResult.vue` - League-agnostic
**League-Specific (keep in frontend-*):**
- `components/Decisions/` - Different for SBA vs PD
- Player cards - Different data structures
- Scouting views - PD only
Move shared components:
```bash
# Move UI components
cp -r frontend-sba/components/UI packages/shared-ui/components/
# Move Game components
cp -r frontend-sba/components/Game packages/shared-ui/components/
# Move Gameplay components
cp -r frontend-sba/components/Gameplay packages/shared-ui/components/
# Move composables
cp frontend-sba/composables/useWebSocket.ts packages/shared-ui/composables/
cp frontend-sba/composables/useGameActions.ts packages/shared-ui/composables/
# Move types
cp frontend-sba/types/game.ts packages/shared-ui/types/
cp frontend-sba/types/player.ts packages/shared-ui/types/
cp frontend-sba/types/websocket.ts packages/shared-ui/types/
```
### Step 4: Create Theme System (2 hours)
Create `packages/shared-ui/styles/themes.ts`:
```typescript
export interface LeagueTheme {
primary: string
secondary: string
accent: string
background: string
surface: string
text: string
textMuted: string
}
export const sbaTheme: LeagueTheme = {
primary: '#1e40af', // Blue
secondary: '#3b82f6',
accent: '#f59e0b', // Amber
background: '#0f172a',
surface: '#1e293b',
text: '#f8fafc',
textMuted: '#94a3b8',
}
export const pdTheme: LeagueTheme = {
primary: '#7c3aed', // Purple
secondary: '#a78bfa',
accent: '#10b981', // Emerald
background: '#0f172a',
surface: '#1e293b',
text: '#f8fafc',
textMuted: '#94a3b8',
}
```
Create `packages/shared-ui/composables/useTheme.ts`:
```typescript
import { inject, provide, ref, readonly } from 'vue'
import type { LeagueTheme } from '../styles/themes'
import { sbaTheme } from '../styles/themes'
const THEME_KEY = Symbol('theme')
export function provideTheme(theme: LeagueTheme) {
const themeRef = ref(theme)
provide(THEME_KEY, readonly(themeRef))
return themeRef
}
export function useTheme(): LeagueTheme {
const theme = inject<LeagueTheme>(THEME_KEY)
if (!theme) {
console.warn('No theme provided, using SBA default')
return sbaTheme
}
return theme
}
```
### Step 5: Update Frontends to Use Shared Package (2 hours)
Update `frontend-sba/package.json`:
```json
{
"dependencies": {
"@strat-gameplay/shared-ui": "workspace:*"
}
}
```
Update `frontend-sba/nuxt.config.ts`:
```typescript
export default defineNuxtConfig({
modules: [
'@strat-gameplay/shared-ui',
],
sharedUi: {
prefix: '',
},
// Override theme
runtimeConfig: {
public: {
league: 'sba',
},
},
})
```
Update `frontend-sba/app.vue`:
```vue
<script setup lang="ts">
import { provideTheme, sbaTheme } from '@strat-gameplay/shared-ui'
provideTheme(sbaTheme)
</script>
```
### Step 6: Setup Workspace (1 hour)
Create/update root `package.json`:
```json
{
"name": "strat-gameplay-webapp",
"private": true,
"workspaces": [
"packages/*",
"frontend-sba",
"frontend-pd"
],
"scripts": {
"dev:sba": "cd frontend-sba && npm run dev",
"dev:pd": "cd frontend-pd && npm run dev",
"build": "npm run build --workspaces",
"test": "npm run test --workspaces"
}
}
```
Install dependencies:
```bash
cd /mnt/NV2/Development/strat-gameplay-webapp
npm install # or bun install
```
### Step 7: Update Imports in Frontends (2 hours)
Update components to import from shared package:
**Before:**
```typescript
// frontend-sba/pages/game/[id].vue
import ScoreBoard from '~/components/Game/ScoreBoard.vue'
import { useWebSocket } from '~/composables/useWebSocket'
```
**After:**
```typescript
// frontend-sba/pages/game/[id].vue
// ScoreBoard auto-imported from shared-ui module
// useWebSocket auto-imported from shared-ui module
```
### Step 8: Add Shared Tests (2 hours)
Create `packages/shared-ui/tests/`:
```typescript
// packages/shared-ui/tests/components/ScoreBoard.spec.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import ScoreBoard from '../components/Game/ScoreBoard.vue'
describe('ScoreBoard', () => {
it('renders team scores', () => {
const wrapper = mount(ScoreBoard, {
props: {
homeScore: 5,
awayScore: 3,
homeTeamName: 'Home Team',
awayTeamName: 'Away Team',
},
})
expect(wrapper.text()).toContain('5')
expect(wrapper.text()).toContain('3')
})
it('applies theme colors', () => {
// Test theme integration
})
})
```
### Step 9: Documentation (1 hour)
Create `packages/shared-ui/README.md`:
```markdown
# @strat-gameplay/shared-ui
Shared Vue 3 components for Strat Gameplay frontends.
## Usage
Add to your Nuxt config:
```typescript
export default defineNuxtConfig({
modules: ['@strat-gameplay/shared-ui'],
})
```
## Components
### Game Components
- `ScoreBoard` - Displays game score, inning, count
- `GameBoard` - Diamond visualization
- `CurrentSituation` - Batter/pitcher matchup
### UI Components
- `ActionButton` - Styled action button
- `ToggleSwitch` - Boolean toggle
- `ButtonGroup` - Button group container
## Composables
- `useWebSocket` - WebSocket connection management
- `useGameActions` - Type-safe game action emitters
- `useTheme` - Theme injection/consumption
## Theming
Provide theme at app root:
```vue
<script setup>
import { provideTheme, sbaTheme } from '@strat-gameplay/shared-ui'
provideTheme(sbaTheme)
</script>
```
```
## Verification Checklist
- [ ] Shared package builds without errors
- [ ] SBA frontend works with shared components
- [ ] PD frontend works with shared components
- [ ] Themes apply correctly per league
- [ ] Tests pass for shared components
- [ ] No duplicate component code remains
## Migration Strategy
1. **Phase 1**: Create package, move UI primitives
2. **Phase 2**: Move game display components
3. **Phase 3**: Move composables and types
4. **Phase 4**: Refactor league-specific components
## Rollback Plan
If issues arise:
1. Remove shared-ui from module list
2. Restore original component imports
3. Keep shared package for future use
## Dependencies
- None (can be implemented independently)
## Notes
- Consider Storybook for component documentation
- May want to publish to private npm registry eventually
- Future: Add design tokens for full design system