feat: global platform v2 update

This commit is contained in:
ivannoskov
2025-12-29 10:12:13 +03:00
parent 8e6a1fa83f
commit f64cf02100
84 changed files with 11023 additions and 11486 deletions

View File

@@ -1,9 +1,8 @@
// ============================================================================
// API Client with Mock Support
// API Client
// ============================================================================
import { API_URL, USE_MOCKS, STORAGE_KEYS } from "@/lib/utils/constants";
import { mockApiHandlers } from "@/lib/mocks/handlers";
import { API_URL, STORAGE_KEYS } from "@/lib/utils/constants";
import type { ApiError } from "@/lib/types/api";
// ============================================================================
@@ -61,169 +60,13 @@ export const clearAuthTokens = (): void => {
*/
export const saveAuthTokens = (
accessToken: string,
refreshToken: string
refreshToken?: string
): void => {
if (typeof window === "undefined") return;
localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken);
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
};
// ============================================================================
// Mock Router
// ============================================================================
/**
* Route request to appropriate mock handler
*/
const routeMockRequest = async (
endpoint: string,
options: RequestOptions
): Promise<any> => {
const method = options.method || "GET";
const params = options.params || {};
const body = options.body ? JSON.parse(options.body as string) : undefined;
console.log(`[MOCK API] ${method} ${endpoint}`, { params, body });
// Auth endpoints
if (endpoint === "/auth/init") {
return mockApiHandlers.authInit();
if (refreshToken) {
localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken);
}
if (endpoint.startsWith("/auth/complete")) {
// Extract token from query params
const token =
params.token ||
(endpoint.includes("?")
? new URLSearchParams(endpoint.split("?")[1]).get("token")
: null);
return mockApiHandlers.authComplete({ token });
}
if (endpoint === "/auth/refresh") {
return mockApiHandlers.authRefresh(body);
}
if (endpoint === "/auth/me") {
return mockApiHandlers.authMe();
}
// Target Channels
if (endpoint === "/target_channels" && method === "GET") {
return mockApiHandlers.getTargetChannels();
}
if (endpoint.startsWith("/target_channels/") && method === "DELETE") {
const id = endpoint.split("/")[2];
return mockApiHandlers.deleteTargetChannel(id);
}
// External Channels
if (endpoint.startsWith("/external_channels/target/") && method === "GET") {
const targetChannelId = endpoint.split("/")[3];
return mockApiHandlers.getExternalChannels(targetChannelId);
}
if (endpoint === "/external_channels" && method === "POST") {
return mockApiHandlers.createExternalChannel(body);
}
if (
endpoint.startsWith("/external_channels/") &&
!endpoint.includes("/target/") &&
!endpoint.includes("/links") &&
method === "GET"
) {
const id = endpoint.split("/")[2];
return mockApiHandlers.getExternalChannel(id);
}
if (
endpoint.startsWith("/external_channels/") &&
endpoint.includes("/links") &&
method === "PATCH"
) {
const id = endpoint.split("/")[2];
return mockApiHandlers.updateExternalChannelLinks(id, body);
}
if (
endpoint.startsWith("/external_channels/") &&
!endpoint.includes("/links") &&
method === "PATCH"
) {
const id = endpoint.split("/")[2];
return mockApiHandlers.updateExternalChannel(id, body);
}
if (endpoint.startsWith("/external_channels/") && method === "DELETE") {
const id = endpoint.split("/")[2];
return mockApiHandlers.deleteExternalChannel(id);
}
// Creatives
if (endpoint === "/creatives" && method === "GET") {
return mockApiHandlers.getCreatives(params);
}
if (endpoint === "/creatives" && method === "POST") {
return mockApiHandlers.createCreative(body);
}
if (endpoint.startsWith("/creatives/") && method === "GET") {
const id = endpoint.split("/")[2];
return mockApiHandlers.getCreative(id);
}
if (endpoint.startsWith("/creatives/") && method === "PATCH") {
const id = endpoint.split("/")[2];
return mockApiHandlers.updateCreative(id, body);
}
if (endpoint.startsWith("/creatives/") && method === "DELETE") {
const id = endpoint.split("/")[2];
return mockApiHandlers.deleteCreative(id);
}
// Placements
if (endpoint === "/placements" && method === "GET") {
return mockApiHandlers.getPlacements(params);
}
if (endpoint === "/placements" && method === "POST") {
return mockApiHandlers.createPlacement(body);
}
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "GET") {
const id = endpoint.split("/")[2];
return mockApiHandlers.getPlacement(id);
}
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "PATCH") {
const id = endpoint.split("/")[2];
return mockApiHandlers.updatePlacement(id, body);
}
if (endpoint.match(/^\/placements\/[^\/]+$/) && method === "DELETE") {
const id = endpoint.split("/")[2];
return mockApiHandlers.deletePlacement(id);
}
if (endpoint.includes("/views/fetch") && method === "POST") {
const id = endpoint.split("/")[2];
return mockApiHandlers.fetchPlacementViews(id);
}
if (endpoint.includes("/views/history") && method === "GET") {
const id = endpoint.split("/")[2];
return mockApiHandlers.getPlacementViewsHistory(id, params);
}
if (endpoint.includes("/views/manual") && method === "POST") {
const id = endpoint.split("/")[2];
return mockApiHandlers.setPlacementViewsManually(id, params?.views_count);
}
// Analytics
if (endpoint === "/analytics/spending") {
return mockApiHandlers.getSpendingAnalytics(params);
}
if (endpoint === "/analytics/placements") {
return mockApiHandlers.getPlacementsAnalytics(params);
}
if (endpoint === "/analytics/creatives") {
return mockApiHandlers.getCreativesAnalytics(params);
}
if (endpoint === "/analytics/external-channels") {
return mockApiHandlers.getExternalChannelsAnalytics(params);
}
throw {
error: {
code: "NOT_IMPLEMENTED",
message: `Mock handler not implemented for ${method} ${endpoint}`,
},
};
};
// ============================================================================
@@ -231,7 +74,7 @@ const routeMockRequest = async (
// ============================================================================
/**
* Main API request function with mock support
* Main API request function
*/
export const apiRequest = async <T = any>(
endpoint: string,
@@ -239,17 +82,7 @@ export const apiRequest = async <T = any>(
): Promise<T> => {
const { params, requireAuth = true, ...fetchOptions } = options;
// Use mocks if enabled
if (USE_MOCKS) {
try {
const result = await routeMockRequest(endpoint, options);
return result as T;
} catch (error) {
throw error;
}
}
// Real API request
// Build URL
const url = buildUrl(`${API_URL}${endpoint}`, params);
const headers: HeadersInit = {
@@ -271,45 +104,29 @@ export const apiRequest = async <T = any>(
headers,
});
// Handle 204 No Content
if (response.status === 204) {
return undefined as T;
}
// Parse response
const data = await response.json();
// Handle errors
if (!response.ok) {
// FastAPI returns errors in format { "detail": "..." }
// Transform to our ApiError format
if (data.detail) {
throw {
error: {
code:
response.status === 401
? "UNAUTHORIZED"
: response.status === 403
? "FORBIDDEN"
: response.status === 404
? "NOT_FOUND"
: response.status === 422
? "VALIDATION_ERROR"
: "API_ERROR",
message: data.detail,
},
detail: data.detail,
};
}
// If it's already in our format, throw as is
const error: ApiError = data;
const error: ApiError = {
detail: data.detail || "An error occurred",
};
throw error;
}
return data as T;
} catch (error: any) {
// Network error or parsing error
if (!error.error) {
if (!error.detail) {
throw {
error: {
code: "NETWORK_ERROR",
message: error.message || "Network error occurred",
},
detail: error.message || "Network error occurred",
} as ApiError;
}
throw error;
@@ -328,47 +145,23 @@ export const api = {
apiRequest<T>(endpoint, {
...options,
method: "POST",
body: JSON.stringify(body),
body: body ? JSON.stringify(body) : undefined,
}),
put: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
apiRequest<T>(endpoint, {
...options,
method: "PUT",
body: body ? JSON.stringify(body) : undefined,
}),
patch: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
apiRequest<T>(endpoint, {
...options,
method: "PATCH",
body: JSON.stringify(body),
body: body ? JSON.stringify(body) : undefined,
}),
delete: <T = any>(endpoint: string, options?: RequestOptions) =>
apiRequest<T>(endpoint, { ...options, method: "DELETE" }),
// Special method for file upload
upload: async <T = any>(
endpoint: string,
formData: FormData,
options?: RequestOptions
): Promise<T> => {
// File import is now done on client side, no need for mock handling
const url = `${API_URL}${endpoint}`;
const token = getAuthToken();
const headers: HeadersInit = {};
if (token) {
headers["Authorization"] = `Bearer ${token}`;
}
const response = await fetch(url, {
method: "POST",
headers,
body: formData,
...options,
});
const data = await response.json();
if (!response.ok) {
throw data as ApiError;
}
return data as T;
},
};