K

Command Palette

Search for a command to run...

Daftar

Toko Kue: Checkout & Halaman Terima Kasih

Tahap akhir Toko Kue! Pelajari cara membuat form checkout sederhana dengan controlled components di React/Next.js, menangani submit, dan melakukan navigasi programatik ke halaman terima kasih.

"Bayar" Kue Enakmu: Bikin Form Checkout & Halaman Terima Kasih!

Toko Kue Online kita udah hampir jadi! Pengunjung udah bisa liat produk, detail produk, dan masukin kue ke keranjang belanja. Sekarang, langkah terakhir sebelum mereka (pura-puranya) bisa nikmatin kuenya adalah proses Checkout.

Di bagian ini, kita bakal:

  1. Bikin halaman Checkout yang isinya form sederhana buat pengguna masukin data (nama, alamat, email). Kita bakal pake konsep Controlled Components lagi.
  2. Nanganin submit form checkout. Karena ini contoh dasar, kita gak bakal proses pembayaran beneran ya. Cukup nampilin pesan sukses dan ngosongin keranjang.
  3. Bikin halaman "Terima Kasih" yang ditampilin setelah checkout berhasil, pake navigasi programatik.

Langkah 1: Membuat Halaman Checkout (src/app/checkout/page.tsx)

  1. Di dalam src/app/, bikin folder baru checkout.
  2. Di dalam src/app/checkout/, bikin file page.tsx.

File src/app/checkout/page.tsx:

tsx
// src/app/checkout/page.tsx
"use client"; // Karena kita akan menggunakan form dan state
 
import React, { useState, FormEvent } from 'react';
import Link from 'next/link';
import { useRouter } from 'next/navigation'; // Untuk navigasi programatik
import { useCart } from '@/context/cart-context'; // Untuk akses keranjang & kosongkan
 
export default function HalamanCheckout() {
  const router = useRouter();
  const { itemDiKeranjang, totalHarga, kosongkanKeranjang } = useCart();
 
  // State untuk field form
  const [nama, setNama] = useState('');
  const [email, setEmail] = useState('');
  const [alamat, setAlamat] = useState('');
  const [telepon, setTelepon] = useState('');
  const [catatan, setCatatan] = useState('');
 
  const handleSubmitCheckout = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault(); // Cegah reload halaman
 
    // Validasi sederhana (bisa lebih kompleks di aplikasi nyata)
    if (!nama || !email || !alamat || !telepon) {
      alert("Mohon isi semua field yang wajib diisi (Nama, Email, Alamat, Telepon).");
      return;
    }
 
    // Di aplikasi nyata, di sini kamu akan mengirim data ini ke backend
    // untuk diproses (misalnya, rekam pesanan, proses pembayaran).
    console.log("Data Checkout yang Dikirim (pura-puranya):", {
      nama, email, alamat, telepon, catatan,
      itemDipesan: itemDiKeranjang,
      totalBayar: totalHarga
    });
 
    alert(`Terima kasih, ${nama}! Pesanan Anda senilai Rp ${totalHarga.toLocaleString('id-ID')} sedang diproses.`);
    
    kosongkanKeranjang(); // Kosongkan keranjang setelah checkout
    router.push('/terima-kasih'); // Arahkan ke halaman terima kasih
  };
 
  if (itemDiKeranjang.length === 0) {
    return (
      <div className="text-center py-10">
        <h1 className="text-2xl font-bold mb-4">Keranjang Anda Kosong</h1>
        <p className="mb-6">Anda tidak bisa checkout jika keranjang masih kosong.</p>
        <Link href="/" className="bg-pink-500 hover:bg-pink-600 text-white font-semibold py-3 px-6 rounded-lg">
          Kembali Belanja
        </Link>
      </div>
    );
  }
 
  return (
    <div className="container mx-auto p-4 md:p-8 max-w-2xl">
      <h1 className="text-3xl font-bold text-gray-800 mb-6 text-center">Formulir Checkout</h1>
      <form onSubmit={handleSubmitCheckout} className="bg-white shadow-md rounded-lg px-8 pt-6 pb-8 mb-4">
        <div className="mb-4">
          <label htmlFor="nama" className="block text-gray-700 text-sm font-bold mb-2">Nama Lengkap <span className="text-red-500">*</span></label>
          <input type="text" id="nama" value={nama} onChange={(e) => setNama(e.target.value)} required 
                 className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:border-blue-500" />
        </div>
 
        <div className="mb-4">
          <label htmlFor="email" className="block text-gray-700 text-sm font-bold mb-2">Alamat Email <span className="text-red-500">*</span></label>
          <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} required
                 className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:border-blue-500" />
        </div>
 
        <div className="mb-6">
          <label htmlFor="alamat" className="block text-gray-700 text-sm font-bold mb-2">Alamat Pengiriman <span className="text-red-500">*</span></label>
          <textarea id="alamat" value={alamat} onChange={(e) => setAlamat(e.target.value)} required rows={3}
                    className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:border-blue-500"></textarea>
        </div>
 
        <div className="mb-4">
          <label htmlFor="telepon" className="block text-gray-700 text-sm font-bold mb-2">Nomor Telepon <span className="text-red-500">*</span></label>
          <input type="tel" id="telepon" value={telepon} onChange={(e) => setTelepon(e.target.value)} required
                 className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:border-blue-500" />
        </div>
        
        <div className="mb-6">
          <label htmlFor="catatan" className="block text-gray-700 text-sm font-bold mb-2">Catatan Tambahan (Opsional)</label>
          <textarea id="catatan" value={catatan} onChange={(e) => setCatatan(e.target.value)} rows={2}
                    className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:border-blue-500"></textarea>
        </div>
 
        <div className="mb-6 p-4 bg-gray-100 rounded">
          <h3 className="font-semibold text-lg mb-2">Ringkasan Pesanan</h3>
          {itemDiKeranjang.map(item => (
            <div key={item.id} className="flex justify-between text-sm mb-1">
              <span>{item.nama} x {item.jumlah}</span>
              <span>Rp {(item.harga * item.jumlah).toLocaleString('id-ID')}</span>
            </div>
          ))}
          <hr className="my-2"/>
          <div className="flex justify-between font-bold text-md">
            <span>Total Bayar:</span>
            <span>Rp {totalHarga.toLocaleString('id-ID')}</span>
          </div>
        </div>
 
        <div className="flex items-center justify-between">
          <button type="submit"
                  className="bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-6 rounded focus:outline-none focus:shadow-outline transition-colors">
            Proses Pesanan
          </button>
          <Link href="/keranjang" className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800">
            Kembali ke Keranjang
          </Link>
        </div>
      </form>
    </div>
  );
}

Bedah HalamanCheckout.tsx:

  • "use client";: Komponen ini butuh state dan event handling, jadi harus Client Component.
  • useRouter(): Kita impor dan pake buat navigasi programatik nanti.
  • useCart(): Kita pake buat ngambil data itemDiKeranjang, totalHarga, dan fungsi kosongkanKeranjang.
  • State Form: Kita bikin beberapa useState buat tiap field input (nama, email, alamat, telepon, catatan). Ini adalah Controlled Components.
  • handleSubmitCheckout(event):
    • event.preventDefault(): Mencegah submit form tradisional.
    • Validasi sederhana: Ngecek field wajib diisi.
    • Di sini kita cuma console.log data dan nampilin alert. Di aplikasi nyata, ini tempat kamu ngirim data ke backend API.
    • kosongkanKeranjang(): Manggil fungsi dari CartContext buat ngosongin keranjang.
    • router.push('/terima-kasih'): Arahin pengguna ke halaman "Terima Kasih".
  • Kondisi Keranjang Kosong: Kalau keranjang kosong, kita nampilin pesan dan tombol buat balik belanja, bukan form checkout.
  • Form JSX:
    • Struktur form biasa dengan <label> dan <input>/<textarea>.
    • Tiap input di-link ke state-nya masing-masing (value={nama}, onChange={(e) => setNama(e.target.value)}).
    • Ada ringkasan pesanan yang diambil dari itemDiKeranjang dan totalHarga.
    • Tombol "Proses Pesanan" bertipe submit.
    • Ada link buat balik ke keranjang.
  • Styling: Saya tambahin beberapa class Tailwind dasar biar tampilannya lumayan rapi.

Langkah 2: Membuat Halaman Terima Kasih (src/app/terima-kasih/page.tsx)

Ini halaman simpel yang ditampilin setelah checkout "berhasil".

  1. Di dalam src/app/, bikin folder baru terima-kasih.
  2. Di dalam src/app/terima-kasih/, bikin file page.tsx.

File src/app/terima-kasih/page.tsx:

tsx
// src/app/terima-kasih/page.tsx
import React from 'react';
import Link from 'next/link';
 
export default function HalamanTerimaKasih() {
  return (
    <div className="container mx-auto p-4 md:p-8 text-center min-h-[60vh] flex flex-col justify-center items-center">
      <div className="bg-green-100 border-l-4 border-green-500 text-green-700 p-6 rounded-md shadow-lg max-w-md">
        <h1 className="text-3xl font-bold mb-4">Pesanan Diterima! 🎉</h1>
        <p className="text-lg mb-2">Terima kasih telah berbelanja di Toko Kue Mahir.dev!</p>
        <p className="mb-6">Kami (pura-puranya) akan segera memproses pesanan Anda.</p>
      </div>
      <Link href="/" className="mt-8 inline-block bg-blue-500 hover:bg-blue-700 text-white font-semibold py-3 px-6 rounded-lg transition-colors">
        Kembali ke Beranda
      </Link>
    </div>
  );
}

Bedah HalamanTerimaKasih.tsx:

  • Ini komponen yang sangat simpel, cuma nampilin pesan terima kasih dan link buat balik ke beranda.
  • Bisa kamu tambahin info lain kalau mau (misal, nomor pesanan bohongan).

Langkah 3: Update Styling (Jika Perlu)

Kamu bisa nambahin style lagi di src/app/globals.css atau App.css buat halaman checkout dan terima kasih biar makin cakep. Contoh di atas udah pake beberapa utility Tailwind dasar.

Tes Alur Checkout!

  1. Pastikan dev server jalan (npm run dev).
  2. Masukin beberapa item ke keranjang belanja.
  3. Pindah ke halaman keranjang (/keranjang).
  4. Klik tombol "Lanjut ke Checkout". Kamu bakal dibawa ke /checkout.
  5. Isi form checkout. Coba submit dengan field kosong, harusnya ada alert.
  6. Isi semua field wajib, terus klik "Proses Pesanan".
  7. Harusnya kamu liat alert "Pesanan Diterima...", keranjang belanjamu (di localStorage dan di tampilan kalau ada indikatornya) jadi kosong, dan kamu otomatis diarahkan ke halaman /terima-kasih.
  8. Di halaman terima kasih, klik link buat balik ke beranda.

Hore! Toko Kue Online sederhana kita udah punya alur lengkap dari liat produk sampe (pura-pura) checkout!

Apa yang Udah Kita Terapin di Studi Kasus Ini (Secara Keseluruhan):

  • Next.js App Router: Bikin halaman dan rute dinamis.
  • Server Components: Buat ngambil data produk awal.
  • Client Components: Buat semua bagian yang interaktif (tombol, form, keranjang).
  • React Components, Props, State (useState): Fondasi UI kita.
  • Event Handling: Buat tombol dan form.
  • Conditional Rendering: Nampilin pesan kalau keranjang kosong.
  • List Rendering (.map() dengan key): Buat nampilin daftar produk dan item keranjang.
  • TypeScript: Ngasih tipe ke data, props, state, dan event buat kode yang lebih aman.
  • Context API (useContext): Buat ngelola state keranjang secara "global".
  • localStorage & useEffect: Buat nyimpen data keranjang biar gak ilang.
  • API Routes (Route Handlers): Bikin backend mini buat data produk.
  • Navigasi Programatik (useRouter().push()).
  • (Jika kamu implementasikan) Styling dengan Tailwind CSS atau CSS biasa.

Ini udah jadi aplikasi web mini yang lumayan kompleks dan ngelibatin banyak banget konsep penting! Kamu bisa jadiin ini basis buat dieksplorasi lebih jauh, misalnya:

  • Nambahin validasi form yang lebih canggih.
  • Integrasi sama API pembayaran beneran (ini udah advance banget!).
  • Nambahin fitur filter atau sortir produk.
  • Nambahin halaman akun pengguna.

Yang pasti, dengan nyelesaiin studi kasus ini, kamu udah dapet pengalaman praktis yang sangat berharga dalam ngebangun aplikasi React (dan Next.js) dengan TypeScript.

Kuis Checkout & Halaman Terima Kasih Toko Kue (Next.js+TS)

Pertanyaan 1 dari 4

Dalam komponen HalamanCheckout, mengapa penting untuk memanggil `event.preventDefault()` di dalam fungsi `handleSubmitCheckout`?