import { FC, ReactElement, useState, useEffect, useRef, useCallback } from 'react'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { Link, useNavigate } from 'react-router'; import { useInfiniteTeams, useJoinTeam, useMyTeams, ETeamVisibility, joinTeamSchema, TJoinTeamForm, } from '@imphnen-frontend-service/service'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { CitySelect } from '../../../components/city-select'; import { Icon } from '@iconify/react'; const BrowseTeamsPage: FC = (): ReactElement => { const navigate = useNavigate(); const [searchTerm, setSearchTerm] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); const [selectedCity, setSelectedCity] = useState(''); const [selectedTeamId, setSelectedTeamId] = useState(null); const [showJoinModal, setShowJoinModal] = useState(false); // Ref for intersection observer const loadMoreRef = useRef(null); // Debounce search term useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchTerm); }, 300); return () => clearTimeout(timer); }, [searchTerm]); const { data: teamsData, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage, } = useInfiniteTeams({ search: debouncedSearch, city: selectedCity || undefined, visibility: ETeamVisibility.PUBLIC, }); const { data: myTeamsData } = useMyTeams(); const { mutateAsync: joinTeam, isPending: isJoining } = useJoinTeam(); const form = useForm({ resolver: zodResolver(joinTeamSchema), mode: 'all', }); // Flatten pages into single array const teams = teamsData?.pages.flatMap((page) => page.data) || []; const myTeams = myTeamsData?.data || []; // Intersection Observer callback const handleObserver = useCallback( (entries: IntersectionObserverEntry[]) => { const [target] = entries; if (target.isIntersecting && hasNextPage && !isFetchingNextPage) { fetchNextPage(); } }, [hasNextPage, isFetchingNextPage, fetchNextPage] ); // Set up intersection observer useEffect(() => { const element = loadMoreRef.current; if (!element) return; const observer = new IntersectionObserver(handleObserver, { root: null, rootMargin: '100px', threshold: 0, }); observer.observe(element); return () => observer.disconnect(); }, [handleObserver]); // Helper function to check if user is a member of a team const isMyTeam = (teamId: string) => { return myTeams.some((team: any) => team.id === teamId); }; const handleJoinRequest = (teamId: string) => { setSelectedTeamId(teamId); setShowJoinModal(true); }; const onSubmit = form.handleSubmit(async (data) => { if (!selectedTeamId) return; try { await joinTeam({ teamId: selectedTeamId, data }); setShowJoinModal(false); form.reset(); setSelectedTeamId(null); } catch (error) { console.error('Failed to send join request:', error); } }); return (
{/* Header */}

Browse Teams

Find and join teams looking for members

{/* Filters */}
setSearchTerm(e.target.value)} className="w-full h-[42px] px-3 border border-gray-300 dark:border-gray-700 rounded-md 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" />
{/* Teams List */} {isLoading ? (

Loading teams...

) : teams.length === 0 ? (

No teams found

Try adjusting your filters

) : ( <>
{teams.map((team) => (
{team.name}
{team.logo ? ( {team.name} ) : (
)}

{team.name}

{team.has_submission && ( Submitted )}

{' '} {team.city}

{' '} {team.member_count || 0} members

{team.description}

{isMyTeam(team.id) ? ( ) : ( <> {myTeams.length === 0 && (team.member_count || 0) < 5 && !team.has_submission && ( )} )}
))}
{/* Intersection Observer Sentinel */}
{isFetchingNextPage && (
Loading more teams...
)} {!hasNextPage && teams.length > 0 && (

No more teams to load

)}
)}
{/* Join Request Modal */} {showJoinModal && (

Request to Join Team

Send a message to the team leader explaining why you want to join