mediahub-fe/components/landing-page/hero-new.tsx

698 lines
25 KiB
TypeScript

import { formatDateToIndonesian, shimmer, toBase64 } from "@/utils/globals";
import React, { useEffect, useRef, useState } from "react";
import "swiper/css/bundle";
import "swiper/css/navigation";
import {
getHeroData,
getListContent,
listPopUp,
listStaticBanner,
} from "@/service/landing/landing";
import { useParams, usePathname, useRouter } from "next/navigation";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
import { Skeleton } from "../ui/skeleton";
import Image from "next/image";
import Cookies from "js-cookie";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../ui/card";
import { Label } from "../ui/label";
import { Input } from "../ui/input";
import { Button } from "../ui/button";
import { Textarea } from "../ui/textarea";
import { Checkbox } from "../ui/checkbox";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "../ui/dialog";
import { Autoplay, Navigation, Pagination } from "swiper/modules";
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Link } from "@/i18n/routing";
import { listBannerHero } from "@/service/settings/settings";
import ImageBlurry from "../ui/image-blurry";
import { Icon } from "@iconify/react/dist/iconify.js";
type HeroModalProps = {
onClose: () => void;
group: string;
poldaName?: string;
satkerName?: string;
dataContent?: any;
};
const HeroModal = ({
onClose,
group,
poldaName,
satkerName,
dataContent,
}: HeroModalProps) => {
const [heroData, setHeroData] = useState<any[]>([]);
const params = useParams();
const locale = params?.locale;
const swiperRef = useRef<SwiperClass | null>(null);
const pathname = usePathname();
const modalRef = useRef<HTMLDivElement>(null);
let prefixPath = "";
if (group === "polda" && poldaName) {
prefixPath = `/polda/${poldaName}`;
} else if (group === "satker" && satkerName) {
prefixPath = `/satker/${satkerName}`;
}
useEffect(() => {
initFetch();
document.body.classList.add("overflow-hidden");
return () => {
document.body.classList.remove("overflow-hidden");
};
}, []);
// const initFetch = async () => {
// const response = await listPopUp(
// group === "mabes"
// ? ""
// : group === "polda" && poldaName
// ? poldaName
// : group === "satker" && satkerName
// ? "satker-" + satkerName
// : "",
// locale == "en"
// );
// const interstitial = response?.data?.data || [];
// setHeroData(interstitial);
// };
const initFetch = async () => {
const response = await listStaticBanner(
group === "mabes"
? ""
: group === "polda" && poldaName
? poldaName
: group === "satker" && satkerName
? "satker-" + satkerName
: "",
locale === "en"
);
const banners = response?.data?.data || [];
const enrichedData = banners.map((item: any) => ({
...item,
fileTypeId: item?.fileType?.id ?? null,
}));
setHeroData(enrichedData);
};
const handleClickOutside = (event: React.MouseEvent<HTMLDivElement>) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
onClose();
}
};
return (
<div
onClick={handleClickOutside}
className="fixed inset-0 flex items-center justify-center backdrop-brightness-50 z-50"
>
<div
ref={modalRef}
className="relative dark:bg-gray-900 rounded-lg w-[90%] md:w-[600px] p-4 shadow-none"
>
{dataContent?.length > 0 && (
<>
<button
className="absolute left-3 top-1/2 z-10 -translate-y-1/2 text-white text-3xl"
onClick={() => swiperRef.current?.slidePrev()}
>
<ChevronLeft />
</button>
<button
className="absolute right-3 top-1/2 z-10 -translate-y-1/2 text-white text-3xl"
onClick={() => swiperRef.current?.slideNext()}
>
<ChevronRight />
</button>
</>
)}
<Swiper
pagination={{ dynamicBullets: true }}
modules={[Pagination, Autoplay]}
onSwiper={(swiper) => (swiperRef.current = swiper)}
autoplay={{ delay: 10000 }}
className="mySwiper w-full"
>
{dataContent?.map((list: any) => (
<SwiperSlide key={list?.id}>
<div className="relative h-[310px] lg:h-[420px]">
<button
onClick={onClose}
className="absolute top-3 right-3 text-gray-700 dark:text-gray-300 hover:text-black dark:hover:text-white border border-white bg-white rounded-full h-8 w-8"
>
</button>
<Image
priority={true}
src={list?.smallThumbnailLink}
alt="gambar-utama"
width={1920}
height={1080}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
className="w-full h-[310px] lg:h-[420px] rounded-lg object-contain bg-black"
/>
{/* <ImageBlurry
priority={true}
src={list?.smallThumbnailLink}
alt="gambar-utama"
className="w-full h-[310px] lg:h-[420px] rounded-lg object-contain"
/> */}
<div className="absolute bottom-0 left-0 right-0 bg-black/30 backdrop-brightness-50 text-white pb-4 px-4 pt-8 rounded-bl-2xl rounded-tr-2xl mx-3 mb-2">
<div className="absolute top-0 left-0 bottom-0 w-2 bg-[#bb3523] rounded-bl-lg"></div>
<span className="absolute top-0 left-0 mt-2 mb-3 mx-3 bg-[#bb3523] text-white text-xs font-semibold uppercase px-2 py-1 rounded">
{list?.categoryName || "Liputan Kegiatan"}
</span>
<Link
href={
Number(list?.fileTypeId) === 1
? `${prefixPath}/image/detail/${list?.slug}`
: Number(list?.fileTypeId) === 2
? `${prefixPath}/video/detail/${list?.slug}`
: Number(list?.fileTypeId) === 3
? `${prefixPath}/document/detail/${list?.slug}`
: `${prefixPath}/audio/detail/${list?.slug}`
}
>
<h2 className="text-lg leading-tight">{list?.title}</h2>
</Link>
<p className="text-xs flex items-center gap-1 mt-2 opacity-80">
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
{list?.timezone || "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 24 24"
className="inline-block"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>
{list?.clickCount}
</p>
</div>
</div>
</SwiperSlide>
))}
<style jsx global>{`
.swiper-pagination-bullet {
background: white !important;
opacity: 0.7;
}
.swiper-pagination-bullet-active {
background: white !important;
opacity: 1;
}
`}</style>
</Swiper>
</div>
</div>
);
};
const ONE_MONTH = 30 * 24 * 60 * 60 * 1000;
const HeroNew = (props: { group?: string }) => {
const router = useRouter();
const pathname = usePathname();
const params = useParams();
const locale = params?.locale;
const [isLoading, setIsLoading] = useState<any>(true);
const [heroData, setHeroData] = useState<any>();
const [content, setContent] = useState<any>();
const [showModal, setShowModal] = useState(false);
const [showSurveyModal, setShowSurveyModal] = useState(false);
const [showFormModal, setShowFormModal] = useState(false);
const poldaName = params?.polda_name;
const satkerName = params?.satker_name;
const [newContent, setNewContent] = useState<any>();
const [selectedTab, setSelectedTab] = useState("image");
let prefixPath = poldaName
? `/polda/${poldaName}`
: satkerName
? `/satker/${satkerName}`
: "";
useEffect(() => {
fecthNewContent();
}, [selectedTab]);
const fecthNewContent = async () => {
console.log("Satker Name : ", satkerName);
const request = {
title: "",
page: 0,
size: 5,
sortBy: "createdAt",
contentTypeId:
selectedTab == "image"
? "1"
: selectedTab == "video"
? "2"
: selectedTab == "text"
? "3"
: selectedTab == "audio"
? "4"
: "",
group:
props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
isInt: locale == "en" ? true : false,
};
const response = await getListContent(request);
console.log("category", response);
setNewContent(response?.data?.data?.content);
};
useEffect(() => {
const timer = setTimeout(() => {
setIsLoading(false);
}, 3000);
return () => clearTimeout(timer);
}, []);
// useEffect(() => {
// const roleId = Cookies.get("urie");
// if (!roleId) {
// setShowModal(true);
// }
// initFetch();
// }, []);
// useEffect(() => {
// const roleId = Cookies.get("urie");
// const lastShown = Cookies.get("surveyLastShown");
// const now = new Date().getTime();
// if (roleId && roleId !== "2") {
// if (!lastShown || now - parseInt(lastShown) > ONE_MONTH) {
// setShowSurveyModal(true);
// Cookies.set("surveyLastShown", now.toString(), { expires: 30 });
// }
// }
// initFetch();
// }, []);
useEffect(() => {
const roleId = Cookies.get("urie");
const lastShown = Cookies.get("surveyLastShown");
const now = new Date().getTime();
const allowedRoles = ["1", "2", "3"];
if (roleId && allowedRoles.includes(roleId)) {
if (!lastShown || now - parseInt(lastShown) > ONE_MONTH) {
setShowSurveyModal(true);
Cookies.set("surveyLastShown", now.toString(), { expires: 30 });
}
}
initFetch();
}, []);
// Show hero modal after 5 seconds when website is fully loaded
useEffect(() => {
const timer = setTimeout(() => {
console.log("Show modal popup");
setShowModal(true);
}, 5000);
return () => clearTimeout(timer);
}, []);
useEffect(() => {
initFetch();
}, []);
const initFetch = async () => {
const request = {
title: "",
page: 0,
size: 5,
sortBy: "createdAt",
contentTypeId:
selectedTab == "image"
? "1"
: selectedTab == "video"
? "2"
: selectedTab == "text"
? "3"
: selectedTab == "audio"
? "4"
: "",
group:
props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
isInt: locale == "en" ? true : false,
};
const response = await getListContent(request);
let data = response?.data?.data?.content;
if (data) {
const resStatic = await listStaticBanner(
props.group == "mabes"
? ""
: props.group == "polda" && poldaName && String(poldaName)?.length > 1
? poldaName
: props.group == "satker" &&
satkerName &&
String(satkerName)?.length > 1
? "satker-" + satkerName
: "",
locale == "en"
);
for (let i = resStatic?.data?.data?.length; i >= 0; i--) {
const media = resStatic?.data?.data[i];
if (!media) continue;
media.fileTypeId = media?.fileType?.id ?? null;
data = data.filter((item: any) => item.id != media.id);
data.splice(0, 0, media);
}
setContent(data);
}
};
const shimmer = (w: number, h: number) => `
<svg width="${w}" height="${h}" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="g">
<stop stop-color="#bcbcbd" offset="20%" />
<stop stop-color="#f9fafb" offset="50%" />
<stop stop-color="#bcbcbd" offset="70%" />
</linearGradient>
</defs>
<rect width="${w}" height="${h}" fill="#bcbcbd" />
<rect id="r" width="${w}" height="${h}" fill="url(#g)" />
<animate xlink:href="#r" attributeName="x" from="-${w}" to="${w}" dur="1s" repeatCount="indefinite" />
</svg>`;
const toBase64 = (str: string) =>
typeof window === "undefined"
? Buffer.from(str).toString("base64")
: window.btoa(str);
const [scrollIndex, setScrollIndex] = useState(0);
useEffect(() => {
if (!content || content.length < 3) return;
const interval = setInterval(() => {
setScrollIndex((prevIndex) => (prevIndex + 1) % content.length);
}, 10000);
return () => clearInterval(interval);
}, [content]);
return (
<div className="flex items-start justify-center mx-auto w-auto">
<div className="relative">
{showModal && (
<HeroModal
onClose={() => setShowModal(false)}
group={props.group || "mabes"}
poldaName={poldaName as string}
satkerName={satkerName as string}
dataContent={content}
/>
)}
</div>
{isLoading ? (
<div className="flex flex-col space-y-3 mx-auto w-full lg:w-2/3">
<Skeleton className="h-[310px] lg:h-[420px]" />
<div className="space-y-2">
<Skeleton className="h-4 w-[250px]" />
<Skeleton className="h-4 w-[200px]" />
</div>
</div>
) : (
<div className="relative w-full h-full">
{/* <Carousel className="relative w-full">
<CarouselContent>
{content?.map((list: any) => (
<CarouselItem key={list?.id}>
<div className="relative h-[310px] lg:h-[700px]">
<Image
priority={true}
src={list?.smallThumbnailLink}
alt="gambar"
fill
className="object-cover w-full h-full"
/>
<div className="absolute inset-0 bg-black/40 z-10" />
<Link
href={
Number(list?.fileTypeId) == 1
? `${prefixPath}/image/detail/${list?.slug}`
: Number(list?.fileTypeId) == 2
? `${prefixPath}/video/detail/${list?.slug}`
: Number(list?.fileTypeId) == 3
? `${prefixPath}/document/detail/${list?.slug}`
: `${prefixPath}/audio/detail/${list?.slug}`
}
className="absolute bottom-20 left-8 lg:left-32 z-20 text-white w-[85%] lg:w-[45%] cursor-pointer"
>
<span className="text-red-600 text-lg font-bold uppercase">
{list?.categoryName}
</span>
<h2 className="text-xl font-bold">{list?.title}</h2>
<p className="text-sm mt-2">
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
{list?.timezone || "WIB"} | 👁 {list?.clickCount}
</p>
</Link>
<CarouselPrevious className="absolute left-6 top-[45%] z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full">
<ChevronLeft className="w-5 h-5" />
</CarouselPrevious>
<CarouselNext className="absolute right-6 top-[45%] z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full">
<ChevronRight className="w-5 h-5" />
</CarouselNext>
</div>
</CarouselItem>
))}
</CarouselContent>
</Carousel> */}
<div className="relative w-full">
{content && content.length > 0 && (
<>
<Swiper
modules={[Autoplay, Navigation]}
autoplay={{
delay: 10000,
disableOnInteraction: false,
}}
loop
navigation={{
nextEl: ".hero-next",
prevEl: ".hero-prev",
}}
className="w-full"
>
{content.map((list: any) => (
<SwiperSlide key={list?.id}>
<div className="relative h-[310px] lg:h-[700px]">
{/* Gambar */}
<ImageBlurry
priority
src={list?.smallThumbnailLink}
alt="gambar"
style={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
{/* Overlay */}
<div className="absolute inset-0 bg-black/40 z-10" />
{/* Judul & Link */}
<Link
href={
Number(list?.fileTypeId) === 1
? `${prefixPath}/image/detail/${list?.slug}`
: Number(list?.fileTypeId) === 2
? `${prefixPath}/video/detail/${list?.slug}`
: Number(list?.fileTypeId) === 3
? `${prefixPath}/document/detail/${list?.slug}`
: `${prefixPath}/audio/detail/${list?.slug}`
}
className="absolute bottom-10 lg:bottom-20 left-8 lg:left-32 z-20 text-white w-[85%] lg:w-[45%] cursor-pointer"
>
<span className="text-red-600 text-[10px] lg:text-lg font-bold uppercase">
{list?.categoryName}
</span>
<h2 className="text-[14px] lg:text-xl font-bold">
{list?.title}
</h2>
<p className="text-[9px] lg:text-sm mt-2">
{formatDateToIndonesian(new Date(list?.createdAt))}{" "}
{list?.timezone || "WIB"} | 👁 {list?.clickCount}
</p>
</Link>
</div>
</SwiperSlide>
))}
</Swiper>
{/* Tombol navigasi — render SEKALI dan beri selector yang di-bind */}
<button
className="hero-prev absolute left-2 top-1/2 z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"
aria-label="Previous"
>
<Icon
icon="mdi:chevron-left"
className="w-5 lg:w-10 h-5 lg:h-10"
/>
</button>
<button
className="hero-next absolute right-2 top-1/2 z-30 -translate-y-1/2 hover:bg-black/70 text-white p-2 rounded-full"
aria-label="Next"
>
<Icon
icon="mdi:chevron-right"
className="w-5 lg:w-10 h-5 lg:h-10"
/>
</button>
</>
)}
</div>
<div className="hidden lg:flex flex-col gap-3 absolute bottom-4 right-[38px] w-[520px] bg-black/40 p-4 rounded-lg z-10 h-[250px] overflow-hidden">
<div
className="transition-transform duration-500 ease-in-out"
style={{
transform: "translateY(0%)",
}}
key={scrollIndex}
>
{content?.length >= 2 &&
[0, 1].map((offset) => {
const item = content[(scrollIndex + offset) % content.length];
return (
<li
key={item?.id}
className="flex gap-4 flex-row lg:w-full mx-2 mb-2"
>
<div className="flex-shrink-0 w-32 rounded-lg">
<Image
priority={true}
placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475)
)}`}
width={720}
height={480}
src={item?.smallThumbnailLink}
alt={item?.title}
className="w-full h-[100px] object-cover rounded-lg"
/>
</div>
<div className="w-[280px] lg:w-full">
<Link
href={
Number(item?.fileTypeId) == 1
? `${prefixPath}/image/detail/${item?.slug}`
: Number(item?.fileTypeId) == 2
? `${prefixPath}/video/detail/${item?.slug}`
: Number(item?.fileTypeId) == 3
? `${prefixPath}/document/detail/${item?.slug}`
: `${prefixPath}/audio/detail/${item?.slug}`
}
className="flex flex-col justify-between"
>
<p className="rounded-lg flex text-red-600 font-bold uppercase w-fit">
{item?.categoryName}
</p>
<h3 className="text-base text-white font-bold">
{item?.title}
</h3>
<p className="text-[10px] flex flex-row items-center gap-1 text-white mt-2">
{formatDateToIndonesian(new Date(item?.createdAt))}{" "}
{item?.timezone || "WIB"} |{" "}
<svg
xmlns="http://www.w3.org/2000/svg"
width="1.2em"
height="1.2em"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M11.5 18c4 0 7.46-2.22 9.24-5.5C18.96 9.22 15.5 7 11.5 7s-7.46 2.22-9.24 5.5C4.04 15.78 7.5 18 11.5 18m0-12c4.56 0 8.5 2.65 10.36 6.5C20 16.35 16.06 19 11.5 19S3 16.35 1.14 12.5C3 8.65 6.94 6 11.5 6m0 2C14 8 16 10 16 12.5S14 17 11.5 17S7 15 7 12.5S9 8 11.5 8m0 1A3.5 3.5 0 0 0 8 12.5a3.5 3.5 0 0 0 3.5 3.5a3.5 3.5 0 0 0 3.5-3.5A3.5 3.5 0 0 0 11.5 9"
/>
</svg>{" "}
{item?.clickCount}
</p>
</Link>
</div>
</li>
);
})}
</div>
</div>
</div>
)}
</div>
);
};
export default HeroNew;