-
- {team.logo ? (
-

- ) : (
-
-
-
-
-
- )}
-
-
- {team.name}
-
-
-
- {' '}
- {team.city}
-
-
- {' '}
- {team.members?.length || 0} members
-
+ <>
+
+ {teams.map((team) => (
+
+

+
+
+ {team.logo ? (
+

+ ) : (
+
+
+
+
+
+ )}
+
+
+ {team.name}
+
+
+
+ {' '}
+ {team.city}
+
+
+ {' '}
+ {team.members?.length || 0} members
+
+
-
-
- {team.description}
-
-
- {isMyTeam(team.id) ? (
-
- ) : (
- <>
- {myTeams.length === 0 &&
- (team.members?.length || 0) < 5 && (
-
- )}
+
+ {team.description}
+
+
+ {isMyTeam(team.id) ? (
- >
- )}
+ ) : (
+ <>
+ {myTeams.length === 0 &&
+ (team.members?.length || 0) < 5 && (
+
+ )}
+
+ >
+ )}
+
-
- ))}
-
+ ))}
+
+
+ {/* Intersection Observer Sentinel */}
+
+ {isFetchingNextPage && (
+
+
+ Loading more teams...
+
+ )}
+ {!hasNextPage && teams.length > 0 && (
+
+ No more teams to load
+
+ )}
+
+ >
)}
diff --git a/apps/hackathon/src/middleware.ts b/apps/hackathon/src/middleware.ts
index 010770d..f77c580 100644
--- a/apps/hackathon/src/middleware.ts
+++ b/apps/hackathon/src/middleware.ts
@@ -1,5 +1,5 @@
import { SessionUser } from '@imphnen-frontend-service/utils';
-import { hackathonApi } from '@imphnen-frontend-service/service';
+import { hackathonApi, SessionToken } from '@imphnen-frontend-service/service';
import { LoaderFunctionArgs, redirect } from 'react-router';
const mappingPublicRoutes = [
@@ -37,9 +37,10 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
const url = new URL(request.url);
const pathname = url.pathname;
- // Get session from local storage (via SessionUser)
- const session = SessionUser.get();
- const isAuthenticated = !!session?.token?.access_token;
+ // Get token from cookies and user from local storage
+ const tokenData = SessionToken.get();
+ const user = SessionUser.get();
+ const isAuthenticated = !!tokenData?.token?.access_token;
// Allow to access the hackathon pages without authentication
if (mappingPublicPrefixRoutes.some((prefix) => pathname.startsWith(prefix))) {
@@ -71,7 +72,7 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
// Skip onboarding check for onboarding routes themselves
if (!mappingOnboardingRoutes.includes(pathname)) {
try {
- const userId = session?.user?.id;
+ const userId = user?.id;
if (!userId) {
return redirect('/auth/login');
}
@@ -85,8 +86,8 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
if (cached && (now - cached.timestamp) < CACHE_DURATION) {
hasLocation = cached.hasLocation;
} else {
- // First check session data (faster)
- if (session?.user?.location) {
+ // First check user data (faster)
+ if (user?.location) {
hasLocation = true;
} else {
// Fetch from backend API
@@ -94,8 +95,8 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
const response = await hackathonApi.get('/users/me');
hasLocation = !!response.data?.data?.location;
} catch {
- // If API fails, check session data as fallback
- hasLocation = !!session?.user?.location;
+ // If API fails, check user data as fallback
+ hasLocation = !!user?.location;
}
}
@@ -113,9 +114,9 @@ export const middleware = async ({ request }: LoaderFunctionArgs) => {
}
}
- // Check route permissions using user data from session
+ // Check route permissions using user data
const userPermissions =
- session?.user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
+ user?.role?.permissions?.map?.((perm) => perm?.name) ?? [];
const matchedRoute = mappingRoutePermissions.find(
(route) => route.path === pathname
diff --git a/libs/service/src/api/hackathon.ts b/libs/service/src/api/hackathon.ts
index 263ae31..9b89a7d 100644
--- a/libs/service/src/api/hackathon.ts
+++ b/libs/service/src/api/hackathon.ts
@@ -16,8 +16,8 @@ export const hackathonApi = axios.create({
hackathonApi.interceptors.request.use(
(config) => {
const { session } = useAuthStore.getState();
- if (session?.token) {
- config.headers.Authorization = `Bearer ${session.token}`;
+ if (session?.token?.access_token) {
+ config.headers.Authorization = `Bearer ${session.token.access_token}`;
}
return config;
},
diff --git a/libs/service/src/hooks/auth/index.ts b/libs/service/src/hooks/auth/index.ts
index fe4a832..ff8e87c 100644
--- a/libs/service/src/hooks/auth/index.ts
+++ b/libs/service/src/hooks/auth/index.ts
@@ -111,43 +111,16 @@ export const useLogin = () => {
});
};
-// Email/Password Signup
+// Email/Password Signup - returns message only (user needs to activate via email)
export const useSignup = () => {
- const { setSession } = useAuthStore();
-
return useMutation({
mutationFn: async (data: SignupRequest) => {
- const response = await hackathonApi.post
>(
+ const response = await hackathonApi.post>(
'/auth/signup',
data
);
return response.data.data;
},
- onSuccess: (data) => {
- setSession({
- token: data.token,
- user: {
- id: data.user.id,
- email: data.user.email,
- fullname: data.user.fullname,
- phone_number: data.user.phone_number || '',
- avatar: data.user.avatar || '',
- birthdate: data.user.birthdate || '',
- gender: data.user.gender || '',
- is_active: data.user.is_active,
- location: data.user.location,
- bio: data.user.bio,
- skills: data.user.skills,
- role: {
- id: '',
- name: 'user',
- permissions: [],
- created_at: '',
- updated_at: '',
- },
- },
- });
- },
});
};
diff --git a/libs/service/src/hooks/teams/index.ts b/libs/service/src/hooks/teams/index.ts
index a2d59bc..1506856 100644
--- a/libs/service/src/hooks/teams/index.ts
+++ b/libs/service/src/hooks/teams/index.ts
@@ -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>(
+ `/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),