diff --git a/apps/hackathon/src/app/teams/browse/page.tsx b/apps/hackathon/src/app/teams/browse/page.tsx index 223da24..08382cc 100644 --- a/apps/hackathon/src/app/teams/browse/page.tsx +++ b/apps/hackathon/src/app/teams/browse/page.tsx @@ -1,6 +1,6 @@ -import { FC, ReactElement, useState, useEffect } from 'react'; +import { FC, ReactElement, useState, useEffect, useCallback } from 'react'; import { Button } from '@imphnen-frontend-service/ui/atoms'; -import { Link, useNavigate } from 'react-router'; +import { Link, useNavigate, useSearchParams } from 'react-router'; import { useTeams, useJoinTeam, @@ -44,26 +44,94 @@ const TeamCardSkeleton: FC = () => ( const BrowseTeamsPage: FC = (): ReactElement => { const navigate = useNavigate(); - const [searchTerm, setSearchTerm] = useState(''); - const [debouncedSearch, setDebouncedSearch] = useState(''); - const [selectedCity, setSelectedCity] = useState(''); + const [searchParams, setSearchParams] = useSearchParams(); + + // Initialize state from URL params + 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 [searchTerm, setSearchTerm] = useState(initialSearch); + const [debouncedSearch, setDebouncedSearch] = useState(initialSearch); + const [selectedCity, setSelectedCity] = useState(initialCity); const [selectedTeamId, setSelectedTeamId] = useState(null); const [showJoinModal, setShowJoinModal] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const [perPage, setPerPage] = useState(DEFAULT_PER_PAGE); + const [currentPage, setCurrentPage] = useState( + PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPage : 1 + ); + const [perPage, setPerPage] = useState( + PER_PAGE_OPTIONS.includes(initialPerPage) ? initialPerPage : DEFAULT_PER_PAGE + ); + + // Update URL when pagination state changes + const updateUrlParams = useCallback( + (params: { + page?: number; + per_page?: number; + search?: string; + city?: 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); + } + } + + setSearchParams(newParams, { replace: true }); + }, + [searchParams, setSearchParams] + ); // Debounce search term and reset page useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchTerm); - setCurrentPage(1); // Reset to first page on search + if (searchTerm !== debouncedSearch) { + setCurrentPage(1); + updateUrlParams({ search: searchTerm, page: 1 }); + } }, 300); return () => clearTimeout(timer); }, [searchTerm]); - // Reset page when city filter changes + // Update URL when city filter changes useEffect(() => { - setCurrentPage(1); + if (selectedCity !== initialCity) { + setCurrentPage(1); + updateUrlParams({ city: selectedCity, page: 1 }); + } }, [selectedCity]); const { @@ -329,7 +397,11 @@ const BrowseTeamsPage: FC = (): ReactElement => {