Enhance ApiStatusIndicator to conditionally render for aws-admin accounts only. Improve error handling in API response management across various components, ensuring expected 400 responses are logged appropriately. Update Credits and ApiMonitor components to handle null values gracefully and implement CRUD operations for planner and writer endpoints.

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-15 21:44:10 +00:00
parent a75ebf2584
commit 5a3706d997
4 changed files with 208 additions and 25 deletions

View File

@@ -236,14 +236,15 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
if (contentType.includes('application/json')) {
try {
errorData = JSON.parse(text);
errorMessage = errorData.error || errorData.message || errorData.detail || errorMessage;
// If the response has a success field set to false, return it as-is
// This allows callers to handle structured error responses
if (errorData.success === false && errorData.error) {
// Return the error response object instead of throwing
// This is a special case for structured error responses
return errorData;
// Handle unified error format: {success: false, error: "...", errors: {...}}
if (errorData.success === false) {
// Extract error message from unified format
errorMessage = errorData.error || errorData.message || errorData.detail || errorMessage;
// Keep errorData for structured error handling
} else {
// Old format or other error structure
errorMessage = errorData.error || errorData.message || errorData.detail || errorMessage;
}
// Classify error type
@@ -314,12 +315,47 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
}
// Parse JSON response
let parsedResponse;
try {
return JSON.parse(text);
parsedResponse = JSON.parse(text);
} catch (e) {
// If JSON parsing fails, return text
return text;
}
// Handle unified API response format
// Paginated responses: {success: true, count: X, results: [...], next: ..., previous: ...}
// Single object/list responses: {success: true, data: {...}}
// Error responses: {success: false, error: "...", errors: {...}}
// If it's a unified format response with success field
if (parsedResponse && typeof parsedResponse === 'object' && 'success' in parsedResponse) {
// For paginated responses, return as-is (results is at top level)
if ('results' in parsedResponse && 'count' in parsedResponse) {
return parsedResponse;
}
// For single object/list responses, extract data field
if ('data' in parsedResponse) {
return parsedResponse.data;
}
// Error responses should have been thrown already in !response.ok block above
// If we somehow get here with an error response (shouldn't happen), throw it
if (parsedResponse.success === false) {
const errorMsg = parsedResponse.error || parsedResponse.message || 'Request failed';
const apiError = new Error(`API Error: ${errorMsg}`);
(apiError as any).response = parsedResponse;
(apiError as any).status = 400;
throw apiError;
}
// If success is true but no data/results, return the whole response
return parsedResponse;
}
// Not a unified format response, return as-is (backward compatibility)
return parsedResponse;
} catch (error: any) {
clearTimeout(timeoutId);
@@ -1114,10 +1150,8 @@ export async function fetchContentImages(filters: ContentImagesFilters = {}): Pr
if (filters.sector_id) params.append('sector_id', filters.sector_id.toString());
const queryString = params.toString();
const response = await fetchAPI(`/v1/writer/images/content_images/${queryString ? `?${queryString}` : ''}`);
// Extract data field from unified API response format
// Response format: { success: true, data: { count: ..., results: [...] }, request_id: "..." }
return response?.data || response;
// fetchAPI automatically extracts data field from unified format
return fetchAPI(`/v1/writer/images/content_images/${queryString ? `?${queryString}` : ''}`);
}
export async function bulkUpdateImagesStatus(contentId: number, status: string): Promise<{ updated_count: number }> {
@@ -1264,7 +1298,9 @@ export async function selectSectorsForSite(
}
export async function fetchSiteSectors(siteId: number): Promise<any[]> {
return fetchAPI(`/v1/auth/sites/${siteId}/sectors/`);
const response = await fetchAPI(`/v1/auth/sites/${siteId}/sectors/`);
// fetchAPI automatically extracts data field from unified format
return Array.isArray(response) ? response : [];
}
// Industries API functions
@@ -1291,7 +1327,20 @@ export interface IndustriesResponse {
}
export async function fetchIndustries(): Promise<IndustriesResponse> {
return fetchAPI('/v1/auth/industries/');
const response = await fetchAPI('/v1/auth/industries/');
// fetchAPI automatically extracts data field, but industries endpoint returns {industries: [...]}
// So we need to handle the nested structure
if (response && typeof response === 'object' && 'industries' in response) {
return {
success: true,
industries: response.industries || []
};
}
// If response is already an array or different format
return {
success: true,
industries: Array.isArray(response) ? response : []
};
}
// Sectors API functions
@@ -1420,7 +1469,14 @@ export interface UsageSummary {
}
export async function fetchCreditBalance(): Promise<CreditBalance> {
return fetchAPI('/v1/billing/credits/balance/balance/');
const response = await fetchAPI('/v1/billing/credits/balance/balance/');
// fetchAPI automatically extracts data field from unified format
return response || {
credits: 0,
plan_credits_per_month: 0,
credits_used_this_month: 0,
credits_remaining: 0,
};
}
export async function fetchCreditUsage(filters?: {
@@ -1445,11 +1501,8 @@ export async function fetchUsageSummary(startDate?: string, endDate?: string): P
if (endDate) params.append('end_date', endDate);
const queryString = params.toString();
const response = await fetchAPI(`/v1/billing/credits/usage/summary/${queryString ? `?${queryString}` : ''}`);
// Extract data field from unified API response format
// Response format: { success: true, data: {...}, request_id: "..." }
const summaryData = response?.data || response;
return summaryData;
// fetchAPI automatically extracts data field from unified format
return fetchAPI(`/v1/billing/credits/usage/summary/${queryString ? `?${queryString}` : ''}`);
}
export interface LimitCard {