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