Compare commits
17 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
24b44a491a | |
|
|
a71cbeefeb | |
|
|
a416c183b6 | |
|
|
02bd3c8233 | |
|
|
05a1f41227 | |
|
|
a39bd17158 | |
|
|
c058e74cf2 | |
|
|
209a6bd37e | |
|
|
5422af21c2 | |
|
|
5ef513f2ee | |
|
|
11d47e4c29 | |
|
|
f191f0edc1 | |
|
|
a07fd232c6 | |
|
|
caa258d0b1 | |
|
|
8ead2c6352 | |
|
|
2dc31164cb | |
|
|
05301cf086 |
|
|
@ -29,11 +29,11 @@ import {
|
||||||
checkRolePlacementsAvailability,
|
checkRolePlacementsAvailability,
|
||||||
getListCompetencies,
|
getListCompetencies,
|
||||||
getListExperiences,
|
getListExperiences,
|
||||||
saveUserInternal,
|
|
||||||
saveUserRolePlacements,
|
saveUserRolePlacements,
|
||||||
} from "@/service/management-user/management-user";
|
} from "@/service/management-user/management-user";
|
||||||
import { error, loading } from "@/config/swal";
|
import { error, loading } from "@/config/swal";
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
|
import { saveUserInternal } from "@/service/service/management-user/management-user";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
name: z.string({
|
name: z.string({
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ import {
|
||||||
getListCompetencies,
|
getListCompetencies,
|
||||||
getListExperiences,
|
getListExperiences,
|
||||||
getUserById,
|
getUserById,
|
||||||
saveUserInternal,
|
|
||||||
saveUserRolePlacements,
|
saveUserRolePlacements,
|
||||||
} from "@/service/management-user/management-user";
|
} from "@/service/management-user/management-user";
|
||||||
import { loading } from "@/config/swal";
|
import { loading } from "@/config/swal";
|
||||||
import { Eye, EyeOff } from "lucide-react";
|
import { Eye, EyeOff } from "lucide-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
|
import { saveUserInternal } from "@/service/service/management-user/management-user";
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
name: z.string({
|
name: z.string({
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
|
|
||||||
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
import { Eye, MoreVertical, SquarePen, Trash2 } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Link, useRouter } from "@/i18n/routing";
|
import { Link, useRouter } from "@/i18n/routing";
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,8 @@ export default function CategoriesDetailForm() {
|
||||||
if (id) {
|
if (id) {
|
||||||
try {
|
try {
|
||||||
const res = await getArticleCategoryDetail(Number(id));
|
const res = await getArticleCategoryDetail(Number(id));
|
||||||
|
const user = await getUserInfo();
|
||||||
|
|
||||||
setDetail(res?.data?.data);
|
setDetail(res?.data?.data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error fetching category detail:", err);
|
console.error("Error fetching category detail:", err);
|
||||||
|
|
@ -136,9 +138,9 @@ export default function CategoriesDetailForm() {
|
||||||
{/* Status */}
|
{/* Status */}
|
||||||
<div>
|
<div>
|
||||||
<Label>Status</Label>
|
<Label>Status</Label>
|
||||||
<p className="text-sm text-slate-600">
|
<p className="text-sm text-green-500">
|
||||||
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
|
{/* {detail.isPublish ? "Published" : "Draft"} |{" "} */}
|
||||||
{detail.isActive ? "Active" : "Inactive"}
|
{detail.isActive ? "* Active" : "- Inactive"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import {
|
||||||
ArticleCategory,
|
ArticleCategory,
|
||||||
} from "@/service/categories/article-categories";
|
} from "@/service/categories/article-categories";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { RevealR } from "../ui/RevealR";
|
||||||
|
|
||||||
export default function Category() {
|
export default function Category() {
|
||||||
const t = useTranslations("MediaUpdate");
|
const t = useTranslations("MediaUpdate");
|
||||||
|
|
@ -21,7 +22,7 @@ export default function Category() {
|
||||||
// Filter hanya kategori yang aktif dan published
|
// Filter hanya kategori yang aktif dan published
|
||||||
const activeCategories = response.data.data.filter(
|
const activeCategories = response.data.data.filter(
|
||||||
(category: ArticleCategory) =>
|
(category: ArticleCategory) =>
|
||||||
category.isActive && category.isPublish
|
category.isActive && category.isPublish,
|
||||||
);
|
);
|
||||||
setCategories(activeCategories);
|
setCategories(activeCategories);
|
||||||
}
|
}
|
||||||
|
|
@ -55,56 +56,58 @@ export default function Category() {
|
||||||
categories.length > 0 ? categories : fallbackCategories;
|
categories.length > 0 ? categories : fallbackCategories;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="px-4 py-10">
|
<RevealR>
|
||||||
<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">
|
<section className="px-4 py-10">
|
||||||
<h2 className="text-xl font-semibold mb-5">
|
<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">
|
||||||
{loading
|
<h2 className="text-xl font-semibold mb-5">
|
||||||
? t("loadCategory")
|
{loading
|
||||||
: `${displayCategories.length} ${t("category")}`}
|
? t("loadCategory")
|
||||||
</h2>
|
: `${displayCategories.length} ${t("category")}`}
|
||||||
|
</h2>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
// Loading skeleton
|
// Loading skeleton
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
{Array.from({ length: 10 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
|
||||||
className="px-4 py-2 rounded border border-gray-200 bg-gray-100 animate-pulse"
|
|
||||||
>
|
|
||||||
<div className="h-4 w-20 bg-gray-300 rounded"></div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-wrap gap-3">
|
|
||||||
{displayCategories.map((category, index) => {
|
|
||||||
// Handle both API data and fallback data
|
|
||||||
const categoryTitle =
|
|
||||||
typeof category === "string" ? category : category.title;
|
|
||||||
const categorySlug =
|
|
||||||
typeof category === "string"
|
|
||||||
? category.toLowerCase().replace(/\s+/g, "-")
|
|
||||||
: category.slug;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={index}
|
key={index}
|
||||||
className="px-4 py-2 rounded border border-gray-300 text-gray-700 dark:text-white text-sm font-medium hover:bg-gray-100 hover:border-gray-400 transition-all duration-200"
|
className="px-4 py-2 rounded border border-gray-200 bg-gray-100 animate-pulse"
|
||||||
onClick={() => {
|
|
||||||
// Navigate to category page or search by category
|
|
||||||
console.log(
|
|
||||||
`Category clicked: ${categoryTitle} (${categorySlug})`
|
|
||||||
);
|
|
||||||
// TODO: Implement navigation to category page
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{categoryTitle}
|
<div className="h-4 w-20 bg-gray-300 rounded"></div>
|
||||||
</button>
|
</div>
|
||||||
);
|
))}
|
||||||
})}
|
</div>
|
||||||
</div>
|
) : (
|
||||||
)}
|
<div className="flex flex-wrap gap-3">
|
||||||
</div>
|
{displayCategories.map((category, index) => {
|
||||||
</section>
|
// 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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import "swiper/css";
|
||||||
import "swiper/css/navigation";
|
import "swiper/css/navigation";
|
||||||
import LocalSwitcher from "../partials/header/locale-switcher";
|
import LocalSwitcher from "../partials/header/locale-switcher";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Reveal } from "./Reveal";
|
||||||
|
|
||||||
// Custom styles for Swiper
|
// Custom styles for Swiper
|
||||||
const swiperStyles = `
|
const swiperStyles = `
|
||||||
|
|
@ -100,178 +101,180 @@ export default function Footer() {
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="border-t bg-white dark:bg-default-50 text-center">
|
<Reveal>
|
||||||
<style jsx>{swiperStyles}</style>
|
<footer className="border-t bg-white dark:bg-default-50 text-center">
|
||||||
<div className="max-w-[1350px] mx-auto">
|
<style jsx>{swiperStyles}</style>
|
||||||
<div className="py-6">
|
<div className="max-w-[1350px] mx-auto">
|
||||||
<h2 className="text-2xl font-semibold mb-4 px-4 md:px-0">
|
<div className="py-6">
|
||||||
{t("publication")}
|
<h2 className="text-2xl font-semibold mb-4 px-4 md:px-0">
|
||||||
</h2>
|
{t("publication")}
|
||||||
<div className="px-4 md:px-12">
|
</h2>
|
||||||
<Swiper
|
<div className="px-4 md:px-12">
|
||||||
modules={[Navigation, Autoplay]}
|
<Swiper
|
||||||
spaceBetween={24}
|
modules={[Navigation, Autoplay]}
|
||||||
slidesPerView="auto"
|
spaceBetween={24}
|
||||||
centeredSlides={clients.length <= 4}
|
slidesPerView="auto"
|
||||||
navigation={{
|
centeredSlides={clients.length <= 4}
|
||||||
nextEl: ".swiper-button-next",
|
navigation={{
|
||||||
prevEl: ".swiper-button-prev",
|
nextEl: ".swiper-button-next",
|
||||||
}}
|
prevEl: ".swiper-button-prev",
|
||||||
autoplay={{
|
}}
|
||||||
delay: 3000,
|
autoplay={{
|
||||||
disableOnInteraction: false,
|
delay: 3000,
|
||||||
}}
|
disableOnInteraction: false,
|
||||||
loop={clients.length > 4}
|
}}
|
||||||
className={`client-swiper ${
|
loop={clients.length > 4}
|
||||||
clients.length <= 4 ? "swiper-centered" : ""
|
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"
|
|
||||||
>
|
>
|
||||||
<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" />
|
{loading
|
||||||
<path
|
? // Loading skeleton
|
||||||
fill="currentColor"
|
Array.from({ length: 8 }).map((_, idx) => (
|
||||||
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"
|
<SwiperSlide key={idx} className="!w-auto">
|
||||||
/>
|
<div className="w-[80px] h-[80px] md:w-[100px] md:h-[100px] bg-gray-200 rounded animate-pulse" />
|
||||||
</g>
|
</SwiperSlide>
|
||||||
</svg>
|
))
|
||||||
|
: 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>
|
||||||
|
|
||||||
{/* button language */}
|
<div className="border-t my-6 w-full max-w-6xl mx-auto" />
|
||||||
<div className={`relative text-left border rounded-lg`}>
|
|
||||||
<LocalSwitcher />
|
<div className="flex flex-col md:flex-row items-center justify-between gap-4 px-4 pb-6 max-w-6xl mx-auto text-sm text-gray-600 dark:text-white">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span>ver 1.0.0 @2025 - {t("netidhub")}</span>
|
||||||
|
<Image
|
||||||
|
src="/qudo.png"
|
||||||
|
alt="qudoco"
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
className="object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Social Media */}
|
||||||
|
<div className="flex gap-3 text-gray-800 dark:text-white">
|
||||||
|
<Instagram className="hover:text-black" />
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M14 13.5h2.5l1-4H14v-2c0-1.03 0-2 2-2h1.5V2.14c-.326-.043-1.557-.14-2.857-.14C11.928 2 10 3.657 10 6.7v2.8H7v4h3V22h4z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g fill="none">
|
||||||
|
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4.594 4.984a1 1 0 0 1 .941.429C7.011 7.572 8.783 8.47 10.75 8.674c.096-.841.323-1.672.75-2.404c.626-1.074 1.644-1.864 3.098-2.156c2.01-.404 3.54.324 4.427 1.215l1.792-.335a1 1 0 0 1 1.053 1.478l-1.72 3.022c.157 4.361-1.055 7.405-3.639 9.502c-1.37 1.112-3.332 1.743-5.485 1.938c-2.17.196-4.623-.041-7.061-.753a1 1 0 0 1 .007-1.922c1.226-.349 2.16-.65 3.003-1.177c-1.199-.636-2.082-1.468-2.707-2.416c-.868-1.318-1.19-2.788-1.254-4.113S3.141 8 3.343 7.115c.115-.505.249-1.011.434-1.495a1 1 0 0 1 .818-.636Z"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
// fill-rule="evenodd"
|
||||||
|
d="M9.935 14.628v-5.62l5.403 2.82zM21.8 8.035s-.195-1.379-.795-1.986c-.76-.796-1.613-.8-2.004-.847C16.203 5 12.004 5 12.004 5h-.008s-4.198 0-6.997.202c-.391.047-1.243.05-2.004.847c-.6.607-.795 1.986-.795 1.986S2 9.653 2 11.272v1.517c0 1.618.2 3.237.2 3.237s.195 1.378.795 1.985c.76.797 1.76.771 2.205.855c1.6.153 6.8.2 6.8.2s4.203-.006 7.001-.208c.391-.047 1.244-.05 2.004-.847c.6-.607.795-1.985.795-1.985s.2-1.619.2-3.237v-1.517c0-1.619-.2-3.237-.2-3.237"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<g
|
||||||
|
fill="none"
|
||||||
|
// fill-rule="evenodd"
|
||||||
|
>
|
||||||
|
<path d="m12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035q-.016-.005-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427q-.004-.016-.017-.018m.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093q.019.005.029-.008l.004-.014l-.034-.614q-.005-.018-.02-.022m-.715.002a.02.02 0 0 0-.027.006l-.006.014l-.034.614q.001.018.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01z" />
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M14 2a2 2 0 0 1 2 2a3.004 3.004 0 0 0 2.398 2.94a2 2 0 0 1-.796 3.92A7 7 0 0 1 16 10.325V16a6 6 0 1 1-7.499-5.81a2 2 0 0 1 .998 3.873A2.002 2.002 0 0 0 10 18a2 2 0 0 0 2-2V4a2 2 0 0 1 2-2"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* button language */}
|
||||||
|
<div className={`relative text-left border rounded-lg`}>
|
||||||
|
<LocalSwitcher />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</footer>
|
||||||
</footer>
|
</Reveal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,9 @@ import "swiper/css/navigation";
|
||||||
import "swiper/css/pagination";
|
import "swiper/css/pagination";
|
||||||
import ImageBlurry from "../ui/image-blurry";
|
import ImageBlurry from "../ui/image-blurry";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { Reveal } from "../ui/Reveal";
|
||||||
|
import { RevealL } from "../ui/RevealL";
|
||||||
|
import { RevealR } from "../ui/RevealR";
|
||||||
|
|
||||||
const images = ["/PPS.png", "/PPS2.jpeg", "/PPS3.jpg", "/PPS4.png"];
|
const images = ["/PPS.png", "/PPS2.jpeg", "/PPS3.jpg", "/PPS4.png"];
|
||||||
|
|
||||||
|
|
@ -41,7 +44,7 @@ export default function Header() {
|
||||||
undefined,
|
undefined,
|
||||||
undefined,
|
undefined,
|
||||||
"createdAt",
|
"createdAt",
|
||||||
slug
|
slug,
|
||||||
);
|
);
|
||||||
|
|
||||||
let articlesData: any[] = [];
|
let articlesData: any[] = [];
|
||||||
|
|
@ -56,14 +59,14 @@ export default function Header() {
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
""
|
"",
|
||||||
);
|
);
|
||||||
articlesData = (fallbackResponse?.data?.data?.content || []).filter(
|
articlesData = (fallbackResponse?.data?.data?.content || []).filter(
|
||||||
(item: any) => item.typeId === 1
|
(item: any) => item.typeId === 1,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
articlesData = (response?.data?.data || []).filter(
|
articlesData = (response?.data?.data || []).filter(
|
||||||
(item: any) => item.typeId === 1
|
(item: any) => item.typeId === 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,14 +109,14 @@ export default function Header() {
|
||||||
const ids = new Set<number>(
|
const ids = new Set<number>(
|
||||||
(Array.isArray(bookmarks) ? bookmarks : [])
|
(Array.isArray(bookmarks) ? bookmarks : [])
|
||||||
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
|
.map((b: any) => Number(b.articleId ?? b.id ?? b.article?.id))
|
||||||
.filter((x) => !isNaN(x))
|
.filter((x) => !isNaN(x)),
|
||||||
);
|
);
|
||||||
|
|
||||||
const merged = new Set([...localSet, ...ids]);
|
const merged = new Set([...localSet, ...ids]);
|
||||||
setBookmarkedIds(merged);
|
setBookmarkedIds(merged);
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"bookmarkedIds",
|
"bookmarkedIds",
|
||||||
JSON.stringify(Array.from(merged))
|
JSON.stringify(Array.from(merged)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -128,75 +131,77 @@ export default function Header() {
|
||||||
if (bookmarkedIds.size > 0) {
|
if (bookmarkedIds.size > 0) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"bookmarkedIds",
|
"bookmarkedIds",
|
||||||
JSON.stringify(Array.from(bookmarkedIds))
|
JSON.stringify(Array.from(bookmarkedIds)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [bookmarkedIds]);
|
}, [bookmarkedIds]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="max-w-[1350px] mx-auto px-4">
|
<RevealR>
|
||||||
<div className="flex flex-col lg:flex-row gap-6 py-6">
|
<section className="max-w-[1350px] mx-auto px-4">
|
||||||
{data.length > 0 && (
|
<div className="flex flex-col lg:flex-row gap-6 py-6">
|
||||||
<Card
|
{data.length > 0 && (
|
||||||
item={data[0]}
|
|
||||||
isBig
|
|
||||||
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
|
|
||||||
onSaved={(id) =>
|
|
||||||
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
|
|
||||||
{data.slice(1, 5).map((item) => (
|
|
||||||
<Card
|
<Card
|
||||||
key={item.id}
|
item={data[0]}
|
||||||
item={item}
|
isBig
|
||||||
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
|
isInitiallyBookmarked={bookmarkedIds.has(Number(data[0].id))}
|
||||||
onSaved={(id) =>
|
onSaved={(id) =>
|
||||||
setBookmarkedIds((prev) => new Set([...prev, Number(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">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 w-full">
|
||||||
<Swiper
|
{data.slice(1, 5).map((item) => (
|
||||||
modules={[Navigation, Pagination]}
|
<Card
|
||||||
navigation
|
key={item.id}
|
||||||
pagination={{ clickable: true }}
|
item={item}
|
||||||
spaceBetween={10}
|
isInitiallyBookmarked={bookmarkedIds.has(Number(item.id))}
|
||||||
slidesPerView={1}
|
onSaved={(id) =>
|
||||||
loop={true}
|
setBookmarkedIds((prev) => new Set([...prev, Number(id)]))
|
||||||
className="w-full h-full"
|
}
|
||||||
>
|
/>
|
||||||
{images.map((img, index) => (
|
))}
|
||||||
<SwiperSlide key={index}>
|
</div>
|
||||||
<div className="relative w-full h-48 sm:h-64 md:h-80 lg:h-[460px]">
|
</div>
|
||||||
{/* <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}
|
src={img}
|
||||||
alt={`slide-${index}`}
|
alt={`slide-${index}`}
|
||||||
fill
|
fill
|
||||||
className="object-cover rounded-xl"
|
className="object-cover rounded-xl"
|
||||||
priority={index === 0}
|
priority={index === 0}
|
||||||
/> */}
|
/> */}
|
||||||
<ImageBlurry
|
<ImageBlurry
|
||||||
priority
|
priority
|
||||||
src={img}
|
src={img}
|
||||||
alt="gambar"
|
alt="gambar"
|
||||||
style={{
|
style={{
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</SwiperSlide>
|
</SwiperSlide>
|
||||||
))}
|
))}
|
||||||
</Swiper>
|
</Swiper>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</RevealR>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,12 +223,26 @@ function Card({
|
||||||
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
|
const [isBookmarked, setIsBookmarked] = useState(isInitiallyBookmarked);
|
||||||
const DEFAULT_IMAGE = "/assets/logo1.png";
|
const DEFAULT_IMAGE = "/assets/logo1.png";
|
||||||
|
|
||||||
const [imageSrc, setImageSrc] = useState(
|
const [imageSrc, setImageSrc] = useState(DEFAULT_IMAGE);
|
||||||
item?.smallThumbnailLink || DEFAULT_IMAGE
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
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]);
|
}, [item?.smallThumbnailLink]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
@ -266,7 +285,7 @@ function Card({
|
||||||
newSet.add(Number(item.id));
|
newSet.add(Number(item.id));
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
"bookmarkedIds",
|
"bookmarkedIds",
|
||||||
JSON.stringify(Array.from(newSet))
|
JSON.stringify(Array.from(newSet)),
|
||||||
);
|
);
|
||||||
|
|
||||||
MySwal.fire({
|
MySwal.fire({
|
||||||
|
|
@ -292,7 +311,7 @@ function Card({
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<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 ${
|
className={`rounded-xl overflow-hidden shadow hover:shadow-lg transition-all bg-white dark:bg-black dark:border dark:border-slate-50 ${
|
||||||
isBig
|
isBig
|
||||||
|
|
@ -312,12 +331,10 @@ function Card({
|
||||||
fill
|
fill
|
||||||
className="object-cover"
|
className="object-cover"
|
||||||
/> */}
|
/> */}
|
||||||
<Image
|
<ImageBlurry
|
||||||
src={imageSrc}
|
src={imageSrc}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
fill
|
className="w-full h-full object-contain"
|
||||||
className="object-cover"
|
|
||||||
onError={() => setImageSrc(DEFAULT_IMAGE)}
|
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -379,7 +396,7 @@ function Card({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</RevealL>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import Swal from "sweetalert2";
|
||||||
import withReactContent from "sweetalert2-react-content";
|
import withReactContent from "sweetalert2-react-content";
|
||||||
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
import { ThumbsUp, ThumbsDown } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import ImageBlurry from "../ui/image-blurry";
|
||||||
|
import { RevealL } from "../ui/RevealL";
|
||||||
|
|
||||||
function formatTanggal(dateString: string) {
|
function formatTanggal(dateString: string) {
|
||||||
if (!dateString) return "";
|
if (!dateString) return "";
|
||||||
|
|
@ -340,61 +342,74 @@ export default function MediaUpdate() {
|
||||||
};
|
};
|
||||||
|
|
||||||
function SafeImage({ src, alt }: { src?: string; alt?: string }) {
|
function SafeImage({ src, alt }: { src?: string; alt?: string }) {
|
||||||
const [imgSrc, setImgSrc] = useState(src || DEFAULT_IMAGE);
|
const [imgSrc, setImgSrc] = useState(DEFAULT_IMAGE);
|
||||||
|
|
||||||
useEffect(() => {
|
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]);
|
}, [src]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Image
|
<ImageBlurry
|
||||||
src={imgSrc}
|
src={imgSrc}
|
||||||
alt={alt || "Image"}
|
alt={alt || "Image"}
|
||||||
fill
|
className="w-full h-full object-contain"
|
||||||
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
|
|
||||||
onError={() => setImgSrc(DEFAULT_IMAGE)}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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">
|
<RevealL>
|
||||||
<div className="max-w-screen-xl mx-auto">
|
<section className="bg-white dark:bg-default-50 px-4 py-10 border max-w-[1350px] mx-auto rounded-md border-[#CDD5DF] my-10">
|
||||||
<h2 className="text-2xl font-semibold text-center mb-6">
|
<div className="max-w-screen-xl mx-auto">
|
||||||
{t("title")}
|
<h2 className="text-2xl font-semibold text-center mb-6">
|
||||||
</h2>
|
{t("title")}
|
||||||
|
</h2>
|
||||||
|
|
||||||
{/* Main Tab */}
|
{/* Main Tab */}
|
||||||
<div className="flex justify-center mb-6 bg-white dark:bg-default-50">
|
<div className="flex justify-center mb-6 bg-white dark:bg-default-50">
|
||||||
<Card className="bg-[#FFFFFF] dark:bg-default-50 rounded-xl flex flex-row p-3 gap-2 shadow-md border border-gray-200">
|
<Card className="bg-[#FFFFFF] dark:bg-default-50 rounded-xl flex flex-row p-3 gap-2 shadow-md border border-gray-200">
|
||||||
<button
|
<button
|
||||||
onClick={() => setTab("latest")}
|
onClick={() => setTab("latest")}
|
||||||
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
|
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
|
||||||
tab === "latest"
|
tab === "latest"
|
||||||
? "bg-[#C6A455] text-white shadow-sm"
|
? "bg-[#C6A455] text-white shadow-sm"
|
||||||
: "text-[#C6A455] hover:bg-[#C6A455]/10"
|
: "text-[#C6A455] hover:bg-[#C6A455]/10"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t("latest")}
|
{t("latest")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setTab("popular")}
|
onClick={() => setTab("popular")}
|
||||||
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
|
className={`px-6 py-3 rounded-lg text-sm font-semibold transition-all duration-200 ${
|
||||||
tab === "popular"
|
tab === "popular"
|
||||||
? "bg-[#C6A455] text-white shadow-sm"
|
? "bg-[#C6A455] text-white shadow-sm"
|
||||||
: "text-[#C6A455] hover:bg-[#C6A455]/10"
|
: "text-[#C6A455] hover:bg-[#C6A455]/10"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{t("popular")}{" "}
|
{t("popular")}{" "}
|
||||||
</button>
|
</button>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Content Type Filter */}
|
{/* Content Type Filter */}
|
||||||
<div className="flex justify-center mb-8">
|
<div className="flex justify-center mb-8">
|
||||||
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-4 border-2 border-blue-100 shadow-lg dark:bg-default-50">
|
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-2xl p-4 border-2 border-blue-100 shadow-lg dark:bg-default-50">
|
||||||
<div className="flex flex-wrap justify-center gap-2">
|
<div className="flex flex-wrap justify-center gap-2">
|
||||||
{/* <button
|
{/* <button
|
||||||
onClick={() => setContentType("all")}
|
onClick={() => setContentType("all")}
|
||||||
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
||||||
contentType === "all"
|
contentType === "all"
|
||||||
|
|
@ -405,180 +420,181 @@ export default function MediaUpdate() {
|
||||||
📋 Semua
|
📋 Semua
|
||||||
</button> */}
|
</button> */}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={() => setContentType("foto")}
|
onClick={() => setContentType("foto")}
|
||||||
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
||||||
contentType === "foto"
|
contentType === "foto"
|
||||||
? "bg-gradient-to-r from-orange-500 to-red-600 text-white shadow-lg ring-2 ring-orange-300"
|
? "bg-gradient-to-r from-orange-500 to-red-600 text-white shadow-lg ring-2 ring-orange-300"
|
||||||
: "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md"
|
: "bg-white text-orange-600 border-2 border-orange-200 hover:border-orange-400 hover:shadow-md"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
📸 {t("image")}
|
📸 {t("image")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setContentType("audiovisual")}
|
onClick={() => setContentType("audiovisual")}
|
||||||
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
||||||
contentType === "audiovisual"
|
contentType === "audiovisual"
|
||||||
? "bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-lg ring-2 ring-purple-300"
|
? "bg-gradient-to-r from-purple-500 to-pink-600 text-white shadow-lg ring-2 ring-purple-300"
|
||||||
: "bg-white text-purple-600 border-2 border-purple-200 hover:border-purple-400 hover:shadow-md"
|
: "bg-white text-purple-600 border-2 border-purple-200 hover:border-purple-400 hover:shadow-md"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
🎬 {t("video")}
|
🎬 {t("video")}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setContentType("audio")}
|
onClick={() => setContentType("audio")}
|
||||||
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
||||||
contentType === "audio"
|
contentType === "audio"
|
||||||
? "bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-lg ring-2 ring-green-300"
|
? "bg-gradient-to-r from-green-500 to-emerald-600 text-white shadow-lg ring-2 ring-green-300"
|
||||||
: "bg-white text-green-600 border-2 border-green-200 hover:border-green-400 hover:shadow-md"
|
: "bg-white text-green-600 border-2 border-green-200 hover:border-green-400 hover:shadow-md"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
🎵 Audio
|
🎵 Audio
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setContentType("text")}
|
onClick={() => setContentType("text")}
|
||||||
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
className={`px-4 py-2 rounded-full text-xs font-semibold transition-all duration-300 transform hover:scale-105 ${
|
||||||
contentType === "text"
|
contentType === "text"
|
||||||
? "bg-gradient-to-r from-gray-500 to-slate-600 text-white shadow-lg ring-2 ring-gray-300"
|
? "bg-gradient-to-r from-gray-500 to-slate-600 text-white shadow-lg ring-2 ring-gray-300"
|
||||||
: "bg-white text-gray-600 border-2 border-gray-200 hover:border-gray-400 hover:shadow-md"
|
: "bg-white text-gray-600 border-2 border-gray-200 hover:border-gray-400 hover:shadow-md"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
📝 {t("text")}
|
📝 {t("text")}
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Slider */}
|
{/* Slider */}
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p className="text-center">{t("loadContent")}</p>
|
<p className="text-center">{t("loadContent")}</p>
|
||||||
) : (
|
) : (
|
||||||
<Swiper
|
<Swiper
|
||||||
modules={[Navigation]}
|
modules={[Navigation]}
|
||||||
navigation
|
navigation
|
||||||
spaceBetween={20}
|
spaceBetween={20}
|
||||||
slidesPerView={1}
|
slidesPerView={1}
|
||||||
breakpoints={{
|
breakpoints={{
|
||||||
640: { slidesPerView: 2 },
|
640: { slidesPerView: 2 },
|
||||||
1024: { slidesPerView: 4 },
|
1024: { slidesPerView: 4 },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{filteredData.map((item) => (
|
{filteredData.map((item) => (
|
||||||
<SwiperSlide key={item.id}>
|
<SwiperSlide key={item.id}>
|
||||||
<div className="rounded-xl shadow-md overflow-hidden bg-white dark:bg-default-50 dark:border dark:border-slate-50">
|
<div className="rounded-xl shadow-md overflow-hidden bg-white dark:bg-default-50 dark:border dark:border-slate-50">
|
||||||
{/* ✅ Kondisi: jika typeId = 3 (text) atau 4 (audio) tampilkan ikon, lainnya tampilkan thumbnail */}
|
{/* ✅ Kondisi: jika typeId = 3 (text) atau 4 (audio) tampilkan ikon, lainnya tampilkan thumbnail */}
|
||||||
{item.typeId === 3 ? (
|
{item.typeId === 3 ? (
|
||||||
// 📝 TEXT
|
// 📝 TEXT
|
||||||
<div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white">
|
<div className="bg-[#e0c350] flex items-center justify-center h-[204px] text-white">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="90"
|
width="90"
|
||||||
height="90"
|
height="90"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207z"
|
d="M5 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V5.414a1.5 1.5 0 0 0-.44-1.06L9.647 1.439A1.5 1.5 0 0 0 8.586 1zM4 3a1 1 0 0 1 1-1h3v2.5A1.5 1.5 0 0 0 9.5 6H12v7a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1zm7.793 2H9.5a.5.5 0 0 1-.5-.5V2.207z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
) : item.typeId === 4 ? (
|
) : item.typeId === 4 ? (
|
||||||
// 🎵 AUDIO
|
// 🎵 AUDIO
|
||||||
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white">
|
<div className="flex items-center justify-center bg-[#bb3523] w-full h-[204px] text-white">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="100"
|
width="100"
|
||||||
height="100"
|
height="100"
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
d="M14.702 2.226A1 1 0 0 1 16 3.18v6.027a5.5 5.5 0 0 0-1-.184V6.18L8 8.368V15.5a2.5 2.5 0 1 1-1-2V5.368a1 1 0 0 1 .702-.955zM8 7.32l7-2.187V3.18L8 5.368zM5.5 14a1.5 1.5 0 1 0 0 3a1.5 1.5 0 0 0 0-3m13.5.5a4.5 4.5 0 1 1-9 0a4.5 4.5 0 0 1 9 0m-2.265-.436l-2.994-1.65a.5.5 0 0 0-.741.438v3.3a.5.5 0 0 0 .741.438l2.994-1.65a.5.5 0 0 0 0-.876"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// 🎬 FOTO / VIDEO (default)
|
// 🎬 FOTO / VIDEO (default)
|
||||||
<div className="w-full h-[204px] relative">
|
<div className="w-full h-[204px] relative">
|
||||||
<Link href={getLink(item)}>
|
<Link href={getLink(item)}>
|
||||||
<SafeImage
|
<SafeImage
|
||||||
src={item.smallThumbnailLink}
|
src={item.smallThumbnailLink}
|
||||||
alt={item.title}
|
alt={item.title}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* <Image
|
{/* <Image
|
||||||
src={item.smallThumbnailLink || "/placeholder.png"}
|
src={item.smallThumbnailLink || "/placeholder.png"}
|
||||||
alt={item.title || "No Title"}
|
alt={item.title || "No Title"}
|
||||||
fill
|
fill
|
||||||
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
|
className="object-cover cursor-pointer hover:opacity-90 transition-opacity"
|
||||||
/> */}
|
/> */}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Caption / info */}
|
{/* Caption / info */}
|
||||||
<div className="p-3">
|
<div className="p-3">
|
||||||
<div className="flex items-center gap-2 text-xs font-semibold flex-row justify-between mb-2">
|
<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">
|
<span className="text-xs text-white px-2 py-0.5 rounded bg-emerald-600">
|
||||||
{item.clientName}
|
{item.clientName}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-orange-600">
|
<span className="text-orange-600">
|
||||||
{item.categories
|
{item.categories
|
||||||
?.map((cat: any) => cat.title)
|
?.map((cat: any) => cat.title)
|
||||||
.join(", ")}
|
.join(", ")}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-gray-500 mb-1">
|
<p className="text-xs text-gray-500 mb-1">
|
||||||
{formatTanggal(item.createdAt)}
|
{formatTanggal(item.createdAt)}
|
||||||
</p>
|
</p>
|
||||||
<Link href={getLink(item)}>
|
<Link href={getLink(item)}>
|
||||||
<p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
|
<p className="text-sm font-semibold mb-3 line-clamp-2 cursor-pointer hover:text-blue-600 transition-colors">
|
||||||
{item.title}
|
{item.title}
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex gap-2 text-gray-600">
|
<div className="flex gap-2 text-gray-600">
|
||||||
<ThumbsUp className="w-4 h-4 cursor-pointer" />
|
<ThumbsUp className="w-4 h-4 cursor-pointer" />
|
||||||
<ThumbsDown 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>
|
||||||
<Button
|
|
||||||
onClick={() => handleSave(item.id)}
|
|
||||||
disabled={bookmarkedIds.has(Number(item.id))}
|
|
||||||
variant="default"
|
|
||||||
size="sm"
|
|
||||||
className={`rounded px-4 ${
|
|
||||||
bookmarkedIds.has(Number(item.id))
|
|
||||||
? "bg-gray-400 cursor-not-allowed text-white"
|
|
||||||
: "bg-red-700 text-white hover:bg-red-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{bookmarkedIds.has(Number(item.id))
|
|
||||||
? t("saved")
|
|
||||||
: t("save")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SwiperSlide>
|
||||||
</SwiperSlide>
|
))}
|
||||||
))}
|
</Swiper>
|
||||||
</Swiper>
|
)}
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Lihat lebih banyak - hanya muncul jika ada data */}
|
{/* Lihat lebih banyak - hanya muncul jika ada data */}
|
||||||
{filteredData.length > 0 && (
|
{filteredData.length > 0 && (
|
||||||
<div className="text-center mt-10">
|
<div className="text-center mt-10">
|
||||||
<Link href={getContentTypeLink()}>
|
<Link href={getContentTypeLink()}>
|
||||||
<Button
|
<Button
|
||||||
size={"lg"}
|
size={"lg"}
|
||||||
className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition"
|
className="text-[#b3882e] bg-transparent border border-[#b3882e] px-6 py-2 rounded-s-sm text-sm font-medium hover:bg-[#b3882e]/10 transition"
|
||||||
>
|
>
|
||||||
{t("seeMore")}
|
{t("seeMore")}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</RevealL>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { DynamicLogoTenant } from "./dynamic-logo-tenant";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import LocalSwitcher from "../partials/header/locale-switcher";
|
import LocalSwitcher from "../partials/header/locale-switcher";
|
||||||
import ThemeSwitcher from "../partials/header/theme-switcher";
|
import ThemeSwitcher from "../partials/header/theme-switcher";
|
||||||
|
import { RevealT } from "../ui/RevealT";
|
||||||
|
|
||||||
export default function Navbar() {
|
export default function Navbar() {
|
||||||
const t = useTranslations("Navbar");
|
const t = useTranslations("Navbar");
|
||||||
|
|
@ -75,217 +76,218 @@ export default function Navbar() {
|
||||||
const fullname = Cookies.get("ufne");
|
const fullname = Cookies.get("ufne");
|
||||||
|
|
||||||
return (
|
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">
|
<RevealT>
|
||||||
<div className="flex flex-row items-center justify-between space-x-4 z-10">
|
<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">
|
||||||
<Menu
|
<div className="flex flex-row items-center justify-between space-x-4 z-10">
|
||||||
className="w-6 h-6 cursor-pointer"
|
<Menu
|
||||||
onClick={() => setIsSidebarOpen(true)}
|
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>
|
|
||||||
|
|
||||||
<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">
|
<DynamicLogoTenant />
|
||||||
<ThemeSwitcher />
|
|
||||||
|
<div className="hidden custom-lg-button:flex items-end">
|
||||||
|
<ThemeSwitcher />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 🌐 NAV MENU */}
|
{/* 🌐 NAV MENU */}
|
||||||
<nav className="absolute left-1/2 -translate-x-1/2 hidden md:flex space-x-3 lg:space-x-8 text-sm font-medium">
|
<nav className="absolute left-1/2 -translate-x-1/2 hidden md:flex space-x-3 lg:space-x-8 text-sm font-medium">
|
||||||
{filteredNavItems.map((item) => {
|
{filteredNavItems.map((item) => {
|
||||||
const isActive = pathname === item.href;
|
const isActive = pathname === item.href;
|
||||||
|
|
||||||
// 🔹 Pengecekan khusus untuk "Untuk Anda"
|
// 🔹 Pengecekan khusus untuk "Untuk Anda"
|
||||||
const handleClick = (e: React.MouseEvent) => {
|
const handleClick = (e: React.MouseEvent) => {
|
||||||
if (item.label === t("forYou")) {
|
if (item.label === t("forYou")) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!checkLoginStatus()) {
|
if (!checkLoginStatus()) {
|
||||||
router.push("/auth");
|
router.push("/auth");
|
||||||
} else {
|
} else {
|
||||||
router.push("/in/for-you");
|
router.push("/in/for-you");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={item.label} className="relative">
|
<div key={item.label} className="relative">
|
||||||
{item.label === t("publication") ? (
|
{item.label === t("publication") ? (
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => setDropdownOpen(!isDropdownOpen)}
|
onClick={() => setDropdownOpen(!isDropdownOpen)}
|
||||||
|
className={cn(
|
||||||
|
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
|
||||||
|
isDropdownOpen ||
|
||||||
|
pathname.startsWith("/public/publication")
|
||||||
|
? "text-black"
|
||||||
|
: "",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded transition-all",
|
||||||
|
isDropdownOpen ||
|
||||||
|
pathname.startsWith("/public/publication")
|
||||||
|
? "opacity-100"
|
||||||
|
: "opacity-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{isDropdownOpen && (
|
||||||
|
<div className="absolute top-full mt-2 w-48 bg-white border rounded shadow z-50">
|
||||||
|
{PUBLIKASI_SUBMENU.map((sub) => (
|
||||||
|
<Link
|
||||||
|
key={sub.label}
|
||||||
|
href={sub.href}
|
||||||
|
className="block px-4 py-2 text-sm hover:bg-gray-100 text-gray-700 dark:text-white"
|
||||||
|
>
|
||||||
|
{sub.label}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
href={item.href}
|
||||||
|
onClick={handleClick}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
|
"relative text-gray-500 dark:text-white dark:hover:text-slate-300 hover:text-black transition-colors",
|
||||||
isDropdownOpen ||
|
isActive && "text-black",
|
||||||
pathname.startsWith("/public/publication")
|
|
||||||
? "text-black"
|
|
||||||
: "",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
<span
|
{isActive && (
|
||||||
className={cn(
|
<span className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded" />
|
||||||
"absolute -bottom-1 left-1/2 -translate-x-1/2 w-6 h-[3px] bg-red-800 rounded transition-all",
|
)}
|
||||||
isDropdownOpen ||
|
</Link>
|
||||||
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"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<span className="text-sm font-medium text-gray-800 dark:text-white">
|
);
|
||||||
{fullname}
|
})}
|
||||||
</span>
|
</nav>
|
||||||
<ChevronDown className="w-4 h-4 text-gray-600" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{showProfileMenu && (
|
{/* 🔹 PROFILE / LOGIN SECTION */}
|
||||||
<div className="absolute right-0 mt-2 w-40 bg-white dark:bg-black border rounded shadow z-50 ">
|
<nav className="hidden md:flex items-center gap-3 z-10 relative">
|
||||||
<Link
|
{!isLoggedIn ? (
|
||||||
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"
|
<Link href="/auth/register">
|
||||||
>
|
<Button className="bg-transparent border text-black hover:bg-red-600 hover:text-white cursor-pointer">
|
||||||
<svg
|
{t("register")}{" "}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
</Button>
|
||||||
width="20"
|
</Link>
|
||||||
height="20"
|
<Link href="/auth">
|
||||||
viewBox="0 0 24 24"
|
<Button className="bg-red-700 text-white cursor-pointer hover:bg-white hover:border hover:border-red-700 hover:text-red-700">
|
||||||
>
|
{t("login")}
|
||||||
<path
|
</Button>
|
||||||
fill="currentColor"
|
</Link>
|
||||||
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>
|
<div className="relative">
|
||||||
Dashboard
|
<button
|
||||||
</Link>
|
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
|
{showProfileMenu && (
|
||||||
onClick={handleLogout}
|
<div className="absolute right-0 mt-2 w-40 bg-white dark:bg-black border rounded shadow z-50 ">
|
||||||
className="w-full flex flex-row text-left px-4 py-2 hover:bg-gray-300 text-gray-700 dark:text-white cursor-pointer"
|
<Link
|
||||||
>
|
href="/admin/dashboard"
|
||||||
<svg
|
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"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="20"
|
|
||||||
height="20"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
>
|
||||||
<g
|
<svg
|
||||||
fill="none"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
stroke="currentColor"
|
width="20"
|
||||||
strokeLinecap="round"
|
height="20"
|
||||||
strokeWidth="1.5"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
strokeLinejoin="round"
|
fill="currentColor"
|
||||||
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"
|
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" />
|
</svg>
|
||||||
<path
|
Dashboard
|
||||||
strokeLinejoin="round"
|
</Link>
|
||||||
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 */}
|
<button
|
||||||
{isSidebarOpen && (
|
onClick={handleLogout}
|
||||||
<div className="fixed inset-0 z-50 flex">
|
className="w-full flex flex-row text-left px-4 py-2 hover:bg-gray-300 text-gray-700 dark:text-white cursor-pointer"
|
||||||
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto">
|
>
|
||||||
<button
|
<svg
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
className="absolute top-4 right-4 text-gray-600"
|
width="20"
|
||||||
>
|
height="20"
|
||||||
✕
|
viewBox="0 0 24 24"
|
||||||
</button>
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
<div className="mt-10">
|
{/* 📱 SIDEBAR MOBILE */}
|
||||||
<h3 className="text-[16px] font-bold text-gray-700 mb-2">
|
{isSidebarOpen && (
|
||||||
{t("language")}
|
<div className="fixed inset-0 z-50 flex">
|
||||||
</h3>
|
<div className="w-80 bg-white p-6 space-y-6 shadow-lg relative h-full overflow-y-auto">
|
||||||
{/* button language */}
|
<button
|
||||||
<div className={`relative text-left border w-fit rounded-lg`}>
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
<LocalSwitcher />
|
className="absolute top-4 right-4 text-gray-600"
|
||||||
</div>
|
>
|
||||||
{/* <div className="space-y-5 ml-3">
|
✕
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-10">
|
||||||
|
<h3 className="text-[16px] font-bold text-gray-700 mb-2">
|
||||||
|
{t("language")}
|
||||||
|
</h3>
|
||||||
|
{/* button language */}
|
||||||
|
<div className={`relative text-left border w-fit rounded-lg`}>
|
||||||
|
<LocalSwitcher />
|
||||||
|
</div>
|
||||||
|
{/* <div className="space-y-5 ml-3">
|
||||||
<button className="flex items-center gap-2 text-sm text-gray-800">
|
<button className="flex items-center gap-2 text-sm text-gray-800">
|
||||||
<Image src={"/Flag.svg"} width={24} height={24} alt="flag" />
|
<Image src={"/Flag.svg"} width={24} height={24} alt="flag" />
|
||||||
English
|
English
|
||||||
|
|
@ -309,86 +311,99 @@ export default function Navbar() {
|
||||||
Bahasa Indonesia
|
Bahasa Indonesia
|
||||||
</button>
|
</button>
|
||||||
</div> */}
|
</div> */}
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-[16px] font-bold text-gray-700 dark:text-white mb-2">
|
|
||||||
{t("features")}
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-5 ml-3">
|
|
||||||
{NAV_ITEMS.map((item) => (
|
|
||||||
<button
|
|
||||||
key={item.label}
|
|
||||||
onClick={() => {
|
|
||||||
if (item.label === t("forYou")) {
|
|
||||||
if (!checkLoginStatus()) {
|
|
||||||
router.push("/auth");
|
|
||||||
} else {
|
|
||||||
router.push("/for-you");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
router.push(item.href);
|
|
||||||
}
|
|
||||||
setIsSidebarOpen(false);
|
|
||||||
}}
|
|
||||||
className="block text-[15px] text-gray-800 dark:text-white text-left w-full"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-5 text-[16px] font-bold">
|
<div>
|
||||||
<Link href="/about" className="block text-black dark:text-white">
|
<h3 className="text-[16px] font-bold text-gray-700 dark:text-white mb-2">
|
||||||
{t("about")}
|
{t("features")}
|
||||||
</Link>
|
</h3>
|
||||||
<Link href="/advertising" className="block text-black dark:text-white">
|
<div className="space-y-5 ml-3">
|
||||||
{t("advertising")}
|
{NAV_ITEMS.map((item) => (
|
||||||
</Link>
|
<button
|
||||||
<Link href="/contact" className="block text-black dark:text-white">
|
key={item.label}
|
||||||
{t("contact")}
|
onClick={() => {
|
||||||
</Link>
|
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 ? (
|
<div className="space-y-5 text-[16px] font-bold">
|
||||||
<>
|
<Link
|
||||||
<Link href="/auth" className="block text-lg text-gray-800 dark:text-white">
|
href="/about"
|
||||||
{t("login")}
|
className="block text-black dark:text-white"
|
||||||
</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")}
|
{t("about")}
|
||||||
</button>
|
</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>
|
||||||
|
|
||||||
<Card className="rounded-none p-4">
|
<div
|
||||||
<h2 className="text-[#C6A455] text-center text-lg font-semibold mb-2">
|
className="flex-1 bg-black/50"
|
||||||
{t("subscribeTitle")}
|
onClick={() => setIsSidebarOpen(false)}
|
||||||
</h2>
|
/>
|
||||||
<Input type="email" placeholder={t("subscribePlaceholder")} />
|
|
||||||
<Button className="bg-[#C6A455] mt-2">
|
|
||||||
{t("subscribeButton")}
|
|
||||||
</Button>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
<div
|
</header>
|
||||||
className="flex-1 bg-black/50"
|
</RevealT>
|
||||||
onClick={() => setIsSidebarOpen(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</header>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,17 +93,17 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
files:
|
files:
|
||||||
article.files?.map((f: any) => ({
|
article.files?.map((f: any) => ({
|
||||||
id: f.id,
|
id: f.id,
|
||||||
url: f.file_url,
|
url: f.fileUrl,
|
||||||
fileName: f.file_name,
|
fileName: f.fileName,
|
||||||
filePath: f.file_path,
|
filePath: f.filePath,
|
||||||
fileThumbnail: f.file_thumbnail,
|
fileThumbnail: f.fileThumbnail,
|
||||||
fileAlt: f.file_alt,
|
fileAlt: f.fileAlt,
|
||||||
widthPixel: f.width_pixel,
|
widthPixel: f.widthPixel,
|
||||||
heightPixel: f.height_pixel,
|
heightPixel: f.heightPixel,
|
||||||
size: f.size,
|
size: f.size,
|
||||||
downloadCount: f.download_count,
|
downloadCount: f.downloadCount,
|
||||||
createdAt: f.created_at,
|
createdAt: f.createdAt,
|
||||||
updatedAt: f.updated_at,
|
updatedAt: f.updatedAt,
|
||||||
})) || [],
|
})) || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -155,7 +155,7 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Image
|
<Image
|
||||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
shimmer(700, 475)
|
shimmer(700, 475),
|
||||||
)}`}
|
)}`}
|
||||||
width={2560}
|
width={2560}
|
||||||
height={1440}
|
height={1440}
|
||||||
|
|
@ -163,8 +163,8 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
data?.files?.[selectedImage]?.url?.trim()
|
data?.files?.[selectedImage]?.url?.trim()
|
||||||
? data.files[selectedImage].url
|
? data.files[selectedImage].url
|
||||||
: data?.thumbnailUrl?.trim()
|
: data?.thumbnailUrl?.trim()
|
||||||
? data.thumbnailUrl
|
? data.thumbnailUrl
|
||||||
: "/assets/empty-data.png"
|
: "/assets/empty-data.png"
|
||||||
}
|
}
|
||||||
alt="Main"
|
alt="Main"
|
||||||
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
className="rounded-lg h-[300px] w-screen lg:h-[600px] lg:w-full object-contain"
|
||||||
|
|
@ -185,7 +185,7 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
<a onClick={() => setSelectedImage(index)} key={file?.id}>
|
||||||
<Image
|
<Image
|
||||||
placeholder={`data:image/svg+xml;base64,${toBase64(
|
placeholder={`data:image/svg+xml;base64,${toBase64(
|
||||||
shimmer(700, 475)
|
shimmer(700, 475),
|
||||||
)}`}
|
)}`}
|
||||||
width={1920}
|
width={1920}
|
||||||
height={1080}
|
height={1080}
|
||||||
|
|
@ -223,7 +223,7 @@ export default function ImageDetail({ id }: { id: number }) {
|
||||||
</span>
|
</span>
|
||||||
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
<span className="flex items-center gap-1 border-r-2 pr-2 border-black text-black">
|
||||||
<Eye className="w-4 h-4" />
|
<Eye className="w-4 h-4" />
|
||||||
{data.viewCount || 0}
|
{data.clickCount || 0}{" "}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-black">
|
<span className="text-black">
|
||||||
Creator: {data.creatorGroupLevelName}
|
Creator: {data.creatorGroupLevelName}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -83,19 +83,14 @@ export async function getListCompetencies() {
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateUserInternal(id: number, data: any) {
|
|
||||||
const url = `users/${id}`;
|
|
||||||
return httpPutInterceptor(url, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getListExperiences() {
|
export async function getListExperiences() {
|
||||||
const url = "users/user-experiences/list";
|
const url = "users/user-experiences/list";
|
||||||
return httpGetInterceptor(url);
|
return httpGetInterceptor(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveUserInternal(data: any) {
|
export async function updateUserInternal(id: number, data: any) {
|
||||||
const url = "users/save";
|
const url = `users/${id}`;
|
||||||
return httpPostInterceptor(url, data);
|
return httpPutInterceptor(url, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkRolePlacementsAvailability(data: any) {
|
export async function checkRolePlacementsAvailability(data: any) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue