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 && ( +
+ )} + {alt} { + 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 && ( -
+
+
{team.name}
)} @@ -90,11 +225,18 @@ const TeamDashboardPage: FC = (): ReactElement => {
{team.logo && ( - {team.name} +
+ {!imagesLoaded && ( +
+ )} + {team.name} +
)}

{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 ? ( {member.user.fullname} ) : (
@@ -274,7 +418,7 @@ const TeamDashboardPage: FC = (): ReactElement => { )}

- {member.user.fullname} + {member.user?.fullname || 'Unknown'}

{member.role === ETeamMemberRole.LEADER ? 'Leader' : 'Member'}