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 (
+
+
+ );
+}
diff --git a/src/app/admin/(dashboard)/_com/selectSocialMediaEdit.tsx b/src/app/admin/(dashboard)/_com/selectSocialMediaEdit.tsx
new file mode 100644
index 00000000..185037fd
--- /dev/null
+++ b/src/app/admin/(dashboard)/_com/selectSocialMediaEdit.tsx
@@ -0,0 +1,56 @@
+'use client';
+
+import { Box, Select } from '@mantine/core';
+import { useEffect, useState } from 'react';
+
+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 },
+};
+
+type SosmedKey = keyof typeof sosmedMap;
+
+const sosmedList = Object.entries(sosmedMap).map(([value, item]) => ({
+ value,
+ label: item.label,
+}));
+
+export default function SelectSocialMediaEdit({
+ value,
+ onChange,
+}: {
+ value: string;
+ onChange: (val: SosmedKey) => void;
+}) {
+ const [selected, setSelected] = useState('facebook');
+
+ useEffect(() => {
+ if (value && sosmedMap[value as SosmedKey]) {
+ setSelected(value as SosmedKey);
+ }
+ }, [value]);
+
+ return (
+
+
+ );
+}
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 (
-
+