diff --git a/.env b/.env new file mode 100644 index 0000000..f104216 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +MEDOLS_CLIENT_KEY=bb65b1ad-e954-4a1a-b4d0-74df5bb0b640 \ No newline at end of file diff --git a/.gitignore b/.gitignore index bfe57c0..acf2fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Dockerfile b/Dockerfile index 335d444..2b874db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/app/category/popular/page.tsx b/app/category/popular/page.tsx index 3684a13..60e05d8 100644 --- a/app/category/popular/page.tsx +++ b/app/category/popular/page.tsx @@ -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() { diff --git a/app/layout.tsx b/app/layout.tsx index 63e2178..e9bc5ad 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -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 ( - {children} + + {children} + ); diff --git a/components/form/article/create-article-form.tsx b/components/form/article/create-article-form.tsx index cfe4e28..1f6b448 100644 --- a/components/form/article/create-article-form.tsx +++ b/components/form/article/create-article-form.tsx @@ -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"; diff --git a/components/form/form-master-user-edit.tsx b/components/form/form-master-user-edit.tsx index 75c3b5f..75b2654 100644 --- a/components/form/form-master-user-edit.tsx +++ b/components/form/form-master-user-edit.tsx @@ -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"; diff --git a/components/form/form-master-user.tsx b/components/form/form-master-user.tsx index de4a3d9..3484c09 100644 --- a/components/form/form-master-user.tsx +++ b/components/form/form-master-user.tsx @@ -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"; diff --git a/components/form/login.tsx b/components/form/login.tsx index 7eeee15..42e3829 100644 --- a/components/form/login.tsx +++ b/components/form/login.tsx @@ -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, diff --git a/components/landing-page/latest-and-popular.tsx b/components/landing-page/latest-and-popular.tsx index c3942b2..67bdd64 100644 --- a/components/landing-page/latest-and-popular.tsx +++ b/components/landing-page/latest-and-popular.tsx @@ -250,7 +250,11 @@ export default function LatestandPopular() {
{"article.title"}
{"article?.title"} { + 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 ( +
+
+
+
+ +
+

+ Chunk Loading Error +

+

+ There was an issue loading a part of the application. This usually happens when the application has been updated. +

+
+ +
+ + + +
+ + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + Error Details (Development) + +
+                  {this.state.error.message}
+                
+
+ )} +
+
+ ); + } + + return this.props.children; + } +} + +export default ChunkErrorBoundary; \ No newline at end of file diff --git a/components/main/dashboard/chart/column-chart.tsx b/components/main/dashboard/chart/column-chart.tsx index 747bd02..d29575c 100644 --- a/components/main/dashboard/chart/column-chart.tsx +++ b/components/main/dashboard/chart/column-chart.tsx @@ -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([]); const [seriesView, setSeriesView] = useState([]); const [seriesShare, setSeriesShare] = useState([]); - const ReactApexChart = dynamic(() => import("react-apexcharts"), { - ssr: false, - }); useEffect(() => { initFetch(); @@ -132,7 +129,7 @@ const ApexChartColumn = (props: { return (
- { - 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); diff --git a/components/table/article-table.tsx b/components/table/article-table.tsx index ff15804..319f42a 100644 --- a/components/table/article-table.tsx +++ b/components/table/article-table.tsx @@ -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) { diff --git a/next.config.ts b/next.config.ts index f955c56..9e3686f 100644 --- a/next.config.ts +++ b/next.config.ts @@ -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; diff --git a/public/default-image.jpg b/public/default-image.jpg new file mode 100644 index 0000000..ce9446e Binary files /dev/null and b/public/default-image.jpg differ diff --git a/service/activity-log.ts b/service/activity-log.ts index 2056d6f..98634ee 100644 --- a/service/activity-log.ts +++ b/service/activity-log.ts @@ -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); } diff --git a/service/advertisement.ts b/service/advertisement.ts index fe5fa69..90b5123 100644 --- a/service/advertisement.ts +++ b/service/advertisement.ts @@ -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); } diff --git a/service/article.ts b/service/article.ts index 98d1d5b..8faddf4 100644 --- a/service/article.ts +++ b/service/article.ts @@ -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( diff --git a/service/http-config/axios-base-instance.ts b/service/http-config/axios-base-instance.ts index 03f30f6..de3efbe 100644 --- a/service/http-config/axios-base-instance.ts +++ b/service/http-config/axios-base-instance.ts @@ -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" }, }); diff --git a/service/http-config/axios-base-service.ts b/service/http-config/axios-base-service.ts deleted file mode 100644 index c33fd16..0000000 --- a/service/http-config/axios-base-service.ts +++ /dev/null @@ -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, - }; - } -} diff --git a/service/http-config/axios-interceptor-instance.ts b/service/http-config/axios-interceptor-instance.ts index e90570d..422a192 100644 --- a/service/http-config/axios-interceptor-instance.ts +++ b/service/http-config/axios-interceptor-instance.ts @@ -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, }); diff --git a/service/http-config/http-base-instance.ts b/service/http-config/http-base-instance.ts deleted file mode 100644 index b7d47a7..0000000 --- a/service/http-config/http-base-instance.ts +++ /dev/null @@ -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; diff --git a/service/http-config/http-base-services.ts b/service/http-config/http-base-services.ts index 92e46f9..755d158 100644 --- a/service/http-config/http-base-services.ts +++ b/service/http-config/http-base-services.ts @@ -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; diff --git a/service/http-config/http-interceptor-services.ts b/service/http-config/http-interceptor-services.ts index a9238b6..4d33d96 100644 --- a/service/http-config/http-interceptor-services.ts +++ b/service/http-config/http-interceptor-services.ts @@ -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; diff --git a/service/http-config/mediahub-base-service.ts b/service/http-config/mediahub-base-service.ts deleted file mode 100644 index 499c570..0000000 --- a/service/http-config/mediahub-base-service.ts +++ /dev/null @@ -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; diff --git a/service/magazine.tsx b/service/magazine.tsx index 9c76d36..d711c8d 100644 --- a/service/magazine.tsx +++ b/service/magazine.tsx @@ -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}`); } diff --git a/service/master-categories.tsx b/service/master-categories.tsx index 9efd3ec..44276b5 100644 --- a/service/master-categories.tsx +++ b/service/master-categories.tsx @@ -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); } diff --git a/service/master-user-role.ts b/service/master-user-role.ts index eda2837..567045b 100644 --- a/service/master-user-role.ts +++ b/service/master-user-role.ts @@ -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}`); } diff --git a/service/master-user.ts b/service/master-user.ts index cf875e1..ede054b 100644 --- a/service/master-user.ts +++ b/service/master-user.ts @@ -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); } \ No newline at end of file diff --git a/service/static-page-service.ts b/service/static-page-service.ts index ad2a87d..41631ec 100644 --- a/service/static-page-service.ts +++ b/service/static-page-service.ts @@ -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); } diff --git a/service/user-levels-service.ts b/service/user-levels-service.ts new file mode 100644 index 0000000..3a2ca3e --- /dev/null +++ b/service/user-levels-service.ts @@ -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); +} diff --git a/service/user-levels/user-levels-service.ts b/service/user-levels/user-levels-service.ts deleted file mode 100644 index 5f1f457..0000000 --- a/service/user-levels/user-levels-service.ts +++ /dev/null @@ -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); -} diff --git a/utils/chunk-error-handler.ts b/utils/chunk-error-handler.ts new file mode 100644 index 0000000..fc09c39 --- /dev/null +++ b/utils/chunk-error-handler.ts @@ -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 = ` +
+ ⚠️ + Application update detected. Please refresh the page. +
+ + `; + + 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(); +} \ No newline at end of file diff --git a/utils/dynamic-import.ts b/utils/dynamic-import.ts new file mode 100644 index 0000000..2469892 --- /dev/null +++ b/utils/dynamic-import.ts @@ -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>( + importFn: () => Promise<{ default: T }>, + options: DynamicImportOptions = {} +) { + const { ssr = false, loading, retries = 3, retryDelay = 1000 } = options; + + return dynamic( + () => { + return new Promise((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 } +); \ No newline at end of file