feat: update content popup
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
2eaa34d052
commit
6176567557
|
|
@ -1,6 +1,12 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
type MutableRefObject,
|
||||||
|
} from "react";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
@ -40,27 +46,47 @@ import {
|
||||||
getPartnerContents,
|
getPartnerContents,
|
||||||
getPopupNewsList,
|
getPopupNewsList,
|
||||||
saveAboutContent,
|
saveAboutContent,
|
||||||
saveAboutUsMediaUrl,
|
saveAboutUsMediaUpload,
|
||||||
saveHeroContent,
|
saveHeroContent,
|
||||||
saveHeroImage,
|
saveHeroImageUpload,
|
||||||
saveOurProductContent,
|
saveOurProductContent,
|
||||||
saveOurServiceContent,
|
saveOurServiceContent,
|
||||||
savePartnerContent,
|
savePartnerContent,
|
||||||
saveOurProductImage,
|
saveOurProductImageUpload,
|
||||||
saveOurServiceImage,
|
saveOurServiceImageUpload,
|
||||||
savePopupNews,
|
savePopupNews,
|
||||||
savePopupNewsImage,
|
savePopupNewsImageUpload,
|
||||||
updateAboutContent,
|
updateAboutContent,
|
||||||
updateHeroContent,
|
updateHeroContent,
|
||||||
updateHeroImage,
|
updateHeroImageUpload,
|
||||||
updateOurProductContent,
|
updateOurProductContent,
|
||||||
updateOurProductImage,
|
updateOurProductImageUpload,
|
||||||
updateOurServiceContent,
|
updateOurServiceContent,
|
||||||
updateOurServiceImage,
|
updateOurServiceImageUpload,
|
||||||
updatePartnerContent,
|
updatePartnerContent,
|
||||||
updatePopupNews,
|
updatePopupNews,
|
||||||
|
uploadPartnerLogo,
|
||||||
} from "@/service/cms-landing";
|
} from "@/service/cms-landing";
|
||||||
|
|
||||||
|
function revokeBlobRef(ref: MutableRefObject<string | null>) {
|
||||||
|
if (ref.current) {
|
||||||
|
URL.revokeObjectURL(ref.current);
|
||||||
|
ref.current = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPickedFile(
|
||||||
|
file: File | null,
|
||||||
|
ref: MutableRefObject<string | null>,
|
||||||
|
setter: (f: File | null) => void,
|
||||||
|
) {
|
||||||
|
revokeBlobRef(ref);
|
||||||
|
if (file) {
|
||||||
|
ref.current = URL.createObjectURL(file);
|
||||||
|
}
|
||||||
|
setter(file);
|
||||||
|
}
|
||||||
|
|
||||||
export default function ContentWebsite() {
|
export default function ContentWebsite() {
|
||||||
const [activeTab, setActiveTab] = useState("hero");
|
const [activeTab, setActiveTab] = useState("hero");
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
@ -73,7 +99,9 @@ export default function ContentWebsite() {
|
||||||
const [heroDesc, setHeroDesc] = useState("");
|
const [heroDesc, setHeroDesc] = useState("");
|
||||||
const [heroCta1, setHeroCta1] = useState("");
|
const [heroCta1, setHeroCta1] = useState("");
|
||||||
const [heroCta2, setHeroCta2] = useState("");
|
const [heroCta2, setHeroCta2] = useState("");
|
||||||
const [heroImgUrl, setHeroImgUrl] = useState("");
|
const [heroRemoteUrl, setHeroRemoteUrl] = useState("");
|
||||||
|
const [heroPendingFile, setHeroPendingFile] = useState<File | null>(null);
|
||||||
|
const heroBlobUrlRef = useRef<string | null>(null);
|
||||||
|
|
||||||
const [aboutId, setAboutId] = useState<number | null>(null);
|
const [aboutId, setAboutId] = useState<number | null>(null);
|
||||||
const [aboutPrimary, setAboutPrimary] = useState("");
|
const [aboutPrimary, setAboutPrimary] = useState("");
|
||||||
|
|
@ -81,18 +109,23 @@ export default function ContentWebsite() {
|
||||||
const [aboutDesc, setAboutDesc] = useState("");
|
const [aboutDesc, setAboutDesc] = useState("");
|
||||||
const [aboutCta1, setAboutCta1] = useState("");
|
const [aboutCta1, setAboutCta1] = useState("");
|
||||||
const [aboutCta2, setAboutCta2] = useState("");
|
const [aboutCta2, setAboutCta2] = useState("");
|
||||||
const [aboutMediaUrl, setAboutMediaUrl] = useState("");
|
const [aboutRemoteMediaUrl, setAboutRemoteMediaUrl] = useState("");
|
||||||
|
const [aboutPendingFile, setAboutPendingFile] = useState<File | null>(null);
|
||||||
|
const aboutBlobUrlRef = useRef<string | null>(null);
|
||||||
const [aboutMediaImageId, setAboutMediaImageId] = useState<number | null>(
|
const [aboutMediaImageId, setAboutMediaImageId] = useState<number | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const [aboutMediaLoadedUrl, setAboutMediaLoadedUrl] = useState("");
|
|
||||||
|
|
||||||
const [products, setProducts] = useState<CmsProductContent[]>([]);
|
const [products, setProducts] = useState<CmsProductContent[]>([]);
|
||||||
const [productEditId, setProductEditId] = useState<string | null>(null);
|
const [productEditId, setProductEditId] = useState<string | null>(null);
|
||||||
const [productPrimary, setProductPrimary] = useState("");
|
const [productPrimary, setProductPrimary] = useState("");
|
||||||
const [productSecondary, setProductSecondary] = useState("");
|
const [productSecondary, setProductSecondary] = useState("");
|
||||||
const [productDesc, setProductDesc] = useState("");
|
const [productDesc, setProductDesc] = useState("");
|
||||||
const [productImgUrl, setProductImgUrl] = useState("");
|
const [productRemoteUrl, setProductRemoteUrl] = useState("");
|
||||||
|
const [productPendingFile, setProductPendingFile] = useState<File | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const productBlobUrlRef = useRef<string | null>(null);
|
||||||
const [productImageId, setProductImageId] = useState<string | null>(null);
|
const [productImageId, setProductImageId] = useState<string | null>(null);
|
||||||
const [productModalOpen, setProductModalOpen] = useState(false);
|
const [productModalOpen, setProductModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -101,13 +134,22 @@ export default function ContentWebsite() {
|
||||||
const [servicePrimary, setServicePrimary] = useState("");
|
const [servicePrimary, setServicePrimary] = useState("");
|
||||||
const [serviceSecondary, setServiceSecondary] = useState("");
|
const [serviceSecondary, setServiceSecondary] = useState("");
|
||||||
const [serviceDesc, setServiceDesc] = useState("");
|
const [serviceDesc, setServiceDesc] = useState("");
|
||||||
const [serviceImgUrl, setServiceImgUrl] = useState("");
|
const [serviceRemoteUrl, setServiceRemoteUrl] = useState("");
|
||||||
|
const [servicePendingFile, setServicePendingFile] = useState<File | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const serviceBlobUrlRef = useRef<string | null>(null);
|
||||||
const [serviceImageId, setServiceImageId] = useState<string | null>(null);
|
const [serviceImageId, setServiceImageId] = useState<string | null>(null);
|
||||||
const [serviceModalOpen, setServiceModalOpen] = useState(false);
|
const [serviceModalOpen, setServiceModalOpen] = useState(false);
|
||||||
|
|
||||||
const [partners, setPartners] = useState<CmsPartnerContent[]>([]);
|
const [partners, setPartners] = useState<CmsPartnerContent[]>([]);
|
||||||
const [partnerTitle, setPartnerTitle] = useState("");
|
const [partnerTitle, setPartnerTitle] = useState("");
|
||||||
const [partnerImgUrl, setPartnerImgUrl] = useState("");
|
const [partnerRemoteUrl, setPartnerRemoteUrl] = useState("");
|
||||||
|
const [partnerStoredPath, setPartnerStoredPath] = useState("");
|
||||||
|
const [partnerPendingFile, setPartnerPendingFile] = useState<File | null>(
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
const partnerBlobUrlRef = useRef<string | null>(null);
|
||||||
const [editingPartnerId, setEditingPartnerId] = useState<string | null>(null);
|
const [editingPartnerId, setEditingPartnerId] = useState<string | null>(null);
|
||||||
const [partnerModalOpen, setPartnerModalOpen] = useState(false);
|
const [partnerModalOpen, setPartnerModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|
@ -118,7 +160,9 @@ export default function ContentWebsite() {
|
||||||
const [popupDesc, setPopupDesc] = useState("");
|
const [popupDesc, setPopupDesc] = useState("");
|
||||||
const [popupCta1, setPopupCta1] = useState("");
|
const [popupCta1, setPopupCta1] = useState("");
|
||||||
const [popupCta2, setPopupCta2] = useState("");
|
const [popupCta2, setPopupCta2] = useState("");
|
||||||
const [popupImgUrl, setPopupImgUrl] = useState("");
|
const [popupRemoteUrl, setPopupRemoteUrl] = useState("");
|
||||||
|
const [popupPendingFile, setPopupPendingFile] = useState<File | null>(null);
|
||||||
|
const popupBlobUrlRef = useRef<string | null>(null);
|
||||||
const [popupModalOpen, setPopupModalOpen] = useState(false);
|
const [popupModalOpen, setPopupModalOpen] = useState(false);
|
||||||
|
|
||||||
const loadAll = useCallback(async () => {
|
const loadAll = useCallback(async () => {
|
||||||
|
|
@ -134,11 +178,13 @@ export default function ContentWebsite() {
|
||||||
setHeroCta1(hero.primary_cta ?? "");
|
setHeroCta1(hero.primary_cta ?? "");
|
||||||
setHeroCta2(hero.secondary_cta_text ?? "");
|
setHeroCta2(hero.secondary_cta_text ?? "");
|
||||||
const first = hero.images?.[0];
|
const first = hero.images?.[0];
|
||||||
|
revokeBlobRef(heroBlobUrlRef);
|
||||||
|
setHeroPendingFile(null);
|
||||||
if (first?.image_url) {
|
if (first?.image_url) {
|
||||||
setHeroImgUrl(first.image_url);
|
setHeroRemoteUrl(first.image_url);
|
||||||
setHeroImageId(first.id ?? null);
|
setHeroImageId(first.id ?? null);
|
||||||
} else {
|
} else {
|
||||||
setHeroImgUrl("");
|
setHeroRemoteUrl("");
|
||||||
setHeroImageId(null);
|
setHeroImageId(null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -149,7 +195,9 @@ export default function ContentWebsite() {
|
||||||
setHeroDesc("");
|
setHeroDesc("");
|
||||||
setHeroCta1("");
|
setHeroCta1("");
|
||||||
setHeroCta2("");
|
setHeroCta2("");
|
||||||
setHeroImgUrl("");
|
revokeBlobRef(heroBlobUrlRef);
|
||||||
|
setHeroPendingFile(null);
|
||||||
|
setHeroRemoteUrl("");
|
||||||
}
|
}
|
||||||
|
|
||||||
const aboutRes = await getAboutContentsList();
|
const aboutRes = await getAboutContentsList();
|
||||||
|
|
@ -164,8 +212,9 @@ export default function ContentWebsite() {
|
||||||
setAboutCta2(ab.secondary_cta_text ?? "");
|
setAboutCta2(ab.secondary_cta_text ?? "");
|
||||||
const am = ab.images?.[0];
|
const am = ab.images?.[0];
|
||||||
const murl = am?.media_url?.trim() ?? "";
|
const murl = am?.media_url?.trim() ?? "";
|
||||||
setAboutMediaUrl(murl);
|
revokeBlobRef(aboutBlobUrlRef);
|
||||||
setAboutMediaLoadedUrl(murl);
|
setAboutPendingFile(null);
|
||||||
|
setAboutRemoteMediaUrl(murl);
|
||||||
setAboutMediaImageId(am?.id ?? null);
|
setAboutMediaImageId(am?.id ?? null);
|
||||||
} else {
|
} else {
|
||||||
setAboutId(null);
|
setAboutId(null);
|
||||||
|
|
@ -174,8 +223,9 @@ export default function ContentWebsite() {
|
||||||
setAboutDesc("");
|
setAboutDesc("");
|
||||||
setAboutCta1("");
|
setAboutCta1("");
|
||||||
setAboutCta2("");
|
setAboutCta2("");
|
||||||
setAboutMediaUrl("");
|
revokeBlobRef(aboutBlobUrlRef);
|
||||||
setAboutMediaLoadedUrl("");
|
setAboutPendingFile(null);
|
||||||
|
setAboutRemoteMediaUrl("");
|
||||||
setAboutMediaImageId(null);
|
setAboutMediaImageId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,15 +281,26 @@ export default function ContentWebsite() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (heroImgUrl.trim() && hid) {
|
if (heroPendingFile && hid) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("file", heroPendingFile);
|
||||||
|
let imgRes;
|
||||||
if (heroImageId) {
|
if (heroImageId) {
|
||||||
await updateHeroImage(heroImageId, { image_url: heroImgUrl.trim() });
|
imgRes = await updateHeroImageUpload(heroImageId, fd);
|
||||||
} else {
|
} else {
|
||||||
await saveHeroImage({
|
fd.append("hero_content_id", hid);
|
||||||
hero_content_id: hid,
|
imgRes = await saveHeroImageUpload(fd);
|
||||||
image_url: heroImgUrl.trim(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (imgRes?.error) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Hero image upload failed",
|
||||||
|
text: String(imgRes.message ?? ""),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revokeBlobRef(heroBlobUrlRef);
|
||||||
|
setHeroPendingFile(null);
|
||||||
}
|
}
|
||||||
await Swal.fire({ icon: "success", title: "Hero section saved", timer: 1600, showConfirmButton: false });
|
await Swal.fire({ icon: "success", title: "Hero section saved", timer: 1600, showConfirmButton: false });
|
||||||
await loadAll();
|
await loadAll();
|
||||||
|
|
@ -280,29 +341,24 @@ export default function ContentWebsite() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = aboutMediaUrl.trim();
|
if (aid != null && aboutPendingFile) {
|
||||||
if (aid != null) {
|
|
||||||
if (!url) {
|
|
||||||
if (aboutMediaImageId != null) {
|
if (aboutMediaImageId != null) {
|
||||||
await deleteAboutUsContentImage(aboutMediaImageId);
|
await deleteAboutUsContentImage(aboutMediaImageId);
|
||||||
}
|
}
|
||||||
} else if (url !== aboutMediaLoadedUrl || aboutMediaImageId == null) {
|
const fd = new FormData();
|
||||||
if (aboutMediaImageId != null) {
|
fd.append("about_us_content_id", String(aid));
|
||||||
await deleteAboutUsContentImage(aboutMediaImageId);
|
fd.append("file", aboutPendingFile);
|
||||||
}
|
const mres = await saveAboutUsMediaUpload(fd);
|
||||||
const mres = await saveAboutUsMediaUrl({
|
|
||||||
about_us_content_id: aid,
|
|
||||||
media_url: url,
|
|
||||||
});
|
|
||||||
if (mres?.error) {
|
if (mres?.error) {
|
||||||
await Swal.fire({
|
await Swal.fire({
|
||||||
icon: "error",
|
icon: "error",
|
||||||
title: "Media URL failed",
|
title: "Media upload failed",
|
||||||
text: String(mres.message ?? ""),
|
text: String(mres.message ?? ""),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
revokeBlobRef(aboutBlobUrlRef);
|
||||||
|
setAboutPendingFile(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Swal.fire({ icon: "success", title: "About Us saved", timer: 1600, showConfirmButton: false });
|
await Swal.fire({ icon: "success", title: "About Us saved", timer: 1600, showConfirmButton: false });
|
||||||
|
|
@ -318,7 +374,9 @@ export default function ContentWebsite() {
|
||||||
setProductPrimary("");
|
setProductPrimary("");
|
||||||
setProductSecondary("");
|
setProductSecondary("");
|
||||||
setProductDesc("");
|
setProductDesc("");
|
||||||
setProductImgUrl("");
|
revokeBlobRef(productBlobUrlRef);
|
||||||
|
setProductPendingFile(null);
|
||||||
|
setProductRemoteUrl("");
|
||||||
setProductImageId(null);
|
setProductImageId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -327,7 +385,9 @@ export default function ContentWebsite() {
|
||||||
setProductSecondary(p.secondary_title ?? "");
|
setProductSecondary(p.secondary_title ?? "");
|
||||||
setProductDesc(p.description ?? "");
|
setProductDesc(p.description ?? "");
|
||||||
const im = p.images?.[0];
|
const im = p.images?.[0];
|
||||||
setProductImgUrl(im?.image_url?.trim() ?? "");
|
revokeBlobRef(productBlobUrlRef);
|
||||||
|
setProductPendingFile(null);
|
||||||
|
setProductRemoteUrl(im?.image_url?.trim() ?? "");
|
||||||
setProductImageId(im?.id ?? null);
|
setProductImageId(im?.id ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,15 +429,26 @@ export default function ContentWebsite() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (productImgUrl.trim() && pid) {
|
if (productPendingFile && pid) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("file", productPendingFile);
|
||||||
|
let ires;
|
||||||
if (productImageId) {
|
if (productImageId) {
|
||||||
await updateOurProductImage(productImageId, { image_url: productImgUrl.trim() });
|
ires = await updateOurProductImageUpload(productImageId, fd);
|
||||||
} else {
|
} else {
|
||||||
await saveOurProductImage({
|
fd.append("our_product_content_id", pid);
|
||||||
our_product_content_id: pid,
|
ires = await saveOurProductImageUpload(fd);
|
||||||
image_url: productImgUrl.trim(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (ires?.error) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Product image upload failed",
|
||||||
|
text: String(ires.message ?? ""),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revokeBlobRef(productBlobUrlRef);
|
||||||
|
setProductPendingFile(null);
|
||||||
}
|
}
|
||||||
await Swal.fire({ icon: "success", title: "Product saved", timer: 1400, showConfirmButton: false });
|
await Swal.fire({ icon: "success", title: "Product saved", timer: 1400, showConfirmButton: false });
|
||||||
await loadAll();
|
await loadAll();
|
||||||
|
|
@ -414,7 +485,9 @@ export default function ContentWebsite() {
|
||||||
setServicePrimary("");
|
setServicePrimary("");
|
||||||
setServiceSecondary("");
|
setServiceSecondary("");
|
||||||
setServiceDesc("");
|
setServiceDesc("");
|
||||||
setServiceImgUrl("");
|
revokeBlobRef(serviceBlobUrlRef);
|
||||||
|
setServicePendingFile(null);
|
||||||
|
setServiceRemoteUrl("");
|
||||||
setServiceImageId(null);
|
setServiceImageId(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +496,9 @@ export default function ContentWebsite() {
|
||||||
setServiceSecondary(s.secondary_title ?? "");
|
setServiceSecondary(s.secondary_title ?? "");
|
||||||
setServiceDesc(s.description ?? "");
|
setServiceDesc(s.description ?? "");
|
||||||
const im = s.images?.[0];
|
const im = s.images?.[0];
|
||||||
setServiceImgUrl(im?.image_url?.trim() ?? "");
|
revokeBlobRef(serviceBlobUrlRef);
|
||||||
|
setServicePendingFile(null);
|
||||||
|
setServiceRemoteUrl(im?.image_url?.trim() ?? "");
|
||||||
setServiceImageId(im?.id != null ? String(im.id) : null);
|
setServiceImageId(im?.id != null ? String(im.id) : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -465,15 +540,26 @@ export default function ContentWebsite() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (serviceImgUrl.trim() && sid != null) {
|
if (servicePendingFile && sid != null) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("file", servicePendingFile);
|
||||||
|
let ires;
|
||||||
if (serviceImageId) {
|
if (serviceImageId) {
|
||||||
await updateOurServiceImage(serviceImageId, { image_url: serviceImgUrl.trim() });
|
ires = await updateOurServiceImageUpload(serviceImageId, fd);
|
||||||
} else {
|
} else {
|
||||||
await saveOurServiceImage({
|
fd.append("our_service_content_id", String(sid));
|
||||||
our_service_content_id: sid,
|
ires = await saveOurServiceImageUpload(fd);
|
||||||
image_url: serviceImgUrl.trim(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (ires?.error) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Service image upload failed",
|
||||||
|
text: String(ires.message ?? ""),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revokeBlobRef(serviceBlobUrlRef);
|
||||||
|
setServicePendingFile(null);
|
||||||
}
|
}
|
||||||
await Swal.fire({ icon: "success", title: "Service saved", timer: 1400, showConfirmButton: false });
|
await Swal.fire({ icon: "success", title: "Service saved", timer: 1400, showConfirmButton: false });
|
||||||
await loadAll();
|
await loadAll();
|
||||||
|
|
@ -508,12 +594,18 @@ export default function ContentWebsite() {
|
||||||
if (!p) {
|
if (!p) {
|
||||||
setEditingPartnerId(null);
|
setEditingPartnerId(null);
|
||||||
setPartnerTitle("");
|
setPartnerTitle("");
|
||||||
setPartnerImgUrl("");
|
revokeBlobRef(partnerBlobUrlRef);
|
||||||
|
setPartnerPendingFile(null);
|
||||||
|
setPartnerRemoteUrl("");
|
||||||
|
setPartnerStoredPath("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setEditingPartnerId(p.id);
|
setEditingPartnerId(p.id);
|
||||||
setPartnerTitle(p.primary_title ?? "");
|
setPartnerTitle(p.primary_title ?? "");
|
||||||
setPartnerImgUrl(p.image_url ?? "");
|
revokeBlobRef(partnerBlobUrlRef);
|
||||||
|
setPartnerPendingFile(null);
|
||||||
|
setPartnerRemoteUrl(p.image_url ?? "");
|
||||||
|
setPartnerStoredPath(p.image_path ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPartnerModalCreate() {
|
function openPartnerModalCreate() {
|
||||||
|
|
@ -535,10 +627,14 @@ export default function ContentWebsite() {
|
||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
primary_title: partnerTitle.trim(),
|
primary_title: partnerTitle.trim(),
|
||||||
image_url: partnerImgUrl.trim() || undefined,
|
|
||||||
};
|
};
|
||||||
|
let partnerId = editingPartnerId;
|
||||||
if (editingPartnerId) {
|
if (editingPartnerId) {
|
||||||
const res = await updatePartnerContent(editingPartnerId, body);
|
const res = await updatePartnerContent(editingPartnerId, {
|
||||||
|
primary_title: body.primary_title,
|
||||||
|
image_path: partnerStoredPath,
|
||||||
|
image_url: partnerRemoteUrl,
|
||||||
|
});
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
await Swal.fire({ icon: "error", title: "Update failed", text: String(res.message ?? "") });
|
await Swal.fire({ icon: "error", title: "Update failed", text: String(res.message ?? "") });
|
||||||
return;
|
return;
|
||||||
|
|
@ -549,6 +645,23 @@ export default function ContentWebsite() {
|
||||||
await Swal.fire({ icon: "error", title: "Save failed", text: String(res.message ?? "") });
|
await Swal.fire({ icon: "error", title: "Save failed", text: String(res.message ?? "") });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const created = apiPayload(res) as CmsPartnerContent | null;
|
||||||
|
partnerId = created?.id ?? null;
|
||||||
|
}
|
||||||
|
if (partnerPendingFile && partnerId) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("file", partnerPendingFile);
|
||||||
|
const up = await uploadPartnerLogo(partnerId, fd);
|
||||||
|
if (up?.error) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Logo upload failed",
|
||||||
|
text: String(up.message ?? ""),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revokeBlobRef(partnerBlobUrlRef);
|
||||||
|
setPartnerPendingFile(null);
|
||||||
}
|
}
|
||||||
beginEditPartner(null);
|
beginEditPartner(null);
|
||||||
setPartnerModalOpen(false);
|
setPartnerModalOpen(false);
|
||||||
|
|
@ -587,7 +700,9 @@ export default function ContentWebsite() {
|
||||||
setPopupDesc("");
|
setPopupDesc("");
|
||||||
setPopupCta1("");
|
setPopupCta1("");
|
||||||
setPopupCta2("");
|
setPopupCta2("");
|
||||||
setPopupImgUrl("");
|
revokeBlobRef(popupBlobUrlRef);
|
||||||
|
setPopupPendingFile(null);
|
||||||
|
setPopupRemoteUrl("");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setPopupEditId(p.id);
|
setPopupEditId(p.id);
|
||||||
|
|
@ -596,7 +711,9 @@ export default function ContentWebsite() {
|
||||||
setPopupDesc(p.description ?? "");
|
setPopupDesc(p.description ?? "");
|
||||||
setPopupCta1(p.primary_cta ?? "");
|
setPopupCta1(p.primary_cta ?? "");
|
||||||
setPopupCta2(p.secondary_cta_text ?? "");
|
setPopupCta2(p.secondary_cta_text ?? "");
|
||||||
setPopupImgUrl(p.images?.[0]?.media_url?.trim() ?? "");
|
revokeBlobRef(popupBlobUrlRef);
|
||||||
|
setPopupPendingFile(null);
|
||||||
|
setPopupRemoteUrl(p.images?.[0]?.media_url?.trim() ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function openPopupModalCreate() {
|
function openPopupModalCreate() {
|
||||||
|
|
@ -630,10 +747,16 @@ export default function ContentWebsite() {
|
||||||
await Swal.fire({ icon: "error", title: "Save failed", text: String(res.message ?? "") });
|
await Swal.fire({ icon: "error", title: "Save failed", text: String(res.message ?? "") });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const listRes = await getPopupNewsList(1, 50);
|
const created = apiPayload(res) as CmsPopupContent | null;
|
||||||
const rows = apiRows(listRes) as CmsPopupContent[];
|
pid = created?.id ?? null;
|
||||||
pid =
|
if (pid == null) {
|
||||||
rows.length > 0 ? Math.max(...rows.map((r) => r.id)) : null;
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Save failed",
|
||||||
|
text: "Could not read new pop-up id from server.",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const res = await updatePopupNews(pid, { id: pid, ...body });
|
const res = await updatePopupNews(pid, { id: pid, ...body });
|
||||||
if (res?.error) {
|
if (res?.error) {
|
||||||
|
|
@ -641,11 +764,21 @@ export default function ContentWebsite() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (popupImgUrl.trim() && pid != null) {
|
if (popupPendingFile && pid != null) {
|
||||||
await savePopupNewsImage({
|
const fd = new FormData();
|
||||||
popup_news_content_id: pid,
|
fd.append("popup_news_content_id", String(pid));
|
||||||
media_url: popupImgUrl.trim(),
|
fd.append("file", popupPendingFile);
|
||||||
|
const imgRes = await savePopupNewsImageUpload(fd);
|
||||||
|
if (imgRes?.error) {
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "error",
|
||||||
|
title: "Popup image upload failed",
|
||||||
|
text: String(imgRes.message ?? ""),
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revokeBlobRef(popupBlobUrlRef);
|
||||||
|
setPopupPendingFile(null);
|
||||||
}
|
}
|
||||||
await Swal.fire({ icon: "success", title: "Pop up saved", timer: 1600, showConfirmButton: false });
|
await Swal.fire({ icon: "success", title: "Pop up saved", timer: 1600, showConfirmButton: false });
|
||||||
await loadAll();
|
await loadAll();
|
||||||
|
|
@ -764,25 +897,33 @@ export default function ContentWebsite() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">Hero image URL</label>
|
<label className="text-sm font-medium text-slate-700">Hero image</label>
|
||||||
<p className="mb-2 text-xs text-slate-500">
|
<p className="mb-2 text-xs text-slate-500">
|
||||||
Paste a public image URL (CDN / MinIO). Shown on the landing hero.
|
Upload a JPG, PNG, GIF, or WebP file. Stored in MinIO and shown on the landing hero.
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mt-1"
|
className="mt-1 cursor-pointer"
|
||||||
value={heroImgUrl}
|
type="file"
|
||||||
onChange={(e) => setHeroImgUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||||
placeholder="https://..."
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, heroBlobUrlRef, setHeroPendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{heroImgUrl ? (
|
{heroBlobUrlRef.current || heroRemoteUrl ? (
|
||||||
<div className="mt-4 flex justify-center rounded-xl border bg-slate-50 p-6">
|
<div className="mt-4 flex justify-center rounded-xl border bg-slate-50 p-6">
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img src={heroImgUrl} alt="" className="max-h-48 object-contain" />
|
<img
|
||||||
|
src={heroBlobUrlRef.current || heroRemoteUrl}
|
||||||
|
alt=""
|
||||||
|
className="max-h-48 object-contain"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4 flex flex-col items-center justify-center rounded-xl border-2 border-dashed bg-slate-50 p-10 text-center">
|
<div className="mt-4 flex flex-col items-center justify-center rounded-xl border-2 border-dashed bg-slate-50 p-10 text-center">
|
||||||
<ImageIcon className="mb-2 h-8 w-8 text-slate-400" />
|
<ImageIcon className="mb-2 h-8 w-8 text-slate-400" />
|
||||||
<p className="text-sm text-slate-500">No image URL yet</p>
|
<p className="text-sm text-slate-500">No hero image yet</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -843,23 +984,27 @@ export default function ContentWebsite() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">Media URL</label>
|
<label className="text-sm font-medium text-slate-700">Media (image or video)</label>
|
||||||
<p className="mb-2 text-xs text-slate-500">
|
<p className="mb-2 text-xs text-slate-500">
|
||||||
Image or video URL (e.g. .mp4 on CDN). Shown inside the phone mockup on the landing page.
|
Upload JPG/PNG/GIF/WebP or MP4/WebM. Stored in MinIO; shown inside the phone mockup on the landing page.
|
||||||
</p>
|
</p>
|
||||||
<Input
|
<Input
|
||||||
className="mt-1"
|
className="mt-1 cursor-pointer"
|
||||||
value={aboutMediaUrl}
|
type="file"
|
||||||
onChange={(e) => setAboutMediaUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp,video/mp4,video/webm"
|
||||||
placeholder="https://.../video.mp4"
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, aboutBlobUrlRef, setAboutPendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{aboutMediaUrl.trim() ? (
|
{aboutBlobUrlRef.current || aboutRemoteMediaUrl ? (
|
||||||
<div className="mt-4 flex justify-center rounded-xl border bg-slate-50 p-6">
|
<div className="mt-4 flex justify-center rounded-xl border bg-slate-50 p-6">
|
||||||
{/\.(mp4|webm)(\?|$)/i.test(aboutMediaUrl) ||
|
{aboutPendingFile?.type.startsWith("video/") ||
|
||||||
aboutMediaUrl.toLowerCase().includes("video") ? (
|
/\.(mp4|webm)(\?|$)/i.test(aboutRemoteMediaUrl) ? (
|
||||||
// eslint-disable-next-line jsx-a11y/media-has-caption
|
// eslint-disable-next-line jsx-a11y/media-has-caption
|
||||||
<video
|
<video
|
||||||
src={aboutMediaUrl}
|
src={aboutBlobUrlRef.current || aboutRemoteMediaUrl}
|
||||||
controls
|
controls
|
||||||
className="max-h-56 max-w-full rounded-lg"
|
className="max-h-56 max-w-full rounded-lg"
|
||||||
playsInline
|
playsInline
|
||||||
|
|
@ -867,7 +1012,7 @@ export default function ContentWebsite() {
|
||||||
) : (
|
) : (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img
|
<img
|
||||||
src={aboutMediaUrl}
|
src={aboutBlobUrlRef.current || aboutRemoteMediaUrl}
|
||||||
alt=""
|
alt=""
|
||||||
className="max-h-56 object-contain"
|
className="max-h-56 object-contain"
|
||||||
/>
|
/>
|
||||||
|
|
@ -876,9 +1021,44 @@ export default function ContentWebsite() {
|
||||||
) : (
|
) : (
|
||||||
<div className="mt-4 flex flex-col items-center justify-center rounded-xl border-2 border-dashed bg-slate-50 p-10 text-center">
|
<div className="mt-4 flex flex-col items-center justify-center rounded-xl border-2 border-dashed bg-slate-50 p-10 text-center">
|
||||||
<ImageIcon className="mb-2 h-8 w-8 text-slate-400" />
|
<ImageIcon className="mb-2 h-8 w-8 text-slate-400" />
|
||||||
<p className="text-sm text-slate-500">No media URL yet</p>
|
<p className="text-sm text-slate-500">No media yet</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{aboutMediaImageId != null ? (
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="mt-3"
|
||||||
|
onClick={async () => {
|
||||||
|
const ok = await Swal.fire({
|
||||||
|
icon: "question",
|
||||||
|
title: "Remove current media?",
|
||||||
|
showCancelButton: true,
|
||||||
|
});
|
||||||
|
if (!ok.isConfirmed) return;
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
await deleteAboutUsContentImage(aboutMediaImageId);
|
||||||
|
revokeBlobRef(aboutBlobUrlRef);
|
||||||
|
setAboutPendingFile(null);
|
||||||
|
setAboutRemoteMediaUrl("");
|
||||||
|
setAboutMediaImageId(null);
|
||||||
|
await Swal.fire({
|
||||||
|
icon: "success",
|
||||||
|
title: "Media removed",
|
||||||
|
timer: 1200,
|
||||||
|
showConfirmButton: false,
|
||||||
|
});
|
||||||
|
await loadAll();
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove current media
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
|
|
@ -1028,13 +1208,27 @@ export default function ContentWebsite() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">Card image URL</label>
|
<label className="text-sm font-medium text-slate-700">Card image</label>
|
||||||
<Input
|
<Input
|
||||||
className="mt-2"
|
className="mt-2 cursor-pointer"
|
||||||
value={productImgUrl}
|
type="file"
|
||||||
onChange={(e) => setProductImgUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||||
placeholder="https://..."
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, productBlobUrlRef, setProductPendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{productBlobUrlRef.current || productRemoteUrl ? (
|
||||||
|
<div className="mt-3 flex justify-center rounded-lg border bg-slate-50 p-3">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={productBlobUrlRef.current || productRemoteUrl}
|
||||||
|
alt=""
|
||||||
|
className="max-h-36 object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
|
@ -1179,13 +1373,27 @@ export default function ContentWebsite() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">Banner image URL</label>
|
<label className="text-sm font-medium text-slate-700">Banner image</label>
|
||||||
<Input
|
<Input
|
||||||
className="mt-2"
|
className="mt-2 cursor-pointer"
|
||||||
value={serviceImgUrl}
|
type="file"
|
||||||
onChange={(e) => setServiceImgUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||||
placeholder="https://..."
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, serviceBlobUrlRef, setServicePendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{serviceBlobUrlRef.current || serviceRemoteUrl ? (
|
||||||
|
<div className="mt-3 flex justify-center rounded-lg border bg-slate-50 p-3">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={serviceBlobUrlRef.current || serviceRemoteUrl}
|
||||||
|
alt=""
|
||||||
|
className="max-h-36 object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
|
@ -1302,7 +1510,7 @@ export default function ContentWebsite() {
|
||||||
{editingPartnerId ? "Edit partner" : "Add Partner"}
|
{editingPartnerId ? "Edit partner" : "Add Partner"}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Partner name and logo URL are shown on the homepage technology partners section.
|
Partner name and logo are shown on the homepage technology partners section. Logo is stored in MinIO.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid gap-4 py-2">
|
<div className="grid gap-4 py-2">
|
||||||
|
|
@ -1315,13 +1523,27 @@ export default function ContentWebsite() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">Logo image URL</label>
|
<label className="text-sm font-medium text-slate-700">Logo image</label>
|
||||||
<Input
|
<Input
|
||||||
className="mt-2"
|
className="mt-2 cursor-pointer"
|
||||||
value={partnerImgUrl}
|
type="file"
|
||||||
onChange={(e) => setPartnerImgUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||||
placeholder="https://..."
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, partnerBlobUrlRef, setPartnerPendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{partnerBlobUrlRef.current || partnerRemoteUrl ? (
|
||||||
|
<div className="mt-3 flex justify-center rounded-lg border bg-slate-50 p-4">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={partnerBlobUrlRef.current || partnerRemoteUrl}
|
||||||
|
alt=""
|
||||||
|
className="max-h-24 max-w-full object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
|
@ -1483,14 +1705,28 @@ export default function ContentWebsite() {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-sm font-medium text-slate-700">
|
<label className="text-sm font-medium text-slate-700">
|
||||||
Banner image URL (optional)
|
Banner image (optional)
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
className="mt-2"
|
className="mt-2 cursor-pointer"
|
||||||
value={popupImgUrl}
|
type="file"
|
||||||
onChange={(e) => setPopupImgUrl(e.target.value)}
|
accept="image/jpeg,image/png,image/gif,image/webp"
|
||||||
placeholder="https://..."
|
onChange={(e) => {
|
||||||
|
const f = e.target.files?.[0] ?? null;
|
||||||
|
setPickedFile(f, popupBlobUrlRef, setPopupPendingFile);
|
||||||
|
e.target.value = "";
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
{popupBlobUrlRef.current || popupRemoteUrl ? (
|
||||||
|
<div className="mt-3 flex justify-center rounded-lg border bg-slate-50 p-3">
|
||||||
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
|
<img
|
||||||
|
src={popupBlobUrlRef.current || popupRemoteUrl}
|
||||||
|
alt=""
|
||||||
|
className="max-h-36 object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DialogFooter className="gap-2 sm:gap-0">
|
<DialogFooter className="gap-2 sm:gap-0">
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
import {
|
import {
|
||||||
httpDeleteInterceptor,
|
httpDeleteInterceptor,
|
||||||
httpGetInterceptor,
|
httpGetInterceptor,
|
||||||
|
httpPostFormDataInterceptor,
|
||||||
httpPostInterceptor,
|
httpPostInterceptor,
|
||||||
|
httpPutFormDataInterceptor,
|
||||||
httpPutInterceptor,
|
httpPutInterceptor,
|
||||||
} from "./http-config/http-interceptor-services";
|
} from "./http-config/http-interceptor-services";
|
||||||
|
|
||||||
|
|
@ -66,6 +68,11 @@ export async function saveHeroImage(body: {
|
||||||
return await httpPostInterceptor("/hero-content-images", body);
|
return await httpPostInterceptor("/hero-content-images", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: fields `hero_content_id`, `file` */
|
||||||
|
export async function saveHeroImageUpload(formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor("/hero-content-images", formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateHeroImage(
|
export async function updateHeroImage(
|
||||||
id: string,
|
id: string,
|
||||||
body: { image_path?: string; image_url?: string },
|
body: { image_path?: string; image_url?: string },
|
||||||
|
|
@ -73,6 +80,11 @@ export async function updateHeroImage(
|
||||||
return await httpPutInterceptor(`/hero-content-images/${id}`, body);
|
return await httpPutInterceptor(`/hero-content-images/${id}`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: field `file` */
|
||||||
|
export async function updateHeroImageUpload(id: string, formData: FormData) {
|
||||||
|
return await httpPutFormDataInterceptor(`/hero-content-images/${id}`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAboutContentsList() {
|
export async function getAboutContentsList() {
|
||||||
return await httpGetInterceptor("/about-us-contents");
|
return await httpGetInterceptor("/about-us-contents");
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +108,11 @@ export async function saveAboutUsMediaUrl(body: {
|
||||||
return await httpPostInterceptor("/about-us-content-images/url", body);
|
return await httpPostInterceptor("/about-us-content-images/url", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: fields `about_us_content_id`, `file` (image or mp4/webm) */
|
||||||
|
export async function saveAboutUsMediaUpload(formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor("/about-us-content-images", formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteAboutUsContentImage(id: number) {
|
export async function deleteAboutUsContentImage(id: number) {
|
||||||
return await httpDeleteInterceptor(`/about-us-content-images/${id}`);
|
return await httpDeleteInterceptor(`/about-us-content-images/${id}`);
|
||||||
}
|
}
|
||||||
|
|
@ -141,6 +158,11 @@ export async function saveOurProductImage(body: {
|
||||||
return await httpPostInterceptor("/our-product-content-images", body);
|
return await httpPostInterceptor("/our-product-content-images", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: fields `our_product_content_id`, `file` */
|
||||||
|
export async function saveOurProductImageUpload(formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor("/our-product-content-images", formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateOurProductImage(
|
export async function updateOurProductImage(
|
||||||
id: string,
|
id: string,
|
||||||
body: { image_path?: string; image_url?: string },
|
body: { image_path?: string; image_url?: string },
|
||||||
|
|
@ -148,6 +170,11 @@ export async function updateOurProductImage(
|
||||||
return await httpPutInterceptor(`/our-product-content-images/${id}`, body);
|
return await httpPutInterceptor(`/our-product-content-images/${id}`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: field `file` */
|
||||||
|
export async function updateOurProductImageUpload(id: string, formData: FormData) {
|
||||||
|
return await httpPutFormDataInterceptor(`/our-product-content-images/${id}`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOurServiceContent() {
|
export async function getOurServiceContent() {
|
||||||
return await httpGetInterceptor("/our-service-contents");
|
return await httpGetInterceptor("/our-service-contents");
|
||||||
}
|
}
|
||||||
|
|
@ -189,6 +216,11 @@ export async function saveOurServiceImage(body: {
|
||||||
return await httpPostInterceptor("/our-service-content-images", body);
|
return await httpPostInterceptor("/our-service-content-images", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: fields `our_service_content_id`, `file` */
|
||||||
|
export async function saveOurServiceImageUpload(formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor("/our-service-content-images", formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateOurServiceImage(
|
export async function updateOurServiceImage(
|
||||||
id: string,
|
id: string,
|
||||||
body: { image_path?: string; image_url?: string },
|
body: { image_path?: string; image_url?: string },
|
||||||
|
|
@ -196,6 +228,11 @@ export async function updateOurServiceImage(
|
||||||
return await httpPutInterceptor(`/our-service-content-images/${id}`, body);
|
return await httpPutInterceptor(`/our-service-content-images/${id}`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: field `file` */
|
||||||
|
export async function updateOurServiceImageUpload(id: string, formData: FormData) {
|
||||||
|
return await httpPutFormDataInterceptor(`/our-service-content-images/${id}`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPartnerContents() {
|
export async function getPartnerContents() {
|
||||||
return await httpGetInterceptor("/partner-contents");
|
return await httpGetInterceptor("/partner-contents");
|
||||||
}
|
}
|
||||||
|
|
@ -219,6 +256,11 @@ export async function updatePartnerContent(
|
||||||
return await httpPutInterceptor(`/partner-contents/${id}`, body);
|
return await httpPutInterceptor(`/partner-contents/${id}`, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: field `file` — updates logo in MinIO */
|
||||||
|
export async function uploadPartnerLogo(id: string, formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor(`/partner-contents/${id}/logo`, formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deletePartnerContent(id: string) {
|
export async function deletePartnerContent(id: string) {
|
||||||
return await httpDeleteInterceptor(`/partner-contents/${id}`);
|
return await httpDeleteInterceptor(`/partner-contents/${id}`);
|
||||||
}
|
}
|
||||||
|
|
@ -265,6 +307,11 @@ export async function savePopupNewsImage(body: {
|
||||||
return await httpPostInterceptor("/popup-news-content-images", body);
|
return await httpPostInterceptor("/popup-news-content-images", body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Multipart: fields `popup_news_content_id`, `file`; optional `is_thumbnail` */
|
||||||
|
export async function savePopupNewsImageUpload(formData: FormData) {
|
||||||
|
return await httpPostFormDataInterceptor("/popup-news-content-images", formData);
|
||||||
|
}
|
||||||
|
|
||||||
export async function deletePopupNews(id: number) {
|
export async function deletePopupNews(id: number) {
|
||||||
return await httpDeleteInterceptor(`/popup-news-contents/${id}`);
|
return await httpDeleteInterceptor(`/popup-news-contents/${id}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,10 @@ const axiosInterceptorInstance = axios.create({
|
||||||
// Request interceptor
|
// Request interceptor
|
||||||
axiosInterceptorInstance.interceptors.request.use(
|
axiosInterceptorInstance.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
|
// Default instance uses application/json; FormData must not use JSON or File becomes {}.
|
||||||
|
if (config.data instanceof FormData) {
|
||||||
|
config.headers.delete("Content-Type");
|
||||||
|
}
|
||||||
console.log("Config interceptor : ", config);
|
console.log("Config interceptor : ", config);
|
||||||
const accessToken = Cookies.get("access_token");
|
const accessToken = Cookies.get("access_token");
|
||||||
if (accessToken) {
|
if (accessToken) {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,74 @@ export async function httpGetInterceptor(pathUrl: any) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clientKeyHeaders = {
|
||||||
|
"X-Client-Key": defaultHeaders["X-Client-Key"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/** POST multipart (do not set Content-Type — browser sets boundary). */
|
||||||
|
export async function httpPostFormDataInterceptor(pathUrl: string, formData: FormData) {
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.csrf_token;
|
||||||
|
|
||||||
|
const mergedHeaders: Record<string, string> = {
|
||||||
|
...clientKeyHeaders,
|
||||||
|
...(csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axiosInterceptorInstance
|
||||||
|
.post(pathUrl, formData, { 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 if (response?.status == 401) {
|
||||||
|
Cookies.set("is_logout", "true");
|
||||||
|
window.location.href = "/";
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: response?.data?.message || response?.data || null,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** PUT multipart (do not set Content-Type — browser sets boundary). */
|
||||||
|
export async function httpPutFormDataInterceptor(pathUrl: string, formData: FormData) {
|
||||||
|
const resCsrf = await getCsrfToken();
|
||||||
|
const csrfToken = resCsrf?.data?.csrf_token;
|
||||||
|
|
||||||
|
const mergedHeaders: Record<string, string> = {
|
||||||
|
...clientKeyHeaders,
|
||||||
|
...(csrfToken ? { "X-CSRF-TOKEN": csrfToken } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await axiosInterceptorInstance
|
||||||
|
.put(pathUrl, formData, { 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 if (response?.status == 401) {
|
||||||
|
Cookies.set("is_logout", "true");
|
||||||
|
window.location.href = "/";
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
error: true,
|
||||||
|
message: response?.data?.message || response?.data || null,
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function httpPostInterceptor(
|
export async function httpPostInterceptor(
|
||||||
pathUrl: any,
|
pathUrl: any,
|
||||||
data: any,
|
data: any,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue