From 615c5e7e0be66d02bcb045b6d215d89b48555a92 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Tue, 9 Dec 2025 16:40:30 +0100 Subject: [PATCH] news - links missing in UI --- .../admin/news-management/hooks/addNews.ts | 27 ++ .../admin/news-management/hooks/deleteNews.ts | 9 + .../admin/news-management/hooks/getNews.ts | 52 ++++ .../admin/news-management/hooks/updateNews.ts | 24 ++ src/app/admin/news-management/page.tsx | 260 ++++++++++++++++++ src/app/news/page.tsx | 74 +++++ 6 files changed, 446 insertions(+) create mode 100644 src/app/admin/news-management/hooks/addNews.ts create mode 100644 src/app/admin/news-management/hooks/deleteNews.ts create mode 100644 src/app/admin/news-management/hooks/getNews.ts create mode 100644 src/app/admin/news-management/hooks/updateNews.ts create mode 100644 src/app/admin/news-management/page.tsx create mode 100644 src/app/news/page.tsx diff --git a/src/app/admin/news-management/hooks/addNews.ts b/src/app/admin/news-management/hooks/addNews.ts new file mode 100644 index 0000000..28bf5e6 --- /dev/null +++ b/src/app/admin/news-management/hooks/addNews.ts @@ -0,0 +1,27 @@ +import React from 'react' +import { authFetch } from '../../../utils/authFetch' +export async function addNews(payload: { title: string; summary?: string; content?: string; slug: string; category?: string; isActive: boolean; publishedAt?: string | null; imageFile?: File }) { + const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001' + const form = new FormData() + form.append('title', payload.title) + if (payload.summary) form.append('summary', payload.summary) + if (payload.content) form.append('content', payload.content) + form.append('slug', payload.slug) + if (payload.category) form.append('category', payload.category) + form.append('isActive', String(payload.isActive)) + if (payload.publishedAt) form.append('publishedAt', payload.publishedAt) + if (payload.imageFile) form.append('image', payload.imageFile) + + const url = `${BASE_URL}/api/admin/news` + const res = await authFetch(url, { method: 'POST', body: form, headers: { Accept: 'application/json' } }) + let body: any = null + try { body = await res.clone().json() } catch {} + if (process.env.NODE_ENV === 'development') { + console.debug('[addNews] status:', res.status) + console.debug('[addNews] body preview:', body ? JSON.stringify(body).slice(0,500) : '') + } + if (res.status === 401) throw new Error('Unauthorized. Please log in.') + if (res.status === 403) throw new Error('Forbidden. Admin access required.') + if (!res.ok) throw new Error(body?.error || body?.message || `Failed to create news (${res.status})`) + return body || res.json() +} diff --git a/src/app/admin/news-management/hooks/deleteNews.ts b/src/app/admin/news-management/hooks/deleteNews.ts new file mode 100644 index 0000000..09ab0f8 --- /dev/null +++ b/src/app/admin/news-management/hooks/deleteNews.ts @@ -0,0 +1,9 @@ +import React from 'react' +import { authFetch } from '../../../utils/authFetch' +export async function deleteNews(id: number) { + const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001' + const url = `${BASE_URL}/api/admin/news/${id}` + const res = await authFetch(url, { method: 'DELETE', headers: { Accept: 'application/json' } }) + if (!res.ok) throw new Error('Failed to delete news') + return res.json() +} diff --git a/src/app/admin/news-management/hooks/getNews.ts b/src/app/admin/news-management/hooks/getNews.ts new file mode 100644 index 0000000..5f7ef13 --- /dev/null +++ b/src/app/admin/news-management/hooks/getNews.ts @@ -0,0 +1,52 @@ +import React from 'react' +import { authFetch } from '../../../utils/authFetch' +export type AdminNewsItem = { + id: number + title: string + summary?: string + slug: string + category?: string + imageUrl?: string + isActive: boolean + publishedAt?: string | null +} + +export function useAdminNews() { + const [items, setItems] = React.useState([]) + const [loading, setLoading] = React.useState(false) + const [error, setError] = React.useState(null) + + const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001' + const refresh = React.useCallback(async () => { + setLoading(true) + setError(null) + try { + const url = `${BASE_URL}/api/admin/news` + const res = await authFetch(url, { headers: { Accept: 'application/json' } }) + let json: any = null + try { json = await res.clone().json() } catch {} + if (res.status === 401) throw new Error('Unauthorized. Please log in.') + if (res.status === 403) throw new Error('Forbidden. Admin access required.') + if (!res.ok) throw new Error(json?.error || json?.message || 'Failed to fetch admin news') + const data = (json.data || []).map((r: any) => ({ + id: r.id, + title: r.title, + summary: r.summary, + slug: r.slug, + category: r.category, + imageUrl: r.imageUrl, + isActive: !!r.is_active, + publishedAt: r.published_at || null, + })) + setItems(data) + } catch (e: any) { + setError(e.message) + } finally { + setLoading(false) + } + }, []) + + React.useEffect(() => { refresh() }, [refresh]) + + return { items, loading, error, refresh } +} diff --git a/src/app/admin/news-management/hooks/updateNews.ts b/src/app/admin/news-management/hooks/updateNews.ts new file mode 100644 index 0000000..9824074 --- /dev/null +++ b/src/app/admin/news-management/hooks/updateNews.ts @@ -0,0 +1,24 @@ +import React from 'react' +import { authFetch } from '../../../utils/authFetch' +export async function updateNews(id: number, payload: { title?: string; summary?: string; content?: string; slug?: string; category?: string; isActive?: boolean; publishedAt?: string | null; imageFile?: File; removeImage?: boolean }) { + const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001' + const form = new FormData() + if (payload.title) form.append('title', payload.title) + if (payload.summary) form.append('summary', payload.summary) + if (payload.content) form.append('content', payload.content) + if (payload.slug) form.append('slug', payload.slug) + if (payload.category) form.append('category', payload.category) + if (payload.isActive !== undefined) form.append('isActive', String(payload.isActive)) + if (payload.publishedAt !== undefined && payload.publishedAt !== null) form.append('publishedAt', payload.publishedAt) + if (payload.removeImage) form.append('removeImage', 'true') + if (payload.imageFile) form.append('image', payload.imageFile) + + const url = `${BASE_URL}/api/admin/news/${id}` + const res = await authFetch(url, { method: 'PATCH', body: form, headers: { Accept: 'application/json' } }) + let body: any = null + try { body = await res.clone().json() } catch {} + if (res.status === 401) throw new Error('Unauthorized. Please log in.') + if (res.status === 403) throw new Error('Forbidden. Admin access required.') + if (!res.ok) throw new Error(body?.error || body?.message || `Failed to update news (${res.status})`) + return body || res.json() +} diff --git a/src/app/admin/news-management/page.tsx b/src/app/admin/news-management/page.tsx new file mode 100644 index 0000000..e23c116 --- /dev/null +++ b/src/app/admin/news-management/page.tsx @@ -0,0 +1,260 @@ +'use client' + +import React from 'react' +import Header from '../../components/nav/Header' +import Footer from '../../components/Footer' +import PageTransitionEffect from '../../components/animation/pageTransitionEffect' +import { PlusIcon, PencilIcon, TrashIcon, PhotoIcon, XMarkIcon } from '@heroicons/react/24/outline' +import AffiliateCropModal from '../affiliate-management/components/AffiliateCropModal' +import { useAdminNews } from './hooks/getNews' +import { addNews } from './hooks/addNews' +import { updateNews } from './hooks/updateNews' +import { deleteNews } from './hooks/deleteNews' + +export default function NewsManagementPage() { + const { items, loading, error, refresh } = useAdminNews() + const [showCreate, setShowCreate] = React.useState(false) + const [selected, setSelected] = React.useState(null) + + return ( + +
+
+
+
+

News Manager

+ +
+ + {error &&
{error}
} + +
+ {items.map(item => ( +
+
+ {item.imageUrl ? ( + {item.title} + ) : ( +
+ +
+ )} +
+
+
+

{item.title}

+ {item.isActive ? 'Active' : 'Inactive'} +
+ {item.summary &&

{item.summary}

} +
+ + +
+
+
+ ))} +
+
+
+