From 3c4f8317037faeac9ff0da5b9aa966e436c7b6c4 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Thu, 2 Oct 2025 05:20:12 +0700 Subject: [PATCH 1/2] feat: update approval article --- .../components/pending-approval-columns.tsx | 4 ++-- .../components/pending-approval-columns.tsx | 4 ++-- .../components/pending-approval-columns.tsx | 4 ++-- .../components/pending-approval-columns.tsx | 4 ++-- .../audio-visual/video-detail-form.tsx | 20 ++++-------------- .../form/content/audio/audio-detail-form.tsx | 21 ++++--------------- .../content/document/teks-detail-form.tsx | 19 +++-------------- .../form/content/image/image-detail-form.tsx | 21 +++---------------- next.config.mjs | 1 + service/content/content.ts | 4 ++-- service/landing/landing.ts | 2 +- 11 files changed, 26 insertions(+), 78 deletions(-) diff --git a/app/[locale]/(admin)/admin/content/audio-visual/components/pending-approval-columns.tsx b/app/[locale]/(admin)/admin/content/audio-visual/components/pending-approval-columns.tsx index 43ea171..d1d00d7 100644 --- a/app/[locale]/(admin)/admin/content/audio-visual/components/pending-approval-columns.tsx +++ b/app/[locale]/(admin)/admin/content/audio-visual/components/pending-approval-columns.tsx @@ -186,7 +186,7 @@ const usePendingApprovalColumns = () => { View - {canApprove && ( + {/* {canApprove && ( { @@ -197,7 +197,7 @@ const usePendingApprovalColumns = () => { Approve - )} + )} */} ); diff --git a/app/[locale]/(admin)/admin/content/audio/components/pending-approval-columns.tsx b/app/[locale]/(admin)/admin/content/audio/components/pending-approval-columns.tsx index 7f66720..5a1baa6 100644 --- a/app/[locale]/(admin)/admin/content/audio/components/pending-approval-columns.tsx +++ b/app/[locale]/(admin)/admin/content/audio/components/pending-approval-columns.tsx @@ -186,7 +186,7 @@ const usePendingApprovalColumns = () => { View - {canApprove && ( + {/* {canApprove && ( { @@ -197,7 +197,7 @@ const usePendingApprovalColumns = () => { Approve - )} + )} */} ); diff --git a/app/[locale]/(admin)/admin/content/document/components/pending-approval-columns.tsx b/app/[locale]/(admin)/admin/content/document/components/pending-approval-columns.tsx index 8362ba4..200fee8 100644 --- a/app/[locale]/(admin)/admin/content/document/components/pending-approval-columns.tsx +++ b/app/[locale]/(admin)/admin/content/document/components/pending-approval-columns.tsx @@ -186,7 +186,7 @@ const usePendingApprovalColumns = () => { View - {canApprove && ( + {/* {canApprove && ( { @@ -197,7 +197,7 @@ const usePendingApprovalColumns = () => { Approve - )} + )} */} ); diff --git a/app/[locale]/(admin)/admin/content/image/components/pending-approval-columns.tsx b/app/[locale]/(admin)/admin/content/image/components/pending-approval-columns.tsx index 9ebe593..317536f 100644 --- a/app/[locale]/(admin)/admin/content/image/components/pending-approval-columns.tsx +++ b/app/[locale]/(admin)/admin/content/image/components/pending-approval-columns.tsx @@ -192,7 +192,7 @@ const usePendingApprovalColumns = () => { View - {canApprove && ( + {/* {canApprove && ( { @@ -203,7 +203,7 @@ const usePendingApprovalColumns = () => { Approve - )} + )} */} ); diff --git a/components/form/content/audio-visual/video-detail-form.tsx b/components/form/content/audio-visual/video-detail-form.tsx index 99a0d1c..c0cdc18 100644 --- a/components/form/content/audio-visual/video-detail-form.tsx +++ b/components/form/content/audio-visual/video-detail-form.tsx @@ -294,31 +294,19 @@ export default function FormVideoDetail() { async function save() { const data = { - mediaUploadId: id, - statusId: status, + action: status == "2" ? "approve" : status == "3" ? "revision" : "reject", message: description, - files: isUserMabesApprover ? getPlacement() : [], }; + setModalOpen(false); loading(); - const response = await submitApproval(data); + const response = await submitApproval(id, data); if (response?.error) { error(response.message); return false; } - const dataReject = { - listFiles: rejectedFiles, - }; - - const resReject = await rejectFiles(dataReject); - - if (resReject?.error) { - error(resReject.message); - return false; - } - close(); submitApprovalSuccesss(); @@ -403,7 +391,7 @@ export default function FormVideoDetail() { confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { - router.push("/contributor/content/video"); + router.push("/admin/content/video"); } }); }; diff --git a/components/form/content/audio/audio-detail-form.tsx b/components/form/content/audio/audio-detail-form.tsx index c8847b7..5340472 100644 --- a/components/form/content/audio/audio-detail-form.tsx +++ b/components/form/content/audio/audio-detail-form.tsx @@ -366,32 +366,19 @@ export default function FormAudioDetail() { async function save() { const data = { - mediaUploadId: id, - statusId: status, + action: status == "2" ? "approve" : status == "3" ? "revision" : "reject", message: description, - // files: [], - files: isUserMabesApprover ? getPlacement() : [], }; + setModalOpen(false); loading(); - const response = await submitApproval(data); + const response = await submitApproval(id, data); if (response?.error) { error(response.message); return false; } - const dataReject = { - listFiles: rejectedFiles, - }; - - const resReject = await rejectFiles(dataReject); - - if (resReject?.error) { - error(resReject.message); - return false; - } - close(); submitApprovalSuccesss(); @@ -471,7 +458,7 @@ export default function FormAudioDetail() { confirmButtonColor: "#3085d6", confirmButtonText: "OK", }).then(() => { - router.push("/in/contributor/content/audio"); + router.push("/admin/content/audio"); }); }; diff --git a/components/form/content/document/teks-detail-form.tsx b/components/form/content/document/teks-detail-form.tsx index c02e616..b6df303 100644 --- a/components/form/content/document/teks-detail-form.tsx +++ b/components/form/content/document/teks-detail-form.tsx @@ -321,32 +321,19 @@ export default function FormTeksDetail() { async function save() { const data = { - mediaUploadId: id, - statusId: status, + action: status == "2" ? "approve" : status == "3" ? "revision" : "reject", message: description, - files: isUserMabesApprover ? getPlacement() : [], }; - setModalOpen(false); + setModalOpen(false); loading(); - const response = await submitApproval(data); + const response = await submitApproval(id, data); if (response?.error) { error(response.message); return false; } - const dataReject = { - listFiles: rejectedFiles, - }; - - const resReject = await rejectFiles(dataReject); - - if (resReject?.error) { - error(resReject.message); - return false; - } - close(); submitApprovalSuccesss(); diff --git a/components/form/content/image/image-detail-form.tsx b/components/form/content/image/image-detail-form.tsx index 9837565..1cec85e 100644 --- a/components/form/content/image/image-detail-form.tsx +++ b/components/form/content/image/image-detail-form.tsx @@ -452,34 +452,19 @@ export default function FormImageDetail() { async function save() { const data = { - mediaUploadId: id, - statusId: status, + action: status == "2" ? "approve" : status == "3" ? "revision" : "reject", message: description, - files: isUserMabesApprover ? getPlacement() : [], }; setModalOpen(false); loading(); - const response = await submitApproval(data); + const response = await submitApproval(id, data); if (response?.error) { error(response.message); return false; } - const dataReject = { - listFiles: rejectedFiles, - }; - - console.log("reject", dataReject); - - const resReject = await rejectFiles(dataReject); - - if (resReject?.error) { - error(resReject.message); - return false; - } - close(); submitApprovalSuccesss(); @@ -551,7 +536,7 @@ export default function FormImageDetail() { confirmButtonText: "OK", }).then((result) => { if (result.isConfirmed) { - router.push("/contributor/content/image"); + router.push("/admin/content/image"); } }); }; diff --git a/next.config.mjs b/next.config.mjs index 9b9d832..0c93552 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -97,6 +97,7 @@ const nextConfig = { { protocol: "https", hostname: "avatars.githubusercontent.com" }, { protocol: "https", hostname: "i.pravatar.cc" }, { protocol: "https", hostname: "dev.mikulnews.com" }, + { protocol: "https", hostname: "kontenhumas.com" }, { protocol: "https", hostname: "netidhub.com" }, ], }, diff --git a/service/content/content.ts b/service/content/content.ts index 1699654..aeb9483 100644 --- a/service/content/content.ts +++ b/service/content/content.ts @@ -285,8 +285,8 @@ export async function convertSPIT(data: any) { return httpPostInterceptor(url, data); } -export async function submitApproval(data: any) { - const url = "media/approval"; +export async function submitApproval(id: string, data: any) { + const url = `article-approval-flows/articles/${id}/approve`; return httpPostInterceptor(url, data); } diff --git a/service/landing/landing.ts b/service/landing/landing.ts index c6075f4..e7a3055 100644 --- a/service/landing/landing.ts +++ b/service/landing/landing.ts @@ -176,7 +176,7 @@ export async function listArticles( categoryId?: string, sortBy = "createdAt" ) { - let url = `articles?page=${page}&totalPage=${totalPage}`; + let url = `articles?page=${page}&totalPage=${totalPage}&isPublish=true`; if (typeId !== undefined) url += `&typeId=${typeId}`; if (search) url += `&title=${encodeURIComponent(search)}`; From 2527870af142644ff6cff569799257d79fc63d14 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Thu, 2 Oct 2025 05:48:26 +0700 Subject: [PATCH 2/2] feat: update users --- components/form/sign-up.tsx | 250 ++++++++++++++++++++++++++++++------ service/auth.ts | 5 + 2 files changed, 216 insertions(+), 39 deletions(-) diff --git a/components/form/sign-up.tsx b/components/form/sign-up.tsx index e4544ed..0990aed 100644 --- a/components/form/sign-up.tsx +++ b/components/form/sign-up.tsx @@ -16,6 +16,7 @@ import Swal from "sweetalert2"; import { error } from "console"; import { EyeFilledIcon, EyeSlashFilledIcon } from "../icons"; import { RadioGroup, RadioGroupItem } from "../ui/radio-group"; +import { registerTenant } from "@/service/auth"; export default function SignUp() { const router = useRouter(); @@ -76,10 +77,19 @@ export default function SignUp() { const [kategoriPerusahaan, setKategoriPerusahaan] = useState(""); const [kontributorPassword, setKontributorPassword] = useState(""); const [confirmKontributorPassword, setConfirmKontributorPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [formErrors, setFormErrors] = useState({}); const handleSendOtp = (e: React.FormEvent) => { e.preventDefault(); - // Kirim OTP ke email + + // If role is tenant, handle tenant registration directly + if (role === "tenant") { + handleTenantRegistration(e); + return; + } + + // For other roles, proceed with OTP flow setStep("otp"); }; @@ -100,6 +110,124 @@ export default function SignUp() { if (value && nextInput) nextInput.focus(); }; + // Form validation functions + const validateEmail = (email: string) => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; + + const validatePhone = (phone: string) => { + const phoneRegex = /^[0-9+\-\s()]+$/; + return phoneRegex.test(phone) && phone.length >= 10; + }; + + const validatePassword = (password: string) => { + return password.length >= 8; + }; + + const validateTenantForm = () => { + const errors: any = {}; + + if (!firstName.trim()) { + errors.firstName = "First name is required"; + } + + if (!lastName.trim()) { + errors.lastName = "Last name is required"; + } + + if (!email.trim()) { + errors.email = "Email is required"; + } else if (!validateEmail(email)) { + errors.email = "Please enter a valid email address"; + } + + if (!whatsapp.trim()) { + errors.whatsapp = "WhatsApp number is required"; + } else if (!validatePhone(whatsapp)) { + errors.whatsapp = "Please enter a valid phone number"; + } + + if (!namaTenant.trim()) { + errors.namaTenant = "Tenant name is required"; + } + + if (!tenantPassword) { + errors.tenantPassword = "Password is required"; + } else if (!validatePassword(tenantPassword)) { + errors.tenantPassword = "Password must be at least 8 characters"; + } + + if (!confirmTenantPassword) { + errors.confirmTenantPassword = "Please confirm your password"; + } else if (tenantPassword !== confirmTenantPassword) { + errors.confirmTenantPassword = "Passwords do not match"; + } + + setFormErrors(errors); + return Object.keys(errors).length === 0; + }; + + const handleTenantRegistration = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateTenantForm()) { + return; + } + + setIsLoading(true); + setFormErrors({}); + + try { + const registrationData = { + adminUser: { + address: "Jakarta", // Default address as per API requirement + email: email, + fullname: `${firstName} ${lastName}`, + password: tenantPassword, + phoneNumber: whatsapp, + username: `${firstName}-${lastName}` // Using firstName + lastName as username + }, + client: { + clientType: "sub_client", // Hardcoded as per API requirement + name: namaTenant, + parentClientId: "78356d32-52fa-4dfc-b836-6cebf4e3eead" // Hardcoded as per API requirement + } + }; + + const response = await registerTenant(registrationData); + + if (response.error) { + MySwal.fire({ + title: "Registration Failed", + text: response.message || "An error occurred during registration", + icon: "error", + confirmButtonText: "OK" + }); + } else { + MySwal.fire({ + title: "Registration Successful", + text: "Your tenant account has been created successfully!", + icon: "success", + confirmButtonText: "OK" + }).then(() => { + // Redirect to login page or dashboard + router.push("/auth"); + }); + } + } catch (error) { + console.error("Registration error:", error); + MySwal.fire({ + title: "Registration Failed", + text: "An unexpected error occurred. Please try again.", + icon: "error", + confirmButtonText: "OK" + }); + } finally { + setIsLoading(false); + } + }; + return (
{/* Left Side - Logo Section */} @@ -318,45 +446,75 @@ export default function SignUp() { {role === "tenant" && (
- setFirstName(e.target.value)} - /> - setLastName(e.target.value)} - /> +
+ setFirstName(e.target.value)} + className={formErrors.firstName ? "border-red-500" : ""} + /> + {formErrors.firstName && ( +

{formErrors.firstName}

+ )} +
+
+ setLastName(e.target.value)} + className={formErrors.lastName ? "border-red-500" : ""} + /> + {formErrors.lastName && ( +

{formErrors.lastName}

+ )} +
- setEmail(e.target.value)} - /> +
+ setEmail(e.target.value)} + className={formErrors.email ? "border-red-500" : ""} + /> + {formErrors.email && ( +

{formErrors.email}

+ )} +
- setWhatsapp(e.target.value)} - /> +
+ setWhatsapp(e.target.value)} + className={formErrors.whatsapp ? "border-red-500" : ""} + /> + {formErrors.whatsapp && ( +

{formErrors.whatsapp}

+ )} +
- setNamaTenant(e.target.value)} - /> +
+ setNamaTenant(e.target.value)} + className={formErrors.namaTenant ? "border-red-500" : ""} + /> + {formErrors.namaTenant && ( +

{formErrors.namaTenant}

+ )} +
setTenantPassword(e.target.value)} - className="pr-10" + className={`pr-10 ${formErrors.tenantPassword ? "border-red-500" : ""}`} /> + {formErrors.tenantPassword && ( +

{formErrors.tenantPassword}

+ )}
@@ -387,7 +548,7 @@ export default function SignUp() { placeholder="Konfirmasi Password" value={confirmTenantPassword} onChange={(e) => setConfirmTenantPassword(e.target.value)} - className="pr-10" + className={`pr-10 ${formErrors.confirmTenantPassword ? "border-red-500" : ""}`} /> + {formErrors.confirmTenantPassword && ( +

{formErrors.confirmTenantPassword}

+ )}
)} @@ -436,8 +600,16 @@ export default function SignUp() { {/* Link Login */} diff --git a/service/auth.ts b/service/auth.ts index 0492ddf..818e01d 100644 --- a/service/auth.ts +++ b/service/auth.ts @@ -173,3 +173,8 @@ export async function getDataPersonil(nrp: any) { const url = `public/users/search-personil?nrp=${nrp}`; return httpGetInterceptor(url); } + +export async function registerTenant(data: any) { + const url = "clients/with-user"; + return httpPost(url, data); +} \ No newline at end of file