chore: upgrade dependencies, restructure shared libs, and fix UI
- Upgrade Nx 22.1.1 → 22.6.3 and all patch/minor dependencies - Restructure shared libs: move business logic from utils to service - Consolidate shadcn-ui into ui lib with atomic design pattern - Fix container centering for landing app (Tailwind v4 compatibility) - Fix button styling by updating @source directive in globals.css - Fix SiCss3 → SiCss rename in react-icons 5.6 - Fix duplicate useSession export conflict - Remove dead code, comments, and unused files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
f68d97188c
commit
3f4461c65c
@@ -7,7 +7,6 @@ import type {
|
||||
|
||||
const ADMIN_BASE_URL = '/admin';
|
||||
|
||||
// Admin Users
|
||||
export const getAdminUsers = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
@@ -26,7 +25,6 @@ export const getAdminUsers = async (params?: {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Admin Teams
|
||||
export const getAdminTeams = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
@@ -39,7 +37,6 @@ export const getAdminTeams = async (params?: {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Admin Submissions
|
||||
export const getAdminSubmissions = async (params?: {
|
||||
page?: number;
|
||||
per_page?: number;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { api } from '../';
|
||||
import { api, getBaseURL } from '../';
|
||||
import {
|
||||
TLoginRequest,
|
||||
TLoginResponse,
|
||||
@@ -54,7 +54,7 @@ export const postSendOtp = async (
|
||||
};
|
||||
|
||||
export const getGoogleAuthUrl = async (): Promise<string> => {
|
||||
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
const baseUrl = getBaseURL() || 'http://localhost:8080';
|
||||
return `${baseUrl}/auth/google/login`;
|
||||
};
|
||||
|
||||
@@ -67,7 +67,7 @@ export const postGoogleCallback = async (code: string, state: string): Promise<T
|
||||
};
|
||||
|
||||
export const getGitHubAuthUrl = async (): Promise<string> => {
|
||||
const baseUrl = import.meta.env.VITE_API_URL || 'http://localhost:8080';
|
||||
const baseUrl = getBaseURL() || 'http://localhost:8080';
|
||||
return `${baseUrl}/auth/github/login`;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../hooks/auth';
|
||||
|
||||
// Backoffice Backend API Base URL
|
||||
// In development, use proxy; in production, use full URL
|
||||
const BACKOFFICE_API_URL = 'https://api.hackathon.imphnen.dev/api/v1';
|
||||
|
||||
// Create axios instance for backoffice backend
|
||||
export const backofficeApi = axios.create({
|
||||
baseURL: BACKOFFICE_API_URL,
|
||||
headers: {
|
||||
@@ -13,7 +10,6 @@ export const backofficeApi = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth token interceptor
|
||||
backofficeApi.interceptors.request.use(
|
||||
(config) => {
|
||||
const { session } = useAuthStore.getState();
|
||||
@@ -27,11 +23,9 @@ backofficeApi.interceptors.request.use(
|
||||
}
|
||||
);
|
||||
|
||||
// Error handling interceptor
|
||||
backofficeApi.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
// Handle 401 - clear session and redirect to login
|
||||
if (error.response?.status === 401) {
|
||||
const isAuthPage =
|
||||
globalThis.window !== undefined &&
|
||||
@@ -45,18 +39,15 @@ backofficeApi.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
|
||||
// If backend sends a message, use it
|
||||
const backendMsg = error?.response?.data?.message;
|
||||
if (backendMsg && typeof backendMsg === 'string') {
|
||||
return Promise.reject(new Error(backendMsg));
|
||||
}
|
||||
|
||||
// Fallback error message
|
||||
return Promise.reject(new Error(error.message || 'An error occurred'));
|
||||
}
|
||||
);
|
||||
|
||||
// Response type
|
||||
export interface BackofficeApiResponse<T> {
|
||||
data: T;
|
||||
message: string;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import axios from 'axios';
|
||||
import { useAuthStore } from '../hooks/auth';
|
||||
|
||||
// Hackathon Backend API Base URL
|
||||
const HACKATHON_API_URL = 'https://api.hackathon.imphnen.dev/api/v1';
|
||||
|
||||
// Create axios instance for hackathon backend
|
||||
export const hackathonApi = axios.create({
|
||||
baseURL: HACKATHON_API_URL,
|
||||
headers: {
|
||||
@@ -12,7 +10,6 @@ export const hackathonApi = axios.create({
|
||||
},
|
||||
});
|
||||
|
||||
// Add auth token interceptor
|
||||
hackathonApi.interceptors.request.use(
|
||||
(config) => {
|
||||
const { session } = useAuthStore.getState();
|
||||
@@ -26,17 +23,13 @@ hackathonApi.interceptors.request.use(
|
||||
}
|
||||
);
|
||||
|
||||
// Error handling interceptor
|
||||
hackathonApi.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
// Handle 401 - clear session and redirect to login
|
||||
// But skip redirect if already on auth pages or certificate pages (to avoid reload on login failure)
|
||||
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/');
|
||||
|
||||
// Only clear session and redirect if not on auth page or certificate page
|
||||
if (!isAuthPage && !isCertificatePage) {
|
||||
useAuthStore.getState().clearSession();
|
||||
if (globalThis.window !== undefined) {
|
||||
@@ -45,7 +38,6 @@ hackathonApi.interceptors.response.use(
|
||||
}
|
||||
}
|
||||
|
||||
// If backend sends a message, use it
|
||||
const backendMsg = error?.response?.data?.message;
|
||||
if (backendMsg && typeof backendMsg === 'string') {
|
||||
return Promise.reject(new Error(backendMsg));
|
||||
@@ -55,7 +47,6 @@ hackathonApi.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
// API Response wrapper type
|
||||
export interface HackathonApiResponse<T> {
|
||||
data: T;
|
||||
message?: string;
|
||||
|
||||
@@ -8,7 +8,6 @@ export * from './upload';
|
||||
export * from './hackathon';
|
||||
export * from './admin';
|
||||
|
||||
// Common API response wrapper interface
|
||||
export interface ApiResponse<T> {
|
||||
data: T;
|
||||
version: string;
|
||||
@@ -16,7 +15,6 @@ export interface ApiResponse<T> {
|
||||
|
||||
const TOKEN_KEY = 'token';
|
||||
|
||||
// Helper functions for session management (avoiding circular dependency)
|
||||
const getSessionTokenFromCookies = () => {
|
||||
if (typeof document === 'undefined') return null;
|
||||
|
||||
@@ -54,13 +52,25 @@ const removeSessionTokenFromCookies = () => {
|
||||
document.cookie = `${TOKEN_KEY}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
|
||||
};
|
||||
|
||||
export const getBaseURL = () => {
|
||||
if (typeof process !== 'undefined' && process.env.NEXT_PUBLIC_API_URL) {
|
||||
return process.env.NEXT_PUBLIC_API_URL;
|
||||
}
|
||||
try {
|
||||
const meta = import.meta as unknown as Record<string, Record<string, string>>;
|
||||
if (meta.env?.VITE_API_URL) return meta.env.VITE_API_URL;
|
||||
} catch {
|
||||
// not in Vite context
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
const config: AxiosRequestConfig = {
|
||||
baseURL: import.meta.env.VITE_API_URL,
|
||||
baseURL: getBaseURL(),
|
||||
};
|
||||
|
||||
export const api = axios.create(config);
|
||||
|
||||
// Add request interceptor to include authentication token
|
||||
api.interceptors.request.use(
|
||||
(config) => {
|
||||
const sessionData = getSessionTokenFromCookies();
|
||||
@@ -77,7 +87,6 @@ api.interceptors.request.use(
|
||||
}
|
||||
);
|
||||
|
||||
// Add response interceptor to handle token refresh
|
||||
api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
@@ -88,7 +97,6 @@ api.interceptors.response.use(
|
||||
return handleTokenRefresh(originalRequest);
|
||||
}
|
||||
|
||||
// If backend sends a message, use it
|
||||
const backendMsg = error?.response?.data?.message;
|
||||
if (backendMsg && typeof backendMsg === 'string') {
|
||||
return Promise.reject(new Error(backendMsg));
|
||||
@@ -98,7 +106,6 @@ api.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
// Helper function to handle token refresh
|
||||
async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
||||
const sessionData = getSessionTokenFromCookies();
|
||||
const refreshToken = sessionData?.token?.refresh_token;
|
||||
@@ -112,7 +119,6 @@ async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
||||
const response = await refreshAccessToken(refreshToken);
|
||||
|
||||
if (response.data?.access_token) {
|
||||
// Update session in cookies
|
||||
setSessionTokenToCookies({
|
||||
token: {
|
||||
access_token: response.data.access_token,
|
||||
@@ -120,7 +126,6 @@ async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
||||
},
|
||||
});
|
||||
|
||||
// Retry original request with new token
|
||||
originalRequest.headers ??= {};
|
||||
originalRequest.headers.Authorization = `Bearer ${response.data.access_token}`;
|
||||
return api(originalRequest);
|
||||
@@ -134,14 +139,12 @@ async function handleTokenRefresh(originalRequest: AxiosRequestConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to refresh access token
|
||||
async function refreshAccessToken(refreshToken: string) {
|
||||
return axios.post(`${import.meta.env.VITE_API_URL}/auth/refresh`, {
|
||||
return axios.post(`${getBaseURL()}/auth/refresh`, {
|
||||
refresh_token: refreshToken,
|
||||
});
|
||||
}
|
||||
|
||||
// Helper function to clear session and redirect
|
||||
function clearSessionAndRedirect() {
|
||||
removeSessionTokenFromCookies();
|
||||
if (typeof window !== 'undefined') {
|
||||
|
||||
@@ -16,7 +16,6 @@ import type {
|
||||
|
||||
const TEAMS_BASE_URL = '/teams';
|
||||
|
||||
// Team CRUD
|
||||
export const getTeams = async (params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
@@ -43,7 +42,6 @@ export const updateTeam = async (teamId: string, data: TUpdateTeamRequest) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Team Members
|
||||
export const getTeamMembers = async (teamId: string) => {
|
||||
const response = await api.get<TTeamMembersResponse>(`${TEAMS_BASE_URL}/${teamId}/members`);
|
||||
return response.data;
|
||||
@@ -64,7 +62,6 @@ export const removeMember = async (teamId: string, userId: string) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Join Requests
|
||||
export const joinTeam = async (teamId: string, data: TJoinTeamRequest) => {
|
||||
const response = await api.post(`${TEAMS_BASE_URL}/${teamId}/join-request`, data);
|
||||
return response.data;
|
||||
@@ -80,7 +77,6 @@ export const respondToJoinRequest = async (teamId: string, requestId: string, ac
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Invitations
|
||||
export const getMyInvitations = async () => {
|
||||
const response = await api.get<TTeamInvitationsResponse>(`${TEAMS_BASE_URL}/invitations/me`);
|
||||
return response.data;
|
||||
@@ -91,13 +87,11 @@ export const respondToInvitation = async (invitationId: string, action: 'accept'
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// User's Teams
|
||||
export const getMyTeams = async () => {
|
||||
const response = await api.get<TTeamListResponse>(`${TEAMS_BASE_URL}/me`);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
// Project Submission
|
||||
export const submitProject = async (teamId: string, data: TSubmitProjectRequest) => {
|
||||
const response = await api.post<TProjectSubmissionResponse>(`${TEAMS_BASE_URL}/${teamId}/submission`, data);
|
||||
return response.data;
|
||||
|
||||
@@ -32,13 +32,11 @@ export const uploadService: UploadService = {
|
||||
},
|
||||
|
||||
async uploadAvatar(file: File) {
|
||||
// Validate file type
|
||||
if (!file.type.startsWith('image/')) {
|
||||
throw new Error('File harus berupa gambar');
|
||||
}
|
||||
|
||||
// Validate file size (max 5MB for images)
|
||||
const maxSize = 5 * 1024 * 1024; // 5MB
|
||||
const maxSize = 5 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('Ukuran file maksimal 5MB');
|
||||
}
|
||||
@@ -47,13 +45,11 @@ export const uploadService: UploadService = {
|
||||
},
|
||||
|
||||
async uploadCV(file: File) {
|
||||
// Validate file type
|
||||
if (file.type !== 'application/pdf') {
|
||||
throw new Error('CV harus berupa file PDF');
|
||||
}
|
||||
|
||||
// Validate file size (max 10MB for PDFs)
|
||||
const maxSize = 10 * 1024 * 1024; // 10MB
|
||||
const maxSize = 10 * 1024 * 1024;
|
||||
if (file.size > maxSize) {
|
||||
throw new Error('Ukuran file maksimal 10MB');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user