diff --git a/app/[locale]/(admin)/admin/schedule/create/page.tsx b/app/[locale]/(admin)/admin/schedule/create/page.tsx new file mode 100644 index 0000000..9461226 --- /dev/null +++ b/app/[locale]/(admin)/admin/schedule/create/page.tsx @@ -0,0 +1,242 @@ +"use client"; + +import { useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { Switch } from "@/components/ui/switch"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useRouter } from "next/navigation"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; +import { createSchedule } from "@/service/landing/landing"; + +const MySwal = withReactContent(Swal); + +export default function CreateSchedulePage() { + const router = useRouter(); + + const [formData, setFormData] = useState({ + createdById: 1, // bisa diganti sesuai ID user login + description: "", + endDate: "", + endTime: "", + isLiveStreaming: false, + location: "", + speakers: "", + startDate: "", + startTime: "", + title: "", + typeId: 1, + liveStreamingUrl: "", + posterImagePath: "", + }); + + const [loading, setLoading] = useState(false); + + const handleChange = ( + e: React.ChangeEvent + ) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleToggle = (checked: boolean) => { + setFormData((prev) => ({ ...prev, isLiveStreaming: checked })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + + try { + // Format tanggal sesuai ISO backend (2025-11-01T00:00:00Z) + const payload = { + ...formData, + startDate: `${formData.startDate}T${formData.startTime}:00Z`, + endDate: `${formData.endDate}T${formData.endTime}:00Z`, + }; + + const res = await createSchedule(payload); + + if (!res?.error) { + MySwal.fire({ + icon: "success", + title: "Berhasil!", + text: "Schedule berhasil dibuat.", + timer: 2000, + showConfirmButton: false, + }); + setTimeout(() => router.push("/admin/schedule"), 2000); + } else { + MySwal.fire({ + icon: "error", + title: "Gagal!", + text: res?.message || "Gagal membuat schedule.", + }); + } + } catch (error) { + MySwal.fire({ + icon: "error", + title: "Error", + text: "Terjadi kesalahan pada sistem.", + }); + console.error(error); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + + Buat Jadwal Baru + + + + +
+
+ + +
+ +
+ + + {errors.message && ( +

{errors.message.message}

+ )} +
+ + +
+ + + ); +}; + +export default ContactForm; diff --git a/app/[locale]/(public)/content/audio/comment/[id]/page.tsx b/app/[locale]/(public)/content/audio/comment/[id]/page.tsx new file mode 100644 index 0000000..cdec591 --- /dev/null +++ b/app/[locale]/(public)/content/audio/comment/[id]/page.tsx @@ -0,0 +1,5 @@ +import DetailCommentVideo from "@/components/main/comment-detail-video"; + +export default async function DetailCommentInfo() { + return ; +} diff --git a/app/[locale]/(public)/content/image/comment/[id]/page.tsx b/app/[locale]/(public)/content/image/comment/[id]/page.tsx new file mode 100644 index 0000000..63297b3 --- /dev/null +++ b/app/[locale]/(public)/content/image/comment/[id]/page.tsx @@ -0,0 +1,5 @@ +import DetailCommentImage from "@/components/main/comment-detail-image"; + +export default async function DetailCommentInfo() { + return ; +} diff --git a/app/[locale]/(public)/content/text/comment/[id]/page.tsx b/app/[locale]/(public)/content/text/comment/[id]/page.tsx new file mode 100644 index 0000000..cdec591 --- /dev/null +++ b/app/[locale]/(public)/content/text/comment/[id]/page.tsx @@ -0,0 +1,5 @@ +import DetailCommentVideo from "@/components/main/comment-detail-video"; + +export default async function DetailCommentInfo() { + return ; +} diff --git a/app/[locale]/auth/forgot-password/page.tsx b/app/[locale]/auth/forgot-password/page.tsx new file mode 100644 index 0000000..ef4e14c --- /dev/null +++ b/app/[locale]/auth/forgot-password/page.tsx @@ -0,0 +1,59 @@ +import { Link } from "@/i18n/routing"; +import ForgotPass from "@/components/partials/auth/forgot-pass"; +import Image from "next/image"; + +const ForgotPassPage = () => { + return ( +
+
+ {/* === KIRI: IMAGE / LOGO AREA === */} +
+
+ + Mikul News Logo + +
+ {/* bisa tambahkan background atau ilustrasi tambahan di sini */} +
+ + {/* === KANAN: FORM AREA === */} +
+
+
+ {/* TITLE */} +
+

Forgot Your Password?

+
+ + {/* INSTRUCTION */} +
+ Enter your Username and instructions will be sent to you! +
+ + {/* FORM */} + + + {/* LINK BACK */} +
+ Forget it?{" "} + + Send me back + {" "} + to the Sign In +
+
+
+
+
+
+ ); +}; + +export default ForgotPassPage; \ No newline at end of file diff --git a/app/[locale]/auth/page.tsx b/app/[locale]/auth/page.tsx index ab7f7b4..99325bc 100644 --- a/app/[locale]/auth/page.tsx +++ b/app/[locale]/auth/page.tsx @@ -8,6 +8,7 @@ import { OTPForm } from "@/components/auth/otp-form"; import { LoginFormData } from "@/types/auth"; import { useAuth, useEmailValidation } from "@/hooks/use-auth"; import { toast } from "sonner"; +import { useTranslations } from "next-intl"; type AuthStep = "login" | "email-setup" | "otp"; diff --git a/components/auth/login-form.tsx b/components/auth/login-form.tsx index 6d2f6e0..2f3e31e 100644 --- a/components/auth/login-form.tsx +++ b/components/auth/login-form.tsx @@ -18,6 +18,7 @@ import { useAuth } from "@/hooks/use-auth"; import { listRole } from "@/service/landing/landing"; import { Role } from "@/types/auth"; import Link from "next/link"; +import { useTranslations } from "next-intl"; export const LoginForm: React.FC = ({ onSuccess, @@ -25,7 +26,7 @@ export const LoginForm: React.FC = ({ className, }) => { const { login } = useAuth(); - + const t = useTranslations("MediaUpdate"); const [showPassword, setShowPassword] = useState(false); const [rememberMe, setRememberMe] = useState(true); const [roles, setRoles] = useState([]); @@ -92,7 +93,7 @@ export const LoginForm: React.FC = ({ />

- MENYATUKAN INDONESIA + {t("unite")}

@@ -147,7 +148,7 @@ export const LoginForm: React.FC = ({ label="Username" name="username" type="text" - placeholder="Enter your username" + placeholder={t("username")} error={errors.username?.message} disabled={isSubmitting} required @@ -158,10 +159,10 @@ export const LoginForm: React.FC = ({ {/* Password Field */} = ({ disabled={isSubmitting} /> - Lupa kata sandi? + {t("forgotPass")} @@ -199,7 +200,7 @@ export const LoginForm: React.FC = ({ type="submit" fullWidth disabled={isSubmitting} - className="mt-6 bg-red-700" + className="mt-6 bg-red-700 cursor-pointer" color="primary" > {isSubmitting ? ( @@ -208,7 +209,7 @@ export const LoginForm: React.FC = ({ Processing... ) : ( - "Selanjutnya" + t("enterOTP5") )} diff --git a/components/auth/otp-form.tsx b/components/auth/otp-form.tsx index 8461469..5732328 100644 --- a/components/auth/otp-form.tsx +++ b/components/auth/otp-form.tsx @@ -2,7 +2,6 @@ import React, { useState } from "react"; import { Button } from "@/components/ui/button"; - import { OTPFormProps } from "@/types/auth"; import { useOTPVerification } from "@/hooks/use-auth"; import { @@ -11,6 +10,7 @@ import { InputOTPSeparator, InputOTPSlot, } from "../ui/input-otp"; +import { useTranslations } from "next-intl"; export const OTPForm: React.FC = ({ loginCredentials, @@ -21,6 +21,7 @@ export const OTPForm: React.FC = ({ }) => { const { verifyOTP, loading } = useOTPVerification(); const [otpValue, setOtpValue] = useState(""); + const t = useTranslations("MediaUpdate"); const handleTypeOTP = (event: React.KeyboardEvent) => { const { key } = event; @@ -75,10 +76,8 @@ export const OTPForm: React.FC = ({
{/* Header */}
-

Please Enter OTP

-

- Enter the 6-digit code sent to your email address. -

+

{t("enterOTP")}

+

{t("enterOTP2")}

{/* OTP Input */} @@ -139,7 +138,7 @@ export const OTPForm: React.FC = ({ disabled={loading} className="text-sm text-blue-600 hover:text-blue-800 underline disabled:opacity-50 disabled:cursor-not-allowed" > - Didn't receive the code? Resend + {t("enterOTP3")}
@@ -155,10 +154,10 @@ export const OTPForm: React.FC = ({ {loading ? (
- Verifying... +
) : ( - "Sign In" + t("enterOTP4") )}
diff --git a/components/form/sign-up.tsx b/components/form/sign-up.tsx index a0e44dd..e11f07e 100644 --- a/components/form/sign-up.tsx +++ b/components/form/sign-up.tsx @@ -689,7 +689,7 @@ export default function SignUp() { )} -
+ {/*
)} -
*/} +
{ + const ref = useRef(null); + const isInView = useInView(ref, { once: false }); + const mainControls = useAnimation(); + const slideControls = useAnimation(); + + useEffect(() => { + if (isInView) { + mainControls.start("visible"); + slideControls.start("visible"); + } else mainControls.start("hidden"); + }, [isInView]); + + return ( +
+ + {children} + + {/* TODO green slide thingy */} + {/* */} +
+ ); +}; diff --git a/components/landing-page/category.tsx b/components/landing-page/category.tsx index f9d47e5..591bd1e 100644 --- a/components/landing-page/category.tsx +++ b/components/landing-page/category.tsx @@ -1,9 +1,14 @@ "use client"; import { useState, useEffect } from "react"; -import { getArticleCategories, ArticleCategory } from "@/service/categories/article-categories"; +import { + getArticleCategories, + ArticleCategory, +} from "@/service/categories/article-categories"; +import { useTranslations } from "next-intl"; export default function Category() { + const t = useTranslations("MediaUpdate"); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); @@ -15,7 +20,8 @@ export default function Category() { if (response?.data?.success && response.data.data) { // Filter hanya kategori yang aktif dan published const activeCategories = response.data.data.filter( - (category: ArticleCategory) => category.isActive && category.isPublish + (category: ArticleCategory) => + category.isActive && category.isPublish ); setCategories(activeCategories); } @@ -45,15 +51,18 @@ export default function Category() { "SEPUTAR PRESTASI", ]; - const displayCategories = categories.length > 0 ? categories : fallbackCategories; + const displayCategories = + categories.length > 0 ? categories : fallbackCategories; return (

- {loading ? "Memuat Kategori..." : `${displayCategories.length} Kategori Paling Populer`} + {loading + ? t("loadCategory") + : `${displayCategories.length} ${t("category")}`}

- + {loading ? ( // Loading skeleton
@@ -70,16 +79,22 @@ export default function Category() {
{displayCategories.map((category, index) => { // Handle both API data and fallback data - const categoryTitle = typeof category === 'string' ? category : category.title; - const categorySlug = typeof category === 'string' ? category.toLowerCase().replace(/\s+/g, '-') : category.slug; - + const categoryTitle = + typeof category === "string" ? category : category.title; + const categorySlug = + typeof category === "string" + ? category.toLowerCase().replace(/\s+/g, "-") + : category.slug; + return (
); } -// 🔹 Helper function -function itemTransform(article: any) { - return { - id: article.id, - title: article.title, - categoryName: - article.categoryName || - (article.categories && article.categories[0]?.title) || - "", - createdAt: article.createdAt, - smallThumbnailLink: article.thumbnailUrl, - fileTypeId: article.typeId, - clientName: article.clientName, - categories: article.categories, - label: - article.typeId === 1 - ? "Image" - : article.typeId === 2 - ? "Video" - : article.typeId === 3 - ? "Text" - : article.typeId === 4 - ? "Audio" - : "", - }; -} - -// 🔹 Komponen Card function Card({ item, isBig = false, - bookmarkedIds, - setBookmarkedIds, + isInitiallyBookmarked = false, + onSaved, }: { item: any; isBig?: boolean; - bookmarkedIds: Set; - setBookmarkedIds: React.Dispatch>>; + isInitiallyBookmarked?: boolean; + onSaved?: (id: number) => void; }) { const router = useRouter(); + const t = useTranslations("MediaUpdate"); const MySwal = withReactContent(Swal); const [isSaving, setIsSaving] = useState(false); + const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked); - const isBookmarked = bookmarkedIds.has(Number(item.id)); + useEffect(() => { + setIsBookmarked(isInitiallyBookmarked); + }, [isInitiallyBookmarked]); const getLink = () => `/content/image/detail/${item?.id}`; - const handleToggleBookmark = async () => { + const handleSave = async () => { const roleId = Number(getCookiesDecrypt("urie")); + if (!roleId || isNaN(roleId)) { MySwal.fire({ icon: "warning", @@ -215,31 +245,26 @@ function Card({ MySwal.fire({ icon: "error", title: "Gagal", - text: "Gagal memperbarui bookmark.", + text: "Gagal menyimpan artikel.", confirmButtonColor: "#d33", }); } else { - const updated = new Set(bookmarkedIds); - let pesan = ""; + setIsBookmarked(true); + onSaved?.(item.id); - if (isBookmarked) { - updated.delete(Number(item.id)); - pesan = "Dihapus dari bookmark."; - } else { - updated.add(Number(item.id)); - pesan = "Artikel disimpan ke bookmark."; - } - - setBookmarkedIds(updated); + const saved = localStorage.getItem("bookmarkedIds"); + const newSet = new Set(saved ? JSON.parse(saved) : []); + newSet.add(Number(item.id)); localStorage.setItem( "bookmarkedIds", - JSON.stringify(Array.from(updated)) + JSON.stringify(Array.from(newSet)) ); MySwal.fire({ icon: "success", - title: isBookmarked ? "Dihapus!" : "Disimpan!", - text: pesan, + title: "Berhasil", + text: "Artikel berhasil disimpan ke bookmark.", + confirmButtonColor: "#3085d6", timer: 1500, showConfirmButton: false, }); @@ -258,26 +283,30 @@ function Card({ }; return ( -
-
- - {item.title} - -
+
+
+
+ + {item.title} + +
-
-
+
{item.clientName} @@ -307,31 +336,31 @@ function Card({ {item.title} -
-
-
- - +
+
+ + +
+ +
- -
@@ -357,18 +386,16 @@ function Card({ // const router = useRouter(); // const params = useParams(); // const MySwal = withReactContent(Swal); - -// // Get slug from URL params // const slug = params?.slug as string; +// // ✅ Ambil data artikel (khusus typeId = 1 -> Image) // useEffect(() => { // const fetchData = async () => { // try { -// // 🔹 Ambil artikel // const response = await listArticles( // 1, // 5, -// undefined, +// 1, // hanya typeId = 1 (image) // undefined, // undefined, // "createdAt", @@ -378,8 +405,7 @@ function Card({ // let articlesData: any[] = []; // if (response?.error) { -// // fallback ke API lama -// const fallbackResponse = await listData( +// const fallback = await listData( // "", // "", // "", @@ -388,53 +414,40 @@ function Card({ // "createdAt", // "", // "", -// "" +// "1" // ); -// articlesData = fallbackResponse?.data?.data?.content || []; +// articlesData = fallback?.data?.data?.content || []; // } else { // articlesData = response?.data?.data || []; // } -// // 🔹 Transform agar seragam -// const transformed = 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, -// clientName: article.clientName, -// categories: article.categories, -// label: -// article.typeId === 1 -// ? "Image" -// : article.typeId === 2 -// ? "Video" -// : article.typeId === 3 -// ? "Text" -// : article.typeId === 4 -// ? "Audio" -// : "", -// })); +// const transformed = articlesData.map((article: any) => +// itemTransform(article) +// ); // setData(transformed); +// } catch (error) { +// console.error("Gagal memuat data:", error); +// } +// }; +// fetchData(); +// }, [slug]); + +// // ✅ Sinkronisasi bookmark: dari localStorage + backend user login +// useEffect(() => { +// const syncBookmarks = async () => { +// try { // const roleId = Number(getCookiesDecrypt("urie")); +// let localSet = new Set(); + +// const simpananLocal = localStorage.getItem("bookmarkedIds"); +// if (simpananLocal) { +// localSet = new Set(JSON.parse(simpananLocal)); +// } + +// // Jika user login, gabungkan dengan data dari backend // if (roleId && !isNaN(roleId)) { -// // const saved = localStorage.getItem("bookmarkedIds"); -// const userId = getCookiesDecrypt("uie"); -// const localKey = `bookmarkedIds_${userId || "guest"}`; -// const saved = localStorage.getItem(localKey); - -// let localSet = new Set(); -// if (saved) { -// localSet = new Set(JSON.parse(saved)); -// setBookmarkedIds(localSet); -// } - // const res = await getBookmarkSummaryForUser(); // const bookmarks = // res?.data?.data?.recentBookmarks || @@ -448,42 +461,34 @@ function Card({ // .filter((x) => !isNaN(x)) // ); -// const merged = new Set([...localSet, ...ids]); -// setBookmarkedIds(merged); +// const gabungan = new Set([...localSet, ...ids]); +// setBookmarkedIds(gabungan); // localStorage.setItem( // "bookmarkedIds", -// JSON.stringify(Array.from(merged)) +// JSON.stringify(Array.from(gabungan)) // ); +// } else { +// // Jika belum login, pakai local saja +// setBookmarkedIds(localSet); // } -// } catch (error) { -// console.error("Gagal memuat data:", error); +// } catch (err) { +// console.error("Gagal sinkronisasi bookmark:", err); // } // }; -// fetchData(); +// syncBookmarks(); // }, []); -// // Simpan setiap kali state berubah -// useEffect(() => { -// if (bookmarkedIds.size > 0) { -// localStorage.setItem( -// "bookmarkedIds", -// JSON.stringify(Array.from(bookmarkedIds)) -// ); -// } -// }, [bookmarkedIds]); - // return ( //
//
// {data.length > 0 && ( // -// setBookmarkedIds((prev) => new Set([...prev, Number(id)])) -// } +// bookmarkedIds={bookmarkedIds} +// setBookmarkedIds={setBookmarkedIds} // /> // )} @@ -492,10 +497,8 @@ function Card({ // -// setBookmarkedIds((prev) => new Set([...prev, Number(id)])) -// } +// bookmarkedIds={bookmarkedIds} +// setBookmarkedIds={setBookmarkedIds} // /> // ))} //
@@ -513,44 +516,55 @@ function Card({ // ); // } +// // 🔹 Helper function +// function itemTransform(article: any) { +// return { +// id: article.id, +// title: article.title, +// categoryName: +// article.categoryName || +// (article.categories && article.categories[0]?.title) || +// "", +// createdAt: article.createdAt, +// smallThumbnailLink: article.thumbnailUrl, +// fileTypeId: article.typeId, +// clientName: article.clientName, +// categories: article.categories, +// label: +// article.typeId === 1 +// ? "Image" +// : article.typeId === 2 +// ? "Video" +// : article.typeId === 3 +// ? "Text" +// : article.typeId === 4 +// ? "Audio" +// : "", +// }; +// } + +// // 🔹 Komponen Card // function Card({ // item, // isBig = false, -// isInitiallyBookmarked = false, -// onSaved, +// bookmarkedIds, +// setBookmarkedIds, // }: { // item: any; // isBig?: boolean; -// isInitiallyBookmarked?: boolean; -// onSaved?: (id: number) => void; +// bookmarkedIds: Set; +// setBookmarkedIds: React.Dispatch>>; // }) { // const router = useRouter(); // const MySwal = withReactContent(Swal); // const [isSaving, setIsSaving] = useState(false); -// const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked); -// useEffect(() => { -// setIsBookmarked(isInitiallyBookmarked); -// }, [isInitiallyBookmarked]); +// const isBookmarked = bookmarkedIds.has(Number(item.id)); -// const getLink = () => { -// switch (item?.fileTypeId) { -// case 1: -// return `/content/image/detail/${item?.id}`; -// case 2: -// return `/content/video/detail/${item?.id}`; -// case 3: -// return `/content/text/detail/${item?.id}`; -// case 4: -// return `/content/audio/detail/${item?.id}`; -// default: -// return "#"; -// } -// }; +// const getLink = () => `/content/image/detail/${item?.id}`; -// const handleSave = async () => { +// const handleToggleBookmark = async () => { // const roleId = Number(getCookiesDecrypt("urie")); - // if (!roleId || isNaN(roleId)) { // MySwal.fire({ // icon: "warning", @@ -570,27 +584,31 @@ function Card({ // MySwal.fire({ // icon: "error", // title: "Gagal", -// text: "Gagal menyimpan artikel.", +// text: "Gagal memperbarui bookmark.", // confirmButtonColor: "#d33", // }); // } else { -// setIsBookmarked(true); -// onSaved?.(item.id); +// const updated = new Set(bookmarkedIds); +// let pesan = ""; -// // 🔹 Simpan ke localStorage -// const saved = localStorage.getItem("bookmarkedIds"); -// const newSet = new Set(saved ? JSON.parse(saved) : []); -// newSet.add(Number(item.id)); +// if (isBookmarked) { +// updated.delete(Number(item.id)); +// pesan = "Dihapus dari bookmark."; +// } else { +// updated.add(Number(item.id)); +// pesan = "Artikel disimpan ke bookmark."; +// } + +// setBookmarkedIds(updated); // localStorage.setItem( // "bookmarkedIds", -// JSON.stringify(Array.from(newSet)) +// JSON.stringify(Array.from(updated)) // ); // MySwal.fire({ // icon: "success", -// title: "Berhasil", -// text: "Artikel berhasil disimpan ke bookmark.", -// confirmButtonColor: "#3085d6", +// title: isBookmarked ? "Dihapus!" : "Disimpan!", +// text: pesan, // timer: 1500, // showConfirmButton: false, // }); @@ -609,30 +627,26 @@ function Card({ // }; // return ( -//
-//
-//
-// -// {item.title} -// -//
+//
+//
+// +// {item.title} +// +//
-//
+//
+//
//
// // {item.clientName} @@ -662,31 +676,31 @@ function Card({ // {item.title} // // +//
-//
-//
-// -// -//
- -// +//
+//
+// +// //
+ +// //
//
//
diff --git a/components/landing-page/media-update.tsx b/components/landing-page/media-update.tsx index 9d257f5..fda874a 100644 --- a/components/landing-page/media-update.tsx +++ b/components/landing-page/media-update.tsx @@ -16,6 +16,7 @@ import { Navigation } from "swiper/modules"; import Swal from "sweetalert2"; import withReactContent from "sweetalert2-react-content"; import { ThumbsUp, ThumbsDown } from "lucide-react"; +import { useTranslations } from "next-intl"; function formatTanggal(dateString: string) { if (!dateString) return ""; @@ -35,6 +36,7 @@ function formatTanggal(dateString: string) { } export default function MediaUpdate() { + const t = useTranslations("MediaUpdate"); const [tab, setTab] = useState<"latest" | "popular">("latest"); const [contentType, setContentType] = useState< "audiovisual" | "audio" | "foto" | "text" | "all" @@ -329,7 +331,7 @@ export default function MediaUpdate() {

- Media Update + {t("title")}

{/* Main Tab */} @@ -343,7 +345,7 @@ export default function MediaUpdate() { : "text-[#C6A455] hover:bg-[#C6A455]/10" }`} > - Terbaru + {t("latest")}
@@ -381,7 +383,7 @@ export default function MediaUpdate() { : "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md" }`} > - 📸 Foto + 📸 {t("image")}
@@ -419,7 +421,7 @@ export default function MediaUpdate() { {/* Slider */} {loading ? ( -

Memuat konten...

+

{t("loadContent")}

) : ( {bookmarkedIds.has(Number(item.id)) - ? "Tersimpan" - : "Simpan"} + ? t("saved") + : t("save")}
@@ -536,7 +538,7 @@ export default function MediaUpdate() { size={"lg"} className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition" > - Lihat Lebih Banyak + {t("seeMore")}
diff --git a/components/landing-page/navbar.tsx b/components/landing-page/navbar.tsx index e87b02e..5442f16 100644 --- a/components/landing-page/navbar.tsx +++ b/components/landing-page/navbar.tsx @@ -11,22 +11,11 @@ import { Input } from "../ui/input"; import { usePathname, useRouter } from "next/navigation"; import { Link } from "@/i18n/routing"; import { DynamicLogoTenant } from "./dynamic-logo-tenant"; - -const NAV_ITEMS = [ - { label: "Beranda", href: "/" }, - { label: "Untuk Anda", href: "/for-you" }, - { label: "Mengikuti", href: "/auth" }, - { label: "Publikasi", href: "/publikasi" }, - { label: "Jadwal", href: "/schedule" }, -]; - -const PUBLIKASI_SUBMENU = [ - { label: "K/L", href: "/in/publication/kl" }, - { label: "BUMN", href: "/in/publication/bumn" }, - { label: "Pemerintah Daerah", href: "/in/publication/pemerintah-daerah" }, -]; +import { useTranslations } from "next-intl"; +import LocalSwitcher from "../partials/header/locale-switcher"; export default function Navbar() { + const t = useTranslations("Navbar"); const [isSidebarOpen, setIsSidebarOpen] = useState(false); const [isDropdownOpen, setDropdownOpen] = useState(false); const [showProfileMenu, setShowProfileMenu] = useState(false); @@ -34,6 +23,20 @@ export default function Navbar() { const pathname = usePathname(); const router = useRouter(); + const NAV_ITEMS = [ + { label: t("home"), href: "/" }, + { label: t("forYou"), href: "/for-you" }, + { label: t("following"), href: "/auth" }, + { label: t("publication"), href: "/publikasi" }, + { label: t("schedule"), href: "/schedule" }, + ]; + + const PUBLIKASI_SUBMENU = [ + { label: "K/L", href: "/in/publication/kl" }, + { label: "BUMN", href: "/in/publication/bumn" }, + { label: t("localGov"), href: "/in/publication/pemerintah-daerah" }, + ]; + // 🔍 Fungsi cek login const checkLoginStatus = () => { const roleId = getCookiesDecrypt("urie"); @@ -46,7 +49,7 @@ export default function Navbar() { }, []); const filteredNavItems = isLoggedIn - ? NAV_ITEMS.filter((item) => item.label !== "Mengikuti") + ? NAV_ITEMS.filter((item) => item.label !== t("following")) : NAV_ITEMS; // 🚪 Fungsi logout @@ -73,18 +76,20 @@ export default function Navbar() { return (
-
+ setIsSidebarOpen(true)} + /> + + Logo -
- setIsSidebarOpen(true)} - /> + +
@@ -95,7 +100,7 @@ export default function Navbar() { // 🔹 Pengecekan khusus untuk "Untuk Anda" const handleClick = (e: React.MouseEvent) => { - if (item.label === "Untuk Anda") { + if (item.label === t("forYou")) { e.preventDefault(); if (!checkLoginStatus()) { router.push("/auth"); @@ -107,7 +112,7 @@ export default function Navbar() { return (
- {item.label === "Publikasi" ? ( + {item.label === t("publication") ? ( <> - + ) : ( @@ -205,7 +210,7 @@ export default function Navbar() { onClick={handleLogout} className="w-full text-left px-4 py-2 text-sm hover:bg-gray-100 text-gray-700" > - Logout + {t("logout")}
)} @@ -226,9 +231,13 @@ export default function Navbar() {

- Bahasa + {t("language")}

-
+ {/* button language */} +
+ +
+ {/*
-
+
*/}

- Fitur + {t("features")}

{NAV_ITEMS.map((item) => ( )}
-

- Subscribe to Our Newsletter +

+ {t("subscribeTitle")}

- - + +
diff --git a/components/landing-page/schedule.tsx b/components/landing-page/schedule.tsx index 3cacf68..81bf7b0 100644 --- a/components/landing-page/schedule.tsx +++ b/components/landing-page/schedule.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import { format } from "date-fns"; import { Calendar } from "@/components/ui/calendar"; import { @@ -20,162 +20,83 @@ import { SelectItem, } from "@/components/ui/select"; import { Badge } from "@/components/ui/badge"; - -const scheduleData = [ - { - date: "Jul 1 2025", - type: "POLRI", - title: "HUT Bhayangkara RI - 79", - location: "Mabes Polri, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "POLRI", - title: "Hari Lahir Pancasila", - location: "Mabes Polri, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "POLRI", - title: "Pers Rilis Kasus", - location: "Mabes Polri, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "POLRI", - title: "Rapat Koordinasi HUT Bhayangkara RI - 79", - location: "Mabes Polri, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "MPR", - title: "Rapat PIMPINAN MPR RI", - location: "Gedung MPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "DPR", - title: "Rapat Anggota Komisi I", - location: "Gedung DPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "MPR", - title: "Sidang Paripurna", - location: "Gedung MPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "KEJAKSAAN AGUNG", - title: "Hari Lahir Pancasila", - location: "Kejaksaan Agung, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "KPU", - title: "Hari Lahir Pancasila", - location: "Kantor KPU, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "DPR", - title: "Rapat Anggota Komisi II", - location: "Gedung DPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "MPR", - title: "Rapat DPR dan Basarnas", - location: "Gedung MPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "BUMN", - title: "Hari Lahir Pancasila", - location: "Kantor BUMN, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "BUMN", - title: "Focus Group Discussion", - location: "Kantor BUMN, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "MPR", - title: "Rapat Anggota MPR RI", - location: "Gedung MPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "BUMN", - title: "Seremoni Sinergi BUMN", - location: "Kantor BUMN, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "MPR", - title: "Sumpah Janji Anggota MPR RI", - location: "Gedung MPR, Jakarta, Indonesia", - }, - { - date: "Jul 1 2025", - type: "KPK", - title: "Hari Lahir Pancasila", - location: "Kantor KPK, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "BUMN", - title: "Monitoring dan Evaluasi Keterbukaan Informasi Publik Tahun 2025", - location: "Kantor BUMN, Jakarta Indonesia", - }, - { - date: "Jul 1 2025", - type: "KEJAKSAAN AGUNG", - title: "Hari Lahir Pancasila", - location: "Kejaksaan Agung, Jakarta, Indonesia", - }, - { - date: "Jul 2 2025", - type: "MPR", - title: "Monitoring dan Evaluasi Informasi MPR Tahun 2025", - location: "Gedung MPR, Jakarta, Indonesia", - }, -]; - -const categories = [ - "SEMUA", - "POLRI", - "MAHKAMAH AGUNG", - "DPR", - "MPR", - "KEJAKSAAN AGUNG", - "KPK", - "PUPR", - "BSKDN", - "BUMN", - "KPU", -]; +import { getAllSchedules } from "@/service/landing/landing"; export default function Schedule() { const [search, setSearch] = useState(""); - const [startDate, setStartDate] = useState( - new Date("2025-07-01") - ); - const [endDate, setEndDate] = useState( - new Date("2025-07-30") - ); + const [startDate, setStartDate] = useState(new Date()); + const [endDate, setEndDate] = useState(new Date()); const [selectedCategory, setSelectedCategory] = useState("SEMUA"); + const [scheduleData, setScheduleData] = useState([]); + const [loading, setLoading] = useState(false); - const filteredData = scheduleData.filter((item) => { - const matchesCategory = - selectedCategory === "SEMUA" || item.type === selectedCategory; - const matchesSearch = item.title - .toLowerCase() - .includes(search.toLowerCase()); - return matchesCategory && matchesSearch; - }); + const categories = [ + "SEMUA", + "POLRI", + "MAHKAMAH AGUNG", + "DPR", + "MPR", + "KEJAKSAAN AGUNG", + "KPK", + "PUPR", + "BSKDN", + "BUMN", + "KPU", + ]; + + const fetchSchedules = async () => { + try { + setLoading(true); + + const params = { + title: search || undefined, + startDate: startDate ? format(startDate, "yyyy-MM-dd") : undefined, + endDate: endDate ? format(endDate, "yyyy-MM-dd") : undefined, + page: 1, + limit: 50, + sortBy: "startDate", + sort: "asc", + }; + + const res = await getAllSchedules(params); + + if (!res.error) { + const apiData = Array.isArray(res.data) + ? res.data + : Array.isArray(res.data?.data) + ? res.data.data + : Array.isArray(res.data?.records) + ? res.data.records + : []; + + setScheduleData(apiData); + } else { + console.error("Gagal memuat jadwal:", res.message); + setScheduleData([]); + } + } catch (error) { + console.error("Error fetching schedules:", error); + setScheduleData([]); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchSchedules(); + }, [startDate, endDate, search]); + + const filteredData = Array.isArray(scheduleData) + ? scheduleData.filter((item) => { + const matchesCategory = + selectedCategory === "SEMUA" || + item.type?.toUpperCase() === selectedCategory; + const matchesSearch = item.title + ?.toLowerCase() + .includes(search.toLowerCase()); + return matchesCategory && matchesSearch; + }) + : []; return (
@@ -276,37 +197,51 @@ export default function Schedule() {

Semua Jadwal

-
- {filteredData.map((item, index) => ( -
-
- {item.date} + + {loading ? ( +
+ Memuat data jadwal... +
+ ) : filteredData.length === 0 ? ( +
+ Tidak ada jadwal ditemukan. +
+ ) : ( +
+ {filteredData.map((item, index) => ( +
+
+ {item.startDate + ? format(new Date(item.startDate), "dd MMM yyyy") + : "-"} +
+
+ + {item.type || "-"} + +
+
{item.title}
+
+ {item.location} +
-
- - {item.type} - -
-
{item.title}
-
- {item.location} -
-
- ))} -
+ ))} +
+ )} +
@@ -314,3 +249,320 @@ export default function Schedule() {
); } + +// "use client"; + +// import { useState } from "react"; +// import { format } from "date-fns"; +// import { Calendar } from "@/components/ui/calendar"; +// import { +// Popover, +// PopoverContent, +// PopoverTrigger, +// } from "@/components/ui/popover"; +// import { Button } from "@/components/ui/button"; +// import { Input } from "@/components/ui/input"; +// import { CalendarIcon, ChevronRight } from "lucide-react"; +// import { cn } from "@/lib/utils"; +// import { +// Select, +// SelectTrigger, +// SelectValue, +// SelectContent, +// SelectItem, +// } from "@/components/ui/select"; +// import { Badge } from "@/components/ui/badge"; + +// const scheduleData = [ +// { +// date: "Jul 1 2025", +// type: "POLRI", +// title: "HUT Bhayangkara RI - 79", +// location: "Mabes Polri, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "POLRI", +// title: "Hari Lahir Pancasila", +// location: "Mabes Polri, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "POLRI", +// title: "Pers Rilis Kasus", +// location: "Mabes Polri, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "POLRI", +// title: "Rapat Koordinasi HUT Bhayangkara RI - 79", +// location: "Mabes Polri, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "MPR", +// title: "Rapat PIMPINAN MPR RI", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "DPR", +// title: "Rapat Anggota Komisi I", +// location: "Gedung DPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "MPR", +// title: "Sidang Paripurna", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "KEJAKSAAN AGUNG", +// title: "Hari Lahir Pancasila", +// location: "Kejaksaan Agung, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "KPU", +// title: "Hari Lahir Pancasila", +// location: "Kantor KPU, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "DPR", +// title: "Rapat Anggota Komisi II", +// location: "Gedung DPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "MPR", +// title: "Rapat DPR dan Basarnas", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "BUMN", +// title: "Hari Lahir Pancasila", +// location: "Kantor BUMN, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "BUMN", +// title: "Focus Group Discussion", +// location: "Kantor BUMN, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "MPR", +// title: "Rapat Anggota MPR RI", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "BUMN", +// title: "Seremoni Sinergi BUMN", +// location: "Kantor BUMN, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "MPR", +// title: "Sumpah Janji Anggota MPR RI", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "KPK", +// title: "Hari Lahir Pancasila", +// location: "Kantor KPK, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "BUMN", +// title: "Monitoring dan Evaluasi Keterbukaan Informasi Publik Tahun 2025", +// location: "Kantor BUMN, Jakarta Indonesia", +// }, +// { +// date: "Jul 1 2025", +// type: "KEJAKSAAN AGUNG", +// title: "Hari Lahir Pancasila", +// location: "Kejaksaan Agung, Jakarta, Indonesia", +// }, +// { +// date: "Jul 2 2025", +// type: "MPR", +// title: "Monitoring dan Evaluasi Informasi MPR Tahun 2025", +// location: "Gedung MPR, Jakarta, Indonesia", +// }, +// ]; + +// const categories = [ +// "SEMUA", +// "POLRI", +// "MAHKAMAH AGUNG", +// "DPR", +// "MPR", +// "KEJAKSAAN AGUNG", +// "KPK", +// "PUPR", +// "BSKDN", +// "BUMN", +// "KPU", +// ]; + +// export default function Schedule() { +// const [search, setSearch] = useState(""); +// const [startDate, setStartDate] = useState( +// new Date("2025-07-01") +// ); +// const [endDate, setEndDate] = useState( +// new Date("2025-07-30") +// ); +// const [selectedCategory, setSelectedCategory] = useState("SEMUA"); + +// const filteredData = scheduleData.filter((item) => { +// const matchesCategory = +// selectedCategory === "SEMUA" || item.type === selectedCategory; +// const matchesSearch = item.title +// .toLowerCase() +// .includes(search.toLowerCase()); +// return matchesCategory && matchesSearch; +// }); + +// return ( +//
+// {/* Filter Bar */} +//
+// setSearch(e.target.value)} +// /> + +// {/* Tanggal Mulai */} +//
+// +// +// +// +// +// +// +// +// +//
+ +// {/* Tanggal Selesai */} +//
+// +// +// +// +// +// +// +// +// +//
+ +// {/* Publikasi */} +//
+// +// +//
+//
+ +// {/* Filter Chips */} +//
+// {categories.map((cat) => ( +// +// ))} +// +//
+ +// {/* Schedule Table */} +//

+// Semua Jadwal +//

+//
+// {filteredData.map((item, index) => ( +//
+//
+// {item.date} +//
+//
+// +// {item.type} +// +//
+//
{item.title}
+//
+// {item.location} +//
+//
+// ))} +//
+//
+// +// +//
+//
+// ); +// } diff --git a/components/main/comment-detail-image.tsx b/components/main/comment-detail-image.tsx new file mode 100644 index 0000000..8d0a7fc --- /dev/null +++ b/components/main/comment-detail-image.tsx @@ -0,0 +1,730 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import { MessageCircle, Share2, Trash2 } from "lucide-react"; +import { useRouter, useParams } from "next/navigation"; +import { + getArticleDetail, + createArticleComment, + getArticleComments, + deleteArticleComment, +} from "@/service/content/content"; +import { getCookiesDecrypt } from "@/lib/utils"; +import Swal from "sweetalert2"; +import withReactContent from "sweetalert2-react-content"; + +function getAvatarColor(name: string) { + const colors = [ + "#F87171", + "#FB923C", + "#FACC15", + "#4ADE80", + "#60A5FA", + "#A78BFA", + "#F472B6", + ]; + const index = name.charCodeAt(0) % colors.length; + return colors[index]; +} + +export default function DetailCommentImage() { + const [isLoggedIn, setIsLoggedIn] = useState(false); + const [currentUserId, setCurrentUserId] = useState(null); + const [article, setArticle] = useState(null); + const [comments, setComments] = useState([]); + const [newComment, setNewComment] = useState(""); + const [replyParentId, setReplyParentId] = useState(null); + const [replyMessage, setReplyMessage] = useState(""); + const router = useRouter(); + const params = useParams(); + const MySwal = withReactContent(Swal); + const id = Number(params?.id); + + useEffect(() => { + checkLoginStatus(); + if (id) { + fetchArticleDetail(id); + fetchComments(id); + } + }, [id]); + + const checkLoginStatus = () => { + const userId = getCookiesDecrypt("urie"); + if (userId) { + setIsLoggedIn(true); + setCurrentUserId(Number(userId)); + } else { + setIsLoggedIn(false); + setCurrentUserId(null); + } + }; + + const fetchArticleDetail = async (articleId: number) => { + try { + const res = await getArticleDetail(articleId); + if (res?.data?.data) setArticle(res.data.data); + } catch (error) { + console.error("Gagal memuat artikel:", error); + } + }; + + const fetchComments = async (articleId: number) => { + try { + const res = await getArticleComments(articleId); + if (res?.data?.data) { + const all = res.data.data.map((c: any) => ({ + ...c, + parentId: c.parentId ?? 0, + })); + const structured = buildCommentTree(all); + setComments(structured); + } + } catch (error) { + console.error("Gagal memuat komentar:", error); + } + }; + + const buildCommentTree: any = (comments: any[], parentId = 0) => + comments + .filter((c) => c.parentId === parentId) + .map((c) => ({ + ...c, + replies: buildCommentTree(comments, c.id), + })); + + const handlePostComment = async () => { + if (!newComment.trim()) { + MySwal.fire("Oops!", "Komentar tidak boleh kosong.", "warning"); + return; + } + await sendComment({ + articleId: id, + message: newComment, + isPublic: true, + parentId: 0, + }); + setNewComment(""); + }; + + const handleReplySubmit = async (parentId: number) => { + if (!replyMessage.trim()) { + MySwal.fire("Oops!", "Balasan tidak boleh kosong.", "warning"); + return; + } + await sendComment({ + articleId: id, + message: replyMessage, + isPublic: true, + parentId, + }); + setReplyMessage(""); + setReplyParentId(null); + }; + + const sendComment = async (payload: any) => { + MySwal.fire({ + title: "Mengirim komentar...", + didOpen: () => MySwal.showLoading(), + allowOutsideClick: false, + showConfirmButton: false, + }); + + try { + const res = await createArticleComment(payload); + if (res?.data?.success || !res?.error) { + MySwal.fire({ + icon: "success", + title: "Komentar terkirim!", + timer: 1000, + showConfirmButton: false, + }); + fetchComments(id); + } else { + MySwal.fire( + "Gagal", + res.message || "Tidak dapat mengirim komentar.", + "error" + ); + } + } catch (error) { + console.error(error); + MySwal.fire( + "Error", + "Terjadi kesalahan saat mengirim komentar.", + "error" + ); + } + }; + + const handleDeleteComment = async (commentId: number) => { + const confirm = await MySwal.fire({ + title: "Hapus komentar ini?", + text: "Tindakan ini tidak dapat dibatalkan!", + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#d33", + cancelButtonColor: "#6b7280", + confirmButtonText: "Ya, hapus", + cancelButtonText: "Batal", + }); + + if (!confirm.isConfirmed) return; + + MySwal.fire({ + title: "Menghapus komentar...", + didOpen: () => MySwal.showLoading(), + allowOutsideClick: false, + showConfirmButton: false, + }); + + try { + const res = await deleteArticleComment(commentId); + if (res?.data?.success || !res?.error) { + MySwal.fire({ + icon: "success", + title: "Komentar dihapus!", + timer: 1000, + showConfirmButton: false, + }); + fetchComments(id); + } else { + MySwal.fire( + "Gagal", + res.message || "Tidak dapat menghapus komentar.", + "error" + ); + } + } catch (error) { + console.error("Gagal menghapus komentar:", error); + MySwal.fire( + "Error", + "Terjadi kesalahan saat menghapus komentar.", + "error" + ); + } + }; + + return ( +
+ + +
+

+ Comments on: +

+

+ {article?.title || "Memuat judul..."} +

+
+ +
+ {isLoggedIn ? ( + <> +