Toko Kue Online
Toko online untuk usaha kue dan makanan rumahan (UMKM) yang memungkinkan pelanggan menelusuri katalog, menambah produk ke keranjang, melakukan checkout dengan pilihan pengiriman terbatas area kota, dan membayar lewat metode pembayaran lokal (Midtrans Snap: QRIS, e-wallet, VA, kartu). Pemilik toko mengelola produk dan memantau pesanan lewat dashboard admin sederhana. MVP menargetkan alur beli-bayar-kirim yang mulus dalam satu kota, mobile-first, dan siap dieksekusi AI coding agent dengan stack Next.js + Prisma + PostgreSQL + Midtrans + Vercel.
Toko Kue Online
Toko online untuk usaha kue dan makanan rumahan (UMKM) yang memungkinkan pelanggan menelusuri katalog, menambah produk ke keranjang, melakukan checkout dengan pilihan pengiriman terbatas area kota, dan membayar lewat metode pembayaran lokal (Midtrans Snap: QRIS, e-wallet, VA, kartu). Pemilik toko mengelola produk dan memantau pesanan lewat dashboard admin sederhana. MVP menargetkan alur beli-bayar-kirim yang mulus dalam satu kota, mobile-first, dan siap dieksekusi AI coding agent dengan stack Next.js + Prisma + PostgreSQL + Midtrans + Vercel.
Problem
Pemilik usaha kue dan makanan rumahan saat ini berjualan lewat WhatsApp dan Instagram secara manual: katalog tersebar di story/chat, harga sering ditanya berulang, rekap pesanan dicatat manual, dan konfirmasi pembayaran mudah keliru. Akibatnya proses lambat, rawan salah hitung ongkir, dan sulit menskalakan pesanan. Belum ada satu tempat rapi di mana pelanggan bisa melihat produk lengkap, menghitung total + ongkir area kota, membayar otomatis, dan pemilik langsung menerima pesanan terstruktur.
Goals
- Pelanggan dapat menyelesaikan alur lengkap: lihat katalog → tambah keranjang → checkout → bayar → dapat konfirmasi, tanpa perlu chat manual.
- Mendukung pembayaran lokal otomatis via Midtrans Snap (QRIS, e-wallet, Virtual Account, kartu) dengan status pesanan ter-update otomatis saat lunas.
- Menghitung ongkir berdasarkan area kota/kecamatan yang dipilih (tarif flat per zona) secara transparan sebelum bayar.
- Pemilik dapat menambah/mengubah produk dan melihat daftar pesanan masuk lewat dashboard admin tanpa menyentuh kode.
- Pengalaman mobile-first yang cepat (LCP < 2.5s di 4G) karena mayoritas pelanggan memesan lewat HP.
- Setiap pesanan berhasil memicu notifikasi ke pemilik dan email konfirmasi ke pelanggan.
Non-Goals
- Integrasi kurir real-time (RajaOngkir/JNE/Gojek API) untuk ongkir dinamis — MVP pakai tarif flat per zona kota.
- Akun pelanggan / login / riwayat pesanan berbasis akun — MVP checkout sebagai tamu (guest checkout).
- Multi-toko / multi-tenant / marketplace — MVP satu toko milik satu pemilik.
- Manajemen stok/inventori kompleks (varian, batch, kedaluwarsa) — MVP hanya flag tersedia/habis per produk.
- Program loyalti, poin, voucher/kupon diskon, dan referral.
- Aplikasi mobile native (iOS/Android) — MVP web responsive.
- Penjadwalan slot pengiriman jam-per-jam & rute pengantaran — MVP hanya tanggal pengiriman yang diinginkan.
- Multi-bahasa & multi-mata uang — MVP Bahasa Indonesia & Rupiah saja.
- Laporan analitik/penjualan lanjutan dan ekspor akuntansi.
Target Users
- Pemilik usaha kue/makanan rumahan (UMKM), melek HP tapi bukan teknis — Butuh etalase online rapi yang bisa dikelola sendiri, menerima pesanan terstruktur beserta alamat dan bukti bayar otomatis, serta notifikasi setiap ada order baru — tanpa harus balas chat satu-satu.
- Pelanggan area kota (mis. ibu rumah tangga, pekerja kantor) yang memesan lewat HP — Ingin melihat pilihan kue lengkap dengan harga & foto, tahu total + ongkir sejak awal, dan membayar cepat pakai QRIS/e-wallet tanpa harus tanya-tanya admin.
- Pelanggan pemesan acara (arisan/ulang tahun/kantor) dengan tanggal tertentu — Perlu memilih tanggal pengiriman yang diinginkan dan memastikan pesanan diterima serta dikonfirmasi jelas sebelum hari-H.
User Stories
- must-haveSebagai pelanggan, saya ingin menelusuri katalog kue lengkap dengan foto, harga, dan deskripsi serta memfilter per kategori, agar saya bisa menemukan kue yang saya inginkan tanpa harus bertanya ke penjual.
- must-haveSebagai pelanggan, saya ingin menambahkan beberapa produk ke keranjang, mengubah jumlah, dan melihat subtotal yang ter-update, agar saya bisa memesan banyak item sekaligus dan tahu total belanja sebelum checkout.
- must-haveSebagai pelanggan, saya ingin mengisi data penerima, memilih area pengiriman kota, dan melihat ongkir sebelum membayar, agar saya tahu total akhir yang harus dibayar secara transparan dan pesanan diantar ke alamat benar.
- must-haveSebagai pelanggan, saya ingin membayar pesanan lewat QRIS/e-wallet/Virtual Account dan langsung mendapat konfirmasi, agar saya tidak perlu transfer manual lalu kirim bukti bayar dan menunggu verifikasi.
- must-haveSebagai pemilik toko, saya ingin menambah, mengubah, dan menandai produk habis lewat dashboard admin, agar katalog selalu akurat tanpa saya harus mengedit kode.
- must-haveSebagai pemilik toko, saya ingin menerima notifikasi dan melihat daftar pesanan masuk lengkap dengan status pembayaran, agar saya bisa langsung menyiapkan dan mengantar pesanan yang sudah lunas.
Features
Katalog Produk
Halaman daftar produk (grid) dan halaman detail produk. Menampilkan foto, nama, harga (Rupiah), deskripsi, kategori, dan status ketersediaan. Mendukung filter per kategori dan pencarian nama sederhana. Produk habis ditandai jelas dan tidak bisa dimasukkan ke keranjang.
- Halaman /produk menampilkan semua produk aktif dalam grid responsif (2 kolom di mobile, 3-4 kolom di desktop) dengan foto, nama, dan harga terformat Rupiah (mis. "Rp 85.000").
- Klik satu produk membuka /produk/[slug] yang menampilkan foto besar, deskripsi lengkap, harga, kategori, dan tombol "Tambah ke Keranjang".
- Filter kategori memperbarui daftar produk tanpa reload penuh dan menampilkan jumlah hasil; jika kosong menampilkan pesan "Tidak ada produk".
- Produk dengan status tidak tersedia menampilkan badge "Habis" dan tombol tambah keranjang dinonaktifkan.
- Pencarian berdasarkan nama produk (case-insensitive) mengembalikan hasil yang relevan.
- Semua data produk diambil dari database (bukan hardcode) dan halaman dapat dibuka langsung via URL (SSR/SSG).
Keranjang Belanja
Keranjang yang menyimpan item terpilih beserta jumlah, bertahan saat refresh (localStorage), dan bisa diakses dari ikon keranjang di header dengan badge jumlah item. Pelanggan dapat mengubah kuantitas atau menghapus item, dengan subtotal yang otomatis ter-update.
- Menekan "Tambah ke Keranjang" menambahkan item; jika produk sama ditambah lagi maka kuantitasnya bertambah, bukan baris baru.
- Ikon keranjang di header menampilkan badge berisi total jumlah item, ter-update seketika saat item ditambah/dihapus.
- Halaman/panel keranjang menampilkan tiap item (foto, nama, harga satuan, kontrol +/-, subtotal per item) dan subtotal keseluruhan terformat Rupiah.
- Mengubah kuantitas atau menghapus item langsung memperbarui subtotal tanpa reload halaman.
- Isi keranjang tetap ada setelah halaman di-refresh atau browser ditutup lalu dibuka lagi (persist di localStorage).
- Keranjang kosong menampilkan pesan ramah dan tombol "Lihat Katalog"; tombol checkout dinonaktifkan saat keranjang kosong.
Checkout & Pengiriman Area Kota
Form checkout tamu (tanpa login) untuk data penerima (nama, no. HP, alamat lengkap), pemilihan zona pengiriman dalam kota (dropdown kecamatan/area dengan tarif flat), catatan, dan tanggal pengiriman yang diinginkan. Menampilkan ringkasan pesanan dengan subtotal + ongkir = total akhir sebelum lanjut membayar.
- Form checkout memvalidasi field wajib (nama, no. HP format Indonesia, alamat, zona pengiriman) dan menampilkan pesan error inline jika kosong/invalid.
- Memilih zona pengiriman dari dropdown menampilkan tarif ongkir flat zona tersebut dan langsung menambahkannya ke total.
- Ringkasan pesanan menampilkan daftar item, subtotal, ongkir, dan total akhir terformat Rupiah dengan perhitungan yang benar (total = subtotal + ongkir).
- Tanggal pengiriman yang dipilih tidak boleh tanggal lampau (minimal H+1 dari hari ini).
- Menekan "Lanjut ke Pembayaran" membuat record Order berstatus "pending" di database berisi item, data penerima, zona, ongkir, dan total, lalu meneruskan ke pembayaran.
- Jika keranjang kosong, halaman checkout mengarahkan kembali ke katalog.
Pembayaran Lokal (Midtrans)
Integrasi Midtrans Snap untuk pembayaran lokal (QRIS, GoPay/e-wallet, Virtual Account bank, kartu). Server membuat Snap transaction token dari data Order, pelanggan membayar via Snap popup, dan webhook Midtrans memperbarui status pembayaran Order secara otomatis dan aman (verifikasi signature).
- Endpoint server membuat Snap transaction token dari Order (order_id, gross_amount, item_details, customer_details) dan mengembalikannya ke frontend.
- Frontend menampilkan Snap popup/redirect Midtrans; setelah bayar sukses pelanggan diarahkan ke halaman konfirmasi.
- Endpoint webhook /api/midtrans/notification memverifikasi signature_key sesuai dokumentasi Midtrans dan menolak notifikasi tidak valid.
- Saat status transaksi "settlement"/"capture", status Order berubah menjadi "paid" di database secara idempoten (notifikasi ganda tidak menggandakan efek).
- Status "expire"/"cancel"/"deny" mengubah Order menjadi "failed"/"cancelled" dengan tepat.
- Server key & client key Midtrans dibaca dari environment variable (tidak pernah hardcode) dan mode sandbox/production dapat dikonfigurasi.
Konfirmasi Pesanan & Notifikasi
Halaman konfirmasi berisi nomor pesanan, ringkasan, dan status; email konfirmasi otomatis ke pelanggan; serta notifikasi ke pemilik (email dan/atau WhatsApp) saat ada pesanan baru yang lunas. Pelanggan dapat mengecek status pesanan lewat halaman/tautan status tanpa login.
- Setelah pembayaran, halaman /pesanan/[orderId] menampilkan nomor pesanan, item, total, alamat, tanggal kirim, dan status pembayaran terkini.
- Email konfirmasi terkirim ke alamat pelanggan (jika diisi) berisi nomor pesanan dan ringkasan dalam waktu < 1 menit setelah status "paid".
- Pemilik menerima notifikasi (email dan/atau pesan WhatsApp) berisi detail pesanan baru yang lunas.
- Halaman status pesanan dapat diakses ulang lewat tautan unik berisi orderId tanpa perlu login.
- Jika pengiriman notifikasi gagal, kegagalan tercatat di log dan tidak menggagalkan pembuatan/pelunasan Order.
Dashboard Admin
Area admin terproteksi (login sederhana / password admin) untuk mengelola produk (buat, ubah, hapus, tandai habis, unggah foto) dan memantau pesanan (daftar dengan filter status, detail pesanan, ubah status pengiriman menjadi diproses/dikirim/selesai).
- Halaman /admin memerlukan autentikasi; pengunjung tanpa sesi valid diarahkan ke halaman login.
- Admin dapat membuat produk baru (nama, harga, deskripsi, kategori, foto, status tersedia) dan produk langsung muncul di katalog publik.
- Admin dapat mengedit dan menghapus produk, serta menandai produk sebagai habis/tersedia.
- Admin melihat daftar pesanan terurut terbaru, difilter per status (pending/paid/diproses/dikirim/selesai), dengan ringkasan total & penerima.
- Admin membuka detail pesanan dan dapat mengubah status pengiriman; perubahan tersimpan dan tercermin di halaman status pelanggan.
- Unggah foto produk tersimpan ke storage (Vercel Blob/Supabase Storage) dan URL-nya tersimpan di database.
Tech Stack
Alasan: stack ini populer, ramah AI coding agent, dan sesuai konteks pengguna (Midtrans, Supabase, Vercel). Guest checkout menyederhanakan MVP. Midtrans dipilih karena mencakup metode pembayaran lokal Indonesia (QRIS, e-wallet, VA) dalam satu integrasi Snap. Ongkir memakai tarif flat per zona (tabel ShippingZone) agar tidak bergantung API kurir eksternal.
Tasks (siap kirim ke AI agent)
Menyiapkan kerangka proyek Next.js App Router dengan TypeScript, Tailwind CSS, shadcn/ui, struktur folder, layout dasar (header dengan ikon keranjang, footer), dan halaman placeholder. Menjadi fondasi untuk semua task berikutnya.
Lihat isi perintah
Buat proyek Next.js 14 dengan App Router + TypeScript bernama toko-kue-online. Langkah: (1) Inisialisasi via `npx create-next-app@latest` dengan opsi TypeScript, ESLint, Tailwind CSS, App Router, dan alias import `@/*`. (2) Pasang & inisialisasi shadcn/ui (`npx shadcn@latest init`) lalu tambah komponen dasar: button, card, input, dialog, badge, sheet, sonner (toast). (3) Buat struktur folder: `src/app` (routes), `src/components` (UI reusable), `src/lib` (util: `format.ts` berisi fungsi `formatRupiah(n: number)` yang mengembalikan format "Rp 85.000"), `src/types`. (4) Buat `src/app/layout.tsx` dengan `<Header />` (logo teks 'Kue Dapur', link ke /produk, ikon keranjang dengan badge jumlah — badge boleh placeholder 0 dulu) dan `<Footer />`. (5) Buat halaman placeholder `src/app/page.tsx` (beranda: hero + tombol 'Lihat Katalog' → /produk) dan `src/app/produk/page.tsx` (placeholder). (6) Set metadata dasar (title 'Toko Kue Online', lang 'id'). Definition of done: `npm run dev` berjalan tanpa error, `npm run build` sukses, header & footer tampil di semua halaman, `formatRupiah(85000)` mengembalikan 'Rp 85.000', dan tidak ada error TypeScript/ESLint.
Mendefinisikan skema Prisma untuk katalog, pesanan, zona pengiriman, dan admin; menghubungkan ke PostgreSQL; membuat migrasi dan seed data awal (produk contoh, kategori, zona kota).
Lihat isi perintah
Tambahkan Prisma + PostgreSQL ke proyek. Langkah: (1) `npm i prisma @prisma/client` lalu `npx prisma init`. Set `DATABASE_URL` di `.env` (gunakan Postgres Supabase/Neon; sediakan juga `.env.example`). (2) Di `prisma/schema.prisma` definisikan model: `Category { id, name, slug (unik) }`; `Product { id, name, slug (unik), description, priceIDR (Int, dalam Rupiah penuh), imageUrl, categoryId, isAvailable (Boolean default true), createdAt }` relasi ke Category; `ShippingZone { id, name (nama kecamatan/area), feeIDR (Int) }`; `Order { id (cuid), orderNumber (unik, mis. INV-<timestamp>), customerName, customerPhone, customerEmail (nullable), address, shippingZoneId, shippingFeeIDR, subtotalIDR, totalIDR, deliveryDate (DateTime), notes (nullable), paymentStatus (enum: PENDING|PAID|FAILED|CANCELLED default PENDING), fulfillmentStatus (enum: NEW|PROCESSING|SHIPPED|DONE default NEW), midtransToken (nullable), createdAt }`; `OrderItem { id, orderId, productId, productName (snapshot), priceIDR (snapshot), quantity }`; `AdminUser { id, email (unik), passwordHash }`. (3) Buat singleton Prisma client di `src/lib/prisma.ts` (pola global untuk hindari koneksi ganda saat dev). (4) Jalankan `npx prisma migrate dev --name init`. (5) Buat `prisma/seed.ts`: 3 kategori (Kue Ulang Tahun, Kue Tradisional, Snack Box), 6 produk contoh dengan harga & imageUrl (boleh placeholder URL), 4 ShippingZone kota dengan tarif berbeda (mis. 10.000/15.000/20.000/25.000), dan 1 AdminUser (email admin, password di-hash pakai bcrypt). Daftarkan seed di package.json. Definition of done: `npx prisma migrate dev` sukses, `npx prisma db seed` mengisi data, `npx prisma studio` menampilkan seluruh tabel terisi, dan tipe Prisma ter-generate tanpa error.Membangun halaman daftar produk (grid, filter kategori, pencarian) dan halaman detail produk, mengambil data dari database via Prisma dengan rendering server-side.
Lihat isi perintah
Bangun fitur Katalog Produk memakai data Prisma. Langkah: (1) `src/app/produk/page.tsx` (Server Component): ambil semua produk (join Category) dari Prisma, render grid responsif dengan komponen `src/components/ProductCard.tsx` (foto via next/image, nama, harga via formatRupiah, badge 'Habis' jika !isAvailable, klik → /produk/[slug]). Grid: 2 kolom mobile, 3 kolom md, 4 kolom lg. (2) Filter kategori: baca `?kategori=<slug>` dari searchParams, tampilkan baris chip kategori (link) di atas grid, filter query Prisma sesuai kategori aktif, tampilkan jumlah hasil; jika kosong tampilkan 'Tidak ada produk'. (3) Pencarian nama: input `?q=` (case-insensitive `contains`, mode insensitive) — boleh berupa form GET sederhana. (4) `src/app/produk/[slug]/page.tsx` (Server Component): ambil produk by slug (jika tidak ada → notFound()), tampilkan foto besar, deskripsi, kategori, harga, dan komponen client `AddToCartButton` (props: productId, name, priceIDR, imageUrl, slug; disabled jika !isAvailable). AddToCartButton untuk sekarang cukup memanggil placeholder hook keranjang (akan diisi di T4) dan menampilkan toast. (5) Tambah `generateMetadata` untuk title dinamis per produk. Definition of done: /produk menampilkan produk dari DB dengan filter & pencarian berfungsi, produk habis tampil badge & tombol nonaktif, /produk/[slug] menampilkan detail benar dan 404 untuk slug tak dikenal, harga selalu terformat Rupiah, `npm run build` sukses.
Implementasi state keranjang global yang bertahan di localStorage, badge jumlah di header, panel/halaman keranjang dengan ubah kuantitas & hapus item, dan subtotal otomatis.
Lihat isi perintah
Implementasikan Keranjang Belanja. Langkah: (1) Buat `src/lib/cart-context.tsx`: React Context + provider (atau Zustand store) menyimpan array item `{ productId, name, priceIDR, imageUrl, slug, quantity }`. Fungsi: `addItem(item)` (jika productId sudah ada → quantity++), `removeItem(productId)`, `updateQty(productId, qty)` (qty < 1 → hapus), `clearCart()`, plus selector `totalItems` dan `subtotalIDR`. Persist ke localStorage (key 'cart-v1'), hydrate saat mount, guard agar aman di SSR. Bungkus aplikasi dengan provider di `src/app/layout.tsx`. (2) Hubungkan `AddToCartButton` (dari T3) ke `addItem` dan tampilkan toast 'Ditambahkan ke keranjang'. (3) Header: ikon keranjang menampilkan badge `totalItems` (real-time), klik membuka Sheet/drawer keranjang (`src/components/CartSheet.tsx`). (4) `CartSheet` menampilkan tiap item (foto, nama, harga satuan, kontrol +/- memakai updateQty, tombol hapus, subtotal per item), subtotal keseluruhan via formatRupiah, tombol 'Checkout' (link ke /checkout) dinonaktifkan jika kosong, dan state kosong dengan pesan + tombol 'Lihat Katalog'. Definition of done: menambah produk sama menaikkan qty (bukan baris baru), badge ter-update seketika, ubah qty & hapus memperbarui subtotal tanpa reload, isi keranjang bertahan setelah refresh/tutup-buka browser, keranjang kosong menonaktifkan checkout. Uji manual alur ini sebelum selesai.Form checkout tamu dengan validasi, dropdown zona pengiriman kota yang menghitung ongkir flat, pemilihan tanggal kirim, ringkasan total, dan pembuatan Order berstatus PENDING di database.
Lihat isi perintah
Bangun alur Checkout & Pengiriman Area Kota. Langkah: (1) `src/app/checkout/page.tsx`: jika keranjang kosong redirect ke /produk. Tampilkan form (React Hook Form + Zod, komponen shadcn) berisi: nama, no. HP (validasi format Indonesia, mis. regex `^(\+62|0)8[0-9]{7,12}$`), email (opsional, validasi jika diisi), alamat lengkap (textarea, wajib), dropdown zona pengiriman (opsi dari endpoint/DB ShippingZone menampilkan nama + tarif), tanggal pengiriman (date picker, minimal H+1, tolak tanggal lampau), catatan (opsional). (2) Panel ringkasan: daftar item dari keranjang, subtotal, ongkir (dari zona terpilih, update saat zona berubah), dan total = subtotal + ongkir, semua via formatRupiah. (3) Endpoint `src/app/api/orders/route.ts` (POST): validasi payload di server dengan Zod, hitung ulang subtotal & total di server dari harga produk DB (JANGAN percaya harga dari client) + feeIDR zona, buat Order (status PENDING) + OrderItem (snapshot nama & harga), generate orderNumber unik. Kembalikan `{ orderId, orderNumber }`. (4) Tombol 'Lanjut ke Pembayaran' memanggil endpoint, dan pada sukses menyimpan orderId lalu mengarahkan ke langkah pembayaran (integrasi Midtrans di T6; untuk sekarang redirect ke /pesanan/[orderId]). Definition of done: field wajib tervalidasi dengan pesan error inline, memilih zona memperbarui ongkir & total dengan benar, tanggal lampau ditolak, total dihitung ulang di server (aman terhadap manipulasi harga), Order + OrderItem tersimpan di DB dengan status PENDING, dan keranjang kosong mengalihkan ke katalog.Menghubungkan Order ke Midtrans Snap: server membuat Snap token, frontend menampilkan popup pembayaran, dan webhook memverifikasi signature lalu memperbarui status pembayaran Order secara idempoten.
Lihat isi perintah
Integrasikan pembayaran lokal via Midtrans Snap (mode sandbox). Langkah: (1) `npm i midtrans-client`. Tambah env: `MIDTRANS_SERVER_KEY`, `MIDTRANS_CLIENT_KEY`, `MIDTRANS_IS_PRODUCTION` (juga di .env.example). Buat helper `src/lib/midtrans.ts` yang menginisialisasi Snap client dari env. (2) Endpoint `src/app/api/payment/route.ts` (POST { orderId }): ambil Order + items dari DB, buat parameter Snap { transaction_details: { order_id: orderNumber, gross_amount: totalIDR }, item_details (termasuk baris ongkir), customer_details }, panggil `snap.createTransaction`, simpan `midtransToken` ke Order, kembalikan token. (3) Frontend: di halaman setelah checkout, muat Snap.js (client key) dan panggil `window.snap.pay(token, { onSuccess, onPending, onError, onClose })`; onSuccess/onPending arahkan ke `/pesanan/[orderId]`. (Muat script Snap sandbox: https://app.sandbox.midtrans.com/snap/snap.js dengan atribut data-client-key). (4) Webhook `src/app/api/midtrans/notification/route.ts` (POST): verifikasi `signature_key` = sha512(order_id + status_code + gross_amount + ServerKey) sesuai dokumentasi Midtrans; jika tidak cocok balas 403. Petakan status: settlement/capture(accept) → paymentStatus PAID; pending → PENDING; expire → CANCELLED; cancel/deny → FAILED. Update Order by orderNumber secara IDEMPOTEN (jika sudah PAID, jangan proses ulang). Selalu balas 200 untuk notifikasi valid. (5) Tangani error & logging. Definition of done: dari halaman pembayaran, Snap popup muncul dan pembayaran sandbox sukses mengubah Order menjadi PAID via webhook; signature invalid ditolak; notifikasi ganda tidak menggandakan efek; semua key dibaca dari env. Sertakan catatan cara menguji webhook lokal (mis. via tunnel) di komentar file.Halaman status pesanan publik lewat orderId, email konfirmasi ke pelanggan via Resend, dan notifikasi pesanan baru lunas ke pemilik.
Lihat isi perintah
Bangun Konfirmasi Pesanan & Notifikasi. Langkah: (1) `src/app/pesanan/[orderId]/page.tsx` (Server Component): ambil Order + items by id, tampilkan orderNumber, status pembayaran (badge warna: PENDING kuning, PAID hijau, FAILED/CANCELLED merah), daftar item, subtotal, ongkir, total, alamat, zona, tanggal kirim, dan status pengiriman. Jika PENDING, tampilkan tombol 'Bayar Sekarang' yang memicu ulang Snap (pakai token/endpoint T6). Halaman dapat diakses ulang via URL tanpa login. (2) Email via Resend: `npm i resend`, env `RESEND_API_KEY` & `OWNER_EMAIL`. Buat `src/lib/notify.ts` dengan `sendCustomerConfirmation(order)` (kirim ke customerEmail jika ada, subjek 'Konfirmasi Pesanan <orderNumber>', isi ringkasan) dan `notifyOwner(order)` (kirim ke OWNER_EMAIL detail pesanan baru). (3) Panggil kedua fungsi dari webhook (T6) TEPAT saat Order berubah menjadi PAID (idempoten: hanya kirim sekali per order — mis. tandai flag `notifiedAt`). (4) Opsional WhatsApp: jika env `WA_NOTIFY_URL` & `WA_NOTIFY_TOKEN` tersedia, kirim POST notifikasi WA ke pemilik (drop-in, non-blocking). (5) Bungkus pengiriman notifikasi dengan try/catch: kegagalan hanya di-log, tidak menggagalkan pelunasan Order. Definition of done: halaman /pesanan/[orderId] menampilkan status akurat & bisa dibuka ulang tanpa login, email konfirmasi terkirim < 1 menit setelah PAID, pemilik menerima notifikasi pesanan baru, notifikasi tidak terkirim dobel, dan kegagalan notifikasi tercatat di log tanpa mengganggu status pesanan.
Area admin terproteksi untuk CRUD produk dengan unggah foto, dan pemantauan/pembaruan status pesanan, dengan login sederhana berbasis kredensial admin.
Lihat isi perintah
Bangun Dashboard Admin terproteksi. Langkah: (1) Auth sederhana pakai NextAuth (Credentials provider) atau session cookie custom: login di `src/app/admin/login/page.tsx` memverifikasi email+password terhadap AdminUser (bcrypt compare). Lindungi seluruh route `/admin/*` via middleware `src/middleware.ts` — tanpa sesi valid redirect ke /admin/login. Simpan secret di env (`AUTH_SECRET`). (2) `src/app/admin/produk/page.tsx`: tabel semua produk dengan aksi Tambah/Edit/Hapus dan toggle Tersedia/Habis. Form produk (`ProductForm`) dengan field nama, harga, deskripsi, kategori (select), status, dan unggah foto. Upload foto ke Vercel Blob (atau Supabase Storage) via route handler `src/app/api/admin/upload/route.ts` yang mengembalikan URL untuk disimpan ke Product.imageUrl. (3) CRUD produk lewat route handlers `src/app/api/admin/products/...` (POST/PATCH/DELETE) — semua diproteksi cek sesi admin di server. Produk baru langsung muncul di katalog publik (revalidate path /produk). (4) `src/app/admin/pesanan/page.tsx`: daftar Order terurut terbaru, filter berdasarkan paymentStatus & fulfillmentStatus, tampilkan orderNumber, penerima, total, tanggal kirim, status. (5) `src/app/admin/pesanan/[id]/page.tsx`: detail pesanan + dropdown ubah fulfillmentStatus (NEW→PROCESSING→SHIPPED→DONE) yang tersimpan ke DB dan tercermin di halaman status pelanggan (revalidate). Definition of done: /admin butuh login (redirect jika belum), admin bisa CRUD produk + unggah foto (muncul di katalog), semua endpoint admin menolak akses tanpa sesi, daftar pesanan bisa difilter, dan ubah status pengiriman tersimpan serta tampil di halaman pelanggan.
Menyiapkan variabel lingkungan produksi, menghubungkan database & storage, konfigurasi webhook Midtrans production/sandbox, dan deploy aplikasi ke Vercel dengan checklist QA akhir.
Lihat isi perintah
Deploy aplikasi ke Vercel dan finalisasi konfigurasi. Langkah: (1) Lengkapi `.env.example` dengan semua variabel: DATABASE_URL, MIDTRANS_SERVER_KEY, MIDTRANS_CLIENT_KEY, MIDTRANS_IS_PRODUCTION, RESEND_API_KEY, OWNER_EMAIL, AUTH_SECRET, BLOB_READ_WRITE_TOKEN (atau kredensial Supabase Storage), dan (opsional) WA_NOTIFY_URL/WA_NOTIFY_TOKEN. Tambahkan README singkat berisi cara set env & menjalankan migrasi. (2) Pastikan `prisma generate` berjalan saat build (tambahkan ke script build: `prisma generate && next build`) dan set `prisma migrate deploy` untuk migrasi produksi. (3) Hubungkan repo ke Vercel, isi seluruh Environment Variables di dashboard Vercel (Production & Preview), hubungkan Postgres (Supabase/Neon) dan Blob/Storage. (4) Jalankan migrasi + seed data awal (kategori & zona pengiriman kota; produk contoh boleh). (5) Set URL webhook Midtrans di dashboard Midtrans ke `https://<domain-vercel>/api/midtrans/notification`. (6) QA akhir end-to-end di URL produksi: buka katalog → tambah keranjang → checkout → bayar (sandbox) → status jadi PAID via webhook → email & notifikasi terkirim → cek pesanan muncul di /admin/pesanan → ubah status. Definition of done: aplikasi live di domain Vercel, `next build` sukses di CI Vercel, alur beli-bayar-kirim berjalan penuh di produksi (sandbox), webhook Midtrans menerima notifikasi ke domain live, dan tidak ada secret yang ter-hardcode di repo. Tuliskan hasil QA (lulus/temuan) sebagai ringkasan akhir.
Risks
- Konfigurasi webhook Midtrans salah/tidak menerima notifikasi (URL/signature) menyebabkan status pesanan macet di PENDING meski sudah dibayar — mitigasi: uji sandbox menyeluruh + verifikasi signature + tombol cek status.
- Harga/total dimanipulasi dari sisi client saat checkout — mitigasi: selalu hitung ulang subtotal, ongkir, dan total di server berdasarkan data DB (bukan payload client).
- Notifikasi/webhook ganda menggandakan email atau efek pembayaran — mitigasi: proses idempoten dengan flag status & notifiedAt.
- Foto produk besar memperlambat halaman di mobile 4G — mitigasi: next/image, kompresi, dan lazy-load; batasi ukuran unggahan.
- Auth admin sederhana kurang aman bila password lemah/terekspos — mitigasi: bcrypt, AUTH_SECRET kuat, dan rate-limit login.
- Tarif ongkir flat per zona bisa tidak akurat untuk alamat pinggiran kota — diterima sebagai batasan MVP; disempurnakan pasca-MVP dengan API kurir.
- Ketergantungan pada layanan pihak ketiga (Midtrans, Resend, Vercel, Postgres) — mitigasi: tangani kegagalan dengan graceful degradation & logging.
Open Questions
- Berapa zona/area kota yang dilayani dan tarif ongkir flat masing-masing? (perlu daftar final dari pemilik untuk seed ShippingZone)
- Apakah pemilik ingin notifikasi utama lewat WhatsApp atau cukup email pada MVP?
- Apakah perlu batas minimal order (mis. minimal Rp 100.000) atau cut-off jam pemesanan untuk pengiriman H+1?
- Apakah pelanggan boleh memilih ambil sendiri (pickup) selain diantar? (memengaruhi opsi ongkir)
- Metode pembayaran mana yang wajib diaktifkan di Midtrans pada rilis awal (QRIS saja, atau QRIS + e-wallet + VA)?
- Apakah butuh halaman kebijakan (syarat pemesanan, pengembalian, jam operasional) sebelum rilis?