import { FC, ReactElement, useState, useEffect, useCallback } from 'react'; import { Button } from '@imphnen-frontend-service/ui/atoms'; import { createFileRoute, Link, useNavigate } from '@tanstack/react-router'; import { useTeams, 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'; export const Route = createFileRoute('/_authenticated/teams/browse')({ component: BrowseTeamsPage, }) const DEFAULT_PER_PAGE = 12; const PER_PAGE_OPTIONS = [6, 12, 24, 48]; const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z'); const MEMBER_FILTER_OPTIONS = [ { label: 'All Teams', value: '', minMembers: undefined, maxMembers: undefined }, { label: 'Looking for Members (1-4)', value: 'looking', minMembers: 1, maxMembers: 4 }, { label: '1 Member', value: '1', minMembers: 1, maxMembers: 1 }, { label: '2 Members', value: '2', minMembers: 2, maxMembers: 2 }, { label: '3 Members', value: '3', minMembers: 3, maxMembers: 3 }, { label: '4 Members', value: '4', minMembers: 4, maxMembers: 4 }, { label: '5 Members (Full)', value: '5', minMembers: 5, maxMembers: 5 }, ]; const SUBMISSION_FILTER_OPTIONS = [ { label: 'All Teams', value: '' }, { label: 'Submitted', value: 'true' }, { label: 'Not Submitted', value: 'false' }, ]; const TeamCardSkeleton: FC = () => (
); const BrowseTeamsPage: FC = (): ReactElement => { const navigate = useNavigate(); const searchParams = new URLSearchParams(globalThis.location.search); const setSearchParams = (newParams: URLSearchParams, opts?: { replace?: boolean }) => { const url = new URL(globalThis.location.href); url.search = newParams.toString(); if (opts?.replace) { globalThis.history.replaceState(null, '', url.toString()); } else { globalThis.history.pushState(null, '', url.toString()); } }; const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE; const initialPage = parseInt(searchParams.get('page') || '1', 10); const initialPerPage = parseInt( searchParams.get('per_page') || String(DEFAULT_PER_PAGE), 10 ); const initialSearch = searchParams.get('search') || ''; const initialCity = searchParams.get('city') || ''; const initialMembers = searchParams.get('members') || ''; const initialSubmission = searchParams.get('submission') || ''; const [searchTerm, setSearchTerm] = useState(initialSearch); const [debouncedSearch, setDebouncedSearch] = useState(initialSearch); const [selectedCity, setSelectedCity] = useState(initialCity); const [selectedMembers, setSelectedMembers] = useState(initialMembers); const [selectedSubmission, setSelectedSubmission] = useState(initialSubmission); const [selectedTeamId, setSelectedTeamId] = useState(null); const [showJoinModal, setShowJoinModal] = useState(false); const [currentPage, setCurrentPage] = useState( PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPage : 1 ); const [perPage, setPerPage] = useState( PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPerPage : DEFAULT_PER_PAGE ); const updateUrlParams = useCallback( (params: { page?: number; per_page?: number; search?: string; city?: string; members?: string; submission?: string; }) => { const newParams = new URLSearchParams(searchParams); if (params.page !== undefined) { if (params.page === 1) { newParams.delete('page'); } else { newParams.set('page', String(params.page)); } } if (params.per_page !== undefined) { if (params.per_page === DEFAULT_PER_PAGE) { newParams.delete('per_page'); } else { newParams.set('per_page', String(params.per_page)); } } if (params.search !== undefined) { if (params.search === '') { newParams.delete('search'); } else { newParams.set('search', params.search); } } if (params.city !== undefined) { if (params.city === '') { newParams.delete('city'); } else { newParams.set('city', params.city); } } if (params.members !== undefined) { if (params.members === '') { newParams.delete('members'); } else { newParams.set('members', params.members); } } if (params.submission !== undefined) { if (params.submission === '') { newParams.delete('submission'); } else { newParams.set('submission', params.submission); } } setSearchParams(newParams, { replace: true }); }, [searchParams, setSearchParams] ); useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchTerm); if (searchTerm !== debouncedSearch) { setCurrentPage(1); updateUrlParams({ search: searchTerm, page: 1 }); } }, 300); return () => clearTimeout(timer); }, [searchTerm]); useEffect(() => { if (selectedCity !== initialCity) { setCurrentPage(1); updateUrlParams({ city: selectedCity, page: 1 }); } }, [selectedCity]); const memberFilter = MEMBER_FILTER_OPTIONS.find( (opt) => opt.value === selectedMembers ); const hasSubmissionFilter = selectedSubmission === 'true' ? true : selectedSubmission === 'false' ? false : undefined; const { data: teamsData, isLoading, isFetching, } = useTeams({ page: currentPage, limit: perPage, search: debouncedSearch, city: selectedCity || undefined, visibility: ETeamVisibility.PUBLIC, minMembers: memberFilter?.minMembers, maxMembers: memberFilter?.maxMembers, hasSubmission: hasSubmissionFilter, }); const { data: myTeamsData } = useMyTeams(); const { mutateAsync: joinTeam, isPending: isJoining } = useJoinTeam(); const form = useForm({ resolver: zodResolver(joinTeamSchema), mode: 'all', }); const teams = teamsData?.teams || []; const totalPages = teamsData?.totalPages || 1; const total = teamsData?.total || 0; const myTeams = myTeamsData?.data || []; 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); } }); const getPageNumbers = () => { const pages: (number | string)[] = []; const maxVisible = 5; if (totalPages <= maxVisible + 2) { for (let i = 1; i <= totalPages; i++) { pages.push(i); } } else { pages.push(1); if (currentPage > 3) { pages.push('...'); } const start = Math.max(2, currentPage - 1); const end = Math.min(totalPages - 1, currentPage + 1); for (let i = start; i <= end; i++) { pages.push(i); } if (currentPage < totalPages - 2) { pages.push('...'); } pages.push(totalPages); } return pages; }; return (

Browse Teams

Find and join teams looking for members

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" />
{!isLoading && total > 0 && (
Showing {(currentPage - 1) * perPage + 1}- {Math.min(currentPage * perPage, total)} of {total} teams
)} {isLoading || isFetching ? (
{Array.from({ length: perPage }).map((_, index) => ( ))}
) : teams.length === 0 ? (

No teams found

Try adjusting your filters

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

{team.name}

{team.has_submission && ( Submitted )}

{' '} {team.city}

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

{team.description}

{(team.member_count || 0) === 1 && !team.has_submission && (

This team needs at least 2 members to submit

)} {isMyTeam(team.id) ? ( ) : ( <> {myTeams.length === 0 && (team.member_count || 0) < 5 && !team.has_submission && !isTeamFeaturesClosed && ( )} )}
))}
{(totalPages > 1 || total > 6) && (
{totalPages > 1 && (
{getPageNumbers().map((page, index) => typeof page === 'string' ? ( ... ) : ( ) )}
)}
{totalPages > 1 && ( Page {currentPage} of {totalPages} )}
Show:
)} )}
{showJoinModal && (

Request to Join Team

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