Skip to content

Commit dcb6f60

Browse files
Merge pull request #114 from Treevyy/feature/sound-toggle
Feature/sound toggle
2 parents 74cb05a + 3f68c59 commit dcb6f60

21 files changed

+203
-99
lines changed

client/public/game_over.mp3

13.5 KB
Binary file not shown.

client/public/icons/spkr-off.png

1.28 MB
Loading

client/public/icons/spkr-on.png

1.28 MB
Loading

client/public/totalwin1.mp3

55 KB
Binary file not shown.

client/src/App.tsx

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import LeaderBoard from './components/LeaderBoard';
2020

2121
import './styles/codezilla.css';
2222
import BackgroundMusicProvider from './components/BackgroundMusicProvider';
23-
23+
import SoundToggle from './components/SoundToggle';
2424

2525
const LogoutButton: React.FC = () => {
2626
const navigate = useNavigate();
@@ -43,11 +43,12 @@ const AppContent: React.FC = () => {
4343
const isGameActive =
4444
location.pathname.startsWith('/map') ||
4545
location.pathname.startsWith('/question');
46+
47+
4648

4749
return (
4850
<div className="app-wrapper">
4951
{/* Only load persistent music during gameplay */}
50-
5152
{isGameActive && (
5253
<BackgroundMusicProvider src="/black.sabbath.mp3" volume={0.03} />
5354
)}
@@ -60,22 +61,25 @@ const AppContent: React.FC = () => {
6061

6162
<LogoutButton />
6263

63-
{/* Routes */}
64-
<Routes>
65-
<Route path="/" element={<IntroPage />} />
66-
<Route path="/login" element={<Login />} />
67-
<Route path="/map" element={<GameMap />} />
68-
<Route path="/gameover" element={<GameOver />} />
69-
<Route path="/signup" element={<Signup />} />
70-
<Route path="/victory" element={<Victory />} />
71-
<Route path="/leaderboard" element={<LeaderBoard />} />
72-
<Route path="/question/:id" element={<Questions />} />
73-
</Routes>
74-
</div>
75-
64+
{/* ✅ GLOBAL SoundToggle button */}
65+
<SoundToggle />
66+
67+
{/* Routes */}
68+
<Routes>
69+
<Route path="/" element={<IntroPage />} />
70+
<Route path="/login" element={<Login />} />
71+
<Route path="/map" element={<GameMap />} />
72+
<Route path="/gameover" element={<GameOver />} />
73+
<Route path="/signup" element={<Signup />} />
74+
<Route path="/victory" element={<Victory />} />
75+
<Route path="/leaderboard" element={<LeaderBoard />} />
76+
<Route path="/question/:id" element={<Questions />} />
77+
</Routes>
78+
</div>
7679
);
7780
};
7881

82+
7983
const App: React.FC = () => {
8084
return (
8185
<Router>
@@ -85,4 +89,3 @@ const App: React.FC = () => {
8589
};
8690

8791
export default App;
88-
// commmits

client/src/components/BackgroundMusicProvider.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
// src/components/BackgroundMusic.tsx
21
import { useEffect, useRef } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
import { useSound } from './SoundContext';
34

45
interface Props {
56
src: string;
@@ -10,22 +11,44 @@ let globalAudio: HTMLAudioElement | null = null;
1011

1112
const BackgroundMusic: React.FC<Props> = ({ src, volume = 0.03 }) => {
1213
const audioRef = useRef<HTMLAudioElement | null>(null);
14+
const { pathname } = useLocation();
15+
const { isSoundOn } = useSound();
1316

1417
useEffect(() => {
18+
// Always stop music on login screen
19+
if (pathname === '/login') {
20+
if (globalAudio) {
21+
globalAudio.pause();
22+
globalAudio.currentTime = 0;
23+
globalAudio = null;
24+
}
25+
return;
26+
}
27+
1528
if (!globalAudio) {
1629
globalAudio = new Audio(src);
1730
globalAudio.loop = true;
1831
globalAudio.volume = volume;
19-
globalAudio.play().catch((err) => {
20-
console.warn('🎵 Autoplay blocked or failed:', err);
21-
});
32+
33+
const handleUserInteraction = () => {
34+
if (isSoundOn) {
35+
globalAudio?.play().catch((err) =>
36+
console.warn('🎵 Autoplay blocked:', err)
37+
);
38+
}
39+
document.removeEventListener('click', handleUserInteraction);
40+
};
41+
42+
document.addEventListener('click', handleUserInteraction);
2243
}
44+
2345
audioRef.current = globalAudio;
2446

25-
return () => {
26-
// DO NOT stop or recreate the audio on unmount
27-
};
28-
}, [src, volume]);
47+
// Sync mute state with toggle
48+
if (globalAudio) {
49+
globalAudio.muted = !isSoundOn;
50+
}
51+
}, [pathname, src, volume, isSoundOn]);
2952

3053
return null;
3154
};

client/src/components/LeaderBoard.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import "../styles/leaderboard.css";
2-
import "../styles/codezilla.css"
32
import { useBodyClass } from '../utils/useBodyClass';
43
import { useQuery } from "@apollo/client";
54
import { GET_USERS } from "@/graphql/queries";
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import React, { createContext, useContext, useState } from 'react';
2+
3+
interface SoundContextType {
4+
isSoundOn: boolean;
5+
toggleSound: () => void;
6+
stopMusic: () => void;
7+
}
8+
9+
const SoundContext = createContext<SoundContextType | undefined>(undefined);
10+
11+
export const SoundProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
12+
const [isSoundOn, setIsSoundOn] = useState(true);
13+
14+
const toggleSound = () => {
15+
setIsSoundOn((prev) => !prev);
16+
};
17+
18+
const stopMusic = () => {
19+
if ((globalThis as any).globalAudio) {
20+
const audio = (globalThis as any).globalAudio as HTMLAudioElement;
21+
audio.pause();
22+
audio.currentTime = 0;
23+
(globalThis as any).globalAudio = null;
24+
}
25+
setIsSoundOn(false);
26+
};
27+
28+
return (
29+
<SoundContext.Provider value={{ isSoundOn, toggleSound, stopMusic }}>
30+
{children}
31+
</SoundContext.Provider>
32+
);
33+
};
34+
35+
export const useSound = () => {
36+
const context = useContext(SoundContext);
37+
if (!context) throw new Error('useSound must be used within SoundProvider');
38+
return context;
39+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import React from 'react';
2+
import { useSound } from '../components/SoundContext';
3+
import { useLocation } from 'react-router-dom';
4+
5+
6+
const SoundToggle: React.FC = () => {
7+
const { isSoundOn, toggleSound } = useSound();
8+
const location = useLocation();
9+
const isGamePage = location.pathname.startsWith('/map') || location.pathname.startsWith('/question');
10+
11+
12+
return (
13+
<div
14+
style={{
15+
position: 'fixed',
16+
bottom: '1rem',
17+
left: '1rem',
18+
zIndex: 9999,
19+
}}
20+
>
21+
<button
22+
onClick={toggleSound}
23+
className={`p-2 bg-transparent rounded-full transition ${isGamePage ? 'hover:opacity-80' : 'opacity-40 cursor-not-allowed'}`}
24+
aria-label={isSoundOn ? 'Mute sound' : 'Unmute sound'}
25+
disabled={!isGamePage}
26+
>
27+
<img
28+
src={isSoundOn && isGamePage ? '/icons/spkr-on.png' : '/icons/spkr-off.png'}
29+
alt="Sound Toggle"
30+
style={{
31+
width: '24px',
32+
height: '24px',
33+
objectFit: 'contain',
34+
}}
35+
/>"
36+
37+
</button>
38+
39+
</div>
40+
);
41+
};
42+
43+
export default SoundToggle;

client/src/components/screens/GameMap.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import "../../styles/codezilla.css";
1010
import drDanImg from '../../../avatars/drdan2.png';
1111
import flameImg from '../../assets/flame.png';
1212

13+
14+
1315
const selectedAvatar = localStorage.getItem('selectedAvatar');
1416
const username = localStorage.getItem('username') || 'Player';
1517

@@ -21,8 +23,7 @@ const GameMap: React.FC = () => {
2123
const [selectedMinionId, setSelectedMinionId] = useState<string | null>(null);
2224

2325
useEffect(() => {
24-
preloadSounds([
25-
'/audio/Dan_correct/Dan-correct-1.wav']);
26+
preloadSounds(['/audio/Dan_correct/Dan-correct-1.wav']);
2627
}, []);
2728

2829
const handleDanHover = () => {
@@ -112,7 +113,6 @@ const GameMap: React.FC = () => {
112113
navigate(`/question/${questionId}`);
113114
};
114115

115-
console.log("selectedAvatar:", selectedAvatar);
116116
return (
117117
<div className="game-map">
118118
<BackgroundMusic src="/black.sabbath.mp3" volume={0.03} />

0 commit comments

Comments
 (0)