Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added client/minions/codezilla3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/minions/pie-thon3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,282 changes: 1,940 additions & 342 deletions client/package-lock.json

Large diffs are not rendered by default.

Binary file added client/public/avatars/DrDanCorrect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/avatars/DrDanWrong.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added client/public/codezilla_intro.mp4
Binary file not shown.
14 changes: 8 additions & 6 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, useNavigate } from 'react-router-dom';
import IntroPage from './components/screens/IntroPage';
import { Login } from './components/screens/Login';
import GameMap from './components/screens/GameMap';
import GameOver from './components/screens/GameOver';
import Victory from './components/screens/Victory';
import Signup from './components/screens/Signup';
import Questions from './components/screens/Questions';

import './styles/codezilla.css';


const LogoutButton: React.FC = () => {
const navigate = useNavigate();

const handleLogout = () => {
localStorage.clear();
navigate('/signup');
navigate('/login');
};

return (
Expand All @@ -28,14 +29,14 @@ const App: React.FC = () => {
return (
<Router>
<div className="app-wrapper">
{/* Background Image */}
{/* Background Image
<img
src="/background/codezilla_bkgd.png"
alt="Dark rainy cityscape"
className="background-image"
/>
/> */}

{/* Logo */}

<img
src="/codezilla_logo.png"
alt="Codezilla Logo"
Expand All @@ -47,7 +48,8 @@ const App: React.FC = () => {

{/* Routes */}
<Routes>
<Route path="/" element={<Login />} />
<Route path="/" element={<IntroPage />} />
<Route path="/login" element={<Login />} />
<Route path="/map" element={<GameMap />} />
<Route path="/gameover" element={<GameOver />} />
<Route path="/signup" element={<Signup />} />
Expand Down
13 changes: 13 additions & 0 deletions client/src/Utils/useBodyClass.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// src/Utils/useBodyClass.ts
import { useEffect } from 'react';

export const useBodyClass = (className: string) => {
useEffect(() => {
console.log(`✅applying body class: ${className}`);

document.body.classList.add(className);
return () => {
document.body.classList.remove(className);
};
}, [className]);
};
63 changes: 30 additions & 33 deletions client/src/components/AnswerResultModal.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,56 @@
import { useEffect, useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '../components/ui/dialog';
import SoundPlayer from './SoundPlayer';
import { useEffect, useState } from "react";
import SoundPlayer from "./SoundPlayer";
import ReactDOM from "react-dom";

interface AnswerResultModalProps {
isOpen: boolean;
onClose: () => void;
isCorrect: boolean;
drDanQuote: string;
audioUrl?: string;
}

const AnswerResultModal = ({
isOpen,
onClose,
isCorrect,
drDanQuote,
audioUrl,
}: AnswerResultModalProps) => {
const [playAudio, setPlayAudio] = useState(false);

// ✅ Start or stop audio based on modal open state
useEffect(() => {
if (isOpen) {
setPlayAudio(true);
} else {
setPlayAudio(false);
}
}, [isOpen]);

return (
<Dialog open={isOpen} onOpenChange={(open) => { if (!open) onClose(); }}>
<DialogContent className="rounded-2xl shadow-xl text-center">
<DialogHeader>
<DialogTitle className={`text-2xl font-bold ${isCorrect ? 'text-green-500' : 'text-red-500'}`}>
{isCorrect ? "Correct!" : "Wrong!"}
</DialogTitle>
<DialogDescription className="text-lg mt-2">
{drDanQuote}
</DialogDescription>
</DialogHeader>

{/* ✅ Use Howler to play full clip, then close modal */}
{audioUrl && (
<SoundPlayer
src={audioUrl}
playing={playAudio}
onEnd={() => {
setPlayAudio(false);
onClose();
}}
/>
)}
</DialogContent>
</Dialog>
if (!isOpen) return null;

const drDanImage = isCorrect
? "/avatars/DrDanCorrect.png"
: "/avatars/DrDanWrong.png";

const modalContent = (
<div className="fixed top-1/2 right-4 transform -translate-y-1/2 z-[9999] w-[200px] flex flex-col items-center gap-2 animate-fadeIn pointer-events-none">

Check failure on line 33 in client/src/components/AnswerResultModal.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
<img

Check failure on line 34 in client/src/components/AnswerResultModal.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
src={drDanImage}
alt="Dr. Dan"
className="w-[160px] h-auto object-contain"
/>
{audioUrl && (
<SoundPlayer

Check failure on line 40 in client/src/components/AnswerResultModal.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
src={audioUrl}
playing={playAudio}
onEnd={() => {
setPlayAudio(false);
onClose();
}}
/>
)}
</div>
);

return ReactDOM.createPortal(modalContent, document.body);
};

export default AnswerResultModal;
//commiting"
3 changes: 2 additions & 1 deletion client/src/components/DanismEvent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
'/audio/Dan_correct/Dan-correct-2.wav',
'/audio/Dan_correct/Dan-correct-3.wav',
'/audio/Dan_correct/Dan-correct-4.wav',
'/audio/Dan_correct/Dan-5inarow.wav',
'/audio/Dan_incorrect/Dan-incorrect-1.wav',
'/audio/Dan_incorrect/Dan-incorrect-2.wav',
'/audio/Dan_incorrect/Dan-incorrect-3.wav',
'/audio/Dan_incorrect/Dan-incorrect-4.wav',
'/audio/drdan_fallback.mp3'
'/audio/Dan_incorrect/Dan-incorrect-5.wav'
]);
}, []);

Expand Down Expand Up @@ -70,16 +71,16 @@


return (
<>

Check failure on line 74 in client/src/components/DanismEvent.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
{/* TEMP TEST BUTTONS */}
<div className="flex gap-4 justify-center my-4">

Check failure on line 76 in client/src/components/DanismEvent.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
<button

Check failure on line 77 in client/src/components/DanismEvent.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
onClick={() => triggerDanism(true)}
className="bg-green-500 hover:bg-green-600 text-white font-bold py-2 px-4 rounded shadow"
>
Test Correct Answer
</button>
<button

Check failure on line 83 in client/src/components/DanismEvent.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
onClick={() => triggerDanism(false)}
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded shadow"
>
Expand All @@ -91,7 +92,7 @@

{/* 🎶 SoundPlayer */}
{audioSrc && (
<SoundPlayer

Check failure on line 95 in client/src/components/DanismEvent.tsx

View workflow job for this annotation

GitHub Actions / test

'React' must be in scope when using JSX
src={audioSrc}
playing={playing}
onEnd={() => setPlaying(false)}
Expand Down
27 changes: 9 additions & 18 deletions client/src/components/screens/GameMap.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// src/components/screens/GameMap.tsx

// IMPORT LIBRARIES
import React, { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import Minion from './Minions';
import { useBodyClass } from '../../Utils/useBodyClass';
import { preloadSounds } from '../../utils/preloadSounds';
import '../../styles/codezilla.css';
import "../../styles/codezilla.css";
import drDanImg from '../../../avatars/drdan2.png';
import flameImg from '../../assets/flame.png';

Expand All @@ -14,9 +12,11 @@ const username = localStorage.getItem('username') || 'Player';

const GameMap: React.FC = () => {
const navigate = useNavigate();
useBodyClass('login-background');

const [completedPaths, setCompletedPaths] = useState<string[]>([]);
const [selectedMinionId, setSelectedMinionId] = useState<string | null>(null);

// ✅ Preload Dr. Dan audio files on mount
useEffect(() => {
preloadSounds([
'/audio/Dan_correct/Dan-correct-1.wav',
Expand All @@ -31,7 +31,6 @@ const GameMap: React.FC = () => {
'/audio/5inarow.wav'
]);
}, []);
const [selectedMinionId, setSelectedMinionId] = useState<string | null>(null);

const minions = [
{
Expand Down Expand Up @@ -105,15 +104,12 @@ const GameMap: React.FC = () => {

const goToQuestion = (questionId: string) => {
const currentIndex = minions.findIndex(m => m.questionId === questionId);

if (currentIndex < nodes.length - 1) {
const pathId = `${nodes[currentIndex].id}-${nodes[currentIndex + 1].id}`;
setCompletedPaths(prev => [...prev, pathId]);
}
const minion = minions.find(m => m.questionId === questionId);
if (minion) {
setSelectedMinionId(minion.id);
}
if (minion) setSelectedMinionId(minion.id);
navigate(`/question/${questionId}`);
};

Expand All @@ -123,7 +119,6 @@ const GameMap: React.FC = () => {
{nodes.map((node, index) => {
const nextNode = nodes[index + 1];
if (!nextNode) return null;

return (
<line
key={`line-${node.id}-${nextNode.id}`}
Expand All @@ -136,10 +131,8 @@ const GameMap: React.FC = () => {
);
})}

{/* GLOWING OUTER CIRCLE + DARKER INNER CIRCLE */}
{nodes.map((node, index) => {
const isCompleted = index < completedPaths.length;

return (
<g key={`node-${node.id}`}>
<circle
Expand All @@ -158,6 +151,7 @@ const GameMap: React.FC = () => {
);
})}
</svg>

<div className="dr-dan-wrapper">
<div className="glow-circle"></div>
<img src={drDanImg} alt="Dr. Dan" className="dr-dan-image" />
Expand All @@ -167,22 +161,19 @@ const GameMap: React.FC = () => {
<div className="player-avatar-wrapper">
<div className="player-glow-circle"></div>
<img src={selectedAvatar} alt="Your Avatar" className="player-avatar" />

<div className="player-info-wrapper">
<div className="player-info-box">
<p className="player-name">{username}</p>
<p className="player-level">Level 1</p>
</div>

<div className="learning-info-box">
<p className='learning-title'> Studying</p>
<p className='learning-title'>Studying</p>
<p className="learning-text">Coding Bootcamp</p>
</div>
{/* Lifeline Box */}
<div className='flames'>
<div className="lifeline-info-box">
<div className="lifeline-flames">
<p className='learning-title'> Life Lines</p>
<p className='learning-title'>Life Lines</p>
<img src={flameImg} alt="Flame 1" />
<img src={flameImg} alt="Flame 2" />
<img src={flameImg} alt="Flame 3" />
Expand Down
5 changes: 3 additions & 2 deletions client/src/components/screens/GameOver.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { useNavigate } from 'react-router-dom';
import "../../styles/codezilla.css";


const GameOverPage = ({
backgroundUrl = 'client/background/codezilla_bkgd.png',
// backgroundUrl = 'client/background/codezilla_bkgd.png',
avatarUrl = 'client/avatars/avatar4.png',
codezillaUrl = 'client/minions/codezilla2.png',
}) => {
Expand All @@ -21,7 +22,7 @@ const GameOverPage = ({
return (
<div
className="game-over-page"
style={{ backgroundImage: `url(${backgroundUrl})` }}
// style={{ backgroundImage: `url(${backgroundUrl})` }}
>
<div className="game-over-container">
<h1 className="game-over-title">Game Over!</h1>
Expand Down
46 changes: 46 additions & 0 deletions client/src/components/screens/IntroPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { useBodyClass } from '../../Utils/useBodyClass';
import "../../styles/codezilla.css";

const IntroPage: React.FC = () => {
const navigate = useNavigate();
const videoRef = useRef<HTMLVideoElement | null>(null);
const [hasClicked, setHasClicked] = useState(false);
useBodyClass('intro-background');

const handleVideoEnd = () => {
navigate('/login');
};

const handleUserClick = () => {
if (videoRef.current && !hasClicked) {
videoRef.current.muted = false;
videoRef.current.play();
setHasClicked(true);
}
};

useEffect(() => {
document.addEventListener('click', handleUserClick);
return () => document.removeEventListener('click', handleUserClick);
}, [hasClicked]);

return (
<div className="intro-overlay">
<video
ref={videoRef}
className="intro-video"
autoPlay
muted
playsInline
onEnded={handleVideoEnd}
>
<source src="/codezilla_intro.mp4" type="video/mp4" />
Your browser does not support the video tag.
</video>
</div>
);
};

export default IntroPage;
Loading
Loading