This commit is contained in:
Anang Yusman 2025-07-04 09:49:42 +08:00
commit 6b10fedabf
35 changed files with 446 additions and 502 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
MEDOLS_CLIENT_KEY=bb65b1ad-e954-4a1a-b4d0-74df5bb0b640

3
.gitignore vendored
View File

@ -30,9 +30,6 @@ yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel

View File

@ -16,6 +16,9 @@ COPY package.json ./
# Menyalin direktori ckeditor5 jika diperlukan
COPY vendor/ckeditor5 ./vendor/ckeditor5
# Menyalin env
COPY .env .env
# Install dependencies
RUN pnpm install
# RUN pnpm install --frozen-lockfile

View File

@ -1,10 +1,6 @@
import Author from "@/components/landing-page/author";
import Footer from "@/components/landing-page/footer";
import Header from "@/components/landing-page/header";
import Latest from "@/components/landing-page/latest";
import LatestandPopular from "@/components/landing-page/latest-and-popular";
import Navbar from "@/components/landing-page/navbar";
import News from "@/components/landing-page/news";
import Image from "next/image";
export default function Home() {

View File

@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import ChunkErrorBoundary from "@/components/layout/chunk-error-boundary";
import "@/utils/chunk-error-handler";
const geistSans = Geist({
variable: "--font-geist-sans",
@ -25,7 +27,9 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`} suppressHydrationWarning>
{children}
<ChunkErrorBoundary>
{children}
</ChunkErrorBoundary>
</body>
</html>
);

View File

@ -27,7 +27,7 @@ import {
saveManualContext,
updateManualArticle,
} from "@/service/generate-article";
import { getUserLevels } from "@/service/user-levels/user-levels-service";
import { getUserLevels } from "@/service/user-levels-service";
import { Checkbox } from "@/components/ui/checkbox";
import { Button } from "@/components/ui/button";
import { getCategoryById } from "@/service/master-categories";

View File

@ -12,7 +12,7 @@ import { z } from "zod";
import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import { editMasterUsers, getDetailMasterUsers } from "@/service/master-user";
import { getAllUserLevels } from "@/service/user-levels/user-levels-service";
import { getAllUserLevels } from "@/service/user-levels-service";
import { listUserRole } from "@/service/master-user-role";
import { Card } from "../ui/card";
import { Label } from "../ui/label";

View File

@ -13,7 +13,7 @@ import ReactSelect from "react-select";
import makeAnimated from "react-select/animated";
import { zodResolver } from "@hookform/resolvers/zod";
import { createMasterUser } from "@/service/master-user";
import { getAllUserLevels } from "@/service/user-levels/user-levels-service";
import { getAllUserLevels } from "@/service/user-levels-service";
import { listUserRole } from "@/service/master-user-role";
import { Card } from "../ui/card";
import { Input } from "../ui/input";

View File

@ -78,13 +78,13 @@ export default function Login() {
secure: true,
sameSite: "strict",
});
const resActivity = await saveActivity(
await saveActivity(
{
activityTypeId: 1,
url: "https://dev.mikulnews.com/auth",
userId: profile?.data?.data?.id,
},
accessData?.id_token
response?.data?.data?.access_token
);
Cookies.set("profile_picture", profile?.data?.data?.profilePictureUrl, {
expires: 1,

View File

@ -250,7 +250,11 @@ export default function LatestandPopular() {
<div key={index}>
<div className="relative w-full aspect-video mb-3">
<Image
src={article.thumbnailUrl}
src={
articles[0]?.thumbnailUrl ||
articles[0]?.files?.[0]?.file_url ||
"/default-image.jpg"
}
alt={"article.title"}
fill
sizes="(max-width: 1024px) 100vw, 33vw"
@ -407,7 +411,11 @@ export default function LatestandPopular() {
<div key={index} className="flex gap-3">
<div className="relative w-[120px] h-[86px] shrink-0">
<Image
src={article?.thumbnailUrl}
src={
articles[0]?.thumbnailUrl ||
articles[0]?.files?.[0]?.file_url ||
"/default-image.jpg"
}
alt={"article?.title"}
fill
className="object-cover"

View File

@ -0,0 +1,104 @@
"use client";
import React, { Component, ErrorInfo, ReactNode } from 'react';
import { Button } from '@/components/ui/button';
import { RefreshCw } from 'lucide-react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
}
class ChunkErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
// Check if it's a chunk loading error
if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) {
return { hasError: true, error };
}
return { hasError: false };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Chunk loading error:', error, errorInfo);
// If it's a chunk loading error, try to reload the page
if (error.name === 'ChunkLoadError' || error.message.includes('Loading chunk')) {
this.setState({ hasError: true, error });
}
}
handleRetry = () => {
// Clear the error state and reload the page
this.setState({ hasError: false, error: undefined });
window.location.reload();
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 via-white to-slate-50">
<div className="text-center p-8 max-w-md">
<div className="mb-6">
<div className="w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mx-auto mb-4">
<RefreshCw className="w-8 h-8 text-red-600" />
</div>
<h2 className="text-xl font-semibold text-gray-900 mb-2">
Chunk Loading Error
</h2>
<p className="text-gray-600 mb-6">
There was an issue loading a part of the application. This usually happens when the application has been updated.
</p>
</div>
<div className="space-y-3">
<Button
onClick={this.handleRetry}
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
>
<RefreshCw className="w-4 h-4 mr-2" />
Reload Application
</Button>
<Button
onClick={() => window.location.href = '/'}
variant="outline"
className="w-full"
>
Go to Homepage
</Button>
</div>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="mt-6 text-left">
<summary className="cursor-pointer text-sm text-gray-500 hover:text-gray-700">
Error Details (Development)
</summary>
<pre className="mt-2 p-3 bg-gray-100 rounded text-xs overflow-auto">
{this.state.error.message}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
export default ChunkErrorBoundary;

View File

@ -1,7 +1,7 @@
"use client";
import { getStatisticMonthly } from "@/service/article";
import React, { useEffect, useState } from "react";
import dynamic from "next/dynamic";
import { SafeReactApexChart } from "@/utils/dynamic-import";
type WeekData = {
week: number;
@ -53,9 +53,6 @@ const ApexChartColumn = (props: {
const [seriesComment, setSeriesComment] = useState<number[]>([]);
const [seriesView, setSeriesView] = useState<number[]>([]);
const [seriesShare, setSeriesShare] = useState<number[]>([]);
const ReactApexChart = dynamic(() => import("react-apexcharts"), {
ssr: false,
});
useEffect(() => {
initFetch();
@ -132,7 +129,7 @@ const ApexChartColumn = (props: {
return (
<div className="h-full">
<div id="chart" className="h-full">
<ReactApexChart
<SafeReactApexChart
options={{
chart: {
height: "100%",

View File

@ -27,12 +27,12 @@ import Image from "next/image";
import { Switch } from "@/components/ui/switch";
import useDisclosure from "@/components/useDisclosure";
import {
createAdvertiseById,
createMediaFileAdvertise,
deleteAdvertise,
editAdvertise,
editAdvertiseIsActive,
getAdvertise,
getAdvertiseById,
} from "@/service/advertisement";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
@ -226,7 +226,7 @@ export default function AdvertiseTable(props: { triggerRefresh: boolean }) {
};
const openModal = async (id: number) => {
const res = await createAdvertiseById(Number(id));
const res = await getAdvertiseById(Number(id));
const data = res?.data?.data;
setValue("id", String(data?.id));
setValue("title", data?.title);

View File

@ -19,7 +19,7 @@ import Cookies from "js-cookie";
import {
deleteArticle,
getArticleByCategory,
getListArticle,
getArticlePagination,
updateIsBannerArticle,
} from "@/service/article";
import {
@ -91,9 +91,6 @@ export default function ArticleTable() {
useEffect(() => {
initState();
}, [page, showData, startDateValue, selectedCategories]);
useEffect(() => {
getCategories();
}, []);
@ -103,7 +100,7 @@ export default function ArticleTable() {
setCategories(data);
}
async function initState() {
const initState = useCallback(async () => {
loading();
const req = {
limit: showData,
@ -116,11 +113,11 @@ export default function ArticleTable() {
sort: "desc",
sortBy: "created_at",
};
const res = await getListArticle(req);
const res = await getArticlePagination(req);
await getTableNumber(parseInt(showData), res.data?.data);
setTotalPage(res?.data?.meta?.totalPage);
close();
}
}, [page]);
const getTableNumber = async (limit: number, data: Article[]) => {
if (data) {

View File

@ -1,15 +1,16 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
env: {
MEDOLS_CLIENT_KEY: process.env.MEDOLS_CLIENT_KEY,
},
images: {
domains: ["mikulnews.com", "dev.mikulnews.com"],
},
eslint: {
ignoreDuringBuilds: true,
},
// Add experimental features for better chunk handling
experimental: {
optimizePackageImports: ['@ckeditor/ckeditor5-react', 'react-apexcharts'],
},
};
export default nextConfig;

BIN
public/default-image.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@ -1,5 +1,4 @@
import { PaginationRequest } from "@/types/globals";
import { httpGet, httpPost, httpPut } from "./http-config/axios-base-service";
import { httpGet, httpPost } from "./http-config/http-base-services";
export async function saveActivity(data: any, token?: string) {
const headers = token
@ -11,11 +10,10 @@ export async function saveActivity(data: any, token?: string) {
"content-type": "application/json",
};
const pathUrl = `/activity-logs`;
return await httpPost(pathUrl, headers, data);
return await httpPost(pathUrl, data, headers);
}
export async function getActivity() {
const headers = { "content-type": "application/json" };
const pathUrl = `/activity-logs/statistics`;
return await httpGet(pathUrl, headers);
return await httpGet(pathUrl);
}

View File

@ -1,68 +1,42 @@
import {
httpDeleteInterceptor,
httpGet,
httpPost,
httpPut,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
import { httpDeleteInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
import { httpGet } from "./http-config/http-base-services";
const token = Cookies.get("access_token");
export async function createAdvertise(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/advertisement`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function createMediaFileAdvertise(id: string | number, data: any) {
const headers = {
"content-type": "multipart/form-data",
};
const pathUrl = `/advertisement/upload/${id}`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data, headers);
}
export async function getAdvertise(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${
data?.limit || ""
}&placement=${data?.placement || ""}&isPublish=${data.isPublish || ""}`;
return await httpGet(pathUrl, headers);
const pathUrl = `/advertisement?page=${data?.page || 1}&limit=${data?.limit || ""}&placement=${data?.placement || ""}&isPublish=${data.isPublish || ""}`;
return await httpGet(pathUrl);
}
export async function createAdvertiseById(id: number) {
const headers = {
"content-type": "application/json",
};
export async function getAdvertiseById(id: number) {
const pathUrl = `/advertisement/${id}`;
return await httpGet(pathUrl, headers);
return await httpGet(pathUrl);
}
export async function editAdvertise(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/advertisement/${data?.id}`;
return await httpPut(pathUrl, headers, data);
return await httpPutInterceptor(pathUrl, data);
}
export async function editAdvertiseIsActive(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/advertisement/publish/${data?.id}?isPublish=${data?.isActive}`;
return await httpPut(pathUrl, headers);
return await httpPutInterceptor(pathUrl, data);
}
export async function deleteAdvertise(id: number) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/advertisement/${id}`;
return await httpDeleteInterceptor(pathUrl, headers);
return await httpDeleteInterceptor(pathUrl);
}

View File

@ -1,7 +1,6 @@
import { PaginationRequest } from "@/types/globals";
import Cookies from "js-cookie";
import { httpGet } from "./http-config/http-base-services";
import { httpDeleteInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
export async function getListArticle(props: PaginationRequest) {
const {
@ -29,6 +28,28 @@ export async function getListArticle(props: PaginationRequest) {
);
}
export async function getArticlePagination(props: PaginationRequest) {
const {
page,
limit,
search,
startDate,
endDate,
category,
sortBy,
sort,
categorySlug,
isBanner,
} = props;
return await httpGetInterceptor(
`/articles?limit=${limit}&page=${page}&title=${search}&startDate=${startDate || ""}&endDate=${
endDate || ""
}&categoryId=${category || ""}&sortBy=${sortBy || "created_at"}&sort=${
sort || "asc"
}&category=${categorySlug || ""}&isBanner=${isBanner || ""}`
);
}
export async function getTopArticles(props: PaginationRequest) {
const { page, limit, search, startDate, endDate, isPublish, category } =
props;
@ -75,7 +96,7 @@ export async function deleteArticle(id: string) {
}
export async function getArticleByCategory() {
return await httpGet(`/article-categories?limit=1000`);
return await httpGetInterceptor(`/article-categories?limit=1000`);
}
export async function getCategoryPagination(data: any) {
return await httpGet(

View File

@ -5,7 +5,8 @@ const baseURL = "https://dev.mikulnews.com/api";
const axiosBaseInstance = axios.create({
baseURL,
headers: {
"content-type": "application/json",
"Content-Type": "application/json",
"X-Client-Key": "bb65b1ad-e954-4a1a-b4d0-74df5bb0b640"
},
});

View File

@ -1,154 +0,0 @@
import { getCsrfToken } from "../master-user";
import axiosInterceptorInstance from "./axios-interceptor-instance";
import axiosBaseInstance from "./http-base-instance";
import mediahubBaseInstance from "./mediahub-base-service";
import Cookies from "js-cookie";
const defaultHeaders = {
"Content-Type": "application/json",
};
export async function httpPost(pathUrl: any, headers: any, data?: any) {
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.csrf_token;
const mergedHeaders = {
...defaultHeaders,
...headers,
...(csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}),
};
const response = await axiosBaseInstance
.post(pathUrl, data, { headers: mergedHeaders })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function httpGet(pathUrl: any, headers: any) {
const response = await axiosInterceptorInstance
.get(pathUrl, { headers })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function httpPut(pathUrl: any, headers: any, data?: any) {
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.csrf_token;
const defaultHeaders = {
"Content-Type": "application/json",
};
const mergedHeaders = {
...defaultHeaders,
...headers,
...(csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}),
};
const response = await axiosBaseInstance
.put(pathUrl, data, { headers: mergedHeaders })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function httpDeleteInterceptor(pathUrl: any, headers: any) {
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.csrf_token;
const defaultHeaders = {
"Content-Type": "application/json",
};
const mergedHeaders = {
...defaultHeaders,
...headers,
...(csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}),
};
const response = await axiosBaseInstance
.delete(pathUrl, { headers: mergedHeaders })
.catch((error) => error.response);
console.log("Response interceptor : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}
export async function mediahubGet(pathUrl: any, headers: any) {
const response = await mediahubBaseInstance
.get(pathUrl, { headers })
.catch(function (error: any) {
console.log(error);
return error.response;
});
console.log("Response base svc : ", response);
if (response?.status == 200 || response?.status == 201) {
return {
error: false,
message: "success",
data: response?.data,
};
} else {
return {
error: true,
message: response?.data?.message || response?.data || null,
data: null,
};
}
}

View File

@ -10,7 +10,7 @@ const axiosInterceptorInstance = axios.create({
baseURL,
headers: {
"Content-Type": "application/json",
"X-Client-Key": process.env.MEDOLS_CLIENT_KEY
"X-Client-Key": "bb65b1ad-e954-4a1a-b4d0-74df5bb0b640"
},
withCredentials: true,
});

View File

@ -1,14 +0,0 @@
import axios from "axios";
const baseURL = "https://dev.mikulnews.com/api";
const axiosBaseInstance = axios.create({
baseURL,
headers: {
"content-type": "application/json",
"X-Client-Key": process.env.MEDOLS_CLIENT_KEY
},
withCredentials: true,
});
export default axiosBaseInstance;

View File

@ -1,12 +1,12 @@
import axiosBaseInstance from "./http-base-instance";
import axiosBaseInstance from "./axios-base-instance";
const defaultHeaders = {
"Content-Type": "application/json",
"X-Client-Key": process.env.MEDOLS_CLIENT_KEY
"X-Client-Key": "bb65b1ad-e954-4a1a-b4d0-74df5bb0b640"
};
export async function httpGet(pathUrl: any, headers?: any) {
export async function httpGet(pathUrl: any, headers?: any) {
console.log("X-HEADERS : ", defaultHeaders)
const mergedHeaders = {
...defaultHeaders,
...headers,
@ -33,9 +33,13 @@ export async function httpGet(pathUrl: any, headers?: any) {
}
}
export async function httpPost(pathUrl: any, headers: any, data: any) {
export async function httpPost(pathUrl: any, data: any, headers?: any) {
const mergedHeaders = {
...defaultHeaders,
...headers,
};
const response = await axiosBaseInstance
.post(pathUrl, data, { headers })
.post(pathUrl, data, { headers: mergedHeaders })
.catch(function (error) {
console.log(error);
return error.response;

View File

@ -5,12 +5,13 @@ import { getCsrfToken } from "../master-user";
const defaultHeaders = {
"Content-Type": "application/json",
"X-Client-Key": process.env.MEDOLS_CLIENT_KEY
"X-Client-Key": "bb65b1ad-e954-4a1a-b4d0-74df5bb0b640"
};
export async function httpGetInterceptor(pathUrl: any) {
console.log("X-HEADERS : ", defaultHeaders)
const response = await axiosInterceptorInstance
.get(pathUrl)
.get(pathUrl, { headers: defaultHeaders })
.catch((error) => error.response);
console.log("Response interceptor : ", response);
if (response?.status == 200 || response?.status == 201) {
@ -97,7 +98,7 @@ export async function httpPutInterceptor(pathUrl: any, data: any, headers?: any)
}
}
export async function httpDeleteInterceptor(pathUrl: any, headers: any) {
export async function httpDeleteInterceptor(pathUrl: any, headers?: any) {
const resCsrf = await getCsrfToken();
const csrfToken = resCsrf?.data?.csrf_token;

View File

@ -1,12 +0,0 @@
import axios from "axios";
const baseURL = "https://mediahub.polri.go.id/api";
const mediahubBaseInstance = axios.create({
baseURL,
headers: {
"content-type": "application/json",
},
});
export default mediahubBaseInstance;

View File

@ -1,75 +1,50 @@
import { PaginationRequest } from "@/types/globals";
import {
httpDeleteInterceptor,
httpGet,
httpPost,
httpPut,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
const token = Cookies.get("access_token");
export async function createMagazine(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/magazines`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function getListMagazine(props: PaginationRequest) {
const { page, limit, search, startDate, endDate } = props;
const headers = {
"content-type": "application/json",
};
return await httpGet(
return await httpGetInterceptor(
`/magazines?limit=${limit}&page=${page}&title=${search}&startDate=${
startDate || ""
}&endDate=${endDate || ""}`,
headers
}&endDate=${endDate || ""}`
);
}
export async function updateMagazine(id: string, data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/magazines/${id}`;
return await httpPut(pathUrl, headers, data);
return await httpPutInterceptor(pathUrl, data);
}
export async function getMagazineById(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/magazines/${id}`, headers);
return await httpGetInterceptor(`/magazines/${id}`);
}
export async function deleteMagazine(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpDeleteInterceptor(`magazines/${id}`, headers);
return await httpDeleteInterceptor(`magazines/${id}`);
}
export async function uploadMagazineFile(id: string, data: any) {
const headers = {
"content-type": "multipart/form-data",
};
return await httpPost(`/magazine-files/${id}`, headers, data);
return await httpPostInterceptor(`/magazine-files/${id}`, data, headers);
}
export async function uploadMagazineThumbnail(id: string, data: any) {
const headers = {
"content-type": "multipart/form-data",
};
return await httpPost(`/magazines/thumbnail/${id}`, headers, data);
return await httpPostInterceptor(`/magazines/thumbnail/${id}`, data, headers);
}
export async function deleteMagazineFiles(id: number) {
const headers = {
"content-type": "multipart/form-data",
};
return await httpDeleteInterceptor(`magazine-files/${id}`, headers);
return await httpDeleteInterceptor(`magazine-files/${id}`);
}

View File

@ -1,47 +1,29 @@
import {
httpDeleteInterceptor,
httpGet,
httpPost,
httpPut,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
const token = Cookies.get("access_token");
export async function createCategory(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/article-categories`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function updateCategory(id: string, data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/article-categories/${id}`;
return await httpPut(pathUrl, headers, data);
return await httpPutInterceptor(pathUrl, data);
}
export async function getCategoryById(id: number) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/article-categories/${id}`, headers);
return await httpGetInterceptor(`/article-categories/${id}`);
}
export async function deleteCategory(id: number) {
const headers = {
"content-type": "application/json",
};
return await httpDeleteInterceptor(`article-categories/${id}`, headers);
return await httpDeleteInterceptor(`article-categories/${id}`);
}
export async function uploadCategoryThumbnail(id: string, data: any) {
const headers = {
"content-type": "multipart/form-data",
};
return await httpPost(`/article-categories/thumbnail/${id}`, headers, data);
return await httpPostInterceptor(`/article-categories/thumbnail/${id}`, data, headers);
}

View File

@ -1,42 +1,29 @@
import {
httpDeleteInterceptor,
httpGet,
httpPost,
} from "./http-config/axios-base-service";
import Cookies from "js-cookie";
import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor } from "./http-config/http-interceptor-services";
const token = Cookies.get("access_token");
export async function listUserRole(data: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/user-roles?limit=${data.limit}&page=${data.page}`,
headers
return await httpGetInterceptor(
`/user-roles?limit=${data.limit}&page=${data.page}`
);
}
export async function createMasterUserRole(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
const pathUrl = `/user-roles`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function getMasterUserRoleById(id: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/user-roles/${id}`, headers);
return await httpGetInterceptor(`/user-roles/${id}`);
}
export async function deleteMasterUserRole(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpDeleteInterceptor(`/user-roles/${id}`, headers);
return await httpDeleteInterceptor(`/user-roles/${id}`);
}

View File

@ -1,6 +1,6 @@
import { httpDeleteInterceptor, httpGet, httpPost, httpPut } from "./http-config/axios-base-service";
import Cookies from "js-cookie";
import axiosBaseInstance from "./http-config/http-base-instance";
import { httpGet, httpPost } from "./http-config/http-base-services";
import { httpDeleteInterceptor, httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
const token = Cookies.get("access_token");
const id = Cookies.get("uie");
@ -13,78 +13,55 @@ export async function listMasterUsers(data: any) {
}
export async function createMasterUser(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/users`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function emailValidation(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/users/email-validation`;
return await httpPost(pathUrl, headers, data);
return await httpPost(pathUrl, data);
}
export async function setupEmail(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/users/setup-email`;
return await httpPost(pathUrl, headers, data);
return await httpPost(pathUrl, data);
}
export async function getDetailMasterUsers(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/users/detail/${id}`, headers);
const pathUrl = `/users/detail/${id}`;
return await httpGetInterceptor(pathUrl);
}
export async function editMasterUsers(data: any, id: string) {
const headers = {
"content-type": "application/json",
};
return await httpPut(`/users/${id}`, headers, data);
const pathUrl = `/users/${id}`
return await httpPutInterceptor(pathUrl, data);
}
export async function deleteMasterUser(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpDeleteInterceptor(`/users/${id}`, headers);
const pathUrl = `/users/${id}`
return await httpDeleteInterceptor(pathUrl);
}
export async function postSignIn(data: any) {
const headers = {
accept: "application/json",
"content-type": "application/json",
};
const pathUrl = `/users/login`;
return await httpPost(pathUrl, headers, data);
return await httpPost(pathUrl, data);
}
export async function getProfile(code?: string) {
export async function getProfile(token?: string) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${code || token}`,
"Authorization": `Bearer ${token}`,
};
return await httpGet(`/users/info`, headers);
const pathUrl = `/users/info`;
return await httpGet(pathUrl, headers);
}
export async function updateProfile(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPut(`/users/${id}`, headers, data);
const pathUrl = `/users/${id}`;
return await httpPutInterceptor(pathUrl, data);
}
export async function savePassword(data: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPost(`/users/save-password`, headers, data);
const pathUrl = `/users/save-password`
return await httpPostInterceptor(pathUrl, data);
}
export async function resetPassword(data: any) {
@ -102,23 +79,13 @@ export async function checkUsernames(username: string) {
}
export async function otpRequest(email: string, name: string) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/otp-request`, headers, { email, name });
const pathUrl = `/users/otp-request`;
return await httpPost(pathUrl, { email, name });
}
export async function otpValidation(email: string, otpCode: string) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/otp-validation`, headers, { email, otpCode });
}
export async function otpValidationLogin(data: any) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`/users/otp-validation`, headers, data);
const pathUrl = `/users/otp-validation`
return await httpPost(pathUrl, { email, otpCode });
}
export async function postArticleComment(data: any) {
@ -134,31 +101,21 @@ export async function postArticleComment(data: any) {
}
export async function editArticleComment(data: any, id: number) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPut(`/article-comments/${id}`, headers, data);
const pathUrl = `/article-comments/${id}`;
return await httpPutInterceptor(pathUrl, data);
}
export async function getArticleComment(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/article-comments?isPublic=true&articleId=${id}`, headers);
const pathUrl = `/article-comments?isPublic=true&articleId=${id}`;
return await httpGet(pathUrl);
}
export async function deleteArticleComment(id: number) {
const headers = {
"content-type": "application/json",
};
return await httpDeleteInterceptor(`/article-comments/${id}`, headers);
const pathUrl = `/article-comments/${id}`
return await httpDeleteInterceptor(pathUrl);
}
export async function getCsrfToken() {
const pathUrl = "csrf-token";
const headers = {
"content-type": "application/json",
};
return httpGet(pathUrl, headers);
return httpGet(pathUrl);
}

View File

@ -1,47 +1,28 @@
import { PaginationRequest } from "@/types/globals";
import {
httpDeleteInterceptor,
httpGet,
httpPost,
httpPut,
} from "./http-config/axios-base-service";
import { httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
import { httpGet } from "./http-config/http-base-services";
export async function createCustomStaticPage(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/custom-static-pages`;
return await httpPost(pathUrl, headers, data);
return await httpPostInterceptor(pathUrl, data);
}
export async function editCustomStaticPage(data: any) {
const headers = {
"content-type": "application/json",
};
const pathUrl = `/custom-static-pages/${data.id}`;
return await httpPut(pathUrl, headers, data);
return await httpPutInterceptor(pathUrl, data);
}
export async function getCustomStaticPage(props: PaginationRequest) {
const { page, limit, search } = props;
const headers = {
"content-type": "application/json",
};
return await httpGet(
`/custom-static-pages?limit=${limit}&page=${page}&title=${search}`,
headers
);
const pathUrl = `/custom-static-pages?limit=${limit}&page=${page}&title=${search}`;
return await httpGetInterceptor(pathUrl);
}
export async function getCustomStaticDetail(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/custom-static-pages/${id}`, headers);
return await httpGetInterceptor(`/custom-static-pages/${id}`);
}
export async function getCustomStaticDetailBySlug(slug: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`/custom-static-pages/slug/${slug}`, headers);
const pathUrl = `/custom-static-pages/slug/${slug}`;
return await httpGet(pathUrl);
}

View File

@ -0,0 +1,30 @@
import Cookies from "js-cookie";
import { httpGetInterceptor, httpPostInterceptor, httpPutInterceptor } from "./http-config/http-interceptor-services";
const token = Cookies.get("access_token");
export async function getAllUserLevels(data?: any) {
const pathUrl = `user-levels?limit=${data?.limit || ""}&levelNumber=${
data?.levelNumber || ""
}&name=${data?.search || ""}&page=${data?.page || "1"}`
return await httpGetInterceptor(pathUrl);
}
export async function getUserLevels(id: string) {
return await httpGetInterceptor(`user-levels/${id}`);
}
export async function getAccountById(id: string) {
return await httpGetInterceptor(`user-account/findById/${id}`);
}
export async function createUserLevels(data: any) {
return await httpPostInterceptor(`user-levels`, data);
}
export async function editUserLevels(id: string, data: any) {
return await httpPutInterceptor(`user-levels/${id}`, data);
}
export async function changeIsApproval(data: any) {
return await httpPutInterceptor(`user-levels/enable-approval`, data);
}

View File

@ -1,52 +0,0 @@
import Cookies from "js-cookie";
import { httpGet, httpPost, httpPut } from "../http-config/axios-base-service";
const token = Cookies.get("access_token");
export async function getAllUserLevels(data?: any) {
const headers = {
"content-type": "application/json",
};
return await httpGet(
`user-levels?limit=${data?.limit || ""}&levelNumber=${
data?.levelNumber || ""
}&name=${data?.search || ""}&page=${data?.page || "1"}`,
headers
);
}
export async function getUserLevels(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`user-levels/${id}`, headers);
}
export async function getAccountById(id: string) {
const headers = {
"content-type": "application/json",
};
return await httpGet(`user-account/findById/${id}`, headers);
}
export async function createUserLevels(request: any) {
const headers = {
"content-type": "application/json",
};
return await httpPost(`user-levels`, headers, request);
}
export async function editUserLevels(id: string, request: any) {
const headers = {
"content-type": "application/json",
};
return await httpPut(`user-levels/${id}`, headers, request);
}
export async function changeIsApproval(request: any) {
const headers = {
"content-type": "application/json",
Authorization: `Bearer ${token}`,
};
return await httpPut(`user-levels/enable-approval`, headers, request);
}

View File

@ -0,0 +1,87 @@
// Global chunk loading error handler
export function setupChunkErrorHandler() {
if (typeof window === 'undefined') return;
// Handle chunk loading errors
window.addEventListener('error', (event) => {
const error = event.error;
// Check if it's a chunk loading error
if (
error?.name === 'ChunkLoadError' ||
error?.message?.includes('Loading chunk') ||
error?.message?.includes('Failed to fetch')
) {
console.warn('Chunk loading error detected:', error);
// Prevent the error from being logged to console
event.preventDefault();
// Show a user-friendly message
const message = document.createElement('div');
message.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #fef2f2;
border: 1px solid #fecaca;
color: #dc2626;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
z-index: 9999;
max-width: 300px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
`;
message.innerHTML = `
<div style="display: flex; align-items: center; gap: 8px;">
<span></span>
<span>Application update detected. Please refresh the page.</span>
</div>
<button onclick="window.location.reload()" style="
margin-top: 8px;
background: #dc2626;
color: white;
border: none;
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
">Refresh</button>
`;
document.body.appendChild(message);
// Auto-remove after 10 seconds
setTimeout(() => {
if (message.parentNode) {
message.parentNode.removeChild(message);
}
}, 10000);
}
});
// Handle unhandled promise rejections (which might include chunk loading errors)
window.addEventListener('unhandledrejection', (event) => {
const error = event.reason;
if (
error?.name === 'ChunkLoadError' ||
error?.message?.includes('Loading chunk') ||
error?.message?.includes('Failed to fetch')
) {
console.warn('Unhandled chunk loading rejection:', error);
event.preventDefault();
// Reload the page after a short delay
setTimeout(() => {
window.location.reload();
}, 1000);
}
});
}
// Auto-setup when this module is imported
if (typeof window !== 'undefined') {
setupChunkErrorHandler();
}

70
utils/dynamic-import.ts Normal file
View File

@ -0,0 +1,70 @@
import dynamic from 'next/dynamic';
import { ComponentType } from 'react';
interface DynamicImportOptions {
ssr?: boolean;
loading?: () => React.ReactElement;
retries?: number;
retryDelay?: number;
}
export function createSafeDynamicImport<T extends ComponentType<any>>(
importFn: () => Promise<{ default: T }>,
options: DynamicImportOptions = {}
) {
const { ssr = false, loading, retries = 3, retryDelay = 1000 } = options;
return dynamic(
() => {
return new Promise<T>((resolve, reject) => {
let attempts = 0;
const attemptImport = async () => {
try {
const module = await importFn();
resolve(module.default);
} catch (error) {
attempts++;
// Check if it's a chunk loading error
if (
(error as any)?.name === 'ChunkLoadError' ||
(error as any)?.message?.includes('Loading chunk') ||
(error as any)?.message?.includes('Failed to fetch')
) {
if (attempts < retries) {
console.warn(`Chunk loading failed, retrying... (${attempts}/${retries})`);
setTimeout(attemptImport, retryDelay);
return;
}
}
reject(error);
}
};
attemptImport();
});
},
{
ssr,
loading,
}
);
}
// Predefined safe dynamic imports for common components
export const SafeCustomEditor = createSafeDynamicImport(
() => import('@/components/editor/custom-editor'),
{ ssr: false }
);
export const SafeViewEditor = createSafeDynamicImport(
() => import('@/components/editor/view-editor'),
{ ssr: false }
);
export const SafeReactApexChart = createSafeDynamicImport(
() => import('react-apexcharts'),
{ ssr: false }
);