diff --git a/src/apps/golf/components/GolfGame.module.css b/src/apps/golf/components/GolfGame.module.css index ca9fb1f..bf95a64 100644 --- a/src/apps/golf/components/GolfGame.module.css +++ b/src/apps/golf/components/GolfGame.module.css @@ -156,6 +156,62 @@ flex-shrink: 0; } +.leaveGameLink { + background: none; + border: none; + color: rgba(255, 255, 255, 0.35); + cursor: pointer; + font-size: 0.9rem; + padding: 0.25rem 0.5rem; + flex-shrink: 0; + line-height: 1; +} + +.leaveGameLink:hover { + color: rgba(255, 255, 255, 0.7); +} + +.leaveConfirm { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + margin-top: 0.5rem; + padding: 0.5rem 0.75rem; + background: rgba(255, 80, 80, 0.15); + border-radius: 0.4rem; + font-size: 0.85rem; + color: rgba(255, 255, 255, 0.9); +} + +.leaveConfirmYes { + background: rgba(255, 80, 80, 0.6); + border: none; + border-radius: 0.3rem; + color: white; + padding: 0.25rem 0.75rem; + cursor: pointer; + font-size: 0.8rem; +} + +.leaveConfirmYes:hover { + background: rgba(255, 80, 80, 0.8); +} + +.leaveConfirmNo { + background: rgba(255, 255, 255, 0.15); + border: none; + border-radius: 0.3rem; + color: rgba(255, 255, 255, 0.8); + padding: 0.25rem 0.75rem; + cursor: pointer; + font-size: 0.8rem; +} + +.leaveConfirmNo:hover { + background: rgba(255, 255, 255, 0.25); +} + .playerList { display: flex; gap: 1rem; @@ -641,39 +697,58 @@ } .gameHeader { - margin-bottom: 0.5rem; - padding: 0.5rem 0.75rem; + margin-bottom: 0.25rem; + padding: 0.35rem 0.5rem; } .gameHeader h2 { - font-size: 0.8rem; + font-size: 0.75rem; margin: 0; + white-space: nowrap; } .gameHeaderTop { flex-direction: row; - gap: 0.5rem; + gap: 0.4rem; align-items: center; } .gameHeaderInfo { + display: flex; + align-items: center; + gap: 0.4rem; text-align: left; min-width: 0; + overflow: hidden; } .playerList { - flex-wrap: wrap; - gap: 0.2rem; - margin-top: 0.2rem; + flex-wrap: nowrap; + gap: 0.15rem; + margin-top: 0; + overflow: hidden; } .playerInfo { - font-size: 0.7rem; - padding: 0.15rem 0.35rem; + font-size: 0.65rem; + padding: 0.1rem 0.3rem; + white-space: nowrap; } .turnLabel { - font-size: 0.6rem; + font-size: 0.55rem; + } + + .leaveGameLink { + font-size: 0.75rem; + padding: 0.2rem 0.35rem; + } + + .leaveConfirm { + margin-top: 0.35rem; + padding: 0.35rem 0.5rem; + font-size: 0.75rem; + gap: 0.5rem; } .gameArea { diff --git a/src/apps/golf/components/GolfGame.tsx b/src/apps/golf/components/GolfGame.tsx index 29679e7..763a2ce 100644 --- a/src/apps/golf/components/GolfGame.tsx +++ b/src/apps/golf/components/GolfGame.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import styles from './GolfGame.module.css' import { useGolfGame } from '@/hooks/useGolfGame' import PermalinkDisplay from './PermalinkDisplay' @@ -27,8 +27,7 @@ interface GolfGameProps { const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, permalinkParams }: GolfGameProps) => { const [showRules, setShowRules] = useState(false) const [showScores, setShowScores] = useState(false) - - // Pass permalink parameters to useGolfGame hook for automatic joining + const [showLeaveConfirm, setShowLeaveConfirm] = useState(false) // Helper function to get display name (now just use the ID directly) const getDisplayName = (player: Player | null) => { @@ -63,11 +62,16 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn knock, handleCardClick, setRoomCode, - clearGameState, + leaveGame, dismissNewGameNotification, joinNewGame } = useGolfGame({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConnectionChange, permalinkParams }) + const confirmLeave = useCallback(() => { + setShowLeaveConfirm(false) + leaveGame() + }, [leaveGame]) + useEffect(() => { if (gameState?.gamePhase === 'ended') { const hideTimer = setTimeout(() => setShowScores(false), 0) @@ -391,7 +395,17 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn /> )} + + {showLeaveConfirm && ( +
+ Leave this game? + + +
+ )} {gameState.gamePhase === 'waiting' && ( @@ -403,6 +417,9 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn Start Game )} + )} @@ -495,12 +512,9 @@ const GolfGame = ({ onGameIdChange, onPlayerIdChange, onPlayerNameChange, onConn )}
- -
diff --git a/src/hooks/useGolfGame.ts b/src/hooks/useGolfGame.ts index 7868f1b..d0248bf 100644 --- a/src/hooks/useGolfGame.ts +++ b/src/hooks/useGolfGame.ts @@ -72,6 +72,7 @@ interface UseGolfGameReturn { handleCardClick: (index: number) => void setRoomCode: (code: string) => void clearGameState: () => void + leaveGame: () => void // Computed currentPlayer: Player | undefined @@ -283,6 +284,14 @@ export const useGolfGame = ({ setSelectedCardIndex(null) }, []) + const leaveGame = useCallback(() => { + networkAdapterRef.current?.leaveGame() + setGameState(null) + setWinner(null) + setFinalScores(null) + setSelectedCardIndex(null) + }, []) + // Navigation helper functions const navigateToRoom = useCallback((roomId: string) => { const roomUrl = generateRoomPermalink(roomId) @@ -803,7 +812,8 @@ export const useGolfGame = ({ handleCardClick, setRoomCode, clearGameState, - + leaveGame, + // Navigation helpers navigateToRoom, navigateToGame, diff --git a/src/plugins/golfNetworkPlugin.ts b/src/plugins/golfNetworkPlugin.ts index 4219e1f..6a6dd1f 100644 --- a/src/plugins/golfNetworkPlugin.ts +++ b/src/plugins/golfNetworkPlugin.ts @@ -12,7 +12,7 @@ interface GolfMessage extends BaseNetworkMessage { type: 'authenticate' | 'authenticated' | 'createRoom' | 'joinRoom' | 'createGame' | 'joinGame' | 'roomJoined' | 'roomStateUpdate' | 'gameState' | 'error' | 'gameStarted' | 'turnChanged' | 'playerKnocked' | 'gameEnded' | 'newGameStarted' | 'startGame' | 'peekCard' | 'drawCard' | 'takeFromDiscard' | - 'swapCard' | 'discardDrawn' | 'knock' | 'hideCards' | 'startNewGame' + 'swapCard' | 'discardDrawn' | 'knock' | 'hideCards' | 'startNewGame' | 'leaveGame' // Request fields roomId?: string gameId?: string @@ -396,6 +396,14 @@ export class GolfNetworkPlugin implements GameNetworkPlugin { console.log('📤 Sent start new game request') } + leaveGame(context: NetworkContext): void { + context.send({ + type: 'leaveGame', + timestamp: Date.now() + }) + console.log('📤 Sent leave game request') + } + // Session token management private storeSessionToken(token: string): void { try { diff --git a/src/utils/networkAdapter.ts b/src/utils/networkAdapter.ts index 18b741c..cb12f19 100644 --- a/src/utils/networkAdapter.ts +++ b/src/utils/networkAdapter.ts @@ -251,6 +251,10 @@ export class GolfNetworkAdapter { this.plugin.startNewGame(this.getContext()) } + leaveGame(): void { + this.plugin.leaveGame(this.getContext()) + } + isMyTurn(): boolean { return this.plugin.isMyTurn(this.getContext()) }