diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 45598bf8..a11a9e64 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -29,6 +29,7 @@ import AdminSignup from './Pages/Admin/AdminSignup'; import AdminDashboard from './Pages/Admin/AdminDashboard'; import ViewDebate from './Pages/ViewDebate'; import SupportOpenSource from './Pages/SupportOpenSource'; +import LoadingSpinner from './components/LoadingSpinner'; // Protects routes based on authentication status function ProtectedRoute() { @@ -38,7 +39,7 @@ function ProtectedRoute() { } const { isAuthenticated, loading: isLoading } = authContext; if (isLoading) { - return
Loading...
; + return } return isAuthenticated ? : ; } diff --git a/frontend/src/Pages/Admin/AdminDashboard.tsx b/frontend/src/Pages/Admin/AdminDashboard.tsx index 512dca1e..6194953b 100644 --- a/frontend/src/Pages/Admin/AdminDashboard.tsx +++ b/frontend/src/Pages/Admin/AdminDashboard.tsx @@ -40,6 +40,7 @@ import { ResponsiveContainer, } from "recharts"; import { TrendingUp, Users, MessageSquare, Activity } from "lucide-react"; +import LoadingSpinner from "@/components/LoadingSpinner"; type SnapshotLike = AnalyticsSnapshot & Partial>; @@ -474,9 +475,13 @@ export default function AdminDashboard() { setSelectedComments(newSelected); }; - if (loading) { - return
Loading...
; - } + if (loading) { + return ( +
+ +
+ ); + } if (error) { return ( diff --git a/frontend/src/Pages/Profile.tsx b/frontend/src/Pages/Profile.tsx index 1642fbd8..8880608f 100644 --- a/frontend/src/Pages/Profile.tsx +++ b/frontend/src/Pages/Profile.tsx @@ -86,6 +86,7 @@ import { transcriptService, SavedDebateTranscript, } from "@/services/transcriptService"; +import LoadingSpinner from "@/components/LoadingSpinner"; const handleProfileAvatarLoadError = ( event: React.SyntheticEvent @@ -637,7 +638,7 @@ const debounceTimer = useRef | null>(null);
{loadingFollowers ? ( -
Loading...
+
) : followers.length === 0 ? (
No followers yet
) : ( @@ -659,7 +660,7 @@ const debounceTimer = useRef | null>(null);
{loadingFollowing ? ( -
Loading...
+
) : following.length === 0 ? (
Not following anyone yet
) : ( diff --git a/frontend/src/Pages/TeamDebateRoom.tsx b/frontend/src/Pages/TeamDebateRoom.tsx index 8a0d42d8..33af494c 100644 --- a/frontend/src/Pages/TeamDebateRoom.tsx +++ b/frontend/src/Pages/TeamDebateRoom.tsx @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button"; import JudgmentPopup from "@/components/JudgementPopup"; import SpeechTranscripts from "@/components/SpeechTranscripts"; import { getAuthToken } from "@/utils/auth"; +import LoadingSpinner from "@/components/LoadingSpinner"; // Define debate phases as an enum (same as OnlineDebateRoom) enum DebatePhase { @@ -1691,10 +1692,10 @@ const TeamDebateRoom: React.FC = () => { Team Debate: {topic || "No topic set"}

- Phase: {debatePhase} | - My Team: {myTeamName} | - Opponent: {opponentTeamName} | - Current Turn:{" "} + Phase: {debatePhase} | My Team:{" "} + {myTeamName} | Opponent:{" "} + {opponentTeamName} | Current + Turn:{" "} {isMyTurn ? "Your Team" : "Opponent Team"} to{" "} {debatePhase.includes("Question") @@ -1704,7 +1705,7 @@ const TeamDebateRoom: React.FC = () => { : "make a statement"}

-
+ {/* Setup Popup */} @@ -1715,10 +1716,7 @@ const TeamDebateRoom: React.FC = () => { {isLoading ? (
-
- - Loading... - +
) : ( <> @@ -1743,7 +1741,7 @@ const TeamDebateRoom: React.FC = () => { placeholder="Or enter a custom debate topic" className="border border-border rounded p-2 w-full bg-input text-foreground" /> - +
@@ -1781,8 +1779,8 @@ const TeamDebateRoom: React.FC = () => { Against
- )} -
+ )} +
{/* Players List - Side by Side */} @@ -1794,12 +1792,19 @@ const TeamDebateRoom: React.FC = () => {
{myTeamMembers.map((member) => { - const isReady = playerReadyStatus.get(member.userId) ?? false; + const isReady = + playerReadyStatus.get(member.userId) ?? false; return ( -
+
{member.displayName} @@ -1831,12 +1836,19 @@ const TeamDebateRoom: React.FC = () => {
{opponentTeamMembers.map((member) => { - const isReady = playerReadyStatus.get(member.userId) ?? false; + const isReady = + playerReadyStatus.get(member.userId) ?? false; return ( -
+
{member.displayName} @@ -1877,7 +1889,8 @@ const TeamDebateRoom: React.FC = () => { Debate starting in {countdown}... - ) : team1ReadyCount !== team1MembersCount || team2ReadyCount !== team2MembersCount ? ( + ) : team1ReadyCount !== team1MembersCount || + team2ReadyCount !== team2MembersCount ? ( Waiting for all team members to be ready... ) : ( All teams ready! Debate starting soon... @@ -1971,16 +1984,40 @@ const TeamDebateRoom: React.FC = () => { @@ -1989,8 +2026,15 @@ const TeamDebateRoom: React.FC = () => { {isCurrentUser && !isCameraOn ? (
- - + +

Camera Off

@@ -2006,9 +2050,14 @@ const TeamDebateRoom: React.FC = () => { } } else { remoteVideoRefs.current.set(member.userId, el); - const existingStream = remoteStreams.get(member.userId); + const existingStream = remoteStreams.get( + member.userId + ); if (existingStream) { - attachStreamToVideo(member.userId, existingStream); + attachStreamToVideo( + member.userId, + existingStream + ); } } } @@ -2017,7 +2066,10 @@ const TeamDebateRoom: React.FC = () => { muted={isCurrentUser} playsInline className="w-full h-48 object-cover" - style={{ display: isCurrentUser && !isCameraOn ? 'none' : 'block' }} + style={{ + display: + isCurrentUser && !isCameraOn ? "none" : "block", + }} /> )}
@@ -2026,49 +2078,74 @@ const TeamDebateRoom: React.FC = () => {

- Time: {formatTime(isMyTurn ? timer : phaseDurations[debatePhase] || 0)} + Time:{" "} + {formatTime(isMyTurn ? timer : phaseDurations[debatePhase] || 0)}

- {isMyTurn && debatePhase !== DebatePhase.Setup && debatePhase !== DebatePhase.Finished && ( -
-
- - {isListening ? "Recording & Speech Recognition Active" : "Waiting..."} - -
- )} - {/* Camera Toggle Button - Only show for current user's team */} - {currentUser && myTeamMembers.some(m => m.userId === currentUser.id) && ( -
- -
- )} +
+ )} + {/* Camera Toggle Button - Only show for current user's team */} + {currentUser && + myTeamMembers.some((m) => m.userId === currentUser.id) && ( +
+ +
+ )}
@@ -2116,7 +2193,8 @@ const TeamDebateRoom: React.FC = () => {

- Time: {formatTime(!isMyTurn ? timer : phaseDurations[debatePhase] || 0)} + Time:{" "} + {formatTime(!isMyTurn ? timer : phaseDurations[debatePhase] || 0)}

diff --git a/frontend/src/components/LoadingSpinner.tsx b/frontend/src/components/LoadingSpinner.tsx new file mode 100644 index 00000000..d31fcf7c --- /dev/null +++ b/frontend/src/components/LoadingSpinner.tsx @@ -0,0 +1,41 @@ +import React from "react"; +import { Loader2 } from "lucide-react"; + +interface LoadingSpinnerProps { + size?: "sm" | "md" | "lg" | "xl"; + fullScreen?: boolean; +} + +const sizeMap = { + sm: "h-6 w-6", + md: "h-10 w-10", + lg: "h-16 w-16", + xl: "h-24 w-24", +}; + +const LoadingSpinner: React.FC = ({ + size, + fullScreen = false, +}) => { + const resolvedSize = size ?? (fullScreen ? "lg" : "md"); + + const content = ( +
+ +
+ ); + + if (fullScreen) { + return ( +
+ {content} +
+ ); + } + + return content; +}; + +export default LoadingSpinner; diff --git a/frontend/src/components/UserProfileModal.tsx b/frontend/src/components/UserProfileModal.tsx index a1600c6a..af2e0794 100644 --- a/frontend/src/components/UserProfileModal.tsx +++ b/frontend/src/components/UserProfileModal.tsx @@ -6,6 +6,7 @@ import { UserPlus, UserCheck, Users } from 'lucide-react'; import { useUser } from '../hooks/useUser'; import defaultAvatar from '@/assets/avatar2.jpg'; import ProfileHover from './ProfileHover'; +import LoadingSpinner from './LoadingSpinner'; interface UserProfile { id: string; @@ -271,7 +272,7 @@ const UserProfileModal: React.FC = ({ )} {currentUser?.id && currentUser.id !== userId && (