feat(hackathon): improve auth flow and UX

- Add email activation requirement for signup (no auto-login)
- Add form validation with React Hook Form and Zod on signup page
- Update callback page to handle email confirmation and password reset redirects
- Fix token format in API interceptor (use access_token)
- Fix middleware to use SessionToken for auth check
- Add infinite scroll with IntersectionObserver on browse teams page
- Replace all internal <a href> with <Link> components
- Add useInfiniteTeams hook for paginated team browsing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Maulana Sodiqin
2025-11-28 17:40:34 +07:00
co-authored by Claude
parent f8d592d811
commit fa30849d03
10 changed files with 363 additions and 181 deletions
+34 -1
View File
@@ -1,4 +1,4 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
import { useAuthStore } from '../auth';
import type {
@@ -131,6 +131,39 @@ export const useTeams = (params?: {
});
};
// Infinite scroll teams hook
const TEAMS_PAGE_SIZE = 12;
export const useInfiniteTeams = (params?: {
city?: string;
visibility?: string;
search?: string;
}) => {
return useInfiniteQuery({
queryKey: [...teamKeys.lists(), 'infinite', params],
queryFn: async ({ pageParam = 1 }) => {
const queryParams = new URLSearchParams();
queryParams.append('page', String(pageParam));
queryParams.append('limit', String(TEAMS_PAGE_SIZE));
if (params?.search) queryParams.append('search', params.search);
if (params?.city) queryParams.append('city', params.city);
if (params?.visibility) queryParams.append('visibility', params.visibility);
const response = await hackathonApi.get<HackathonApiResponse<Team[]>>(
`/teams/browse?${queryParams.toString()}`
);
const teams = response.data.data || [];
return {
data: teams,
nextPage: teams.length === TEAMS_PAGE_SIZE ? pageParam + 1 : undefined,
};
},
initialPageParam: 1,
getNextPageParam: (lastPage) => lastPage.nextPage,
});
};
export const useTeamById = (teamId: string, enabled = true) => {
return useQuery({
queryKey: teamKeys.detail(teamId),