Files
imphnen-frontend-service/libs/service/src/hooks/auth/index.ts
T
Maulana SodiqinandClaude fa30849d03 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>
2025-11-28 17:40:34 +07:00

349 lines
8.5 KiB
TypeScript

import { useMutation, useQuery } from '@tanstack/react-query';
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
import { useAuthStore } from './use-auth-store';
export * from './use-auth-store';
// Types matching backend response
interface TokenInfo {
access_token: string;
refresh_token: string;
}
interface User {
id: string;
email: string;
fullname: string;
phone_number?: string;
avatar?: string;
birthdate?: string;
gender?: string;
is_active: boolean;
location?: string;
bio?: string;
skills?: string[];
role_id?: string;
created_at: string;
updated_at?: string;
}
interface AuthResponse {
token: TokenInfo;
user: User;
}
interface SessionResponse {
user: User;
}
interface MessageResponse {
message: string;
}
// Login request type
interface LoginRequest {
email: string;
password: string;
}
// Signup request type
interface SignupRequest {
email: string;
password: string;
fullname: string;
}
// GitHub auth request type
interface GitHubAuthRequest {
code: string;
}
// Forgot password request type
interface ForgotPasswordRequest {
email: string;
}
// Reset password request type
interface ResetPasswordRequest {
access_token: string;
new_password: string;
}
// Backend API-based auth hooks
// Email/Password Login
export const useLogin = () => {
const { setSession } = useAuthStore();
return useMutation({
mutationFn: async (data: LoginRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<AuthResponse>>(
'/auth/login',
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: '',
},
},
});
},
});
};
// Email/Password Signup - returns message only (user needs to activate via email)
export const useSignup = () => {
return useMutation({
mutationFn: async (data: SignupRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<MessageResponse>>(
'/auth/signup',
data
);
return response.data.data;
},
});
};
// GitHub OAuth - exchange code for token
export const useGitHubCallback = () => {
const { setSession } = useAuthStore();
return useMutation({
mutationFn: async (data: GitHubAuthRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<AuthResponse>>(
'/auth/github',
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: '',
},
},
});
},
});
};
// Get current session (protected)
export const useSession = () => {
const { session } = useAuthStore();
return useQuery({
queryKey: ['auth-session'],
queryFn: async () => {
const response = await hackathonApi.get<HackathonApiResponse<SessionResponse>>(
'/auth/session'
);
return response.data.data;
},
enabled: !!session?.token,
});
};
// Forgot password
export const useForgotPassword = () => {
return useMutation({
mutationFn: async (data: ForgotPasswordRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<MessageResponse>>(
'/auth/forgot-password',
data
);
return response.data.data;
},
});
};
// Reset password
export const useResetPassword = () => {
return useMutation({
mutationFn: async (data: ResetPasswordRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<MessageResponse>>(
'/auth/reset-password',
data
);
return response.data.data;
},
});
};
// Sign out (clears local session)
export const useSignOut = () => {
const { clearSession } = useAuthStore();
return useMutation({
mutationFn: async () => {
// No backend call needed - just clear local session
clearSession();
return { success: true };
},
});
};
// GitHub OAuth URL helper
// The frontend needs to redirect to GitHub with the client_id
// After GitHub redirects back with a code, use useGitHubCallback
export const getGitHubOAuthUrl = (clientId: string, redirectUri: string) => {
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope: 'read:user user:email',
});
return `https://github.com/login/oauth/authorize?${params.toString()}`;
};
// Backward compatibility hooks - these wrap the new backend API
// GitHub OAuth hook (backward compatible)
export const useGitHubAuth = () => {
const signInWithGitHub = async () => {
// Get GitHub client ID from environment
const clientId = import.meta.env.VITE_GITHUB_CLIENT_ID || '';
if (!clientId) {
throw new Error('GitHub Client ID not configured. Set VITE_GITHUB_CLIENT_ID environment variable.');
}
const redirectUri = `${globalThis.location.origin}/auth/callback`;
const url = getGitHubOAuthUrl(clientId, redirectUri);
return { url };
};
return {
signInWithGitHub,
};
};
// Email/Password auth hook (backward compatible)
export const useEmailAuth = () => {
const loginMutation = useLogin();
const signupMutation = useSignup();
const { clearSession } = useAuthStore();
const signInWithEmail = async (email: string, password: string) => {
const result = await loginMutation.mutateAsync({ email, password });
return {
user: result.user,
session: {
access_token: result.token.access_token,
refresh_token: result.token.refresh_token,
},
};
};
const signUpWithEmail = async (email: string, password: string, fullname: string) => {
const result = await signupMutation.mutateAsync({ email, password, fullname });
return {
user: result.user,
session: {
access_token: result.token.access_token,
refresh_token: result.token.refresh_token,
},
};
};
const signOut = async () => {
clearSession();
};
return {
signInWithEmail,
signUpWithEmail,
signOut,
};
};
// Legacy hooks for old API compatibility (deprecated)
/** @deprecated Use useLogin instead */
export const usePostLogin = () => {
return useMutation({
mutationFn: async (data: LoginRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<AuthResponse>>(
'/auth/login',
data
);
return { data: response.data.data };
},
});
};
/** @deprecated Use useSignup instead */
export const usePostRegister = () => {
return useMutation({
mutationFn: async (data: SignupRequest) => {
const response = await hackathonApi.post<HackathonApiResponse<AuthResponse>>(
'/auth/signup',
data
);
return { data: response.data.data };
},
});
};
/** @deprecated Not needed with new backend */
export const usePostVerifyEmail = () => {
return useMutation({
mutationFn: async () => {
throw new Error('Email verification not required with new backend');
},
});
};
/** @deprecated Not needed with new backend */
export const usePostSendOtp = () => {
return useMutation({
mutationFn: async () => {
throw new Error('OTP not required with new backend');
},
});
};
/** @deprecated Use useGitHubCallback instead */
export const useGoogleCallback = () => {
return useMutation({
mutationFn: async () => {
throw new Error('Google OAuth not supported. Use GitHub OAuth instead.');
},
});
};