diff --git a/frontend/src/pages/Planner/Keywords.tsx b/frontend/src/pages/Planner/Keywords.tsx
index 89036e60..36d9ac5a 100644
--- a/frontend/src/pages/Planner/Keywords.tsx
+++ b/frontend/src/pages/Planner/Keywords.tsx
@@ -342,7 +342,7 @@ export default function Keywords() {
};
// Import/Export handlers
- const { handleExport, handleImportClick, ImportModal } = useKeywordsImportExport(
+ const { handleExport, handleImportClick: baseHandleImportClick, ImportModal } = useKeywordsImportExport(
() => {
toast.success('Import successful', 'Keywords imported successfully.');
loadKeywords();
@@ -350,12 +350,34 @@ export default function Keywords() {
(error) => {
toast.error('Import failed', error.message);
},
- // Pass active site_id and active sector_id for import
- activeSite && activeSector
+ // Pass active site_id and active sector_id for import, but validate sector belongs to site
+ activeSite && activeSector && activeSector.site_id === activeSite.id
? { site_id: activeSite.id, sector_id: activeSector.id }
+ : activeSite // Only pass site_id if sector doesn't belong to site
+ ? { site_id: activeSite.id }
: undefined
);
+ // Custom import click handler with validation
+ const handleImportClick = () => {
+ // Validate that sector belongs to the selected site
+ if (activeSite && activeSector && activeSector.site_id !== activeSite.id) {
+ toast.error('Import failed', `Selected sector "${activeSector.name}" does not belong to site "${activeSite.name}". Please select a sector from the current site.`);
+ return;
+ }
+
+ // Debug logging
+ console.log('Import attempt:', {
+ site_id: activeSite?.id,
+ site_name: activeSite?.name,
+ sector_id: activeSector?.id,
+ sector_name: activeSector?.name,
+ sector_site_id: activeSector?.site_id
+ });
+
+ baseHandleImportClick();
+ };
+
// Handle bulk actions (delete, export, update_status are now handled by TablePageTemplate)
// This is only for actions that don't have modals (like auto_cluster)
const handleBulkAction = useCallback(async (action: string, ids: string[]) => {
diff --git a/frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx b/frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx
index da91d2cb..7d853417 100644
--- a/frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx
+++ b/frontend/src/pages/Setup/IndustriesSectorsKeywords.tsx
@@ -404,29 +404,84 @@ export default function IndustriesSectorsKeywords() {
const formData = new FormData();
formData.append('file', importFile);
- const response = await fetch('/api/v1/auth/seed-keywords/import_seed_keywords/', {
- method: 'POST',
- headers: {
- 'Authorization': `Token ${localStorage.getItem('authToken')}`,
- },
- body: formData,
- });
+ // Get token from auth store (consistent with other API calls)
+ const getAuthToken = () => {
+ try {
+ const authStorage = localStorage.getItem('auth-storage');
+ if (authStorage) {
+ const parsed = JSON.parse(authStorage);
+ return parsed?.state?.token || null;
+ }
+ } catch (e) {
+ // Ignore parsing errors
+ }
+ return null;
+ };
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(errorData.error || 'Import failed');
+ const token = getAuthToken();
+ const headers: HeadersInit = {};
+ if (token) {
+ headers['Authorization'] = `Bearer ${token}`;
}
- const result = await response.json();
- toast.success(`Successfully imported ${result.created || 0} keywords`);
+ const response = await fetch('/api/v1/auth/seed-keywords/import_seed_keywords/', {
+ method: 'POST',
+ headers,
+ body: formData,
+ credentials: 'include', // Add credentials for consistency
+ });
+
+ let errorMessage = 'Import failed';
- // Reset and close modal
- setImportFile(null);
- setIsImportModalOpen(false);
+ // Handle common HTTP status codes
+ if (response.status === 404) {
+ errorMessage = 'Import endpoint not found. Please check if the backend service is running.';
+ } else if (response.status === 429) {
+ errorMessage = 'Too many requests. Please wait a moment before trying again.';
+ } else if (response.status === 403) {
+ errorMessage = 'Access denied. Admin privileges required for seed keyword import.';
+ }
- // Reload keywords
- if (activeSite) {
- loadSeedKeywords();
+ try {
+ const result = await response.json();
+
+ if (!response.ok) {
+ // Try to get error message from response (override status-based message if available)
+ if (result.error) {
+ errorMessage = result.error;
+ } else if (result.message) {
+ errorMessage = result.message;
+ }
+ throw new Error(errorMessage);
+ }
+
+ // Success case
+ const importedCount = result.data?.imported || result.imported || 0;
+ const skippedCount = result.data?.skipped || result.skipped || 0;
+ const errors = result.data?.errors || result.errors || [];
+
+ toast.success(
+ `Successfully imported ${importedCount} keyword(s)${skippedCount > 0 ? `, ${skippedCount} skipped` : ''}`
+ );
+
+ if (errors.length > 0) {
+ toast.info(`Errors: ${errors.slice(0, 3).join(', ')}`);
+ }
+
+ // Reset and close modal
+ setImportFile(null);
+ setIsImportModalOpen(false);
+
+ // Reload keywords
+ if (activeSite) {
+ loadSeedKeywords();
+ }
+ } catch (jsonError: any) {
+ // If JSON parsing fails, handle it gracefully
+ if (!response.ok) {
+ throw new Error(errorMessage || `Server error: ${response.status}`);
+ }
+ throw jsonError;
}
} catch (error: any) {
console.error('Import error:', error);
@@ -631,6 +686,21 @@ export default function IndustriesSectorsKeywords() {
title="Add Keywords"
badge={{ icon: