Membangun Website Membership Sendiri
dari Ide Kosong sampai Production
Sebuah perjalanan naratif dari ide kosong sampai platform jualan kelas online yang benar-benar jalan di production. Ditulis untuk kamu yang baru mulai, agar punya keberanian bikin produk sendiri. Stack Laravel 12, Filament 3, Livewire 3, Tailwind v4.
17
Section Perjalanan Utuh
6
Fase Development
4
Wajah Aplikasi
Ditulis oleh Anjrah Ari Susanto, S.Psi.
Founder & Pengajar Alanotech
Ringkasan Cepat
Membangun platform membership dan e-course sendiri terdengar rumit kalau kamu baru mulai. Tapi sebenarnya, ini cuma rangkaian langkah kecil yang masing-masing sederhana. Panduan ini menelusuri satu perjalanan utuh, dari PRD, ERD, pemilihan stack (Laravel 12, Filament 3, Livewire 3, Tailwind v4), arsitektur big picture, design system, wireframe, lalu 6 fase development: pondasi auth, sales page dan checkout dengan payment gateway, member area dengan PDF signed URL, event offline dengan tiket QR, lead magnet funnel dengan autoresponder, sampai polish SEO dan deploy ke VPS. Ditutup dengan anti-bloat rules dan epilog keberanian untuk mulai.
Ibarat Membangun Rumah
Apa Sebenarnya Dokumen Ini?
Kalau kamu belum pernah coding, dokumen ini mungkin terlihat seperti buku teks berbahasa asing. Tapi sebenarnya, ini cuma sebuah panduan membangun rumah. Rumah yang akan kamu bangun bukan dari batu bata dan semen, tapi dari baris-baris kode.
| Section | Ibarat Membangun Rumah | Apa yang Sebenarnya Terjadi |
|---|---|---|
| 1. Prolog | Cerita kenapa kamu mau punya rumah sendiri, bukan ngontrak selamanya | Kenapa kamu perlu bikin platform sendiri, bukan tergantung platform orang lain |
| 2. Mindset | Sepakat dengan diri sendiri, rumah tidak perlu jadi istana di hari pertama | Prinsip MVP, YAGNI, jangan over-engineering sebelum mulai coding |
| 3. PRD | Menggambar denah rumah, kamar berapa, kamar mandi dimana, dapur ukuran apa | Menulis Product Requirements Document, fitur apa saja yang dibutuhkan |
| 4. ERD | Menggambar pondasi dan struktur tulangan, kolom mana yang menopang lantai mana | Mendesain database, tabel apa saja dan bagaimana hubungannya |
| 5. Stack | Memilih bahan bangunan, bata merah atau hebel, semen mana, cat apa | Memilih teknologi, Laravel, PostgreSQL, Redis, Tailwind, dan kenapa |
| 6. Arsitektur | Melihat gambar rumah utuh dari luar, berapa lantai, dimana pintu masuk | Memahami big picture, public area, member area, admin panel, PWA check-in |
| 7. Design System | Memilih warna cat, jenis keramik, model pintu, supaya rumah tidak acak | Menetapkan warna, font, radius, shadow, supaya semua halaman konsisten |
| 8. Wireframe | Sketsa kasar tiap ruangan, ini dapur, ini kamar tamu, ini tempat TV | Sketsa letak elemen di tiap halaman kunci sebelum mulai coding |
| 9. Phase 1 | Membangun pondasi, pasang pipa air, instalasi listrik, bikin pagar dan pintu gerbang | Setup project, auth, login, register, user management, data model dasar |
| 10. Phase 2 | Memasang pintu depan yang bisa dibuka tamu, lengkap dengan kotak pembayaran | Sales page, checkout, integrasi payment gateway, webhook, auto-enroll |
| 11. Phase 3 | Menata kamar tamu, pasang TV, rak buku, supaya tamu betah duduk | Member area, course viewer, PDF download, jadwal Zoom, PWA installable |
| 12. Phase 4 | Memasang palang tiket di gerbang acara, tamu tunjukkan undangan, satpam cek | Event offline, tiket QR, PWA check-in untuk staff, one-time scan |
| 13. Phase 5 | Memasang papan nama di depan rumah, supaya tetangga penasaran dan mampir | Lead magnet, opt-in landing, autoresponder email, funnel konversi |
| 14. Phase 6 | Pengecatan akhir, pasang lampu tidur, rapikan taman, cek bocor atap | SEO, Schema.org, sitemap, performance audit, security checklist, testing |
| 15. Anti-Bloat | Aturan diri, jangan terus tambah kamar yang tidak dipakai, rumah jadi penuh sesak | Prinsip anti over-engineering, simplicity first, minimize file count, YAGNI |
| 16. Deploy | Serah terima kunci, rumah siap ditempati, air dan listrik sudah nyala | Deploy ke VPS, Nginx, SSL, Supervisor, cron, optimize production |
| 17. Epilog | Pesan terakhir, rumah tidak akan pernah sempurna, tapi sudah cukup untuk ditinggali | Kata penutup, keberanian untuk mulai, ship dan iterasi |
Jadi, dokumen ini bukan buku coding. Ini adalah cerita perjalanan membangun sebuah rumah digital, dari tanah kosong sampai kunci diserahkan. Kalau kamu ikuti urutannya, kamu tidak akan tersesat.
Navigasi
Daftar Isi Perjalanan
17 section dari prolog sampai epilog, susun mengikuti urutan pengembangan asli
Prolog, Kenapa Manual Ini Ada
Banyak orang berhenti di tengah jalan saat belajar bikin produk web. Bukan karena tidak pintar, tapi karena tidak pernah melihat keseluruhan perjalanan dari awal sampai akhir. Mereka hanya melihat potongan-potongan tutorial yang tidak nyambung.
Manual ini berbeda. Di sini kamu akan dibawa menelusuri satu perjalanan utuh, dari sebuah ide kosong di kepala sampai sebuah platform membership dan e-course yang benar-benar bisa terima pembayaran, kirim email otomatis, beri akses ke member area, dan cetak tiket QR untuk event offline. Semua ditulis sebagai cerita, bukan daftar perintah yang harus dihafal.
Contoh produk yang akan kita bangun adalah platform pelatihan online. Bisa pelatihan apa saja, mulai dari memasak, melukis, trading, public speaking, sampai tahsin Quran. Yang penting strukturnya sama, ada kelas berbayar yang dijual, ada member area tempat peserta belajar, dan ada admin panel untuk mengelola semuanya.
Target akhir manual ini bukan kamu jago Laravel. Tapi kamu punya keberanian untuk membuka editor dan mulai menulis baris pertama, karena kamu sudah tahu gambar besar kemana produk ini akan pergi.
Inti Pesan Prolog
Platform seperti ini tidak sesulit kelihatannya. Sebagian besar "kesulitan" sebenarnya hanya ketidakjelasan. Begitu kamu tahu urutannya, semuanya jadi rangkaian langkah kecil yang masing-masing sederhana. Itulah kenapa manual ini disusun mengikuti urutan pengembangan asli, bukan urutan bab buku teks.
Mindset Vibecoder Sebelum Ngetik
Sebelum kita bicara soal kode, ada beberapa prinsip yang akan menyelamatkan kamu dari jebakan paling umum, yaitu over-engineering. Vibecoding itu bukan ngetik secepat mungkin sambil dengar musik. Vibecoding itu menulis dengan rasa, tahu kenapa setiap baris ada, dan berani menghapus yang tidak perlu.
2.1 Mulai dari MVP, Bukan dari Mimpi
MVP artinya Minimum Viable Product, produk paling kecil yang sudah bisa dipakai dan menghasilkan nilai. Bukan versi jelek dari produk impianmu, tapi versi paling jujur yang hanya berisi apa yang benar-benar dibutuhkan hari ini. Kalau kamu bisa jualan kelas tanpa fitur progress tracking, jangan buat progress tracking dulu. Kalau bisa tanpa komentar di video, jangan buat sistem komentar dulu.
2.2 YAGNI, You Aren't Gonna Need It
Setiap kali kamu tergoda menambah fitur "buat jaga-jaga nanti kalau butuh", tahan diri. Fitur yang belum dibutuhkan adalah beban. Setiap file, setiap class, setiap baris config adalah sesuatu yang harus kamu rawat selama bertahun-tahun. Tambahkan hanya ketika memang sudah dibutuhkan, bukan ketika kamu membayangkan mungkin butuh.
2.3 Rule of Three
Jangan membuat abstraksi (Service, Repository, Interface, Trait) sebelum logika yang sama benar-benar muncul tiga kali. Dua kali masih bisa ditoleransi sebagai duplikasi. Tiga kali baru boleh diabstraksi. Abstraksi prematur adalah penyebab nomor satu kode yang sulit dibaca orang lain, termasuk dirimu sendiri tiga bulan ke depan.
2.4 Setiap File Harus Punya Alasan Hidup
Sebelum membuat file baru, tanyakan, apakah logic ini bisa ditempatkan di file yang sudah ada? Kalau bisa, jangan buat file baru. Struktur folder yang ramping lebih berharga daripada struktur folder yang "secara teori rapi" tapi berisi file-file kosong.
Prinsip KISS, Keep It Simple and Maintainable. Pilih solusi yang paling membosankan, paling umum, paling sering dipakai orang. Bukan yang paling canggih. Teknologi bosan itu kekuatan, karena sudah diuji ribuan developer lain sebelummu.
2.5 Pikirkan SEO dan Tracking Sejak Awal
Kalau produkmu akan dipromosikan dengan iklan berbayar atau harap traffic dari Google, maka Meta Pixel, Google Tag Manager, dan struktur SEO wajib dipikirkan sejak halaman pertama dibuat. Memasangnya di akhir jauh lebih menyakitkan daripada memasangnya sejak awal, karena kamu harus mengubah layout dan struktur data yang sudah jadi.
Menulis PRD, Produk Sebelum Kode
Aturan paling penting dalam vibecoding, jangan langsung generate fitur. Sebelum menulis satu baris kode pun, tulis dulu Product Requirements Document. PRD bukan dokumen formal perusahaan. PRD adalah percakapanmu dengan diri sendiri soal apa yang sebenarnya sedang kamu bangun.
PRD yang baik menjawab pertanyaan, siapa target penggunaku, apa produk yang dijual, fitur apa yang wajib ada, dan apa yang sengaja tidak masuk scope. Tanpa PRD, kamu akan terus menambah fitur tanpa akhir. Dengan PRD, kamu punya garis finish.
Contoh PRD, Platform Kelas Online
Latar belakang. Platform ini menjual pelatihan online via video dan sesi live (Zoom), serta pelatihan offline di lokasi fisik dengan tiket QR. Sistem perlu mengelola funnel marketing, sales page, checkout, member area, dan admin panel.
Target Pengguna
| Role | Deskripsi |
|---|---|
| Visitor | Pengunjung sales page, belum register |
| Lead | Sudah submit opt-in form, dapat akses free course, belum beli |
| Member | Sudah beli course, akses member area (video, PDF, Zoom) |
| Field staff | Staff lapangan yang scan QR untuk verifikasi peserta event offline |
| Admin | Kelola course, event, user, transaksi, konten member area |
Produk. Kelas berbayar, delivery online (Zoom) atau offline (lokasi fisik), mode group atau private 1-on-1. Lead magnet, free course di member area + autoresponder email, tujuannya capture email dan upsell ke produk berbayar.
Fitur Utama
- Sales page per produk, copywriting persuasif, pricing, testimoni, FAQ, CTA ke checkout, SEO optimized.
- Checkout & payment, order form, integrasi payment gateway, webhook handler, auto-enroll setelah pembayaran confirmed, email konfirmasi.
- Member area, login dengan email verification, dashboard list course, course viewer (video embed, PDF download via signed URL, Zoom schedule), PWA installable ke home screen.
- Event offline & QR ticket, admin create event, auto-generate QR tiket per enrollment, tiket dikirim via email, multi-event di kota berbeda.
- PWA check-in (field staff), installable, login staff, scan QR via kamera, manual code input sebagai fallback, real-time log per event, offline-capable.
- Lead magnet funnel, landing page opt-in, auto-deliver free course, autoresponder sequence, tracking konversi lead ke order.
- Admin panel, CRUD course, event, user, enrollment, transaksi, check-in dashboard, lead management.
Non-Functional Requirements
- Performance. Page load di bawah 2 detik untuk sales page. Redis untuk cache, session, queue.
- Mobile first. Target 80% pengguna di HP. Semua halaman wajib perfect di mobile (lebar 640px ke bawah) sebelum di-enhance ke desktop.
- Security. Signed URL untuk PDF, QR tiket unik one-time check-in, webhook signature verification, rate limiting, HTTPS only.
- SEO. Meta tags, Schema.org (Course, Product, FAQPage), sitemap.xml, robots.txt, clean URL.
Fase Pengembangan
- Phase 1, Foundation. Setup project, auth, user management, navigasi, data model.
- Phase 2, Sales & Checkout. Sales page, checkout flow, payment integration, email konfirmasi.
- Phase 3, Member Area. Course viewer, PDF download, Zoom schedule, PWA member.
- Phase 4, Offline Event & QR. Event management, QR ticket, PWA check-in, dashboard.
- Phase 5, Lead Magnet & Funnel. Opt-in landing, free course, autoresponder, tracking.
- Phase 6, Polish. SEO, performance, responsive audit, testing.
Kenapa PRD Disusun Per Fase?
Karena setiap fase adalah milestone yang bisa di-ship dan diuji sendiri. Phase 1 selesai, kamu sudah punya aplikasi yang bisa login. Phase 2 selesai, kamu sudah bisa terima pembayaran. Tidak perlu menunggu semua selesai untuk merasakan produk jadi.
Menggambar ERD, Tulang Punggung Data
Setelah PRD jelas, langkah berikutnya adalah menggambar Entity Relationship Diagram. ERD adalah peta hubungan antar tabel di database. Tanpa ERD, kamu akan tambah kolom di tengah jalan berkali-kali, dan migration-migrationmu jadi berantakan.
Untuk platform membership dan e-course, inti datanya cukup sederhana. Berikut entitas utamanya.
4.1 Entitas Inti
| Tabel | Peran | Relasi Kunci |
|---|---|---|
| users | Akun semua orang (member, staff, admin) | has many orders, enrollments |
| courses | Produk kelas yang dijual | has many modules, events, orders, enrollments |
| modules | Bab/modul di dalam course | belongs to course, has many videos, pdfs |
| videos | Video materi (embed dari provider) | belongs to module |
| pdfs | File PDF materi | belongs to module |
| zoom_sessions | Jadwal sesi live Zoom per course | belongs to course |
| events | Sesi offline di lokasi fisik | belongs to course, has many tickets, staff |
| orders | Transaksi pembelian | belongs to user, course, (event); has one enrollment |
| enrollments | Hak akses user ke course | belongs to user, course, order, (event); has one ticket |
| tickets | Tiket QR untuk event offline | belongs to enrollment, event |
| check_ins | Log check-in peserta di event | belongs to ticket, staff |
| leads | Daftar orang yang opt-in lead magnet | belongs to user (opsional) |
4.2 Diagram Relasi (ASCII)
users 1 ──────< many orders >──────< 1 courses
│ │ │
│ │ ├──< many modules >──< many videos
│ │ ├──< many zoom_sessions
│ │ └──< many events >──< many tickets
│ │ └──< many check_ins
│ │
└──< many enrollments >── belongs to ──< 1 orders
│
└── has one tickets
leads ── (optional) belongs to ── users
4.3 Prinsip Mendesain Tabel
Pakai foreign key dengan explicit onDelete. Tentukan apa yang terjadi ke child ketika parent dihapus. cascadeOnDelete untuk data yang tidak berdiri sendiri (tiket ketika enrollment dihapus), restrictOnDelete untuk data penting (enrollment tidak boleh hilang ketika course dihapus), nullOnDelete untuk relasi opsional.
Tambah index untuk kolom yang sering di-query. status di orders, customer_email di orders, kombinasi (user_id, course_id) di enrollments. Index membuat query tetap cepat saat data bertumbuh.
Pisahkan order dan enrollment. Order adalah transaksi keuangan, enrollment adalah hak akses. Satu order menghasilkan satu enrollment, tapi konsepnya berbeda. Order bisa di-refund tanpa menghapus enrollment, atau enrollment bisa di-revoke tanpa menghapus order.
Gunakan enum untuk status yang pasti. status order dengan nilai pending, paid, failed, refunded. Lebih jelas dan lebih aman daripada string bebas. Tapi ingat, enum sulit diubah setelah ada data, jadi pikirkan matang sebelum create table.
Soft deletes untuk data penting. Course dan user sebaiknya pakai softDeletes() supaya tidak benar-benar hilang saat dihapus dari admin. Cukup di-flag sebagai deleted, masih bisa di-restore kalau salah hapus.
4.4 Contoh Migration, Tabel Orders
Ini contoh migration yang sebenarnya dipakai di production. Perhatikan foreign key, index, dan enum yang sudah dipikirkan dari awal.
// database/migrations/xxxx_create_orders_table.php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void
{
Schema::create('orders', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->foreignId('course_id')->constrained()->restrictOnDelete();
$table->foreignId('event_id')->nullable()->constrained()->nullOnDelete();
$table->decimal('amount', 12, 2);
$table->enum('status', ['pending', 'paid', 'failed', 'refunded'])->default('pending');
$table->string('payment_id')->nullable();
$table->string('customer_name');
$table->string('customer_email');
$table->string('customer_phone')->nullable();
$table->timestamps();
// Index untuk query yang sering dipakai
$table->index('status');
$table->index('customer_email');
});
}
public function down(): void
{
Schema::dropIfExists('orders');
}
};
Pola Penting
Perhatikan user_id dibuat nullable. Kenapa? Karena saat checkout, customer belum tentu punya akun. Order dibuat duluan, baru setelah pembayaran confirmed, sistem auto-create user dan link order ke user tersebut. Order tidak tergantung pada keberadaan user.
Memilih Stack dan Alasannya
Stack yang dipakai di manual ini bukan satu-satunya yang benar. Tapi ini stack yang sudah terbukti jalan di production, dengan ekosistem matang, dan cocok untuk vibecoder yang ingin cepat selesai tanpa kehilangan kualitas.
5.1 Backend, Laravel 12
Laravel adalah framework PHP paling populer dan paling lengkap. Dia sudah membawa auth, migration, queue, mail, cache, validation, dan ribuan package siap pakai. Untuk platform membership, Laravel cocok karena hampir semua primitif yang dibutuhkan sudah ada di dalamnya. Kamu tidak perlu merakit sendiri.
5.2 Admin Panel, Filament 3
Filament adalah admin panel builder untuk Laravel. Dengan beberapa baris kode, kamu sudah punya CRUD lengkap untuk semua model, lengkap dengan form, table, filter, dan relation manager. Filament menghemat waktu sangat besar. Tanpa Filament, membangun admin panel sendiri bisa makan waktu berminggu-minggu.
filament-shield, untuk RBAC (role dan permission berbasis Spatie).filament-breezy, untuk profile management, reset password, dan 2FA.
5.3 Frontend Interactivity, Livewire 3 + Alpine.js
Livewire membuat kamu bisa menulis interaktivitas frontend dengan PHP, tanpa menulis JavaScript. Cocok untuk form checkout, opt-in, scanner QR, dan komponen interaktif lain. Alpine.js untuk interaksi kecil di sisi client (dropdown, modal, tab).
5.4 CSS, Tailwind v4
Tailwind adalah utility-first CSS framework. Dia membuat kamu menulis styling langsung di HTML tanpa file CSS terpisah. Untuk platform dengan banyak halaman, Tailwind membuat iterasi desain sangat cepat. Versi 4 sudah lebih cepat dan lebih ringan.
5.5 Build, Vite
Vite menggantikan Laravel Mix. Lebih cepat, lebih modern, dan terintegrasi mulus dengan Laravel via laravel-vite-plugin.
5.6 Database, PostgreSQL
PostgreSQL dipilih karena stabil, mendukung tipe data kaya (JSONB, array, UUID), dan cocok untuk data transaksional. MySQL juga oke, tapi PostgreSQL lebih ketat dan lebih powerful untuk query kompleks.
5.7 Cache, Session, Queue, Redis
Redis dipakai untuk tiga hal sekaligus, cache, session, dan queue. Satu instance Redis, beberapa database logical. Ini membuat aplikasi cepat dan queue reliable. Email sending, payment polling, dan job berat dijalankan via queue, bukan synchronous.
5.8 Icons, Tabler Icons
Tabler Icons dipakai via blade-tabler-icons. Ribuan icon, konsisten, dan bisa dipanggil sebagai Blade component <x-tabler-book />.
5.9 Font, Inter + Font Arabik (opsional)
Inter untuk UI, sangat readable di layar kecil. Kalau produkmu ada konten Arab, tambahkan Amiri atau Scheherazade. Load via Google Fonts dengan preconnect supaya cepat.
Kenapa Tidak Pakai React atau Vue SPA?
Karena untuk platform membership, SPA adalah overkill. Sebagian besar halaman adalah konten statis yang SEO-friendly, dan Livewire sudah cukup untuk interaktivitas yang dibutuhkan. SPA menambah kompleksitas build, routing, dan state management yang tidak sepadan dengan manfaatnya.
Arsitektur Big Picture
Sebelum masuk ke kode, kamu perlu melihat gambar besar bagaimana semua komponen berinteraksi. Ini arsitektur platform membership dan e-course secara umum.
+-------------------------------------------------------------------+
| yourdomain.com |
| |
| +----------+ +----------+ +----------+ +-----------------+ |
| | Public | | Member | | Admin | | PWA Check-in | |
| | (Sales, | | Area | | Panel | | (Field Staff) | |
| | LeadMag) | | (Online) | | (Filament)| | | |
| +----+-----+ +----+-----+ +----+-----+ +--------+--------+ |
| | | | | |
| +----+-------------+-------------+-----------------+--------+ |
| | Laravel Application | |
| | | |
| | +--------+ +---------+ +---------+ +---------+ | |
| | | Routes | | Livewire| | Jobs | | Mail | | |
| | | & Ctrls| | Comp. | | & Queue | |Mailables| | |
| | +--------+ +---------+ +---------+ +---------+ | |
| | | |
| | +-----------------------------------------------------+ | |
| | | Service Layer | | |
| | | Payment | Video | Email | Zoom | | |
| | +-----------------------------------------------------+ | |
| +-------------------------+--------------------------------+ |
| | |
| +----------+ +----------+----------+ +--------------+ |
| |PostgreSQL| | Redis | | Storage | |
| | | | cache|session|queue | | (PDFs, QR, | |
| +----------+ +---------------------+ | images) | |
| +--------------+ |
+-------------------------------------------------------------------+
External Integrations
+----------+ +----------+ +----------+ +----------+
| Payment | | Video | | Email | | Zoom |
| Gateway | | Hosting | | Service | | API |
+----------+ +----------+ +----------+ +----------+
6.1 Empat "Wajah" Aplikasi
Aplikasi ini punya empat permukaan yang berbeda, masing-masing dengan user dan tujuan berbeda.
- Public, sales page dan lead magnet landing. Tujuannya konversi. SEO dan performance paling penting di sini.
- Member area, tempat peserta belajar. Login required, PWA installable, mobile first.
- Admin panel, Filament. Kelola course, event, user, transaksi. Hanya admin dan staff.
- PWA check-in, untuk field staff scan QR di lokasi event. Installable, offline-capable.
6.2 Service Layer, Kapan Wajib?
Service layer dipakai hanya untuk integrasi external (payment gateway, video hosting, email service, Zoom API). Bukan untuk logic bisnis internal. Logic bisnis seperti "create enrollment after payment" cukup di Job atau Controller. Service layer hanya membungkus API call ke pihak ketiga, supaya mudah di-test dan di-swap kalau provider diganti.
Jangan Over-Abstract. Service Layer untuk logic internal (misal EnrollmentService yang hanya wrap Eloquent) adalah anti-pattern. Itu hanya menambah file tanpa manfaat. Pakai Service hanya untuk hal yang benar-benar kompleks atau external.
6.3 Queue untuk Hal Berat
Email sending, payment polling, QR generation, dan webhook processing dijalankan via queue, bukan synchronous. Ini membuat response time tetap cepat walau ada proses berat di belakang. Queue worker dijalankan via Supervisor supaya auto-restart kalau crash.
Design System, Supaya Tidak Acak
Tanpa design system, setiap halaman akan terlihat berbeda. Tombol di halaman A beda warna dengan halaman B, padding card di halaman C beda dengan halaman D. Design system adalah kontrak dengan dirimu sendiri supaya semua halaman terlihat seperti produk yang sama.
7.1 Prinsip Desain
- Tenang dan terpercaya. Bukan SaaS startup yang penuh gradient mencolok. Nuansa edukasi yang serius tapi accessible.
- White space adalah luxury. Spacing generous, tidak crowded. Biarkan konten bernapas.
- Hierarki jelas. Heading besar, body nyaman dibaca. Typography hierarchy yang strong.
- Mobile first. Target 80% pengguna di HP. Semua halaman wajib perfect di mobile sebelum di-enhance ke desktop.
- Portrait only. Desain untuk HP orientasi portrait. Jangan paksa user memutar HP ke landscape.
- Bright and fresh. Light theme, tidak ada background gelap kecuali footer.
7.2 Color Palette
Pilih satu warna primary, satu accent, dan beberapa netral. Patuh rasio, jangan boros warna.
| Warna | Proporsi | Token |
|---|---|---|
| White | 70% | bg-main, bg-section, bg-card |
| Primary (hijau) | 20% | primary, primary-hover, primary-light |
| Gray | 7% | heading, body, caption, border |
| Accent (oranye) | 3% | accent, maksimal 5% dari tampilan |
Contoh konfigurasi Tailwind.
// tailwind.config.js
export default {
theme: {
extend: {
colors: {
primary: {
DEFAULT: '#0F8A4B',
hover: '#0B6E3C',
light: '#EAF8F0',
},
accent: { DEFAULT: '#F58220' },
'bg-main': '#FFFFFF',
'bg-section': '#F8FAF8',
heading: '#1B1B1B',
body: '#4B5563',
caption: '#6B7280',
border: '#E5E7EB',
},
borderRadius: {
card: '16px',
btn: '12px',
},
},
},
};
7.3 Typography
- UI font. Inter, weight 400 (body), 600 (semibold), 700 (bold heading).
- Arab font (opsional). Amiri atau Scheherazade, untuk konten Arab.
- Body size. 16-17px, line-height 1.7 untuk readability.
- Heading. H1 2-2.3rem, H2 1.6rem, H3 1.25rem.
7.4 Komponen dan Radius
- Card radius 16px, button radius 12px. Konsisten di seluruh aplikasi.
- Shadow sangat tipis, neutral tint. Bukan drop shadow tebal.
- Touch target minimum 48px untuk mobile.
Dilarang Horizontal Ruler
Hindari <hr>, border-t, atau border-b full-width sebagai pemisah section. Gunakan spacing atau background section alternatif (bg-section vs bg-main) untuk memisah konten secara visual. Pemisah garis penuh membuat desain terlihat kaku dan jadul.
Wireframe Halaman Kunci
Sebelum menulis Blade, sketsa dulu wireframe halaman kunci. Wireframe bukan desain akhir, tapi peta letak elemen. Ini membantu kamu tahu komponen apa yang perlu dibuat sebelum mulai coding.
8.1 Sales Page (Mobile)
+---------------------------+
| [logo] [menu=] | header sticky
+---------------------------+
| |
| [hero image/video] |
| |
| Judul Kelas (H1) |
| Subjudul 1 baris |
| |
| [ Daftar Sekarang ] | CTA primary, accent
+---------------------------+
| Untuk Siapa Kelas Ini |
| - point 1 |
| - point 2 |
| - point 3 |
+---------------------------+
| Apa yang Dipelajari |
| [card modul 1] |
| [card modul 2] |
| [card modul 3] |
+---------------------------+
| Testimoni |
| [card testimoni 1] |
| [card testimoni 2] |
+---------------------------+
| Harga |
| Rp 497.000 |
| [ Daftar Sekarang ] |
+---------------------------+
| FAQ (accordion) |
+---------------------------+
| [ Daftar Sekarang ] | CTA terakhir
+---------------------------+
| footer |
+---------------------------+
8.2 Checkout (Mobile)
+---------------------------+
| < Kembali Checkout |
+---------------------------+
| Ringkasan Pesanan |
| Judul Kelas |
| Rp 497.000 |
+---------------------------+
| Data Pemesan |
| Nama Lengkap [________] |
| Email [________] |
| No. WhatsApp [________] |
+---------------------------+
| Metode Pembayaran |
| (o) QRIS |
| ( ) Virtual Account |
| ( ) Credit Card |
+---------------------------+
| [ Bayar Sekarang ] |
+---------------------------+
8.3 Member Dashboard (Mobile)
+---------------------------+
| [logo] [logout] |
+---------------------------+
| Halo, Nama Member |
| |
| Kelas Saya |
| +---------------------+ |
| | [thumb] Kelas A | |
| | 3 modul Lanjut > | |
| +---------------------+ |
| +---------------------+ |
| | [thumb] Kelas B | |
| | 5 modul Mulai > | |
| +---------------------+ |
+---------------------------+
| [Home] [Kelas] [Akun] | bottom tab bar
+---------------------------+
8.4 Course Viewer (Mobile)
+---------------------------+
| < Judul Kelas |
+---------------------------+
| [video player embed] |
+---------------------------+
| Modul 1, Video 2 |
| Deskripsi singkat... |
+---------------------------+
| Materi Pendukung |
| [ PDF Handout ] |
| [ Sesi Zoom ] |
+---------------------------+
| Daftar Modul |
| > Modul 1 |
| - Video 1 (done) |
| - Video 2 (now) |
| > Modul 2 |
+---------------------------+
8.5 PWA Check-in (Field Staff, Mobile)
+---------------------------+
| Check-in Event |
| Jakarta, 12 Aug |
+---------------------------+
| Stat: 42 / 100 check-in |
+---------------------------+
| |
| [ SCAN QR ] | buka kamera
| |
+---------------------------+
| atau input manual |
| [ Kode Tiket ____ ] |
+---------------------------+
| Log Check-in Terakhir |
| - Andi 14:02 OK |
| - Budi 14:01 OK |
| - Cici 14:00 Duplikat |
+---------------------------+
Phase 1, Pondasi dan Auth
Phase 1 adalah pondasi. Tanpa pondasi yang kuat, semua fase berikutnya akan goyah. Di phase ini kita bangun project setup, auth, user management, navigasi, dan data model dasar.
Langkah-langkah Phase 1
- Audit environment dulu. Sebelum install apapun, cek VPS atau environment lokal. Pastikan tidak bentrok dengan project lain. Cek versi PHP, Node, PostgreSQL, Redis. Pastikan port tidak dipakai. Ini langkah yang sering dilewati tapi menyebabkan kerusakan besar kalau diabaikan.
- Install Laravel fresh.
composer create-project laravel/laravel nama-project. Lalu install dependency tambahan, Filament, filament-shield, filament-breezy, blade-tabler-icons, Livewire, Tailwind. - Konfigurasi
.env. SetAPP_URL,DB_CONNECTION=pgsql,REDIS_HOST,QUEUE_CONNECTION=redis,SESSION_DRIVER=redis,CACHE_STORE=redis. Jangan hardcode secret, semua di.env. - Setup Filament. Jalankan
php artisan make:filament-useruntuk admin pertama. Konfigurasi panel diapp/Providers/Filament/AdminPanelProvider.php. Pasang filament-shield untuk role, filament-breezy untuk profile dan 2FA. - Buat migration dan model inti. Users (extend default), courses, modules, videos, pdfs, zoom_sessions, events, orders, enrollments, tickets. Ikuti ERD yang sudah digambar. Jalankan
php artisan migrate. - Auth flow. Registrasi dengan email verification (wajib verifikasi sebelum akses member area), login, reset password via signed URL. Manfaatkan trait
MustVerifyEmaildan middlewareEnsureVerified. - Navigasi layout. Mobile: bottom tab bar (3-4 item utama) + hamburger drawer untuk menu sekunder. Desktop: top nav + sidebar. Buat sebagai Blade layout component supaya bisa dipakai ulang.
- User management di Filament. CRUD user lengkap, assign role (admin/staff/member), reset password, kirim ulang email verifikasi, filter & search, enrollment history per user.
9.1 Model User yang Baik
Model User adalah fondasi auth. Implementasikan MustVerifyEmail supaya email verification otomatis jalan. Implementasikan FilamentUser supaya bisa akses admin panel. Pakai trait HasRoles dari Spatie untuk RBAC.
// app/Models/User.php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Jeffgreco13\FilamentBreezy\Traits\TwoFactorAuthenticatable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable implements MustVerifyEmail, FilamentUser
{
use Notifiable, HasRoles, TwoFactorAuthenticatable;
protected $fillable = ['name', 'email', 'phone', 'password'];
protected $hidden = ['password', 'remember_token'];
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
}
// Hanya admin/super_admin yang bisa akses panel Filament
public function canAccessPanel(Panel $panel): bool
{
return $this->hasRole(['super_admin', 'admin']);
}
public function enrollments(): HasMany
{
return $this->hasMany(Enrollment::class);
}
}
9.2 Middleware EnsureVerified
Middleware ini memastikan user sudah verifikasi email sebelum bisa akses member area. Tanpa ini, orang bisa register dengan email palsu dan langsung masuk.
// app/Http/Middleware/EnsureVerified.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class EnsureVerified
{
public function handle(Request $request, Closure $next)
{
if (! $request->user() || ! $request->user()->hasVerifiedEmail()) {
return redirect()->route('verification.notice')
->with('warning', 'Verifikasi email dulu untuk akses member area.');
}
return $next($request);
}
}
9.3 Filament Resource untuk Course
Filament Resource mendefinisikan form dan table untuk CRUD di admin panel. Berikut contoh CourseResource yang membungkus field utama course. Perhatikan section grouping, helper text, dan formatting harga Rupiah.
// app/Filament/Resources/CourseResource.php
namespace App\Filament\Resources;
use App\Filament\Resources\CourseResource\Pages;
use App\Models\Course;
use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
class CourseResource extends Resource
{
protected static ?string $model = Course::class;
protected static ?string $navigationIcon = 'tabler-book';
protected static ?string $navigationGroup = 'Konten';
public static function form(Form $form): Form
{
return $form->schema([
Forms\Components\Section::make('Informasi Kursus')
->icon('tabler-book')
->schema([
Forms\Components\TextInput::make('title')
->label('Judul Kursus')
->required()
->maxLength(255),
Forms\Components\TextInput::make('slug')
->required()
->unique(ignoreRecord: true)
->helperText('URL: /courses/{slug}. Isi manual.'),
Forms\Components\Textarea::make('description')
->rows(4)
->columnSpanFull(),
])->columns(2),
Forms\Components\Section::make('Harga & Tipe')
->icon('tabler-credit-card')
->schema([
Forms\Components\TextInput::make('price')
->numeric()
->minValue(0)
->step(1000)
->prefix('Rp')
->formatStateUsing(fn ($state): string =>
$state ? number_format((float) $state, 0, ',', '.') : '')
->dehydrateStateUsing(fn ($state): ?float =>
$state ? (float) str_replace(['.', ','], '', $state) : 0),
Forms\Components\Select::make('type')
->required()
->options([
'online' => 'Online (Zoom)',
'offline' => 'Offline (Lokasi Fisik)',
]),
Forms\Components\Toggle::make('is_published')
->label('Publish'),
])->columns(2),
]);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title')->searchable()->sortable(),
Tables\Columns\TextColumn::make('price')
->money('IDR')->sortable(),
Tables\Columns\IconColumn::make('is_published')
->boolean()->label('Published'),
])
->filters([
Tables\Filters\SelectFilter::make('type')
->options(['online' => 'Online', 'offline' => 'Offline']),
])
->actions([Tables\Actions\EditAction::make()])
->bulkActions([Tables\Actions\DeleteBulkAction::make()]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListCourses::route('/'),
'create' => Pages\CreateCourse::route('/create'),
'edit' => Pages\EditCourse::route('/{record}/edit'),
];
}
}
Hasil Phase 1
Setelah phase ini selesai, kamu punya aplikasi yang bisa register, login, verifikasi email, reset password, dan admin panel untuk kelola user dan course. Belum bisa jualan, tapi pondasi sudah kokoh.
Phase 2, Sales Page dan Checkout
Phase 2 adalah jantung bisnis. Di sini kamu bangun sales page yang meyakinkan dan checkout flow yang aman. Kalau phase ini gagal, tidak ada uang masuk. Kalau berhasil, produkmu hidup.
Langkah-langkah Phase 2
- Sales page per produk. Buat Blade template yang dinamis, kontennya diisi dari database (judul, deskripsi, harga, modul, testimoni, FAQ). SEO meta tags dan Schema.org dipasang sejak awal.
- Checkout form (Livewire). Form nama, email, WhatsApp, pilihan metode pembayaran. Validasi ketat, rate limiting untuk cegah spam.
- Integrasi payment gateway. Bungkus API call di Service class. Buat invoice di gateway, dapatkan payment URL, redirect customer.
- Webhook handler. Endpoint yang dengar notifikasi pembayaran dari gateway. Verifikasi signature, update status order, dispatch job enrollment.
- Email konfirmasi. Kirim via queue (Mailable + Job). Jangan synchronous, supaya response webhook cepat.
- Success page + auto-login. Setelah bayar, redirect ke success page yang polling status, lalu auto-login via signed URL supaya customer langsung masuk member area.
10.1 Pola Penting, Order Dulu, User Belum Tentu Ada
Saat checkout, customer belum tentu punya akun. Order dibuat duluan dengan data customer (nama, email, phone). Setelah pembayaran confirmed, sistem auto-create user berdasarkan email order, lalu link order ke user tersebut. Ini penting karena tidak semua customer mau register dulu sebelum bayar. Buatlah checkout senyaman mungkin, jangan paksa register di awal.
10.2 Service Layer untuk Payment Gateway
Bungkus API call ke payment gateway di Service class. Ini salah satu kasus di mana Service layer memang wajib, karena integrasi external dan perlu bisa di-test terisolasi.
// app/Services/PaymentService.php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class PaymentService
{
public function __construct(
private string $apiKey,
private string $apiSecret,
) {}
// Buat invoice di gateway, return payment URL
public function createInvoice(array $payload): array
{
$response = Http::withHeaders($this->authHeaders())
->post(config('services.payment.base_url') . '/invoices', $payload);
if (! $response->successful()) {
Log::error('Payment createInvoice failed', ['body' => $response->body()]);
throw new \Exception('Gagal membuat invoice pembayaran.');
}
return $response->json();
}
// Verifikasi signature webhook, cegah webhook palsu
public function verifyWebhookSignature(string $rawBody, string $signature, string $timestamp): bool
{
// Cek timestamp freshness (tolak webhook lebih dari 5 menit)
if (abs(time() - (int) $timestamp) > 300) {
return false;
}
$expected = hash_hmac('sha256', $timestamp . $rawBody, $this->apiSecret);
return hash_equals($expected, $signature);
}
private function authHeaders(): array
{
return [
'Authorization' => 'Bearer ' . $this->apiKey,
'Content-Type' => 'application/json',
];
}
}
Daftarkan service di service provider supaya dependency injection jalan.
// app/Providers/AppServiceProvider.php
use App\Services\PaymentService;
public function register(): void
{
$this->app->singleton(PaymentService::class, function ($app) {
return new PaymentService(
config('services.payment.key'),
config('services.payment.secret'),
);
});
}
10.3 Webhook Handler, Jantung Otomasi
Webhook adalah cara payment gateway memberitahu aplikasimu bahwa pembayaran sudah selesai. Tanpa webhook, kamu harus poll status manual. Dengan webhook, sistem otomatis reaksi. Tapi webhook punya dua jebakan, signature palsu dan duplikat. Atasi keduanya.
// app/Http/Controllers/WebhookController.php
namespace App\Http\Controllers;
use App\Jobs\AutoEnrollUser;
use App\Jobs\SendConfirmationEmail;
use App\Jobs\SendWelcomeEmail;
use App\Models\Order;
use App\Services\PaymentService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class WebhookController extends Controller
{
public function __construct(
private PaymentService $payment
) {}
public function handle(Request $request)
{
$rawBody = $request->getContent();
$signature = $request->header('X-Webhook-Signature', '');
$timestamp = $request->header('X-Webhook-Timestamp', '');
// 1. Verifikasi signature, tolak webhook palsu
if (! $this->payment->verifyWebhookSignature($rawBody, $signature, $timestamp)) {
Log::warning('Webhook signature invalid');
return response()->json(['message' => 'Invalid signature'], 401);
}
$data = json_decode($rawBody, true);
$order = Order::find($data['external_id'] ?? null);
if (! $order) {
return response()->json(['message' => 'Order not found'], 404);
}
// 2. Idempotency, skip kalau order sudah paid (webhook bisa datang 2x)
if ($order->status === 'paid') {
Log::info('Webhook skipped, order already paid', ['order_id' => $order->id]);
return response()->json(['message' => 'Already processed'], 200);
}
// 3. Update status, dispatch job chain (semua via queue)
$order->update(['status' => 'paid', 'paid_at' => now()]);
AutoEnrollUser::dispatch($order)->chain([
new SendWelcomeEmail($order),
])->onQueue('emails');
SendConfirmationEmail::dispatch($order)->onQueue('emails');
return response()->json(['message' => 'Webhook processed'], 200);
}
}
Dua Jebakan Webhook
Signature palsu. Tanpa verifikasi signature, siapapun bisa kirim webhook palsu ke endpoint-mu dan menandai order sebagai paid. Selalu verifikasi signature dengan HMAC dan cek timestamp freshness.
Duplikat. Gateway sering kirim webhook yang sama berkali-kali untuk memastikan diterima. Tanpa idempotency check, customer bisa di-enroll dua kali. Selalu cek if order already paid, skip.
10.4 AutoEnrollUser Job, Otomasi Inti
Setelah pembayaran confirmed, job ini berjalan di queue. Dia find-or-create user berdasarkan email order, lalu create enrollment. Idempotent, aman dijalankan berkali-kali.
// app/Jobs/AutoEnrollUser.php
namespace App\Jobs;
use App\Models\Enrollment;
use App\Models\Order;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class AutoEnrollUser implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public function __construct(
public Order $order
) {}
public function handle(): void
{
// Idempotency, skip kalau enrollment sudah ada untuk order ini
if (Enrollment::where('order_id', $this->order->id)->exists()) {
return;
}
DB::transaction(function () {
$user = $this->findOrCreateUser();
$enrollment = $this->createEnrollment($user);
// Untuk offline course dengan event, generate ticket QR
if ($enrollment->type === 'offline' && $this->order->event_id) {
$this->createTicket($enrollment);
}
});
}
private function findOrCreateUser(): User
{
$user = User::where('email', $this->order->customer_email)->first();
if ($user) {
return $user;
}
// Auto-create user dengan password random, mark email verified
$user = User::create([
'name' => $this->order->customer_name,
'email' => $this->order->customer_email,
'phone' => $this->order->customer_phone,
'password' => Str::random(32),
]);
$user->markEmailAsVerified();
$user->assignRole('member');
return $user;
}
private function createEnrollment(User $user): Enrollment
{
$course = $this->order->course;
return Enrollment::create([
'user_id' => $user->id,
'course_id' => $this->order->course_id,
'order_id' => $this->order->id,
'event_id' => $this->order->event_id,
'type' => $course->is_free ? 'free_lead_magnet' : $course->type,
'status' => 'active',
'enrolled_at' => now(),
]);
}
private function createTicket(Enrollment $enrollment): Ticket
{
// Generate kode unik format BRAND-XXXX-YYYY-ZZZZ
do {
$code = sprintf('BRND-%s-%s-%s',
strtoupper(Str::random(4)),
strtoupper(Str::random(4)),
strtoupper(Str::random(4))
);
} while (Ticket::where('code', $code)->exists());
return Ticket::create([
'enrollment_id' => $enrollment->id,
'event_id' => $this->order->event_id,
'code' => $code,
'status' => 'unused',
]);
}
}
Hasil Phase 2
Aplikasi sudah bisa jualan. Customer checkout, bayar via gateway, webhook trigger auto-enroll, email konfirmasi terkirim. Uang masuk, akses diberikan, semua otomatis.
Soal Payment Gateway, Pilih yang Punya Dokumentasi
Di manual ini, contoh kode payment memakai pola generik (Service class + webhook + signature verification). Untuk integrasi nyata, pilih payment gateway yang punya dokumentasi API lengkap dan resmi. Kalau kamu pakai Alanotech, rujuk dokumentasi internal mereka untuk endpoint, format payload, dan header signature yang spesifik. Jangan mengandalkan contoh kode dari manual ini untuk hal yang detail teknisnya bergantung provider. Prinsipnya sederhana, baca docs provider-mu, baru adaptasi pola di sini.
Phase 3, Member Area
Member area adalah rumah peserta setelah membayar. Di sinilah mereka belajar, menonton video, download materi, dan cek jadwal Zoom. Kalau member area nyaman, mereka akan cerita ke teman. Kalau tidak, mereka akan minta refund.
Langkah-langkah Phase 3
- Dashboard member. Halaman sambutan dengan list course yang sudah di-enroll. Tampilkan thumbnail, judul, progress modul, dan tombol "Lanjut Belajar". Mobile first, bottom tab bar untuk navigasi.
- Course viewer. Halaman detail course dengan video player embed, deskripsi modul, daftar modul di sidebar (atau accordion di mobile), dan materi pendukung (PDF, jadwal Zoom).
- PDF download via signed URL. Jangan expose file PDF langsung. Gunakan signed URL yang expired dalam 15 menit, supaya link tidak bisa dibagikan. Generate di backend, return ke frontend saat user klik download.
- Zoom schedule. Tampilkan jadwal sesi live per course. Integrasi Zoom API untuk auto-create meeting link. Link Zoom hanya muncul untuk member yang sudah ter-enroll.
- PWA manifest. Buat
manifest.jsondengan name, short_name, icons, theme_color, display standalone. Tambahkan meta tagapple-mobile-web-app-capablesupaya bisa di-install ke home screen iPhone. - Service worker (opsional). Cache halaman statis (dashboard, navigasi) supaya member area tetap bisa dibuka saat koneksi lambat. Jangan cache video, itu urusan provider video hosting.
11.1 Signed URL untuk PDF
Inilah cara melindungi file PDF tanpa authentication yang ribet. User klik download, backend cek enrollment, lalu generate URL sementara yang hanya valid 15 menit. Setelah expired, link mati. Aman dibagikan pun tidak akan lama.
// app/Http/Controllers/MaterialController.php
public function downloadPdf(Request $request, Pdf $pdf)
{
// Cek apakah user ter-enroll ke course yang punya PDF ini
$enrolled = Enrollment::where('user_id', $request->user()->id)
->where('course_id', $pdf->module->course_id)
->where('status', 'active')
->exists();
if (! $enrolled) {
abort(403, 'Kamu tidak punya akses ke materi ini.');
}
// Generate signed URL valid 15 menit
$url = Storage::disk('private')->temporaryUrl(
$pdf->file_path,
now()->addMinutes(15)
);
return redirect($url);
}
11.2 PWA Manifest Sederhana
Member area harus installable. Dengan PWA, peserta bisa "Add to Home Screen" dan aplikasi muncul seperti app native. Ini meningkatkan retensi karena peserta tidak perlu buka browser setiap kali.
// public/manifest.json
{
"name": "Member Area",
"short_name": "Member",
"start_url": "/dashboard",
"display": "standalone",
"background_color": "#FFFFFF",
"theme_color": "#0F8A4B",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}
Tambahkan di <head> Blade layout member area.
<!-- resources/views/layouts/member.blade.php -->
<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#0F8A4B">
<meta name="apple-mobile-web-app-capable" content="yes">
Hasil Phase 3
Peserta login, lihat dashboard dengan course mereka, tonton video, download PDF via link aman, cek jadwal Zoom, dan install member area ke home screen HP. Pengalaman belajar yang terasa seperti aplikasi native.
Phase 4, Event Offline dan Tiket QR
Kelas offline punya tantangan yang berbeda. Peserta datang ke lokasi fisik, dan kamu perlu verifikasi siapa yang hadir. Solusinya, tiket QR yang di-scan di pintu masuk. Tidak perlu kertas, tidak perlu daftar manual.
Langkah-langkah Phase 4
- Admin create event. Admin buat event offline di Filament, lengkap dengan tanggal, lokasi, kapasitas, dan course terkait. Setiap event punya banyak tiket, satu per enrollment.
- Auto-generate QR tiket. Saat enrollment untuk offline course dibuat (lewat Phase 2), sistem auto-generate tiket dengan kode unik. Tiket dikirim via email sebagai QR image.
- PWA check-in untuk field staff. Aplikasi terpisah (subdomain berbeda, misal
checkin.domain.com) yang bisa di-install ke HP staff. Login staff, pilih event, scan QR via kamera. - Scan QR dan verifikasi. Saat QR di-scan, sistem cek apakah kode valid, belum pernah di-scan (one-time check-in), dan sesuai dengan event yang dipilih. Tampilkan nama peserta dan status.
- Manual code input fallback. Kalau kamera tidak bisa scan (QR buram, HP rusak), staff bisa input kode manual. Validasi sama seperti scan.
- Check-in dashboard. Admin panel menampilkan real-time count check-in per event, log check-in, dan export laporan kehadiran.
12.1 Generate QR Code
QR code bisa di-generate di backend dengan package simplesoftwareio/simple-qrcode atau di frontend dengan JavaScript library. Untuk email tiket, generate di backend supaya langsung embedded di email sebagai inline image.
// app/Jobs/SendTicketEmail.php
use SimpleSoftwareIO\QrCode\Facades\QrCode;
public function handle(): void
{
$qr = QrCode::format('png')
->size(300)
->generate($this->ticket->code);
Mail::to($this->ticket->enrollment->user->email)
->send(new TicketMail($this->ticket, $qr));
}
12.2 Check-in Logic, One-Time Scan
Inti check-in adalah, satu tiket hanya boleh di-scan sekali. Kalau di-scan kedua kali, tampilkan "Duplikat". Ini mencegah penyalahgunaan tiket yang di-foto dan dipakai orang lain.
// app/Livewire/CheckInScanner.php (Livewire component)
public function processScan(string $code): array
{
$ticket = Ticket::where('code', $code)
->where('event_id', $this->eventId)
->first();
if (! $ticket) {
return ['status' => 'invalid', 'message' => 'Tiket tidak ditemukan.'];
}
if ($ticket->status === 'used') {
return ['status' => 'duplicate', 'message' => 'Tiket sudah di-scan.', 'name' => $ticket->enrollment->user->name];
}
$ticket->update(['status' => 'used', 'checked_in_at' => now()]);
CheckIn::create([
'ticket_id' => $ticket->id,
'staff_id' => auth()->id(),
'event_id' => $this->eventId,
]);
return ['status' => 'ok', 'message' => 'Check-in berhasil!', 'name' => $ticket->enrollment->user->name];
}
Offline-Capable PWA Check-in
Check-in app harus tetap jalan walau sinyal di lokasi event jelek. Gunakan service worker untuk cache halaman check-in, dan sinkronisasi data check-in saat koneksi pulih. Tapi validasi tiket tetap butuh server, jangan simpan data tiket di client.
Hasil Phase 4
Admin buat event, peserta dapat tiket QR via email, staff scan di lokasi, dashboard real-time menunjukkan siapa yang hadir. Tidak ada kertas, tidak ada daftar manual, tidak ada kecurangan tiket dipakai dua kali.
Phase 5, Lead Magnet dan Funnel
Lead magnet adalah umpan. Kamu kasih sesuatu gratis (mini course, PDF, checklist) sebagai ganti email. Dari email itu, kamu bangun hubungan lewat autoresponder, sampai mereka akhirnya beli produk berbayar. Ini adalah mesin pertumbuhan organik.
Langkah-langkah Phase 5
- Opt-in landing page. Halaman sederhana dengan satu janji jelas, "Daftar dan dapatkan mini course gratis". Form email saja, jangan minta data lain. Semakin sedikit field, semakin tinggi konversi.
- Auto-deliver free course. Setelah submit, sistem auto-create enrollment ke free course. Email selamat datang langsung terkirim dengan link akses member area.
- Autoresponder sequence. Rangkaian email otomatis yang dikirim selama beberapa hari setelah opt-in. Email 1 (hari 0) selamat datang, Email 2 (hari 2) tips, Email 3 (hari 4) testimoni, Email 4 (hari 6) soft pitch, Email 5 (hari 7) hard pitch dengan diskon.
- Tracking konversi. Pasang Meta Pixel dan Google Tag Manager di opt-in page dan checkout page. Track event "Lead" saat opt-in, "Purchase" saat bayar. Ini memberi kamu data, berapa persen lead yang akhirnya jadi buyer.
- Lead management di admin. Filament resource untuk melihat daftar lead, status (new, engaged, converted), riwayat email yang sudah dikirim, dan catatan manual untuk follow-up.
13.1 Tabel Leads yang Sederhana
Tabel leads tidak perlu rumit. Cukup email, source (landing page mana), status, dan timestamp. Jangan tambah kolom yang belum dibutuhkan.
// database/migrations/xxxx_create_leads_table.php
Schema::create('leads', function (Blueprint $table) {
$table->id();
$table->string('email')->unique();
$table->string('source')->nullable();
$table->enum('status', ['new', 'engaged', 'converted'])->default('new');
$table->foreignId('user_id')->nullable()->constrained()->nullOnDelete();
$table->timestamps();
$table->index('status');
});
13.2 Autoresponder dengan Scheduled Job
Autoresponder adalah email otomatis yang dikirim berdasarkan jadwal. Gunakan Laravel Scheduler untuk dispatch email pada interval tertentu setelah lead opt-in.
// app/Console/Commands/SendAutoresponder.php
public function handle(): void
{
$sequences = [
0 => 'WelcomeEmail',
2 => 'TipsEmail',
4 => 'TestimoniEmail',
6 => 'SoftPitchEmail',
7 => 'HardPitchEmail',
];
foreach ($sequences as $day => $mailable) {
$leads = Lead::where('status', 'new')
->whereDate('created_at', now()->subDays($day))
->whereDoesntHave('sentEmails', fn ($q) => $q->where('mailable', $mailable))
->get();
foreach ($leads as $lead) {
dispatch(new SendAutoresponderEmail($lead, $mailable));
}
}
}
Daftarkan di scheduler, jalankan setiap jam.
// app/Console/Kernel.php atau routes/console.php
Schedule::command('autoresponder:send')->hourly()->onQueue('emails');
Hasil Phase 5
Funnel lengkap. Visitor datang ke opt-in page, submit email, dapat free course, terima 5 email autoresponder selama seminggu, sebagian di antaranya akhirnya checkout produk berbayar. Semua tercatat dan terukur.
Phase 6, Polish, SEO, dan Testing
Fase terakhir bukan tambahan, tapi penyempurnaan. Semua fitur sudah jalan, tapi belum tentu cepat, belum tentu SEO-friendly, dan belum tentu aman. Phase 6 memastikan produk siap bertemu dunia.
Langkah-langkah Phase 6
- SEO meta tags. Setiap sales page punya title, meta description, og:title, og:description, og:image, canonical. Gunakan SEO package seperti
artesaos/seotoolsatau manual di Blade. - Schema.org structured data. Tambahkan JSON-LD untuk Course, Product (dengan Offer), dan FAQPage. Ini membantu Google memahami konten dan menampilkan rich snippet.
- Sitemap dan robots.txt. Generate
sitemap.xmlotomatis denganspatie/laravel-sitemap. Submit ke Google Search Console.robots.txtallow public pages, disallow member area dan admin. - Performance audit. Jalankan Google PageSpeed Insights. Target, sales page load di bawah 2 detik di HP 4G. Optimize image (WebP), minify CSS/JS via Vite, enable gzip di Nginx, cache query berat di Redis.
- Responsive audit. Test semua halaman di HP (Chrome DevTools, lebar 360px dan 390px). Pastikan tidak ada horizontal scroll, touch target minimal 48px, font readable.
- Security checklist. HTTPS only (redirect HTTP ke HTTPS), HSTS header, CSRF token di semua form, rate limiting di auth dan checkout, input validation di semua endpoint, tidak ada secret di git.
- Testing dasar. Test feature kritis, checkout flow, webhook handler, check-in scanner, auto-enroll. Gunakan PHPUnit untuk feature test. Tidak perlu 100% coverage, tapi wajib cover jalur bisnis utama.
14.1 Schema.org untuk Sales Page
Structured data membantu Google menampilkan rich snippet di hasil pencarian. Untuk sales page course, gabungkan Course dan Product.
<!-- resources/views/sales/page.blade.php -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Course",
"name": "{{ $course->title }}",
"description": "{{ $course->description }}",
"provider": {
"@type": "Organization",
"name": "Nama Platform Kamu",
"sameAs": "https://yourdomain.com"
},
"offers": {
"@type": "Offer",
"price": "{{ $course->price }}",
"priceCurrency": "IDR",
"availability": "https://schema.org/InStock"
}
}
</script>
14.2 FAQ Schema untuk Accordion
Kalau sales page punya FAQ accordion, tambahkan FAQPage schema. Google sering menampilkan FAQ langsung di hasil pencarian, yang meningkatkan CTR.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": [
@foreach($faqs as $faq)
{
"@type": "Question",
"name": "{{ $faq->question }}",
"acceptedAnswer": {
"@type": "Answer",
"text": "{{ $faq->answer }}"
}
}@if(!$loop->last),@endif
@endforeach
]
}
</script>
14.3 Nginx Gzip dan Cache Headers
Performance bukan cuma soal kode, tapi juga soal konfigurasi server. Nginx gzip dan cache header yang tepat bisa mempercepat load time signifikan.
# /etc/nginx/sites-available/yourdomain.com
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1000;
location ~* \.(css|js|png|jpg|jpeg|webp|svg|ico|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
Hasil Phase 6
Produk siap produksi. SEO lengkap, performance cepat, security terkunci, testing dasar pass. Kamu bisa deploy dengan tenang.
Anti-Bloat, Aturan yang Menyelamatkan
Sepanjang perjalanan ini, kamu akan tergoda menambah hal yang tidak perlu. File baru, class baru, fitur baru. Aturan ini bukan pembatas, tapi penyelamat. Mereka menjaga produk tetap ramping dan mudah dirawat.
15.1 Simplicity First
Pilih solusi paling sederhana yang bisa berjalan di production. Bukan solusi paling elegan, bukan solusi paling canggih. Tapi solusi yang bisa kamu pahami tiga bulan dari sekarang tanpa baca dokumentasi sendiri.
15.2 MVP First
Bangun kebutuhan hari ini, bukan kemungkinan kebutuhan masa depan. Kalau hari ini kamu butuh form checkout dengan 3 field, jangan buat 7 field "buat jaga-jaga nanti kalau butuh data tambahan". Tambah field saat memang dibutuhkan.
15.3 No Premature Abstraction
Jangan membuat Service, Repository, Interface, DTO, Event, Listener, atau Helper kecuali memang sudah dibutuhkan. Abstraksi prematur adalah penyebab nomor satu kode yang sulit dibaca. Tunggu sampai logic yang sama muncul tiga kali, baru boleh diabstraksi.
15.4 Minimize File Count
Jangan membuat file baru jika masih layak ditempatkan pada struktur yang sudah ada. Satu controller bisa menangani beberapa action terkait. Satu Livewire component bisa menangani satu halaman lengkap. File yang sedikit berarti navigasi mudah, dan developer baru cepat paham struktur.
15.5 Delete Test
Sebelum membuat file, tanyakan, kalau file ini dihapus, apakah sistem berhenti berjalan? Kalau tidak, jangan buat file itu. Setiap file adalah beban maintenance. File yang tidak memberikan nilai bisnis atau teknis adalah sampah yang menumpuk.
15.6 Prefer Boring Technology
Pilih teknologi yang stabil, umum digunakan, dan sudah diuji ribuan developer. Laravel, PostgreSQL, Redis, Nginx. Bukan framework baru yang baru rilis minggu lalu. Teknologi bosan itu kekuatan, karena setiap error sudah pernah dihadapi orang lain sebelummu.
Tanda-Tanda Over-Engineering
Kalau kamu menulis interface yang hanya punya satu implementasi, itu over-engineering. Kalau kamu membuat Repository yang hanya wrap Model::find(), itu over-engineering. Kalau kamu membuat Event dan Listener padahal bisa langsung di Controller, itu over-engineering.
Hentikan dan sederhanakan.
Deploy ke VPS, Langkah Nyata
Setelah semua fase selesai, saatnya produk hidup. Deploy ke VPS bukan sihir, tapi rangkaian langkah yang bisa diikuti. Berikut panduan praktis untuk deploy Laravel ke VPS Ubuntu.
Langkah-langkah Deploy
- Siapkan VPS. VPS Ubuntu 22.04 atau 24.04. Update sistem, install PHP 8.2+, PostgreSQL, Redis, Nginx, Node.js, Composer. Pastikan versi sesuai dengan yang dipakai di development.
- Konfigurasi database. Buat database dan user di PostgreSQL. Set password kuat. Jangan pakai user
postgresuntuk aplikasi. - Clone project dan install dependency.
git cloneke/var/www/yourdomain.com. Jalankancomposer install --no-dev --optimize-autoloaderdannpm install && npm run build. - Konfigurasi
.env. SetAPP_ENV=production,APP_DEBUG=false,APP_URL=https://yourdomain.com. Isi semua credential database, Redis, payment gateway, email service, Zoom API. Jangan commit.envke git. - Migrate dan seed.
php artisan migrate --force. Jalankan seeder untuk data awal (admin user, roles, permissions, course contoh). - Konfigurasi Nginx. Buat server block untuk domain. Root ke
/var/www/yourdomain.com/public. Pasang SSL dengan Certbot. Enable gzip, cache headers, dan security headers. - Setup Supervisor untuk queue worker. Queue worker harus jalan terus dan auto-restart kalau crash. Buat konfigurasi Supervisor, mulai dan enable saat boot.
- Setup cron untuk scheduler. Tambahkan
* * * * * cd /var/www/yourdomain.com && php artisan schedule:run >> /dev/null 2>&1di crontab. Ini menjalankan task terjadwal seperti autoresponder. - Optimize untuk production.
php artisan config:cache,php artisan route:cache,php artisan view:cache,php artisan event:cache. Ini mempercepat aplikasi dengan cache config dan route. - Test end-to-end. Buka domain di browser, test register, login, checkout (pakai mode test payment gateway), cek email masuk, akses member area. Pastikan semua jalan sebelum umumkan ke publik.
16.1 Supervisor untuk Queue Worker
# /etc/supervisor/conf.d/yourdomain-worker.conf
[program:yourdomain-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/yourdomain.com/artisan queue:work --queue=emails,default --tries=3 --max-time=3600
autostart=true
autorestart=true
user=www-data
numprocs=2
redirect_stderr=true
stdout_logfile=/var/www/yourdomain.com/storage/logs/worker.log
stopwaitsecs=3600
16.2 Nginx Server Block
# /etc/nginx/sites-available/yourdomain.com
server {
listen 80;
server_name yourdomain.com www.yourdomain.com;
root /var/www/yourdomain.com/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
Setelah konfigurasi, enable site dan install SSL.
sudo ln -s /etc/nginx/sites-available/yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
Jangan Lupa Security Headers
Tambahkan security headers di Nginx, HSTS, X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, Content-Security-Policy. Ini melindungi dari serangan umum seperti clickjacking, MIME sniffing, dan XSS. Buat sebagai snippet terpisah dan include di semua server block.
Produk Hidup
Domain terbuka, HTTPS aktif, queue worker jalan, cron scheduler berjalan, SSL valid. Produkmu sudah hidup di internet. Selamat, kamu sudah selesai dari nol sampai production.
Epilog dan Keberanian untuk Mulai
Kamu sudah membaca seluruh perjalanan. Dari ide kosong, PRD, ERD, stack, arsitektur, design system, wireframe, enam fase development, anti-bloat rules, sampai deploy ke VPS. Sekarang kamu tahu gambar besarnya. Tapi membaca tidak sama dengan membuat.
Manual ini bukan resep yang harus diikuti baris demi baris. Manual ini adalah peta. Peta membantu kamu tidak tersesat, tapi kamu tetap harus berjalan sendiri. Beberapa bagian akan berbeda di produkmu, beberapa tantangan tidak ada di manual ini, dan beberapa solusi akan kamu temukan sendiri. Itu wajar. Itu bagian dari proses.
Hal terpenting yang ingin saya sampaikan
Mulai saja. Jangan tunggu sampai merasa siap. Tidak ada momen di mana kamu merasa 100% siap. Buka editor, buat project baru, tulis baris pertama. Kalau tersandung, baca manual ini lagi. Kalau bingung, kembali ke PRD. Kalau tergoda menambah fitur, baca Anti-Bloat rules.
Setiap platform besar yang kamu kenal hari ini dimulai dari satu orang yang berani menulis baris pertama. Mereka tidak lebih pintar dari kamu. Mereka hanya lebih cepat mulai.
Dan ingat, produk yang "cukup baik dan jalan" selalu mengalahkan produk yang "sempurna tapi tidak pernah selesai". Ship, pelajari dari feedback, iterasi. Itu siklus yang tidak pernah berakhir, dan itulah yang membuat produk tumbuh.
Selamat membangun. Semoga produkmu bermanfaat bagi banyak orang.
Ditulis Oleh
Anjrah Ari Susanto, S.Psi.
Founder & Pengajar Alanotech yang fokus pada pembelajaran vibecoding dan praktik membangun produk web berbasis Laravel. Menyusun artikel ini dari pengalaman membangun platform membership dan e-course dari nol sampai production, untuk membantu pemula dan vibecoder Indonesia punya keberanian bikin produk sendiri, dari ide kosong sampai deploy ke VPS.
Baca Juga
Artikel Terkait
AEOS Core, Panduan Lengkap Memahami Vibecoding
Pahami 7 modul, 6 prinsip inti, dan 10 aturan engineering di balik standar vibecoding.
Product Planning AEOS, Berpikir Sebelum Ngoding
Kerangka berpikir sebelum mulai coding, supaya tidak over-engineering dan ship tepat.
Project Brain AEOS, 5 File Otak Setiap Project
5 file yang menjadi otak setiap project vibecoding, supaya AI dan kamu sama haluannya.
VPS atau Hosting, Panduan Memilih
Kenapa VPS lebih cocok untuk platform membership daripada shared hosting.
Elemen Desain Website untuk Vibecoding
Istilah desain web yang wajib dipahami vibecoder sebelum mulai membangun UI.
Dari User ke Owner, 6 Level Manusia di Era AI
Posisi Anda di perjalanan digital menentukan jenis produk yang harus dibangun.
Semua materi disusun oleh Anjrah Ari Susanto, S.Psi. dari praktik membangun produk web berbasis Laravel dan vibecoding di Alanotech.