feat: integrate all backend APIs into frontend apps
- Fix API service paths to use /v1/iam/, /v1/dimentorin/, /v1/gacha/, /v1/hackathon/ prefixes - Add roles, permissions, events, testimonials, sessions API services and hooks - Rewrite gacha service with full CRUD + roll/claim endpoints - Replace all mock data in backoffice pages with real API calls (accounts, roles, permissions, gacha-roll, dashboard, sessions, users-dimentorin, feedback-review, settings) - Wire dimentorin mentoring list to useMentorList with search + pagination - Wire dimentorin mentor detail page to useMentorById, pass real data to all sections - Wire appointment modal to useBookSession with controlled schedule inputs - Wire gacha app Spin Now to useExecuteGachaRoll, show credits and real items Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
8d2aaf5188
commit
53ad40f3fd
@@ -5,23 +5,28 @@ import type {
|
||||
TAdminSubmissionsResponse,
|
||||
} from '../../types/admin';
|
||||
|
||||
const ADMIN_BASE_URL = '/admin';
|
||||
|
||||
export const getAdminUsers = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
is_admin?: boolean;
|
||||
}) => {
|
||||
const response = await api.get<TAdminUsersResponse>(
|
||||
`${ADMIN_BASE_URL}/users`,
|
||||
{
|
||||
params: {
|
||||
...params,
|
||||
is_admin: params?.is_admin ?? false,
|
||||
},
|
||||
}
|
||||
);
|
||||
const response = await api.get<TAdminUsersResponse>('/v1/hackathon/admin/users', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAdminUserById = async (userId: string) => {
|
||||
const response = await api.get(`/v1/hackathon/admin/users/${userId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAdminUser = async (userId: string) => {
|
||||
const response = await api.delete(`/v1/hackathon/admin/users/${userId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const setAdminUser = async (userId: string, is_admin: boolean) => {
|
||||
const response = await api.post(`/v1/hackathon/admin/users/${userId}/set-admin`, { is_admin });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -30,10 +35,12 @@ export const getAdminTeams = async (params?: {
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
}) => {
|
||||
const response = await api.get<TAdminTeamsResponse>(
|
||||
`${ADMIN_BASE_URL}/teams`,
|
||||
{ params }
|
||||
);
|
||||
const response = await api.get<TAdminTeamsResponse>('/v1/hackathon/admin/teams', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAdminTeam = async (teamId: string) => {
|
||||
const response = await api.delete(`/v1/hackathon/admin/teams/${teamId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -43,9 +50,21 @@ export const getAdminSubmissions = async (params?: {
|
||||
search?: string;
|
||||
status?: string;
|
||||
}) => {
|
||||
const response = await api.get<TAdminSubmissionsResponse>(
|
||||
`${ADMIN_BASE_URL}/submissions`,
|
||||
{ params }
|
||||
);
|
||||
const response = await api.get<TAdminSubmissionsResponse>('/v1/hackathon/admin/submissions', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getAdminWinners = async () => {
|
||||
const response = await api.get('/v1/hackathon/admin/winners');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const setWinner = async (data: { team_id: string; rank: number; prize?: string }) => {
|
||||
const response = await api.post('/v1/hackathon/admin/winners', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeWinner = async (teamId: string) => {
|
||||
const response = await api.delete(`/v1/hackathon/admin/winners/${teamId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -1,80 +1,65 @@
|
||||
import { api, getBaseURL } from '../';
|
||||
import {
|
||||
TLoginRequest,
|
||||
TLoginResponse,
|
||||
TRegisterRequest,
|
||||
TSendOTPRequest,
|
||||
TVerifyEmailRequest,
|
||||
TGoogleCallbackResponse,
|
||||
} from '../../types/auth';
|
||||
import { TResponseMessage } from '../../types/common';
|
||||
import { api } from '../index';
|
||||
import type { TLoginResponse, TRegisterRequest, TSendOTPRequest, TVerifyEmailRequest } from '../../types/auth';
|
||||
import type { TResponseMessage } from '../../types/common';
|
||||
|
||||
export const postLogin = async (
|
||||
payload: TLoginRequest
|
||||
): Promise<TLoginResponse> => {
|
||||
export type TLoginRequest = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type TForgotPasswordRequest = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
export type TNewPasswordRequest = {
|
||||
token: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type TRefreshTokenRequest = {
|
||||
refresh_token: string;
|
||||
};
|
||||
|
||||
export const postLogin = async (payload: TLoginRequest): Promise<TLoginResponse> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/login', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postLoginMentor = async (payload: TLoginRequest): Promise<TLoginResponse> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/login-mentor', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postRegister = async (payload: TRegisterRequest): Promise<TResponseMessage> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/register', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postVerifyEmail = async (payload: TVerifyEmailRequest): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/login',
|
||||
data: payload,
|
||||
url: '/v1/iam/auth/verify-email',
|
||||
data: { otp: parseInt(payload.otp), email: payload.email },
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postRegister = async (
|
||||
payload: TRegisterRequest
|
||||
): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/register',
|
||||
data: payload,
|
||||
});
|
||||
export const postSendOtp = async (payload: TSendOTPRequest): Promise<TResponseMessage> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/send-otp', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postVerifyEmail = async (
|
||||
payload: TVerifyEmailRequest
|
||||
): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/verify-email',
|
||||
data: {otp: parseInt(payload.otp), email: payload.email},
|
||||
});
|
||||
export const postForgotPassword = async (payload: TForgotPasswordRequest): Promise<TResponseMessage> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/forgot', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const postSendOtp = async (
|
||||
payload: TSendOTPRequest
|
||||
): Promise<TResponseMessage> => {
|
||||
const { data } = await api({
|
||||
method: 'POST',
|
||||
url: '/auth/send-otp',
|
||||
data: payload,
|
||||
});
|
||||
export const postNewPassword = async (payload: TNewPasswordRequest): Promise<TResponseMessage> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/new-password', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getGoogleAuthUrl = async (): Promise<string> => {
|
||||
const baseUrl = getBaseURL() || 'http://localhost:8080';
|
||||
return `${baseUrl}/auth/google/login`;
|
||||
};
|
||||
|
||||
export const postGoogleCallback = async (code: string, state: string): Promise<TGoogleCallbackResponse> => {
|
||||
const { data } = await api({
|
||||
method: 'GET',
|
||||
url: `/auth/google/callback?code=${code}&state=${state}`,
|
||||
});
|
||||
return data;
|
||||
};
|
||||
|
||||
export const getGitHubAuthUrl = async (): Promise<string> => {
|
||||
const baseUrl = getBaseURL() || 'http://localhost:8080';
|
||||
return `${baseUrl}/auth/github/login`;
|
||||
};
|
||||
|
||||
export const postGitHubCallback = async (code: string, state: string): Promise<TGoogleCallbackResponse> => {
|
||||
const { data } = await api({
|
||||
method: 'GET',
|
||||
url: `/auth/github/callback?code=${code}&state=${state}`,
|
||||
});
|
||||
export const postRefreshToken = async (payload: TRefreshTokenRequest): Promise<{ access_token: string; refresh_token: string }> => {
|
||||
const { data } = await api({ method: 'POST', url: '/v1/iam/auth/refresh', data: payload });
|
||||
return data;
|
||||
};
|
||||
|
||||
@@ -1,52 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../hooks/auth';
|
||||
|
||||
const BACKOFFICE_API_URL = 'https://api.hackathon.imphnen.dev/api/v1';
|
||||
|
||||
export const backofficeApi = axios.create({
|
||||
baseURL: BACKOFFICE_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
backofficeApi.interceptors.request.use(
|
||||
(config) => {
|
||||
const { session } = useAuthStore.getState();
|
||||
if (session?.token?.access_token) {
|
||||
config.headers.Authorization = `Bearer ${session.token.access_token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(new Error(error.message || 'Request failed'));
|
||||
}
|
||||
);
|
||||
|
||||
backofficeApi.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
const isAuthPage =
|
||||
globalThis.window !== undefined &&
|
||||
globalThis.location.pathname.startsWith('/auth');
|
||||
|
||||
if (!isAuthPage) {
|
||||
useAuthStore.getState().clearSession();
|
||||
if (globalThis.window !== undefined) {
|
||||
globalThis.location.href = '/auth/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const backendMsg = error?.response?.data?.message;
|
||||
if (backendMsg && typeof backendMsg === 'string') {
|
||||
return Promise.reject(new Error(backendMsg));
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(error.message || 'An error occurred'));
|
||||
}
|
||||
);
|
||||
// All API calls now go through the main `api` instance (api.imphnen.dev).
|
||||
// This file re-exports `api` as `backofficeApi` for backward compatibility.
|
||||
export { api as backofficeApi } from './index';
|
||||
|
||||
export interface BackofficeApiResponse<T> {
|
||||
data: T;
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type { TEventsListItem, TEventsDetailItem, TEventCreateRequest, TEventUpdateRequest } from '../../types/events';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export const getEventList = async (params?: TPaginationParams): Promise<TApiPaginated<TEventsListItem>> => {
|
||||
const response = await api.get<TApiPaginated<TEventsListItem>>('/v1/landing/cms/events', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getEventById = async (id: string): Promise<TEventsDetailItem> => {
|
||||
const response = await api.get<ApiResponse<TEventsDetailItem>>(`/v1/landing/cms/events/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createEvent = async (data: TEventCreateRequest): Promise<TEventsDetailItem> => {
|
||||
const response = await api.post<ApiResponse<TEventsDetailItem>>('/v1/landing/cms/events/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateEvent = async (id: string, data: TEventUpdateRequest): Promise<TEventsDetailItem> => {
|
||||
const response = await api.patch<ApiResponse<TEventsDetailItem>>(`/v1/landing/cms/events/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteEvent = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/landing/cms/events/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
@@ -1 +1,87 @@
|
||||
export {};
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type {
|
||||
TGachaItemDto,
|
||||
TGachaItemCreateRequest,
|
||||
TGachaItemUpdateRequest,
|
||||
TGachaRollItemDto,
|
||||
TGachaRollCreateRequest,
|
||||
TGachaCreditDto,
|
||||
TGachaCreditAddRequest,
|
||||
TGachaClaimDetailDto,
|
||||
TGachaClaimCreateRequest,
|
||||
} from '../../types/gacha';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
// ----- Credits -----
|
||||
export const getUserCredits = async (): Promise<TGachaCreditDto> => {
|
||||
const response = await api.get<ApiResponse<TGachaCreditDto>>('/v1/gacha/credits/');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const addCredits = async (data: TGachaCreditAddRequest): Promise<TGachaCreditDto> => {
|
||||
const response = await api.post<ApiResponse<TGachaCreditDto>>('/v1/gacha/credits/add', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const consumeCredit = async (): Promise<{ message: string }> => {
|
||||
const response = await api.post<{ message: string }>('/v1/gacha/credits/consume');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// ----- Items -----
|
||||
export const getGachaItemList = async (params?: TPaginationParams): Promise<TApiPaginated<TGachaItemDto>> => {
|
||||
const response = await api.get<TApiPaginated<TGachaItemDto>>('/v1/gacha/items/', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getGachaItemById = async (id: string): Promise<TGachaItemDto> => {
|
||||
const response = await api.get<ApiResponse<TGachaItemDto>>(`/v1/gacha/items/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createGachaItem = async (data: TGachaItemCreateRequest): Promise<TGachaItemDto> => {
|
||||
const response = await api.post<ApiResponse<TGachaItemDto>>('/v1/gacha/items/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateGachaItem = async (id: string, data: TGachaItemUpdateRequest): Promise<TGachaItemDto> => {
|
||||
const response = await api.put<ApiResponse<TGachaItemDto>>(`/v1/gacha/items/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteGachaItem = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/gacha/items/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// ----- Rolls -----
|
||||
export const getGachaRollById = async (id: string): Promise<TGachaRollItemDto> => {
|
||||
const response = await api.get<ApiResponse<TGachaRollItemDto>>(`/v1/gacha/rolls/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createGachaRoll = async (data: TGachaRollCreateRequest): Promise<TGachaRollItemDto> => {
|
||||
const response = await api.post<ApiResponse<TGachaRollItemDto>>('/v1/gacha/rolls/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const executeGachaRoll = async (): Promise<TGachaRollItemDto> => {
|
||||
const response = await api.post<ApiResponse<TGachaRollItemDto>>('/v1/gacha/rolls/execute');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteGachaRoll = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/gacha/rolls/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// ----- Claims -----
|
||||
export const getGachaClaimById = async (id: string): Promise<TGachaClaimDetailDto> => {
|
||||
const response = await api.get<ApiResponse<TGachaClaimDetailDto>>(`/v1/gacha/claims/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createGachaClaim = async (data: TGachaClaimCreateRequest): Promise<TGachaClaimDetailDto> => {
|
||||
const response = await api.post<ApiResponse<TGachaClaimDetailDto>>('/v1/gacha/claims/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
@@ -1,51 +1,6 @@
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../hooks/auth';
|
||||
|
||||
const HACKATHON_API_URL = 'https://api.hackathon.imphnen.dev/api/v1';
|
||||
|
||||
export const hackathonApi = axios.create({
|
||||
baseURL: HACKATHON_API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
hackathonApi.interceptors.request.use(
|
||||
(config) => {
|
||||
const { session } = useAuthStore.getState();
|
||||
if (session?.token?.access_token) {
|
||||
config.headers.Authorization = `Bearer ${session.token.access_token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(new Error(error.message || 'Request failed'));
|
||||
}
|
||||
);
|
||||
|
||||
hackathonApi.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
if (error.response?.status === 401) {
|
||||
const isAuthPage = globalThis.window !== undefined && globalThis.location.pathname.startsWith('/auth');
|
||||
const isCertificatePage = globalThis.window !== undefined && globalThis.location.pathname.startsWith('/certificate/');
|
||||
|
||||
if (!isAuthPage && !isCertificatePage) {
|
||||
useAuthStore.getState().clearSession();
|
||||
if (globalThis.window !== undefined) {
|
||||
globalThis.location.href = '/auth/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const backendMsg = error?.response?.data?.message;
|
||||
if (backendMsg && typeof backendMsg === 'string') {
|
||||
return Promise.reject(new Error(backendMsg));
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(error.message || 'Request failed'));
|
||||
}
|
||||
);
|
||||
// All API calls now go through the main `api` instance (api.imphnen.dev).
|
||||
// This file re-exports `api` as `hackathonApi` for backward compatibility.
|
||||
export { api as hackathonApi } from './index';
|
||||
|
||||
export interface HackathonApiResponse<T> {
|
||||
data: T;
|
||||
|
||||
@@ -7,6 +7,11 @@ export * from './mentors';
|
||||
export * from './upload';
|
||||
export * from './hackathon';
|
||||
export * from './admin';
|
||||
export * from './roles';
|
||||
export * from './permissions';
|
||||
export * from './events';
|
||||
export * from './testimonials';
|
||||
export * from './sessions';
|
||||
|
||||
export interface ApiResponse<T> {
|
||||
data: T;
|
||||
@@ -140,7 +145,7 @@ async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
||||
}
|
||||
|
||||
async function refreshAccessToken(refreshToken: string) {
|
||||
return axios.post(`${getBaseURL()}/auth/refresh`, {
|
||||
return axios.post(`${getBaseURL()}/v1/iam/auth/refresh`, {
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,34 +1,61 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type {
|
||||
MentorDetailResponseDto,
|
||||
MentorUpdateRequestDto
|
||||
} from '../../types/mentors';
|
||||
import type { MentorDetailResponseDto, MentorUpdateRequestDto } from '../../types/mentors';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export interface MentorService {
|
||||
getMentorMe(): Promise<MentorDetailResponseDto>;
|
||||
getMentorById(id: string): Promise<MentorDetailResponseDto>;
|
||||
updateMentorMe(data: MentorUpdateRequestDto): Promise<MentorDetailResponseDto>;
|
||||
updateMentorById(id: string, data: MentorUpdateRequestDto): Promise<MentorDetailResponseDto>;
|
||||
}
|
||||
|
||||
export const mentorService: MentorService = {
|
||||
async getMentorMe() {
|
||||
const response = await api.get<ApiResponse<MentorDetailResponseDto>>('/mentors/me');
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async getMentorById(id: string) {
|
||||
const response = await api.get<ApiResponse<MentorDetailResponseDto>>(`/mentors/detail/${id}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async updateMentorMe(data: MentorUpdateRequestDto) {
|
||||
const response = await api.put<ApiResponse<MentorDetailResponseDto>>('/mentors/update/me', data);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async updateMentorById(id: string, data: MentorUpdateRequestDto) {
|
||||
const response = await api.put<ApiResponse<MentorDetailResponseDto>>(`/mentors/update/${id}`, data);
|
||||
return response.data.data;
|
||||
},
|
||||
export type MentorRegisterRequest = MentorUpdateRequestDto & {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export const registerMentor = async (data: MentorRegisterRequest): Promise<MentorDetailResponseDto> => {
|
||||
const response = await api.post<ApiResponse<MentorDetailResponseDto>>('/v1/dimentorin/mentors/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getMentorList = async (params?: TPaginationParams): Promise<TApiPaginated<MentorDetailResponseDto>> => {
|
||||
const response = await api.get<TApiPaginated<MentorDetailResponseDto>>('/v1/dimentorin/mentors', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getMentorMe = async (): Promise<MentorDetailResponseDto> => {
|
||||
const response = await api.get<ApiResponse<MentorDetailResponseDto>>('/v1/dimentorin/mentors/me');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getMentorStatus = async (): Promise<{ status: string }> => {
|
||||
const response = await api.get<ApiResponse<{ status: string }>>('/v1/dimentorin/mentors/me/status');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getMentorById = async (id: string): Promise<MentorDetailResponseDto> => {
|
||||
const response = await api.get<ApiResponse<MentorDetailResponseDto>>(`/v1/dimentorin/mentors/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateMentorMe = async (data: MentorUpdateRequestDto): Promise<MentorDetailResponseDto> => {
|
||||
const response = await api.put<ApiResponse<MentorDetailResponseDto>>('/v1/dimentorin/mentors/me/update', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateMentorById = async (id: string, data: MentorUpdateRequestDto): Promise<MentorDetailResponseDto> => {
|
||||
const response = await api.put<ApiResponse<MentorDetailResponseDto>>(`/v1/dimentorin/mentors/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteMentor = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/dimentorin/mentors/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const verifyMentor = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.put<{ message: string }>(`/v1/dimentorin/mentors/verify/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Legacy service object for backward compatibility
|
||||
export const mentorService = {
|
||||
getMentorMe,
|
||||
getMentorById,
|
||||
updateMentorMe,
|
||||
updateMentorById: (id: string, data: MentorUpdateRequestDto) => updateMentorById(id, data),
|
||||
};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type { TPermissionItem, TPermissionCreateRequest, TPermissionUpdateRequest } from '../../types/permissions';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export const getPermissionList = async (params?: TPaginationParams): Promise<TApiPaginated<TPermissionItem>> => {
|
||||
const response = await api.get<TApiPaginated<TPermissionItem>>('/v1/iam/permissions', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getPermissionById = async (id: string): Promise<TPermissionItem> => {
|
||||
const response = await api.get<ApiResponse<TPermissionItem>>(`/v1/iam/permissions/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createPermission = async (data: TPermissionCreateRequest): Promise<TPermissionItem> => {
|
||||
const response = await api.post<ApiResponse<TPermissionItem>>('/v1/iam/permissions/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updatePermission = async (id: string, data: TPermissionUpdateRequest): Promise<TPermissionItem> => {
|
||||
const response = await api.put<ApiResponse<TPermissionItem>>(`/v1/iam/permissions/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deletePermission = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/iam/permissions/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type { TRolesListItem, TRoleDetailItem, TRoleCreateRequest, TRoleUpdateRequest } from '../../types/roles';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export const getRoleList = async (params?: TPaginationParams): Promise<TApiPaginated<TRolesListItem>> => {
|
||||
const response = await api.get<TApiPaginated<TRolesListItem>>('/v1/iam/roles', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getRoleById = async (id: string): Promise<TRoleDetailItem> => {
|
||||
const response = await api.get<ApiResponse<TRoleDetailItem>>(`/v1/iam/roles/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createRole = async (data: TRoleCreateRequest): Promise<TRoleDetailItem> => {
|
||||
const response = await api.post<ApiResponse<TRoleDetailItem>>('/v1/iam/roles/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateRole = async (id: string, data: TRoleUpdateRequest): Promise<TRoleDetailItem> => {
|
||||
const response = await api.put<ApiResponse<TRoleDetailItem>>(`/v1/iam/roles/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteRole = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/iam/roles/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type {
|
||||
TBookSessionRequest,
|
||||
TBookSessionResponse,
|
||||
TUpdateSessionStatusRequest,
|
||||
TUpdateSessionStatusResponse,
|
||||
TSessionFeedbackRequest,
|
||||
TSessionFeedbackResponse,
|
||||
TSessionListResponse,
|
||||
TMentorAvailability,
|
||||
} from '../../types/sessions';
|
||||
|
||||
export const getMentorAvailability = async (mentorId: string): Promise<TMentorAvailability> => {
|
||||
const response = await api.get<ApiResponse<TMentorAvailability>>(
|
||||
`/v1/dimentorin/mentors/${mentorId}/availability`
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const bookSession = async (mentorId: string, data: TBookSessionRequest): Promise<TBookSessionResponse> => {
|
||||
const response = await api.post<ApiResponse<TBookSessionResponse>>(
|
||||
`/v1/dimentorin/mentors/${mentorId}/sessions/create`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getMentorSessions = async (mentorId: string, params?: { status?: string }): Promise<TSessionListResponse> => {
|
||||
const response = await api.get<ApiResponse<TSessionListResponse>>(
|
||||
`/v1/dimentorin/mentors/${mentorId}/sessions`,
|
||||
{ params }
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getMySessions = async (params?: { status?: string }): Promise<TSessionListResponse> => {
|
||||
const response = await api.get<ApiResponse<TSessionListResponse>>(
|
||||
'/v1/dimentorin/sessions/me',
|
||||
{ params }
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateSessionStatus = async (
|
||||
id: string,
|
||||
data: TUpdateSessionStatusRequest
|
||||
): Promise<TUpdateSessionStatusResponse> => {
|
||||
const response = await api.put<ApiResponse<TUpdateSessionStatusResponse>>(
|
||||
`/v1/dimentorin/sessions/update/${id}/status`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const submitFeedback = async (
|
||||
id: string,
|
||||
data: TSessionFeedbackRequest
|
||||
): Promise<TSessionFeedbackResponse> => {
|
||||
const response = await api.post<ApiResponse<TSessionFeedbackResponse>>(
|
||||
`/v1/dimentorin/sessions/${id}/feedback/create`,
|
||||
data
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import type {
|
||||
TUpdateTeamRequest,
|
||||
TInviteMemberRequest,
|
||||
TJoinTeamRequest,
|
||||
TManageMemberRequest,
|
||||
TSubmitProjectRequest,
|
||||
TTeamListResponse,
|
||||
TTeamDetailResponse,
|
||||
@@ -14,8 +13,6 @@ import type {
|
||||
TProjectSubmissionResponse,
|
||||
} from '../../types/teams';
|
||||
|
||||
const TEAMS_BASE_URL = '/teams';
|
||||
|
||||
export const getTeams = async (params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
@@ -23,86 +20,93 @@ export const getTeams = async (params?: {
|
||||
visibility?: string;
|
||||
search?: string;
|
||||
}) => {
|
||||
const response = await api.get<TTeamListResponse>(TEAMS_BASE_URL, { params });
|
||||
const response = await api.get<TTeamListResponse>('/v1/hackathon/teams/browse', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTeamById = async (teamId: string) => {
|
||||
const response = await api.get<TTeamDetailResponse>(`${TEAMS_BASE_URL}/${teamId}`);
|
||||
const response = await api.get<TTeamDetailResponse>(`/v1/hackathon/teams/${teamId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const createTeam = async (data: TCreateTeamRequest) => {
|
||||
const response = await api.post<TTeamDetailResponse>(TEAMS_BASE_URL, data);
|
||||
const response = await api.post<TTeamDetailResponse>('/v1/hackathon/teams', data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateTeam = async (teamId: string, data: TUpdateTeamRequest) => {
|
||||
const response = await api.put<TTeamDetailResponse>(`${TEAMS_BASE_URL}/${teamId}`, data);
|
||||
const response = await api.put<TTeamDetailResponse>(`/v1/hackathon/teams/${teamId}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteTeam = async (teamId: string) => {
|
||||
const response = await api.delete(`/v1/hackathon/teams/${teamId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTeamMembers = async (teamId: string) => {
|
||||
const response = await api.get<TTeamMembersResponse>(`${TEAMS_BASE_URL}/${teamId}/members`);
|
||||
const response = await api.get<TTeamMembersResponse>(`/v1/hackathon/teams/${teamId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const inviteMember = async (teamId: string, data: TInviteMemberRequest) => {
|
||||
const response = await api.post(`${TEAMS_BASE_URL}/${teamId}/invite`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const manageMember = async (teamId: string, userId: string, data: TManageMemberRequest) => {
|
||||
const response = await api.put(`${TEAMS_BASE_URL}/${teamId}/members/${userId}`, data);
|
||||
const response = await api.post(`/v1/hackathon/invitations/teams/${teamId}/invite`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const removeMember = async (teamId: string, userId: string) => {
|
||||
const response = await api.delete(`${TEAMS_BASE_URL}/${teamId}/members/${userId}`);
|
||||
const response = await api.delete(`/v1/hackathon/teams/${teamId}/members/${userId}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const joinTeam = async (teamId: string, data: TJoinTeamRequest) => {
|
||||
const response = await api.post(`${TEAMS_BASE_URL}/${teamId}/join-request`, data);
|
||||
const response = await api.post(`/v1/hackathon/join-requests/teams/${teamId}`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTeamJoinRequests = async (teamId: string) => {
|
||||
const response = await api.get<TTeamJoinRequestsResponse>(`${TEAMS_BASE_URL}/${teamId}/join-requests`);
|
||||
const response = await api.get<TTeamJoinRequestsResponse>(
|
||||
`/v1/hackathon/join-requests/teams/${teamId}/pending`
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const respondToJoinRequest = async (teamId: string, requestId: string, action: 'approve' | 'reject') => {
|
||||
const response = await api.put(`${TEAMS_BASE_URL}/${teamId}/join-requests/${requestId}`, { action });
|
||||
export const respondToJoinRequest = async (requestId: string, action: 'accept' | 'reject') => {
|
||||
const response = await api.post(`/v1/hackathon/join-requests/${requestId}/respond`, { action });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getMyInvitations = async () => {
|
||||
const response = await api.get<TTeamInvitationsResponse>(`${TEAMS_BASE_URL}/invitations/me`);
|
||||
const response = await api.get<TTeamInvitationsResponse>('/v1/hackathon/invitations/my');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const respondToInvitation = async (invitationId: string, action: 'accept' | 'reject') => {
|
||||
const response = await api.put(`${TEAMS_BASE_URL}/invitations/${invitationId}`, { action });
|
||||
const response = await api.post(`/v1/hackathon/invitations/${invitationId}/respond`, { action });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getMyTeams = async () => {
|
||||
const response = await api.get<TTeamListResponse>(`${TEAMS_BASE_URL}/me`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const submitProject = async (teamId: string, data: TSubmitProjectRequest) => {
|
||||
const response = await api.post<TProjectSubmissionResponse>(`${TEAMS_BASE_URL}/${teamId}/submission`, data);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTeamSubmission = async (teamId: string) => {
|
||||
const response = await api.get<TProjectSubmissionResponse>(`${TEAMS_BASE_URL}/${teamId}/submission`);
|
||||
const response = await api.get('/v1/hackathon/teams/my');
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const leaveTeam = async (teamId: string) => {
|
||||
const response = await api.post(`${TEAMS_BASE_URL}/${teamId}/leave`);
|
||||
const response = await api.post(`/v1/hackathon/teams/${teamId}/leave`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const submitProject = async (teamId: string, data: TSubmitProjectRequest) => {
|
||||
const response = await api.post<TProjectSubmissionResponse>(
|
||||
`/v1/hackathon/submissions/teams/${teamId}`,
|
||||
data
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTeamSubmission = async (teamId: string) => {
|
||||
const response = await api.get<TProjectSubmissionResponse>(
|
||||
`/v1/hackathon/submissions/teams/${teamId}`
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import type {
|
||||
TTestimonialsListItem,
|
||||
TTestimonialsDetailItem,
|
||||
TTestimonialCreateRequest,
|
||||
TTestimonialUpdateRequest,
|
||||
} from '../../types/testimonials';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export const getTestimonialList = async (params?: TPaginationParams): Promise<TApiPaginated<TTestimonialsListItem>> => {
|
||||
const response = await api.get<TApiPaginated<TTestimonialsListItem>>('/v1/landing/cms/testimonials', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getTestimonialById = async (id: string): Promise<TTestimonialsDetailItem> => {
|
||||
const response = await api.get<ApiResponse<TTestimonialsDetailItem>>(`/v1/landing/cms/testimonials/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createTestimonial = async (data: TTestimonialCreateRequest): Promise<TTestimonialsDetailItem> => {
|
||||
const response = await api.post<ApiResponse<TTestimonialsDetailItem>>('/v1/landing/cms/testimonials/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateTestimonial = async (id: string, data: TTestimonialUpdateRequest): Promise<TTestimonialsDetailItem> => {
|
||||
const response = await api.patch<ApiResponse<TTestimonialsDetailItem>>(`/v1/landing/cms/testimonials/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const deleteTestimonial = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/landing/cms/testimonials/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
@@ -1,59 +1,39 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
|
||||
export interface UploadResponse {
|
||||
filename: string;
|
||||
original_filename: string;
|
||||
uploaded_path: string;
|
||||
filename?: string;
|
||||
original_filename?: string;
|
||||
uploaded_path?: string;
|
||||
url: string;
|
||||
size: number;
|
||||
content_type: string;
|
||||
file_type: string;
|
||||
user_id: string;
|
||||
email: string;
|
||||
size?: number;
|
||||
content_type?: string;
|
||||
file_type?: string;
|
||||
user_id?: string;
|
||||
email?: string;
|
||||
}
|
||||
|
||||
export interface UploadService {
|
||||
uploadFile(file: File): Promise<UploadResponse>;
|
||||
uploadAvatar(file: File): Promise<UploadResponse>;
|
||||
uploadCV(file: File): Promise<UploadResponse>;
|
||||
}
|
||||
const multipartPost = async (url: string, file: File, fieldName = 'file'): Promise<UploadResponse> => {
|
||||
const formData = new FormData();
|
||||
formData.append(fieldName, file);
|
||||
const response = await api.post<ApiResponse<UploadResponse>>(url, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const uploadService: UploadService = {
|
||||
async uploadFile(file: File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
export const uploadUserFile = (file: File) => multipartPost('/v1/iam/users/upload', file);
|
||||
|
||||
const response = await api.post<ApiResponse<UploadResponse>>('/users/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
return response.data.data;
|
||||
},
|
||||
export const uploadHackathonFile = (file: File) => multipartPost('/v1/hackathon/upload', file);
|
||||
|
||||
async uploadAvatar(file: File) {
|
||||
if (!file.type.startsWith('image/')) {
|
||||
throw new Error('File harus berupa gambar');
|
||||
}
|
||||
export const uploadHackathonAvatar = (file: File) => multipartPost('/v1/hackathon/upload/avatar', file);
|
||||
|
||||
const maxSize = 5 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('Ukuran file maksimal 5MB');
|
||||
}
|
||||
export const uploadHackathonTeamFile = (file: File) => multipartPost('/v1/hackathon/upload/team', file);
|
||||
|
||||
return this.uploadFile(file);
|
||||
},
|
||||
export const uploadHackathonSubmission = (file: File) => multipartPost('/v1/hackathon/upload/submission', file);
|
||||
|
||||
async uploadCV(file: File) {
|
||||
if (file.type !== 'application/pdf') {
|
||||
throw new Error('CV harus berupa file PDF');
|
||||
}
|
||||
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('Ukuran file maksimal 10MB');
|
||||
}
|
||||
|
||||
return this.uploadFile(file);
|
||||
},
|
||||
// Legacy service object for backward compatibility
|
||||
export const uploadService = {
|
||||
uploadFile: uploadUserFile,
|
||||
uploadAvatar: uploadUserFile,
|
||||
uploadCV: uploadUserFile,
|
||||
};
|
||||
|
||||
@@ -1,89 +1,69 @@
|
||||
import { api, ApiResponse } from '../index';
|
||||
import { TUserItem } from '../../types/users';
|
||||
import type {
|
||||
TUsersListItem,
|
||||
TUsersDetailItem,
|
||||
TUserCreateRequest,
|
||||
TUserUpdateRequest,
|
||||
} from '../../types/users';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
|
||||
export interface UserDetailResponseDto extends TUserItem {
|
||||
bio?: string;
|
||||
location?: string;
|
||||
website_url?: string;
|
||||
linkedin_url?: string;
|
||||
github_url?: string;
|
||||
twitter_url?: string;
|
||||
skills?: string[];
|
||||
career_status?: string;
|
||||
experience?: Array<{
|
||||
id: string;
|
||||
company: string;
|
||||
position: string;
|
||||
duration: string;
|
||||
period: string;
|
||||
}>;
|
||||
education?: Array<{
|
||||
id: string;
|
||||
institution: string;
|
||||
degree: string;
|
||||
field: string;
|
||||
period: string;
|
||||
}>;
|
||||
}
|
||||
// Re-export for backward compatibility
|
||||
export type UserDetailResponseDto = TUsersDetailItem;
|
||||
export type UserUpdateRequestDto = TUserUpdateRequest;
|
||||
|
||||
export interface UserUpdateRequestDto {
|
||||
fullname?: string;
|
||||
bio?: string;
|
||||
location?: string;
|
||||
website_url?: string;
|
||||
linkedin_url?: string;
|
||||
github_url?: string;
|
||||
twitter_url?: string;
|
||||
skills?: string[];
|
||||
phone_number?: string;
|
||||
birthdate?: string;
|
||||
gender?: string;
|
||||
career_status?: string;
|
||||
avatar?: string;
|
||||
cv_url?: string;
|
||||
phone_for_verification?: string;
|
||||
domicile?: string;
|
||||
experience?: Array<{
|
||||
id: string;
|
||||
company: string;
|
||||
position: string;
|
||||
duration: string;
|
||||
period: string;
|
||||
}>;
|
||||
education?: Array<{
|
||||
id: string;
|
||||
institution: string;
|
||||
degree: string;
|
||||
field: string;
|
||||
period: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface UserService {
|
||||
getUserMe(): Promise<UserDetailResponseDto>;
|
||||
getUserById(id: string): Promise<UserDetailResponseDto>;
|
||||
updateUserMe(data: UserUpdateRequestDto): Promise<UserDetailResponseDto>;
|
||||
updateUserById(id: string, data: UserUpdateRequestDto): Promise<UserDetailResponseDto>;
|
||||
}
|
||||
|
||||
export const userService: UserService = {
|
||||
async getUserMe() {
|
||||
const response = await api.get<ApiResponse<UserDetailResponseDto>>('/users/me');
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async getUserById(id: string) {
|
||||
const response = await api.get<ApiResponse<UserDetailResponseDto>>(`/users/detail/${id}`);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async updateUserMe(data: UserUpdateRequestDto) {
|
||||
const response = await api.put<ApiResponse<UserDetailResponseDto>>('/users/update/me', data);
|
||||
return response.data.data;
|
||||
},
|
||||
|
||||
async updateUserById(id: string, data: UserUpdateRequestDto) {
|
||||
const response = await api.put<ApiResponse<UserDetailResponseDto>>(`/users/${id}`, data);
|
||||
return response.data.data;
|
||||
},
|
||||
export const getUserList = async (params?: TPaginationParams): Promise<TApiPaginated<TUsersListItem>> => {
|
||||
const response = await api.get<TApiPaginated<TUsersListItem>>('/v1/iam/users', { params });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getUserMe = async (): Promise<TUsersDetailItem> => {
|
||||
const response = await api.get<ApiResponse<TUsersDetailItem>>('/v1/iam/users/me');
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const getUserById = async (id: string): Promise<TUsersDetailItem> => {
|
||||
const response = await api.get<ApiResponse<TUsersDetailItem>>(`/v1/iam/users/detail/${id}`);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const createUser = async (data: TUserCreateRequest): Promise<TUsersDetailItem> => {
|
||||
const response = await api.post<ApiResponse<TUsersDetailItem>>('/v1/iam/users/create', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateUserMe = async (data: TUserUpdateRequest): Promise<TUsersDetailItem> => {
|
||||
const response = await api.put<ApiResponse<TUsersDetailItem>>('/v1/iam/users/update/me', data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateUserById = async (id: string, data: TUserUpdateRequest): Promise<TUsersDetailItem> => {
|
||||
const response = await api.put<ApiResponse<TUsersDetailItem>>(`/v1/iam/users/update/${id}`, data);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const activateUser = async (id: string, is_active: boolean): Promise<{ message: string }> => {
|
||||
const response = await api.put<{ message: string }>(`/v1/iam/users/activate/${id}`, { is_active });
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteUser = async (id: string): Promise<{ message: string }> => {
|
||||
const response = await api.delete<{ message: string }>(`/v1/iam/users/delete/${id}`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const uploadUserFile = async (file: File): Promise<{ url: string }> => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
const response = await api.post<ApiResponse<{ url: string }>>('/v1/iam/users/upload', formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
});
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
// Legacy service object for backward compatibility
|
||||
export const userService = {
|
||||
getUserMe,
|
||||
getUserById,
|
||||
updateUserMe,
|
||||
updateUserById: (id: string, data: TUserUpdateRequest) => updateUserById(id, data),
|
||||
};
|
||||
|
||||
@@ -1,242 +1,140 @@
|
||||
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { backofficeApi, BackofficeApiResponse } from '../../api/backoffice';
|
||||
import { useAuthStore } from './use-auth-store';
|
||||
import {
|
||||
postLogin,
|
||||
postRegister,
|
||||
postVerifyEmail,
|
||||
postSendOtp,
|
||||
postForgotPassword,
|
||||
postNewPassword,
|
||||
} from '../../api/auth';
|
||||
import { getUserMe } from '../../api/users';
|
||||
|
||||
export * from './use-auth-store';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface LoginRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface SignupRequest {
|
||||
email: string;
|
||||
password: string;
|
||||
fullname: string;
|
||||
}
|
||||
|
||||
interface GitHubAuthRequest {
|
||||
code: string;
|
||||
}
|
||||
|
||||
interface ForgotPasswordRequest {
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface ResetPasswordRequest {
|
||||
access_token: string;
|
||||
new_password: string;
|
||||
}
|
||||
|
||||
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) => {
|
||||
mutationFn: postLogin,
|
||||
onSuccess: (res) => {
|
||||
const u = res.data?.user;
|
||||
const t = res.data?.token;
|
||||
if (!u || !t) return;
|
||||
setSession({
|
||||
token: data.token,
|
||||
token: t,
|
||||
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: '',
|
||||
},
|
||||
id: u.id,
|
||||
email: u.email,
|
||||
fullname: u.fullname,
|
||||
phone_number: u.phone_number || '',
|
||||
avatar: u.avatar || '',
|
||||
birthdate: u.birthdate || '',
|
||||
gender: u.gender || '',
|
||||
is_active: u.is_active,
|
||||
location: u.location,
|
||||
bio: u.bio,
|
||||
skills: u.skills,
|
||||
role: u.role ?? { id: '', name: 'user', permissions: [], created_at: '', updated_at: '' },
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSignup = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: SignupRequest) => {
|
||||
const response = await hackathonApi.post<
|
||||
HackathonApiResponse<MessageResponse>
|
||||
>('/auth/signup', data);
|
||||
return response.data.data;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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: '',
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSessionQuery = () => {
|
||||
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,
|
||||
});
|
||||
};
|
||||
|
||||
export const useForgotPassword = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: ForgotPasswordRequest) => {
|
||||
const response = await hackathonApi.post<
|
||||
HackathonApiResponse<MessageResponse>
|
||||
>('/auth/forgot-password', data);
|
||||
return response.data.data;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useResetPassword = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: ResetPasswordRequest) => {
|
||||
const response = await hackathonApi.post<
|
||||
HackathonApiResponse<MessageResponse>
|
||||
>('/auth/reset-password', data);
|
||||
return response.data.data;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSignOut = () => {
|
||||
const { clearSession } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
clearSession();
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useBackofficeLogin = () => {
|
||||
const { setSession } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: LoginRequest) => {
|
||||
const response = await backofficeApi.post<
|
||||
BackofficeApiResponse<AuthResponse>
|
||||
>('/auth/login', data);
|
||||
return response.data.data;
|
||||
},
|
||||
onSuccess: (data) => {
|
||||
mutationFn: postLogin,
|
||||
onSuccess: (res) => {
|
||||
const u = res.data?.user;
|
||||
const t = res.data?.token;
|
||||
if (!u || !t) return;
|
||||
setSession({
|
||||
token: data.token,
|
||||
token: t,
|
||||
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: data.user.role_id || '',
|
||||
name: 'admin',
|
||||
permissions: [],
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
},
|
||||
id: u.id,
|
||||
email: u.email,
|
||||
fullname: u.fullname,
|
||||
phone_number: u.phone_number || '',
|
||||
avatar: u.avatar || '',
|
||||
birthdate: u.birthdate || '',
|
||||
gender: u.gender || '',
|
||||
is_active: u.is_active,
|
||||
location: u.location,
|
||||
bio: u.bio,
|
||||
skills: u.skills,
|
||||
role: u.role ?? { id: '', name: 'admin', permissions: [], created_at: '', updated_at: '' },
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSignup = () => {
|
||||
return useMutation({ mutationFn: postRegister });
|
||||
};
|
||||
|
||||
export const usePostLogin = () => {
|
||||
return useMutation({
|
||||
mutationFn: postLogin,
|
||||
onSuccess: (res) => res,
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostRegister = () => {
|
||||
return useMutation({ mutationFn: postRegister });
|
||||
};
|
||||
|
||||
export const usePostVerifyEmail = () => {
|
||||
return useMutation({ mutationFn: postVerifyEmail });
|
||||
};
|
||||
|
||||
export const usePostSendOtp = () => {
|
||||
return useMutation({ mutationFn: postSendOtp });
|
||||
};
|
||||
|
||||
export const useForgotPassword = () => {
|
||||
return useMutation({ mutationFn: postForgotPassword });
|
||||
};
|
||||
|
||||
export const useResetPassword = () => {
|
||||
return useMutation({
|
||||
mutationFn: (data: { token: string; password: string }) => postNewPassword(data),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSignOut = () => {
|
||||
const { clearSession } = useAuthStore();
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
clearSession();
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useSessionQuery = () => {
|
||||
const { session } = useAuthStore();
|
||||
return useQuery({
|
||||
queryKey: ['auth-session'],
|
||||
queryFn: async () => {
|
||||
const user = await getUserMe();
|
||||
return { user };
|
||||
},
|
||||
enabled: !!session?.token,
|
||||
});
|
||||
};
|
||||
|
||||
export const useGoogleCallback = () => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
throw new Error('Google OAuth not supported. Use GitHub OAuth instead.');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const getGitHubOAuthUrl = (clientId: string, redirectUri: string) => {
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
@@ -257,21 +155,23 @@ export const useGitHubAuth = () => {
|
||||
if (meta.env?.VITE_GITHUB_CLIENT_ID) clientId = meta.env.VITE_GITHUB_CLIENT_ID;
|
||||
} catch { /* not in Vite context */ }
|
||||
}
|
||||
if (!clientId) {
|
||||
throw new Error(
|
||||
'GitHub Client ID not configured.'
|
||||
);
|
||||
}
|
||||
|
||||
if (!clientId) throw new Error('GitHub Client ID not configured.');
|
||||
const redirectUri = `${globalThis.location.origin}/auth/callback`;
|
||||
const url = getGitHubOAuthUrl(clientId, redirectUri);
|
||||
|
||||
return { url };
|
||||
return { url: getGitHubOAuthUrl(clientId, redirectUri) };
|
||||
};
|
||||
return { signInWithGitHub };
|
||||
};
|
||||
|
||||
return {
|
||||
signInWithGitHub,
|
||||
};
|
||||
export const useGitHubCallback = () => {
|
||||
const { setSession } = useAuthStore();
|
||||
return useMutation({
|
||||
mutationFn: async (data: { code: string }) => {
|
||||
// GitHub OAuth callback is handled by the backend redirect.
|
||||
// This hook exists for compatibility; in practice the backend
|
||||
// redirects to the frontend with a token in the URL params.
|
||||
throw new Error(`GitHub callback must be handled via backend redirect. Code: ${data.code}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useEmailAuth = () => {
|
||||
@@ -282,82 +182,20 @@ export const useEmailAuth = () => {
|
||||
const signInWithEmail = async (email: string, password: string) => {
|
||||
const result = await loginMutation.mutateAsync({ email, password });
|
||||
return {
|
||||
user: result.user,
|
||||
user: result.data?.user,
|
||||
session: {
|
||||
access_token: result.token.access_token,
|
||||
refresh_token: result.token.refresh_token,
|
||||
access_token: result.data?.token?.access_token,
|
||||
refresh_token: result.data?.token?.refresh_token,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const signUpWithEmail = async (
|
||||
email: string,
|
||||
password: string,
|
||||
fullname: string
|
||||
) => {
|
||||
const result = await signupMutation.mutateAsync({
|
||||
email,
|
||||
password,
|
||||
fullname,
|
||||
});
|
||||
return {
|
||||
message: result.message,
|
||||
};
|
||||
const signUpWithEmail = async (email: string, password: string, fullname: string) => {
|
||||
const result = await signupMutation.mutateAsync({ email, password, fullname });
|
||||
return { message: result.message };
|
||||
};
|
||||
|
||||
const signOut = async () => {
|
||||
clearSession();
|
||||
};
|
||||
const signOut = async () => { clearSession(); };
|
||||
|
||||
return {
|
||||
signInWithEmail,
|
||||
signUpWithEmail,
|
||||
signOut,
|
||||
};
|
||||
};
|
||||
|
||||
export const usePostLogin = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: LoginRequest) => {
|
||||
const response = await hackathonApi.post<
|
||||
HackathonApiResponse<AuthResponse>
|
||||
>('/auth/login', data);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostRegister = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (data: SignupRequest) => {
|
||||
const response = await hackathonApi.post<
|
||||
HackathonApiResponse<AuthResponse>
|
||||
>('/auth/signup', data);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostVerifyEmail = () => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
throw new Error('Email verification not required with new backend');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const usePostSendOtp = () => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
throw new Error('OTP not required with new backend');
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useGoogleCallback = () => {
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
throw new Error('Google OAuth not supported. Use GitHub OAuth instead.');
|
||||
},
|
||||
});
|
||||
return { signInWithEmail, signUpWithEmail, signOut };
|
||||
};
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getEventList,
|
||||
getEventById,
|
||||
createEvent,
|
||||
updateEvent,
|
||||
deleteEvent,
|
||||
} from '../../api/events';
|
||||
import type { TEventCreateRequest, TEventUpdateRequest } from '../../types/events';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const eventKeys = {
|
||||
all: ['events'] as const,
|
||||
lists: () => [...eventKeys.all, 'list'] as const,
|
||||
list: (params?: TPaginationParams) => [...eventKeys.lists(), params] as const,
|
||||
detail: (id: string) => [...eventKeys.all, 'detail', id] as const,
|
||||
};
|
||||
|
||||
export const useEventList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: eventKeys.list(params),
|
||||
queryFn: () => getEventList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useEventById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: eventKeys.detail(id),
|
||||
queryFn: () => getEventById(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateEvent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TEventCreateRequest) => createEvent(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: eventKeys.lists() }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateEvent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TEventUpdateRequest }) => updateEvent(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: eventKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: eventKeys.detail(vars.id) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteEvent = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deleteEvent(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: eventKeys.lists() }),
|
||||
});
|
||||
};
|
||||
@@ -1 +1,109 @@
|
||||
export {};
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getUserCredits,
|
||||
addCredits,
|
||||
consumeCredit,
|
||||
getGachaItemList,
|
||||
getGachaItemById,
|
||||
createGachaItem,
|
||||
updateGachaItem,
|
||||
deleteGachaItem,
|
||||
executeGachaRoll,
|
||||
createGachaClaim,
|
||||
} from '../../api/gacha';
|
||||
import type {
|
||||
TGachaCreditAddRequest,
|
||||
TGachaItemCreateRequest,
|
||||
TGachaItemUpdateRequest,
|
||||
TGachaClaimCreateRequest,
|
||||
} from '../../types/gacha';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const gachaKeys = {
|
||||
credits: ['gacha-credits'] as const,
|
||||
items: (params?: TPaginationParams) => ['gacha-items', params] as const,
|
||||
item: (id: string) => ['gacha-item', id] as const,
|
||||
};
|
||||
|
||||
// ----- Credits -----
|
||||
export const useUserCredits = () => {
|
||||
return useQuery({
|
||||
queryKey: gachaKeys.credits,
|
||||
queryFn: getUserCredits,
|
||||
});
|
||||
};
|
||||
|
||||
export const useAddCredits = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TGachaCreditAddRequest) => addCredits(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: gachaKeys.credits }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useConsumeCredit = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: consumeCredit,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: gachaKeys.credits }),
|
||||
});
|
||||
};
|
||||
|
||||
// ----- Items -----
|
||||
export const useGachaItemList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: gachaKeys.items(params),
|
||||
queryFn: () => getGachaItemList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useGachaItemById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: gachaKeys.item(id),
|
||||
queryFn: () => getGachaItemById(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateGachaItem = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TGachaItemCreateRequest) => createGachaItem(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['gacha-items'] }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateGachaItem = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TGachaItemUpdateRequest }) => updateGachaItem(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['gacha-items'] });
|
||||
queryClient.invalidateQueries({ queryKey: gachaKeys.item(vars.id) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteGachaItem = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deleteGachaItem(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['gacha-items'] }),
|
||||
});
|
||||
};
|
||||
|
||||
// ----- Roll -----
|
||||
export const useExecuteGachaRoll = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: executeGachaRoll,
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: gachaKeys.credits }),
|
||||
});
|
||||
};
|
||||
|
||||
// ----- Claims -----
|
||||
export const useCreateGachaClaim = () => {
|
||||
return useMutation({
|
||||
mutationFn: (data: TGachaClaimCreateRequest) => createGachaClaim(data),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -7,3 +7,8 @@ export * from './teams';
|
||||
export * from './messages';
|
||||
export * from './winners';
|
||||
export * from './use-session';
|
||||
export * from './roles';
|
||||
export * from './permissions';
|
||||
export * from './events';
|
||||
export * from './testimonials';
|
||||
export * from './sessions';
|
||||
|
||||
@@ -1,45 +1,72 @@
|
||||
import { useQuery, useMutation, UseQueryResult, UseMutationResult, UseQueryOptions } from '@tanstack/react-query';
|
||||
import { mentorService } from '../../api/mentors';
|
||||
import { MentorDetailResponseDto, MentorUpdateRequestDto } from '../../types/mentors';
|
||||
import { TResponseError } from '../../types/common';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getMentorMe,
|
||||
getMentorById,
|
||||
getMentorList,
|
||||
updateMentorMe,
|
||||
updateMentorById,
|
||||
verifyMentor,
|
||||
deleteMentor,
|
||||
} from '../../api/mentors';
|
||||
import type { MentorDetailResponseDto, MentorUpdateRequestDto } from '../../types/mentors';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const useMentorMe = (options?: UseQueryOptions<MentorDetailResponseDto, TResponseError>): UseQueryResult<MentorDetailResponseDto, TResponseError> => {
|
||||
export const useMentorMe = () => {
|
||||
return useQuery({
|
||||
queryKey: ['mentor-me'],
|
||||
queryFn: () => mentorService.getMentorMe(),
|
||||
...options,
|
||||
queryFn: getMentorMe,
|
||||
});
|
||||
};
|
||||
|
||||
export const useMentorById = (id: string, options?: UseQueryOptions<MentorDetailResponseDto, TResponseError>): UseQueryResult<MentorDetailResponseDto, TResponseError> => {
|
||||
export const useMentorById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['mentor-by-id', id],
|
||||
queryFn: () => mentorService.getMentorById(id),
|
||||
queryFn: () => getMentorById(id),
|
||||
enabled: !!id,
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateMentorMe = (): UseMutationResult<
|
||||
MentorDetailResponseDto,
|
||||
TResponseError,
|
||||
MentorUpdateRequestDto,
|
||||
unknown
|
||||
> => {
|
||||
export const useMentorList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: ['mentor-list', params],
|
||||
queryFn: () => getMentorList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateMentorMe = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationKey: ['update-mentor-me'],
|
||||
mutationFn: (data) => mentorService.updateMentorMe(data),
|
||||
mutationFn: (data: MentorUpdateRequestDto) => updateMentorMe(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['mentor-me'] }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateMentorById = (): UseMutationResult<
|
||||
MentorDetailResponseDto,
|
||||
TResponseError,
|
||||
{ id: string; data: MentorUpdateRequestDto },
|
||||
unknown
|
||||
> => {
|
||||
export const useUpdateMentorById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationKey: ['update-mentor-by-id'],
|
||||
mutationFn: ({ id, data }) => mentorService.updateMentorById(id, data),
|
||||
mutationFn: ({ id, data }: { id: string; data: MentorUpdateRequestDto }) =>
|
||||
updateMentorById(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['mentor-by-id', vars.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['mentor-list'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useVerifyMentor = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => verifyMentor(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['mentor-list'] }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteMentor = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deleteMentor(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['mentor-list'] }),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { api } from '../../api/index';
|
||||
import { useAuthStore } from '../auth';
|
||||
|
||||
export type Message = {
|
||||
@@ -9,12 +9,7 @@ export type Message = {
|
||||
message: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
user?: {
|
||||
id: string;
|
||||
fullname: string;
|
||||
avatar: string;
|
||||
email: string;
|
||||
};
|
||||
user?: { id: string; fullname: string; avatar: string; email: string };
|
||||
};
|
||||
|
||||
export const messageKeys = {
|
||||
@@ -22,13 +17,13 @@ export const messageKeys = {
|
||||
team: (teamId: string) => [...messageKeys.all, 'team', teamId] as const,
|
||||
};
|
||||
|
||||
interface ApiResp<T> { data: T; message?: string; }
|
||||
|
||||
export const useTeamMessages = (teamId: string) => {
|
||||
return useQuery({
|
||||
queryKey: messageKeys.team(teamId),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<Message[]>>(
|
||||
`/chat/teams/${teamId}`
|
||||
);
|
||||
const response = await api.get<ApiResp<Message[]>>(`/v1/hackathon/chat/teams/${teamId}`);
|
||||
return response.data.data || [];
|
||||
},
|
||||
enabled: !!teamId,
|
||||
@@ -43,20 +38,11 @@ export const useSendMessage = (teamId: string) => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (message: string) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to send messages');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<Message>>(
|
||||
`/chat/teams/${teamId}`,
|
||||
{ message }
|
||||
);
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to send messages');
|
||||
const response = await api.post<ApiResp<Message>>(`/v1/hackathon/chat/teams/${teamId}`, { message });
|
||||
return response.data.data;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: messageKeys.team(teamId) });
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: messageKeys.team(teamId) }),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -65,10 +51,8 @@ export const useDeleteMessage = (teamId: string) => {
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (messageId: string) => {
|
||||
await hackathonApi.delete(`/chat/messages/${messageId}`);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: messageKeys.team(teamId) });
|
||||
await api.delete(`/v1/hackathon/chat/messages/${messageId}`);
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: messageKeys.team(teamId) }),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getPermissionList,
|
||||
getPermissionById,
|
||||
createPermission,
|
||||
updatePermission,
|
||||
deletePermission,
|
||||
} from '../../api/permissions';
|
||||
import type { TPermissionCreateRequest, TPermissionUpdateRequest } from '../../types/permissions';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const permissionKeys = {
|
||||
all: ['permissions'] as const,
|
||||
lists: () => [...permissionKeys.all, 'list'] as const,
|
||||
list: (params?: TPaginationParams) => [...permissionKeys.lists(), params] as const,
|
||||
detail: (id: string) => [...permissionKeys.all, 'detail', id] as const,
|
||||
};
|
||||
|
||||
export const usePermissionList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: permissionKeys.list(params),
|
||||
queryFn: () => getPermissionList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const usePermissionById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: permissionKeys.detail(id),
|
||||
queryFn: () => getPermissionById(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreatePermission = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TPermissionCreateRequest) => createPermission(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: permissionKeys.lists() }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdatePermission = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TPermissionUpdateRequest }) => updatePermission(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: permissionKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: permissionKeys.detail(vars.id) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeletePermission = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deletePermission(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: permissionKeys.lists() }),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { getRoleList, getRoleById, createRole, updateRole, deleteRole } from '../../api/roles';
|
||||
import type { TRoleCreateRequest, TRoleUpdateRequest } from '../../types/roles';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const roleKeys = {
|
||||
all: ['roles'] as const,
|
||||
lists: () => [...roleKeys.all, 'list'] as const,
|
||||
list: (params?: TPaginationParams) => [...roleKeys.lists(), params] as const,
|
||||
detail: (id: string) => [...roleKeys.all, 'detail', id] as const,
|
||||
};
|
||||
|
||||
export const useRoleList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: roleKeys.list(params),
|
||||
queryFn: () => getRoleList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useRoleById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: roleKeys.detail(id),
|
||||
queryFn: () => getRoleById(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateRole = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TRoleCreateRequest) => createRole(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: roleKeys.lists() }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateRole = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TRoleUpdateRequest }) => updateRole(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: roleKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: roleKeys.detail(vars.id) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteRole = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deleteRole(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: roleKeys.lists() }),
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getMentorAvailability,
|
||||
bookSession,
|
||||
getMentorSessions,
|
||||
getMySessions,
|
||||
updateSessionStatus,
|
||||
submitFeedback,
|
||||
} from '../../api/sessions';
|
||||
import type {
|
||||
TBookSessionRequest,
|
||||
TUpdateSessionStatusRequest,
|
||||
TSessionFeedbackRequest,
|
||||
} from '../../types/sessions';
|
||||
|
||||
export const sessionKeys = {
|
||||
all: ['sessions'] as const,
|
||||
mine: (params?: Record<string, unknown>) => [...sessionKeys.all, 'mine', params] as const,
|
||||
mentor: (id: string) => [...sessionKeys.all, 'mentor', id] as const,
|
||||
availability: (id: string) => [...sessionKeys.all, 'availability', id] as const,
|
||||
};
|
||||
|
||||
export const useMentorAvailability = (mentorId: string) => {
|
||||
return useQuery({
|
||||
queryKey: sessionKeys.availability(mentorId),
|
||||
queryFn: () => getMentorAvailability(mentorId),
|
||||
enabled: !!mentorId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useBookSession = (mentorId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TBookSessionRequest) => bookSession(mentorId, data),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: sessionKeys.mentor(mentorId) });
|
||||
queryClient.invalidateQueries({ queryKey: sessionKeys.all });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useMentorSessions = (mentorId: string, params?: { status?: string }) => {
|
||||
return useQuery({
|
||||
queryKey: sessionKeys.mentor(mentorId),
|
||||
queryFn: () => getMentorSessions(mentorId, params),
|
||||
enabled: !!mentorId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useMySessions = (params?: { status?: string }) => {
|
||||
return useQuery({
|
||||
queryKey: sessionKeys.mine(params as Record<string, unknown>),
|
||||
queryFn: () => getMySessions(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateSessionStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TUpdateSessionStatusRequest }) =>
|
||||
updateSessionStatus(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: sessionKeys.all }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useSubmitFeedback = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TSessionFeedbackRequest }) =>
|
||||
submitFeedback(id, data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: sessionKeys.all }),
|
||||
});
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useMutation, useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { api } from '../../api/index';
|
||||
import { useAuthStore } from '../auth';
|
||||
import type {
|
||||
TCreateTeamRequest,
|
||||
@@ -23,110 +23,41 @@ export const teamKeys = {
|
||||
};
|
||||
|
||||
interface TeamMember {
|
||||
id: string;
|
||||
team_id: string;
|
||||
user_id: string;
|
||||
role: string;
|
||||
status: string;
|
||||
joined_at: string;
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatar: string;
|
||||
};
|
||||
id: string; team_id: string; user_id: string; role: string; status: string;
|
||||
joined_at: string; user?: { id: string; email: string; fullname: string; avatar: string };
|
||||
}
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
logo?: string;
|
||||
banner?: string;
|
||||
description?: string;
|
||||
city?: string;
|
||||
visibility: string;
|
||||
leader_id: string;
|
||||
created_at: string;
|
||||
leader?: {
|
||||
id: string;
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatar: string;
|
||||
};
|
||||
members?: TeamMember[];
|
||||
member_count?: number;
|
||||
has_submission?: boolean;
|
||||
id: string; name: string; logo?: string; banner?: string; description?: string;
|
||||
city?: string; visibility: string; leader_id: string; created_at: string;
|
||||
leader?: { id: string; email: string; fullname: string; avatar: string };
|
||||
members?: TeamMember[]; member_count?: number; has_submission?: boolean;
|
||||
}
|
||||
|
||||
interface JoinRequest {
|
||||
id: string;
|
||||
team_id: string;
|
||||
user_id: string;
|
||||
message?: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
user?: {
|
||||
id: string;
|
||||
email: string;
|
||||
fullname: string;
|
||||
avatar: string;
|
||||
};
|
||||
id: string; team_id: string; user_id: string; message?: string; status: string;
|
||||
created_at: string; user?: { id: string; email: string; fullname: string; avatar: string };
|
||||
}
|
||||
|
||||
interface Invitation {
|
||||
id: string;
|
||||
team_id: string;
|
||||
inviter_id: string;
|
||||
invitee_email: string;
|
||||
invitee_id?: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
team?: Team;
|
||||
inviter?: {
|
||||
id: string;
|
||||
fullname: string;
|
||||
email: string;
|
||||
avatar: string;
|
||||
};
|
||||
id: string; team_id: string; inviter_id: string; invitee_email: string;
|
||||
invitee_id?: string; status: string; created_at: string; team?: Team;
|
||||
inviter?: { id: string; fullname: string; email: string; avatar: string };
|
||||
}
|
||||
|
||||
interface Submission {
|
||||
id: string;
|
||||
team_id: string;
|
||||
project_name: string;
|
||||
description?: string;
|
||||
repository_url?: string;
|
||||
demo_url?: string;
|
||||
video_url?: string;
|
||||
presentation_url?: string;
|
||||
status: string;
|
||||
submitted_at?: string;
|
||||
created_at: string;
|
||||
id: string; team_id: string; project_name: string; description?: string;
|
||||
repository_url?: string; demo_url?: string; video_url?: string;
|
||||
presentation_url?: string; status: string; submitted_at?: string; created_at: string;
|
||||
}
|
||||
interface ListMeta { page: number; per_page: number; total_page: number; total_data: number; }
|
||||
interface ListResponse<T> { message: string; data: T[]; meta: ListMeta; }
|
||||
interface ApiResp<T> { data: T; message?: string; }
|
||||
|
||||
interface ListResponseWithMeta<T> {
|
||||
message: string;
|
||||
data: T[];
|
||||
meta: {
|
||||
page: number;
|
||||
per_page: number;
|
||||
total_page: number;
|
||||
total_data: number;
|
||||
};
|
||||
}
|
||||
const TEAMS_PAGE_SIZE = 12;
|
||||
|
||||
export const useTeams = (params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
city?: string;
|
||||
visibility?: string;
|
||||
search?: string;
|
||||
minMembers?: number;
|
||||
maxMembers?: number;
|
||||
hasSubmission?: boolean;
|
||||
page?: number; limit?: number; city?: string; visibility?: string; search?: string;
|
||||
minMembers?: number; maxMembers?: number; hasSubmission?: boolean;
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: teamKeys.list(params),
|
||||
queryKey: teamKeys.list(params as Record<string, unknown>),
|
||||
queryFn: async () => {
|
||||
const queryParams = new URLSearchParams();
|
||||
if (params?.page) queryParams.append('page', String(params.page));
|
||||
@@ -137,11 +68,10 @@ export const useTeams = (params?: {
|
||||
if (params?.minMembers) queryParams.append('min_members', String(params.minMembers));
|
||||
if (params?.maxMembers) queryParams.append('max_members', String(params.maxMembers));
|
||||
if (params?.hasSubmission !== undefined) queryParams.append('has_submission', String(params.hasSubmission));
|
||||
|
||||
const response = await hackathonApi.get<ListResponseWithMeta<Team>>(
|
||||
`/teams/browse${queryParams.toString() ? `?${queryParams.toString()}` : ''}`
|
||||
const qs = queryParams.toString();
|
||||
const response = await api.get<ListResponse<Team>>(
|
||||
`/v1/hackathon/teams/browse${qs ? `?${qs}` : ''}`
|
||||
);
|
||||
|
||||
const { data, meta } = response.data;
|
||||
return {
|
||||
teams: data || [],
|
||||
@@ -154,13 +84,7 @@ export const useTeams = (params?: {
|
||||
});
|
||||
};
|
||||
|
||||
const TEAMS_PAGE_SIZE = 12;
|
||||
|
||||
export const useInfiniteTeams = (params?: {
|
||||
city?: string;
|
||||
visibility?: string;
|
||||
search?: string;
|
||||
}) => {
|
||||
export const useInfiniteTeams = (params?: { city?: string; visibility?: string; search?: string }) => {
|
||||
return useInfiniteQuery({
|
||||
queryKey: [...teamKeys.lists(), 'infinite', params],
|
||||
queryFn: async ({ pageParam = 1 }) => {
|
||||
@@ -170,19 +94,12 @@ export const useInfiniteTeams = (params?: {
|
||||
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 response = await api.get<ApiResp<Team[]>>(`/v1/hackathon/teams/browse?${queryParams}`);
|
||||
const teams = response.data.data || [];
|
||||
return {
|
||||
data: teams,
|
||||
nextPage: teams.length === TEAMS_PAGE_SIZE ? pageParam + 1 : undefined,
|
||||
};
|
||||
return { data: teams, nextPage: teams.length === TEAMS_PAGE_SIZE ? pageParam + 1 : undefined };
|
||||
},
|
||||
initialPageParam: 1,
|
||||
getNextPageParam: (lastPage) => lastPage.nextPage,
|
||||
getNextPageParam: (lastPage: { nextPage?: number }) => lastPage.nextPage,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -190,7 +107,7 @@ export const useTeamById = (teamId: string, enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: teamKeys.detail(teamId),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<Team>>(`/teams/${teamId}`);
|
||||
const response = await api.get<ApiResp<Team>>(`/v1/hackathon/teams/${teamId}`);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
enabled: enabled && !!teamId,
|
||||
@@ -200,22 +117,10 @@ export const useTeamById = (teamId: string, enabled = true) => {
|
||||
export const useCreateTeam = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: TCreateTeamRequest) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to create a team');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<Team>>('/teams', {
|
||||
name: data.name,
|
||||
logo: data.logo,
|
||||
banner: data.banner,
|
||||
description: data.description,
|
||||
city: data.city,
|
||||
visibility: data.visibility,
|
||||
});
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to create a team');
|
||||
const response = await api.post<ApiResp<Team>>('/v1/hackathon/teams', data);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -228,22 +133,10 @@ export const useCreateTeam = () => {
|
||||
export const useUpdateTeam = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: TUpdateTeamRequest) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to update a team');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.put<HackathonApiResponse<Team>>(`/teams/${teamId}`, {
|
||||
name: data.name,
|
||||
logo: data.logo,
|
||||
banner: data.banner,
|
||||
description: data.description,
|
||||
city: data.city,
|
||||
visibility: data.visibility,
|
||||
});
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to update a team');
|
||||
const response = await api.put<ApiResp<Team>>(`/v1/hackathon/teams/${teamId}`, data);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -257,7 +150,7 @@ export const useTeamMembers = (teamId: string, enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: teamKeys.members(teamId),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<Team>>(`/teams/${teamId}`);
|
||||
const response = await api.get<ApiResp<Team>>(`/v1/hackathon/teams/${teamId}`);
|
||||
return { data: response.data.data?.members || [] };
|
||||
},
|
||||
enabled: enabled && !!teamId,
|
||||
@@ -267,32 +160,26 @@ export const useTeamMembers = (teamId: string, enabled = true) => {
|
||||
export const useInviteMember = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: TInviteMemberRequest) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to invite a member');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<Invitation>>(
|
||||
`/teams/${teamId}/invite`,
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to invite a member');
|
||||
const response = await api.post<ApiResp<Invitation>>(
|
||||
`/v1/hackathon/invitations/teams/${teamId}/invite`,
|
||||
{ invitee_email: data.email }
|
||||
);
|
||||
|
||||
return { data: response.data.data };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.members(teamId) });
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: teamKeys.members(teamId) }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useManageMember = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ userId, data }: { userId: string; data: { role?: string; status?: string } }) => {
|
||||
throw new Error('Manage member functionality not yet implemented in backend');
|
||||
// Remove member is the available operation; role management not exposed by backend
|
||||
await api.delete(`/v1/hackathon/teams/${teamId}/members/${userId}`);
|
||||
return { success: true };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.members(teamId) });
|
||||
@@ -304,14 +191,10 @@ export const useManageMember = (teamId: string) => {
|
||||
export const useRemoveMember = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (userId: string) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to remove a member');
|
||||
}
|
||||
|
||||
await hackathonApi.delete(`/teams/${teamId}/members/${userId}`);
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to remove a member');
|
||||
await api.delete(`/v1/hackathon/teams/${teamId}/members/${userId}`);
|
||||
return { success: true };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -324,23 +207,16 @@ export const useRemoveMember = (teamId: string) => {
|
||||
export const useJoinTeam = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ teamId, data }: { teamId: string; data: TJoinTeamRequest }) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to join a team');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<JoinRequest>>(
|
||||
`/join-requests/teams/${teamId}`,
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to join a team');
|
||||
const response = await api.post<ApiResp<JoinRequest>>(
|
||||
`/v1/hackathon/join-requests/teams/${teamId}`,
|
||||
{ message: data.message }
|
||||
);
|
||||
|
||||
return { data: response.data.data };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.lists() });
|
||||
},
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: teamKeys.lists() }),
|
||||
});
|
||||
};
|
||||
|
||||
@@ -348,8 +224,8 @@ export const useTeamJoinRequests = (teamId: string, enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: teamKeys.joinRequests(teamId),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<JoinRequest[]>>(
|
||||
`/join-requests/teams/${teamId}/pending`
|
||||
const response = await api.get<ApiResp<JoinRequest[]>>(
|
||||
`/v1/hackathon/join-requests/teams/${teamId}/pending`
|
||||
);
|
||||
return { data: response.data.data || [] };
|
||||
},
|
||||
@@ -360,19 +236,11 @@ export const useTeamJoinRequests = (teamId: string, enabled = true) => {
|
||||
export const useRespondToJoinRequest = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ requestId, action }: { requestId: string; action: 'approve' | 'reject' }) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to respond to join requests');
|
||||
}
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to respond to join requests');
|
||||
const backendAction = action === 'approve' ? 'accept' : 'reject';
|
||||
|
||||
await hackathonApi.post(`/join-requests/${requestId}/respond`, {
|
||||
action: backendAction,
|
||||
});
|
||||
|
||||
await api.post(`/v1/hackathon/join-requests/${requestId}/respond`, { action: backendAction });
|
||||
return { success: true, action };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -385,11 +253,10 @@ export const useRespondToJoinRequest = (teamId: string) => {
|
||||
|
||||
export const useMyInvitations = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: teamKeys.myInvitations(),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<Invitation[]>>('/invitations/my');
|
||||
const response = await api.get<ApiResp<Invitation[]>>('/v1/hackathon/invitations/my');
|
||||
return { data: response.data.data || [] };
|
||||
},
|
||||
enabled: !!session?.user?.id,
|
||||
@@ -399,15 +266,10 @@ export const useMyInvitations = () => {
|
||||
export const useRespondToInvitation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({ invitationId, action }: { invitationId: string; action: 'accept' | 'reject' }) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('User not authenticated');
|
||||
}
|
||||
|
||||
await hackathonApi.post(`/invitations/${invitationId}/respond`, { action });
|
||||
|
||||
if (!session?.user?.id) throw new Error('User not authenticated');
|
||||
await api.post(`/v1/hackathon/invitations/${invitationId}/respond`, { action });
|
||||
return { success: true, action };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -420,13 +282,12 @@ export const useRespondToInvitation = () => {
|
||||
|
||||
export const useMyTeams = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: teamKeys.myTeams(),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<any[]>>('/teams/my');
|
||||
const response = await api.get<ApiResp<unknown[]>>('/v1/hackathon/teams/my');
|
||||
const rawData = response.data.data || [];
|
||||
const teams = rawData.map((item: any) => item.team || item);
|
||||
const teams = rawData.map((item: unknown) => (item as Record<string, unknown>).team ?? item);
|
||||
return { data: teams };
|
||||
},
|
||||
enabled: !!session?.user?.id,
|
||||
@@ -435,58 +296,22 @@ export const useMyTeams = () => {
|
||||
|
||||
export const useSubmitProject = (teamId: string) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (data: TSubmitProjectRequest) => {
|
||||
let submissionId: string;
|
||||
|
||||
try {
|
||||
const existingResponse = await hackathonApi.get<HackathonApiResponse<Submission | null>>(
|
||||
`/submissions/teams/${teamId}`
|
||||
);
|
||||
|
||||
if (existingResponse.data.data?.id) {
|
||||
const response = await hackathonApi.put<HackathonApiResponse<Submission>>(
|
||||
`/submissions/${existingResponse.data.data.id}`,
|
||||
{
|
||||
project_name: data.project_name,
|
||||
description: data.description,
|
||||
repository_url: data.repository_url,
|
||||
demo_url: data.demo_url,
|
||||
video_url: data.video_url,
|
||||
presentation_url: data.presentation_url,
|
||||
screenshots: data.screenshots,
|
||||
}
|
||||
);
|
||||
submissionId = response.data.data.id;
|
||||
} else {
|
||||
throw new Error('No existing submission');
|
||||
}
|
||||
const existing = await api.get<ApiResp<Submission | null>>(`/v1/hackathon/submissions/teams/${teamId}`);
|
||||
if (existing.data.data?.id) {
|
||||
const res = await api.put<ApiResp<Submission>>(`/v1/hackathon/submissions/${existing.data.data.id}`, data);
|
||||
submissionId = res.data.data.id;
|
||||
} else throw new Error('No existing submission');
|
||||
} catch {
|
||||
const response = await hackathonApi.post<HackathonApiResponse<Submission>>(
|
||||
`/submissions/teams/${teamId}`,
|
||||
{
|
||||
project_name: data.project_name,
|
||||
description: data.description,
|
||||
repository_url: data.repository_url,
|
||||
demo_url: data.demo_url,
|
||||
video_url: data.video_url,
|
||||
presentation_url: data.presentation_url,
|
||||
screenshots: data.screenshots,
|
||||
}
|
||||
);
|
||||
submissionId = response.data.data.id;
|
||||
const res = await api.post<ApiResp<Submission>>(`/v1/hackathon/submissions/teams/${teamId}`, data);
|
||||
submissionId = res.data.data.id;
|
||||
}
|
||||
|
||||
await hackathonApi.post<HackathonApiResponse<Submission>>(
|
||||
`/submissions/${submissionId}/submit`
|
||||
);
|
||||
|
||||
const finalResponse = await hackathonApi.post<HackathonApiResponse<Submission>>(
|
||||
`/submissions/${submissionId}/confirm`
|
||||
);
|
||||
|
||||
return { data: finalResponse.data.data };
|
||||
await api.post<ApiResp<Submission>>(`/v1/hackathon/submissions/${submissionId}/submit`);
|
||||
const final = await api.post<ApiResp<Submission>>(`/v1/hackathon/submissions/${submissionId}/confirm`);
|
||||
return { data: final.data.data };
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: teamKeys.submission(teamId) });
|
||||
@@ -499,9 +324,7 @@ export const useTeamSubmission = (teamId: string, enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: teamKeys.submission(teamId),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<Submission | null>>(
|
||||
`/submissions/teams/${teamId}`
|
||||
);
|
||||
const response = await api.get<ApiResp<Submission | null>>(`/v1/hackathon/submissions/teams/${teamId}`);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
enabled: enabled && !!teamId,
|
||||
@@ -511,14 +334,10 @@ export const useTeamSubmission = (teamId: string, enabled = true) => {
|
||||
export const useLeaveTeam = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (teamId: string) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to leave a team');
|
||||
}
|
||||
|
||||
await hackathonApi.post(`/teams/${teamId}/leave`);
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to leave a team');
|
||||
await api.post(`/v1/hackathon/teams/${teamId}/leave`);
|
||||
return { success: true };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -531,14 +350,10 @@ export const useLeaveTeam = () => {
|
||||
export const useDeleteTeam = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (teamId: string) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to delete a team');
|
||||
}
|
||||
|
||||
await hackathonApi.delete(`/teams/${teamId}`);
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to delete a team');
|
||||
await api.delete(`/v1/hackathon/teams/${teamId}`);
|
||||
return { success: true };
|
||||
},
|
||||
onSuccess: () => {
|
||||
@@ -552,9 +367,9 @@ export const useTeamsByUserId = (userId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['teams-by-user', userId],
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<any[]>>(`/users/${userId}/teams`);
|
||||
const response = await api.get<ApiResp<unknown[]>>(`/v1/hackathon/users/${userId}/teams`);
|
||||
const rawData = response.data.data || [];
|
||||
const teams = rawData.map((item: any) => item.team || item);
|
||||
const teams = rawData.map((item: unknown) => (item as Record<string, unknown>).team ?? item);
|
||||
return { data: teams };
|
||||
},
|
||||
enabled: !!userId,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import {
|
||||
getTestimonialList,
|
||||
getTestimonialById,
|
||||
createTestimonial,
|
||||
updateTestimonial,
|
||||
deleteTestimonial,
|
||||
} from '../../api/testimonials';
|
||||
import type { TTestimonialCreateRequest, TTestimonialUpdateRequest } from '../../types/testimonials';
|
||||
import type { TPaginationParams } from '../../types/common';
|
||||
|
||||
export const testimonialKeys = {
|
||||
all: ['testimonials'] as const,
|
||||
lists: () => [...testimonialKeys.all, 'list'] as const,
|
||||
list: (params?: TPaginationParams) => [...testimonialKeys.lists(), params] as const,
|
||||
detail: (id: string) => [...testimonialKeys.all, 'detail', id] as const,
|
||||
};
|
||||
|
||||
export const useTestimonialList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: testimonialKeys.list(params),
|
||||
queryFn: () => getTestimonialList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useTestimonialById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: testimonialKeys.detail(id),
|
||||
queryFn: () => getTestimonialById(id),
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCreateTestimonial = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (data: TTestimonialCreateRequest) => createTestimonial(data),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: testimonialKeys.lists() }),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateTestimonial = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: ({ id, data }: { id: string; data: TTestimonialUpdateRequest }) => updateTestimonial(id, data),
|
||||
onSuccess: (_, vars) => {
|
||||
queryClient.invalidateQueries({ queryKey: testimonialKeys.lists() });
|
||||
queryClient.invalidateQueries({ queryKey: testimonialKeys.detail(vars.id) });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useDeleteTestimonial = () => {
|
||||
const queryClient = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (id: string) => deleteTestimonial(id),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: testimonialKeys.lists() }),
|
||||
});
|
||||
};
|
||||
@@ -1,208 +1,84 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { useAuthStore } from '../auth';
|
||||
|
||||
interface UploadResponse {
|
||||
url: string;
|
||||
}
|
||||
|
||||
const fileToBase64 = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);
|
||||
reader.onload = () => {
|
||||
const base64 = (reader.result as string).split(',')[1];
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
};
|
||||
import {
|
||||
uploadHackathonAvatar,
|
||||
uploadHackathonTeamFile,
|
||||
uploadHackathonSubmission,
|
||||
uploadHackathonFile,
|
||||
uploadUserFile,
|
||||
} from '../../api/upload';
|
||||
|
||||
export const useUploadFile = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['upload-file'],
|
||||
mutationFn: async (file: File) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to upload files');
|
||||
}
|
||||
|
||||
const base64Data = await fileToBase64(file);
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<UploadResponse>>(
|
||||
'/upload/team',
|
||||
{
|
||||
filename: file.name,
|
||||
content_type: file.type,
|
||||
data: base64Data,
|
||||
}
|
||||
);
|
||||
|
||||
return { data: { url: response.data.data.url } };
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to upload files');
|
||||
const data = await uploadHackathonTeamFile(file);
|
||||
return { data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadAvatar = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['upload-avatar'],
|
||||
mutationFn: async (file: File) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to upload avatar');
|
||||
}
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to upload avatar');
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
throw new Error('Invalid file type. Allowed types: JPEG, PNG, WebP, GIF');
|
||||
}
|
||||
|
||||
const maxSize = 5 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('File too large. Maximum size: 5MB');
|
||||
}
|
||||
|
||||
const base64Data = await fileToBase64(file);
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<UploadResponse>>(
|
||||
'/upload/avatar',
|
||||
{
|
||||
filename: file.name,
|
||||
content_type: file.type,
|
||||
data: base64Data,
|
||||
}
|
||||
);
|
||||
|
||||
return { data: { url: response.data.data.url } };
|
||||
if (!allowedTypes.includes(file.type)) throw new Error('Invalid file type. Allowed: JPEG, PNG, WebP, GIF');
|
||||
if (file.size > 5 * 1024 * 1024) throw new Error('File too large. Maximum size: 5MB');
|
||||
const data = await uploadHackathonAvatar(file);
|
||||
return { data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadTeamFile = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['upload-team-file'],
|
||||
mutationFn: async (file: File) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to upload files');
|
||||
}
|
||||
|
||||
const allowedTypes = [
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
throw new Error('Invalid file type. Allowed types: JPEG, PNG, WebP, GIF, PDF');
|
||||
}
|
||||
|
||||
const maxSize = 20 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('File too large. Maximum size: 20MB');
|
||||
}
|
||||
|
||||
const base64Data = await fileToBase64(file);
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<UploadResponse>>(
|
||||
'/upload/team',
|
||||
{
|
||||
filename: file.name,
|
||||
content_type: file.type,
|
||||
data: base64Data,
|
||||
}
|
||||
);
|
||||
|
||||
return { data: { url: response.data.data.url } };
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to upload files');
|
||||
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif', 'application/pdf'];
|
||||
if (!allowedTypes.includes(file.type)) throw new Error('Invalid file type. Allowed: JPEG, PNG, WebP, GIF, PDF');
|
||||
if (file.size > 20 * 1024 * 1024) throw new Error('File too large. Maximum size: 20MB');
|
||||
const data = await uploadHackathonTeamFile(file);
|
||||
return { data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadSubmission = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['upload-submission'],
|
||||
mutationFn: async (file: File) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to upload submissions');
|
||||
}
|
||||
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to upload submissions');
|
||||
const allowedTypes = [
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/webp',
|
||||
'image/gif',
|
||||
'application/pdf',
|
||||
'application/zip',
|
||||
'application/x-zip-compressed',
|
||||
'video/mp4',
|
||||
'video/webm',
|
||||
'image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif',
|
||||
'application/pdf', 'application/zip', 'application/x-zip-compressed',
|
||||
'video/mp4', 'video/webm',
|
||||
];
|
||||
if (!allowedTypes.includes(file.type)) {
|
||||
throw new Error(
|
||||
'Invalid file type. Allowed types: Images, PDF, ZIP, MP4, WebM'
|
||||
);
|
||||
}
|
||||
|
||||
const maxSize = 50 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('File too large. Maximum size: 50MB');
|
||||
}
|
||||
|
||||
const base64Data = await fileToBase64(file);
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<UploadResponse>>(
|
||||
'/upload/submission',
|
||||
{
|
||||
filename: file.name,
|
||||
content_type: file.type,
|
||||
data: base64Data,
|
||||
}
|
||||
);
|
||||
|
||||
return { data: { url: response.data.data.url } };
|
||||
if (!allowedTypes.includes(file.type)) throw new Error('Invalid file type. Allowed: Images, PDF, ZIP, MP4, WebM');
|
||||
if (file.size > 50 * 1024 * 1024) throw new Error('File too large. Maximum size: 50MB');
|
||||
const data = await uploadHackathonSubmission(file);
|
||||
return { data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUploadCV = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['upload-cv'],
|
||||
mutationFn: async (file: File) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to upload CV');
|
||||
}
|
||||
|
||||
if (file.type !== 'application/pdf') {
|
||||
throw new Error('CV must be a PDF file');
|
||||
}
|
||||
|
||||
const maxSize = 20 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('File too large. Maximum size: 20MB');
|
||||
}
|
||||
|
||||
const base64Data = await fileToBase64(file);
|
||||
|
||||
const response = await hackathonApi.post<HackathonApiResponse<UploadResponse>>(
|
||||
'/upload/team',
|
||||
{
|
||||
filename: file.name,
|
||||
content_type: file.type,
|
||||
data: base64Data,
|
||||
}
|
||||
);
|
||||
|
||||
return { data: { url: response.data.data.url } };
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to upload CV');
|
||||
if (file.type !== 'application/pdf') throw new Error('CV must be a PDF file');
|
||||
if (file.size > 20 * 1024 * 1024) throw new Error('File too large. Maximum size: 20MB');
|
||||
const data = await uploadUserFile(file);
|
||||
return { data };
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,69 +1,30 @@
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { useAuthStore } from '../auth';
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
fullname: string;
|
||||
bio?: string;
|
||||
location?: string;
|
||||
avatar?: string;
|
||||
skills?: string[];
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
interface CertificateUserData {
|
||||
id: string;
|
||||
fullname: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
interface CertificateTeamData {
|
||||
id: string;
|
||||
name: string;
|
||||
logo?: string;
|
||||
is_leader: boolean;
|
||||
}
|
||||
|
||||
interface CertificateSubmissionData {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
repository_url?: string;
|
||||
demo_url?: string;
|
||||
}
|
||||
|
||||
interface CertificateWinnerData {
|
||||
rank: number;
|
||||
prize?: string;
|
||||
}
|
||||
import {
|
||||
getUserMe,
|
||||
getUserById,
|
||||
getUserList,
|
||||
updateUserMe,
|
||||
updateUserById,
|
||||
} from '../../api/users';
|
||||
import { api } from '../../api/index';
|
||||
import type { TApiPaginated, TPaginationParams } from '../../types/common';
|
||||
import type { TUsersDetailItem, TUserUpdateRequest } from '../../types/users';
|
||||
|
||||
export interface CertificatePublicData {
|
||||
user: CertificateUserData;
|
||||
team?: CertificateTeamData;
|
||||
submission?: CertificateSubmissionData;
|
||||
winner?: CertificateWinnerData;
|
||||
}
|
||||
|
||||
interface UpdateUserRequest {
|
||||
fullname?: string;
|
||||
bio?: string;
|
||||
location?: string;
|
||||
avatar?: string;
|
||||
skills?: string[];
|
||||
user: { id: string; fullname: string; email: string; avatar?: string };
|
||||
team?: { id: string; name: string; logo?: string; is_leader: boolean };
|
||||
submission?: { id: string; title: string; description: string; repository_url?: string; demo_url?: string };
|
||||
winner?: { rank: number; prize?: string };
|
||||
}
|
||||
|
||||
export const useUserMe = () => {
|
||||
const { session } = useAuthStore();
|
||||
|
||||
return useQuery({
|
||||
queryKey: ['user-me'],
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<User>>('/users/me');
|
||||
return { data: response.data.data };
|
||||
const data = await getUserMe();
|
||||
return { data };
|
||||
},
|
||||
enabled: !!session?.user?.id,
|
||||
});
|
||||
@@ -73,45 +34,52 @@ export const useUserById = (id: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['user-by-id', id],
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<User>>(`/users/${id}`);
|
||||
return { data: response.data.data };
|
||||
const data = await getUserById(id);
|
||||
return { data };
|
||||
},
|
||||
enabled: !!id,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUserDetailsById = (userId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['user-details', userId],
|
||||
queryFn: async () => {
|
||||
const data = await getUserById(userId);
|
||||
return { data };
|
||||
},
|
||||
enabled: !!userId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useUserList = (params?: TPaginationParams) => {
|
||||
return useQuery({
|
||||
queryKey: ['user-list', params],
|
||||
queryFn: () => getUserList(params),
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateUserMe = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { session, setSession } = useAuthStore();
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['update-user-me'],
|
||||
mutationFn: async (data: UpdateUserRequest) => {
|
||||
if (!session?.user?.id) {
|
||||
throw new Error('You must be logged in to update profile');
|
||||
}
|
||||
|
||||
const response = await hackathonApi.put<HackathonApiResponse<User>>('/users/me', {
|
||||
fullname: data.fullname,
|
||||
bio: data.bio,
|
||||
location: data.location,
|
||||
avatar: data.avatar,
|
||||
skills: data.skills,
|
||||
});
|
||||
|
||||
return { data: response.data.data };
|
||||
mutationFn: (data: TUserUpdateRequest) => {
|
||||
if (!session?.user?.id) throw new Error('You must be logged in to update profile');
|
||||
return updateUserMe(data);
|
||||
},
|
||||
onSuccess: (result) => {
|
||||
if (session?.user && result.data) {
|
||||
if (session?.user && result) {
|
||||
setSession({
|
||||
token: session.token,
|
||||
user: {
|
||||
...session.user,
|
||||
fullname: result.data.fullname || session.user.fullname,
|
||||
bio: result.data.bio || '',
|
||||
location: result.data.location || '',
|
||||
avatar: result.data.avatar || session.user.avatar,
|
||||
skills: result.data.skills || [],
|
||||
fullname: result.fullname || session.user.fullname,
|
||||
bio: result.profile_extension?.bio || '',
|
||||
location: result.profile_extension?.location || '',
|
||||
avatar: result.avatar || session.user.avatar,
|
||||
skills: result.profile_extension?.skills || [],
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -125,33 +93,20 @@ export const useUpdateUserById = () => {
|
||||
|
||||
return useMutation({
|
||||
mutationKey: ['update-user-by-id'],
|
||||
mutationFn: async ({ id, data }: { id: string; data: UpdateUserRequest }) => {
|
||||
const response = await hackathonApi.put<HackathonApiResponse<User>>(`/users/${id}`, data);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
mutationFn: ({ id, data }: { id: string; data: TUserUpdateRequest }) => updateUserById(id, data),
|
||||
onSuccess: (_, variables) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['user-by-id', variables.id] });
|
||||
queryClient.invalidateQueries({ queryKey: ['user-list'] });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUserDetailsById = (userId: string) => {
|
||||
return useQuery({
|
||||
queryKey: ['user-details', userId],
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<User>>(`/users/${userId}`);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
enabled: !!userId,
|
||||
});
|
||||
};
|
||||
|
||||
export const useCertificatePublicData = (userId: string, enabled = true) => {
|
||||
return useQuery({
|
||||
queryKey: ['certificate-public-data', userId],
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get<HackathonApiResponse<CertificatePublicData>>(
|
||||
`/certificates/${userId}`
|
||||
const response = await api.get<{ data: CertificatePublicData }>(
|
||||
`/v1/hackathon/certificates/${userId}`
|
||||
);
|
||||
return { data: response.data.data };
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { hackathonApi, HackathonApiResponse } from '../../api/hackathon';
|
||||
import { api } from '../../api/index';
|
||||
|
||||
export const winnerKeys = {
|
||||
all: ['winners'] as const,
|
||||
@@ -7,34 +7,19 @@ export const winnerKeys = {
|
||||
};
|
||||
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
city: string;
|
||||
visibility: string;
|
||||
logo: string;
|
||||
banner: string;
|
||||
leader_id: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
id: string; name: string; description: string; city: string; visibility: string;
|
||||
logo: string; banner: string; leader_id: string; created_at: string; updated_at: string;
|
||||
}
|
||||
|
||||
interface Winner {
|
||||
id: string;
|
||||
team_id: string;
|
||||
team: Team;
|
||||
rank: number;
|
||||
prize: string;
|
||||
announced_at: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
id: string; team_id: string; team: Team; rank: number; prize: string;
|
||||
announced_at: string; created_at: string; updated_at: string;
|
||||
}
|
||||
|
||||
export const useWinners = () => {
|
||||
return useQuery<HackathonApiResponse<Winner[]>>({
|
||||
return useQuery({
|
||||
queryKey: winnerKeys.lists(),
|
||||
queryFn: async () => {
|
||||
const response = await hackathonApi.get('/winners');
|
||||
const response = await api.get<{ data: Winner[] }>('/v1/hackathon/winners');
|
||||
return response.data;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
import { AxiosError } from 'axios';
|
||||
|
||||
export type TPaginationMeta = {
|
||||
page: number;
|
||||
per_page: number;
|
||||
total?: number;
|
||||
total_pages?: number;
|
||||
has_next: boolean;
|
||||
has_prev: boolean;
|
||||
next_cursor?: string;
|
||||
prev_cursor?: string;
|
||||
};
|
||||
|
||||
export type TPaginationParams = {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
search?: string;
|
||||
sort_by?: string;
|
||||
order?: string;
|
||||
filter?: string;
|
||||
filter_by?: string;
|
||||
};
|
||||
|
||||
export type TApiSuccess<T> = {
|
||||
data: T;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type TApiPaginated<T> = {
|
||||
data: T[];
|
||||
meta: TPaginationMeta;
|
||||
version: string;
|
||||
};
|
||||
|
||||
export type TApiMessage = {
|
||||
message: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
// Legacy aliases kept for backward compatibility
|
||||
export type TMetaResponse = {
|
||||
page: number;
|
||||
per_page: number;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
export type TEventsListItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
detail_link: string;
|
||||
price: number;
|
||||
is_online: boolean;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
created_at: string;
|
||||
location?: string;
|
||||
is_deleted: boolean;
|
||||
};
|
||||
|
||||
export type TEventsDetailItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
detail_link: string;
|
||||
price: number;
|
||||
is_online: boolean;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
location?: string;
|
||||
};
|
||||
|
||||
export type TEventCreateRequest = {
|
||||
name: string;
|
||||
description: string;
|
||||
detail_link: string;
|
||||
price: number;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
location?: string;
|
||||
is_online: boolean;
|
||||
};
|
||||
|
||||
export type TEventUpdateRequest = TEventCreateRequest;
|
||||
@@ -1,11 +1,89 @@
|
||||
export type TGachaItem = {
|
||||
itemName: string;
|
||||
quantity: number;
|
||||
foto?: File;
|
||||
export type TGachaItemDto = {
|
||||
id: string;
|
||||
name: string;
|
||||
is_deleted: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TGachaRollItem = {
|
||||
itemName: string;
|
||||
quantity: number;
|
||||
chanceRate: number;
|
||||
export type TGachaItemCreateRequest = {
|
||||
item_code: string;
|
||||
name: string;
|
||||
description: string;
|
||||
rarity: string;
|
||||
type_: string;
|
||||
category: string;
|
||||
value: number;
|
||||
weight: number;
|
||||
stock: number;
|
||||
is_limited: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type TGachaItemUpdateRequest = {
|
||||
item_code?: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
rarity?: string;
|
||||
type_?: string;
|
||||
category?: string;
|
||||
value?: number;
|
||||
weight?: number;
|
||||
stock?: number;
|
||||
is_limited?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export type TGachaRollItemDto = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
gacha_id: string;
|
||||
item_id: string;
|
||||
weight: number;
|
||||
quantity: number;
|
||||
is_deleted: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TGachaRollCreateRequest = {
|
||||
item_id: string;
|
||||
weight: number;
|
||||
quantity: number;
|
||||
};
|
||||
|
||||
export type TGachaCreditDto = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
available_rolls: number;
|
||||
is_deleted: boolean;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TGachaCreditAddRequest = {
|
||||
amount: number;
|
||||
};
|
||||
|
||||
export type TGachaClaimDetailDto = {
|
||||
id: string;
|
||||
user: {
|
||||
id: string;
|
||||
fullname: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
};
|
||||
item: TGachaItemDto;
|
||||
is_deleted: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TGachaClaimCreateRequest = {
|
||||
user_id: string;
|
||||
item_id: string;
|
||||
};
|
||||
|
||||
// Legacy aliases kept for backward compatibility
|
||||
export type TGachaItem = TGachaItemDto;
|
||||
export type TGachaRollItem = TGachaRollItemDto;
|
||||
|
||||
@@ -6,3 +6,6 @@ export * from './permissions';
|
||||
export * from './mentors';
|
||||
export * from './teams';
|
||||
export * from './admin';
|
||||
export * from './events';
|
||||
export * from './testimonials';
|
||||
export * from './sessions';
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
export type TPermissionItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TPermissionCreateRequest = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type TPermissionUpdateRequest = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
@@ -1,17 +1,31 @@
|
||||
import { TPermissionItem } from '../permissions';
|
||||
|
||||
export type TRoleDetailItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions: TPermissionItem[];
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TRolesListItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
permissions_count: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TRoleDetailItem = {
|
||||
id: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
is_system_role?: boolean;
|
||||
is_default?: boolean;
|
||||
is_deleted?: boolean;
|
||||
permissions: TPermissionItem[];
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
};
|
||||
|
||||
export type TRoleCreateRequest = {
|
||||
name: string;
|
||||
permissions: string[];
|
||||
};
|
||||
|
||||
export type TRoleUpdateRequest = {
|
||||
name?: string;
|
||||
permissions?: string[];
|
||||
};
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
export type TSessionListItem = {
|
||||
id: string;
|
||||
mentor_id: string;
|
||||
mentee_id: string;
|
||||
mentee_fullname?: string;
|
||||
mentee_email?: string;
|
||||
topic: string;
|
||||
scheduled_at: string;
|
||||
duration_minutes: number;
|
||||
session_type: string;
|
||||
status: string;
|
||||
rating?: number;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type TSessionListResponse = {
|
||||
sessions: TSessionListItem[];
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type TBookSessionRequest = {
|
||||
topic: string;
|
||||
description?: string;
|
||||
scheduled_at: string;
|
||||
duration_minutes?: number;
|
||||
session_type?: string;
|
||||
};
|
||||
|
||||
export type TBookSessionResponse = {
|
||||
id: string;
|
||||
mentor_id: string;
|
||||
mentee_id: string;
|
||||
topic: string;
|
||||
description?: string;
|
||||
scheduled_at: string;
|
||||
duration_minutes: number;
|
||||
session_type: string;
|
||||
status: string;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
export type TUpdateSessionStatusRequest = {
|
||||
status: string;
|
||||
meeting_link?: string;
|
||||
};
|
||||
|
||||
export type TUpdateSessionStatusResponse = {
|
||||
id: string;
|
||||
status: string;
|
||||
meeting_link?: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TSessionFeedbackRequest = {
|
||||
feedback: string;
|
||||
rating: number;
|
||||
};
|
||||
|
||||
export type TSessionFeedbackResponse = {
|
||||
id: string;
|
||||
feedback: string;
|
||||
rating: number;
|
||||
submitted_at: string;
|
||||
};
|
||||
|
||||
export type TAvailabilitySlot = {
|
||||
date: string;
|
||||
time: string;
|
||||
available: boolean;
|
||||
};
|
||||
|
||||
export type TMentorAvailability = {
|
||||
mentor_id: string;
|
||||
availability_commitment: string;
|
||||
preferred_formats: string[];
|
||||
slots: TAvailabilitySlot[];
|
||||
booked_dates: string[];
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
export type TTestimonialsListItem = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user_fullname: string;
|
||||
role: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
is_deleted: boolean;
|
||||
};
|
||||
|
||||
export type TTestimonialsDetailItem = {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user_fullname: string;
|
||||
role: string;
|
||||
content: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TTestimonialCreateRequest = {
|
||||
role: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type TTestimonialUpdateRequest = TTestimonialCreateRequest;
|
||||
@@ -1,18 +1,101 @@
|
||||
import { TRoleDetailItem } from '../roles';
|
||||
|
||||
export type TExperienceDto = {
|
||||
id: string;
|
||||
company: string;
|
||||
position: string;
|
||||
duration: string;
|
||||
period: string;
|
||||
};
|
||||
|
||||
export type TEducationDto = {
|
||||
id: string;
|
||||
institution: string;
|
||||
degree: string;
|
||||
field: string;
|
||||
period: string;
|
||||
};
|
||||
|
||||
export type TUserProfileExtension = {
|
||||
phone_number?: string;
|
||||
phone_for_verification?: string;
|
||||
gender?: string;
|
||||
birthdate?: string;
|
||||
domicile?: string;
|
||||
bio?: string;
|
||||
last_education?: string;
|
||||
linkedin_url?: string;
|
||||
github_url?: string;
|
||||
cv_url?: string;
|
||||
portfolio_url?: string;
|
||||
website_url?: string;
|
||||
twitter_url?: string;
|
||||
location?: string;
|
||||
skills?: string[];
|
||||
experience?: TExperienceDto[];
|
||||
education?: TEducationDto[];
|
||||
career_status?: string;
|
||||
};
|
||||
|
||||
export type TUserItem = {
|
||||
id: string;
|
||||
avatar: string;
|
||||
birthdate: string;
|
||||
avatar?: string;
|
||||
birthdate?: string;
|
||||
email: string;
|
||||
fullname: string;
|
||||
gender: string;
|
||||
gender?: string;
|
||||
is_active: boolean;
|
||||
phone_number: string;
|
||||
phone_number?: string;
|
||||
role: TRoleDetailItem;
|
||||
location?: string;
|
||||
bio?: string;
|
||||
skills?: string[];
|
||||
};
|
||||
|
||||
export type { UserDetailResponseDto, UserUpdateRequestDto } from '../../api/users';
|
||||
export type TUsersListItem = {
|
||||
id: string;
|
||||
role: string;
|
||||
fullname: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TUsersDetailItem = {
|
||||
id: string;
|
||||
role: TRoleDetailItem;
|
||||
fullname: string;
|
||||
legal_name?: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
is_active: boolean;
|
||||
profile_extension?: TUserProfileExtension;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
};
|
||||
|
||||
export type TUserCreateRequest = {
|
||||
email: string;
|
||||
password: string;
|
||||
fullname: string;
|
||||
is_active: boolean;
|
||||
role_id: string;
|
||||
avatar?: string;
|
||||
};
|
||||
|
||||
export type TUserUpdateRequest = {
|
||||
email?: string;
|
||||
password?: string;
|
||||
fullname?: string;
|
||||
legal_name?: string;
|
||||
is_active?: boolean;
|
||||
avatar?: string;
|
||||
role_id?: string;
|
||||
profile_extension?: TUserProfileExtension;
|
||||
};
|
||||
|
||||
// Legacy alias
|
||||
export type { TUsersDetailItem as UserDetailResponseDto };
|
||||
export type { TUserUpdateRequest as UserUpdateRequestDto };
|
||||
|
||||
Reference in New Issue
Block a user