From 5fdcfdfdb990b4c0d10154fe83bde80b749ae0a8 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Wed, 17 Sep 2025 09:47:48 +0700 Subject: [PATCH] feat: update create form content and public landing --- .../form/content/audio-visual/video-form.tsx | 250 ++++++++++----- components/form/content/audio/audio-form.tsx | 214 +++++++++---- .../form/content/document/teks-form.tsx | 288 ++++++++++++------ components/landing-page/header.tsx | 84 +++-- components/landing-page/media-update.tsx | 75 ++++- components/main/content/audio-detail.tsx | 79 ++++- components/main/content/document-detail.tsx | 79 ++++- components/main/content/image-detail.tsx | 67 +++- components/main/content/video-detail.tsx | 63 +++- components/main/publication-filter.tsx | 2 +- next.config.ts | 2 +- service/landing/landing.ts | 25 ++ 12 files changed, 936 insertions(+), 292 deletions(-) diff --git a/components/form/content/audio-visual/video-form.tsx b/components/form/content/audio-visual/video-form.tsx index b941731..ff462b7 100644 --- a/components/form/content/audio-visual/video-form.tsx +++ b/components/form/content/audio-visual/video-form.tsx @@ -41,11 +41,18 @@ import { Item } from "@radix-ui/react-dropdown-menu"; import dynamic from "next/dynamic"; import { getCsrfToken } from "@/service/auth"; import { - listEnableCategory, - getTagsBySubCategoryId, createMedia, + createArticle, + getTagsBySubCategoryId, + listEnableCategory, + listArticleCategories, uploadThumbnail, -} from "@/service/content"; + uploadArticleFiles, + uploadArticleThumbnail, + CreateArticleData, +} from "@/service/content/content"; +import { request } from "http"; +import { toast } from "sonner"; import { htmlToString } from "@/utils/globals"; import { generateDataArticle, @@ -54,6 +61,7 @@ import { getGenerateKeywords, getGenerateTitle, } from "@/service/content/ai"; +import Link from "next/link"; const CustomEditor = dynamic( () => { @@ -188,27 +196,30 @@ export default function FormVideo() { }); const videoSchema = z.object({ - title: z.string().min(1, { message: "Judul diperlukan" }), + title: z.string().min(1, { message: "titleRequired" }), description: z.string().optional(), descriptionOri: z.string().optional(), rewriteDescription: z.string().optional(), - creatorName: z.string().min(1, { message: "Creator diperlukan" }), - category: z.string().min(1, { message: "Kategori harus dipilih" }), - tags: z - .array(z.string().min(1)) - .min(1, { message: "Minimal 1 tag diperlukan" }), + creatorName: z.string().min(1, { message: "creatorRequired" }), files: z .array(z.any()) - .min(1, { message: "File video harus dipilih" }) + .min(1, { message: "Minimal 1 file harus diunggah." }) .refine( (files) => - files.every((file: File) => ACCEPTED_FILE_TYPES.includes(file.type)), - { message: "File harus berformat mp4 atau mov" } - ) - .refine( - (files) => files.every((file: File) => file.size <= MAX_FILE_SIZE), - { message: "Ukuran file maksimal 100 MB" } + files.every( + (file: File) => + ["video/mp4", "video/mov", "video/avi"].includes(file.type) && + file.size <= 100 * 1024 * 1024 + ), + { + message: + "Hanya file .mp4, .mov, .avi, maksimal 100MB yang diperbolehkan.", + } ), + categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), + tags: z + .array(z.string()) + .min(1, { message: "Minimal 1 tag harus ditambahkan." }), publishedFor: z .array(z.string()) .min(1, { message: "Minimal 1 target publish harus dipilih." }), @@ -227,8 +238,9 @@ export default function FormVideo() { description: "", descriptionOri: "", rewriteDescription: "", - category: "", + creatorName: "", files: [], + categoryId: "", tags: [], publishedFor: [], }, @@ -463,11 +475,30 @@ export default function FormVideo() { const getCategories = async () => { try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; + // Use new Article Categories API + const category = await listArticleCategories(1, 100); + console.log("Article categories response:", category); + + if (category?.error) { + console.error("Failed to fetch article categories:", category.message); + // Fallback to old API if new one fails + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + return; + } + + // Handle new API response structure + const resCategory: Category[] = category?.data?.data?.map((item: any) => ({ + id: item.id, + name: item.title, // map title to name for backward compatibility + title: item.title, + description: item.description, + ...item + })) || []; setCategories(resCategory); - console.log("data category", resCategory); + console.log("Article categories loaded:", resCategory); if (scheduleId && scheduleType === "3") { const findCategory = resCategory.find((o) => @@ -475,7 +506,6 @@ export default function FormVideo() { ); if (findCategory) { - // setValue("categoryId", findCategory.id); setSelectedCategory(findCategory.id); const response = await getTagsBySubCategoryId(findCategory.id); setTags(response?.data?.data); @@ -483,6 +513,14 @@ export default function FormVideo() { } } catch (error) { console.error("Failed to fetch categories:", error); + // Fallback to old API if error occurs + try { + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + } catch (fallbackError) { + console.error("Fallback category fetch also failed:", fallbackError); + } } }; @@ -578,40 +616,99 @@ export default function FormVideo() { } if (id == undefined) { - const response = await createMedia(requestData); - console.log("Form Data Submitted:", requestData); + // New Articles API request data structure + const articleData: CreateArticleData = { + title: finalTitle, + description: htmlToString(finalDescription), + htmlDescription: finalDescription, + categoryIds: selectedCategory.toString(), + typeId: 4, // Video content type + tags: finalTags, + isDraft: true, + isPublish: false, + oldId: 0, + slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), + }; + + // Use new Articles API + const response = await createArticle(articleData); + console.log("Article Data Submitted:", articleData); + console.log("Article API Response:", response); if (response?.error) { - MySwal.fire("Error", response?.message, "error"); - return; + MySwal.fire("Error", response.message || "Failed to create article", "error"); + return false; } - Cookies.set("idCreate", response?.data?.data, { expires: 1 }); - id = response?.data?.data; - if (thumbnail) { - const formMedia = new FormData(); - formMedia.append("file", thumbnail); - const responseThumbnail = await uploadThumbnail(id, formMedia); - if (responseThumbnail?.error) { - error(responseThumbnail.message); + // Get the article ID from the new API response + const articleId = response?.data?.data?.id; + Cookies.set("idCreate", articleId, { expires: 1 }); + id = articleId; + + // Upload files using new article-files API + const formData = new FormData(); + + // Add all files to FormData + files.forEach((file, index) => { + formData.append('files', file); + }); + + console.log("Uploading files to article:", articleId); + console.log("Files to upload:", files.length); + + try { + const uploadResponse = await uploadArticleFiles(articleId, formData); + + if (uploadResponse?.error) { + MySwal.fire("Error", uploadResponse.message || "Failed to upload files", "error"); return false; } + + console.log("Files uploaded successfully:", uploadResponse); + + // Upload thumbnail using first file as thumbnail + if (files.length > 0) { + const thumbnailFormData = new FormData(); + thumbnailFormData.append('files', files[0]); // Use first file as thumbnail + + console.log("Uploading thumbnail for article:", articleId); + + try { + const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData); + + if (thumbnailResponse?.error) { + console.warn("Thumbnail upload failed:", thumbnailResponse.message); + // Don't fail the whole process if thumbnail upload fails + } else { + console.log("Thumbnail uploaded successfully:", thumbnailResponse); + } + } catch (thumbnailError) { + console.warn("Thumbnail upload error:", thumbnailError); + // Don't fail the whole process if thumbnail upload fails + } + } + + } catch (uploadError) { + console.error("Upload error:", uploadError); + MySwal.fire("Error", "Failed to upload files. Please try again.", "error"); + return false; } + + // Show success message + MySwal.fire({ + title: "Sukses", + text: "Article dan files berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/admin/content/video"); + }); + + Cookies.remove("idCreate"); + return; } - const progressInfoArr = []; - for (const item of files) { - progressInfoArr.push({ percentage: 0, fileName: item.name }); - } - progressInfo = progressInfoArr; - setIsStartUpload(true); - setProgressList(progressInfoArr); - - close(); - // showProgress(); - files.map(async (item: any, index: number) => { - await uploadResumableFile(index, String(id), item, "0"); - }); - + Cookies.remove("idCreate"); }; @@ -885,37 +982,40 @@ export default function FormVideo() { ( - +
+ + + + {errors.categoryId && ( +

+ {errors.categoryId.message} +

+ )} +
)} /> - {errors.category && ( -

- {errors.category.message} -

- )}
diff --git a/components/form/content/audio/audio-form.tsx b/components/form/content/audio/audio-form.tsx index 2569b54..04b56d4 100644 --- a/components/form/content/audio/audio-form.tsx +++ b/components/form/content/audio/audio-form.tsx @@ -32,9 +32,14 @@ import { Switch } from "@/components/ui/switch"; import Cookies from "js-cookie"; import { createMedia, + createArticle, getTagsBySubCategoryId, listEnableCategory, + listArticleCategories, uploadThumbnail, + uploadArticleFiles, + uploadArticleThumbnail, + CreateArticleData, } from "@/service/content/content"; import { Textarea } from "@/components/ui/textarea"; import { @@ -54,6 +59,8 @@ import { Item } from "@radix-ui/react-dropdown-menu"; import dynamic from "next/dynamic"; import { getCsrfToken } from "@/service/auth"; import { useParams } from "next/navigation"; +import { request } from "http"; +import { toast } from "sonner"; import { htmlToString } from "@/utils/globals"; import Link from "next/link"; @@ -127,7 +134,7 @@ export default function FormAudio() { polres: false, }); - let fileTypeId = "4"; + let fileTypeId = "3"; let progressInfo: any = []; let counterUpdateProgress = 0; const [progressList, setProgressList] = useState([]); @@ -197,18 +204,30 @@ export default function FormAudio() { }; const audioSchema = z.object({ - title: z.string().min(1, { message: "Judul diperlukan" }), + title: z.string().min(1, { message: "titleRequired" }), description: z.string().optional(), descriptionOri: z.string().optional(), rewriteDescription: z.string().optional(), - creatorName: z.string().min(1, { message: "Creator diperlukan" }), - category: z.string().min(1, { message: "Category diperlukan" }), + creatorName: z.string().min(1, { message: "creatorRequired" }), files: z .array(z.any()) - .min(1, { message: "Wajib upload minimal 1 file mp3 atau wav" }), + .min(1, { message: "Minimal 1 file harus diunggah." }) + .refine( + (files) => + files.every( + (file: File) => + ["audio/mpeg", "audio/wav", "audio/mp3"].includes(file.type) && + file.size <= 100 * 1024 * 1024 + ), + { + message: + "Hanya file .mp3, .wav, maksimal 100MB yang diperbolehkan.", + } + ), + categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), tags: z - .array(z.string().min(1)) - .min(1, { message: "Wajib isi minimal 1 tag" }), + .array(z.string()) + .min(1, { message: "Minimal 1 tag harus ditambahkan." }), publishedFor: z .array(z.string()) .min(1, { message: "Minimal 1 target publish harus dipilih." }), @@ -227,7 +246,9 @@ export default function FormAudio() { description: "", descriptionOri: "", rewriteDescription: "", - category: "", + creatorName: "", + files: [], + categoryId: "", tags: [], publishedFor: [], }, @@ -456,11 +477,30 @@ export default function FormAudio() { const getCategories = async () => { try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; + // Use new Article Categories API + const category = await listArticleCategories(1, 100); + console.log("Article categories response:", category); + + if (category?.error) { + console.error("Failed to fetch article categories:", category.message); + // Fallback to old API if new one fails + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + return; + } + + // Handle new API response structure + const resCategory: Category[] = category?.data?.data?.map((item: any) => ({ + id: item.id, + name: item.title, // map title to name for backward compatibility + title: item.title, + description: item.description, + ...item + })) || []; setCategories(resCategory); - console.log("data category", resCategory); + console.log("Article categories loaded:", resCategory); if (scheduleId && scheduleType === "3") { const findCategory = resCategory.find((o) => @@ -468,7 +508,6 @@ export default function FormAudio() { ); if (findCategory) { - // setValue("categoryId", findCategory.id); setSelectedCategory(findCategory.id); const response = await getTagsBySubCategoryId(findCategory.id); setTags(response?.data?.data); @@ -476,6 +515,14 @@ export default function FormAudio() { } } catch (error) { console.error("Failed to fetch categories:", error); + // Fallback to old API if error occurs + try { + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + } catch (fallbackError) { + console.error("Fallback category fetch also failed:", fallbackError); + } } }; @@ -567,43 +614,100 @@ export default function FormAudio() { } if (id == undefined) { - const response = await createMedia(requestData); - console.log("Form Data Submitted:", requestData); + // New Articles API request data structure + const articleData: CreateArticleData = { + title: finalTitle, + description: htmlToString(finalDescription), + htmlDescription: finalDescription, + categoryIds: selectedCategory.toString(), + typeId: 3, // Audio content type + tags: finalTags, + isDraft: true, + isPublish: false, + oldId: 0, + slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), + }; + + // Use new Articles API + const response = await createArticle(articleData); + console.log("Article Data Submitted:", articleData); + console.log("Article API Response:", response); if (response?.error) { - MySwal.fire("Error", response?.message, "error"); - return; - } - Cookies.set("idCreate", response?.data?.data, { expires: 1 }); - id = response?.data?.data; - - const formMedia = new FormData(); - console.log("Thumbnail : ", files[0]); - formMedia.append("file", files[0]); - const responseThumbnail = await uploadThumbnail(id, formMedia); - if (responseThumbnail?.error == true) { - error(responseThumbnail?.message); + MySwal.fire("Error", response.message || "Failed to create article", "error"); return false; } + + // Get the article ID from the new API response + const articleId = response?.data?.data?.id; + Cookies.set("idCreate", articleId, { expires: 1 }); + id = articleId; + + // Upload files using new article-files API + const formData = new FormData(); + + // Add all files to FormData + files.forEach((file, index) => { + formData.append('files', file); + }); + + console.log("Uploading files to article:", articleId); + console.log("Files to upload:", files.length); + + try { + const uploadResponse = await uploadArticleFiles(articleId, formData); + + if (uploadResponse?.error) { + MySwal.fire("Error", uploadResponse.message || "Failed to upload files", "error"); + return false; + } + + console.log("Files uploaded successfully:", uploadResponse); + + // Upload thumbnail using first file as thumbnail + if (files.length > 0) { + const thumbnailFormData = new FormData(); + thumbnailFormData.append('files', files[0]); // Use first file as thumbnail + + console.log("Uploading thumbnail for article:", articleId); + + try { + const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData); + + if (thumbnailResponse?.error) { + console.warn("Thumbnail upload failed:", thumbnailResponse.message); + // Don't fail the whole process if thumbnail upload fails + } else { + console.log("Thumbnail uploaded successfully:", thumbnailResponse); + } + } catch (thumbnailError) { + console.warn("Thumbnail upload error:", thumbnailError); + // Don't fail the whole process if thumbnail upload fails + } + } + + } catch (uploadError) { + console.error("Upload error:", uploadError); + MySwal.fire("Error", "Failed to upload files. Please try again.", "error"); + return false; + } + + // Show success message + MySwal.fire({ + title: "Sukses", + text: "Article dan files berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/admin/content/audio"); + }); + + Cookies.remove("idCreate"); + return; } - - const progressInfoArr = []; - for (const item of files) { - progressInfoArr.push({ percentage: 0, fileName: item.name }); - } - progressInfo = progressInfoArr; - setIsStartUpload(true); - setProgressList(progressInfoArr); - - close(); - // showProgress(); - files.map(async (item: any, index: number) => { - await uploadResumableFile(index, String(id), item, "0"); - }); - + Cookies.remove("idCreate"); - - // MySwal.fire("Sukses", "Data berhasil disimpan.", "success"); }; const onSubmit = (data: AudioSchema) => { @@ -867,17 +971,16 @@ export default function FormAudio() {
( - <> + name="categoryId" + render={({ field }) => ( +
+ - {fieldState.error && ( -

- {fieldState.error.message} + + {errors.categoryId && ( +

+ {errors.categoryId.message}

)} - +
)} />
diff --git a/components/form/content/document/teks-form.tsx b/components/form/content/document/teks-form.tsx index 8373630..50404be 100644 --- a/components/form/content/document/teks-form.tsx +++ b/components/form/content/document/teks-form.tsx @@ -30,9 +30,14 @@ import { Switch } from "@/components/ui/switch"; import Cookies from "js-cookie"; import { createMedia, + createArticle, getTagsBySubCategoryId, listEnableCategory, + listArticleCategories, uploadThumbnail, + uploadArticleFiles, + uploadArticleThumbnail, + CreateArticleData, } from "@/service/content/content"; import { Textarea } from "@/components/ui/textarea"; import { @@ -50,6 +55,8 @@ import Image from "next/image"; import { error, loading } from "@/config/swal"; import dynamic from "next/dynamic"; import { getCsrfToken } from "@/service/auth"; +import { request } from "http"; +import { toast } from "sonner"; import { htmlToString } from "@/utils/globals"; import Link from "next/link"; @@ -125,7 +132,7 @@ export default function FormTeks() { polres: false, }); - let fileTypeId = "3"; + let fileTypeId = "2"; let progressInfo: any = []; let counterUpdateProgress = 0; const [progressList, setProgressList] = useState([]); @@ -178,38 +185,34 @@ export default function FormTeks() { }); const teksSchema = z.object({ - title: z.string().min(1, { message: "Judul diperlukan" }), - creatorName: z.string().min(1, { message: "Creator diperlukan" }), - category: z.string().min(1, { message: "Kategori harus dipilih" }), - tags: z - .array(z.string()) - .min(1, { message: "Minimal 1 tag harus ditambahkan." }), + title: z.string().min(1, { message: "titleRequired" }), + description: z.string().optional(), + descriptionOri: z.string().optional(), + rewriteDescription: z.string().optional(), + creatorName: z.string().min(1, { message: "creatorRequired" }), files: z - .array( - z - .object({ - name: z.string(), - type: z.string(), - size: z - .number() - .max(20 * 1024 * 1024, { message: "Max file size 20 MB" }), - }) - .refine( - (file) => + .array(z.any()) + .min(1, { message: "Minimal 1 file harus diunggah." }) + .refine( + (files) => + files.every( + (file: File) => [ "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/vnd.ms-powerpoint", - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ].includes(file.type), - { message: "Format file tidak didukung" } - ) - ) - .min(1, { message: "File wajib diunggah" }), - description: z.string().optional(), - descriptionOri: z.string().optional(), - rewriteDescription: z.string().optional(), + "text/plain", + ].includes(file.type) && file.size <= 100 * 1024 * 1024 + ), + { + message: + "Hanya file .pdf, .doc, .docx, .txt, maksimal 100MB yang diperbolehkan.", + } + ), + categoryId: z.string().min(1, { message: "Kategori wajib dipilih." }), + tags: z + .array(z.string()) + .min(1, { message: "Minimal 1 tag harus ditambahkan." }), publishedFor: z .array(z.string()) .min(1, { message: "Minimal 1 target publish harus dipilih." }), @@ -226,13 +229,13 @@ export default function FormTeks() { resolver: zodResolver(teksSchema), defaultValues: { title: "", - creatorName: "", - category: "", - tags: [], - files: [], description: "", descriptionOri: "", rewriteDescription: "", + creatorName: "", + files: [], + categoryId: "", + tags: [], publishedFor: [], }, }); @@ -470,11 +473,30 @@ export default function FormTeks() { const getCategories = async () => { try { - const category = await listEnableCategory(fileTypeId); - const resCategory: Category[] = category?.data?.data?.content; + // Use new Article Categories API + const category = await listArticleCategories(1, 100); + console.log("Article categories response:", category); + + if (category?.error) { + console.error("Failed to fetch article categories:", category.message); + // Fallback to old API if new one fails + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + return; + } + + // Handle new API response structure + const resCategory: Category[] = category?.data?.data?.map((item: any) => ({ + id: item.id, + name: item.title, // map title to name for backward compatibility + title: item.title, + description: item.description, + ...item + })) || []; setCategories(resCategory); - console.log("data category", resCategory); + console.log("Article categories loaded:", resCategory); if (scheduleId && scheduleType === "3") { const findCategory = resCategory.find((o) => @@ -482,14 +504,21 @@ export default function FormTeks() { ); if (findCategory) { - // setValue("categoryId", findCategory.id); - setSelectedCategory(findCategory.id); // Set the selected category + setSelectedCategory(findCategory.id); const response = await getTagsBySubCategoryId(findCategory.id); setTags(response?.data?.data); } } } catch (error) { console.error("Failed to fetch categories:", error); + // Fallback to old API if error occurs + try { + const fallbackCategory = await listEnableCategory(fileTypeId); + const resCategory: Category[] = fallbackCategory?.data.data.content || []; + setCategories(resCategory); + } catch (fallbackError) { + console.error("Fallback category fetch also failed:", fallbackError); + } } }; @@ -589,38 +618,99 @@ export default function FormTeks() { } if (id == undefined) { - const response = await createMedia(requestData); - console.log("Form Data Submitted:", requestData); + // New Articles API request data structure + const articleData: CreateArticleData = { + title: finalTitle, + description: htmlToString(finalDescription), + htmlDescription: finalDescription, + categoryIds: selectedCategory.toString(), + typeId: 2, // Document content type + tags: finalTags, + isDraft: true, + isPublish: false, + oldId: 0, + slug: finalTitle.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''), + }; - Cookies.set("idCreate", response?.data?.data, { expires: 1 }); - id = response?.data?.data; - const formMedia = new FormData(); - const thumbnail = files[0]; - formMedia.append("file", thumbnail); - const responseThumbnail = await uploadThumbnail(id, formMedia); - if (responseThumbnail?.error == true) { - error(responseThumbnail?.message); + // Use new Articles API + const response = await createArticle(articleData); + console.log("Article Data Submitted:", articleData); + console.log("Article API Response:", response); + + if (response?.error) { + MySwal.fire("Error", response.message || "Failed to create article", "error"); return false; } + + // Get the article ID from the new API response + const articleId = response?.data?.data?.id; + Cookies.set("idCreate", articleId, { expires: 1 }); + id = articleId; + + // Upload files using new article-files API + const formData = new FormData(); + + // Add all files to FormData + files.forEach((file, index) => { + formData.append('files', file); + }); + + console.log("Uploading files to article:", articleId); + console.log("Files to upload:", files.length); + + try { + const uploadResponse = await uploadArticleFiles(articleId, formData); + + if (uploadResponse?.error) { + MySwal.fire("Error", uploadResponse.message || "Failed to upload files", "error"); + return false; + } + + console.log("Files uploaded successfully:", uploadResponse); + + // Upload thumbnail using first file as thumbnail + if (files.length > 0) { + const thumbnailFormData = new FormData(); + thumbnailFormData.append('files', files[0]); // Use first file as thumbnail + + console.log("Uploading thumbnail for article:", articleId); + + try { + const thumbnailResponse = await uploadArticleThumbnail(articleId, thumbnailFormData); + + if (thumbnailResponse?.error) { + console.warn("Thumbnail upload failed:", thumbnailResponse.message); + // Don't fail the whole process if thumbnail upload fails + } else { + console.log("Thumbnail uploaded successfully:", thumbnailResponse); + } + } catch (thumbnailError) { + console.warn("Thumbnail upload error:", thumbnailError); + // Don't fail the whole process if thumbnail upload fails + } + } + + } catch (uploadError) { + console.error("Upload error:", uploadError); + MySwal.fire("Error", "Failed to upload files. Please try again.", "error"); + return false; + } + + // Show success message + MySwal.fire({ + title: "Sukses", + text: "Article dan files berhasil disimpan.", + icon: "success", + confirmButtonColor: "#3085d6", + confirmButtonText: "OK", + }).then(() => { + router.push("/admin/content/document"); + }); + + Cookies.remove("idCreate"); + return; } - const progressInfoArr = files.map((item) => ({ - percentage: 0, - fileName: item.name, - })); - progressInfo = progressInfoArr; - setIsStartUpload(true); - setProgressList(progressInfoArr); - - close(); - files.map(async (item: any, index: number) => { - await uploadResumableFile( - index, - String(id), - item, - fileTypeId == "2" || fileTypeId == "4" ? item.duration : "0" - ); - }); - + Cookies.remove("idCreate"); }; @@ -848,7 +938,7 @@ export default function FormTeks() {
-

Form Text

+

Form Document

{/* Input Title */}
@@ -873,38 +963,42 @@ export default function FormTeks() {
- ( - + ( +
+ + + + {errors.categoryId && ( +

+ {errors.categoryId.message} +

)} - /> - {errors.category?.message && ( -

- {errors.category.message} -

- )} +
+ )} + />
diff --git a/components/landing-page/header.tsx b/components/landing-page/header.tsx index 2cf0ad6..fa18a64 100644 --- a/components/landing-page/header.tsx +++ b/components/landing-page/header.tsx @@ -6,6 +6,7 @@ import { useEffect, useState } from "react"; import { getListContent, listData, + listArticles, listStaticBanner, } from "@/service/landing/landing"; import { data } from "framer-motion/client"; @@ -17,25 +18,66 @@ export default function Header() { useEffect(() => { const fetchData = async () => { try { - // const request = { - // group: "mabes", - // }; - const response = await listData( - "", - "", - "", - 5, - 0, - "createdAt", - "", - "", - "" - ); - const content = response?.data?.data?.content || []; - console.log("data", content); - setData(content); + // Use new Articles API + const response = await listArticles(1, 5, undefined, undefined, undefined, "createdAt"); + console.log("Articles API response:", response); + + if (response?.error) { + console.error("Articles API failed, falling back to old API"); + // Fallback to old API + const fallbackResponse = await listData( + "", + "", + "", + 5, + 0, + "createdAt", + "", + "", + "" + ); + const content = fallbackResponse?.data?.data?.content || []; + setData(content); + return; + } + + // Handle new API response structure + const articlesData = response?.data?.data || []; + console.log("Articles data:", articlesData); + + // Transform articles data to match old structure for backward compatibility + const transformedData = articlesData.map((article: any) => ({ + id: article.id, + title: article.title, + categoryName: article.categoryName || (article.categories && article.categories[0]?.title) || "", + createdAt: article.createdAt, + smallThumbnailLink: article.thumbnailUrl, + fileTypeId: article.typeId, + label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "", + ...article + })); + + setData(transformedData); } catch (error) { console.error("Gagal memuat data:", error); + // Try fallback to old API if new API fails + try { + const fallbackResponse = await listData( + "", + "", + "", + 5, + 0, + "createdAt", + "", + "", + "" + ); + const content = fallbackResponse?.data?.data?.content || []; + setData(content); + } catch (fallbackError) { + console.error("Fallback API also failed:", fallbackError); + } } }; @@ -70,13 +112,13 @@ function Card({ item, isBig = false }: { item: any; isBig?: boolean }) { const getLink = () => { switch (item?.fileTypeId) { case 1: - return `/public/content/image/detail/${item?.id}`; + return `/content/image/detail/${item?.id}`; case 2: - return `/public/content/video/detail/${item?.id}`; + return `/content/video/detail/${item?.id}`; case 3: - return `/public/content/text/detail/${item?.id}`; + return `/content/text/detail/${item?.id}`; case 4: - return `/public/content/audio/detail/${item?.id}`; + return `/content/audio/detail/${item?.id}`; default: return "#"; // fallback kalau type tidak dikenali } diff --git a/components/landing-page/media-update.tsx b/components/landing-page/media-update.tsx index 98861c6..f76d8a0 100644 --- a/components/landing-page/media-update.tsx +++ b/components/landing-page/media-update.tsx @@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button"; import { ThumbsUp, ThumbsDown } from "lucide-react"; import { Card } from "../ui/card"; import Link from "next/link"; -import { listData } from "@/service/landing/landing"; +import { listData, listArticles } from "@/service/landing/landing"; import { Swiper, SwiperSlide } from "swiper/react"; import "swiper/css"; import "swiper/css/navigation"; @@ -42,20 +42,71 @@ export default function MediaUpdate() { async function fetchData(section: "latest" | "popular") { try { setLoading(true); - const res = await listData( - "1", - "", - "", - 20, - 0, - section === "latest" ? "createdAt" : "clickCount", - "", - "", - "" + + // Use new Articles API + const response = await listArticles( + 1, + 20, + 1, // typeId for images + undefined, + undefined, + section === "latest" ? "createdAt" : "viewCount" ); - setDataToRender(res?.data?.data?.content || []); + + console.log("Media Update Articles API response:", response); + + if (response?.error) { + console.error("Articles API failed, falling back to old API"); + // Fallback to old API + const fallbackRes = await listData( + "1", + "", + "", + 20, + 0, + section === "latest" ? "createdAt" : "clickCount", + "", + "", + "" + ); + setDataToRender(fallbackRes?.data?.data?.content || []); + return; + } + + // Handle new API response structure + const articlesData = response?.data?.data || []; + + // Transform articles data to match old structure for backward compatibility + const transformedData = articlesData.map((article: any) => ({ + id: article.id, + title: article.title, + category: article.categoryName || (article.categories && article.categories[0]?.title) || "Tanpa Kategori", + createdAt: article.createdAt, + smallThumbnailLink: article.thumbnailUrl, + label: article.typeId === 1 ? "Image" : article.typeId === 2 ? "Video" : article.typeId === 3 ? "Text" : article.typeId === 4 ? "Audio" : "", + ...article + })); + + setDataToRender(transformedData); } catch (err) { console.error("Gagal memuat data:", err); + // Try fallback to old API if new API fails + try { + const fallbackRes = await listData( + "1", + "", + "", + 20, + 0, + section === "latest" ? "createdAt" : "clickCount", + "", + "", + "" + ); + setDataToRender(fallbackRes?.data?.data?.content || []); + } catch (fallbackError) { + console.error("Fallback API also failed:", fallbackError); + } } finally { setLoading(false); } diff --git a/components/main/content/audio-detail.tsx b/components/main/content/audio-detail.tsx index c1370e7..d97fcc9 100644 --- a/components/main/content/audio-detail.tsx +++ b/components/main/content/audio-detail.tsx @@ -15,7 +15,7 @@ import { FaLink, FaShareAlt, } from "react-icons/fa"; -import { getDetail } from "@/service/landing/landing"; +import { getDetail, getArticleDetail } from "@/service/landing/landing"; export default function AudioDetail({ id }: { id: string }) { const [copied, setCopied] = useState(false); @@ -60,14 +60,75 @@ export default function AudioDetail({ id }: { id: string }) { const fetchDetail = async () => { try { setLoading(true); - const response = await getDetail(id); - setData(response?.data?.data); - console.log( - "doc", - response?.data?.data.files[selectedDoc]?.secondaryUrl - ); + + // Try new Articles API first + const response = await getArticleDetail(id); + console.log("Article Detail API response:", response); + + if (response?.error) { + console.error("Articles API failed, falling back to old API"); + // Fallback to old API + const fallbackResponse = await getDetail(id); + setData(fallbackResponse?.data?.data); + console.log( + "doc", + fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl + ); + return; + } + + // Handle new API response structure + const articleData = response?.data?.data; + if (articleData) { + // Transform article data to match old structure for backward compatibility + const transformedData = { + id: articleData.id, + title: articleData.title, + description: articleData.description, + createdAt: articleData.createdAt, + clickCount: articleData.viewCount, + creatorGroupLevelName: articleData.createdByName || "Unknown", + uploadedBy: { + publisher: articleData.createdByName || "MABES POLRI" + }, + files: articleData.files?.map((file: any) => ({ + id: file.id, + url: file.file_url, + fileName: file.file_name, + filePath: file.file_path, + fileThumbnail: file.file_thumbnail, + fileAlt: file.file_alt, + widthPixel: file.width_pixel, + heightPixel: file.height_pixel, + size: file.size, + downloadCount: file.download_count, + createdAt: file.created_at, + updatedAt: file.updated_at, + secondaryUrl: file.file_url, // For audio files, use same URL + ...file + })) || [], + ...articleData + }; + + setData(transformedData); + console.log( + "doc", + transformedData.files[selectedDoc]?.secondaryUrl + ); + } } catch (error) { console.error("Error fetching detail:", error); + // Try fallback to old API if new API fails + try { + const fallbackResponse = await getDetail(id); + setData(fallbackResponse?.data?.data); + console.log( + "doc", + fallbackResponse?.data?.data.files[selectedDoc]?.secondaryUrl + ); + } catch (fallbackError) { + console.error("Fallback API also failed:", fallbackError); + } } finally { setLoading(false); } @@ -185,7 +246,7 @@ export default function AudioDetail({ id }: { id: string }) {
- +
- +
- +
- +
-
+
*/} {/* Content */}
@@ -282,7 +335,7 @@ export default function ImageDetail({ id }: { id: string }) { SHARE
- +
- +
- +