PART 1 - Setup Proyek dan membuat halaman Home, Produk, dan Keranjang

Pada bagian pertama ini, kita akan memulai dengan menyiapkan proyek Next.js dan menginstal beberapa library pendukung yang akan digunakan untuk membangun toko online sederhana. Tahapan ini penting agar struktur proyek rapi, mudah dikelola, dan siap untuk dikembangkan pada bagian-bagian selanjutnya.

Tujuan utama pada Part 1 adalah

  • Setup Next.js di komputer
  • Menginstal library yang diperlukan
  • Menyiapkan struktur folder yang rapi
  • Membuat komponen dasar UI
  • Membuat halaman Home, Produk, Produk Detail, dan Keranjang
  • Menjalankan aplikasi di browser
Mari kita mulai bagaimana langkah-langkah detail nya.

  • Setup Next.js

Langkah awal adalah membuat project baru menggunakan Next.js. Framework ini dipilih karena mendukung fitur server-side rendering (SSR), routing bawaan, serta fitur lainnya yang sangat dibutuhkan dalam proses pengembangan web modern.

  • Install Library yang Dibutuhkan

Agar aplikasi bisa berjalan dengan baik serta mendukung berbagai fitur seperti fetch API, state management, icon, dan notifikasi, kita perlu menginstal beberapa library berikut:
Mari kita mulai bagaimana langkah-langkah nya.

  • Setup Next.js di Lokal

Langkah pertama adalah menginstal Next.js. Buka folder dimana anda akan menyimpan project nya, kemudian buka terminal dan jalankan perintah berikut pada terminal:
npx create-next-app@latest
Lanjutkan intruksi seperti pada Gambar 1 sampai selesai
Gambar 1

Lalu masuk ke folder proyek, seperti pada Gambar 2:
Gambar 2

  • Instal Library yang Dibutuhkan

Selanjutnya kita menginstal beberapa package yang akan digunakan:

  • axios → melakukan fetch data API
  • zustand → state management
  • react-icons → icon
  • react-hot-toast → notifikasi
Perintah untuk menginstal semuanya:
npm install axios zustand react-icons react-hot-toast
Jika sudah berhasil akan terlihat di terminal seperti pada Gambar 3 berikut:
Gambar 3

  • Membuka Proyek di VS Code

Gunakan perintah pada terminal seperti pada code berikut:
code .
Tampilan pada VSCode seperti pada Gambar 4
Gambar 4

Setelah terbuka, struktur awal folder seperti pada Gambar 5 berikut:
Gambar 5
Berikut penjelasan singkat dan jelas mengenai folder awal:
.next
Folder build yang dibuat otomatis oleh Next.js. Berisi hasil kompilasi, cache, dan file yang diperlukan untuk menjalankan aplikasi. Tidak perlu diedit secara manual.
node_modules
Berisi semua library dan dependensi yang diinstal oleh project. Folder ini dibuat otomatis setelah menjalankan npm install.
public
Tempat menyimpan file statis seperti gambar, ikon, atau aset lain yang dapat diakses langsung melalui URL.
app
Folder utama untuk mengatur halaman, routing, layout, dan komponen berbasis App Router. Di dalamnya terdapat file seperti layout.js dan page.js yang mengatur tampilan aplikasi.


  • Mengganti Format File

Next.js secara default membuat file layout.js dan page.js.
Agar konsisten dengan standar React, ubah keduanya menjadi:
  • layout.jsx
  • page.jsx
Jika sudah diubah seperti pada Gambar 6 berikut:
Gambar 6

  • Membuat Struktur Folder Komponen

Agar mudah di-maintenance, kita pisahkan semua component ke dalam folder khusus.

Buat folder di dalam src dengan nama components, di dalam folder components buat beberapa folder berikut: layouts, pages, dan ui

Setelah anda membuat akan seperti Gambar 7 berikut:

Gambar 7

Penjelasan Folder
  • layouts → berisi layout global atau layout halaman
  • pages → berisi komponen untuk halaman
  • ui → komponen kecil seperti button, card, navbar, dsb

  • Membuat Komponen di Folder UI

Di dalam folder ui, buat folder lagi dengan nama berikut:
  • button
  • card
  • error
  • footer
  • loading
  • navbar
  • section
Jika sudah, maka hasil nya seperti Gambar 8 berikut:
Gambar 8

Selanjutnya kita akan membuat componen di dalam folder tersebut.

Dalam folder button buat file dengan nama Button.jsx, kemudian masukan code berikut pada file tersebut:

import React from "react";

export default function Button({ className, children, ...props }) {
  return (
    <button
      type="button"
      className={`text-white flex cursor-pointer items-center gap-1 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg px-3 py-2 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800 ${className}`}
      {...props}
    >
      {children}
    </button>
  );
}
Dalam folder card buat beberapa file berikut:
  • ProductCard.jsx
  • ProductCartCard.jsx
  • SummaryOrderCard.jsx
Masukan kode berikut pada ProductCard.jsx
import Link from "next/link";
import { FaBagShopping } from "react-icons/fa6";
import Button from "../button/Button";

export default function ProductCard({ product }) {
  return (
    <div className="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 hover:shadow-xl shadow-gray-300 transition-all duration-300 ">
      <Link href={`/products/${product.id}`}>
        <img
          className="p-4 rounded-t-lg h-72 w-full object-contain"
          src={product.image}
          alt="product image"
        />
      </Link>
      <div className="px-5 pb-5 flex flex-col gap-2">
        <Link href={`/products/${product.id}`}>
          <h5 className="text-xl font-semibold tracking-tight text-gray-900 dark:text-white">
            {product.name}
          </h5>
        </Link>
        <div className="flex justify-between gap-2 items-center">
          <span className="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-gray-700 dark:text-blue-400 border border-blue-400">
            {product.category.name}
          </span>
          <span className="text-gray-700 text-base">
            {product.stock} Tersedia
          </span>
        </div>
        <div className="flex items-center justify-between">
          <span className="text-xl font-bold text-gray-900 dark:text-white">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(product.price))}
          </span>
          <Button
            className={`text-xs ${product.stock === 0 && "opacity-50"}`}
            disabled={product.stock === 0}
          >
            <FaBagShopping className="size-4" />
            Tambah ke Keranjang
          </Button>
        </div>
      </div>
    </div>
  );
}

Masukan kode berikut pada ProductCartCard.jsx
import Link from "next/link";
import { FaTrashAlt } from "react-icons/fa";
import { FaCircleMinus, FaCirclePlus } from "react-icons/fa6";

export default function ProductCartCard({ productCart }) {
  return (
    <div className="flex gap-4 bg-white px-4 py-6 rounded-md shadow-sm border border-gray-200">
      <div className="flex items-center w-fit">
        <input
          id="checked-checkbox"
          type="checkbox"
          className="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded-sm focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
        />
      </div>
      <div className="flex gap-6 sm:gap-4 max-sm:flex-col">
        <div className="w-24 h-24 max-sm:w-24 max-sm:h-24 shrink-0">
          <img
            src={productCart.image}
            alt="product image"
            className="w-full h-full object-contain border rounded-sm border-gray-200"
          />
        </div>
        <div className="flex flex-col gap-4">
          <div>
            <Link href={`/products/${productCart.id}`}>
              <h3 className="text-sm sm:text-base font-semibold text-slate-900 hover:underline">
                {productCart.name}
              </h3>
            </Link>
            <span className="bg-blue-100 text-blue-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded-sm dark:bg-gray-700 dark:text-blue-400 border border-blue-400">
              {productCart.category.name}
            </span>
          </div>
          <div className="mt-auto">
            <h3 className="text-sm font-semibold text-slate-900">
              {new Intl.NumberFormat("id-ID", {
                style: "currency",
                currency: "IDR",
                minimumFractionDigits: 0,
              }).format(parseFloat(productCart.price * productCart.quantity))}
            </h3>
          </div>
        </div>
      </div>

      <div className="ml-auto flex flex-col">
        <div className="flex items-start gap-4 justify-end">
          <button type="button">
            <FaTrashAlt className="text-red-500" />
          </button>
        </div>
        <div className="flex items-center gap-3 mt-auto">
          <button type="button" disabled={productCart.quantity === 1}>
            <FaCircleMinus
              className={`size-4 ${
                productCart.quantity === 1 ? "text-gray-400" : "text-gray-800"
              }`}
            />
          </button>
          <span className="font-semibold text-base leading-[18px]">
            {productCart.quantity}
          </span>
          <button
            type="button"
            disabled={productCart.quantity === productCart.stock}
          >
            <FaCirclePlus
              className={`size-4 ${
                productCart.quantity === productCart.stock
                  ? "text-gray-400"
                  : "text-gray-800"
              }`}
            />
          </button>
        </div>
      </div>
    </div>
  );
}

Masukan kode berikut pada SummaryOrderCard.jsx
export default function SummaryOrderCard() {
  const SHIPMENT_COST = 0;

  const subTotal = 10000;

  const total = subTotal + SHIPMENT_COST;

  return (
    <div className="bg-white rounded-md px-4 py-6 h-max shadow-sm border border-gray-200">
      <ul className="text-slate-500 font-medium space-y-4">
        <li className="flex flex-wrap gap-4 text-sm">
          Subtotal{" "}
          <span className="ml-auto font-semibold text-slate-900">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(subTotal))}
          </span>
        </li>
        <li className="flex flex-wrap gap-4 text-sm">
          Pengiriman{" "}
          <span className="ml-auto font-semibold text-slate-900">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(SHIPMENT_COST))}
          </span>
        </li>
        <hr className="border-slate-300" />
        <li className="flex flex-wrap gap-4 text-sm font-semibold text-slate-900">
          Total{" "}
          <span className="ml-auto">
            {new Intl.NumberFormat("id-ID", {
              style: "currency",
              currency: "IDR",
              minimumFractionDigits: 0,
            }).format(parseFloat(total))}
          </span>
        </li>
      </ul>
      <div className="mt-8 space-y-4">
        <button
          type="button"
          className={`text-sm px-4 py-2.5 w-full font-medium tracking-wide bg-blue-800 hover:bg-blue-900 text-white rounded-md cursor-pointer`}
        >
          Beli Sekarang
        </button>
      </div>
    </div>
  );
}

Pada folder error buat file dengan nama FetchError.jsx, kemudian masukan code berikut:
import React from "react";
import { BiSolidError } from "react-icons/bi";

export default function FetchError({ text = "Terjadi Kesalahan" }) {
  return (
    <div className="flex flex-col items-center">
      <BiSolidError className="size-16 text-red-600" />
      <p className="text-red-600 text-base">{text}</p>
    </div>
  );
}


Pada folder footer buat file dengan nama FrontpageFooter, kemudian masukan kode berikut:
import React from "react";

export default function FrontpageFooter() {
  return (
    <footer className="bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700">
      <div className="mx-auto w-full max-w-screen-xl p-6 lg:py-8">
        <div className="md:flex md:justify-between">
          <div className="mb-6 md:mb-0">
            <a href="/" className="flex items-center">
              <img src="/favicon.ico" className="h-8 me-3" alt="Logo" />
              <span className="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">
                Simple Ecommerce
              </span>
            </a>
          </div>

          <div className="grid grid-cols-2 gap-8 sm:gap-6 sm:grid-cols-3">
            <div>
              <h2 className="mb-4 text-sm font-semibold text-gray-900 uppercase dark:text-white">
                Tentang Kami
              </h2>
              <ul className="text-gray-500 dark:text-gray-400 font-medium space-y-2">
                <li>
                  <a href="#" className="hover:underline">
                    Profil Kami
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Kontak Kami
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Karir
                  </a>
                </li>
              </ul>
            </div>

            <div>
              <h2 className="mb-4 text-sm font-semibold text-gray-900 uppercase dark:text-white">
                Bantuan
              </h2>
              <ul className="text-gray-500 dark:text-gray-400 font-medium space-y-2">
                <li>
                  <a href="#" className="hover:underline">
                    FAQ
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Pengiriman
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Kebijakan Pengembalian
                  </a>
                </li>
              </ul>
            </div>

            <div>
              <h2 className="mb-4 text-sm font-semibold text-gray-900 uppercase dark:text-white">
                Ikuti Kami
              </h2>
              <ul className="text-gray-500 dark:text-gray-400 font-medium space-y-2">
                <li>
                  <a href="#" className="hover:underline">
                    Instagram
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Facebook
                  </a>
                </li>
                <li>
                  <a href="#" className="hover:underline">
                    Twitter
                  </a>
                </li>
              </ul>
            </div>
          </div>
        </div>

        <hr className="my-6 border-gray-200 sm:mx-auto dark:border-gray-700 lg:my-8" />

        <div className="sm:flex sm:items-center sm:justify-between">
          <span className="text-sm text-gray-500 sm:text-center dark:text-gray-400">
            © {new Date().getFullYear()}{" "}
            <a href="/" className="hover:underline">
              Simple Ecommerce
            </a>
          </span>

          <div className="flex mt-4 sm:justify-center sm:mt-0 space-x-5">
            <a
              href="#"
              className="text-gray-500 hover:text-gray-900 dark:hover:text-white"
              aria-label="Facebook"
            >
              <i className="fa-brands fa-facebook-f"></i>
            </a>
            <a
              href="#"
              className="text-gray-500 hover:text-gray-900 dark:hover:text-white"
              aria-label="Instagram"
            >
              <i className="fa-brands fa-instagram"></i>
            </a>
            <a
              href="#"
              className="text-gray-500 hover:text-gray-900 dark:hover:text-white"
              aria-label="Twitter"
            >
              <i className="fa-brands fa-x-twitter"></i>
            </a>
          </div>
        </div>
      </div>
    </footer>
  );
}

Pada folder loading buat file dengan nama SpinnerLoading.jsx, kemudian masukan kode berikut:
import React from "react";

export default function SpinnerLoading() {
  return (
    <div
      role="status"
      className="flex flex-col justify-center items-center gap-1"
    >
      <svg
        aria-hidden="true"
        className="inline size-12 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600"
        viewBox="0 0 100 101"
        fill="none"
        xmlns="http://www.w3.org/2000/svg"
      >
        <path
          d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
          fill="currentColor"
        />
        <path
          d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
          fill="currentFill"
        />
      </svg>
      <p>Loading...</p>
    </div>
  );
}

Pada folder navbar buat file dengan nama FrontpageNavbar.jsx, kemudian masukan kode berikut:
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { FaBagShopping } from "react-icons/fa6";
import Button from "../button/Button";

export default function FrontpageNavbar() {
  const [isOpenDropdown, setIsOpenDropdown] = useState(false);
  const navbarRef = useRef(null);
  const pathname = usePathname();

  // click outside
  useEffect(() => {
    const handleClickOutside = (event) => {
      if (navbarRef.current && !navbarRef.current.contains(event.target)) {
        setIsOpenDropdown(false);
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [navbarRef]);

  return (
    <nav
      ref={navbarRef}
      className="bg-white dark:bg-gray-900 fixed w-full z-20 top-0 start-0 border-b border-gray-200 dark:border-gray-600"
    >
      <div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
        <Link
          href="/"
          className="flex items-center space-x-3 rtl:space-x-reverse"
        >
          <img src="/favicon.ico" className="h-8" alt="Flowbite Logo" />
          <span className="self-center text-lg sm:text-2xl font-semibold whitespace-nowrap dark:text-white">
            Simple Ecommerce
          </span>
        </Link>
        <div className="flex md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
          <Link href="/carts">
            <Button className={"text-xs"}>
              <FaBagShopping className="size-4" />
              <span>0</span>
            </Button>
          </Link>
          <button
            data-collapse-toggle="navbar-sticky"
            type="button"
            className="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
            aria-controls="navbar-sticky"
            aria-expanded="false"
            onClick={() => setIsOpenDropdown(!isOpenDropdown)}
          >
            <span className="sr-only">Open main menu</span>
            <svg
              className="w-5 h-5"
              aria-hidden="true"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 17 14"
            >
              <path
                stroke="currentColor"
                strokeLinecap="round"
                strokeLinejoin="round"
                strokeWidth="2"
                d="M1 1h15M1 7h15M1 13h15"
              />
            </svg>
          </button>
        </div>
        <div
          className={`items-center justify-between ${
            isOpenDropdown ? "" : "hidden"
          } w-full md:flex md:w-auto md:order-1`}
          id="navbar-sticky"
        >
          <ul className="flex flex-col p-4 md:p-0 mt-4 font-medium border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
            <li>
              <Link
                href="/"
                className={`
      block py-2 px-3 rounded-sm 
      ${
        pathname === "/"
          ? "text-white bg-blue-700 md:bg-transparent md:text-blue-700 md:dark:text-blue-500"
          : "text-gray-900 hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
      }
    `}
              >
                Home
              </Link>
            </li>
            <li>
              <Link
                href="/products"
                className={`
      block py-2 px-3 rounded-sm 
      ${
        pathname.startsWith("/products")
          ? "text-white bg-blue-700 md:bg-transparent md:text-blue-700 md:dark:text-blue-500"
          : "text-gray-900 hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:dark:hover:text-blue-500 dark:text-white dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent"
      }
    `}
              >
                Produk
              </Link>
            </li>
          </ul>
        </div>
      </div>
    </nav>
  );
}

Pada folder section buat file dengan nama HomeHeroSection.jsx, kemudian masukan kode berikut:
import Link from "next/link";
import React from "react";
import Button from "../button/Button";
import { FaArrowRightLong } from "react-icons/fa6";

export default function HomeHeroSection() {
  return (
    <section className="relative isolate overflow-hidden bg-white dark:bg-gray-900 py-12">
      <div className="mx-auto max-w-2xl text-center">
        <h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-white sm:text-6xl">
          Belanja Mudah & Aman di{" "}
          <span className="text-blue-600">Simple Ecommerce</span>
        </h1>
        <p className="mt-6 text-lg leading-8 text-gray-600 dark:text-gray-400">
          Temukan berbagai produk pilihan dengan harga terbaik. Nikmati
          pengalaman belanja online yang cepat, aman, dan menyenangkan.
        </p>

        <div className="mt-10 flex items-center justify-center gap-x-6">
          <Link href="/products">
            <Button>
              Belanja Sekarang
              <FaArrowRightLong />
            </Button>
          </Link>
        </div>
      </div>
    </section>
  );
}

Component pada folder ui sudah kita buat, jika mengikuti dengan seksama maka struktur nya akan seperti pada Gambar 9 berikut:
Gambar 9


  • Membuat Layout dan Halaman

Langkah selanjutnya kita akan membuat component pada folder layout dan pages

Pada folder layout buat file dengan nama FrontpageLayout.jsx kemudian masukan kode berikut:

import { Toaster } from "react-hot-toast";
import FrontpageFooter from "../ui/footer/FrontpageFooter";
import FrontpageNavbar from "../ui/navbar/FrontpageNavbar";

export default function FrontpageLayout({ children }) {
  return (
    <>
      <Toaster position="top-center" reverseOrder={false} />
      <FrontpageNavbar />
      <div className="min-h-[60vh] mt-24 mb-16 max-w-screen-xl mx-auto">
        {children}
      </div>
      <FrontpageFooter />
    </>
  );
}

Dalam kode tersebut kita mengambil component yang sudah kita buat sebelumnya di folder ui, dan ada konfigurasi untuk Toaster (react-hot-toast) kode nya berikut:
<Toaster position="top-center" reverseOrder={false} />
Pada folder pages kita buat beberapa folder yaitu: cart, home, dan product, struktur nya seperti Gambar 10 berikut:
Gambar 10

Selanjutnya pada folder cart buatkan file dengan nama CartPage.jsx, kemudian masukan kode berikut:
"use client";
import FrontpageLayout from "@/components/layouts/FrontpageLayout";
import Button from "@/components/ui/button/Button";
import ProductCartCard from "@/components/ui/card/ProductCartCard";
import SummaryOrderCard from "@/components/ui/card/SummaryOrderCard";
import FetchError from "@/components/ui/error/FetchError";
import Link from "next/link";
import { FaArrowRightLong } from "react-icons/fa6";

export default function CartPageContent() {
  const carts = [
    {
      id: 1,
      name: "Smartphone Android Terbaru",
      description:
        "Ponsel pintar dengan kamera 108MP dan RAM 8GB. Performa tinggi untuk gaming dan multitasking.",
      image:
        "https://images.unsplash.com/photo-1592890288564-76628a30a657?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1170",
      price: "5999000.00",
      stock: 15,
      slug: "smartphone-android-terbaru-OfxcQ",
      category: {
        id: 1,
        name: "Elektronik",
        description: "Produk-produk seperti ponsel, laptop, dan TV.",
        created_at: "2025-11-03 08:06:01",
        updated_at: "2025-11-03 08:06:01",
      },
      created_at: "2025-11-03 08:06:01",
      updated_at: "2025-11-03 08:06:01",
      quantity: 1,
      isSelected: false,
    },
    {
      id: 2,
      name: "Laptop Ultra Tipis 13 Inci",
      description:
        "Laptop ringan dan portabel. Ideal untuk profesional yang sering bepergian.",
      image:
        "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1171",
      price: "12500000.00",
      stock: 0,
      slug: "laptop-ultra-tipis-13-inci-Dr7oL",
      category: {
        id: 1,
        name: "Elektronik",
        description: "Produk-produk seperti ponsel, laptop, dan TV.",
        created_at: "2025-11-03 08:06:01",
        updated_at: "2025-11-03 08:06:01",
      },
      created_at: "2025-11-03 08:06:01",
      updated_at: "2025-11-03 08:06:01",
      quantity: 2,
      isSelected: true,
    },
  ];

  return (
    <FrontpageLayout>
      <h3 className="font-bold text-gray-800 text-3xl text-start px-4 mb-4">
        Keranjang
      </h3>
      {!carts.length ? (
        <div className="flex flex-col gap-4 items-center">
          <FetchError text="Produk tidak ditemukan, silahkan tambahkan produk ke keranjang" />
          <Link href={"/products"}>
            <Button>
              Belanja Sekarang
              <FaArrowRightLong />
            </Button>
          </Link>
        </div>
      ) : (
        <>
          <div
            key={carts.length}
            className="grid lg:grid-cols-3 lg:gap-x-8 gap-x-6 gap-y-8 mt-6 px-4"
          >
            <div className="lg:col-span-2 space-y-6">
              {carts.map((cart, index) => (
                <ProductCartCard key={index} productCart={cart} />
              ))}
            </div>

            <SummaryOrderCard />
          </div>
          <div className="p-4">
            <button
              type="button"
              className="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900"
            >
              Bersihkan Keranjang
            </button>
          </div>
        </>
      )}
    </FrontpageLayout>
  );
}
Sementara kita pake data dummy yang tersimpan dalam variabel carts

Pada folder home buat file dengan nama HomePage.jsx, kemudian masukan kode berikut:
import FrontpageLayout from "@/components/layouts/FrontpageLayout";
import HomeHeroSection from "@/components/ui/section/HomeHeroSection";

export default function HomePageContent() {
  return (
    <FrontpageLayout>
      <HomeHeroSection />
    </FrontpageLayout>
  );
}

Pada folder product kita akan membuat 2 file yaitu:

  • ProductPage.jsx
  • ProductDetailPage.jsx
Masukan kode berikut pada ProductPage.jsx

"use client";
import FrontpageLayout from "@/components/layouts/FrontpageLayout";
import ProductCard from "@/components/ui/card/ProductCard";
import FetchError from "@/components/ui/error/FetchError";

export default function ProductPageContent() {
  const productList = [
    {
      id: 1,
      name: "Smartphone Android Terbaru",
      description:
        "Ponsel pintar dengan kamera 108MP dan RAM 8GB. Performa tinggi untuk gaming dan multitasking.",
      image:
        "https://images.unsplash.com/photo-1592890288564-76628a30a657?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1170",
      price: "5999000.00",
      stock: 15,
      slug: "smartphone-android-terbaru-OfxcQ",
      category: {
        id: 1,
        name: "Elektronik",
        description: "Produk-produk seperti ponsel, laptop, dan TV.",
        created_at: "2025-11-03 08:06:01",
        updated_at: "2025-11-03 08:06:01",
      },
      created_at: "2025-11-03 08:06:01",
      updated_at: "2025-11-03 08:06:01",
    },
    {
      id: 2,
      name: "Laptop Ultra Tipis 13 Inci",
      description:
        "Laptop ringan dan portabel. Ideal untuk profesional yang sering bepergian.",
      image:
        "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1171",
      price: "12500000.00",
      stock: 0,
      slug: "laptop-ultra-tipis-13-inci-Dr7oL",
      category: {
        id: 1,
        name: "Elektronik",
        description: "Produk-produk seperti ponsel, laptop, dan TV.",
        created_at: "2025-11-03 08:06:01",
        updated_at: "2025-11-03 08:06:01",
      },
      created_at: "2025-11-03 08:06:01",
      updated_at: "2025-11-03 08:06:01",
    },
  ];

  return (
    <FrontpageLayout>
      <div className="flex flex-col gap-4 w-full px-4">
        <div className="flex justify-between items-center gap-2">
          <h3 className="font-bold text-gray-800 text-3xl text-start mb-4">
            Produk
          </h3>

          <form className="w-1/2 sm:w-1/3">
            <label
              htmlFor="default-search"
              className="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white"
            >
              Search
            </label>
            <div className="relative">
              <input
                type="search"
                id="default-search"
                className="block w-full p-4 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
                placeholder="Cari produk..."
              />
              <button
                type="submit"
                className="text-white absolute end-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
              >
                Cari
              </button>
            </div>
          </form>
        </div>

        {productList.length === 0 && (
          <FetchError text={"Produk tidak ditemukan"} />
        )}

        {productList.length > 0 && (
          <>
            <div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
              {productList.map((product, index) => (
                <ProductCard key={index} product={product} />
              ))}
            </div>
          </>
        )}
      </div>
    </FrontpageLayout>
  );
}

Sementara kita pake data dummy yang tersimpan dalam variabel productList


Masukan kode berikut pada ProductDetailPage.jsx
"use client";
import FrontpageLayout from "@/components/layouts/FrontpageLayout";
import Button from "@/components/ui/button/Button";
import { useParams } from "next/navigation";
import { FaBagShopping } from "react-icons/fa6";

export default function ProductDetailPageContent() {
  const { productId } = useParams();

  const product = {
    id: productId,
    name: `Product dengan id ${productId}`,
    description:
      "Ponsel pintar dengan kamera 108MP dan RAM 8GB. Performa tinggi untuk gaming dan multitasking.",
    image:
      "https://images.unsplash.com/photo-1592890288564-76628a30a657?ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&q=80&w=1170",
    price: "5999000.00",
    stock: 15,
    slug: "smartphone-android-terbaru-OfxcQ",
    category: {
      id: 1,
      name: "Elektronik",
      description: "Produk-produk seperti ponsel, laptop, dan TV.",
      created_at: "2025-11-03 08:06:01",
      updated_at: "2025-11-03 08:06:01",
    },
    created_at: "2025-11-03 08:06:01",
    updated_at: "2025-11-03 08:06:01",
  };

  return (
    <FrontpageLayout>
      <h3 className="font-bold text-gray-800 text-3xl text-start px-4 mb-4">
        Detail Produk
      </h3>
      <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 px-4">
        <img src={product.image} alt="product image" className="rounded-sm" />
        <ul className="flex flex-col gap-4">
          <li className="text-3xl font-semibold">{product.name}</li>{" "}
          <li>Kategori: {product?.category?.name}</li>{" "}
          <li>
            {" "}
            Harga:{" "}
            {product.price
              ? new Intl.NumberFormat("id-ID", {
                  style: "currency",
                  currency: "IDR",
                  minimumFractionDigits: 0,
                }).format(parseFloat(product.price))
              : "Rp 0"}{" "}
          </li>
          <li>Stok: {product.stock}</li>{" "}
          <li>Deskripsi: {product.description}</li>{" "}
          <li>
            <Button
              className={`text-xs ${product.stock === 0 && "opacity-50"}`}
              disabled={product.stock === 0}
            >
              <FaBagShopping className="size-4" />
              Tambah ke Keranjang
            </Button>
          </li>
        </ul>
      </div>
    </FrontpageLayout>
  );
}
Sementara kita pake data dummy yang tersimpan pada variabel product

Nahh, kita sudah membuat component pada folder pages, jika anda sudah mengikuti dengan seksama, maka strukturnya seperti Gambar 11 berikut:
Gambar 11


  • Menghubungkan ke Routing Next.js (Folder app)

Tahap selanjutnya kita akan berfokus pada folder app untuk membuat halaman nya supaya bisa tampil di web browser

Pertama buka file globals.css pada folder app, dan sesuaikan menjadi seperti kode berikut:
@import "tailwindcss";


Pada file layout.jsx dan ubah kode nya menjadi seperti berikut:
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata = {
  title: "Simple Ecommerce",
  description: "Generated by create next app",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

Pada file app/page.jsx, ubah kode nya menjadi seperti berikut:
import HomePageContent from "@/components/pages/home/HomePage";

export default function HomePage() {
  return <HomePageContent />;
}

Nahh kita sudah berhasil membuat halaman home sederhana, bisa dijalankan dengan buka terminal masukan perintah
npm run dev

Jika sudah berhasil di run, silahkan buka http://localhost:3000 di browser, maka tampilan web nya seperti Gambar 12 berikut:
Gambar 12



Yess, sudah berhasil membuat halaman home nya, kelihatan menarik bukann??

Eitss, tapi ini baru halam home nya saja, rencana kita adalah membuat halaman Home, Produk, dan Keranjang. jadi, ayoo lanjut lagi ke tahap selanjutnya..


Dalam folder app/products buat file dengan nama page.jsx, kemudian masukan kode berikut:

import ProductPageContent from "@/components/pages/product/ProductPage";
import { Suspense } from "react";

export default function ProductPage() {
  return (
    <Suspense fallback={<div className="p-4">Loading...</div>}>
      <ProductPageContent />
    </Suspense>
  );
}

Coba sekarang cek di browser http://localhost:3000/products tampilan nya akan seperti Gambar 13 berikut:
Gambar 13

Itu baru tampilan list produk nya, untuk tampilan detail produk nya lanjutkan tahapan selanjutnya.
Buat folder di dalam app/products dengan format/nama [productId], kemudian buat file dengan nama page.jsx. struktur nya akan seperti Gambar 14 berikut:
Gambar 14

Dalam file app/products/[productId]/page.jsx masukan kode berikut:
import ProductDetailPageContent from "@/components/pages/product/ProductDetailPage";
import React from "react";

export default function ProductDetailPage() {
  return <ProductDetailPageContent />;
}

Nahh, sekarang kita sudah berhasil membuat halaman detail produk, kita bisa akses di http://localhost:3000/products/1 maka tampilan nya akan seperti Gambar 15 berikut:
Gambar 15


Satu tahap lagi untuk halaman keranjang.

Buat folder di dalam folder app dengan nama carts, dan file di dalam carts dengan nama page.jsx

struktur nya seperti Gambar 16 berikut:

Gambar 16

Pada file app/carts/page.jsx masukan kode berikut:

import CartPageContent from "@/components/pages/cart/CartPage";
import React from "react";

export default function CartPage() {
  return <CartPageContent />;
}

Jika sudah, kita bisa cek halaman keranjang di http://localhost:3000/carts tampilan nya seperti Gambar 17 berikut:
Gambar 17


Jika semua langkah sudah dilakukan dengan seksama, maka hasil keseluruhan pada bagian ini seperti pada video berikut:


Hore! 🎉
Kita sudah berhasil membuat struktur dan halaman dasar toko online sederhana menggunakan Next.js.

Sampai jumpa di PART 2, di mana kita akan mulai implementasi API dengan Axios