Fitur & Build Notes · · 3 menit baca
Fitur e-commerce: kartu produk & keranjang yang tidak bocor konversi
Fitur dasar website e-commerce dibedah: format Rupiah dengan Intl.NumberFormat, anatomi kartu produk yang menjual, dan keranjang localStorage dengan badge — lengkap dengan preview live.
Arabika
Kopi Gayo Premium 250g
Rp 85.000
Arabika
Kopi Toraja Sapan 250g
Rp 95.000
Robusta
Robusta Temanggung 500g
Rp 62.000
Kartu produk dan keranjang adalah dua fitur pertama yang kubangun di setiap project e-commerce klien. Terlihat sederhana, tapi di sinilah konversi bocor kalau salah: harga yang membingungkan, tombol yang tidak jelas, dan keranjang yang “lupa” isinya. Klik Lihat preview di bawah untuk melihat hasil akhirnya berjalan.
1. Format harga: selesaikan sekali, benar selamanya
Kesalahan paling umum di toko online lokal: Rp 85000 (tanpa pemisah ribuan) atau format yang berubah-ubah antar halaman. Satu helper, dipakai di mana pun:
export const rupiah = (n: number) =>
new Intl.NumberFormat("id-ID", {
style: "currency",
currency: "IDR",
maximumFractionDigits: 0, // tanpa ,00 — tidak ada yang menulis sen
}).format(n);
rupiah(85000); // "Rp 85.000"
Intl.NumberFormat bawaan browser & server — tanpa library, konsisten di mana pun.
2. Kartu produk yang menjual
Anatomi kartu yang terbukti bekerja: gambar rasio tetap, nama maksimal dua baris, harga menonjol, dan satu aksi utama:
<article class="group border border-line bg-base">
<div class="aspect-square overflow-hidden bg-surface">
<img src="/img/kopi-gayo.webp" alt="Kopi Gayo 250g" loading="lazy"
class="h-full w-full object-cover transition-transform group-hover:scale-105" />
</div>
<div class="p-4">
<p class="font-mono text-[10px] uppercase tracking-[0.18em] text-muted">Arabika</p>
<h3 class="mt-1 line-clamp-2 font-display text-[15px] font-semibold">Kopi Gayo Premium 250g</h3>
<p class="mt-2 font-display text-lg font-semibold">Rp 85.000</p>
<button class="btn-primary mt-3 w-full" data-add-to-cart data-id="kopi-gayo-250">
+ Keranjang
</button>
</div>
</article>
Detail yang sering dilupakan: loading="lazy" (halaman katalog memuat puluhan gambar), aspect-square (grid tidak “loncat” saat gambar termuat), dan line-clamp-2 (nama panjang tidak merusak tinggi kartu).
3. Keranjang yang tidak pelupa: localStorage + badge
Sebelum menyentuh backend, keranjang bisa hidup di browser — bertahan saat halaman berpindah dan saat tab ditutup:
const CART_KEY = "cart";
const readCart = () => JSON.parse(localStorage.getItem(CART_KEY) ?? "[]");
function addToCart(id) {
const cart = readCart();
const row = cart.find((r) => r.id === id);
row ? row.qty++ : cart.push({ id, qty: 1 });
localStorage.setItem(CART_KEY, JSON.stringify(cart));
renderBadge();
}
function renderBadge() {
const total = readCart().reduce((s, r) => s + r.qty, 0);
const badge = document.querySelector("[data-cart-badge]");
badge.textContent = total;
badge.hidden = total === 0; // badge "0" lebih buruk daripada tanpa badge
}
document.addEventListener("click", (e) => {
const btn = e.target.closest("[data-add-to-cart]");
if (btn) addToCart(btn.dataset.id);
});
renderBadge(); // sinkron saat halaman dibuka
Satu event listener di document (event delegation) menangani semua tombol — termasuk kartu yang datang belakangan dari infinite scroll/HTMX.
4. Kapan pindah ke keranjang server?
localStorage cukup sampai kamu butuh: keranjang lintas perangkat (user login), validasi stok saat menambah, atau harga yang bisa berubah (promo). Polanya tetap sama — addToCart tinggal berubah dari tulis-localStorage menjadi POST /cart, dan badge dirender server. Mulai sederhana; upgrade saat datanya menuntut.
Pelajaran
- Harga adalah UI paling penting di toko — formatnya tidak boleh goyah satu piksel pun antar halaman.
- Grid produk yang stabil (aspect ratio terkunci) terasa “mahal”; grid yang loncat-loncat terasa murahan.
- Keranjang kosong yang hilang saat refresh = penjualan yang hilang. localStorage adalah perbaikan 15 menit.
Mau fitur seperti ini dibangun untuk bisnismu?