diff --git a/apps/hackathon/src/app/teams/[teamId]/page.tsx b/apps/hackathon/src/app/teams/[teamId]/page.tsx
index 7872211..c720004 100644
--- a/apps/hackathon/src/app/teams/[teamId]/page.tsx
+++ b/apps/hackathon/src/app/teams/[teamId]/page.tsx
@@ -1,4 +1,4 @@
-import { FC, ReactElement, useState } from 'react';
+import { FC, ReactElement, useState, useEffect } from 'react';
import { Button } from '@imphnen-frontend-service/ui/atoms';
import { Link, useParams, useNavigate } from 'react-router';
import { useTeamById, useTeamMembers, useInviteMember, useTeamJoinRequests, useRespondToJoinRequest, ETeamMemberRole, useAuthStore } from '@imphnen-frontend-service/service';
@@ -6,6 +6,33 @@ import { toast } from 'sonner';
const MAX_TEAM_MEMBERS = 5;
+// Image component with loading state
+const ImageWithLoader: FC<{
+ src: string;
+ alt: string;
+ className?: string;
+ onLoad?: () => void;
+}> = ({ src, alt, className, onLoad }) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+
+ return (
+
+ {!isLoaded && (
+
+ )}
+

{
+ setIsLoaded(true);
+ onLoad?.();
+ }}
+ />
+
+ );
+};
+
const TeamDashboardPage: FC = (): ReactElement => {
const { teamId } = useParams<{ teamId: string }>();
const navigate = useNavigate();
@@ -13,6 +40,8 @@ const TeamDashboardPage: FC = (): ReactElement => {
const [showInviteModal, setShowInviteModal] = useState(false);
const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false);
const [inviteEmail, setInviteEmail] = useState('');
+ const [imagesLoaded, setImagesLoaded] = useState(false);
+ const [imageLoadCount, setImageLoadCount] = useState(0);
const { data: teamData, isLoading: isLoadingTeam } = useTeamById(teamId || '');
const { data: membersData, isLoading: isLoadingMembers } = useTeamMembers(teamId || '');
@@ -29,6 +58,41 @@ const TeamDashboardPage: FC = (): ReactElement => {
const isLeader = currentUserId === team?.leader_id;
const canInvite = isLeader && members.length < MAX_TEAM_MEMBERS;
+ // Calculate total images to load
+ const totalImagesToLoad = (team?.banner ? 1 : 0) +
+ (team?.logo ? 1 : 0) +
+ members.filter((m: any) => m.user?.avatar).length;
+
+ // Track image loading with timeout fallback
+ useEffect(() => {
+ if (!isLoadingTeam && !isLoadingMembers && team) {
+ if (totalImagesToLoad === 0) {
+ setImagesLoaded(true);
+ } else if (imageLoadCount >= totalImagesToLoad) {
+ setImagesLoaded(true);
+ }
+ }
+ }, [isLoadingTeam, isLoadingMembers, team, imageLoadCount, totalImagesToLoad]);
+
+ // Fallback timeout - if images take too long, show content anyway
+ useEffect(() => {
+ if (!isLoadingTeam && !isLoadingMembers && team && !imagesLoaded) {
+ const timeout = setTimeout(() => {
+ setImagesLoaded(true);
+ }, 3000); // 3 second timeout
+ return () => clearTimeout(timeout);
+ }
+ }, [isLoadingTeam, isLoadingMembers, team, imagesLoaded]);
+
+ const handleImageLoad = () => {
+ setImageLoadCount((prev) => prev + 1);
+ };
+
+ // Also count error as loaded to prevent stuck
+ const handleImageError = () => {
+ setImageLoadCount((prev) => prev + 1);
+ };
+
console.log('Leader check:', { currentUserId, leaderId: team?.leader_id, isLeader });
const handleInviteMember = async (e: React.FormEvent) => {
@@ -58,8 +122,65 @@ const TeamDashboardPage: FC = (): ReactElement => {
if (isLoadingTeam || isLoadingMembers) {
return (
-
-
Loading team...
+
+ {/* Skeleton Header */}
+
+
+
+
+ {/* Main Content Skeleton */}
+
+
+ {/* Sidebar Skeleton */}
+
+
+
+
+ {[1, 2, 3].map((i) => (
+
+ ))}
+
+
+
+
+
+
+ {/* Loading Overlay */}
+
+
+
+
Loading team data...
+
+
);
}
@@ -74,15 +195,29 @@ const TeamDashboardPage: FC = (): ReactElement => {
}
return (
-
+
+ {/* Loading overlay while images are loading */}
+ {!imagesLoaded && totalImagesToLoad > 0 && (
+
+
+
+
Loading images...
+
{imageLoadCount} / {totalImagesToLoad}
+
+
+ )}
+
{/* Header with Banner */}
{team.banner && (
-
+
+
)}
@@ -90,11 +225,18 @@ const TeamDashboardPage: FC = (): ReactElement => {
{team.logo && (
-

+
+ {!imagesLoaded && (
+
+ )}
+

+
)}
{team.name}
@@ -259,13 +401,15 @@ const TeamDashboardPage: FC = (): ReactElement => {
Members ({members.length})
- {members.map((member) => (
+ {members.map((member: any) => (
- {member.user.avatar ? (
+ {member.user?.avatar ? (

) : (
@@ -274,7 +418,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
)}
- {member.user.fullname}
+ {member.user?.fullname || 'Unknown'}
{member.role === ETeamMemberRole.LEADER ? 'Leader' : 'Member'}