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.logo ? (

) : (
)}
{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
)}
);
};