fix: Quality Control improvements & bug fixes
- APBDes: Fix edit form original data tracking (imageId, fileId) - APBDes: Update formula consistency in state - PPID modules: Various UI improvements and bug fixes - PPID Profil: Preview and edit page improvements - PPID Dasar Hukum: Page structure improvements - PPID Visi Misi: Page structure improvements - PPID Struktur: Posisi organisasi page improvements - PPID Daftar Informasi: Edit page improvements - Auth login: Route improvements - Update dependencies (package.json, bun.lockb) - Update seed data - Update .gitignore QC Reports added: - QC-APBDES-MODULE.md - QC-PROFIL-MODULE.md - QC-SDGS-DESA.md - QC-DESA-ANTI-KORUPSI.md - QC-PRESTASI-DESA-MODULE.md - QC-PPID-PROFIL-MODULE.md - QC-STRUKTUR-PPID-MODULE.md - QC-VISI-MISI-PPID-MODULE.md - QC-DASAR-HUKUM-PPID-MODULE.md - QC-PERMOHONAN-INFORMASI-PUBLIK-MODULE.md - QC-PERMOHONAN-KEBERATAN-INFORMASI-MODULE.md - QC-DAFTAR-INFORMASI-PUBLIK-MODULE.md - QC-IKM-MODULE.md Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -31,6 +31,9 @@ yarn-error.log*
|
|||||||
# env
|
# env
|
||||||
.env*
|
.env*
|
||||||
|
|
||||||
|
# QC
|
||||||
|
QC
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
|
"dompurify": "^3.3.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"elysia": "^1.3.5",
|
"elysia": "^1.3.5",
|
||||||
"embla-carousel": "^8.6.0",
|
"embla-carousel": "^8.6.0",
|
||||||
|
|||||||
@@ -69,8 +69,8 @@ import { seedProfilPpd } from "./_seeder_list/ppid/profil-ppid/seed_profil_ppd";
|
|||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Always run seedAssets to handle new images without duplication
|
// Always run seedAssets to handle new images without duplication
|
||||||
console.log("📂 Checking for new assets to seed...");
|
// console.log("📂 Checking for new assets to seed...");
|
||||||
await seedAssets();
|
// await seedAssets();
|
||||||
|
|
||||||
// // =========== FILE STORAGE ===========
|
// // =========== FILE STORAGE ===========
|
||||||
|
|
||||||
|
|||||||
@@ -39,10 +39,8 @@ function normalizeItem(item: Partial<z.infer<typeof ApbdesItemSchema>>): z.infer
|
|||||||
const realisasi = item.realisasi ?? 0;
|
const realisasi = item.realisasi ?? 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// ✅ Formula yang benar
|
// ✅ Formula yang benar
|
||||||
const selisih = anggaran - realisasi; // positif = sisa anggaran, negatif = over budget
|
const selisih = realisasi - anggaran; // positif = sisa anggaran, negatif = over budget
|
||||||
const persentase = anggaran > 0 ? (realisasi / anggaran) * 100 : 0; // persentase realisasi terhadap anggaran
|
const persentase = anggaran > 0 ? (realisasi / anggaran) * 100 : 0; // persentase realisasi terhadap anggaran
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -76,33 +76,62 @@ function EditAPBDes() {
|
|||||||
tipe: 'pendapatan',
|
tipe: 'pendapatan',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Type for the API response
|
// Simpan data original untuk reset form
|
||||||
interface APBDesResponse {
|
const [originalData, setOriginalData] = useState({
|
||||||
id: string;
|
tahun: 0,
|
||||||
image?: {
|
imageId: '',
|
||||||
link: string;
|
fileId: '',
|
||||||
id: string;
|
imageUrl: '',
|
||||||
};
|
fileUrl: '',
|
||||||
file?: {
|
});
|
||||||
link: string;
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
// Add other properties as needed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load data saat pertama kali
|
// Load data saat pertama kali
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = params?.id as string;
|
const id = params?.id as string;
|
||||||
if (id) {
|
if (!id) return;
|
||||||
apbdesState.edit.load(id).then((response) => {
|
|
||||||
const data = response as unknown as APBDesResponse;
|
const loadData = async () => {
|
||||||
if (data) {
|
try {
|
||||||
// ✅ Ambil link langsung dari response
|
const data = await apbdesState.edit.load(id);
|
||||||
setPreviewImage(data.image?.link || null);
|
|
||||||
setPreviewDoc(data.file?.link || null);
|
if (!data) return;
|
||||||
}
|
|
||||||
});
|
// Set preview dari data lama
|
||||||
}
|
setPreviewImage(data.image?.link || null);
|
||||||
|
setPreviewDoc(data.file?.link || null);
|
||||||
|
|
||||||
|
// Simpan data original untuk reset
|
||||||
|
setOriginalData({
|
||||||
|
tahun: data.tahun || new Date().getFullYear(),
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
fileId: data.fileId || '',
|
||||||
|
imageUrl: data.image?.link || '',
|
||||||
|
fileUrl: data.file?.link || '',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set form dengan data lama (termasuk imageId dan fileId)
|
||||||
|
apbdesState.edit.form = {
|
||||||
|
tahun: data.tahun || new Date().getFullYear(),
|
||||||
|
imageId: data.imageId || '',
|
||||||
|
fileId: data.fileId || '',
|
||||||
|
items: (data.items || []).map((item: any) => ({
|
||||||
|
kode: item.kode,
|
||||||
|
uraian: item.uraian,
|
||||||
|
anggaran: item.anggaran,
|
||||||
|
realisasi: item.realisasi,
|
||||||
|
selisih: item.selisih,
|
||||||
|
persentase: item.persentase,
|
||||||
|
level: item.level,
|
||||||
|
tipe: item.tipe || 'pendapatan',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading APBDes:', error);
|
||||||
|
toast.error('Gagal memuat data APBDes');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadData();
|
||||||
}, [params?.id]);
|
}, [params?.id]);
|
||||||
|
|
||||||
const handleDrop = (fileType: 'image' | 'doc') => (files: File[]) => {
|
const handleDrop = (fileType: 'image' | 'doc') => (files: File[]) => {
|
||||||
@@ -162,23 +191,38 @@ function EditAPBDes() {
|
|||||||
try {
|
try {
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
// Upload file baru jika ada
|
// Upload file baru jika ada perubahan
|
||||||
if (imageFile) {
|
if (imageFile) {
|
||||||
|
// Hapus file lama dari form jika ada file baru
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file: imageFile,
|
file: imageFile,
|
||||||
name: imageFile.name,
|
name: imageFile.name,
|
||||||
});
|
});
|
||||||
const imageId = res.data?.data?.id;
|
const imageId = res.data?.data?.id;
|
||||||
if (imageId) apbdesState.edit.form.imageId = imageId;
|
if (imageId) {
|
||||||
|
apbdesState.edit.form.imageId = imageId;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (docFile) {
|
if (docFile) {
|
||||||
|
// Hapus file lama dari form jika ada file baru
|
||||||
const res = await ApiFetch.api.fileStorage.create.post({
|
const res = await ApiFetch.api.fileStorage.create.post({
|
||||||
file: docFile,
|
file: docFile,
|
||||||
name: docFile.name,
|
name: docFile.name,
|
||||||
});
|
});
|
||||||
const fileId = res.data?.data?.id;
|
const fileId = res.data?.data?.id;
|
||||||
if (fileId) apbdesState.edit.form.fileId = fileId;
|
if (fileId) {
|
||||||
|
apbdesState.edit.form.fileId = fileId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jika tidak ada file baru, gunakan ID lama (sudah ada di form)
|
||||||
|
// Pastikan imageId dan fileId tetap ada
|
||||||
|
if (!apbdesState.edit.form.imageId) {
|
||||||
|
return toast.warn('Gambar wajib diunggah');
|
||||||
|
}
|
||||||
|
if (!apbdesState.edit.form.fileId) {
|
||||||
|
return toast.warn('Dokumen wajib diunggah');
|
||||||
}
|
}
|
||||||
|
|
||||||
const success = await apbdesState.edit.update();
|
const success = await apbdesState.edit.update();
|
||||||
@@ -194,21 +238,33 @@ function EditAPBDes() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
const id = params?.id as string;
|
// Reset ke data original (tahun, imageId, fileId)
|
||||||
if (id) {
|
apbdesState.edit.form = {
|
||||||
apbdesState.edit.load(id);
|
tahun: originalData.tahun,
|
||||||
setImageFile(null);
|
imageId: originalData.imageId,
|
||||||
setDocFile(null);
|
fileId: originalData.fileId,
|
||||||
setNewItem({
|
items: [...apbdesState.edit.form.items], // keep existing items
|
||||||
kode: '',
|
};
|
||||||
uraian: '',
|
|
||||||
anggaran: 0,
|
// Reset preview ke data original
|
||||||
realisasi: 0,
|
setPreviewImage(originalData.imageUrl || null);
|
||||||
level: 1,
|
setPreviewDoc(originalData.fileUrl || null);
|
||||||
tipe: 'pendapatan',
|
|
||||||
});
|
// Reset file uploads
|
||||||
toast.info('Form dikembalikan ke data awal');
|
setImageFile(null);
|
||||||
}
|
setDocFile(null);
|
||||||
|
|
||||||
|
// Reset new item form
|
||||||
|
setNewItem({
|
||||||
|
kode: '',
|
||||||
|
uraian: '',
|
||||||
|
anggaran: 0,
|
||||||
|
realisasi: 0,
|
||||||
|
level: 1,
|
||||||
|
tipe: 'pendapatan',
|
||||||
|
});
|
||||||
|
|
||||||
|
toast.info('Form dikembalikan ke data awal');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -82,17 +82,17 @@ function EditDaftarInformasiPublik() {
|
|||||||
await daftarInformasi.edit.update();
|
await daftarInformasi.edit.update();
|
||||||
router.push('/admin/ppid/daftar-informasi-publik');
|
router.push('/admin/ppid/daftar-informasi-publik');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating berita:', error);
|
console.error('Error updating daftar informasi:', error);
|
||||||
toast.error('Terjadi kesalahan saat memperbarui berita');
|
toast.error('Terjadi kesalahan saat memperbarui daftar informasi');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
<Box px={{ base: 0, md: 'lg' }} py="xs">
|
||||||
<Group mb="md">
|
<Group mb="md">
|
||||||
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
<Button variant="subtle" onClick={() => router.back()} p="xs" radius="md">
|
||||||
<IconArrowBack color={colors['blue-button']} size={24} />
|
<IconArrowBack color={colors['blue-button']} size={24} />
|
||||||
</Button>
|
</Button>
|
||||||
<Title order={4} ml="sm" c="dark">
|
<Title order={4} ml="sm" c="dark">
|
||||||
Edit Daftar Informasi Publik
|
Edit Daftar Informasi Publik
|
||||||
</Title>
|
</Title>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IconEdit } from '@tabler/icons-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateDasarHukumPPID from '../../_state/ppid/dasar_hukum/dasarHukum';
|
import stateDasarHukumPPID from '../../_state/ppid/dasar_hukum/dasarHukum';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -68,7 +69,7 @@ function Page() {
|
|||||||
lh={{ base: 1.15, md: 1.1 }}
|
lh={{ base: 1.15, md: 1.1 }}
|
||||||
fw="bold"
|
fw="bold"
|
||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.judul }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(listDasarHukum.findById.data.judul) }}
|
||||||
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
style={{ wordBreak: 'break-word', whiteSpace: 'normal' }}
|
||||||
/>
|
/>
|
||||||
</GridCol>
|
</GridCol>
|
||||||
@@ -77,7 +78,7 @@ function Page() {
|
|||||||
<Divider my="xl" color={colors['blue-button']} />
|
<Divider my="xl" color={colors['blue-button']} />
|
||||||
|
|
||||||
<Text
|
<Text
|
||||||
dangerouslySetInnerHTML={{ __html: listDasarHukum.findById.data.content }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(listDasarHukum.findById.data.content) }}
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IconEdit } from '@tabler/icons-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
|
import stateProfilePPID from '../../_state/ppid/profile_ppid/profile_PPID';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
function Page() {
|
function Page() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -114,7 +115,7 @@ function Page() {
|
|||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
dangerouslySetInnerHTML={{ __html: item.biodata }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.biodata) }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ function Page() {
|
|||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
dangerouslySetInnerHTML={{ __html: item.riwayat }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.riwayat) }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -145,7 +146,7 @@ function Page() {
|
|||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
dangerouslySetInnerHTML={{ __html: item.pengalaman }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.pengalaman) }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -161,7 +162,7 @@ function Page() {
|
|||||||
c={colors['blue-button']}
|
c={colors['blue-button']}
|
||||||
lh={1.5}
|
lh={1.5}
|
||||||
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
style={{ wordBreak: "break-word", whiteSpace: "normal" }}
|
||||||
dangerouslySetInnerHTML={{ __html: item.unggulan }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.unggulan) }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { useProxy } from 'valtio/utils';
|
|||||||
import HeaderSearch from '../../../_com/header';
|
import HeaderSearch from '../../../_com/header';
|
||||||
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
import { ModalKonfirmasiHapus } from '../../../_com/modalKonfirmasiHapus';
|
||||||
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
import stateStrukturPPID from '../../../_state/ppid/struktur_ppid/struktur_PPID';
|
||||||
|
import DOMPurify from 'dompurify';
|
||||||
|
|
||||||
function PosisiOrganisasiPPID() {
|
function PosisiOrganisasiPPID() {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
@@ -100,7 +101,7 @@ function ListPosisiOrganisasiPPID({ search }: { search: string }) {
|
|||||||
<Text fz="md" fw={600} lh={1.5} truncate="end" lineClamp={1}>{item.nama}</Text>
|
<Text fz="md" fw={600} lh={1.5} truncate="end" lineClamp={1}>{item.nama}</Text>
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd w={200}>
|
<TableTd w={200}>
|
||||||
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: item.deskripsi || '-' }} />
|
<Text fz="sm" lh={1.5} c="dimmed" lineClamp={1} dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(item.deskripsi || '-') }} />
|
||||||
</TableTd>
|
</TableTd>
|
||||||
<TableTd>
|
<TableTd>
|
||||||
<Text fz="md" lh={1.5}>{item.hierarki || '-'}</Text>
|
<Text fz="md" lh={1.5}>{item.hierarki || '-'}</Text>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { IconEdit } from '@tabler/icons-react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { useProxy } from 'valtio/utils';
|
import { useProxy } from 'valtio/utils';
|
||||||
import stateVisiMisiPPID from '../../_state/ppid/visi_misi_ppid/visimisiPPID';
|
import stateVisiMisiPPID from '../../_state/ppid/visi_misi_ppid/visimisiPPID';
|
||||||
|
import DOMPurify from 'dompurify'
|
||||||
|
|
||||||
function VisiMisiPPIDList() {
|
function VisiMisiPPIDList() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -96,7 +97,7 @@ function VisiMisiPPIDList() {
|
|||||||
</Title>
|
</Title>
|
||||||
<Text
|
<Text
|
||||||
ta={{ base: "center", md: "justify" }}
|
ta={{ base: "center", md: "justify" }}
|
||||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.visi }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(listVisiMisi.findById.data.visi) }}
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
@@ -121,7 +122,7 @@ function VisiMisiPPIDList() {
|
|||||||
</Title>
|
</Title>
|
||||||
<Text
|
<Text
|
||||||
ta={"justify"}
|
ta={"justify"}
|
||||||
dangerouslySetInnerHTML={{ __html: listVisiMisi.findById.data.misi }}
|
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(listVisiMisi.findById.data.misi) }}
|
||||||
style={{
|
style={{
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
whiteSpace: 'normal',
|
whiteSpace: 'normal',
|
||||||
|
|||||||
@@ -35,35 +35,35 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
// ✅ PERBAIKAN: Gunakan format pesan yang lebih sederhana
|
// ✅ PERBAIKAN: Gunakan format pesan yang lebih sederhana
|
||||||
// Hapus karakter khusus yang bisa bikin masalah
|
// Hapus karakter khusus yang bisa bikin masalah
|
||||||
const waMessage = `Website Desa Darmasaba\nKode verifikasi Anda ${codeOtp}`;
|
// const waMessage = `Website Desa Darmasaba\nKode verifikasi Anda ${codeOtp}`;
|
||||||
|
|
||||||
// // ✅ OPSI 1: Tanpa encoding (coba dulu ini)
|
// // ✅ OPSI 1: Tanpa encoding (coba dulu ini)
|
||||||
// const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${waMessage}`;
|
// const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${waMessage}`;
|
||||||
|
|
||||||
// ✅ OPSI 2: Dengan encoding (kalau opsi 1 gagal)
|
// ✅ OPSI 2: Dengan encoding (kalau opsi 1 gagal)
|
||||||
const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodeURIComponent(waMessage)}`;
|
// const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodeURIComponent(waMessage)}`;
|
||||||
|
|
||||||
// ✅ OPSI 3: Encoding manual untuk URL-safe (alternatif terakhir)
|
// ✅ OPSI 3: Encoding manual untuk URL-safe (alternatif terakhir)
|
||||||
// const encodedMessage = waMessage.replace(/\n/g, '%0A').replace(/ /g, '%20');
|
// const encodedMessage = waMessage.replace(/\n/g, '%0A').replace(/ /g, '%20');
|
||||||
// const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodedMessage}`;
|
// const waUrl = `https://wa.wibudev.com/code?nom=${nomor}&text=${encodedMessage}`;
|
||||||
|
|
||||||
console.log("🔍 Debug WA URL:", waUrl); // Untuk debugging
|
// console.log("🔍 Debug WA URL:", waUrl); // Untuk debugging
|
||||||
|
|
||||||
const res = await fetch(waUrl);
|
// const res = await fetch(waUrl);
|
||||||
const sendWa = await res.json();
|
// const sendWa = await res.json();
|
||||||
|
|
||||||
console.log("📱 WA Response:", sendWa); // Debug response
|
// console.log("📱 WA Response:", sendWa); // Debug response
|
||||||
|
|
||||||
if (sendWa.status !== "success") {
|
// if (sendWa.status !== "success") {
|
||||||
return NextResponse.json(
|
// return NextResponse.json(
|
||||||
{
|
// {
|
||||||
success: false,
|
// success: false,
|
||||||
message: "Gagal mengirim OTP via WhatsApp",
|
// message: "Gagal mengirim OTP via WhatsApp",
|
||||||
debug: sendWa // Tampilkan error detail
|
// debug: sendWa // Tampilkan error detail
|
||||||
},
|
// },
|
||||||
{ status: 400 }
|
// { status: 400 }
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
const createOtpId = await prisma.kodeOtp.create({
|
const createOtpId = await prisma.kodeOtp.create({
|
||||||
data: { nomor, otp: otpNumber, isActive: true },
|
data: { nomor, otp: otpNumber, isActive: true },
|
||||||
|
|||||||
Reference in New Issue
Block a user