Compare commits
No commits in common. "main" and "dev-1" have entirely different histories.
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
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";
|
||||
|
|
|
|||
|
|
@ -50,8 +50,6 @@ 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);
|
||||
|
|
@ -138,9 +136,9 @@ export default function CategoriesDetailForm() {
|
|||
{/* Status */}
|
||||
<div>
|
||||
<Label>Status</Label>
|
||||
<p className="text-sm text-green-500">
|
||||
<p className="text-sm text-slate-600">
|
||||
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
|
||||
{detail.isActive ? "* Active" : "- Inactive"}
|
||||
{detail.isActive ? "Active" : "Inactive"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ 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");
|
||||
|
|
@ -22,7 +21,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);
|
||||
}
|
||||
|
|
@ -56,58 +55,56 @@ export default function Category() {
|
|||
categories.length > 0 ? categories : fallbackCategories;
|
||||
|
||||
return (
|
||||
<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>
|
||||
<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
|
||||
{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
|
||||
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>
|
||||
</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>
|
||||
{categoryTitle}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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 = `
|
||||
|
|
@ -101,180 +100,178 @@ export default function Footer() {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<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" : ""
|
||||
}`}
|
||||
>
|
||||
{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"
|
||||
>
|
||||
<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.src}
|
||||
alt={`logo-${idx}`}
|
||||
width={80}
|
||||
height={80}
|
||||
className="md:w-[100px] md:h-[100px] object-contain hover:opacity-80 transition"
|
||||
src="/logo-netidhub.png"
|
||||
alt={`${client.name} placeholder`}
|
||||
width={100}
|
||||
height={100}
|
||||
className="md:w-[100px] md:h-[100px] object-contain opacity-70"
|
||||
/>
|
||||
</a>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</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"
|
||||
>
|
||||
<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>
|
||||
{/* 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>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ 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"];
|
||||
|
||||
|
|
@ -44,7 +41,7 @@ export default function Header() {
|
|||
undefined,
|
||||
undefined,
|
||||
"createdAt",
|
||||
slug,
|
||||
slug
|
||||
);
|
||||
|
||||
let articlesData: any[] = [];
|
||||
|
|
@ -59,14 +56,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
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -109,14 +106,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) {
|
||||
|
|
@ -131,77 +128,75 @@ export default function Header() {
|
|||
if (bookmarkedIds.size > 0) {
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(bookmarkedIds)),
|
||||
JSON.stringify(Array.from(bookmarkedIds))
|
||||
);
|
||||
}
|
||||
}, [bookmarkedIds]);
|
||||
|
||||
return (
|
||||
<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 && (
|
||||
<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) => (
|
||||
<Card
|
||||
item={data[0]}
|
||||
isBig
|
||||
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
|
||||
key={item.id}
|
||||
item={item}
|
||||
isInitiallyBookmarked={bookmarkedIds.has(Number(item.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
|
||||
key={item.id}
|
||||
item={item}
|
||||
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
|
||||
onSaved={(id) =>
|
||||
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</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="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>
|
||||
</RevealR>
|
||||
<ImageBlurry
|
||||
priority
|
||||
src={img}
|
||||
alt="gambar"
|
||||
style={{
|
||||
objectFit: "contain",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -223,26 +218,12 @@ function Card({
|
|||
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
|
||||
const DEFAULT_IMAGE = "/assets/logo1.png";
|
||||
|
||||
const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE);
|
||||
const [imageSrc, setImageSrc] = useState(
|
||||
item?.smallThumbnailLink || DEFAULT_IMAGE
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
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);
|
||||
};
|
||||
setImageSrc(item?.smallThumbnailLink || DEFAULT_IMAGE);
|
||||
}, [item?.smallThumbnailLink]);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -285,7 +266,7 @@ function Card({
|
|||
newSet.add(Number(item.id));
|
||||
localStorage.setItem(
|
||||
"bookmarkedIds",
|
||||
JSON.stringify(Array.from(newSet)),
|
||||
JSON.stringify(Array.from(newSet))
|
||||
);
|
||||
|
||||
MySwal.fire({
|
||||
|
|
@ -311,7 +292,7 @@ function Card({
|
|||
};
|
||||
|
||||
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
|
||||
|
|
@ -331,10 +312,12 @@ function Card({
|
|||
fill
|
||||
className="object-cover"
|
||||
/> */}
|
||||
<ImageBlurry
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={item.title}
|
||||
className="w-full h-full object-contain"
|
||||
fill
|
||||
className="object-cover"
|
||||
onError={() => setImageSrc(DEFAULT_IMAGE)}
|
||||
/>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
@ -396,7 +379,7 @@ function Card({
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RevealL>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,6 @@ 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 "";
|
||||
|
|
@ -342,74 +340,61 @@ export default function MediaUpdate() {
|
|||
};
|
||||
|
||||
function SafeImage({ src, alt }: { src?: string; alt?: string }) {
|
||||
const [imgSrc, setImgSrc] = useState(DEFAULT_IMAGE);
|
||||
const [imgSrc, setImgSrc] = useState(src || DEFAULT_IMAGE);
|
||||
|
||||
useEffect(() => {
|
||||
if (!src) {
|
||||
setImgSrc(DEFAULT_IMAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
const img = new window.Image();
|
||||
img.src = src;
|
||||
|
||||
img.onload = () => {
|
||||
setImgSrc(src);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
setImgSrc(DEFAULT_IMAGE);
|
||||
};
|
||||
setImgSrc(src || DEFAULT_IMAGE);
|
||||
}, [src]);
|
||||
|
||||
return (
|
||||
<ImageBlurry
|
||||
<Image
|
||||
src={imgSrc}
|
||||
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 (
|
||||
<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>
|
||||
<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"
|
||||
|
|
@ -420,181 +405,180 @@ 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>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
{/* 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>
|
||||
)}
|
||||
|
||||
<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>
|
||||
{/* 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>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
)}
|
||||
</div>
|
||||
</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>
|
||||
</RevealL>
|
||||
{/* 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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ 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");
|
||||
|
|
@ -76,218 +75,217 @@ export default function Navbar() {
|
|||
const fullname = Cookies.get("ufne");
|
||||
|
||||
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">
|
||||
<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)}
|
||||
<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"
|
||||
/>
|
||||
</Link>
|
||||
|
||||
<Link href="/" className="relative w-32 h-20">
|
||||
<Image
|
||||
src="/assets/logo1.png"
|
||||
alt="Logo"
|
||||
fill
|
||||
className="object-contain"
|
||||
/>
|
||||
</Link>
|
||||
<DynamicLogoTenant />
|
||||
|
||||
<DynamicLogoTenant />
|
||||
|
||||
<div className="hidden custom-lg-button:flex items-end">
|
||||
<ThemeSwitcher />
|
||||
</div>
|
||||
<div className="hidden custom-lg-button:flex items-end">
|
||||
<ThemeSwitcher />
|
||||
</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)}
|
||||
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}
|
||||
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",
|
||||
isActive && "text-black",
|
||||
isDropdownOpen ||
|
||||
pathname.startsWith("/public/publication")
|
||||
? "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"
|
||||
/>
|
||||
</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>
|
||||
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>
|
||||
{t("logout")}
|
||||
<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>
|
||||
</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>
|
||||
)}
|
||||
</nav>
|
||||
);
|
||||
})}
|
||||
</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>
|
||||
{/* 🔹 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>
|
||||
|
||||
<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">
|
||||
{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>
|
||||
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>
|
||||
{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">
|
||||
<Image src={"/Flag.svg"} width={24} height={24} alt="flag" />
|
||||
English
|
||||
|
|
@ -311,99 +309,86 @@ 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>
|
||||
|
||||
{!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
|
||||
className="flex-1 bg-black/50"
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
/>
|
||||
<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>
|
||||
)}
|
||||
</header>
|
||||
</RevealT>
|
||||
|
||||
<div
|
||||
className="flex-1 bg-black/50"
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,17 +93,17 @@ export default function ImageDetail({ id }: { id: number }) {
|
|||
files:
|
||||
article.files?.map((f: any) => ({
|
||||
id: f.id,
|
||||
url: f.fileUrl,
|
||||
fileName: f.fileName,
|
||||
filePath: f.filePath,
|
||||
fileThumbnail: f.fileThumbnail,
|
||||
fileAlt: f.fileAlt,
|
||||
widthPixel: f.widthPixel,
|
||||
heightPixel: f.heightPixel,
|
||||
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,
|
||||
size: f.size,
|
||||
downloadCount: f.downloadCount,
|
||||
createdAt: f.createdAt,
|
||||
updatedAt: f.updatedAt,
|
||||
downloadCount: f.download_count,
|
||||
createdAt: f.created_at,
|
||||
updatedAt: f.updated_at,
|
||||
})) || [],
|
||||
};
|
||||
|
||||
|
|
@ -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.clickCount || 0}{" "}
|
||||
{data.viewCount || 0}
|
||||
</span>
|
||||
<span className="text-black">
|
||||
Creator: {data.creatorGroupLevelName}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -83,14 +83,19 @@ 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 updateUserInternal(id: number, data: any) {
|
||||
const url = `users/${id}`;
|
||||
return httpPutInterceptor(url, data);
|
||||
export async function saveUserInternal(data: any) {
|
||||
const url = "users/save";
|
||||
return httpPostInterceptor(url, data);
|
||||
}
|
||||
|
||||
export async function checkRolePlacementsAvailability(data: any) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue