diff --git a/src/api/adminBroadcasts.ts b/src/api/adminBroadcasts.ts index 2f0d0dd..4e6fb0a 100644 --- a/src/api/adminBroadcasts.ts +++ b/src/api/adminBroadcasts.ts @@ -242,11 +242,7 @@ export const adminBroadcastsApi = { formData.append('file', file); formData.append('media_type', mediaType); - const response = await apiClient.post('/cabinet/media/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + const response = await apiClient.post('/cabinet/media/upload', formData); return response.data; }, }; diff --git a/src/api/adminPinnedMessages.ts b/src/api/adminPinnedMessages.ts index edbfcec..3eff552 100644 --- a/src/api/adminPinnedMessages.ts +++ b/src/api/adminPinnedMessages.ts @@ -163,11 +163,7 @@ export const adminPinnedMessagesApi = { formData.append('file', file); formData.append('media_type', mediaType); - const response = await apiClient.post('/cabinet/media/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + const response = await apiClient.post('/cabinet/media/upload', formData); return response.data; }, }; diff --git a/src/api/branding.ts b/src/api/branding.ts index 094a0d2..ec3b9c8 100644 --- a/src/api/branding.ts +++ b/src/api/branding.ts @@ -153,11 +153,7 @@ export const brandingApi = { uploadLogo: async (file: File): Promise => { const formData = new FormData(); formData.append('file', file); - const response = await apiClient.post('/cabinet/branding/logo', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + const response = await apiClient.post('/cabinet/branding/logo', formData); // Invalidate cached blob so it gets re-fetched if (_logoBlobUrl) { URL.revokeObjectURL(_logoBlobUrl); diff --git a/src/api/client.ts b/src/api/client.ts index 57f9116..5b0fb78 100644 --- a/src/api/client.ts +++ b/src/api/client.ts @@ -82,6 +82,11 @@ function isAuthEndpoint(url: string | undefined): boolean { } apiClient.interceptors.request.use(async (config: InternalAxiosRequestConfig) => { + // Let axios set the correct multipart/form-data header with boundary for FormData + if (config.data instanceof FormData && config.headers) { + delete config.headers['Content-Type']; + } + if (!isAuthEndpoint(config.url)) { let token = tokenStorage.getAccessToken(); diff --git a/src/api/news.ts b/src/api/news.ts index b97ea31..70ff5c0 100644 --- a/src/api/news.ts +++ b/src/api/news.ts @@ -66,10 +66,7 @@ export const newsApi = { const response = await apiClient.post( '/cabinet/admin/news/media/upload', formData, - { - headers: { 'Content-Type': 'multipart/form-data' }, - signal, - }, + { signal }, ); return response.data; }, diff --git a/src/api/tickets.ts b/src/api/tickets.ts index df3b3b2..1cb4405 100644 --- a/src/api/tickets.ts +++ b/src/api/tickets.ts @@ -68,11 +68,7 @@ export const ticketsApi = { formData.append('file', file); formData.append('media_type', mediaType); - const response = await apiClient.post('/cabinet/media/upload', formData, { - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); + const response = await apiClient.post('/cabinet/media/upload', formData); return response.data; }, diff --git a/src/pages/AdminNewsCreate.tsx b/src/pages/AdminNewsCreate.tsx index c63e990..3dd31b0 100644 --- a/src/pages/AdminNewsCreate.tsx +++ b/src/pages/AdminNewsCreate.tsx @@ -430,7 +430,7 @@ export default function AdminNewsCreate() { const handleCreateCategory = useCallback( async (name: string, color: string) => { const cat = await newsApi.createCategory({ name, color }); - queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'categories'] }); + await queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'categories'] }); return cat; }, [queryClient], @@ -439,7 +439,7 @@ export default function AdminNewsCreate() { const handleCreateTag = useCallback( async (name: string, color: string) => { const tag = await newsApi.createTag({ name, color }); - queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'tags'] }); + await queryClient.invalidateQueries({ queryKey: ['admin', 'news', 'tags'] }); return tag; }, [queryClient], @@ -475,10 +475,11 @@ export default function AdminNewsCreate() { }); } if (articleData.tag) { + const matchedTag = tagsData?.find((t) => t.id === articleData.tag_id); setSelectedTag({ id: articleData.tag_id ?? 0, name: articleData.tag, - color: articleData.category_color, + color: matchedTag?.color ?? '#94a3b8', }); } setExcerpt(articleData.excerpt ?? ''); @@ -490,7 +491,7 @@ export default function AdminNewsCreate() { editor.commands.setContent(articleData.content); editorPopulated.current = true; } - }, [articleData, editor]); + }, [articleData, editor, tagsData]); // Auto-generate slug from title useEffect(() => { @@ -531,9 +532,9 @@ export default function AdminNewsCreate() { excerpt: excerpt.trim() || null, category: selectedCategory.name, category_color: selectedCategory.color, - category_id: selectedCategory.id || null, - tag: selectedTag?.name || null, - tag_id: selectedTag?.id || null, + category_id: selectedCategory.id !== 0 ? selectedCategory.id : null, + tag: selectedTag?.name ?? null, + tag_id: selectedTag?.id != null && selectedTag.id !== 0 ? selectedTag.id : null, featured_image_url: isSafeUrl(featuredImageUrl.trim()) ? featuredImageUrl.trim() : null, is_published: isPublished, is_featured: isFeatured,