limits vlaiadtion adn keywrods forms

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-12 20:05:21 +00:00
parent 9824e9a4dc
commit 44ecd3fa7d
7 changed files with 224 additions and 110 deletions

View File

@@ -101,6 +101,29 @@ const getAuthToken = (): string | null => {
return null;
};
/**
* Extract user-friendly error message from API error
* Removes technical prefixes like "Failed to save:", "Failed to load:", etc.
* if the backend error message is already descriptive
*/
export function getUserFriendlyError(error: any, fallback: string = 'An error occurred. Please try again.'): string {
const message = error?.message || error?.error || fallback;
// If the message already describes a limit or specific problem, use it directly
if (message.includes('limit exceeded') ||
message.includes('not found') ||
message.includes('already exists') ||
message.includes('invalid') ||
message.includes('required') ||
message.includes('permission') ||
message.includes('upgrade')) {
return message;
}
// Otherwise return the message as-is
return message;
}
// Get refresh token from store - try Zustand store first, then localStorage as fallback
const getRefreshToken = (): string | null => {
try {
@@ -376,9 +399,11 @@ export async function fetchAPI(endpoint: string, options?: RequestInit & { timeo
});
// Attach error data to error object so it can be accessed in catch block
const apiError = new Error(`API Error (${response.status}): ${errorType} - ${errorMessage}`);
// Use clean user-friendly message without technical jargon
const apiError = new Error(errorMessage);
(apiError as any).response = errorData;
(apiError as any).status = response.status;
(apiError as any).errorType = errorType;
throw apiError;
}
@@ -493,7 +518,11 @@ export interface Keyword {
}
export interface KeywordCreateData {
seed_keyword_id: number;
keyword?: string; // For creating new custom keywords
volume?: number | null; // For custom keywords
difficulty?: number | null; // For custom keywords
intent?: string; // For custom keywords
seed_keyword_id?: number; // For linking existing seed keywords (optional)
volume_override?: number | null;
difficulty_override?: number | null;
cluster_id?: number | null;
@@ -554,9 +583,27 @@ export async function fetchKeyword(id: number): Promise<Keyword> {
}
export async function createKeyword(data: KeywordCreateData): Promise<Keyword> {
// Transform frontend field names to backend field names
const requestData: any = {
...data,
};
// If creating a custom keyword, map to backend field names
if (data.keyword) {
requestData.custom_keyword = data.keyword;
requestData.custom_volume = data.volume;
requestData.custom_difficulty = data.difficulty;
requestData.custom_intent = data.intent || 'informational';
// Remove the frontend-only fields
delete requestData.keyword;
delete requestData.volume;
delete requestData.difficulty;
delete requestData.intent;
}
return fetchAPI('/v1/planner/keywords/', {
method: 'POST',
body: JSON.stringify(data),
body: JSON.stringify(requestData),
});
}
@@ -1988,13 +2035,14 @@ export async function addSeedKeywordsToWorkflow(seedKeywordIds: number[], siteId
return { success: true, ...response } as any;
} catch (error: any) {
// Error responses are thrown by fetchAPI - return as failed result instead of re-throwing
// This allows component to handle limit errors gracefully
// Error responses are thrown by fetchAPI - return as failed result
// Extract clean user-friendly message (error.message is already cleaned in fetchAPI)
const userMessage = error.message || 'Failed to add keywords';
return {
success: false,
created: 0,
skipped: 0,
errors: [error.message || 'Failed to add keywords']
errors: [userMessage]
};
}
}