@@ -342,7 +342,7 @@ export default function Keywords() {
|
||||
};
|
||||
|
||||
// Import/Export handlers
|
||||
const { handleExport, handleImportClick: baseHandleImportClick, ImportModal } = useKeywordsImportExport(
|
||||
const { handleExport, handleImportClick, ImportModal } = useKeywordsImportExport(
|
||||
() => {
|
||||
toast.success('Import successful', 'Keywords imported successfully.');
|
||||
loadKeywords();
|
||||
@@ -350,34 +350,12 @@ export default function Keywords() {
|
||||
(error) => {
|
||||
toast.error('Import failed', error.message);
|
||||
},
|
||||
// Pass active site_id and active sector_id for import, but validate sector belongs to site
|
||||
activeSite && activeSector && activeSector.site_id === activeSite.id
|
||||
// Pass active site_id and active sector_id for import
|
||||
activeSite && activeSector
|
||||
? { 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[]) => {
|
||||
|
||||
@@ -404,84 +404,29 @@ export default function IndustriesSectorsKeywords() {
|
||||
const formData = new FormData();
|
||||
formData.append('file', importFile);
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
const token = getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/v1/auth/seed-keywords/import_seed_keywords/', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
headers: {
|
||||
'Authorization': `Token ${localStorage.getItem('authToken')}`,
|
||||
},
|
||||
body: formData,
|
||||
credentials: 'include', // Add credentials for consistency
|
||||
});
|
||||
|
||||
let errorMessage = 'Import failed';
|
||||
|
||||
// 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.';
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Import failed');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
toast.success(`Successfully imported ${result.created || 0} keywords`);
|
||||
|
||||
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;
|
||||
// Reset and close modal
|
||||
setImportFile(null);
|
||||
setIsImportModalOpen(false);
|
||||
|
||||
// Reload keywords
|
||||
if (activeSite) {
|
||||
loadSeedKeywords();
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Import error:', error);
|
||||
@@ -686,21 +631,6 @@ export default function IndustriesSectorsKeywords() {
|
||||
title="Add Keywords"
|
||||
badge={{ icon: <BoltIcon />, color: 'blue' }}
|
||||
/>
|
||||
|
||||
{/* Action Buttons */}
|
||||
{isAdmin && (
|
||||
<div className="px-6 pt-4 pb-2 flex justify-end gap-2">
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="md"
|
||||
startIcon={<PlusIcon className="w-4 h-4" />}
|
||||
onClick={handleImportClick}
|
||||
>
|
||||
Import Keywords
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TablePageTemplate
|
||||
columns={pageConfig.columns}
|
||||
data={seedKeywords}
|
||||
@@ -731,7 +661,18 @@ export default function IndustriesSectorsKeywords() {
|
||||
}
|
||||
}}
|
||||
bulkActions={pageConfig.bulkActions}
|
||||
customActions={undefined}
|
||||
customActions={
|
||||
isAdmin ? (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
onClick={handleImportClick}
|
||||
>
|
||||
<PlusIcon className="w-4 h-4 mr-2" />
|
||||
Import Keywords
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
pagination={{
|
||||
currentPage,
|
||||
totalPages,
|
||||
@@ -761,7 +702,6 @@ export default function IndustriesSectorsKeywords() {
|
||||
setImportFile(null);
|
||||
}
|
||||
}}
|
||||
className="max-w-md"
|
||||
>
|
||||
<div className="p-6 space-y-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
|
||||
@@ -155,29 +155,8 @@ export const importTableData = async (
|
||||
const queryString = params.toString();
|
||||
const url = `${API_BASE_URL}${endpoint}${queryString ? `?${queryString}` : ''}`;
|
||||
|
||||
// Get authentication token (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;
|
||||
};
|
||||
|
||||
const token = getAuthToken();
|
||||
const headers: HeadersInit = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
keyword,volume,difficulty,intent,status
|
||||
keyword 1,700,52,informational,pending
|
||||
keyword 2,950,92,commercial,pending
|
||||
keyword 3,850,76,commercial,pending
|
||||
|
@@ -1,4 +0,0 @@
|
||||
keyword,volume,difficulty,intent,industry_name,sector_name
|
||||
keyword 1,550,28,commercial,Home & Garden,Home Decor
|
||||
keyword 2,700,52,informational,Home & Garden,Home Decor
|
||||
keyword 3,950,92,commercial,Home & Garden,Home Decor
|
||||
|
Reference in New Issue
Block a user