fix(frontend): Export missing APIs for dashboard and backoffice
This commit is contained in:
parent
3701daaeaf
commit
300c76a156
1 changed files with 232 additions and 31 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue