# QC Summary - Profil Landing Page Module **Scope:** Media Sosial, Pejabat Desa, Program Inovasi **Date:** 2026-02-23 **Status:** βœ… Secara umum sudah baik, ada beberapa improvement minor --- ## πŸ“Š OVERVIEW | Module | Schema | API | UI Admin | Public Page | Overall | |--------|--------|-----|----------|-------------|---------| | Media Sosial | βœ… Baik | βœ… Baik | βœ… Baik | N/A | 🟒 Baik | | Pejabat Desa | βœ… Baik | ⚠️ Ada issue | βœ… Baik | N/A | 🟑 Perlu fix | | Program Inovasi | βœ… Baik | βœ… Baik | βœ… Baik | N/A | 🟒 Baik | --- ## βœ… YANG SUDAH BAIK (COMMON) ### **1. Konsistensi UI/UX** - βœ… Semua halaman menggunakan pattern yang sama (list β†’ detail β†’ edit) - βœ… Responsive design (desktop table + mobile cards) - βœ… Loading states dengan Skeleton - βœ… Empty state handling yang informatif - βœ… Search dengan debounce (1000ms) - βœ… Pagination konsisten di semua modul ### **2. File Upload Handling** - βœ… Dropzone dengan preview image - βœ… Validasi format & ukuran file (max 5MB) - βœ… Tombol hapus preview (IconX di pojok kanan atas) - βœ… URL.createObjectURL untuk preview lokal - βœ… Cleanup file state saat reset form ### **3. Form Validation** - βœ… Zod schema untuk validasi typed - βœ… isFormValid() check sebelum submit - βœ… Error toast dengan pesan spesifik - βœ… Button disabled saat invalid/loading ### **4. State Management (Valtio)** - βœ… Proxy state untuk reaktivitas - βœ… Separate state per modul (programInovasi, pejabatDesa, mediaSosial) - βœ… Reset form function di setiap create/edit - βœ… Original data tracking untuk reset ### **5. Error Handling** - βœ… Try-catch di semua async operation - βœ… Toast error dengan pesan user-friendly - βœ… Console.error untuk debugging - βœ… Modal konfirmasi hapus --- ## ⚠️ ISSUES & SARAN PERBAIKAN ### **πŸ”΄ CRITICAL** #### **1. Pejabat Desa - Edit Form Tidak Reset imageId ke Original** **Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/pejabat-desa/[id]/edit/page.tsx` **Masalah:** ```typescript // Line ~100 - Load data setFormData({ name: profileData.name || "", position: profileData.position || "", imageId: profileData.imageId || "", // βœ… Sudah benar }); // Line ~170 - Handle reset setFormData({ name: originalData.name, position: originalData.position, imageId: originalData.imageId, // βœ… Sudah benar }); ``` **Status:** βœ… **SUDAH BENAR** - Tidak ada issue di sini **Verdict:** Tidak ada action needed. --- #### **2. Media Sosial - Edit Form Sudah Benar** **Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx` **Verdict:** βœ… **SUDAH BENAR** - Original data tracking sudah implementasi dengan baik: ```typescript const [originalData, setOriginalData] = useState({ name: '', icon: '', iconUrl: '', imageId: '', imageUrl: '', }); // Load data setOriginalData({ ...newForm, imageUrl: data.image?.link || '', }); // Reset form setFormData({ name: originalData.name, icon: originalData.icon, iconUrl: originalData.iconUrl, imageId: originalData.imageId, }); ``` **Verdict:** Tidak ada action needed. --- #### **3. Program Inovasi - Edit Form Sudah Benar** **Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/edit/page.tsx` **Verdict:** βœ… **SUDAH BENAR** - Original data tracking sudah implementasi dengan baik. **Verdict:** Tidak ada action needed. --- ### **🟑 MEDIUM** #### **4. Inconsistency: Fetch Method di State** **Lokasi:** `src/app/admin/(dashboard)/_state/landing-page/profile.ts` **Masalah:** Ada 3 pattern berbeda untuk fetch API: ```typescript // ❌ Pattern 1: ApiFetch (programInovasi.create) const res = await ApiFetch.api.landingpage.programinovasi["create"].post(formData); // ❌ Pattern 2: fetch manual (programInovasi.findUnique) const res = await fetch(`/api/landingpage/programinovasi/${id}`); // ❌ Pattern 3: fetch dengan headers (programInovasi.update) const response = await fetch(`/api/landingpage/programinovasi/${this.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({...}), }); // ❌ Pattern 4: fetch dengan delete (programInovasi.delete) const response = await fetch(`/api/landingpage/programinovasi/del/${id}`, { method: "DELETE", ... }); ``` **Dampak:** - Code consistency buruk - Sulit maintenance - Type safety tidak konsisten **Rekomendasi:** Gunakan **ApiFetch** untuk semua operasi: ```typescript // βœ…η»ŸδΈ€ pattern const res = await ApiFetch.api.landingpage.programinovasi["create"].post(formData); const res = await ApiFetch.api.landingpage.programinovasi[id].get(); const res = await ApiFetch.api.landingpage.programinovasi[id].put(data); const res = await ApiFetch.api.landingpage.programinovasi["del"][id].delete(); ``` **Priority:** 🟑 Medium **Effort:** Low (refactor saja, tidak ada logic change) --- #### **5. Media Sosial - Validasi IconUrl Tidak Selalu Relevan** **Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx` **Masalah:** ```typescript // Line ~67 const isFormValid = () => { const isNameValid = stateMediaSosial.create.form.name?.trim() !== ''; const isIconUrlValid = stateMediaSosial.create.form.iconUrl?.trim() !== ''; // ❌ Selalu required const isCustomIconValid = selectedSosmed !== 'custom' || file !== null; return isNameValid && isIconUrlValid && isCustomIconValid; }; ``` **Scenario:** - User pilih icon "telephone" β†’ iconUrl **seharusnya** required (nomor telepon) - User pilih icon "facebook" β†’ iconUrl **seharusnya** required (URL profile) - Tapi jika user hanya mau tampil icon tanpa link β†’ **tidak bisa** **Rekomendasi:** Jadikan optional atau berikan default value: ```typescript const isFormValid = () => { const isNameValid = stateMediaSosial.create.form.name?.trim() !== ''; // IconUrl optional, atau validasi berdasarkan selectedSosmed const isIconUrlValid = true; // atau validasi spesifik const isCustomIconValid = selectedSosmed !== 'custom' || file !== null; return isNameValid && isCustomIconValid; }; ``` **Priority:** 🟑 Medium **Effort:** Low --- #### **6. Pejabat Desa - Hanya Ada 1 Data (Hardcoded ID "edit")** **Lokasi:** `src/app/admin/(dashboard)/landing-page/profil/pejabat-desa/page.tsx` **Masalah:** ```typescript // Line ~17 useShallowEffect(() => { allList.findUnique.load("edit"); // ❌ Hardcoded ID }, []); ``` **Dampak:** - Tidak scalable jika nanti ada multiple pejabat desa - Pattern berbeda dari modul lain (yang pakai findMany) - Confusing untuk developer baru **Rekomendasi:** - Jika memang hanya 1 data, tambahkan komentar: ```typescript // Note: "edit" adalah special ID untuk single pejabat desa record // Backend akan return data pertama jika ID tidak ditemukan allList.findUnique.load("edit"); ``` - Atau gunakan pattern yang lebih clear: ```typescript allList.findUnique.load("single"); // atau "default" ``` **Priority:** 🟑 Low-Medium **Effort:** Low --- #### **7. Program Inovasi - HTML Injection Risk di Deskripsi** **Lokasi:** - `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/page.tsx` (line ~107) - `src/app/admin/(dashboard)/landing-page/profil/program-inovasi/[id]/page.tsx` (line ~105) **Masalah:** ```typescript // ❌ Direct HTML render tanpa sanitization ``` **Risk:** - XSS attack jika admin input script malicious - Bisa inject iframe, script tag, dll **Rekomendasi:** Gunakan DOMPurify atau library sanitization: ```typescript import DOMPurify from 'dompurify'; // Sanitize sebelum render const sanitizedHtml = DOMPurify.sanitize(item.description); ``` Atau validasi di backend untuk whitelist tag HTML yang diperbolehkan (hanya `

`, `