feat: global ui & functional update
This commit is contained in:
@@ -15,13 +15,14 @@ import type {
|
||||
OverviewAnalyticsQueryParams,
|
||||
ProjectsAnalyticsResponse,
|
||||
ProjectsAnalyticsQueryParams,
|
||||
PlacementsAnalyticsQueryParams,
|
||||
} from "@/lib/types/api";
|
||||
|
||||
export const analyticsApi = {
|
||||
/**
|
||||
* Get placements analytics
|
||||
*/
|
||||
placements: (workspaceId: string, params?: AnalyticsQueryParams) =>
|
||||
placements: (workspaceId: string, params?: PlacementsAnalyticsQueryParams) =>
|
||||
api.get<PaginatedResponse<PlacementAnalyticsItem>>(
|
||||
`/workspaces/${workspaceId}/analytics/placements`,
|
||||
{ params }
|
||||
|
||||
@@ -81,15 +81,20 @@ export const apiRequest = async <T = any>(
|
||||
options: RequestOptions = {}
|
||||
): Promise<T> => {
|
||||
const { params, requireAuth = true, ...fetchOptions } = options;
|
||||
const body = fetchOptions.body;
|
||||
|
||||
// Build URL
|
||||
const url = buildUrl(`${API_URL}${endpoint}`, params);
|
||||
|
||||
const headers: HeadersInit = {
|
||||
"Content-Type": "application/json",
|
||||
...fetchOptions.headers,
|
||||
};
|
||||
|
||||
// Don't set Content-Type for FormData - browser will set it with boundary
|
||||
if (!(body instanceof FormData)) {
|
||||
(headers as Record<string, string>)["Content-Type"] = "application/json";
|
||||
}
|
||||
|
||||
// Add auth token if required
|
||||
if (requireAuth) {
|
||||
const token = getAuthToken();
|
||||
@@ -99,9 +104,16 @@ export const apiRequest = async <T = any>(
|
||||
}
|
||||
|
||||
try {
|
||||
// Serialize body to JSON if it's an object (not FormData or string)
|
||||
let serializedBody = body;
|
||||
if (body && typeof body === 'object' && !(body instanceof FormData) && !(body instanceof String)) {
|
||||
serializedBody = JSON.stringify(body);
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
body: serializedBody,
|
||||
});
|
||||
|
||||
// Handle 204 No Content
|
||||
@@ -145,21 +157,21 @@ export const api = {
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "POST",
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
body,
|
||||
}),
|
||||
|
||||
put: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "PUT",
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
body,
|
||||
}),
|
||||
|
||||
patch: <T = any>(endpoint: string, body?: any, options?: RequestOptions) =>
|
||||
apiRequest<T>(endpoint, {
|
||||
...options,
|
||||
method: "PATCH",
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
body,
|
||||
}),
|
||||
|
||||
delete: <T = any>(endpoint: string, options?: RequestOptions) =>
|
||||
|
||||
@@ -21,6 +21,15 @@ export const creativesApi = {
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get list of creatives for a specific project
|
||||
*/
|
||||
listByProject: (workspaceId: string, projectId: string) =>
|
||||
api.get<PaginatedResponse<Creative>>(
|
||||
`/workspaces/${workspaceId}/creatives`,
|
||||
{ params: { project_id: projectId } }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get single creative
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { api } from "./client";
|
||||
import type {
|
||||
Placement,
|
||||
PlacementWithStats,
|
||||
PlacementCreateRequest,
|
||||
PlacementUpdateRequest,
|
||||
PlacementsQueryParams,
|
||||
@@ -23,6 +24,13 @@ export const placementsApi = {
|
||||
{ params }
|
||||
),
|
||||
|
||||
/**
|
||||
* Get placements for a specific project (grouped by channel UI)
|
||||
*/
|
||||
getByProject: (workspaceId: string, projectId: string) =>
|
||||
api.get<{ placements: Placement[] }>(`/workspaces/${workspaceId}/projects/${projectId}/placements`)
|
||||
.then((response) => response.placements),
|
||||
|
||||
/**
|
||||
* Get single placement
|
||||
*/
|
||||
@@ -35,6 +43,46 @@ export const placementsApi = {
|
||||
create: (workspaceId: string, data: PlacementCreateRequest) =>
|
||||
api.post<Placement>(`/workspaces/${workspaceId}/placements`, data),
|
||||
|
||||
/**
|
||||
* Create placements for multiple channels (bulk creation)
|
||||
*/
|
||||
createBulk: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
data: {
|
||||
creative_id?: string;
|
||||
channels: Array<{
|
||||
username: string;
|
||||
status?: string;
|
||||
comment?: string;
|
||||
details?: {
|
||||
placement_at?: string;
|
||||
payment_at?: string;
|
||||
cost?: { type: string; value: number };
|
||||
cost_before_bargain?: number;
|
||||
placement_type?: string;
|
||||
format?: string;
|
||||
comment?: string;
|
||||
creative_id?: string;
|
||||
};
|
||||
}>;
|
||||
details?: {
|
||||
placement_at?: string;
|
||||
payment_at?: string;
|
||||
cost?: { type: string; value: number };
|
||||
cost_before_bargain?: number;
|
||||
placement_type?: string;
|
||||
format?: string;
|
||||
comment?: string;
|
||||
creative_id?: string;
|
||||
};
|
||||
}
|
||||
) =>
|
||||
api.post<{ placements: Placement[] }>(
|
||||
`/workspaces/${workspaceId}/projects/${projectId}/placements`,
|
||||
data
|
||||
),
|
||||
|
||||
/**
|
||||
* Update placement
|
||||
*/
|
||||
|
||||
@@ -9,6 +9,10 @@ export interface UpdateProjectInviteLinkTypeRequest {
|
||||
purchase_invite_type_default: "public" | "approval";
|
||||
}
|
||||
|
||||
export interface MoveProjectRequest {
|
||||
target_workspace_id: string;
|
||||
}
|
||||
|
||||
export const projectsApi = {
|
||||
/**
|
||||
* Get list of projects for workspace
|
||||
@@ -41,10 +45,31 @@ export const projectsApi = {
|
||||
`/workspaces/${workspaceId}/projects/${projectId}/archive`
|
||||
),
|
||||
|
||||
/**
|
||||
* Unarchive project
|
||||
*/
|
||||
unarchive: (workspaceId: string, projectId: string) =>
|
||||
api.post<Project>(
|
||||
`/workspaces/${workspaceId}/projects/${projectId}/unarchive`
|
||||
),
|
||||
|
||||
/**
|
||||
* Delete project (soft delete)
|
||||
*/
|
||||
delete: (workspaceId: string, projectId: string) =>
|
||||
api.delete(`/workspaces/${workspaceId}/projects/${projectId}`),
|
||||
|
||||
/**
|
||||
* Move project to another workspace
|
||||
*/
|
||||
move: (
|
||||
workspaceId: string,
|
||||
projectId: string,
|
||||
data: MoveProjectRequest
|
||||
) =>
|
||||
api.put<Project>(
|
||||
`/workspaces/${workspaceId}/projects/${projectId}/move`,
|
||||
data
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
@@ -37,5 +37,22 @@ export const workspacesApi = {
|
||||
*/
|
||||
delete: (workspaceId: string) =>
|
||||
api.delete<void>(`${BASE_PATH}/${workspaceId}`),
|
||||
|
||||
/**
|
||||
* Upload workspace avatar
|
||||
*/
|
||||
uploadAvatar: (workspaceId: string, file: File) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return api.post<Workspace>(`${BASE_PATH}/${workspaceId}/avatar`, formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete workspace avatar
|
||||
*/
|
||||
deleteAvatar: (workspaceId: string) =>
|
||||
api.delete<Workspace>(`${BASE_PATH}/${workspaceId}/avatar`),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user