diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1e73563b..cce83ef8 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -136,6 +136,7 @@ model MediaSosial { name String image FileStorage? @relation(fields: [imageId], references: [id]) imageId String? + icon String? iconUrl String? @db.VarChar(255) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/src/app/admin/(dashboard)/_com/selectSocialMedia.tsx b/src/app/admin/(dashboard)/_com/selectSocialMedia.tsx new file mode 100644 index 00000000..e92222ed --- /dev/null +++ b/src/app/admin/(dashboard)/_com/selectSocialMedia.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { Box, Image, Select, rem } from '@mantine/core'; + +const sosmedMap = { + facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' }, + instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' }, + tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' }, + youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' }, + whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' }, + gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' }, + telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' }, + x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' }, + telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' }, + custom: { label: 'Custom Icon', src: null }, +}; + +type SosmedKey = keyof typeof sosmedMap; + +const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({ + value, + label: item.label, +})); + +export default function SelectSosialMedia({ + value, + onChange, +}: { + value: SosmedKey; + onChange: (value: SosmedKey) => void; +}) { + const selected = value; + const selectedImage = sosmedMap[selected]?.src; + + return ( + + { + if (!val) return; + setSelected(val as SosmedKey); + onChange(val as SosmedKey); + }} + /> + + ); +} diff --git a/src/app/admin/(dashboard)/_state/landing-page/profile.ts b/src/app/admin/(dashboard)/_state/landing-page/profile.ts index e3a5fe18..d4abaf81 100644 --- a/src/app/admin/(dashboard)/_state/landing-page/profile.ts +++ b/src/app/admin/(dashboard)/_state/landing-page/profile.ts @@ -27,7 +27,7 @@ const programInovasi = proxy({ name: "", description: "", imageId: "", - link: "" + link: "", } as ProgramInovasiForm, loading: false, async create() { @@ -71,20 +71,21 @@ const programInovasi = proxy({ total: 0, loading: false, search: "", - load: async (page = 1, limit = 10, search = "") => { // Change to arrow function - programInovasi.findMany.loading = true; // Use the full path to access the property + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + programInovasi.findMany.loading = true; // Use the full path to access the property programInovasi.findMany.page = page; programInovasi.findMany.search = search; try { const query: any = { page, limit }; if (search) query.search = search; - + const res = await ApiFetch.api.landingpage.programinovasi[ "findMany" ].get({ - query + query, }); - + if (res.status === 200 && res.data?.success) { programInovasi.findMany.data = res.data.data || []; programInovasi.findMany.total = res.data.total || 0; @@ -389,7 +390,10 @@ const pejabatDesa = proxy({ try { // Ensure ID is properly encoded in the URL - const url = new URL(`/api/landingpage/pejabatdesa/${encodeURIComponent(this.id)}`, window.location.origin); + const url = new URL( + `/api/landingpage/pejabatdesa/${encodeURIComponent(this.id)}`, + window.location.origin + ); const response = await fetch(url.toString(), { method: "PUT", headers: { @@ -438,16 +442,19 @@ const pejabatDesa = proxy({ const templateMediaSosial = z.object({ name: z.string().min(3, "Nama minimal 3 karakter"), - imageId: z.string().min(1, "Gambar wajib dipilih"), + imageId: z.string().nullable().optional(), iconUrl: z.string().min(3, "Icon URL minimal 3 karakter"), + icon: z.string().nullable().optional(), }); type MediaSosialForm = { name: string; - imageId: string; + imageId: string | null; // boleh null iconUrl: string; + icon: string | null; // boleh null }; + const mediaSosial = proxy({ create: { form: {} as MediaSosialForm, @@ -455,9 +462,10 @@ const mediaSosial = proxy({ async create() { // Ensure all required fields are non-null const formData = { - name: mediaSosial.create.form.name || "", - imageId: mediaSosial.create.form.imageId || "", - iconUrl: mediaSosial.create.form.iconUrl || "", + name: mediaSosial.create.form.name ?? "", + imageId: mediaSosial.create.form.imageId ?? null, // FIXED + iconUrl: mediaSosial.create.form.iconUrl ?? "", + icon: mediaSosial.create.form.icon ?? null, // FIXED }; const cek = templateMediaSosial.safeParse(formData); @@ -492,20 +500,19 @@ const mediaSosial = proxy({ total: 0, loading: false, search: "", - load: async (page = 1, limit = 10, search = "") => { // Change to arrow function - mediaSosial.findMany.loading = true; // Use the full path to access the property + load: async (page = 1, limit = 10, search = "") => { + // Change to arrow function + mediaSosial.findMany.loading = true; // Use the full path to access the property mediaSosial.findMany.page = page; mediaSosial.findMany.search = search; - try { + try { const query: any = { page, limit }; if (search) query.search = search; - - const res = await ApiFetch.api.landingpage.mediasosial[ - "findMany" - ].get({ + + const res = await ApiFetch.api.landingpage.mediasosial["findMany"].get({ query, }); - + if (res.status === 200 && res.data?.success) { mediaSosial.findMany.data = res.data.data || []; mediaSosial.findMany.total = res.data.total || 0; @@ -537,7 +544,7 @@ const mediaSosial = proxy({ toast.warn("ID tidak valid"); return null; } - + mediaSosial.update.loading = true; try { const res = await fetch(`/api/landingpage/mediasosial/${id}`); @@ -586,66 +593,72 @@ const mediaSosial = proxy({ }, }, update: { - id: "", - form: {} as MediaSosialForm, - loading: false, - - async load(id: string) { - if (!id) { - toast.warn("ID tidak valid"); - return null; + id: "", + form: {} as MediaSosialForm, + loading: false, + + async load(id: string) { + if (!id) { + toast.warn("ID tidak valid"); + return null; + } + + mediaSosial.update.loading = true; // ✅ Tambahkan ini di awal + + try { + const response = await fetch(`/api/landingpage/mediasosial/${id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); } - - mediaSosial.update.loading = true; // ✅ Tambahkan ini di awal - - try { - const response = await fetch(`/api/landingpage/mediasosial/${id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const result = await response.json(); - - if (result?.success) { - const data = result.data; - this.id = data.id; - this.form = { - name: data.name || "", - imageId: data.imageId || "", - iconUrl: data.iconUrl || "", - }; - return data; - } else { - throw new Error(result?.message || "Gagal mengambil data media sosial"); - } - } catch (error) { - console.error((error as Error).message); - toast.error("Terjadi kesalahan saat mengambil data media sosial"); - } finally { - mediaSosial.update.loading = false; // ✅ Supaya berhenti loading walau error + + const result = await response.json(); + + if (result?.success) { + const data = result.data; + this.id = data.id; + this.form = { + name: data.name || "", + imageId: data.imageId || null, + iconUrl: data.iconUrl || "", + icon: data.icon || null, + + }; + return data; + } else { + throw new Error( + result?.message || "Gagal mengambil data media sosial" + ); } - }, - - async update() { - const cek = templateMediaSosial.safeParse(mediaSosial.update.form); - if (!cek.success) { - const err = `[${cek.error.issues - .map((v) => `${v.path.join(".")}`) - .join("\n")}] required`; - toast.error(err); - return false; - } - - try { - mediaSosial.update.loading = true; - - const response = await fetch(`/api/landingpage/mediasosial/${this.id}`, { + } catch (error) { + console.error((error as Error).message); + toast.error("Terjadi kesalahan saat mengambil data media sosial"); + } finally { + mediaSosial.update.loading = false; // ✅ Supaya berhenti loading walau error + } + }, + + async update() { + const cek = templateMediaSosial.safeParse(mediaSosial.update.form); + if (!cek.success) { + const err = `[${cek.error.issues + .map((v) => `${v.path.join(".")}`) + .join("\n")}] required`; + toast.error(err); + return false; + } + + try { + mediaSosial.update.loading = true; + + const response = await fetch( + `/api/landingpage/mediasosial/${this.id}`, + { method: "PUT", headers: { "Content-Type": "application/json", @@ -654,38 +667,40 @@ const mediaSosial = proxy({ name: this.form.name, imageId: this.form.imageId, iconUrl: this.form.iconUrl, + icon: this.form.icon, }), - }); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - throw new Error( - errorData.message || `HTTP error! status: ${response.status}` - ); } - - const result = await response.json(); - - if (result.success) { - toast.success("Berhasil update media sosial"); - await mediaSosial.findMany.load(); // refresh list - return true; - } else { - throw new Error(result.message || "Gagal update media sosial"); - } - } catch (error) { - console.error("Error updating media sosial:", error); - toast.error( - error instanceof Error - ? error.message - : "Terjadi kesalahan saat update media sosial" + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || `HTTP error! status: ${response.status}` ); - return false; - } finally { - mediaSosial.update.loading = false; } - }, + + const result = await response.json(); + + if (result.success) { + toast.success("Berhasil update media sosial"); + await mediaSosial.findMany.load(); // refresh list + return true; + } else { + throw new Error(result.message || "Gagal update media sosial"); + } + } catch (error) { + console.error("Error updating media sosial:", error); + toast.error( + error instanceof Error + ? error.message + : "Terjadi kesalahan saat update media sosial" + ); + return false; + } finally { + mediaSosial.update.loading = false; + } }, + }, }); const profileLandingPageState = proxy({ diff --git a/src/app/admin/(dashboard)/landing-page/profil/_lib/sosmed.ts b/src/app/admin/(dashboard)/landing-page/profil/_lib/sosmed.ts new file mode 100644 index 00000000..b5b7c9e8 --- /dev/null +++ b/src/app/admin/(dashboard)/landing-page/profil/_lib/sosmed.ts @@ -0,0 +1,12 @@ +export const sosmedMap = { + facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' }, + instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' }, + tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' }, + youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' }, + whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' }, + gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' }, + telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' }, + x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' }, + telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' }, + custom: { label: 'Custom Icon', src: null }, +}; diff --git a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx index 2cb77a63..9be66fd9 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/[id]/edit/page.tsx @@ -1,5 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client'; + +import SelectSocialMediaEdit from '@/app/admin/(dashboard)/_com/selectSocialMediaEdit'; import profileLandingPageState from '@/app/admin/(dashboard)/_state/landing-page/profile'; import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; @@ -14,7 +16,7 @@ import { Text, TextInput, Title, - Loader + Loader, } from '@mantine/core'; import { Dropzone } from '@mantine/dropzone'; import { IconArrowBack, IconPhoto, IconUpload, IconX } from '@tabler/icons-react'; @@ -23,15 +25,45 @@ import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; +type SosmedKey = + | 'none' + | 'facebook' + | 'instagram' + | 'tiktok' + | 'youtube' + | 'whatsapp' + | 'gmail' + | 'telegram' + | 'x' + | 'telephone' + | 'custom'; + +const sosmedMap: Record = { + none: { label: "None", src: '/no-image.jpg' }, + facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' }, + instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' }, + tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' }, + youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' }, + whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' }, + gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' }, + telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' }, + x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' }, + telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' }, + custom: { label: 'Custom Icon', src: null }, +}; + function EditMediaSosial() { const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial); const router = useRouter(); const params = useParams(); + const [selectedSosmed, setSelectedSosmed] = useState('facebook'); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); + const [formData, setFormData] = useState({ name: '', + icon: '', iconUrl: '', imageId: '', }); @@ -39,13 +71,14 @@ function EditMediaSosial() { const [isSubmitting, setIsSubmitting] = useState(false); const [originalData, setOriginalData] = useState({ - name: "", - iconUrl: "", - imageId: "", - imageUrl: "", + name: '', + icon: '', + iconUrl: '', + imageId: '', + imageUrl: '', }); - // Load data by ID + // Load Data by ID useEffect(() => { const id = params?.id as string; if (!id) return; @@ -54,81 +87,97 @@ function EditMediaSosial() { try { const data = await stateMediaSosial.update.load(id); - if (data) { - // isi form awal - const newForm = { - name: data.name || "", - iconUrl: data.iconUrl || "", - imageId: data.imageId || "", - }; - setFormData(newForm); + if (!data) return; - // simpan juga versi original - setOriginalData({ - ...newForm, - imageUrl: data.image?.link || "", - }); - - setPreviewImage(data.image?.link || null); + // Tentukan default/custom icon + // Tentukan default/custom icon + if (data.imageId) { + setSelectedSosmed('custom'); + } else { + // ✅ Gunakan langsung data.icon jika ada dan valid + if (data.icon && sosmedMap[data.icon as SosmedKey]) { + setSelectedSosmed(data.icon as SosmedKey); + } else { + setSelectedSosmed('none'); // fallback + } } - } catch (error) { - console.error('Error loading media sosial:', error); - toast.error( - error instanceof Error ? error.message : 'Gagal mengambil data media sosial' - ); + + const newForm = { + name: data.name || '', + icon: data.icon || '', + iconUrl: data.iconUrl || '', + imageId: data.imageId || '', + }; + + setFormData(newForm); + + setOriginalData({ + ...newForm, + imageUrl: data.image?.link || '', + }); + + setPreviewImage(data.image?.link || null); + } catch { + toast.error('Gagal mengambil data media sosial'); } }; loadData(); }, [params?.id]); - const handleChange = (field: string, value: string) => { + const handleChange = (field: keyof typeof formData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); }; const handleSubmit = async () => { setIsSubmitting(true); + try { - // update global state hanya saat submit stateMediaSosial.update.form = { ...stateMediaSosial.update.form, ...formData }; if (file) { - const res = await ApiFetch.api.fileStorage.create.post({ file, name: file.name }); + const res = await ApiFetch.api.fileStorage.create.post({ + file, + name: file.name, + }); const uploaded = res.data?.data; - if (!uploaded?.id) return toast.error('Gagal upload gambar'); + if (!uploaded?.id) { + toast.error('Gagal upload gambar'); + return; + } stateMediaSosial.update.form.imageId = uploaded.id; } + // 🚨 Tambahkan ini untuk debugging + console.log("Data yang akan dikirim ke backend:", stateMediaSosial.update.form); + await stateMediaSosial.update.update(); toast.success('Media sosial berhasil diperbarui!'); router.push('/admin/landing-page/profil/media-sosial'); } catch (error) { - console.error('Error updating media sosial:', error); + console.error("Error di handleSubmit:", error); // 🚨 Tambahkan ini juga toast.error('Terjadi kesalahan saat memperbarui media sosial'); } finally { setIsSubmitting(false); } }; - // ✅ Tombol Batal → balikin ke data original const handleResetForm = () => { setFormData({ name: originalData.name, + icon: originalData.icon, iconUrl: originalData.iconUrl, imageId: originalData.imageId, }); setPreviewImage(originalData.imageUrl || null); setFile(null); - toast.info("Form dikembalikan ke data awal"); + toast.info('Form dikembalikan ke data awal'); }; return ( - + - {/* Tombol Simpan */} + - + diff --git a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx index 937f08c7..ccbfdb5c 100644 --- a/src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx +++ b/src/app/admin/(dashboard)/landing-page/profil/media-sosial/create/page.tsx @@ -1,5 +1,6 @@ /* eslint-disable react-hooks/exhaustive-deps */ 'use client'; + import colors from '@/con/colors'; import ApiFetch from '@/lib/api-fetch'; import { @@ -22,10 +23,41 @@ import { useEffect, useState } from 'react'; import { toast } from 'react-toastify'; import { useProxy } from 'valtio/utils'; import profileLandingPageState from '../../../../_state/landing-page/profile'; +import SelectSosialMedia from '@/app/admin/(dashboard)/_com/selectSocialMedia'; + + +// ⭐ Tambah type SosmedKey +type SosmedKey = + | 'facebook' + | 'instagram' + | 'tiktok' + | 'youtube' + | 'whatsapp' + | 'gmail' + | 'telegram' + | 'x' + | 'telephone' + | 'custom'; + +// ⭐ mapping icon sosmed bawaan +const sosmedMap: Record = { + facebook: { label: 'Facebook', src: '/assets/images/sosmed/facebook.png' }, + instagram: { label: 'Instagram', src: '/assets/images/sosmed/instagram.png' }, + tiktok: { label: 'Tiktok', src: '/assets/images/sosmed/tiktok.png' }, + youtube: { label: 'YouTube', src: '/assets/images/sosmed/youtube.png' }, + whatsapp: { label: 'WhatsApp', src: '/assets/images/sosmed/whatsapp.png' }, + gmail: { label: 'Gmail', src: '/assets/images/sosmed/gmail.png' }, + telegram: { label: 'Telegram', src: '/assets/images/sosmed/telegram.png' }, + x: { label: 'X (Twitter)', src: '/assets/images/sosmed/x-twitter.png' }, + telephone: { label: 'Telephone', src: '/assets/images/sosmed/telephone-call.png' }, + custom: { label: 'Custom Icon', src: null }, +}; export default function CreateMediaSosial() { const router = useRouter(); const stateMediaSosial = useProxy(profileLandingPageState.mediaSosial); + + const [selectedSosmed, setSelectedSosmed] = useState('facebook'); const [previewImage, setPreviewImage] = useState(null); const [file, setFile] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); @@ -39,16 +71,34 @@ export default function CreateMediaSosial() { name: '', imageId: '', iconUrl: '', + icon: '' }; - setPreviewImage(null); + setFile(null); + setPreviewImage(null); + setSelectedSosmed('facebook'); }; const handleSubmit = async () => { setIsSubmitting(true); + try { + // ──────────────── ⭐ CASE 1: PAKAI ICON DEFAULT ──────────────── + if (selectedSosmed !== 'custom') { + stateMediaSosial.create.form.imageId = null; + stateMediaSosial.create.form.icon = sosmedMap[selectedSosmed].src!; + + + await stateMediaSosial.create.create(); + resetForm(); + router.push('/admin/landing-page/profil/media-sosial'); + return; + } + + // ──────────────── ⭐ CASE 2: CUSTOM ICON → WAJIB UPLOAD ──────────────── if (!file) { - return toast.warn('Silakan pilih file gambar terlebih dahulu'); + toast.warn('Silakan upload icon custom terlebih dahulu'); + return; } const res = await ApiFetch.api.fileStorage.create.post({ @@ -59,10 +109,12 @@ export default function CreateMediaSosial() { const uploaded = res.data?.data; if (!uploaded?.id) { - return toast.error('Gagal mengunggah gambar, silakan coba lagi'); + toast.error('Gagal mengunggah icon custom'); + return; } stateMediaSosial.create.form.imageId = uploaded.id; + stateMediaSosial.create.form.icon = null; await stateMediaSosial.create.create(); @@ -78,6 +130,7 @@ export default function CreateMediaSosial() { return ( + {/* Header */} + @@ -77,13 +87,26 @@ function ListMediaSosial({ search }: { search: string }) { {item.name} - - - {item.image?.link ? ( - - ) : ( - - )} + + + + {(() => { + const src = getIconSource(item); + + if (src) { + return ( + + ); + } + + return ; + })()} + diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/create.ts b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/create.ts index a545be3e..d7c3f4e5 100644 --- a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/create.ts +++ b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/create.ts @@ -5,6 +5,7 @@ type FormCreate = { name: string; imageId: string; iconUrl: string; + icon: string; }; export default async function mediaSosialCreate(context: Context) { @@ -14,8 +15,9 @@ export default async function mediaSosialCreate(context: Context) { const result = await prisma.mediaSosial.create({ data: { name: body.name, - imageId: body.imageId, + imageId: body.imageId || null, iconUrl: body.iconUrl, + icon: body.icon || null, }, include: { image: true, @@ -29,8 +31,6 @@ export default async function mediaSosialCreate(context: Context) { }; } catch (error) { console.error("Error creating media sosial:", error); - throw new Error( - "Gagal membuat media sosial: " + (error as Error).message - ); + throw new Error("Gagal membuat media sosial: " + (error as Error).message); } } diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/index.ts b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/index.ts index 7fb00c25..89f10e21 100644 --- a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/index.ts +++ b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/index.ts @@ -20,8 +20,9 @@ const MediaSosial = new Elysia({ .post("/create", MediaSosialCreate, { body: t.Object({ name: t.String(), - imageId: t.String(), - iconUrl: t.String(), + imageId: t.Union([t.String(), t.Null()]), + iconUrl: t.Union([t.String(), t.Null()]), + icon: t.Union([t.String(), t.Null()]), }), }) @@ -29,8 +30,9 @@ const MediaSosial = new Elysia({ .put("/:id", MediaSosialUpdate, { body: t.Object({ name: t.String(), - imageId: t.Optional(t.String()), - iconUrl: t.Optional(t.String()), + imageId: t.Optional(t.Union([t.String(), t.Null()])), + iconUrl: t.Optional(t.Union([t.String(), t.Null()])), + icon: t.Optional(t.Union([t.String(), t.Null()])), }), }) // ✅ Delete diff --git a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/updt.ts b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/updt.ts index 5a5ca174..cc92bf6c 100644 --- a/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/updt.ts +++ b/src/app/api/[[...slugs]]/_lib/landing_page/profile/media-sosial/updt.ts @@ -6,6 +6,7 @@ type FormUpdateMediaSosial = { name?: string; imageId?: string; iconUrl?: string; + icon?: string; }; export default async function mediaSosialUpdate(context: Context) { @@ -20,13 +21,29 @@ export default async function mediaSosialUpdate(context: Context) { }; } + // 🚨 Tambahkan validasi di sini + if (!body.name || body.name.trim().length < 3) { + return { + success: false, + message: "Nama media sosial minimal 3 karakter", + }; + } + + if (!body.iconUrl || body.iconUrl.trim().length < 3) { + return { + success: false, + message: "Icon URL minimal 3 karakter", + }; + } + try { const updated = await prisma.mediaSosial.update({ where: { id }, data: { name: body.name, - imageId: body.imageId, + imageId: body.imageId || null, // pastikan null jika kosong iconUrl: body.iconUrl, + icon: body.icon || null, // pastikan null jika kosong }, include: { image: true, diff --git a/src/app/darmasaba/_com/main-page/apbdes/index.tsx b/src/app/darmasaba/_com/main-page/apbdes/index.tsx index 1ce44d1c..ad848d55 100644 --- a/src/app/darmasaba/_com/main-page/apbdes/index.tsx +++ b/src/app/darmasaba/_com/main-page/apbdes/index.tsx @@ -5,7 +5,7 @@ import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes' import APBDesProgress from '@/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress' import { transformAPBDesData } from '@/app/darmasaba/(tambahan)/apbdes/lib/types' import colors from '@/con/colors' -import { ActionIcon, BackgroundImage, Box, Button, Center, Flex, Group, Loader, Select, SimpleGrid, Stack, Text } from '@mantine/core' +import { ActionIcon, BackgroundImage, Box, Button, Center, Group, Loader, Select, SimpleGrid, Stack, Text } from '@mantine/core' import { IconDownload } from '@tabler/icons-react' import Link from 'next/link' import { useEffect, useState } from 'react' @@ -133,7 +133,7 @@ function Apbdes() { style={{ overflow: 'hidden' }} > - + {v.jumlah} - +
+ + + + +
+ {/* - - - + + + - +
*/}
))