This commit is contained in:
IGNY8 VPS (Salman)
2025-11-30 00:21:00 +00:00
parent d2f3f3ef97
commit 550a8f26a2
5 changed files with 144 additions and 33 deletions

View File

@@ -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[]) => {

View File

@@ -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
});
// Reset and close modal
setImportFile(null);
setIsImportModalOpen(false);
let errorMessage = 'Import failed';
// Reload keywords
if (activeSite) {
loadSeedKeywords();
// 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.';
}
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: <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}
@@ -661,18 +731,7 @@ export default function IndustriesSectorsKeywords() {
}
}}
bulkActions={pageConfig.bulkActions}
customActions={
isAdmin ? (
<Button
variant="secondary"
size="sm"
onClick={handleImportClick}
>
<PlusIcon className="w-4 h-4 mr-2" />
Import Keywords
</Button>
) : undefined
}
customActions={undefined}
pagination={{
currentPage,
totalPages,
@@ -702,6 +761,7 @@ 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">

View File

@@ -155,8 +155,29 @@ 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',
});

4
keywordsplanner.csv Normal file
View File

@@ -0,0 +1,4 @@
keyword,volume,difficulty,intent,status
keyword 1,700,52,informational,pending
keyword 2,950,92,commercial,pending
keyword 3,850,76,commercial,pending
1 keyword volume difficulty intent status
2 keyword 1 700 52 informational pending
3 keyword 2 950 92 commercial pending
4 keyword 3 850 76 commercial pending

4
seedKeywords_fixed.csv Normal file
View File

@@ -0,0 +1,4 @@
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
1 keyword volume difficulty intent industry_name sector_name
2 keyword 1 550 28 commercial Home & Garden Home Decor
3 keyword 2 700 52 informational Home & Garden Home Decor
4 keyword 3 950 92 commercial Home & Garden Home Decor