Snippets & Fix Error
Utility Functions

Utility Functions (Snippets) 🛠️

Ini gudang fungsi-fungsi kecil ("Helper") yang wajib ada di folder src/utils atau src/lib. Tinggal copas aja kalau butuh.

1. Format Rupiah (IDR) 💰

Wajib buat Marketplace Desa atau PPDB (bayar SPP).

export const formatRupiah = (number: number) => {
  return new Intl.NumberFormat("id-ID", {
    style: "currency",
    currency: "IDR",
    minimumFractionDigits: 0,
    maximumFractionDigits: 0,
  }).format(number);
};
 
// Cara pakai:
// formatRupiah(50000) -> Output: "Rp 50.000"

2. Format Tanggal Indo 📅

Biar tanggalnya gak bahasa Inggris (e.g., "17 August 1945").

export const formatDateIndo = (dateString: string | Date) => {
  return new Date(dateString).toLocaleDateString("id-ID", {
    day: "numeric",
    month: "long",
    year: "numeric",
    weekday: "long", // senin, selasa...
  });
};
 
// Cara pakai:
// formatDateIndo("2025-08-17") -> Output: "Minggu, 17 Agustus 2025"

3. Class Name Merger (cn) 🎨

Kalau lo pake Tailwind + Shadcn UI, fungsi ini wajib ada buat ngegabungin class biar gak konflik/tabrakan. Syarat: install dulu npm i clsx tailwind-merge

import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
 
// Cara pakai:
// <div className={cn("bg-red-500 p-4", props.className)}>
// (Class bawaan bg-red-500 bakal aman gak ketimpa sembarangan)

4. Delay / Sleep ⏳

Buat simulasi loading pas lagi testing komponen loading.tsx.

export const wait = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));
 
// Cara pakai:
// await wait(2000); // Nunggu 2 detik baru kode bawahnya jalan

5. Format File Size (Bytes to MB) 📦

Pas lo upload file pake UploadThing, ukuran filenya itu dalam Bytes (angka doang). User gak ngerti "2048000 bytes". Mereka ngertinya "2 MB". Pake fungsi ini.

export function formatBytes(bytes: number, decimals = 2) {
  if (!+bytes) return "0 Bytes";
 
  const k = 1024;
  const dm = decimals < 0 ? 0 : decimals;
  const sizes = ["Bytes", "KB", "MB", "GB", "TB"];
 
  const i = Math.floor(Math.log(bytes) / Math.log(k));
 
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
 
// Cara pakai:
// formatBytes(2500000) -> Output: "2.38 MB"

6. Slug Generator (SEO Friendly URL) 🔗

Kalau lo bikin blog atau Produk Desa. Judul: "Cara Ternak Lele 2024" -> URL: /blog/cara-ternak-lele-2024

export function slugify(text: string) {
  return text
    .toString()
    .toLowerCase()
    .trim()
    .replace(/\s+/g, "-") // Ganti spasi jadi strip
    .replace(/[^\w\-]+/g, "") // Buang karakter aneh (?!@#)
    .replace(/\-\-+/g, "-"); // Ganti strip dobel jadi satu
}
 
// Cara pakai:
// slugify("Halo Dunia! Apa Kabar?") -> Output: "halo-dunia-apa-kabar"

7. Truncate Text (Potong Teks) ✂️

Buat nampilin cuplikan berita di halaman depan (Dashboard). Kalau teksnya kepanjangan, potong terus kasih titik-titik "...".

export function truncate(str: string, length: number) {
  if (!str) return "";
  if (str.length <= length) return str;
  return str.slice(0, length) + "...";
}
 
// Cara pakai:
// truncate("Ini adalah deskripsi produk yang sangat panjang sekali lho", 20)
// Output: "Ini adalah deskripsi..."

8. Upload dan hapus File (Cloudinary) 📸

Kalo lo pake Cloudinary sebagai penyimpanan file lo dan ingin upload dan hapus file. Buat fungsi ini:

Fungsi Upload:

export const uploadToCloudinarySigned = async (
  file,
  folder = "ggs",
  resourceType = "image",
) => {
  // VALIDASI UKURAN GAMBAR - 1MB
  let LIMIT_MB = 1;
 
  // jika ukuran gambar lebih besar dari limit lempar error
  if (file.size > LIMIT_MB * 1024 * 1024) {
    throw new Error(`Ukuran file terlalu besar! Maksimal ${LIMIT_MB}MB.`);
  }
 
  try {
    // MINTA TANDA TANGAN KE SERVER
    const signRes = await fetch("/api/cloudinary/sign", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ folder }),
    });
 
    if (!signRes.ok) throw new Error("Gagal mendapatkan izin upload.");
 
    const signData = await signRes.json();
 
    // UPLOAD KE CLOUDINARY DENGAN TANDA TANGAN
    const formData = new FormData(); // formdata untuk upload
    formData.append("file", file); // file dari input
    formData.append("api_key", signData.apiKey); // api key cloudinary
    formData.append("timestamp", signData.timestamp); // timestamp dari server
    formData.append("signature", signData.signature); // tandatangan dari server
    formData.append("folder", folder); // folder tujuan di cloudinary
 
    // URL API Cloudinary => resource_type: image
    const uploadUrl = `https://api.cloudinary.com/v1_1/${signData.cloudName}/${resourceType}/upload`;
 
    //     Lakukan upload
    const uploadRes = await fetch(uploadUrl, {
      method: "POST",
      body: formData,
    });
 
    //     jika gagal lempar error
    if (!uploadRes.ok) {
      const err = await uploadRes.json();
      throw new Error(err.error?.message || "Gagal upload ke cloud.");
    }
 
    //     ambil hasil upload
    const result = await uploadRes.json();
 
    // KEMBALIKAN URL FILE
    return result.secure_url;
  } catch (error) {
    throw error;
  }
};
  • Cara pakai:
const imageUrl = await uploadToCloudinarySigned(file, "ggs", "image");

Fungsi Hapus:

export const deleteFromCloudinary = async (url, resourceType = "image") => {
  if (!url) return; // jika tidak ada url, tidak perlu dihapus
 
  // Ekstrak Public ID dari URL
  // Public ID: folder/filename
  try {
    const parts = url.split("/"); // pecah berdasarkan '/'
    const uploadIndex = parts.indexOf("upload"); // cari index 'upload'
    if (uploadIndex === -1) return; // jika tidak ada 'upload', keluar
 
    // Ambil bagian setelah 'version' (v123...)
    const pathParts = parts.slice(uploadIndex + 2);
    const fileNameWithExt = pathParts.join("/"); // gabungkan kembali menjadi path file
 
    // Hapus ekstensi (.jpg, .docx) untuk mendapatkan public_id murni
    const publicId = fileNameWithExt.substring(
      0,
      fileNameWithExt.lastIndexOf("."),
    );
 
    // Panggil API Delete
    const res = await fetch("/api/cloudinary/delete", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ publicId, resourceType }),
    });
 
    if (!res.ok) throw new Error("Gagal menghapus file lama");
 
    return true;
  } catch (error) {
    console.error("Gagal hapus file:", error);
    return false; // Kembalikan false jika gagal agar ui tidak error
  }
};
  • Cara Pakai:
const imageUrl = await uploadToCloudinarySigned(url, "image");

9. Format Angka (Ribuan) 📈

Buat format angka ribuan. Pake fungsi ini:

export const formatSingkatNomer = (number) => {
  if (number === null || number === undefined) return "0";
 
  return new Intl.NumberFormat("id-ID", {
    notation: "compact",
    compactDisplay: "short",
    maximumFractionDigits: 1,
  }).format(number);
};
 
// cara pakai => formatSingkatNomer(50000) => "50k"