This commit is contained in:
Anang Yusman 2025-11-02 19:17:30 +08:00
parent 8f7c6024bb
commit 87528c9870
11 changed files with 80 additions and 30 deletions

View File

@ -0,0 +1,19 @@
import DetailContent from "@/components/details/details-content";
import Footer from "@/components/landing-page/footer";
import Navbar from "@/components/landing-page/navbar";
import Image from "next/image";
export default function Slug() {
return (
<div className="relative min-h-screen font-[family-name:var(--font-geist-sans)]">
<div className="relative z-10 bg-[#F2F4F3] max-w-7xl mx-auto">
<Navbar />
<div className="flex-1">
<DetailContent />
</div>
<Footer />
</div>
</div>
);
}

View File

@ -2,7 +2,11 @@
import Image from "next/image"; import Image from "next/image";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import Link from "next/link"; import Link from "next/link";
import { getArticleById, getListArticle } from "@/service/article"; import {
getArticleById,
getArticleBySlug,
getListArticle,
} from "@/service/article";
import { close, error, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
import { useParams, usePathname } from "next/navigation"; import { useParams, usePathname } from "next/navigation";
import { CommentIcon } from "../icons/sidebar-icon"; import { CommentIcon } from "../icons/sidebar-icon";
@ -16,6 +20,7 @@ import {
} from "@/service/master-user"; } from "@/service/master-user";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { saveActivity } from "@/service/activity-log"; import { saveActivity } from "@/service/activity-log";
import { Badge } from "../ui/badge";
type TabKey = "trending" | "comments" | "latest"; type TabKey = "trending" | "comments" | "latest";
@ -25,6 +30,7 @@ type Article = {
description: string; description: string;
categoryName: string; categoryName: string;
createdAt: string; createdAt: string;
slug: string;
createdByName: string; createdByName: string;
customCreatorName: string; customCreatorName: string;
thumbnailUrl: string; thumbnailUrl: string;
@ -55,6 +61,7 @@ type Advertise = {
export default function DetailContent() { export default function DetailContent() {
const params = useParams(); const params = useParams();
const id = params?.id; const id = params?.id;
const slug = params?.slug;
const pathname = usePathname(); const pathname = usePathname();
const [page, setPage] = useState(1); const [page, setPage] = useState(1);
const [totalPage, setTotalPage] = useState(1); const [totalPage, setTotalPage] = useState(1);
@ -253,8 +260,8 @@ export default function DetailContent() {
async function initStateData() { async function initStateData() {
loading(); loading();
const res = await getArticleById(id); const res = await getArticleBySlug(slug);
const data = res.data?.data; const data = res?.data?.data;
setThumbnail(data?.thumbnailUrl); setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId); setDiseId(data?.aiArticleId);
@ -533,9 +540,17 @@ export default function DetailContent() {
</span> </span>
<div className="flex flex-wrap gap-2 mt-1"> <div className="flex flex-wrap gap-2 mt-1">
{articleDetail?.tags ? ( {articleDetail?.tags ? (
<span className="bg-gray-100 text-gray-700 text-sm px-2 py-1 rounded"> articleDetail.tags
{articleDetail.tags} .split(",") // pisahkan berdasarkan koma
</span> .map((tag: string, index: number) => (
<Badge
key={index}
variant="secondary"
className="text-sm"
>
{tag.trim()}
</Badge>
))
) : ( ) : (
<span className="text-sm text-gray-500">Tidak ada tag</span> <span className="text-sm text-gray-500">Tidak ada tag</span>
)} )}
@ -705,7 +720,7 @@ export default function DetailContent() {
<div> <div>
<Link <Link
className="flex space-x-3 mb-2" className="flex space-x-3 mb-2"
href={`/detail/${article.id}`} href={`/details/${article.slug}`}
> >
<Image <Image
src={article.thumbnailUrl || "/default-thumb.png"} src={article.thumbnailUrl || "/default-thumb.png"}

View File

@ -10,6 +10,7 @@ type Article = {
title: string; title: string;
description: string; description: string;
categoryName: string; categoryName: string;
slug: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
thumbnailUrl: string; thumbnailUrl: string;
@ -72,7 +73,7 @@ export default function HeaderEkonomi() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{articles.map((article) => ( {articles.map((article) => (
<div key={article.id}> <div key={article.id}>
<Link href={`/detail/${article.id}`}> <Link href={`/details/${article.slug}`}>
<Image <Image
src={article.thumbnailUrl || "/default.jpg"} src={article.thumbnailUrl || "/default.jpg"}
alt={article.title} alt={article.title}
@ -99,7 +100,7 @@ export default function HeaderEkonomi() {
})} })}
</p> </p>
<Link <Link
href={`/detail/${article.id}`} href={`/details/${article.slug}`}
className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline" className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline"
> >
READ MORE &gt; READ MORE &gt;
@ -130,7 +131,7 @@ export default function HeaderEkonomi() {
<h4 className="text-xl font-bold">Recent Posts</h4> <h4 className="text-xl font-bold">Recent Posts</h4>
{recentPosts.map((post, index) => ( {recentPosts.map((post, index) => (
<div key={post.id}> <div key={post.id}>
<Link className="flex gap-3" href={`/detail/${post.id}`}> <Link className="flex gap-3" href={`/details/${post.slug}`}>
<Image <Image
src={post.thumbnailUrl || "/default.jpg"} src={post.thumbnailUrl || "/default.jpg"}
alt={post.title} alt={post.title}

View File

@ -10,6 +10,7 @@ type Article = {
title: string; title: string;
description: string; description: string;
categoryName: string; categoryName: string;
slug: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
thumbnailUrl: string; thumbnailUrl: string;
@ -71,7 +72,7 @@ export default function HeaderHealth() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{articles.map((article) => ( {articles.map((article) => (
<div key={article.id}> <div key={article.id}>
<Link href={`/detail/${article.id}`}> <Link href={`/details/${article.slug}`}>
<Image <Image
src={article.thumbnailUrl || "/default.jpg"} src={article.thumbnailUrl || "/default.jpg"}
alt={article.title} alt={article.title}
@ -98,7 +99,7 @@ export default function HeaderHealth() {
})} })}
</p> </p>
<Link <Link
href={`/detail/${article.id}`} href={`/details/${article.slug}`}
className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline" className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline"
> >
READ MORE &gt; READ MORE &gt;
@ -129,7 +130,7 @@ export default function HeaderHealth() {
<h4 className="text-xl font-bold">Recent Posts</h4> <h4 className="text-xl font-bold">Recent Posts</h4>
{recentPosts.map((post, index) => ( {recentPosts.map((post, index) => (
<div key={post.id}> <div key={post.id}>
<Link className="flex gap-3" href={`/detail/${post.id}`}> <Link className="flex gap-3" href={`/details/${post.slug}`}>
<Image <Image
src={post.thumbnailUrl || "/default.jpg"} src={post.thumbnailUrl || "/default.jpg"}
alt={post.title} alt={post.title}

View File

@ -9,6 +9,7 @@ type Article = {
id: number; id: number;
title: string; title: string;
description: string; description: string;
slug: string;
categoryName: string; categoryName: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
@ -71,7 +72,7 @@ export default function HeaderNarrative() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{articles.map((article) => ( {articles.map((article) => (
<div key={article.id}> <div key={article.id}>
<Link href={`/detail/${article.id}`}> <Link href={`/details/${article.slug}`}>
<Image <Image
src={article.thumbnailUrl || "/default.jpg"} src={article.thumbnailUrl || "/default.jpg"}
alt={article.title} alt={article.title}
@ -98,7 +99,7 @@ export default function HeaderNarrative() {
})} })}
</p> </p>
<Link <Link
href={`/detail/${article.id}`} href={`/details/${article.slug}`}
className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline" className="mt-2 inline-block text-sm text-yellow-600 font-semibold hover:underline"
> >
READ MORE &gt; READ MORE &gt;
@ -129,7 +130,7 @@ export default function HeaderNarrative() {
<h4 className="text-xl font-bold">Recent Posts</h4> <h4 className="text-xl font-bold">Recent Posts</h4>
{recentPosts.map((post, index) => ( {recentPosts.map((post, index) => (
<div key={post.id}> <div key={post.id}>
<Link className="flex gap-3" href={`/detail/${post.id}`}> <Link className="flex gap-3" href={`/details/${post.slug}`}>
<Image <Image
src={post.thumbnailUrl || "/default.jpg"} src={post.thumbnailUrl || "/default.jpg"}
alt={post.title} alt={post.title}

View File

@ -11,6 +11,7 @@ type Article = {
title: string; title: string;
description: string; description: string;
categoryName: string; categoryName: string;
slug: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
thumbnailUrl: string; thumbnailUrl: string;
@ -66,7 +67,7 @@ export default function Beranda() {
<div key={item.id}> <div key={item.id}>
<Link <Link
className="relative overflow-hidden group cursor-pointer" className="relative overflow-hidden group cursor-pointer"
href={`/detail/${item?.id}`} href={`/details/${item?.slug}`}
> >
<Image <Image
src={item.thumbnailUrl || "/placeholder.jpg"} src={item.thumbnailUrl || "/placeholder.jpg"}

View File

@ -13,6 +13,7 @@ type Article = {
categoryName: string; categoryName: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
slug: string;
thumbnailUrl: string; thumbnailUrl: string;
categories: { categories: {
title: string; title: string;
@ -107,7 +108,7 @@ const MoreNews = () => {
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{articles.map((item) => ( {articles.map((item) => (
<div key={item?.id} className="space-y-2"> <div key={item?.id} className="space-y-2">
<Link className="space-y-2" href={`/detail/${item?.id}`}> <Link className="space-y-2" href={`/details/${item?.slug}`}>
<div className="relative"> <div className="relative">
<Image <Image
src={item.thumbnailUrl} src={item.thumbnailUrl}

View File

@ -175,6 +175,7 @@ type Article = {
categoryName: string; categoryName: string;
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
slug: string;
customCreatorName: string; customCreatorName: string;
thumbnailUrl: string; thumbnailUrl: string;
categories: { categories: {
@ -296,7 +297,7 @@ export default function News() {
<div key={item.id}> <div key={item.id}>
<Link <Link
className="relative rounded overflow-hidden group cursor-pointer" className="relative rounded overflow-hidden group cursor-pointer"
href={`/detail/${item?.id}`} href={`/details/${item?.slug}`}
> >
<Image <Image
src={item.thumbnailUrl || "/placeholder.jpg"} src={item.thumbnailUrl || "/placeholder.jpg"}
@ -329,7 +330,7 @@ export default function News() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{articles.map((item) => ( {articles.map((item) => (
<div key={item.id}> <div key={item.id}>
<Link className="flex gap-4" href={`/detail/${item?.id}`}> <Link className="flex gap-4" href={`/details/${item?.slug}`}>
<Image <Image
src={item.thumbnailUrl} src={item.thumbnailUrl}
alt={item.title} alt={item.title}
@ -409,7 +410,7 @@ export default function News() {
<> <>
<Link <Link
className="flex gap-4" className="flex gap-4"
href={`/detail/${articles[0]?.id}`} href={`/details/${articles[0]?.slug}`}
> >
<Image <Image
src={articles[0]?.thumbnailUrl || "/placeholder.jpg"} src={articles[0]?.thumbnailUrl || "/placeholder.jpg"}
@ -454,7 +455,7 @@ export default function News() {
<div key={item.id}> <div key={item.id}>
<Link <Link
className="relative rounded overflow-hidden group cursor-pointer" className="relative rounded overflow-hidden group cursor-pointer"
href={`/detail/${item?.id}`} href={`/details/${item?.slug}`}
> >
<Image <Image
src={item.thumbnailUrl} src={item.thumbnailUrl}
@ -546,7 +547,7 @@ export default function News() {
key={post.id} key={post.id}
className="flex flex-col md:flex-row border-b border-gray-200 pb-6" className="flex flex-col md:flex-row border-b border-gray-200 pb-6"
> >
<Link href={`/detail/${post?.id}`}> <Link href={`/details/${post?.slug}`}>
{/* Left Content */} {/* Left Content */}
<div className="flex-1 pr-0 md:pr-6"> <div className="flex-1 pr-0 md:pr-6">
<h2 className="text-xl md:text-2xl font-bold mb-2"> <h2 className="text-xl md:text-2xl font-bold mb-2">
@ -569,7 +570,7 @@ export default function News() {
{post.description.split(" ").slice(0, 50).join(" ")} {post.description.split(" ").slice(0, 50).join(" ")}
{post.description.split(" ").length > 50 && "..."} {post.description.split(" ").length > 50 && "..."}
</p> </p>
<Link href={`/detail/${post?.id}`}> <Link href={`/details/${post?.slug}`}>
<button className="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-100 transition"> <button className="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-100 transition">
READ MORE READ MORE
</button> </button>
@ -645,7 +646,7 @@ export default function News() {
{post.description.split(" ").slice(0, 50).join(" ")} {post.description.split(" ").slice(0, 50).join(" ")}
{post.description.split(" ").length > 50 && "..."} {post.description.split(" ").length > 50 && "..."}
</p> </p>
<Link href={`/detail/${post?.id}`}> <Link href={`/details/${post?.slug}`}>
<button className="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-100 transition"> <button className="px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded hover:bg-gray-100 transition">
READ MORE READ MORE
</button> </button>

View File

@ -13,6 +13,7 @@ type Article = {
createdAt: string; createdAt: string;
createdByName: string; createdByName: string;
thumbnailUrl: string; thumbnailUrl: string;
slug: string;
categories: { categories: {
title: string; title: string;
}[]; }[];
@ -119,7 +120,7 @@ const TravelNews = () => {
<div key={item.id}> <div key={item.id}>
<Link <Link
className="relative rounded-md overflow-hidden shadow-lg" className="relative rounded-md overflow-hidden shadow-lg"
href={`/detail/${item?.id}`} href={`/details/${item?.slug}`}
> >
<Image <Image
src={item.thumbnailUrl || "/placeholder.jpg"} src={item.thumbnailUrl || "/placeholder.jpg"}

View File

@ -199,11 +199,11 @@ export default function ArticleTable() {
initState(); initState();
}; };
const copyUrlArticle = async (id: number) => { const copyUrlArticle = async (slug: any) => {
const url = const url =
`${window.location.protocol}//${window.location.host}` + `${window.location.protocol}//${window.location.host}` +
"/detail/" + "/details/" +
`${id}`; `${slug}`;
try { try {
await navigator.clipboard.writeText(url); await navigator.clipboard.writeText(url);
successToast("Success", "Article Copy to Clipboard"); successToast("Success", "Article Copy to Clipboard");
@ -263,7 +263,9 @@ export default function ArticleTable() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56"> <DropdownMenuContent className="w-56">
<DropdownMenuItem onClick={() => copyUrlArticle(article.id)}> <DropdownMenuItem
onClick={() => copyUrlArticle(article.slug)}
>
<CopyIcon className="mr-2 h-4 w-4" /> <CopyIcon className="mr-2 h-4 w-4" />
Copy Url Article Copy Url Article
</DropdownMenuItem> </DropdownMenuItem>

View File

@ -106,6 +106,13 @@ export async function getArticleById(id: any) {
return await httpGet(`/articles/${id}`, headers); return await httpGet(`/articles/${id}`, headers);
} }
export async function getArticleBySlug(slug: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/articles/slug/${slug}`, headers);
}
export async function deleteArticle(id: string) { export async function deleteArticle(id: string) {
const headers = { const headers = {
"content-type": "application/json", "content-type": "application/json",