Compare commits

..

No commits in common. "9ba7172d456880bf9ba04d14642c03c65fa3311a" and "0a9c119df17dd8360bc7f02007d085e59ae7c26e" have entirely different histories.

17 changed files with 1831 additions and 498 deletions

View File

@ -11,12 +11,12 @@ build-dev:
name: docker:25.0.3-cli name: docker:25.0.3-cli
services: services:
- name: docker:25.0.3-dind - name: docker:25.0.3-dind
command: ["--insecure-registry=38.47.185.86:8900"] command: ["--insecure-registry=103.82.242.92:8900"]
script: script:
- docker logout - docker logout
- docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 38.47.185.86:8900 - docker login -u $DEPLOY_USERNAME -p $DEPLOY_TOKEN 103.82.242.92:8900
- docker build -t 38.47.185.86:8900/medols/web-arah-negeri:dev . - docker build -t 103.82.242.92:8900/medols/web-arah-negeri:dev .
- docker push 38.47.185.86:8900/medols/web-arah-negeri:dev - docker push 103.82.242.92:8900/medols/web-arah-negeri:dev
auto-deploy: auto-deploy:
stage: deploy stage: deploy
@ -27,4 +27,4 @@ auto-deploy:
services: services:
- docker:dind - docker:dind
script: script:
- curl --user admin:$JENKINS_PWD http://38.47.185.86:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols - curl --user admin:$JENKINS_PWD http://103.31.38.120:8080/job/auto-deploy-arah-negeri/build?token=autodeploymedols

View File

@ -5,7 +5,6 @@ import Link from "next/link";
import { import {
getArticleById, getArticleById,
getArticleBySlug, getArticleBySlug,
getArticleFiles,
getListArticle, getListArticle,
} from "@/service/article"; } from "@/service/article";
import { close, error, loading } from "@/config/swal"; import { close, error, loading } from "@/config/swal";
@ -21,7 +20,6 @@ import {
} from "@/service/master-user"; } from "@/service/master-user";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { Badge } from "../ui/badge"; import { Badge } from "../ui/badge";
import { formatTextToHtmlTag } from "@/utils/global";
type TabKey = "trending" | "comments" | "latest"; type TabKey = "trending" | "comments" | "latest";
@ -77,13 +75,13 @@ export default function DetailContent() {
startDate: null, startDate: null,
endDate: null, endDate: null,
}); });
const [detailFiles, setDetailFiles] = useState<any[]>([]); const [detailfiles, setDetailFiles] = useState<any>([]);
const [mainImage, setMainImage] = useState(0); const [mainImage, setMainImage] = useState(0);
const [thumbnail, setThumbnail] = useState("-"); const [thumbnail, setThumbnail] = useState("-");
const [diseId, setDiseId] = useState(0); const [diseId, setDiseId] = useState(0);
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]); const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>( const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null, null
); );
const [selectedIndex, setSelectedIndex] = useState(0); const [selectedIndex, setSelectedIndex] = useState(0);
@ -299,39 +297,18 @@ export default function DetailContent() {
initStateData(); initStateData();
}, [listCategory]); }, [listCategory]);
useEffect(() => {
setSelectedIndex(0);
}, [detailFiles]);
async function initStateData() { async function initStateData() {
loading(); loading();
try { const res = await getArticleBySlug(slug);
// 1⃣ Ambil artikel by slug const data = res?.data?.data;
const res = await getArticleBySlug(slug);
const data = res?.data?.data;
if (!data?.id) return; setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setArticleDetail(data); setDetailFiles(data?.files);
setThumbnail(data.thumbnailUrl); setArticleDetail(data); // <-- Add this
setDiseId(data.aiArticleId); close();
// 2⃣ Ambil SEMUA article files
const filesRes = await getArticleFiles();
const allFiles = filesRes?.data?.data ?? [];
// 3⃣ FILTER sesuai articleId
const filteredFiles = allFiles.filter(
(file: any) => file.articleId === data.id,
);
setDetailFiles(filteredFiles);
} catch (error) {
console.error("Init state detail error:", error);
} finally {
close();
}
} }
// if (!articleDetail?.files || articleDetail?.files?.length === 0) { // if (!articleDetail?.files || articleDetail?.files?.length === 0) {
// return ( // return (
// <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg"> // <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
@ -339,16 +316,6 @@ export default function DetailContent() {
// </div> // </div>
// ); // );
// } // }
function removeImgTags(htmlString?: { __html: string }) {
const parser = new DOMParser();
const doc = parser.parseFromString(String(htmlString?.__html), "text/html");
const images = doc.querySelectorAll("img");
images.forEach((img) => img.remove());
return { __html: doc.body.innerHTML };
}
function decodeHtmlString(raw: string = "") { function decodeHtmlString(raw: string = "") {
if (!raw) return ""; if (!raw) return "";
@ -400,18 +367,16 @@ export default function DetailContent() {
</span> </span>
<span></span> <span></span>
<span> <span>
{new Date(articleDetail?.publishedAt ?? articleDetail?.createdAt) <span>
.toLocaleString("id-ID", { {new Date(articleDetail?.publishedAt).toLocaleDateString(
day: "numeric", "id-ID",
month: "long", {
year: "numeric", day: "numeric",
hour: "2-digit", month: "long",
minute: "2-digit", year: "numeric",
hour12: false, }
timeZone: "Asia/Jakarta", )}
}) </span>
.replace("pukul ", "")}{" "}
WIB
</span> </span>
<span></span> <span></span>
<span>{articleDetail?.categories?.[0]?.title}</span> <span>{articleDetail?.categories?.[0]?.title}</span>
@ -419,30 +384,36 @@ export default function DetailContent() {
<div className="w-full h-auto mb-6"> <div className="w-full h-auto mb-6">
{/* Gambar utama */} {/* Gambar utama */}
{detailFiles.length > 0 ? ( {articleDetail?.files && articleDetail.files.length > 0 ? (
<> <>
<Image {/* Gambar utama */}
src={detailFiles[selectedIndex]?.fileUrl} <div className="w-full">
alt={detailFiles[selectedIndex]?.fileAlt || "Berita"} <Image
width={800} src={articleDetail.files[selectedIndex]?.fileUrl}
height={400} alt={
className="rounded-lg w-full object-cover" articleDetail.files[selectedIndex]?.fileAlt || "Berita"
/> }
width={800}
height={400}
className="rounded-lg w-full object-cover"
/>
</div>
{/* Thumbnail */}
<div className="flex gap-2 mt-3 overflow-x-auto"> <div className="flex gap-2 mt-3 overflow-x-auto">
{detailFiles.map((file, index) => ( {articleDetail.files.map((file: any, index: number) => (
<button <button
key={file.id} key={file?.id || index}
onClick={() => setSelectedIndex(index)} onClick={() => setSelectedIndex(index)}
className={`border-2 rounded-lg ${ className={`border-2 rounded-lg overflow-hidden ${
selectedIndex === index selectedIndex === index
? "border-red-500" ? "border-red-500"
: "border-transparent" : "border-transparent"
}`} }`}
> >
<Image <Image
src={file.fileUrl} src={file?.fileUrl}
alt={file.fileAlt || "Thumbnail"} alt={file?.fileAlt || "Thumbnail"}
width={100} width={100}
height={80} height={80}
className="object-cover" className="object-cover"
@ -452,8 +423,9 @@ export default function DetailContent() {
</div> </div>
</> </>
) : ( ) : (
<div className="h-[400px] flex items-center justify-center bg-gray-100 rounded-lg"> // Jika file kosong/null tapi tetap render data lainnya
<p className="text-gray-400">Gambar tidak tersedia</p> <div className="w-full h-[400px] bg-gray-100 flex items-center justify-center rounded-lg">
<p className="text-gray-400 text-sm">Gambar tidak tersedia</p>
</div> </div>
)} )}
@ -531,10 +503,11 @@ export default function DetailContent() {
<div className="flex-1 overflow-y-auto"> <div className="flex-1 overflow-y-auto">
<div className="prose max-w-none text-justify"> <div className="prose max-w-none text-justify">
<div <div
dangerouslySetInnerHTML={removeImgTags( dangerouslySetInnerHTML={{
formatTextToHtmlTag(articleDetail?.htmlDescription), __html: decodeHtmlString(
)} articleDetail?.htmlDescription || ""
className="text-sm lg:text-xl lg:leading-8 text-justify space-y-4" ),
}}
/> />
</div> </div>
@ -842,7 +815,7 @@ export default function DetailContent() {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
}, }
)} )}
</p> </p>
</div> </div>
@ -874,7 +847,7 @@ export default function DetailContent() {
day: "2-digit", day: "2-digit",
month: "long", month: "long",
year: "numeric", year: "numeric",
}, }
)} )}
</p> </p>
</div> </div>

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useRef, useState } from "react"; // components/custom-editor.js
import { CKEditor } from "@ckeditor/ckeditor5-react";
import "@/styles/custom-editor.css"; import React from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import Editor from "@/vendor/ckeditor5/build/ckeditor"; import Editor from "@/vendor/ckeditor5/build/ckeditor";
function CustomEditor(props) { function CustomEditor(props) {
@ -47,7 +47,7 @@ function CustomEditor(props) {
padding: 1rem; padding: 1rem;
} }
p { p {
margin: 0.5em 0 !important; margin: 0.5em 0;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
margin: 1em 0 0.5em 0; margin: 1em 0 0.5em 0;
@ -72,6 +72,98 @@ function CustomEditor(props) {
}, },
}} }}
/> />
<style jsx>{`
.ckeditor-wrapper {
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
.ckeditor-wrapper :global(.ck.ck-editor__main) {
min-height: ${props.height || 400}px;
max-height: ${maxHeight}px;
}
.ckeditor-wrapper :global(.ck.ck-editor__editable) {
min-height: ${(props.height || 400) - 50}px;
max-height: ${maxHeight - 50}px;
overflow-y: auto !important;
scrollbar-width: thin;
scrollbar-color: #cbd5e1 #f1f5f9;
background: #fff !important;
color: #111 !important;
}
/* Dark mode support */
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable) {
background: #111 !important;
color: #f9fafb !important;
}
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h1),
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h2),
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h3),
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h4),
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h5),
:global(.dark) .ckeditor-wrapper :global(.ck.ck-editor__editable h6) {
color: #f9fafb !important;
}
:global(.dark)
.ckeditor-wrapper
:global(.ck.ck-editor__editable blockquote) {
background-color: #1f2937 !important;
border-left-color: #374151 !important;
color: #f3f4f6 !important;
}
/* Custom scrollbar styling for webkit browsers */
.ckeditor-wrapper :global(.ck.ck-editor__editable::-webkit-scrollbar) {
width: 8px;
}
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
background: #f1f5f9;
border-radius: 4px;
}
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
background: #cbd5e1;
border-radius: 4px;
}
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
background: #94a3b8;
}
/* Dark mode scrollbar */
:global(.dark)
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-track) {
background: #1f2937;
}
:global(.dark)
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb) {
background: #4b5563;
}
:global(.dark)
.ckeditor-wrapper
:global(.ck.ck-editor__editable::-webkit-scrollbar-thumb:hover) {
background: #6b7280;
}
/* Ensure content doesn't overflow */
.ckeditor-wrapper :global(.ck.ck-editor__editable .ck-content) {
overflow: hidden;
}
`}</style>
</div> </div>
); );
} }

View File

@ -48,8 +48,7 @@ function ViewEditor(props) {
.ckeditor-view-wrapper { .ckeditor-view-wrapper {
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
box-shadow: box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06); 0 1px 2px 0 rgba(0, 0, 0, 0.06);
} }

View File

@ -56,7 +56,7 @@ const CustomEditor = dynamic(
() => { () => {
return import("@/components/editor/custom-editor"); return import("@/components/editor/custom-editor");
}, },
{ ssr: false }, { ssr: false }
); );
interface FileWithPreview extends File { interface FileWithPreview extends File {
@ -118,14 +118,14 @@ export default function CreateArticleForm() {
const [tag, setTag] = useState(""); const [tag, setTag] = useState("");
const [thumbnailImg, setThumbnailImg] = useState<File[]>([]); const [thumbnailImg, setThumbnailImg] = useState<File[]>([]);
const [selectedMainImage, setSelectedMainImage] = useState<number | null>( const [selectedMainImage, setSelectedMainImage] = useState<number | null>(
null, null
); );
const [thumbnailValidation, setThumbnailValidation] = useState(""); const [thumbnailValidation, setThumbnailValidation] = useState("");
const [filesValidation, setFileValidation] = useState(""); const [filesValidation, setFileValidation] = useState("");
const [diseData, setDiseData] = useState<DiseData>(); const [diseData, setDiseData] = useState<DiseData>();
const [selectedWritingType, setSelectedWritingType] = useState("single"); const [selectedWritingType, setSelectedWritingType] = useState("single");
const [status, setStatus] = useState<"publish" | "draft" | "scheduled">( const [status, setStatus] = useState<"publish" | "draft" | "scheduled">(
"publish", "publish"
); );
const [isScheduled, setIsScheduled] = useState(false); const [isScheduled, setIsScheduled] = useState(false);
const [startDateValue, setStartDateValue] = useState<Date | undefined>(); const [startDateValue, setStartDateValue] = useState<Date | undefined>();
@ -230,7 +230,7 @@ export default function CreateArticleForm() {
} }
const saveArticleToDise = async ( const saveArticleToDise = async (
values: z.infer<typeof createArticleSchema>, values: z.infer<typeof createArticleSchema>
) => { ) => {
if (useAi) { if (useAi) {
const request = { const request = {
@ -351,12 +351,12 @@ export default function CreateArticleForm() {
// format: 2025-10-08 14:30:00 // format: 2025-10-08 14:30:00
const formattedDateTime = `${combinedDate.getFullYear()}-${String( const formattedDateTime = `${combinedDate.getFullYear()}-${String(
combinedDate.getMonth() + 1, combinedDate.getMonth() + 1
).padStart(2, "0")}-${String(combinedDate.getDate()).padStart( ).padStart(2, "0")}-${String(combinedDate.getDate()).padStart(
2, 2,
"0", "0"
)} ${String(combinedDate.getHours()).padStart(2, "0")}:${String( )} ${String(combinedDate.getHours()).padStart(2, "0")}:${String(
combinedDate.getMinutes(), combinedDate.getMinutes()
).padStart(2, "0")}:00`; ).padStart(2, "0")}:00`;
const request = { const request = {
@ -493,7 +493,7 @@ export default function CreateArticleForm() {
} }
} }
const uniqueArray = temp.filter( const uniqueArray = temp.filter(
(item, index) => temp.indexOf(item) === index, (item, index) => temp.indexOf(item) === index
); );
setValue("tags", uniqueArray as [string, ...string[]]); setValue("tags", uniqueArray as [string, ...string[]]);
@ -505,7 +505,7 @@ export default function CreateArticleForm() {
onSubmit={handleSubmit(onSubmit)} onSubmit={handleSubmit(onSubmit)}
> >
<div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1"> <div className="w-full lg:w-[65%] bg-white rounded-lg p-8 flex flex-col gap-1">
<p className="text-sm">Judulss</p> <p className="text-sm">Judul</p>
<Controller <Controller
control={control} control={control}
name="title" name="title"
@ -514,7 +514,7 @@ export default function CreateArticleForm() {
id="title" id="title"
type="text" type="text"
placeholder="Masukkan judul artikel" placeholder="Masukkan judul artikel"
className="h-16 px-4 text-2xl leading-tight" className="w-full border rounded-lg dark:border-gray-400"
{...field} {...field}
/> />
)} )}
@ -578,7 +578,7 @@ export default function CreateArticleForm() {
// }); // });
setValue( setValue(
"description", "description",
data?.articleBody ? data?.articleBody : "", data?.articleBody ? data?.articleBody : ""
); );
}} }}
/> />
@ -588,7 +588,7 @@ export default function CreateArticleForm() {
setDiseData(data); setDiseData(data);
setValue( setValue(
"description", "description",
data?.articleBody ? data?.articleBody : "", data?.articleBody ? data?.articleBody : ""
); );
}} }}
/> />
@ -781,7 +781,7 @@ export default function CreateArticleForm() {
type="button" type="button"
onClick={() => { onClick={() => {
const filteredTags = value.filter( const filteredTags = value.filter(
(tag: string) => tag !== item, (tag: string) => tag !== item
); );
if (filteredTags.length === 0) { if (filteredTags.length === 0) {
setError("tags", { setError("tags", {
@ -792,7 +792,7 @@ export default function CreateArticleForm() {
clearErrors("tags"); clearErrors("tags");
setValue( setValue(
"tags", "tags",
filteredTags as [string, ...string[]], filteredTags as [string, ...string[]]
); );
} }
}} }}

View File

@ -23,7 +23,6 @@ import {
deleteArticleFiles, deleteArticleFiles,
getArticleByCategory, getArticleByCategory,
getArticleById, getArticleById,
getArticleFiles,
submitApproval, submitApproval,
unPublishArticle, unPublishArticle,
updateArticle, updateArticle,
@ -197,49 +196,27 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
async function initState() { async function initState() {
loading(); loading();
try { const res = await getArticleById(id);
// 1⃣ Ambil ARTICLE const data = res.data?.data;
const articleRes = await getArticleById(id); setDetailData(data);
const articleData = articleRes.data?.data; setValue("title", data?.title);
setValue("customCreatorName", data?.customCreatorName);
setValue("slug", data?.slug);
setValue("source", data?.source);
const cleanDescription = data?.htmlDescription
? data.htmlDescription
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n", "\\")
.trim()
: "";
setValue("description", cleanDescription);
setValue("tags", data?.tags ? data.tags.split(",") : []);
setThumbnail(data?.thumbnailUrl);
setDiseId(data?.aiArticleId);
setDetailFiles(data?.files);
if (!articleData) return; setupInitCategory(data?.categories);
close();
// ===== ARTICLE DATA =====
setDetailData(articleData);
setValue("title", articleData.title);
setValue("customCreatorName", articleData.customCreatorName);
setValue("slug", articleData.slug);
setValue("source", articleData.source);
const cleanDescription = articleData.htmlDescription
? articleData.htmlDescription
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n")
.trim()
: "";
setValue("description", cleanDescription);
setValue("tags", articleData.tags ? articleData.tags.split(",") : []);
setThumbnail(articleData.thumbnailUrl);
setDiseId(articleData.aiArticleId);
setupInitCategory(articleData.categories);
// 2⃣ Ambil SEMUA article files
const filesRes = await getArticleFiles();
const allFiles = filesRes.data?.data ?? [];
// 3⃣ FILTER berdasarkan ARTICLE ID yang sedang dibuka
const filteredFiles = allFiles.filter(
(file: any) => file.articleId === articleData.id
);
setDetailFiles(filteredFiles);
} catch (error) {
console.error("Init state error:", error);
} finally {
close();
}
} }
const setupInitCategory = (data: any) => { const setupInitCategory = (data: any) => {
@ -690,10 +667,9 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
name="title" name="title"
render={({ field: { onChange, value } }) => ( render={({ field: { onChange, value } }) => (
<div className="w-full"> <div className="w-full">
<label htmlFor="title" className="block text-xl font-medium mb-2"> <label htmlFor="title" className="block text-sm font-medium mb-1">
Judul Judul
</label> </label>
<Input <Input
type="text" type="text"
id="title" id="title"
@ -701,7 +677,7 @@ export default function EditArticleForm(props: { isDetail: boolean }) {
value={value ?? ""} value={value ?? ""}
readOnly={isDetail} readOnly={isDetail}
onChange={onChange} onChange={onChange}
className="h-16 px-4 text-2xl leading-tight" className="w-full border rounded-lg"
/> />
</div> </div>
)} )}

View File

@ -316,7 +316,7 @@ export default function ArticleTable() {
return cellValue; return cellValue;
} }
}, },
[article, page], [article, page]
); );
let typingTimer: NodeJS.Timeout; let typingTimer: NodeJS.Timeout;
@ -445,8 +445,8 @@ export default function ArticleTable() {
</div> </div>
</div> </div>
<div className="w-full overflow-x-hidden"> <div className="w-full overflow-x-hidden">
<div className="w-full overflow-x-auto"> <div className="w-full mx-auto overflow-x-hidden">
<Table className="min-w-[1000px] w-full table-auto border text-sm"> <Table className="w-full table-fixed border text-sm">
<TableHeader> <TableHeader>
<TableRow> <TableRow>
{(username === "admin-mabes" {(username === "admin-mabes"
@ -455,18 +455,7 @@ export default function ArticleTable() {
).map((column) => ( ).map((column) => (
<TableHead <TableHead
key={column.uid} key={column.uid}
className={`bg-white dark:bg-black text-black dark:text-white className="truncate bg-white dark:bg-black text-black dark:text-white border-b text-md"
text-sm font-semibold border-b px-3 py-3
${
column.uid === "no"
? "min-w-[60px] text-center"
: column.uid === "title"
? "min-w-[280px]"
: column.uid === "actions"
? "min-w-[100px] text-center"
: "min-w-[160px]"
}
`}
> >
{column.name} {column.name}
</TableHead> </TableHead>
@ -483,17 +472,7 @@ export default function ArticleTable() {
).map((column) => ( ).map((column) => (
<TableCell <TableCell
key={column.uid} key={column.uid}
className={`text-black dark:text-white text-sm px-3 py-3 align-top className="truncate text-black dark:text-white max-w-[200px]"
${
column.uid === "no"
? "min-w-[60px] text-center font-medium"
: column.uid === "title"
? "min-w-[280px] whitespace-normal break-words leading-snug"
: column.uid === "actions"
? "min-w-[100px] text-center"
: "min-w-[160px] whitespace-normal break-words"
}
`}
> >
{renderCell(item, column.uid)} {renderCell(item, column.uid)}
</TableCell> </TableCell>

View File

@ -1,16 +1,13 @@
/** @type {import('next').NextConfig} */ import type { NextConfig } from "next";
const nextConfig = {
const nextConfig: NextConfig = {
images: { images: {
domains: [ domains: ["mikulnews.com", "dev.mikulnews.com"],
"mikulnews.com",
"dev.mikulnews.com",
"jaecoocihampelasbdg.com",
"dev.arahnegeri.com",
],
}, },
eslint: { eslint: {
ignoreDuringBuilds: true, ignoreDuringBuilds: true,
}, },
// Add experimental features for better chunk handling
experimental: { experimental: {
optimizePackageImports: ["@ckeditor/ckeditor5-react", "react-apexcharts"], optimizePackageImports: ["@ckeditor/ckeditor5-react", "react-apexcharts"],
}, },

1568
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -39,12 +39,12 @@
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lightningcss": "^1.30.1", "lightningcss": "^1.30.1",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"next": "^16.1.1", "next": "15.3.4",
"react": "^19.2.3", "react": "^19.0.0",
"react-apexcharts": "^1.7.0", "react-apexcharts": "^1.7.0",
"react-datepicker": "^8.4.0", "react-datepicker": "^8.4.0",
"react-day-picker": "^9.7.0", "react-day-picker": "^9.7.0",
"react-dom": "^19.2.4", "react-dom": "^19.0.0",
"react-dropzone": "^14.3.8", "react-dropzone": "^14.3.8",
"react-hook-form": "^7.65.0", "react-hook-form": "^7.65.0",
"react-password-checklist": "^1.8.1", "react-password-checklist": "^1.8.1",

View File

@ -136,13 +136,6 @@ export async function uploadArticleFile(id: string, data: any) {
return await httpPostInterceptor(`/article-files/${id}`, data, headers); return await httpPostInterceptor(`/article-files/${id}`, data, headers);
} }
export async function getArticleFiles() {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/article-files`, headers);
}
export async function uploadArticleThumbnail(id: string, data: any) { export async function uploadArticleThumbnail(id: string, data: any) {
const headers = { const headers = {
"content-type": "multipart/form-data", "content-type": "multipart/form-data",

View File

@ -1,6 +1,6 @@
import axios from "axios"; import axios from "axios";
const baseURL = "https://arahnegeri.com/api"; const baseURL = "https://dev.mikulnews.com/api";
const axiosBaseInstance = axios.create({ const axiosBaseInstance = axios.create({
baseURL, baseURL,

View File

@ -2,7 +2,7 @@ import axios from "axios";
import { postSignIn } from "../master-user"; import { postSignIn } from "../master-user";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
const baseURL = "https://arahnegeri.com/api"; const baseURL = "https://dev.mikulnews.com/api";
const refreshToken = Cookies.get("refresh_token"); const refreshToken = Cookies.get("refresh_token");
@ -28,7 +28,7 @@ axiosInterceptorInstance.interceptors.request.use(
}, },
(error) => { (error) => {
return Promise.reject(error); return Promise.reject(error);
}, }
); );
// Response interceptor // Response interceptor
@ -66,7 +66,7 @@ axiosInterceptorInstance.interceptors.response.use(
} }
return Promise.reject(error); return Promise.reject(error);
}, }
); );
export default axiosInterceptorInstance; export default axiosInterceptorInstance;

215
style/ckeditor.css Normal file
View File

@ -0,0 +1,215 @@
/* CKEditor Custom Styling */
/* Main editor container */
.ck.ck-editor {
border-radius: 6px;
overflow: hidden;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
/* Toolbar styling */
.ck.ck-toolbar {
background: #f8fafc;
border: 1px solid #d1d5db;
border-bottom: none;
border-radius: 6px 6px 0 0;
padding: 8px;
}
.ck.ck-toolbar .ck-toolbar__items {
gap: 4px;
}
/* Main editable area */
.ck.ck-editor__editable {
background: #ffffff;
border: 1px solid #d1d5db;
border-top: none;
border-radius: 0 0 6px 6px;
padding: 1.5em 2em !important;
min-height: 400px;
line-height: 1.6;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
color: #333;
}
/* Focus state */
.ck.ck-editor__editable.ck-focused {
border-color: #1a9aef;
box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
outline: none;
}
/* Content styling */
.ck.ck-editor__editable .ck-content {
padding: 0;
}
/* Typography improvements */
.ck.ck-editor__editable p {
margin: 0.5em 0;
line-height: 1.6;
}
.ck.ck-editor__editable h1,
.ck.ck-editor__editable h2,
.ck.ck-editor__editable h3,
.ck.ck-editor__editable h4,
.ck.ck-editor__editable h5,
.ck.ck-editor__editable h6 {
margin: 1em 0 0.5em 0;
font-weight: 600;
line-height: 1.4;
}
.ck.ck-editor__editable h1 { font-size: 1.75em; }
.ck.ck-editor__editable h2 { font-size: 1.5em; }
.ck.ck-editor__editable h3 { font-size: 1.25em; }
.ck.ck-editor__editable h4 { font-size: 1.1em; }
.ck.ck-editor__editable h5 { font-size: 1em; }
.ck.ck-editor__editable h6 { font-size: 0.9em; }
/* Lists */
.ck.ck-editor__editable ul,
.ck.ck-editor__editable ol {
margin: 0.5em 0;
padding-left: 2em;
}
.ck.ck-editor__editable li {
margin: 0.25em 0;
line-height: 1.6;
}
/* Blockquotes */
.ck.ck-editor__editable blockquote {
margin: 1em 0;
padding: 0.75em 1em;
border-left: 4px solid #1a9aef;
background-color: #f8fafc;
border-radius: 0 4px 4px 0;
font-style: italic;
color: #4b5563;
}
/* Tables */
.ck.ck-editor__editable table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
}
.ck.ck-editor__editable table td,
.ck.ck-editor__editable table th {
border: 1px solid #d1d5db;
padding: 0.5em 0.75em;
text-align: left;
}
.ck.ck-editor__editable table th {
background-color: #f8fafc;
font-weight: 600;
}
/* Links */
.ck.ck-editor__editable a {
color: #1a9aef;
text-decoration: underline;
}
.ck.ck-editor__editable a:hover {
color: #0d7cd6;
}
/* Code blocks */
.ck.ck-editor__editable pre {
background-color: #f8fafc;
border: 1px solid #d1d5db;
border-radius: 4px;
padding: 1em;
margin: 1em 0;
overflow-x: auto;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 13px;
line-height: 1.4;
}
.ck.ck-editor__editable code {
background-color: #f1f5f9;
padding: 0.2em 0.4em;
border-radius: 3px;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 0.9em;
}
/* Images */
.ck.ck-editor__editable img {
max-width: 100%;
height: auto;
border-radius: 4px;
margin: 0.5em 0;
}
/* Horizontal rule */
.ck.ck-editor__editable hr {
border: none;
border-top: 1px solid #d1d5db;
margin: 2em 0;
}
/* Placeholder text */
.ck.ck-editor__editable.ck-blurred:empty::before {
content: attr(data-placeholder);
color: #9ca3af;
font-style: italic;
}
/* Mobile responsiveness */
@media (max-width: 768px) {
.ck.ck-editor__editable {
padding: 1em 1.5em !important;
font-size: 16px; /* Better for mobile */
}
.ck.ck-toolbar {
padding: 6px;
}
.ck.ck-toolbar .ck-toolbar__items {
gap: 2px;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.ck.ck-editor__editable {
background: #1f2937;
color: #f9fafb;
border-color: #4b5563;
}
.ck.ck-editor__editable h1,
.ck.ck-editor__editable h2,
.ck.ck-editor__editable h3,
.ck.ck-editor__editable h4,
.ck.ck-editor__editable h5,
.ck.ck-editor__editable h6 {
color: #f9fafb;
}
.ck.ck-editor__editable blockquote {
background-color: #374151;
border-left-color: #1a9aef;
color: #d1d5db;
}
.ck.ck-editor__editable pre {
background-color: #374151;
border-color: #4b5563;
}
.ck.ck-editor__editable code {
background-color: #4b5563;
}
}

View File

@ -1,121 +0,0 @@
/* ========== CKEditor Wrapper ========== */
.ckeditor-wrapper {
border-radius: 6px;
overflow: hidden;
box-shadow:
0 1px 3px 0 rgba(0, 0, 0, 0.1),
0 1px 2px 0 rgba(0, 0, 0, 0.06);
}
/* ========== Main Editor Container ========== */
.ckeditor-wrapper .ck.ck-editor__main {
min-height: var(--editor-min-height, 400px);
max-height: var(--editor-max-height, 600px);
}
/* ========== Editable Content Area (ClassicEditor) ========== */
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
min-height: calc(var(--editor-min-height, 400px) - 50px);
max-height: calc(var(--editor-max-height, 600px) - 50px);
overflow-y: auto !important;
scrollbar-width: thin;
scrollbar-color: #cbd5e1 #f1f5f9;
background: #fff !important;
color: #111 !important;
font-family:
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 14px;
line-height: 1.6;
padding: 1rem;
border: none !important;
}
/* ========== Headings and Text Formatting ========== */
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
margin: 1em 0 0.5em 0;
color: inherit !important;
}
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline p {
margin: 0.5em 0 !important;
}
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ul,
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ol {
margin: 0.5em 0;
padding-left: 2em;
}
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
margin: 1em 0;
padding: 0.5em 1em;
border-left: 4px solid #d1d5db;
background-color: #f9fafb;
color: inherit !important;
}
/* ========== Dark Mode Support ========== */
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
background: #111 !important;
color: #f9fafb !important;
}
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
color: #f9fafb !important;
}
.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
background-color: #1f2937 !important;
border-left-color: #374151 !important;
color: #f3f4f6 !important;
}
/* ========== Custom Scrollbars (Light & Dark) ========== */
.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar {
width: 8px;
}
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 4px;
}
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 4px;
}
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
.dark
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
background: #1f2937;
}
.dark
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
background: #4b5563;
}
.dark
.ckeditor-wrapper
.ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
background: #6b7280;
}

View File

@ -1,11 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2017", "target": "ES2017",
"lib": [ "lib": ["dom", "dom.iterable", "esnext"],
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
"strict": true, "strict": true,
@ -15,7 +11,7 @@
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "react-jsx", "jsx": "preserve",
"incremental": true, "incremental": true,
"plugins": [ "plugins": [
{ {
@ -23,19 +19,9 @@
} }
], ],
"paths": { "paths": {
"@/*": [ "@/*": ["./*"]
"./*"
]
} }
}, },
"include": [ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"next-env.d.ts", "exclude": ["node_modules"]
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": [
"node_modules"
]
} }

View File

@ -97,7 +97,7 @@ export function delay(ms: number) {
export function textEllipsis( export function textEllipsis(
str: string, str: string,
maxLength: number, maxLength: number,
{ side = "end", ellipsis = "..." } = {}, { side = "end", ellipsis = "..." } = {}
) { ) {
if (str !== undefined && str?.length > maxLength) { if (str !== undefined && str?.length > maxLength) {
switch (side) { switch (side) {