fix(frontend): Export missing APIs for dashboard and backoffice

This commit is contained in:
Tiago Yamamoto 2025-12-23 19:37:47 -03:00
parent 3701daaeaf
commit 300c76a156

View file

@ -1,9 +1,7 @@
import { toast } from "sonner";
// import { useAuthStore } from "./store/auth-store"; // This file might not exist or be named differently
// For now, rely on localStorage directly as used in other functions
// import { useAuthStore } from "./store/auth-store";
import { Job } from "./types";
//const API_BASE_URL = "http://localhost:8521"; // Backend URL // Use this for dev
// const API_BASE_URL = "http://localhost:8521"; // Backend URL // Use this for dev
const API_BASE_URL = ""; // Use this for production/setup with proxy
/**
@ -17,7 +15,7 @@ function logCrudAction(action: string, entity: string, details?: any) {
* Generic API Request Wrapper
*/
async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
const token = localStorage.getItem("token"); // Or useAuthStore.getState().token
const token = localStorage.getItem("token");
const headers = {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
@ -31,12 +29,9 @@ async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promi
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
// Use toast for feedback
// toast.error(`Error: ${errorData.message || response.statusText}`);
throw new Error(errorData.message || `Request failed with status ${response.status}`);
}
// Handle 204 No Content
if (response.status === 204) {
return {} as T;
}
@ -44,6 +39,103 @@ async function apiRequest<T>(endpoint: string, options: RequestInit = {}): Promi
return response.json();
}
// --- Types ---
export interface ApiUser {
id: string;
name: string;
email: string;
role: string;
status: string;
created_at: string;
avatarUrl?: string; // Add this
}
export interface ApiJob {
id: string;
title: string;
companyName?: string;
companyId: string;
location: string;
type: string; // "full-time", etc.
workMode: string; // "remote", etc.
salaryMin?: number;
salaryMax?: number;
salaryType?: string;
description: string;
requirements: string; // Backend likely returns string, frontend might expect array? Warning here.
status: string;
createdAt: string;
isFeatured: boolean;
applicationCount?: number;
}
export interface AdminCompany {
id: string;
name: string;
email?: string;
slug: string;
active: boolean;
verified: boolean;
created_at: string;
}
export interface AdminJob {
id: number;
title: string;
companyName: string;
status: string;
createdAt: string;
}
export interface AdminRoleAccess {
role: string;
description: string;
actions: string[];
}
export interface AdminLoginAudit {
id: string;
identifier: string;
roles: string;
ipAddress: string;
createdAt: string;
}
export interface AdminTag {
id: number;
name: string;
category: "area" | "level" | "stack";
active: boolean;
}
export interface AdminCandidate {
id: number;
name: string;
email: string;
phone: string;
location: string;
title?: string;
experience?: string;
avatarUrl?: string;
bio?: string;
skills: string[];
applications: {
id: string;
jobTitle: string;
company: string;
status: string;
}[];
}
export interface AdminCandidateStats {
totalCandidates: number;
newCandidates: number;
activeApplications: number;
hiringRate: number;
}
// --- Auth API ---
export const authApi = {
login: (data: any) => {
@ -65,49 +157,94 @@ export const authApi = {
},
};
// --- Users (Admin) ---
export const adminUsersApi = {
list: (page = 1, limit = 10) => {
// --- Users API (General/Admin) ---
// Unified to match usage in users/page.tsx
export const usersApi = {
list: (params: { page: number; limit: number }) => {
const query = new URLSearchParams({
page: params.page.toString(),
limit: params.limit.toString(),
});
return apiRequest<{
data: any[];
total: number;
page: number;
limit: number;
totalPages: number;
}>(`/api/v1/users?page=${page}&limit=${limit}`);
data: ApiUser[];
pagination: { total: number; page: number; limit: number };
}>(`/api/v1/users?${query.toString()}`);
},
create: (data: any) => {
logCrudAction("create", "admin/users", data);
logCrudAction("create", "users", data);
return apiRequest<any>("/api/v1/users", {
method: "POST",
body: JSON.stringify(data),
});
},
update: (id: string, data: any) => {
logCrudAction("update", "admin/users", { id, ...data });
logCrudAction("update", "users", { id, ...data });
return apiRequest<any>(`/api/v1/users/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
});
},
updateStatus: (id: string, active: boolean) => {
logCrudAction("updateStatus", "admin/users", { id, active });
return apiRequest<void>(`/api/v1/users/${id}`, {
method: "PATCH",
body: JSON.stringify({ active }),
});
},
delete: (id: string) => {
logCrudAction("delete", "admin/users", { id });
logCrudAction("delete", "users", { id });
return apiRequest<void>(`/api/v1/users/${id}`, {
method: "DELETE",
});
},
};
export const adminUsersApi = usersApi; // Alias for backward compatibility if needed
// --- Admin Backoffice API ---
export const adminAccessApi = {
listRoles: () => apiRequest<AdminRoleAccess[]>("/api/v1/admin/access/roles"),
};
export const adminAuditApi = {
listLogins: (limit = 20) => apiRequest<AdminLoginAudit[]>(`/api/v1/admin/audit/logins?limit=${limit}`),
};
export const adminJobsApi = {
list: (params: { status?: string; page?: number; limit?: number }) => {
const query = new URLSearchParams();
if (params.status) query.append("status", params.status);
if (params.page) query.append("page", params.page.toString());
if (params.limit) query.append("limit", params.limit.toString());
return apiRequest<{ data: AdminJob[]; pagination: any }>(`/api/v1/admin/jobs?${query.toString()}`);
},
updateStatus: (id: number, status: string) =>
apiRequest<void>(`/api/v1/admin/jobs/${id}/status`, {
method: "PATCH",
body: JSON.stringify({ status }),
}),
duplicate: (id: number) =>
apiRequest<void>(`/api/v1/admin/jobs/${id}/duplicate`, {
method: "POST",
}),
};
export const adminTagsApi = {
list: (category?: string) => {
const query = category ? `?category=${category}` : "";
return apiRequest<AdminTag[]>(`/api/v1/admin/tags${query}`);
},
create: (data: { name: string; category: string }) =>
apiRequest<AdminTag>("/api/v1/admin/tags", {
method: "POST",
body: JSON.stringify(data),
}),
update: (id: number, data: { name?: string; active?: boolean }) =>
apiRequest<AdminTag>(`/api/v1/admin/tags/${id}`, {
method: "PATCH",
body: JSON.stringify(data),
}),
};
export const adminCandidatesApi = {
list: () => apiRequest<{ candidates: AdminCandidate[]; stats: AdminCandidateStats }>("/api/v1/admin/candidates"),
};
// --- Companies (Admin) ---
// Note: Backend endpoint is /api/v1/companies for listing (likely public or admin) and creation
// Assuming specific admin endpoints might be added later, for now using existing
export const adminCompaniesApi = {
list: (verified?: boolean, page = 1, limit = 10) => {
const query = new URLSearchParams({
@ -116,7 +253,7 @@ export const adminCompaniesApi = {
...(verified !== undefined && { verified: verified.toString() })
});
return apiRequest<{
data: any[];
data: AdminCompany[];
pagination: {
page: number;
limit: number;
@ -133,7 +270,7 @@ export const adminCompaniesApi = {
},
updateStatus: (id: number, data: { active?: boolean; verified?: boolean }) => {
logCrudAction("update", "admin/companies", { id, ...data });
return apiRequest<void>(`/api/v1/companies/${id}/status`, { // Ensure route exists
return apiRequest<void>(`/api/v1/admin/companies/${id}/status`, {
method: "PATCH",
body: JSON.stringify(data),
});
@ -146,6 +283,70 @@ export const adminCompaniesApi = {
}
};
// --- Jobs API (Public/Candidate) ---
export const jobsApi = {
list: (params: {
page?: number;
limit?: number;
q?: string;
location?: string;
type?: string;
workMode?: string;
}) => {
const query = new URLSearchParams();
if (params.page) query.append("page", params.page.toString());
if (params.limit) query.append("limit", params.limit.toString());
if (params.q) query.append("q", params.q);
if (params.location) query.append("location", params.location);
if (params.type) query.append("type", params.type);
if (params.workMode) query.append("workMode", params.workMode);
return apiRequest<{
data: ApiJob[];
pagination: { total: number; page: number; limit: number };
}>(`/api/v1/jobs?${query.toString()}`);
},
getById: (id: string) => apiRequest<ApiJob>(`/api/v1/jobs/${id}`),
};
export const applicationsApi = {
create: (data: any) =>
apiRequest<void>("/api/v1/applications", {
method: "POST",
body: JSON.stringify(data),
}),
};
// --- Helper Functions ---
export function transformApiJobToFrontend(apiJob: ApiJob): Job {
// Requirements might come as a string derived from DB
let reqs: string[] = [];
if (apiJob.requirements) {
// Assuming it might be a JSON string or just text
// Simple split by newline for now if it's a block of text
reqs = apiJob.requirements.split('\n').filter(Boolean);
}
return {
id: apiJob.id,
title: apiJob.title,
company: apiJob.companyName || "Unknown Company",
location: apiJob.location,
type: (apiJob.type as any) || "full-time",
workMode: (apiJob.workMode as any) || "onsite",
salary: apiJob.salaryMin && apiJob.salaryMax
? `$${apiJob.salaryMin} - $${apiJob.salaryMax}`
: apiJob.salaryMin
? `$${apiJob.salaryMin}+`
: undefined,
description: apiJob.description,
requirements: reqs,
postedAt: apiJob.createdAt,
isFeatured: apiJob.isFeatured,
};
}
// --- Notifications ---
export interface Notification {
id: string;