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, useLeaveTeam, useDeleteTeam, ETeamMemberRole, useAuthStore, } from '@imphnen-frontend-service/service'; import { toast } from 'sonner'; import { Icon } from '@iconify/react'; const MAX_TEAM_MEMBERS = 5; // Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7) const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z'); // 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(); const { session } = useAuthStore(); // Check if team features are closed const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE; const [showInviteModal, setShowInviteModal] = useState(false); const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false); const [showLeaveModal, setShowLeaveModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [deleteConfirmText, setDeleteConfirmText] = useState(''); const [inviteEmail, setInviteEmail] = useState(''); const [imagesLoaded, setImagesLoaded] = useState(false); const [imageLoadCount, setImageLoadCount] = useState(0); // Calculate total images to load (banner + logo + member avatars) const totalImagesToLoad = 0; // Simplified - disable image loading overlay const handleImageLoad = () => { setImageLoadCount((prev) => { const newCount = prev + 1; if (newCount >= totalImagesToLoad) { setImagesLoaded(true); } return newCount; }); }; const handleImageError = () => { // Treat error as loaded to not block the UI handleImageLoad(); }; // Set images as loaded immediately since we disabled the loading overlay useEffect(() => { setImagesLoaded(true); }, []); const { data: teamData, isLoading: isLoadingTeam } = useTeamById( teamId || '' ); const { data: membersData, isLoading: isLoadingMembers } = useTeamMembers( teamId || '' ); const team = teamData?.data; const members = membersData?.data || []; const currentUserId = session?.user?.id; const isLeader = currentUserId === team?.leader_id; // Only fetch join requests if user is the team leader const { data: joinRequestsData } = useTeamJoinRequests( teamId || '', !!teamId && isLeader ); const { mutateAsync: inviteMember, isPending: isInviting } = useInviteMember( teamId || '' ); const { mutateAsync: respondToJoinRequest, isPending: isResponding } = useRespondToJoinRequest(teamId || ''); const { mutateAsync: leaveTeam, isPending: isLeaving } = useLeaveTeam(); const { mutateAsync: deleteTeam, isPending: isDeleting } = useDeleteTeam(); const joinRequests = joinRequestsData?.data || []; const pendingJoinRequests = joinRequests.filter( (req: any) => req.status === 'pending' ); const isMember = members.some( (member: any) => member.user_id === currentUserId ); const canInvite = isLeader && members.length < MAX_TEAM_MEMBERS; const handleInviteMember = async (e: React.FormEvent) => { e.preventDefault(); if (!inviteEmail.trim() || isInviting) return; try { await inviteMember({ email: inviteEmail.trim() }); toast.success('Invitation sent successfully!'); setInviteEmail(''); setShowInviteModal(false); } catch (error) { console.error('Failed to invite member:', error); toast.error('Failed to send invitation'); } }; const handleRespondToJoinRequest = async ( requestId: string, action: 'approve' | 'reject' ) => { try { await respondToJoinRequest({ requestId, action }); toast.success( action === 'approve' ? 'Request approved!' : 'Request rejected' ); } catch (error) { console.error('Failed to respond to join request:', error); toast.error('Failed to process request'); } }; const handleLeaveTeam = async () => { if (!teamId) return; try { await leaveTeam(teamId); toast.success('You have left the team'); navigate('/dashboard'); } catch (error) { console.error('Failed to leave team:', error); toast.error('Failed to leave team'); } }; const handleDeleteTeam = async () => { if (!teamId) return; try { await deleteTeam(teamId); toast.success('Team deleted successfully'); navigate('/dashboard'); } catch (error: any) { console.error('Failed to delete team:', error); toast.error(error?.message || 'Failed to delete team'); } }; if (isLoadingTeam || isLoadingMembers) { return (
{/* Skeleton Header */}
{/* Main Content Skeleton */}
{/* Sidebar Skeleton */}
{[1, 2, 3].map((i) => (
))}
{/* Loading Overlay */}

Loading team data...

); } if (!team) { return (

Team not found

); } return (
{/* Loading overlay while images are loading */} {!imagesLoaded && totalImagesToLoad > 0 && (

Loading images...

{imageLoadCount} / {totalImagesToLoad}

)} {/* Header with Banner */}
{team.banner && (
{team.name}
)}
{team.logo && (
{!imagesLoaded && (
)} {team.name}
)}

{team.name}

{team.city}

{members.length}{' '} {members.length === 1 ? 'Member' : 'Members'} {team.visibility === 'public' ? 'Public' : 'Private'} {isLeader && ( Team Leader )}
{/* Main Content */}
{/* Team Description */}

About Team

{team.description}

{/* Team Actions - Only for Leader */} {isLeader && (

Team Management

{/* Hide invite/join management after submission or deadline */} {!team.has_submission && !isTeamFeaturesClosed && ( <> )} {!team.has_submission && !isTeamFeaturesClosed && ( )} {team.has_submission ? ( ) : ( )}
{!team.has_submission && !canInvite && members.length >= MAX_TEAM_MEMBERS && (

Maximum team size reached ({MAX_TEAM_MEMBERS} members)

)} {/* Danger Zone - Only show when leader is alone, no submission, and features not closed */} {members.length === 1 && !team.has_submission && !isTeamFeaturesClosed && (

Danger Zone

)}
)} {/* Quick Actions for Members (non-leaders) */} {!isLeader && isMember && (

Quick Actions

{team.has_submission && ( )} {!team.has_submission && !isTeamFeaturesClosed && ( )}
)} {/* Warning for single member teams */} {members.length === 1 && !team.has_submission && isMember && (
⚠️

Team Needs More Members

Your team needs at least 2 members to submit a project. Invite someone or wait for join requests!

)} {/* Submission Status - Only show to members */} {team.has_submission && isMember && (

Project Submitted

Your team has successfully submitted a project

)}
{/* Sidebar */}
{/* Team Leader */}

Team Leader

{team.leader && ( )}
{/* Team Members */}

Members ({members.length})

{members.map((member: any) => ( ))}
{/* Invite Member Modal */} {showInviteModal && (

Invite Team Member

Send an invitation to join your team. The invited member will see the invitation on their dashboard after logging in.

Important: The email you enter must match the GitHub email address the member uses to sign in.

setInviteEmail(e.target.value)} placeholder="Enter email address..." className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500" required />

Current members: {members.length}/{MAX_TEAM_MEMBERS}

)} {/* Join Requests Modal */} {showJoinRequestsModal && (

Join Requests ({pendingJoinRequests.length})

{pendingJoinRequests.length === 0 ? (

No pending join requests

When users request to join your team, they'll appear here

) : (
{pendingJoinRequests.map((request: any) => (
{request.user?.avatar ? ( {request.user.fullname} ) : (
)}

{request.user?.fullname || 'Unknown User'}

{request.user?.email}

{request.message && (

Message: {request.message}

)}

Requested{' '} {new Date(request.created_at).toLocaleDateString()}

{members.length >= MAX_TEAM_MEMBERS && (

Team is full ({MAX_TEAM_MEMBERS}/{MAX_TEAM_MEMBERS}{' '} members). Remove a member before accepting new requests.

)}
))}
)}
)} {/* Leave Team Confirmation Modal */} {showLeaveModal && (

Leave Team?

Are you sure you want to leave {team?.name}?

Warning: If you leave, you will need to request to join again or be re-invited by the team leader.

)} {/* Delete Team Confirmation Modal */} {showDeleteModal && (

Delete Team?

This action is permanent and cannot be undone.

Warning: All team data, chat messages, and submissions will be permanently deleted.

setDeleteConfirmText(e.target.value)} placeholder="Type team name here" className="w-full px-4 py-2.5 border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder:text-gray-400 dark:placeholder:text-gray-500" autoComplete="off" />
)}
); }; export default TeamDashboardPage;