Compare commits

..

No commits in common. "124b0cdfefaa5bb3165e9289cd60fde89c552cf1" and "283e8b4cdb27b053604151f5a956d1307a36d3eb" have entirely different histories.

17 changed files with 830 additions and 1126 deletions

View File

@ -29,11 +29,11 @@ import {
checkRolePlacementsAvailability, checkRolePlacementsAvailability,
getListCompetencies, getListCompetencies,
getListExperiences, getListExperiences,
saveUserInternal,
saveUserRolePlacements, saveUserRolePlacements,
} from "@/service/management-user/management-user"; } from "@/service/management-user/management-user";
import { error, loading } from "@/config/swal"; import { error, loading } from "@/config/swal";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { saveUserInternal } from "@/service/service/management-user/management-user";
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string({ name: z.string({

View File

@ -29,12 +29,12 @@ import {
getListCompetencies, getListCompetencies,
getListExperiences, getListExperiences,
getUserById, getUserById,
saveUserInternal,
saveUserRolePlacements, saveUserRolePlacements,
} from "@/service/management-user/management-user"; } from "@/service/management-user/management-user";
import { loading } from "@/config/swal"; import { loading } from "@/config/swal";
import { Eye, EyeOff } from "lucide-react"; import { Eye, EyeOff } from "lucide-react";
import { useParams } from "next/navigation"; import { useParams } from "next/navigation";
import { saveUserInternal } from "@/service/service/management-user/management-user";
const FormSchema = z.object({ const FormSchema = z.object({
name: z.string({ name: z.string({

View File

@ -1,5 +1,6 @@
import * as React from "react"; import * as React from "react";
import { ColumnDef } from "@tanstack/react-table"; import { ColumnDef } from "@tanstack/react-table";
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react"; import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Link, useRouter } from "@/i18n/routing"; import { Link, useRouter } from "@/i18n/routing";

View File

@ -208,7 +208,7 @@ function TenantSettingsContentTable() {
</p> </p>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
{/* <SettingsIcon className="h-6 w-6 text-gray-500" /> */} <SettingsIcon className="h-6 w-6 text-gray-500" />
{/* <Button variant="outline" size="sm" onClick={checkWorkflowStatus}> {/* <Button variant="outline" size="sm" onClick={checkWorkflowStatus}>
Check Workflow Status Check Workflow Status
</Button> */} </Button> */}

View File

@ -50,8 +50,6 @@ export default function CategoriesDetailForm() {
if (id) { if (id) {
try { try {
const res = await getArticleCategoryDetail(Number(id)); const res = await getArticleCategoryDetail(Number(id));
const user = await getUserInfo();
setDetail(res?.data?.data); setDetail(res?.data?.data);
} catch (err) { } catch (err) {
console.error("Error fetching category detail:", err); console.error("Error fetching category detail:", err);
@ -138,9 +136,9 @@ export default function CategoriesDetailForm() {
{/* Status */} {/* Status */}
<div> <div>
<Label>Status</Label> <Label>Status</Label>
<p className="text-sm text-green-500"> <p className="text-sm text-slate-600">
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */} {/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
{detail.isActive ? "* Active" : "- Inactive"} {detail.isActive ? "Active" : "Inactive"}
</p> </p>
</div> </div>

View File

@ -6,7 +6,6 @@ import {
ArticleCategory, ArticleCategory,
} from "@/service/categories/article-categories"; } from "@/service/categories/article-categories";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { RevealR } from "../ui/RevealR";
export default function Category() { export default function Category() {
const t = useTranslations("MediaUpdate"); const t = useTranslations("MediaUpdate");
@ -22,7 +21,7 @@ export default function Category() {
// Filter hanya kategori yang aktif dan published // Filter hanya kategori yang aktif dan published
const activeCategories = response.data.data.filter( const activeCategories = response.data.data.filter(
(category: ArticleCategory) => (category: ArticleCategory) =>
category.isActive && category.isPublish, category.isActive && category.isPublish
); );
setCategories(activeCategories); setCategories(activeCategories);
} }
@ -56,58 +55,56 @@ export default function Category() {
categories.length > 0 ? categories : fallbackCategories; categories.length > 0 ? categories : fallbackCategories;
return ( return (
<RevealR> <section className="px-4 py-10">
<section className="px-4 py-10"> <div className="max-w-[1350px] mx-auto bg-white dark:bg-default-50 dark:border dark:border-slate-50 rounded-xl shadow-md p-6">
<div className="max-w-[1350px] mx-auto bg-white dark:bg-default-50 dark:border dark:border-slate-50 rounded-xl shadow-md p-6"> <h2 className="text-xl font-semibold mb-5">
<h2 className="text-xl font-semibold mb-5"> {loading
{loading ? t("loadCategory")
? t("loadCategory") : `${displayCategories.length} ${t("category")}`}
: `${displayCategories.length} ${t("category")}`} </h2>
</h2>
{loading ? ( {loading ? (
// Loading skeleton // Loading skeleton
<div className="flex flex-wrap gap-3"> <div className="flex flex-wrap gap-3">
{Array.from({ length: 10 }).map((_, index) => ( {Array.from({ length: 10 }).map((_, index) => (
<div <div
key={index}
className="px-4 py-2 rounded border border-gray-200 bg-gray-100 animate-pulse"
>
<div className="h-4 w-20 bg-gray-300 rounded"></div>
</div>
))}
</div>
) : (
<div className="flex flex-wrap gap-3">
{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;
return (
<button
key={index} key={index}
className="px-4 py-2 rounded border border-gray-200 bg-gray-100 animate-pulse" className="px-4 py-2 rounded border border-gray-300 text-gray-700 dark:text-white text-sm font-medium hover:bg-gray-100 hover:border-gray-400 transition-all duration-200"
onClick={() => {
// Navigate to category page or search by category
console.log(
`Category clicked: ${categoryTitle} (${categorySlug})`
);
// TODO: Implement navigation to category page
}}
> >
<div className="h-4 w-20 bg-gray-300 rounded"></div> {categoryTitle}
</div> </button>
))} );
</div> })}
) : ( </div>
<div className="flex flex-wrap gap-3"> )}
{displayCategories.map((category, index) => { </div>
// Handle both API data and fallback data </section>
const categoryTitle =
typeof category === "string" ? category : category.title;
const categorySlug =
typeof category === "string"
? category.toLowerCase().replace(/\s+/g, "-")
: category.slug;
return (
<button
key={index}
className="px-4 py-2 rounded border border-gray-300 text-gray-700 dark:text-white text-sm font-medium hover:bg-gray-100 hover:border-gray-400 transition-all duration-200"
onClick={() => {
// Navigate to category page or search by category
console.log(
`Category clicked: ${categoryTitle} (${categorySlug})`,
);
// TODO: Implement navigation to category page
}}
>
{categoryTitle}
</button>
);
})}
</div>
)}
</div>
</section>
</RevealR>
); );
} }

View File

@ -13,7 +13,6 @@ import "swiper/css";
import "swiper/css/navigation"; import "swiper/css/navigation";
import LocalSwitcher from "../partials/header/locale-switcher"; import LocalSwitcher from "../partials/header/locale-switcher";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Reveal } from "./Reveal";
// Custom styles for Swiper // Custom styles for Swiper
const swiperStyles = ` const swiperStyles = `
@ -101,180 +100,178 @@ export default function Footer() {
}, []); }, []);
return ( return (
<Reveal> <footer className="border-t bg-white dark:bg-default-50 text-center">
<footer className="border-t bg-white dark:bg-default-50 text-center"> <style jsx>{swiperStyles}</style>
<style jsx>{swiperStyles}</style> <div className="max-w-[1350px] mx-auto">
<div className="max-w-[1350px] mx-auto"> <div className="py-6">
<div className="py-6"> <h2 className="text-2xl font-semibold mb-4 px-4 md:px-0">
<h2 className="text-2xl font-semibold mb-4 px-4 md:px-0"> {t("publication")}
{t("publication")} </h2>
</h2> <div className="px-4 md:px-12">
<div className="px-4 md:px-12"> <Swiper
<Swiper modules={[Navigation, Autoplay]}
modules={[Navigation, Autoplay]} spaceBetween={24}
spaceBetween={24} slidesPerView="auto"
slidesPerView="auto" centeredSlides={clients.length <= 4}
centeredSlides={clients.length <= 4} navigation={{
navigation={{ nextEl: ".swiper-button-next",
nextEl: ".swiper-button-next", prevEl: ".swiper-button-prev",
prevEl: ".swiper-button-prev", }}
}} autoplay={{
autoplay={{ delay: 3000,
delay: 3000, disableOnInteraction: false,
disableOnInteraction: false, }}
}} loop={clients.length > 4}
loop={clients.length > 4} className={`client-swiper ${
className={`client-swiper ${ clients.length <= 4 ? "swiper-centered" : ""
clients.length <= 4 ? "swiper-centered" : "" }`}
}`} >
> {loading
{loading ? // Loading skeleton
? // Loading skeleton Array.from({ length: 8 }).map((_, idx) => (
Array.from({ length: 8 }).map((_, idx) => ( <SwiperSlide key={idx} className="!w-auto">
<SwiperSlide key={idx} className="!w-auto"> <div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] bg-gray-200 rounded animate-pulse" />
<div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] bg-gray-200 rounded animate-pulse" /> </SwiperSlide>
</SwiperSlide> ))
)) : clients.length > 0
: clients.length > 0 ? // Dynamic clients from API
? // Dynamic clients from API clients.map((client, idx) => (
clients.map((client, idx) => ( <SwiperSlide key={idx} className="!w-auto">
<SwiperSlide key={idx} className="!w-auto"> <a
<a href={`/in/tenant/${client.slug}`}
href={`/in/tenant/${client.slug}`} target="_blank"
target="_blank" rel="noopener noreferrer"
rel="noopener noreferrer" className="group block"
className="group block" >
> {client.logoUrl ? (
{client.logoUrl ? ( <Image
<Image src={client.logoUrl}
src={client.logoUrl} alt={client.name}
alt={client.name} width={100}
width={100} height={100}
height={100} className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition"
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition" />
/> ) : (
) : ( // Fallback when no logo - menggunakan placeholder image
// Fallback when no logo - menggunakan placeholder image <div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] rounded flex items-center justify-center hover:from-blue-200 hover:to-blue-300 transition-all duration-200">
<div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] rounded flex items-center justify-center hover:from-blue-200 hover:to-blue-300 transition-all duration-200">
<Image
src="/logo-netidhub.png"
alt={`${client.name} placeholder`}
width={100}
height={100}
className="md:w-[100px] md:h-[100px] object-contain opacity-70"
/>
</div>
)}
</a>
</SwiperSlide>
))
: // Fallback to static logos if API fails or no data
logos.map((logo, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<a
href={`/in/tenant/${logo.slug}`}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Image <Image
src={logo.src} src="/logo-netidhub.png"
alt={`logo-${idx}`} alt={`${client.name} placeholder`}
width={80} width={100}
height={80} height={100}
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition" className="md:w-[100px] md:h-[100px] object-contain opacity-70"
/> />
</a> </div>
</SwiperSlide> )}
))} </a>
</Swiper> </SwiperSlide>
))
: // Fallback to static logos if API fails or no data
logos.map((logo, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<a
href={`/in/tenant/${logo.slug}`}
target="_blank"
rel="noopener noreferrer"
className="block"
>
<Image
src={logo.src}
alt={`logo-${idx}`}
width={80}
height={80}
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition"
/>
</a>
</SwiperSlide>
))}
</Swiper>
{/* Navigation Buttons */} {/* Navigation Buttons */}
{/* <div className="swiper-button-prev !text-gray-600 !w-8 !h-8 !mt-0 !top-1/2 !left-0 !-translate-y-1/2"></div> */} {/* <div className="swiper-button-prev !text-gray-600 !w-8 !h-8 !mt-0 !top-1/2 !left-0 !-translate-y-1/2"></div> */}
{/* <div className="swiper-button-next !text-gray-600 !w-8 !h-8 !mt-0 !top-1/2 !right-0 !-translate-y-1/2"></div> */} {/* <div className="swiper-button-next !text-gray-600 !w-8 !h-8 !mt-0 !top-1/2 !right-0 !-translate-y-1/2"></div> */}
</div>
</div>
<div className="border-t my-6 w-full max-w-6xl mx-auto" />
<div className="flex flex-col md:flex-row items-center justify-between gap-4 px-4 pb-6 max-w-6xl mx-auto text-sm text-gray-600 dark:text-white">
<div className="flex items-center gap-2">
<span>ver 1.0.0 @2025 - {t("netidhub")}</span>
<Image
src="/qudo.png"
alt="qudoco"
width={80}
height={80}
className="object-contain"
/>
</div>
{/* Social Media */}
<div className="flex gap-3 text-gray-800 dark:text-white">
<Instagram className="hover:text-black" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M14 13.5h2.5l1-4H14v-2c0-1.03 0-2 2-2h1.5V2.14c-.326-.043-1.557-.14-2.857-.14C11.928 2 10 3.657 10 6.7v2.8H7v4h3V22h4z"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g fill="none">
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path
fill="currentColor"
d="M4.594 4.984a1 1 0 0 1 .941.429C7.011 7.572 8.783 8.47 10.75 8.674c.096-.841.323-1.672.75-2.404c.626-1.074 1.644-1.864 3.098-2.156c2.01-.404 3.54.324 4.427 1.215l1.792-.335a1 1 0 0 1 1.053 1.478l-1.72 3.022c.157 4.361-1.055 7.405-3.639 9.502c-1.37 1.112-3.332 1.743-5.485 1.938c-2.17.196-4.623-.041-7.061-.753a1 1 0 0 1 .007-1.922c1.226-.349 2.16-.65 3.003-1.177c-1.199-.636-2.082-1.468-2.707-2.416c-.868-1.318-1.19-2.788-1.254-4.113S3.141 8 3.343 7.115c.115-.505.249-1.011.434-1.495a1 1 0 0 1 .818-.636Z"
/>
</g>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
// fill-rule="evenodd"
d="M9.935 14.628v-5.62l5.403 2.82zM21.8 8.035s-.195-1.379-.795-1.986c-.76-.796-1.613-.8-2.004-.847C16.203 5 12.004 5 12.004 5h-.008s-4.198 0-6.997.202c-.391.047-1.243.05-2.004.847c-.6.607-.795 1.986-.795 1.986S2 9.653 2 11.272v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.771 2.205.855c1.6.153 6.8.2 6.8.2s4.203-.006 7.001-.208c.391-.047 1.244-.05 2.004-.847c.6-.607.795-1.985.795-1.985s.2-1.619.2-3.237v-1.517c0-1.619-.2-3.237-.2-3.237"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g
fill="none"
// fill-rule="evenodd"
>
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path
fill="currentColor"
d="M14 2a2 2 0 0 1 2 2a3.004 3.004 0 0 0 2.398 2.94a2 2 0 0 1-.796 3.92A7 7 0 0 1 16 10.325V16a6 6 0 1 1-7.499-5.81a2 2 0 0 1 .998 3.873A2.002 2.002 0 0 0 10 18a2 2 0 0 0 2-2V4a2 2 0 0 1 2-2"
/>
</g>
</svg>
</div>
{/* button language */}
<div className={`relative text-left border rounded-lg`}>
<LocalSwitcher />
</div>
</div> </div>
</div> </div>
</footer>
</Reveal> <div className="border-t my-6 w-full max-w-6xl mx-auto" />
<div className="flex flex-col md:flex-row items-center justify-between gap-4 px-4 pb-6 max-w-6xl mx-auto text-sm text-gray-600 dark:text-white">
<div className="flex items-center gap-2">
<span>ver 1.0.0 @2025 - {t("netidhub")}</span>
<Image
src="/qudo.png"
alt="qudoco"
width={80}
height={80}
className="object-contain"
/>
</div>
{/* Social Media */}
<div className="flex gap-3 text-gray-800 dark:text-white">
<Instagram className="hover:text-black" />
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M14 13.5h2.5l1-4H14v-2c0-1.03 0-2 2-2h1.5V2.14c-.326-.043-1.557-.14-2.857-.14C11.928 2 10 3.657 10 6.7v2.8H7v4h3V22h4z"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g fill="none">
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path
fill="currentColor"
d="M4.594 4.984a1 1 0 0 1 .941.429C7.011 7.572 8.783 8.47 10.75 8.674c.096-.841.323-1.672.75-2.404c.626-1.074 1.644-1.864 3.098-2.156c2.01-.404 3.54.324 4.427 1.215l1.792-.335a1 1 0 0 1 1.053 1.478l-1.72 3.022c.157 4.361-1.055 7.405-3.639 9.502c-1.37 1.112-3.332 1.743-5.485 1.938c-2.17.196-4.623-.041-7.061-.753a1 1 0 0 1 .007-1.922c1.226-.349 2.16-.65 3.003-1.177c-1.199-.636-2.082-1.468-2.707-2.416c-.868-1.318-1.19-2.788-1.254-4.113S3.141 8 3.343 7.115c.115-.505.249-1.011.434-1.495a1 1 0 0 1 .818-.636Z"
/>
</g>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
// fill-rule="evenodd"
d="M9.935 14.628v-5.62l5.403 2.82zM21.8 8.035s-.195-1.379-.795-1.986c-.76-.796-1.613-.8-2.004-.847C16.203 5 12.004 5 12.004 5h-.008s-4.198 0-6.997.202c-.391.047-1.243.05-2.004.847c-.6.607-.795 1.986-.795 1.986S2 9.653 2 11.272v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.771 2.205.855c1.6.153 6.8.2 6.8.2s4.203-.006 7.001-.208c.391-.047 1.244-.05 2.004-.847c.6-.607.795-1.985.795-1.985s.2-1.619.2-3.237v-1.517c0-1.619-.2-3.237-.2-3.237"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
>
<g
fill="none"
// fill-rule="evenodd"
>
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
<path
fill="currentColor"
d="M14 2a2 2 0 0 1 2 2a3.004 3.004 0 0 0 2.398 2.94a2 2 0 0 1-.796 3.92A7 7 0 0 1 16 10.325V16a6 6 0 1 1-7.499-5.81a2 2 0 0 1 .998 3.873A2.002 2.002 0 0 0 10 18a2 2 0 0 0 2-2V4a2 2 0 0 1 2-2"
/>
</g>
</svg>
</div>
{/* button language */}
<div className={`relative text-left border rounded-lg`}>
<LocalSwitcher />
</div>
</div>
</div>
</footer>
); );
} }

View File

@ -18,9 +18,6 @@ import "swiper/css/navigation";
import "swiper/css/pagination"; import "swiper/css/pagination";
import ImageBlurry from "../ui/image-blurry"; import ImageBlurry from "../ui/image-blurry";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { Reveal } from "../ui/Reveal";
import { RevealL } from "../ui/RevealL";
import { RevealR } from "../ui/RevealR";
const images = ["/PPS.png", "/PPS2.jpeg", "/PPS3.jpg", "/PPS4.png"]; const images = ["/PPS.png", "/PPS2.jpeg", "/PPS3.jpg", "/PPS4.png"];
@ -44,7 +41,7 @@ export default function Header() {
undefined, undefined,
undefined, undefined,
"createdAt", "createdAt",
slug, slug
); );
let articlesData: any[] = []; let articlesData: any[] = [];
@ -59,14 +56,14 @@ export default function Header() {
"createdAt", "createdAt",
"", "",
"", "",
"", ""
); );
articlesData = (fallbackResponse?.data?.data?.content || []).filter( articlesData = (fallbackResponse?.data?.data?.content || []).filter(
(item: any) => item.typeId === 1, (item: any) => item.typeId === 1
); );
} else { } else {
articlesData = (response?.data?.data || []).filter( articlesData = (response?.data?.data || []).filter(
(item: any) => item.typeId === 1, (item: any) => item.typeId === 1
); );
} }
@ -109,14 +106,14 @@ export default function Header() {
const ids = new Set<number>( const ids = new Set<number>(
(Array.isArray(bookmarks) ? bookmarks : []) (Array.isArray(bookmarks) ? bookmarks : [])
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id)) .map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
.filter((x) => !isNaN(x)), .filter((x) => !isNaN(x))
); );
const merged = new Set([...localSet, ...ids]); const merged = new Set([...localSet, ...ids]);
setBookmarkedIds(merged); setBookmarkedIds(merged);
localStorage.setItem( localStorage.setItem(
"bookmarkedIds", "bookmarkedIds",
JSON.stringify(Array.from(merged)), JSON.stringify(Array.from(merged))
); );
} }
} catch (error) { } catch (error) {
@ -131,39 +128,38 @@ export default function Header() {
if (bookmarkedIds.size > 0) { if (bookmarkedIds.size > 0) {
localStorage.setItem( localStorage.setItem(
"bookmarkedIds", "bookmarkedIds",
JSON.stringify(Array.from(bookmarkedIds)), JSON.stringify(Array.from(bookmarkedIds))
); );
} }
}, [bookmarkedIds]); }, [bookmarkedIds]);
return ( return (
<section className="max-w-[1350px] mx-auto px-4"> <section className="max-w-[1350px] mx-auto px-4">
<RevealR> <div className="flex flex-col lg:flex-row gap-6 py-6">
<div className="flex flex-col lg:flex-row gap-6 py-6"> {data.length > 0 && (
{data.length > 0 && ( <Card
item={data[0]}
isBig
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
}
/>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
{data.slice(1, 5).map((item) => (
<Card <Card
item={data[0]} key={item.id}
isBig item={item}
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))} isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
onSaved={(id) => onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)])) setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
} }
/> />
)} ))}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
{data.slice(1, 5).map((item) => (
<Card
key={item.id}
item={item}
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
}
/>
))}
</div>
</div> </div>
</RevealR> </div>
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl overflow-hidden"> <div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl overflow-hidden">
<Swiper <Swiper
@ -222,26 +218,12 @@ function Card({
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked); const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
const DEFAULT_IMAGE = "/assets/logo1.png"; const DEFAULT_IMAGE = "/assets/logo1.png";
const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE); const [imageSrc, setImageSrc] = useState(
item?.smallThumbnailLink || DEFAULT_IMAGE
);
useEffect(() => { useEffect(() => {
const src = item?.smallThumbnailLink; setImageSrc(item?.smallThumbnailLink || DEFAULT_IMAGE);
if (!src) {
setImageSrc(DEFAULT_IMAGE);
return;
}
const img = new window.Image();
img.src = src;
img.onload = () => {
setImageSrc(src);
};
img.onerror = () => {
setImageSrc(DEFAULT_IMAGE);
};
}, [item?.smallThumbnailLink]); }, [item?.smallThumbnailLink]);
useEffect(() => { useEffect(() => {
@ -284,7 +266,7 @@ function Card({
newSet.add(Number(item.id)); newSet.add(Number(item.id));
localStorage.setItem( localStorage.setItem(
"bookmarkedIds", "bookmarkedIds",
JSON.stringify(Array.from(newSet)), JSON.stringify(Array.from(newSet))
); );
MySwal.fire({ MySwal.fire({
@ -310,92 +292,94 @@ function Card({
}; };
return ( return (
// <RevealL> <div>
<div
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white dark:bg-black dark:border dark:border-slate-50 ${
isBig
? "w-full lg:max-w-[670px] lg:min-h-[680px]"
: "w-full h-[350px] md:h-[330px]"
}`}
>
<div <div
className={`relative ${ className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white dark:bg-black dark:border dark:border-slate-50 ${
isBig ? "aspect-[3/2] lg:h-[525px]" : "aspect-video" isBig
} w-full`} ? "w-full lg:max-w-[670px] lg:min-h-[680px]"
: "w-full h-[350px] md:h-[330px]"
}`}
> >
<Link href={getLink()}> <div
{/* <Image className={`relative ${
isBig ? "aspect-[3/2] lg:h-[525px]" : "aspect-video"
} w-full`}
>
<Link href={getLink()}>
{/* <Image
src={item.smallThumbnailLink || "/contributor.png"} src={item.smallThumbnailLink || "/contributor.png"}
alt={item.title} alt={item.title}
fill fill
className="object-cover" className="object-cover"
/> */} /> */}
<ImageBlurry <Image
src={imageSrc} src={imageSrc}
alt={item.title} alt={item.title}
className="w-full h-full object-contain" fill
/> className="object-cover"
</Link> onError={() => setImageSrc(DEFAULT_IMAGE)}
</div> />
</Link>
<div className="py-[26px] px-4 space-y-2">
<div className="flex justify-between items-center gap-2 text-xs font-semibold flex-row">
<span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
{item.clientName}
</span>
<span className="text-orange-600">
{item.categories?.map((cat: any) => cat.title).join(", ")}
</span>
</div> </div>
<div className="text-xs text-gray-500"> <div className="py-[26px] px-4 space-y-2">
{new Date(item.createdAt) <div className="flex justify-between items-center gap-2 text-xs font-semibold flex-row">
.toLocaleString("id-ID", { <span className="bg-emerald-600 text-white px-2 py-0.5 rounded">
day: "2-digit", {item.clientName}
month: "short", </span>
year: "numeric", <span className="text-orange-600">
hour: "2-digit", {item.categories?.map((cat: any) => cat.title).join(", ")}
minute: "2-digit", </span>
hour12: false,
timeZone: "Asia/Jakarta",
})
.replace(".", ":")}{" "}
WIB
</div>
<Link href={getLink()}>
<h3 className="text-sm font-semibold leading-snug line-clamp-2">
{item.title}
</h3>
</Link>
<div className="flex justify-between items-center pt-2">
<div className="flex gap-2 text-gray-500">
<ThumbsUp
size={18}
className="cursor-pointer hover:text-[#F60100]"
/>
<ThumbsDown
size={18}
className="cursor-pointer hover:text-red-600"
/>
</div> </div>
<button <div className="text-xs text-gray-500">
onClick={handleSave} {new Date(item.createdAt)
disabled={isSaving || isBookmarked} .toLocaleString("id-ID", {
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${ day: "2-digit",
isBookmarked month: "short",
? "bg-gray-400 text-white cursor-not-allowed" year: "numeric",
: "bg-[#F60100] text-white hover:bg-[#c90000]" hour: "2-digit",
}`} minute: "2-digit",
> hour12: false,
{isSaving ? t("saving") : isBookmarked ? t("saved") : t("save")} timeZone: "Asia/Jakarta",
</button> })
.replace(".", ":")}{" "}
WIB
</div>
<Link href={getLink()}>
<h3 className="text-sm font-semibold leading-snug line-clamp-2">
{item.title}
</h3>
</Link>
<div className="flex justify-between items-center pt-2">
<div className="flex gap-2 text-gray-500">
<ThumbsUp
size={18}
className="cursor-pointer hover:text-[#F60100]"
/>
<ThumbsDown
size={18}
className="cursor-pointer hover:text-red-600"
/>
</div>
<button
onClick={handleSave}
disabled={isSaving || isBookmarked}
className={`text-sm px-3 py-1 rounded-md transition-all duration-200 ${
isBookmarked
? "bg-gray-400 text-white cursor-not-allowed"
: "bg-[#F60100] text-white hover:bg-[#c90000]"
}`}
>
{isSaving ? t("saving") : isBookmarked ? t("saved") : t("save")}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
// </RevealL>
); );
} }

View File

@ -17,8 +17,6 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content"; import withReactContent from "sweetalert2-react-content";
import { ThumbsUp, ThumbsDown } from "lucide-react"; import { ThumbsUp, ThumbsDown } from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import ImageBlurry from "../ui/image-blurry";
import { RevealL } from "../ui/RevealL";
function formatTanggal(dateString: string) { function formatTanggal(dateString: string) {
if (!dateString) return ""; if (!dateString) return "";
@ -342,74 +340,61 @@ export default function MediaUpdate() {
}; };
function SafeImage({ src, alt }: { src?: string; alt?: string }) { function SafeImage({ src, alt }: { src?: string; alt?: string }) {
const [imgSrc, setImgSrc] = useState(DEFAULT_IMAGE); const [imgSrc, setImgSrc] = useState(src || DEFAULT_IMAGE);
useEffect(() => { useEffect(() => {
if (!src) { setImgSrc(src || DEFAULT_IMAGE);
setImgSrc(DEFAULT_IMAGE);
return;
}
const img = new window.Image();
img.src = src;
img.onload = () => {
setImgSrc(src);
};
img.onerror = () => {
setImgSrc(DEFAULT_IMAGE);
};
}, [src]); }, [src]);
return ( return (
<ImageBlurry <Image
src={imgSrc} src={imgSrc}
alt={alt || "Image"} alt={alt || "Image"}
className="w-full h-full object-contain" fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
onError={() => setImgSrc(DEFAULT_IMAGE)}
/> />
); );
} }
return ( return (
<RevealL> <section className="bg-white dark:bg-default-50 px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10">
<section className="bg-white dark:bg-default-50 px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10"> <div className="max-w-screen-xl mx-auto">
<div className="max-w-screen-xl mx-auto"> <h2 className="text-2xl font-semibold text-center mb-6">
<h2 className="text-2xl font-semibold text-center mb-6"> {t("title")}
{t("title")} </h2>
</h2>
{/* Main Tab */} {/* Main Tab */}
<div className="flex justify-center mb-6 bg-white dark:bg-default-50"> <div className="flex justify-center mb-6 bg-white dark:bg-default-50">
<Card className="bg-[#FFFFFF] dark:bg-default-50 rounded-xl flex flex-row p-3 gap-2 shadow-md border border-gray-200"> <Card className="bg-[#FFFFFF] dark:bg-default-50 rounded-xl flex flex-row p-3 gap-2 shadow-md border border-gray-200">
<button <button
onClick={() => setTab("latest")} onClick={() => setTab("latest")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${ className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "latest" tab === "latest"
? "bg-[#C6A455] text-white shadow-sm" ? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10" : "text-[#C6A455] hover:bg-[#C6A455]/10"
}`} }`}
> >
{t("latest")} {t("latest")}
</button> </button>
<button <button
onClick={() => setTab("popular")} onClick={() => setTab("popular")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${ className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "popular" tab === "popular"
? "bg-[#C6A455] text-white shadow-sm" ? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10" : "text-[#C6A455] hover:bg-[#C6A455]/10"
}`} }`}
> >
{t("popular")}{" "} {t("popular")}{" "}
</button> </button>
</Card> </Card>
</div> </div>
{/* Content Type Filter */} {/* Content Type Filter */}
<div className="flex justify-center mb-8"> <div className="flex justify-center mb-8">
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-4 border-2 border-blue-100 shadow-lg dark:bg-default-50"> <div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-4 border-2 border-blue-100 shadow-lg dark:bg-default-50">
<div className="flex flex-wrap justify-center gap-2"> <div className="flex flex-wrap justify-center gap-2">
{/* <button {/* <button
onClick={() => setContentType("all")} onClick={() => setContentType("all")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${ className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "all" contentType === "all"
@ -420,181 +405,180 @@ export default function MediaUpdate() {
📋 Semua 📋 Semua
</button> */} </button> */}
<button <button
onClick={() => setContentType("foto")} onClick={() => setContentType("foto")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${ className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "foto" contentType === "foto"
? "bg-gradient-to-r from-orange-500 to-red-600 text-white shadow-lg ring-2 ring-orange-300" ? "bg-gradient-to-r from-orange-500 to-red-600 text-white shadow-lg ring-2 ring-orange-300"
: "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md" : "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md"
}`} }`}
> >
📸 {t("image")} 📸 {t("image")}
</button> </button>
<button <button
onClick={() => setContentType("audiovisual")} onClick={() => setContentType("audiovisual")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${ className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audiovisual" contentType === "audiovisual"
? "bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-lg ring-2 ring-purple-300" ? "bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-lg ring-2 ring-purple-300"
: "bg-white text-purple-600 border-2 border-purple-200 hover:border-purple-400 hover:shadow-md" : "bg-white text-purple-600 border-2 border-purple-200 hover:border-purple-400 hover:shadow-md"
}`} }`}
> >
🎬 {t("video")} 🎬 {t("video")}
</button> </button>
<button <button
onClick={() => setContentType("audio")} onClick={() => setContentType("audio")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${ className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audio" contentType === "audio"
? "bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-lg ring-2 ring-green-300" ? "bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-lg ring-2 ring-green-300"
: "bg-white text-green-600 border-2 border-green-200 hover:border-green-400 hover:shadow-md" : "bg-white text-green-600 border-2 border-green-200 hover:border-green-400 hover:shadow-md"
}`} }`}
> >
🎵 Audio 🎵 Audio
</button> </button>
<button <button
onClick={() => setContentType("text")} onClick={() => setContentType("text")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${ className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "text" contentType === "text"
? "bg-gradient-to-r from-gray-500 to-slate-600 text-white shadow-lg ring-2 ring-gray-300" ? "bg-gradient-to-r from-gray-500 to-slate-600 text-white shadow-lg ring-2 ring-gray-300"
: "bg-white text-gray-600 border-2 border-gray-200 hover:border-gray-400 hover:shadow-md" : "bg-white text-gray-600 border-2 border-gray-200 hover:border-gray-400 hover:shadow-md"
}`} }`}
> >
📝 {t("text")} 📝 {t("text")}
</button> </button>
</div>
</div> </div>
</div> </div>
</div>
{/* Slider */} {/* Slider */}
{loading ? ( {loading ? (
<p className="text-center">{t("loadContent")}</p> <p className="text-center">{t("loadContent")}</p>
) : ( ) : (
<Swiper <Swiper
modules={[Navigation]} modules={[Navigation]}
navigation navigation
spaceBetween={20} spaceBetween={20}
slidesPerView={1} slidesPerView={1}
breakpoints={{ breakpoints={{
640: { slidesPerView: 2 }, 640: { slidesPerView: 2 },
1024: { slidesPerView: 4 }, 1024: { slidesPerView: 4 },
}} }}
> >
{filteredData.map((item) => ( {filteredData.map((item) => (
<SwiperSlide key={item.id}> <SwiperSlide key={item.id}>
<div className="rounded-xl shadow-md overflow-hidden bg-white dark:bg-default-50 dark:border dark:border-slate-50"> <div className="rounded-xl shadow-md overflow-hidden bg-white dark:bg-default-50 dark:border dark:border-slate-50">
{/* ✅ Kondisi: jika typeId = 3 (text) atau 4 (audio) tampilkan ikon, lainnya tampilkan thumbnail */} {/* ✅ Kondisi: jika typeId = 3 (text) atau 4 (audio) tampilkan ikon, lainnya tampilkan thumbnail */}
{item.typeId === 3 ? ( {item.typeId === 3 ? (
// 📝 TEXT // 📝 TEXT
<div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white"> <div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="90" width="90"
height="90" height="90"
viewBox="0 0 16 16" viewBox="0 0 16 16"
> >
<path <path
fill="currentColor" fill="currentColor"
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207z" d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207z"
/> />
</svg> </svg>
</div> </div>
) : item.typeId === 4 ? ( ) : item.typeId === 4 ? (
// 🎵 AUDIO // 🎵 AUDIO
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white"> <div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="100" width="100"
height="100" height="100"
viewBox="0 0 20 20" viewBox="0 0 20 20"
> >
<path <path
fill="currentColor" fill="currentColor"
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876" d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
/> />
</svg> </svg>
</div> </div>
) : ( ) : (
// 🎬 FOTO / VIDEO (default) // 🎬 FOTO / VIDEO (default)
<div className="w-full h-[204px] relative"> <div className="w-full h-[204px] relative">
<Link href={getLink(item)}> <Link href={getLink(item)}>
<SafeImage <SafeImage
src={item.smallThumbnailLink} src={item.smallThumbnailLink}
alt={item.title} alt={item.title}
/> />
{/* <Image {/* <Image
src={item.smallThumbnailLink || "/placeholder.png"} src={item.smallThumbnailLink || "/placeholder.png"}
alt={item.title || "No Title"} alt={item.title || "No Title"}
fill fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity" className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
/> */} /> */}
</Link>
</div>
)}
{/* Caption / info */}
<div className="p-3">
<div className="flex items-center gap-2 text-xs font-semibold flex-row justify-between mb-2">
<span className="text-xs text-white px-2 py-0.5 rounded bg-emerald-600">
{item.clientName}
</span>
<span className="text-orange-600">
{item.categories
?.map((cat: any) => cat.title)
.join(", ")}
</span>
</div>
<p className="text-xs text-gray-500 mb-1">
{formatTanggal(item.createdAt)}
</p>
<Link href={getLink(item)}>
<p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
{item.title}
</p>
</Link> </Link>
</div>
)}
<div className="flex items-center justify-between"> {/* Caption / info */}
<div className="flex gap-2 text-gray-600"> <div className="p-3">
<ThumbsUp className="w-4 h-4 cursor-pointer" /> <div className="flex items-center gap-2 text-xs font-semibold flex-row justify-between mb-2">
<ThumbsDown className="w-4 h-4 cursor-pointer" /> <span className="text-xs text-white px-2 py-0.5 rounded bg-emerald-600">
</div> {item.clientName}
<Button </span>
onClick={() => handleSave(item.id)} <span className="text-orange-600">
disabled={bookmarkedIds.has(Number(item.id))} {item.categories
variant="default" ?.map((cat: any) => cat.title)
size="sm" .join(", ")}
className={`rounded px-4 ${ </span>
bookmarkedIds.has(Number(item.id)) </div>
? "bg-gray-400 cursor-not-allowed text-white" <p className="text-xs text-gray-500 mb-1">
: "bg-red-700 text-white hover:bg-red-500" {formatTanggal(item.createdAt)}
}`} </p>
> <Link href={getLink(item)}>
{bookmarkedIds.has(Number(item.id)) <p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
? t("saved") {item.title}
: t("save")} </p>
</Button> </Link>
<div className="flex items-center justify-between">
<div className="flex gap-2 text-gray-600">
<ThumbsUp className="w-4 h-4 cursor-pointer" />
<ThumbsDown className="w-4 h-4 cursor-pointer" />
</div> </div>
<Button
onClick={() => handleSave(item.id)}
disabled={bookmarkedIds.has(Number(item.id))}
variant="default"
size="sm"
className={`rounded px-4 ${
bookmarkedIds.has(Number(item.id))
? "bg-gray-400 cursor-not-allowed text-white"
: "bg-red-700 text-white hover:bg-red-500"
}`}
>
{bookmarkedIds.has(Number(item.id))
? t("saved")
: t("save")}
</Button>
</div> </div>
</div> </div>
</SwiperSlide> </div>
))} </SwiperSlide>
</Swiper> ))}
)} </Swiper>
)}
{/* Lihat lebih banyak - hanya muncul jika ada data */} {/* Lihat lebih banyak - hanya muncul jika ada data */}
{filteredData.length > 0 && ( {filteredData.length > 0 && (
<div className="text-center mt-10"> <div className="text-center mt-10">
<Link href={getContentTypeLink()}> <Link href={getContentTypeLink()}>
<Button <Button
size={"lg"} 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" className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition"
> >
{t("seeMore")} {t("seeMore")}
</Button> </Button>
</Link> </Link>
</div> </div>
)} )}
</div> </div>
</section> </section>
</RevealL>
); );
} }

View File

@ -14,7 +14,6 @@ import { DynamicLogoTenant } from "./dynamic-logo-tenant";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import LocalSwitcher from "../partials/header/locale-switcher"; import LocalSwitcher from "../partials/header/locale-switcher";
import ThemeSwitcher from "../partials/header/theme-switcher"; import ThemeSwitcher from "../partials/header/theme-switcher";
import { RevealT } from "../ui/RevealT";
export default function Navbar() { export default function Navbar() {
const t = useTranslations("Navbar"); const t = useTranslations("Navbar");
@ -76,218 +75,217 @@ export default function Navbar() {
const fullname = Cookies.get("ufne"); const fullname = Cookies.get("ufne");
return ( return (
<RevealT> <header className="relative max-w-[1400px] mx-auto flex items-center justify-between px-4 py-3 border-b bg-white dark:bg-default-50 z-50">
<header className="relative max-w-[1400px] mx-auto flex items-center justify-between px-4 py-3 border-b bg-white dark:bg-default-50 z-50"> <div className="flex flex-row items-center justify-between space-x-4 z-10">
<div className="flex flex-row items-center justify-between space-x-4 z-10"> <Menu
<Menu className="w-6 h-6 cursor-pointer"
className="w-6 h-6 cursor-pointer" onClick={() => setIsSidebarOpen(true)}
onClick={() => setIsSidebarOpen(true)} />
<Link href="/" className="relative w-32 h-20">
<Image
src="/assets/logo1.png"
alt="Logo"
fill
className="object-contain"
/> />
</Link>
<Link href="/" className="relative w-32 h-20"> <DynamicLogoTenant />
<Image
src="/assets/logo1.png"
alt="Logo"
fill
className="object-contain"
/>
</Link>
<DynamicLogoTenant /> <div className="hidden custom-lg-button:flex items-end">
<ThemeSwitcher />
<div className="hidden custom-lg-button:flex items-end">
<ThemeSwitcher />
</div>
</div> </div>
</div>
{/* 🌐 NAV MENU */} {/* 🌐 NAV MENU */}
<nav className="absolute left-1/2 -translate-x-1/2 hidden md:flex space-x-3 lg:space-x-8 text-sm font-medium"> <nav className="absolute left-1/2 -translate-x-1/2 hidden md:flex space-x-3 lg:space-x-8 text-sm font-medium">
{filteredNavItems.map((item) => { {filteredNavItems.map((item) => {
const isActive = pathname === item.href; const isActive = pathname === item.href;
// 🔹 Pengecekan khusus untuk "Untuk Anda" // 🔹 Pengecekan khusus untuk "Untuk Anda"
const handleClick = (e: React.MouseEvent) => { const handleClick = (e: React.MouseEvent) => {
if (item.label === t("forYou")) { if (item.label === t("forYou")) {
e.preventDefault(); e.preventDefault();
if (!checkLoginStatus()) { if (!checkLoginStatus()) {
router.push("/auth"); router.push("/auth");
} else { } else {
router.push("/in/for-you"); router.push("/in/for-you");
}
} }
}; }
};
return ( return (
<div key={item.label} className="relative"> <div key={item.label} className="relative">
{item.label === t("publication") ? ( {item.label === t("publication") ? (
<> <>
<button <button
onClick={() => setDropdownOpen(!isDropdownOpen)} onClick={() => setDropdownOpen(!isDropdownOpen)}
className={cn(
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
isDropdownOpen ||
pathname.startsWith("/public/publication")
? "text-black"
: "",
)}
>
{item.label}
<span
className={cn(
"absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded transition-all",
isDropdownOpen ||
pathname.startsWith("/public/publication")
? "opacity-100"
: "opacity-0",
)}
/>
</button>
{isDropdownOpen && (
<div className="absolute top-full mt-2 w-48 bg-white border rounded shadow z-50">
{PUBLIKASI_SUBMENU.map((sub) => (
<Link
key={sub.label}
href={sub.href}
className="block px-4 py-2 text-sm hover:bg-gray-100 text-gray-700 dark:text-white"
>
{sub.label}
</Link>
))}
</div>
)}
</>
) : (
<Link
href={item.href}
onClick={handleClick}
className={cn( className={cn(
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors", "relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
isActive && "text-black", isDropdownOpen ||
pathname.startsWith("/public/publication")
? "text-black"
: "",
)} )}
> >
{item.label} {item.label}
{isActive && ( <span
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded" /> className={cn(
)} "absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded transition-all",
</Link> isDropdownOpen ||
)} pathname.startsWith("/public/publication")
</div> ? "opacity-100"
); : "opacity-0",
})} )}
</nav> />
{/* 🔹 PROFILE / LOGIN SECTION */}
<nav className="hidden md:flex items-center gap-3 z-10 relative">
{!isLoggedIn ? (
<>
<Link href="/auth/register">
<Button className="bg-transparent border text-black hover:bg-red-600 hover:text-white cursor-pointer">
{t("register")}{" "}
</Button>
</Link>
<Link href="/auth">
<Button className="bg-red-700 text-white cursor-pointer hover:bg-white hover:border hover:border-red-700 hover:text-red-700">
{t("login")}
</Button>
</Link>
</>
) : (
<div className="relative">
<button
onClick={() => setShowProfileMenu((prev) => !prev)}
className="flex items-center gap-2 border-2 py-1 px-3 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-700 cursor-pointer"
>
<div className="w-9 h-9 rounded-full overflow-hidden border">
<Image
src="/avatar-profile.png"
alt={username || "User avatar"}
width={36}
height={36}
className="object-cover"
/>
</div>
<span className="text-sm font-medium text-gray-800 dark:text-white">
{fullname}
</span>
<ChevronDown className="w-4 h-4 text-gray-600" />
</button>
{showProfileMenu && (
<div className="absolute right-0 mt-2 w-40 bg-white dark:bg-black border rounded shadow z-50 ">
<Link
href="/admin/dashboard"
className="flex flex-row items-center text-left px-4 py-2 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-700 dark:text-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M14 21a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1zM4 13a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1zm5-2V5H5v6zM4 21a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1zm1-2h4v-2H5zm10 0h4v-6h-4zM13 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1zm2 1v2h4V5z"
/>
</svg>
&nbsp;Dashboard
</Link>
<button
onClick={handleLogout}
className="w-full flex flex-row text-left px-4 py-2 hover:bg-gray-300 text-gray-700 dark:text-white cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="1.5"
>
<path
strokeLinejoin="round"
d="M13.477 21.245H8.34a4.92 4.92 0 0 1-5.136-4.623V7.378A4.92 4.92 0 0 1 8.34 2.755h5.136"
/>
<path strokeMiterlimit="10" d="M20.795 12H7.442" />
<path
strokeLinejoin="round"
d="m16.083 17.136l4.404-4.404a1.04 1.04 0 0 0 0-1.464l-4.404-4.404"
/>
</g>
</svg>
&nbsp;{t("logout")}
</button> </button>
</div>
{isDropdownOpen && (
<div className="absolute top-full mt-2 w-48 bg-white border rounded shadow z-50">
{PUBLIKASI_SUBMENU.map((sub) => (
<Link
key={sub.label}
href={sub.href}
className="block px-4 py-2 text-sm hover:bg-gray-100 text-gray-700 dark:text-white"
>
{sub.label}
</Link>
))}
</div>
)}
</>
) : (
<Link
href={item.href}
onClick={handleClick}
className={cn(
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
isActive && "text-black",
)}
>
{item.label}
{isActive && (
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded" />
)}
</Link>
)} )}
</div> </div>
)} );
</nav> })}
</nav>
{/* 📱 SIDEBAR MOBILE */} {/* 🔹 PROFILE / LOGIN SECTION */}
{isSidebarOpen && ( <nav className="hidden md:flex items-center gap-3 z-10 relative">
<div className="fixed inset-0 z-50 flex"> {!isLoggedIn ? (
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto"> <>
<button <Link href="/auth/register">
onClick={() => setIsSidebarOpen(false)} <Button className="bg-transparent border text-black hover:bg-red-600 hover:text-white cursor-pointer">
className="absolute top-4 right-4 text-gray-600" {t("register")}{" "}
> </Button>
</Link>
</button> <Link href="/auth">
<Button className="bg-red-700 text-white cursor-pointer hover:bg-white hover:border hover:border-red-700 hover:text-red-700">
{t("login")}
</Button>
</Link>
</>
) : (
<div className="relative">
<button
onClick={() => setShowProfileMenu((prev) => !prev)}
className="flex items-center gap-2 border-2 py-1 px-3 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-700 cursor-pointer"
>
<div className="w-9 h-9 rounded-full overflow-hidden border">
<Image
src="/avatar-profile.png"
alt={username || "User avatar"}
width={36}
height={36}
className="object-cover"
/>
</div>
<span className="text-sm font-medium text-gray-800 dark:text-white">
{fullname}
</span>
<ChevronDown className="w-4 h-4 text-gray-600" />
</button>
<div className="mt-10"> {showProfileMenu && (
<h3 className="text-[16px] font-bold text-gray-700 mb-2"> <div className="absolute right-0 mt-2 w-40 bg-white dark:bg-black border rounded shadow z-50 ">
{t("language")} <Link
</h3> href="/admin/dashboard"
{/* button language */} className="flex flex-row items-center text-left px-4 py-2 hover:bg-gray-300 dark:hover:bg-gray-700 text-gray-700 dark:text-white"
<div className={`relative text-left border w-fit rounded-lg`}> >
<LocalSwitcher /> <svg
</div> xmlns="http://www.w3.org/2000/svg"
{/* <div className="space-y-5 ml-3"> width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill="currentColor"
d="M14 21a1 1 0 0 1-1-1v-8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1zM4 13a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1zm5-2V5H5v6zM4 21a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1zm1-2h4v-2H5zm10 0h4v-6h-4zM13 4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1h-6a1 1 0 0 1-1-1zm2 1v2h4V5z"
/>
</svg>
&nbsp;Dashboard
</Link>
<button
onClick={handleLogout}
className="w-full flex flex-row text-left px-4 py-2 hover:bg-gray-300 text-gray-700 dark:text-white cursor-pointer"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="1.5"
>
<path
strokeLinejoin="round"
d="M13.477 21.245H8.34a4.92 4.92 0 0 1-5.136-4.623V7.378A4.92 4.92 0 0 1 8.34 2.755h5.136"
/>
<path strokeMiterlimit="10" d="M20.795 12H7.442" />
<path
strokeLinejoin="round"
d="m16.083 17.136l4.404-4.404a1.04 1.04 0 0 0 0-1.464l-4.404-4.404"
/>
</g>
</svg>
&nbsp;{t("logout")}
</button>
</div>
)}
</div>
)}
</nav>
{/* 📱 SIDEBAR MOBILE */}
{isSidebarOpen && (
<div className="fixed inset-0 z-50 flex">
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto">
<button
onClick={() => setIsSidebarOpen(false)}
className="absolute top-4 right-4 text-gray-600"
>
</button>
<div className="mt-10">
<h3 className="text-[16px] font-bold text-gray-700 mb-2">
{t("language")}
</h3>
{/* button language */}
<div className={`relative text-left border w-fit rounded-lg`}>
<LocalSwitcher />
</div>
{/* <div className="space-y-5 ml-3">
<button className="flex items-center gap-2 text-sm text-gray-800"> <button className="flex items-center gap-2 text-sm text-gray-800">
<Image src={"/Flag.svg"} width={24} height={24} alt="flag" /> <Image src={"/Flag.svg"} width={24} height={24} alt="flag" />
English English
@ -311,99 +309,86 @@ export default function Navbar() {
Bahasa Indonesia Bahasa Indonesia
</button> </button>
</div> */} </div> */}
</div>
<div>
<h3 className="text-[16px] font-bold text-gray-700 dark:text-white mb-2">
{t("features")}
</h3>
<div className="space-y-5 ml-3">
{NAV_ITEMS.map((item) => (
<button
key={item.label}
onClick={() => {
if (item.label === t("forYou")) {
if (!checkLoginStatus()) {
router.push("/auth");
} else {
router.push("/for-you");
}
} else {
router.push(item.href);
}
setIsSidebarOpen(false);
}}
className="block text-[15px] text-gray-800 dark:text-white text-left w-full"
>
{item.label}
</button>
))}
</div>
</div>
<div className="space-y-5 text-[16px] font-bold">
<Link
href="/about"
className="block text-black dark:text-white"
>
{t("about")}
</Link>
<Link
href="/advertising"
className="block text-black dark:text-white"
>
{t("advertising")}
</Link>
<Link
href="/contact"
className="block text-black dark:text-white"
>
{t("contact")}
</Link>
{!isLoggedIn ? (
<>
<Link
href="/auth"
className="block text-lg text-gray-800 dark:text-white"
>
{t("login")}
</Link>
<Link
href="/auth/register"
className="block text-lg text-gray-800 dark:text-white"
>
{t("register")}
</Link>
</>
) : (
<button
onClick={handleLogout}
className="block text-left w-full text-lg text-red-600 hover:underline"
>
{t("logout")}
</button>
)}
</div>
<Card className="rounded-none p-4">
<h2 className="text-[#C6A455] text-center text-lg font-semibold mb-2">
{t("subscribeTitle")}
</h2>
<Input type="email" placeholder={t("subscribePlaceholder")} />
<Button className="bg-[#C6A455] mt-2">
{t("subscribeButton")}
</Button>
</Card>
</div> </div>
<div <div>
className="flex-1 bg-black/50" <h3 className="text-[16px] font-bold text-gray-700 dark:text-white mb-2">
onClick={() => setIsSidebarOpen(false)} {t("features")}
/> </h3>
<div className="space-y-5 ml-3">
{NAV_ITEMS.map((item) => (
<button
key={item.label}
onClick={() => {
if (item.label === t("forYou")) {
if (!checkLoginStatus()) {
router.push("/auth");
} else {
router.push("/for-you");
}
} else {
router.push(item.href);
}
setIsSidebarOpen(false);
}}
className="block text-[15px] text-gray-800 dark:text-white text-left w-full"
>
{item.label}
</button>
))}
</div>
</div>
<div className="space-y-5 text-[16px] font-bold">
<Link href="/about" className="block text-black dark:text-white">
{t("about")}
</Link>
<Link href="/advertising" className="block text-black dark:text-white">
{t("advertising")}
</Link>
<Link href="/contact" className="block text-black dark:text-white">
{t("contact")}
</Link>
{!isLoggedIn ? (
<>
<Link href="/auth" className="block text-lg text-gray-800 dark:text-white">
{t("login")}
</Link>
<Link
href="/auth/register"
className="block text-lg text-gray-800 dark:text-white"
>
{t("register")}
</Link>
</>
) : (
<button
onClick={handleLogout}
className="block text-left w-full text-lg text-red-600 hover:underline"
>
{t("logout")}
</button>
)}
</div>
<Card className="rounded-none p-4">
<h2 className="text-[#C6A455] text-center text-lg font-semibold mb-2">
{t("subscribeTitle")}
</h2>
<Input type="email" placeholder={t("subscribePlaceholder")} />
<Button className="bg-[#C6A455] mt-2">
{t("subscribeButton")}
</Button>
</Card>
</div> </div>
)}
</header> <div
</RevealT> className="flex-1 bg-black/50"
onClick={() => setIsSidebarOpen(false)}
/>
</div>
)}
</header>
); );
} }

View File

@ -93,17 +93,17 @@ export default function ImageDetail({ id }: { id: number }) {
files: files:
article.files?.map((f: any) => ({ article.files?.map((f: any) => ({
id: f.id, id: f.id,
url: f.fileUrl, url: f.file_url,
fileName: f.fileName, fileName: f.file_name,
filePath: f.filePath, filePath: f.file_path,
fileThumbnail: f.fileThumbnail, fileThumbnail: f.file_thumbnail,
fileAlt: f.fileAlt, fileAlt: f.file_alt,
widthPixel: f.widthPixel, widthPixel: f.width_pixel,
heightPixel: f.heightPixel, heightPixel: f.height_pixel,
size: f.size, size: f.size,
downloadCount: f.downloadCount, downloadCount: f.download_count,
createdAt: f.createdAt, createdAt: f.created_at,
updatedAt: f.updatedAt, updatedAt: f.updated_at,
})) || [], })) || [],
}; };
@ -155,7 +155,7 @@ export default function ImageDetail({ id }: { id: number }) {
<div className="relative"> <div className="relative">
<Image <Image
placeholder={`data:image/svg+xml;base64,${toBase64( placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475), shimmer(700, 475)
)}`} )}`}
width={2560} width={2560}
height={1440} height={1440}
@ -163,8 +163,8 @@ export default function ImageDetail({ id }: { id: number }) {
data?.files?.[selectedImage]?.url?.trim() data?.files?.[selectedImage]?.url?.trim()
? data.files[selectedImage].url ? data.files[selectedImage].url
: data?.thumbnailUrl?.trim() : data?.thumbnailUrl?.trim()
? data.thumbnailUrl ? data.thumbnailUrl
: "/assets/empty-data.png" : "/assets/empty-data.png"
} }
alt="Main" alt="Main"
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain" className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
@ -185,7 +185,7 @@ export default function ImageDetail({ id }: { id: number }) {
<a onClick={() => setSelectedImage(index)} key={file?.id}> <a onClick={() => setSelectedImage(index)} key={file?.id}>
<Image <Image
placeholder={`data:image/svg+xml;base64,${toBase64( placeholder={`data:image/svg+xml;base64,${toBase64(
shimmer(700, 475), shimmer(700, 475)
)}`} )}`}
width={1920} width={1920}
height={1080} height={1080}
@ -223,7 +223,7 @@ export default function ImageDetail({ id }: { id: number }) {
</span> </span>
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black"> <span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
<Eye className="w-4 h-4" /> <Eye className="w-4 h-4" />
{data.clickCount || 0}{" "} {data.viewCount || 0}
</span> </span>
<span className="text-black"> <span className="text-black">
Creator: {data.creatorGroupLevelName} Creator: {data.creatorGroupLevelName}

View File

@ -1,58 +0,0 @@
import React, { useRef, useEffect } from "react";
import { motion, useInView, useAnimation } from "framer-motion";
interface Props {
children: React.ReactNode;
}
export const Reveal = ({ children }: Props) => {
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 (
<div ref={ref}>
<motion.div
variants={{
hidden: { opacity: 0, y: 75 },
visible: { opacity: 1, y: 0 },
}}
initial="hidden"
animate={mainControls}
transition={{
duration: 1,
delay: 0.1,
}}
>
{children}
</motion.div>
{/* TODO green slide thingy */}
{/* <motion.div
variants={{
hidden: { left: 0 },
visible: { left: "100%" },
}}
initial="hidden"
animate={slideControls}
transition={{ duration: 0.5, ease: "easeIn" }}
style={{
position: "absolute",
top: 4,
bottom: 4,
left: 0,
right: 0,
background: "#5e84ff",
zIndex: 20,
}}
/> */}
</div>
);
};

View File

@ -1,63 +0,0 @@
import React, { useRef, useEffect } from "react";
import { motion, useInView, useAnimation } from "framer-motion";
interface Props {
children: React.ReactNode;
}
export const RevealL = ({ children }: Props) => {
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 (
<div ref={ref} className="relative overflow-hidden">
<motion.div
variants={{
hidden: { opacity: 0, x: -75 }, // ← muncul dari kiri
visible: { opacity: 1, x: 0 },
}}
initial="hidden"
animate={mainControls}
transition={{
duration: 1,
delay: 0.1,
}}
>
{children}
</motion.div>
{/* Optional: Slide Overlay Animation */}
{/*
<motion.div
variants={{
hidden: { left: 0 },
visible: { left: "100%" },
}}
initial="hidden"
animate={slideControls}
transition={{ duration: 0.5, ease: "easeIn" }}
style={{
position: "absolute",
top: 4,
bottom: 4,
left: 0,
right: 0,
background: "#5e84ff",
zIndex: 20,
}}
/>
*/}
</div>
);
};

View File

@ -1,63 +0,0 @@
import React, { useRef, useEffect } from "react";
import { motion, useInView, useAnimation } from "framer-motion";
interface Props {
children: React.ReactNode;
}
export const RevealR = ({ children }: Props) => {
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 (
<div ref={ref} className="relative overflow-hidden">
<motion.div
variants={{
hidden: { opacity: 0, x: 75 },
visible: { opacity: 1, x: 0 },
}}
initial="hidden"
animate={mainControls}
transition={{
duration: 1,
delay: 0.1,
}}
>
{children}
</motion.div>
{/* Optional: Slide Overlay Animation */}
{/*
<motion.div
variants={{
hidden: { left: 0 },
visible: { left: "100%" },
}}
initial="hidden"
animate={slideControls}
transition={{ duration: 0.5, ease: "easeIn" }}
style={{
position: "absolute",
top: 4,
bottom: 4,
left: 0,
right: 0,
background: "#5e84ff",
zIndex: 20,
}}
/>
*/}
</div>
);
};

View File

@ -1,63 +0,0 @@
import React, { useRef, useEffect } from "react";
import { motion, useInView, useAnimation } from "framer-motion";
interface Props {
children: React.ReactNode;
}
export const RevealT = ({ children }: Props) => {
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 (
<div ref={ref} className="relative overflow-hidden">
<motion.div
variants={{
hidden: { opacity: 0, y: -75 }, // 👈 muncul dari atas
visible: { opacity: 1, y: 0 },
}}
initial="hidden"
animate={mainControls}
transition={{
duration: 1,
delay: 0.1,
}}
>
{children}
</motion.div>
{/* Optional: Slide Overlay Animation */}
{/*
<motion.div
variants={{
hidden: { top: 0 },
visible: { top: "100%" },
}}
initial="hidden"
animate={slideControls}
transition={{ duration: 0.5, ease: "easeIn" }}
style={{
position: "absolute",
left: 0,
right: 0,
top: 0,
bottom: 0,
background: "#5e84ff",
zIndex: 20,
}}
/>
*/}
</div>
);
};

View File

@ -238,13 +238,13 @@ export function getMenuList(pathname: string, t: any): Group[] {
icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
children: [], children: [],
}, },
// { {
// href: "/admin/settings/tag", href: "/admin/settings/tag",
// label: "Tag", label: "Tag",
// active: pathname === "/admin/settings/tag", active: pathname === "/admin/settings/tag",
// icon: "heroicons:arrow-trending-up", icon: "heroicons:arrow-trending-up",
// children: [], children: [],
// }, },
{ {
href: "/admin/settings/banner", href: "/admin/settings/banner",
label: "Banner", label: "Banner",

View File

@ -83,14 +83,19 @@ export async function getListCompetencies() {
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }
export async function updateUserInternal(id: number, data: any) {
const url = `users/${id}`;
return httpPutInterceptor(url, data);
}
export async function getListExperiences() { export async function getListExperiences() {
const url = "users/user-experiences/list"; const url = "users/user-experiences/list";
return httpGetInterceptor(url); return httpGetInterceptor(url);
} }
export async function updateUserInternal(id: number, data: any) { export async function saveUserInternal(data: any) {
const url = `users/${id}`; const url = "users/save";
return httpPutInterceptor(url, data); return httpPostInterceptor(url, data);
} }
export async function checkRolePlacementsAvailability(data: any) { export async function checkRolePlacementsAvailability(data: any) {