Compare commits

..

17 Commits
dev-1 ... main

Author SHA1 Message Date
Sabda Yagra 24b44a491a fix: add reveal animation in landing
continuous-integration/drone/push Build is passing Details
2026-03-06 11:19:17 +07:00
Sabda Yagra a71cbeefeb Merge branch 'dev-1' of http://38.47.180.165:3000/kontenhumas/kontenhumas-fe
continuous-integration/drone/push Build is passing Details
2026-03-03 16:26:43 +07:00
Sabda Yagra a416c183b6 Merge branch 'dev-1' of http://38.47.180.165:3000/kontenhumas/kontenhumas-fe 2026-02-19 14:07:53 +07:00
Sabda Yagra 02bd3c8233 fix: drone image
continuous-integration/drone/push Build is passing Details
2026-02-19 13:45:11 +07:00
Sabda Yagra 05a1f41227 fix: drone image 2026-02-19 13:44:42 +07:00
Sabda Yagra a39bd17158 fix: drone
continuous-integration/drone/push Build is failing Details
2026-02-19 12:27:04 +07:00
Sabda Yagra c058e74cf2 fix: drone again
continuous-integration/drone/push Build was killed Details
2026-02-19 11:17:43 +07:00
Sabda Yagra 209a6bd37e feat: drone
continuous-integration/drone/push Build was killed Details
2026-02-19 11:08:45 +07:00
Sabda Yagra 5422af21c2 feat: drone 2026-02-19 11:02:39 +07:00
Sabda Yagra 5ef513f2ee Merge branch 'dev-1' of https://gitlab.com/hanifsalafi/new-netidhub-public 2026-02-18 10:24:44 +07:00
Sabda Yagra 11d47e4c29 Merge branch 'dev-1' of https://gitlab.com/hanifsalafi/new-netidhub-public 2026-02-13 19:44:21 +07:00
Sabda Yagra f191f0edc1 Merge branch 'dev-1' of https://gitlab.com/hanifsalafi/new-netidhub-public 2026-02-12 12:58:28 +07:00
Sabda Yagra a07fd232c6 fix: detail categories 2026-02-10 13:27:38 +07:00
Sabda Yagra caa258d0b1 Merge branch 'dev-1' of https://gitlab.com/hanifsalafi/new-netidhub-public 2026-02-09 14:50:04 +07:00
Sabda Yagra 8ead2c6352 fix: merge dev-1 2026-02-09 10:57:30 +07:00
Sabda Yagra 2dc31164cb fix: fixing page tag and banner 2026-02-09 09:20:39 +07:00
Sabda Yagra 05301cf086 fix: tab banner and tag 2026-02-09 09:09:21 +07:00
15 changed files with 1077 additions and 780 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import {
ArticleCategory,
} from "@/service/categories/article-categories";
import { useTranslations } from "next-intl";
import { RevealR } from "../ui/RevealR";
export default function Category() {
const t = useTranslations("MediaUpdate");
@ -21,7 +22,7 @@ export default function Category() {
// Filter hanya kategori yang aktif dan published
const activeCategories = response.data.data.filter(
(category: ArticleCategory) =>
category.isActive && category.isPublish
category.isActive && category.isPublish,
);
setCategories(activeCategories);
}
@ -55,56 +56,58 @@ export default function Category() {
categories.length > 0 ? categories : fallbackCategories;
return (
<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">
<h2 className="text-xl font-semibold mb-5">
{loading
? t("loadCategory")
: `${displayCategories.length} ${t("category")}`}
</h2>
<RevealR>
<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">
<h2 className="text-xl font-semibold mb-5">
{loading
? t("loadCategory")
: `${displayCategories.length} ${t("category")}`}
</h2>
{loading ? (
// Loading skeleton
<div className="flex flex-wrap gap-3">
{Array.from({ length: 10 }).map((_, index) => (
<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
{loading ? (
// Loading skeleton
<div className="flex flex-wrap gap-3">
{Array.from({ length: 10 }).map((_, index) => (
<div
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
}}
className="px-4 py-2 rounded border border-gray-200 bg-gray-100 animate-pulse"
>
{categoryTitle}
</button>
);
})}
</div>
)}
</div>
</section>
<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}
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,6 +13,7 @@ import "swiper/css";
import "swiper/css/navigation";
import LocalSwitcher from "../partials/header/locale-switcher";
import { useTranslations } from "next-intl";
import { Reveal } from "./Reveal";
// Custom styles for Swiper
const swiperStyles = `
@ -100,178 +101,180 @@ export default function Footer() {
}, []);
return (
<footer className="border-t bg-white dark:bg-default-50 text-center">
<style jsx>{swiperStyles}</style>
<div className="max-w-[1350px] mx-auto">
<div className="py-6">
<h2 className="text-2xl font-semibold mb-4 px-4 md:px-0">
{t("publication")}
</h2>
<div className="px-4 md:px-12">
<Swiper
modules={[Navigation, Autoplay]}
spaceBetween={24}
slidesPerView="auto"
centeredSlides={clients.length <= 4}
navigation={{
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
}}
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
loop={clients.length > 4}
className={`client-swiper ${
clients.length <= 4 ? "swiper-centered" : ""
}`}
>
{loading
? // Loading skeleton
Array.from({ length: 8 }).map((_, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] bg-gray-200 rounded animate-pulse" />
</SwiperSlide>
))
: clients.length > 0
? // Dynamic clients from API
clients.map((client, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<a
href={`/in/tenant/${client.slug}`}
target="_blank"
rel="noopener noreferrer"
className="group block"
>
{client.logoUrl ? (
<Image
src={client.logoUrl}
alt={client.name}
width={100}
height={100}
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition"
/>
) : (
// 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">
<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
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 */}
{/* <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>
</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"
<Reveal>
<footer className="border-t bg-white dark:bg-default-50 text-center">
<style jsx>{swiperStyles}</style>
<div className="max-w-[1350px] mx-auto">
<div className="py-6">
<h2 className="text-2xl font-semibold mb-4 px-4 md:px-0">
{t("publication")}
</h2>
<div className="px-4 md:px-12">
<Swiper
modules={[Navigation, Autoplay]}
spaceBetween={24}
slidesPerView="auto"
centeredSlides={clients.length <= 4}
navigation={{
nextEl: ".swiper-button-next",
prevEl: ".swiper-button-prev",
}}
autoplay={{
delay: 3000,
disableOnInteraction: false,
}}
loop={clients.length > 4}
className={`client-swiper ${
clients.length <= 4 ? "swiper-centered" : ""
}`}
>
<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>
{loading
? // Loading skeleton
Array.from({ length: 8 }).map((_, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] bg-gray-200 rounded animate-pulse" />
</SwiperSlide>
))
: clients.length > 0
? // Dynamic clients from API
clients.map((client, idx) => (
<SwiperSlide key={idx} className="!w-auto">
<a
href={`/in/tenant/${client.slug}`}
target="_blank"
rel="noopener noreferrer"
className="group block"
>
{client.logoUrl ? (
<Image
src={client.logoUrl}
alt={client.name}
width={100}
height={100}
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition"
/>
) : (
// 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">
<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
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 */}
{/* <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>
</div>
{/* button language */}
<div className={`relative text-left border rounded-lg`}>
<LocalSwitcher />
<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>
</footer>
</footer>
</Reveal>
);
}

View File

@ -18,6 +18,9 @@ import "swiper/css/navigation";
import "swiper/css/pagination";
import ImageBlurry from "../ui/image-blurry";
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"];
@ -41,7 +44,7 @@ export default function Header() {
undefined,
undefined,
"createdAt",
slug
slug,
);
let articlesData: any[] = [];
@ -56,14 +59,14 @@ export default function Header() {
"createdAt",
"",
"",
""
"",
);
articlesData = (fallbackResponse?.data?.data?.content || []).filter(
(item: any) => item.typeId === 1
(item: any) => item.typeId === 1,
);
} else {
articlesData = (response?.data?.data || []).filter(
(item: any) => item.typeId === 1
(item: any) => item.typeId === 1,
);
}
@ -106,14 +109,14 @@ export default function Header() {
const ids = new Set<number>(
(Array.isArray(bookmarks) ? bookmarks : [])
.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]);
setBookmarkedIds(merged);
localStorage.setItem(
"bookmarkedIds",
JSON.stringify(Array.from(merged))
JSON.stringify(Array.from(merged)),
);
}
} catch (error) {
@ -128,75 +131,77 @@ export default function Header() {
if (bookmarkedIds.size > 0) {
localStorage.setItem(
"bookmarkedIds",
JSON.stringify(Array.from(bookmarkedIds))
JSON.stringify(Array.from(bookmarkedIds)),
);
}
}, [bookmarkedIds]);
return (
<section className="max-w-[1350px] mx-auto px-4">
<div className="flex flex-col lg:flex-row gap-6 py-6">
{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) => (
<RevealR>
<section className="max-w-[1350px] mx-auto px-4">
<div className="flex flex-col lg:flex-row gap-6 py-6">
{data.length > 0 && (
<Card
key={item.id}
item={item}
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
item={data[0]}
isBig
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
onSaved={(id) =>
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
}
/>
))}
</div>
</div>
)}
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl overflow-hidden">
<Swiper
modules={[Navigation, Pagination]}
navigation
pagination={{ clickable: true }}
spaceBetween={10}
slidesPerView={1}
loop={true}
className="w-full h-full"
>
{images.map((img, index) => (
<SwiperSlide key={index}>
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px]">
{/* <Image
<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 className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px] mt-4 rounded-xl overflow-hidden">
<Swiper
modules={[Navigation, Pagination]}
navigation
pagination={{ clickable: true }}
spaceBetween={10}
slidesPerView={1}
loop={true}
className="w-full h-full"
>
{images.map((img, index) => (
<SwiperSlide key={index}>
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px]">
{/* <Image
src={img}
alt={`slide-${index}`}
fill
className="object-cover rounded-xl"
priority={index === 0}
/> */}
<ImageBlurry
priority
src={img}
alt="gambar"
style={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</section>
<ImageBlurry
priority
src={img}
alt="gambar"
style={{
objectFit: "contain",
width: "100%",
height: "100%",
}}
/>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</section>
</RevealR>
);
}
@ -218,12 +223,26 @@ function Card({
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
const DEFAULT_IMAGE = "/assets/logo1.png";
const [imageSrc, setImageSrc] = useState(
item?.smallThumbnailLink || DEFAULT_IMAGE
);
const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE);
useEffect(() => {
setImageSrc(item?.smallThumbnailLink || DEFAULT_IMAGE);
const src = item?.smallThumbnailLink;
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]);
useEffect(() => {
@ -266,7 +285,7 @@ function Card({
newSet.add(Number(item.id));
localStorage.setItem(
"bookmarkedIds",
JSON.stringify(Array.from(newSet))
JSON.stringify(Array.from(newSet)),
);
MySwal.fire({
@ -292,7 +311,7 @@ function Card({
};
return (
<div>
<RevealL>
<div
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white dark:bg-black dark:border dark:border-slate-50 ${
isBig
@ -312,12 +331,10 @@ function Card({
fill
className="object-cover"
/> */}
<Image
<ImageBlurry
src={imageSrc}
alt={item.title}
fill
className="object-cover"
onError={() => setImageSrc(DEFAULT_IMAGE)}
className="w-full h-full object-contain"
/>
</Link>
</div>
@ -379,7 +396,7 @@ function Card({
</div>
</div>
</div>
</div>
</RevealL>
);
}

View File

@ -17,6 +17,8 @@ import Swal from "sweetalert2";
import withReactContent from "sweetalert2-react-content";
import { ThumbsUp, ThumbsDown } from "lucide-react";
import { useTranslations } from "next-intl";
import ImageBlurry from "../ui/image-blurry";
import { RevealL } from "../ui/RevealL";
function formatTanggal(dateString: string) {
if (!dateString) return "";
@ -340,61 +342,74 @@ export default function MediaUpdate() {
};
function SafeImage({ src, alt }: { src?: string; alt?: string }) {
const [imgSrc, setImgSrc] = useState(src || DEFAULT_IMAGE);
const [imgSrc, setImgSrc] = useState(DEFAULT_IMAGE);
useEffect(() => {
setImgSrc(src || DEFAULT_IMAGE);
if (!src) {
setImgSrc(DEFAULT_IMAGE);
return;
}
const img = new window.Image();
img.src = src;
img.onload = () => {
setImgSrc(src);
};
img.onerror = () => {
setImgSrc(DEFAULT_IMAGE);
};
}, [src]);
return (
<Image
<ImageBlurry
src={imgSrc}
alt={alt || "Image"}
fill
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
onError={() => setImgSrc(DEFAULT_IMAGE)}
className="w-full h-full object-contain"
/>
);
}
return (
<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">
<h2 className="text-2xl font-semibold text-center mb-6">
{t("title")}
</h2>
<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">
<div className="max-w-screen-xl mx-auto">
<h2 className="text-2xl font-semibold text-center mb-6">
{t("title")}
</h2>
{/* Main Tab */}
<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">
<button
onClick={() => setTab("latest")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "latest"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
}`}
>
{t("latest")}
</button>
<button
onClick={() => setTab("popular")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "popular"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
}`}
>
{t("popular")}{" "}
</button>
</Card>
</div>
{/* Main Tab */}
<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">
<button
onClick={() => setTab("latest")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "latest"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
}`}
>
{t("latest")}
</button>
<button
onClick={() => setTab("popular")}
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
tab === "popular"
? "bg-[#C6A455] text-white shadow-sm"
: "text-[#C6A455] hover:bg-[#C6A455]/10"
}`}
>
{t("popular")}{" "}
</button>
</Card>
</div>
{/* Content Type Filter */}
<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="flex flex-wrap justify-center gap-2">
{/* <button
{/* Content Type Filter */}
<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="flex flex-wrap justify-center gap-2">
{/* <button
onClick={() => setContentType("all")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "all"
@ -405,180 +420,181 @@ export default function MediaUpdate() {
📋 Semua
</button> */}
<button
onClick={() => setContentType("foto")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "foto"
? "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"
}`}
>
📸 {t("image")}
</button>
<button
onClick={() => setContentType("audiovisual")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audiovisual"
? "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"
}`}
>
🎬 {t("video")}
</button>
<button
onClick={() => setContentType("audio")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audio"
? "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"
}`}
>
🎵 Audio
</button>
<button
onClick={() => setContentType("text")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "text"
? "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"
}`}
>
📝 {t("text")}
</button>
<button
onClick={() => setContentType("foto")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "foto"
? "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"
}`}
>
📸 {t("image")}
</button>
<button
onClick={() => setContentType("audiovisual")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audiovisual"
? "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"
}`}
>
🎬 {t("video")}
</button>
<button
onClick={() => setContentType("audio")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "audio"
? "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"
}`}
>
🎵 Audio
</button>
<button
onClick={() => setContentType("text")}
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
contentType === "text"
? "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"
}`}
>
📝 {t("text")}
</button>
</div>
</div>
</div>
</div>
{/* Slider */}
{loading ? (
<p className="text-center">{t("loadContent")}</p>
) : (
<Swiper
modules={[Navigation]}
navigation
spaceBetween={20}
slidesPerView={1}
breakpoints={{
640: { slidesPerView: 2 },
1024: { slidesPerView: 4 },
}}
>
{filteredData.map((item) => (
<SwiperSlide key={item.id}>
<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 */}
{item.typeId === 3 ? (
// 📝 TEXT
<div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
width="90"
height="90"
viewBox="0 0 16 16"
>
<path
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"
/>
</svg>
</div>
) : item.typeId === 4 ? (
// 🎵 AUDIO
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
viewBox="0 0 20 20"
>
<path
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"
/>
</svg>
</div>
) : (
// 🎬 FOTO / VIDEO (default)
<div className="w-full h-[204px] relative">
<Link href={getLink(item)}>
<SafeImage
src={item.smallThumbnailLink}
alt={item.title}
/>
{/* Slider */}
{loading ? (
<p className="text-center">{t("loadContent")}</p>
) : (
<Swiper
modules={[Navigation]}
navigation
spaceBetween={20}
slidesPerView={1}
breakpoints={{
640: { slidesPerView: 2 },
1024: { slidesPerView: 4 },
}}
>
{filteredData.map((item) => (
<SwiperSlide key={item.id}>
<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 */}
{item.typeId === 3 ? (
// 📝 TEXT
<div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
width="90"
height="90"
viewBox="0 0 16 16"
>
<path
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"
/>
</svg>
</div>
) : item.typeId === 4 ? (
// 🎵 AUDIO
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white">
<svg
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
viewBox="0 0 20 20"
>
<path
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"
/>
</svg>
</div>
) : (
// 🎬 FOTO / VIDEO (default)
<div className="w-full h-[204px] relative">
<Link href={getLink(item)}>
<SafeImage
src={item.smallThumbnailLink}
alt={item.title}
/>
{/* <Image
{/* <Image
src={item.smallThumbnailLink || "/placeholder.png"}
alt={item.title || "No Title"}
fill
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>
<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" />
</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>
<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>
<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>
<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>
</SwiperSlide>
))}
</Swiper>
)}
</SwiperSlide>
))}
</Swiper>
)}
{/* Lihat lebih banyak - hanya muncul jika ada data */}
{filteredData.length > 0 && (
<div className="text-center mt-10">
<Link href={getContentTypeLink()}>
<Button
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"
>
{t("seeMore")}
</Button>
</Link>
</div>
)}
</div>
</section>
{/* Lihat lebih banyak - hanya muncul jika ada data */}
{filteredData.length > 0 && (
<div className="text-center mt-10">
<Link href={getContentTypeLink()}>
<Button
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"
>
{t("seeMore")}
</Button>
</Link>
</div>
)}
</div>
</section>
</RevealL>
);
}

View File

@ -14,6 +14,7 @@ import { DynamicLogoTenant } from "./dynamic-logo-tenant";
import { useTranslations } from "next-intl";
import LocalSwitcher from "../partials/header/locale-switcher";
import ThemeSwitcher from "../partials/header/theme-switcher";
import { RevealT } from "../ui/RevealT";
export default function Navbar() {
const t = useTranslations("Navbar");
@ -75,217 +76,218 @@ export default function Navbar() {
const fullname = Cookies.get("ufne");
return (
<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">
<Menu
className="w-6 h-6 cursor-pointer"
onClick={() => setIsSidebarOpen(true)}
/>
<Link href="/" className="relative w-32 h-20">
<Image
src="/assets/logo1.png"
alt="Logo"
fill
className="object-contain"
<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">
<div className="flex flex-row items-center justify-between space-x-4 z-10">
<Menu
className="w-6 h-6 cursor-pointer"
onClick={() => setIsSidebarOpen(true)}
/>
</Link>
<DynamicLogoTenant />
<Link href="/" className="relative w-32 h-20">
<Image
src="/assets/logo1.png"
alt="Logo"
fill
className="object-contain"
/>
</Link>
<div className="hidden custom-lg-button:flex items-end">
<ThemeSwitcher />
<DynamicLogoTenant />
<div className="hidden custom-lg-button:flex items-end">
<ThemeSwitcher />
</div>
</div>
</div>
{/* 🌐 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">
{filteredNavItems.map((item) => {
const isActive = pathname === item.href;
{/* 🌐 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">
{filteredNavItems.map((item) => {
const isActive = pathname === item.href;
// 🔹 Pengecekan khusus untuk "Untuk Anda"
const handleClick = (e: React.MouseEvent) => {
if (item.label === t("forYou")) {
e.preventDefault();
if (!checkLoginStatus()) {
router.push("/auth");
} else {
router.push("/in/for-you");
// 🔹 Pengecekan khusus untuk "Untuk Anda"
const handleClick = (e: React.MouseEvent) => {
if (item.label === t("forYou")) {
e.preventDefault();
if (!checkLoginStatus()) {
router.push("/auth");
} else {
router.push("/in/for-you");
}
}
}
};
};
return (
<div key={item.label} className="relative">
{item.label === t("publication") ? (
<>
<button
onClick={() => setDropdownOpen(!isDropdownOpen)}
return (
<div key={item.label} className="relative">
{item.label === t("publication") ? (
<>
<button
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(
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
isDropdownOpen ||
pathname.startsWith("/public/publication")
? "text-black"
: "",
isActive && "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(
"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>
);
})}
</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"
/>
{isActive && (
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded" />
)}
</Link>
)}
</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>
);
})}
</nav>
{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>
{/* 🔹 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>
<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"
{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"
>
<g
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeWidth="1.5"
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
>
<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"
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"
/>
<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>
</svg>
&nbsp;Dashboard
</Link>
{/* 📱 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>
<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>
<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">
{/* 📱 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">
<Image src={"/Flag.svg"} width={24} height={24} alt="flag" />
English
@ -309,86 +311,99 @@ export default function Navbar() {
Bahasa Indonesia
</button>
</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>
<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>
{!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"
<div className="space-y-5 text-[16px] font-bold">
<Link
href="/about"
className="block text-black dark:text-white"
>
{t("logout")}
</button>
)}
{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>
<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
className="flex-1 bg-black/50"
onClick={() => setIsSidebarOpen(false)}
/>
</div>
<div
className="flex-1 bg-black/50"
onClick={() => setIsSidebarOpen(false)}
/>
</div>
)}
</header>
)}
</header>
</RevealT>
);
}

View File

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

58
components/ui/Reveal.tsx Normal file
View File

@ -0,0 +1,58 @@
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>
);
};

63
components/ui/RevealL.tsx Normal file
View File

@ -0,0 +1,63 @@
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>
);
};

63
components/ui/RevealR.tsx Normal file
View File

@ -0,0 +1,63 @@
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>
);
};

63
components/ui/RevealT.tsx Normal file
View File

@ -0,0 +1,63 @@
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

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