feat: update nav components for UUID players
- Update all player ID types from number to string - Remove switch statements for player lookups - Use Map/Set operations instead of array methods - Support arbitrary number of players - PlayerConfigDialog now accepts string IDs
This commit is contained in:
parent
2b94cad11b
commit
e85d0415f2
|
|
@ -3,7 +3,6 @@
|
|||
import React from 'react'
|
||||
import { AppNavBar } from './AppNavBar'
|
||||
import { useGameMode } from '../contexts/GameModeContext'
|
||||
import { useUserProfile } from '../contexts/UserProfileContext'
|
||||
import { GameContextNav } from './nav/GameContextNav'
|
||||
import { PlayerConfigDialog } from './nav/PlayerConfigDialog'
|
||||
|
||||
|
|
@ -15,10 +14,9 @@ interface PageWithNavProps {
|
|||
}
|
||||
|
||||
export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false, children }: PageWithNavProps) {
|
||||
const { players, activePlayerCount, updatePlayer } = useGameMode()
|
||||
const { profile } = useUserProfile()
|
||||
const { players, activePlayers, setActive, activePlayerCount } = useGameMode()
|
||||
const [mounted, setMounted] = React.useState(false)
|
||||
const [configurePlayerId, setConfigurePlayerId] = React.useState<1 | 2 | 3 | 4 | null>(null)
|
||||
const [configurePlayerId, setConfigurePlayerId] = React.useState<string | null>(null)
|
||||
|
||||
// Delay mounting animation slightly for smooth transition
|
||||
React.useEffect(() => {
|
||||
|
|
@ -26,57 +24,27 @@ export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false,
|
|||
return () => clearTimeout(timer)
|
||||
}, [])
|
||||
|
||||
const handleRemovePlayer = (playerId: number) => {
|
||||
updatePlayer(playerId, { isActive: false })
|
||||
const handleRemovePlayer = (playerId: string) => {
|
||||
setActive(playerId, false)
|
||||
}
|
||||
|
||||
const handleAddPlayer = (playerId: number) => {
|
||||
updatePlayer(playerId, { isActive: true })
|
||||
const handleAddPlayer = (playerId: string) => {
|
||||
setActive(playerId, true)
|
||||
}
|
||||
|
||||
const handleConfigurePlayer = (playerId: number) => {
|
||||
// Support configuring all players (1-4)
|
||||
if (playerId >= 1 && playerId <= 4) {
|
||||
setConfigurePlayerId(playerId as 1 | 2 | 3 | 4)
|
||||
}
|
||||
const handleConfigurePlayer = (playerId: string) => {
|
||||
setConfigurePlayerId(playerId)
|
||||
}
|
||||
|
||||
// Transform players to use profile emojis and names for all players
|
||||
const getPlayerEmoji = (playerId: number) => {
|
||||
switch (playerId) {
|
||||
case 1: return profile.player1Emoji
|
||||
case 2: return profile.player2Emoji
|
||||
case 3: return profile.player3Emoji
|
||||
case 4: return profile.player4Emoji
|
||||
default: return players.find(p => p.id === playerId)?.emoji || '😀'
|
||||
}
|
||||
}
|
||||
// Get active and inactive players as arrays
|
||||
const activePlayerList = Array.from(activePlayers)
|
||||
.map(id => players.get(id))
|
||||
.filter(p => p !== undefined)
|
||||
.map(p => ({ id: p.id, name: p.name, emoji: p.emoji }))
|
||||
|
||||
const getPlayerName = (playerId: number) => {
|
||||
switch (playerId) {
|
||||
case 1: return profile.player1Name
|
||||
case 2: return profile.player2Name
|
||||
case 3: return profile.player3Name
|
||||
case 4: return profile.player4Name
|
||||
default: return players.find(p => p.id === playerId)?.name || `Player ${playerId}`
|
||||
}
|
||||
}
|
||||
|
||||
const activePlayers = players
|
||||
.filter(p => p.isActive)
|
||||
.map(player => ({
|
||||
...player,
|
||||
emoji: getPlayerEmoji(player.id),
|
||||
name: getPlayerName(player.id)
|
||||
}))
|
||||
|
||||
const inactivePlayers = players
|
||||
.filter(p => !p.isActive)
|
||||
.map(player => ({
|
||||
...player,
|
||||
emoji: getPlayerEmoji(player.id),
|
||||
name: getPlayerName(player.id)
|
||||
}))
|
||||
const inactivePlayerList = Array.from(players.values())
|
||||
.filter(p => !activePlayers.has(p.id))
|
||||
.map(p => ({ id: p.id, name: p.name, emoji: p.emoji }))
|
||||
|
||||
// Compute game mode from active player count
|
||||
const gameMode = activePlayerCount === 0 ? 'none' :
|
||||
|
|
@ -93,8 +61,8 @@ export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false,
|
|||
navTitle={navTitle}
|
||||
navEmoji={navEmoji}
|
||||
gameMode={gameMode}
|
||||
activePlayers={activePlayers}
|
||||
inactivePlayers={inactivePlayers}
|
||||
activePlayers={activePlayerList}
|
||||
inactivePlayers={inactivePlayerList}
|
||||
shouldEmphasize={shouldEmphasize}
|
||||
showFullscreenSelection={showFullscreenSelection}
|
||||
onAddPlayer={handleAddPlayer}
|
||||
|
|
@ -115,4 +83,4 @@ export function PageWithNav({ navTitle, navEmoji, emphasizeGameContext = false,
|
|||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Player {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
}
|
||||
|
|
@ -9,12 +9,12 @@ interface Player {
|
|||
interface ActivePlayersListProps {
|
||||
activePlayers: Player[]
|
||||
shouldEmphasize: boolean
|
||||
onRemovePlayer: (playerId: number) => void
|
||||
onConfigurePlayer: (playerId: number) => void
|
||||
onRemovePlayer: (playerId: string) => void
|
||||
onConfigurePlayer: (playerId: string) => void
|
||||
}
|
||||
|
||||
export function ActivePlayersList({ activePlayers, shouldEmphasize, onRemovePlayer, onConfigurePlayer }: ActivePlayersListProps) {
|
||||
const [hoveredPlayerId, setHoveredPlayerId] = React.useState<number | null>(null)
|
||||
const [hoveredPlayerId, setHoveredPlayerId] = React.useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Player {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ interface Player {
|
|||
interface AddPlayerButtonProps {
|
||||
inactivePlayers: Player[]
|
||||
shouldEmphasize: boolean
|
||||
onAddPlayer: (playerId: number) => void
|
||||
onAddPlayer: (playerId: string) => void
|
||||
}
|
||||
|
||||
export function AddPlayerButton({ inactivePlayers, shouldEmphasize, onAddPlayer }: AddPlayerButtonProps) {
|
||||
|
|
@ -30,7 +30,7 @@ export function AddPlayerButton({ inactivePlayers, shouldEmphasize, onAddPlayer
|
|||
}
|
||||
}, [showPopover])
|
||||
|
||||
const handleAddPlayerClick = (playerId: number) => {
|
||||
const handleAddPlayerClick = (playerId: string) => {
|
||||
onAddPlayer(playerId)
|
||||
setShowPopover(false)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import React from 'react'
|
||||
|
||||
interface Player {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
}
|
||||
|
||||
interface FullscreenPlayerSelectionProps {
|
||||
inactivePlayers: Player[]
|
||||
onSelectPlayer: (playerId: number) => void
|
||||
onConfigurePlayer: (playerId: number) => void
|
||||
onSelectPlayer: (playerId: string) => void
|
||||
onConfigurePlayer: (playerId: string) => void
|
||||
isVisible: boolean
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { FullscreenPlayerSelection } from './FullscreenPlayerSelection'
|
|||
type GameMode = 'none' | 'single' | 'battle' | 'tournament'
|
||||
|
||||
interface Player {
|
||||
id: number
|
||||
id: string
|
||||
name: string
|
||||
emoji: string
|
||||
}
|
||||
|
|
@ -20,9 +20,9 @@ interface GameContextNavProps {
|
|||
inactivePlayers: Player[]
|
||||
shouldEmphasize: boolean
|
||||
showFullscreenSelection: boolean
|
||||
onAddPlayer: (playerId: number) => void
|
||||
onRemovePlayer: (playerId: number) => void
|
||||
onConfigurePlayer: (playerId: number) => void
|
||||
onAddPlayer: (playerId: string) => void
|
||||
onRemovePlayer: (playerId: string) => void
|
||||
onConfigurePlayer: (playerId: string) => void
|
||||
}
|
||||
|
||||
export function GameContextNav({
|
||||
|
|
|
|||
|
|
@ -1,54 +1,49 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useUserProfile } from '../../contexts/UserProfileContext'
|
||||
import { useGameMode } from '../../contexts/GameModeContext'
|
||||
import { EmojiPicker } from '../../app/games/matching/components/EmojiPicker'
|
||||
|
||||
interface PlayerConfigDialogProps {
|
||||
playerId: 1 | 2 | 3 | 4
|
||||
playerId: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProps) {
|
||||
const { profile, updatePlayerEmoji, updatePlayerName } = useUserProfile()
|
||||
const { getPlayer, updatePlayer } = useGameMode()
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
|
||||
const getCurrentName = () => {
|
||||
switch (playerId) {
|
||||
case 1: return profile.player1Name
|
||||
case 2: return profile.player2Name
|
||||
case 3: return profile.player3Name
|
||||
case 4: return profile.player4Name
|
||||
}
|
||||
const player = getPlayer(playerId)
|
||||
|
||||
if (!player) {
|
||||
return null
|
||||
}
|
||||
|
||||
const getCurrentEmoji = () => {
|
||||
switch (playerId) {
|
||||
case 1: return profile.player1Emoji
|
||||
case 2: return profile.player2Emoji
|
||||
case 3: return profile.player3Emoji
|
||||
case 4: return profile.player4Emoji
|
||||
}
|
||||
}
|
||||
|
||||
const [tempName, setTempName] = useState(getCurrentName())
|
||||
const currentEmoji = getCurrentEmoji()
|
||||
const [tempName, setTempName] = useState(player.name)
|
||||
|
||||
const handleSave = () => {
|
||||
updatePlayerName(playerId, tempName)
|
||||
updatePlayer(playerId, { name: tempName })
|
||||
onClose()
|
||||
}
|
||||
|
||||
const handleEmojiSelect = (emoji: string) => {
|
||||
updatePlayerEmoji(playerId, emoji)
|
||||
updatePlayer(playerId, { emoji })
|
||||
setShowEmojiPicker(false)
|
||||
}
|
||||
|
||||
// Get player number for UI theming (first 4 players get special colors)
|
||||
const allPlayers = Array.from(useGameMode().players.values()).sort((a, b) => a.createdAt - b.createdAt)
|
||||
const playerIndex = allPlayers.findIndex(p => p.id === playerId)
|
||||
const displayNumber = playerIndex + 1
|
||||
|
||||
// Color based on player's actual color
|
||||
const gradientColor = player.color
|
||||
|
||||
if (showEmojiPicker) {
|
||||
return (
|
||||
<EmojiPicker
|
||||
currentEmoji={currentEmoji}
|
||||
currentEmoji={player.emoji}
|
||||
onEmojiSelect={handleEmojiSelect}
|
||||
onClose={() => setShowEmojiPicker(false)}
|
||||
playerNumber={playerId}
|
||||
playerNumber={displayNumber}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -87,18 +82,12 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
<h2 style={{
|
||||
fontSize: '24px',
|
||||
fontWeight: 'bold',
|
||||
background: playerId === 1
|
||||
? 'linear-gradient(135deg, #60a5fa, #3b82f6)'
|
||||
: playerId === 2
|
||||
? 'linear-gradient(135deg, #f472b6, #ec4899)'
|
||||
: playerId === 3
|
||||
? 'linear-gradient(135deg, #a78bfa, #8b5cf6)'
|
||||
: 'linear-gradient(135deg, #fbbf24, #f59e0b)',
|
||||
background: `linear-gradient(135deg, ${gradientColor}, ${gradientColor}dd)`,
|
||||
backgroundClip: 'text',
|
||||
color: 'transparent',
|
||||
margin: 0
|
||||
}}>
|
||||
Configure Player {playerId}
|
||||
Configure Player
|
||||
</h2>
|
||||
<button
|
||||
onClick={onClose}
|
||||
|
|
@ -144,8 +133,7 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
gap: '12px'
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
const borderColor = playerId === 1 ? '#60a5fa' : playerId === 2 ? '#f472b6' : playerId === 3 ? '#a78bfa' : '#fbbf24'
|
||||
e.currentTarget.style.borderColor = borderColor
|
||||
e.currentTarget.style.borderColor = gradientColor
|
||||
e.currentTarget.style.transform = 'translateY(-2px)'
|
||||
e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)'
|
||||
}}
|
||||
|
|
@ -159,7 +147,7 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
fontSize: '48px',
|
||||
lineHeight: 1
|
||||
}}>
|
||||
{currentEmoji}
|
||||
{player.emoji}
|
||||
</div>
|
||||
<div style={{
|
||||
flex: 1,
|
||||
|
|
@ -204,7 +192,7 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
type="text"
|
||||
value={tempName}
|
||||
onChange={(e) => setTempName(e.target.value)}
|
||||
placeholder={`Player ${playerId}`}
|
||||
placeholder="Player Name"
|
||||
maxLength={20}
|
||||
style={{
|
||||
width: '100%',
|
||||
|
|
@ -217,16 +205,8 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
fontWeight: '500'
|
||||
}}
|
||||
onFocus={(e) => {
|
||||
const focusColor = playerId === 1 ? '#60a5fa' : playerId === 2 ? '#f472b6' : playerId === 3 ? '#a78bfa' : '#fbbf24'
|
||||
const shadowColor = playerId === 1
|
||||
? 'rgba(96, 165, 250, 0.1)'
|
||||
: playerId === 2
|
||||
? 'rgba(244, 114, 182, 0.1)'
|
||||
: playerId === 3
|
||||
? 'rgba(167, 139, 250, 0.1)'
|
||||
: 'rgba(251, 191, 36, 0.1)'
|
||||
e.currentTarget.style.borderColor = focusColor
|
||||
e.currentTarget.style.boxShadow = `0 0 0 3px ${shadowColor}`
|
||||
e.currentTarget.style.borderColor = gradientColor
|
||||
e.currentTarget.style.boxShadow = `0 0 0 3px ${gradientColor}20`
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
e.currentTarget.style.borderColor = '#e5e7eb'
|
||||
|
|
@ -278,13 +258,7 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
style={{
|
||||
flex: 1,
|
||||
padding: '12px',
|
||||
background: playerId === 1
|
||||
? 'linear-gradient(135deg, #60a5fa, #3b82f6)'
|
||||
: playerId === 2
|
||||
? 'linear-gradient(135deg, #f472b6, #ec4899)'
|
||||
: playerId === 3
|
||||
? 'linear-gradient(135deg, #a78bfa, #8b5cf6)'
|
||||
: 'linear-gradient(135deg, #fbbf24, #f59e0b)',
|
||||
background: `linear-gradient(135deg, ${gradientColor}, ${gradientColor}dd)`,
|
||||
border: 'none',
|
||||
borderRadius: '12px',
|
||||
fontSize: '14px',
|
||||
|
|
@ -309,4 +283,4 @@ export function PlayerConfigDialog({ playerId, onClose }: PlayerConfigDialogProp
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue