π Mengecek koneksi otomatis ke server (config.php / /api/config)...
β Terhubung otomatis dari server (config.php). Semua modul blueprint sudah pakai kredensial ini tanpa perlu isi manual. Mau ganti project Supabase? Lihat blueprint/SUPABASE_SETUP.md.
β οΈ Belum ada config Supabase otomatis dari server. Isi URL dan keys di bawah lalu simpan β semua modul akan otomatis membaca dari sini. (Atau edit blueprint/config.php β lihat SUPABASE_SETUP.md)
β Config tersimpan! Semua modul sudah bisa membaca Supabase config dari sini.
Project URL
Anon Key (aman untuk frontend)
Project Ref (ID singkat)
Service Role Key (JANGAN expose ke frontend)
Disimpan lokal β tidak terkirim ke mana pun
Config tersimpan
Project URL
Project Ref
π Setup Awal Supabase Project
π‘ Supabase = backend open-source (PostgreSQL + Edge Functions + Storage + Auth) yang bisa dipakai gratis untuk project awal. Kamu tidak perlu server sendiri.
1
Buat Akun & Project Baru
1. Buka supabase.com β klik Start your project
2. Sign up dengan GitHub (paling cepat)
3. Klik New Project β pilih organisasi β isi nama project & region
4. Pilih region: Southeast Asia (Singapore) untuk latensi terbaik dari Indonesia
5. Tunggu ~2 menit sampai project siap
2
Ambil API Keys
Di Supabase dashboard: Settings (βοΈ) β API
Yang perlu dicatat:
β’ Project URL β format: https://[ref].supabase.co
β’ anon / public key β aman dipakai di frontend HTML/JS
β’ service_role key β HANYA untuk edge functions & server, JANGAN expose
β’ Project Ref β ID singkat di URL dashboard (misal: iyatwaubpyqbjwrguuna)
3
Install Supabase CLI
Dibutuhkan untuk deploy Edge Functions dan manage project via terminal.
# Opsi 1: via npm (paling umum)
npm install -g supabase
# Opsi 2: via Homebrew (macOS)
brew install supabase/tap/supabase
# Verifikasi instalasi
supabase --version
# Login dengan akun Supabase
supabase login
# Link ke project kamu
supabase link --project-ref [PROJECT_REF]
# Contoh
supabase link --project-ref iyatwaubpyqbjwrguuna
// Test koneksi dari browser console atau file HTML
const SUPABASE_URL = '[PROJECT_URL]'
const SUPABASE_ANON_KEY = '[ANON_KEY]'
// Test query sederhana (setelah tabel leads dibuat)
const res = await fetch(`${SUPABASE_URL}/rest/v1/leads?limit=1`, {
headers: {
'apikey': SUPABASE_ANON_KEY,
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`
}
})
const data = await res.json()
console.log('Koneksi OK:', data)
ποΈ Database Schema β Semua Tabel
π‘ Jalankan semua SQL ini di Supabase Dashboard β SQL Editor. Urutan: jalankan satu per satu, atau semuanya sekaligus.
β
Tabel: leads β Untuk Landing Page & Lead Magnet
-- Tabel utama untuk simpan semua leads dari landing page, lead magnet, quiz, dll
CREATE TABLE IF NOT EXISTS leads (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
email TEXT NOT NULL,
name TEXT,
phone TEXT,
source TEXT, -- 'landing_page' | 'lead_magnet' | 'quiz' | 'cold_wa' | 'organic'
utm_source TEXT,
utm_medium TEXT,
utm_campaign TEXT,
utm_content TEXT,
fbc TEXT, -- Meta click ID (fbclid β diformat jadi fbc)
fbp TEXT, -- Meta browser pixel ID (cookie _fbp)
user_agent TEXT,
ip TEXT,
status TEXT DEFAULT 'lead', -- 'lead' | 'nurture' | 'buyer' | 'refund' | 'unsubscribed'
tags TEXT[], -- array tag: ['quiz_taker', 'email_subscriber', ...]
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Index untuk query cepat
CREATE INDEX IF NOT EXISTS leads_email_idx ON leads(email);
CREATE INDEX IF NOT EXISTS leads_status_idx ON leads(status);
CREATE INDEX IF NOT EXISTS leads_source_idx ON leads(source);
CREATE INDEX IF NOT EXISTS leads_created_at_idx ON leads(created_at DESC);
-- Auto-update timestamp
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN NEW.updated_at = NOW(); RETURN NEW; END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER leads_updated_at
BEFORE UPDATE ON leads
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
-- Row Level Security
ALTER TABLE leads ENABLE ROW LEVEL SECURITY;
β‘
Tabel: purchases β Untuk Tracking Transaksi
-- Tabel transaksi dari payment gateway (Mayar.id, Xendit, dll)
CREATE TABLE IF NOT EXISTS purchases (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
lead_id UUID REFERENCES leads(id) ON DELETE SET NULL,
order_id TEXT UNIQUE NOT NULL,
product TEXT NOT NULL, -- nama produk
product_type TEXT, -- 'ebook' | 'bundle' | 'workbook'
amount INTEGER NOT NULL, -- dalam Rupiah (tanpa desimal)
currency TEXT DEFAULT 'IDR',
status TEXT DEFAULT 'pending', -- 'pending' | 'paid' | 'refund' | 'failed'
gateway TEXT, -- 'mayar' | 'xendit' | 'midtrans'
gateway_ref TEXT, -- ID transaksi dari gateway
paid_at TIMESTAMPTZ,
buyer_email TEXT,
buyer_name TEXT,
buyer_phone TEXT,
fbc TEXT,
fbp TEXT,
utm_source TEXT,
utm_campaign TEXT,
metadata JSONB DEFAULT '{}', -- data tambahan dari webhook
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS purchases_lead_id_idx ON purchases(lead_id);
CREATE INDEX IF NOT EXISTS purchases_status_idx ON purchases(status);
CREATE INDEX IF NOT EXISTS purchases_order_id_idx ON purchases(order_id);
ALTER TABLE purchases ENABLE ROW LEVEL SECURITY;
β’
Tabel: quiz_results β Untuk Modul Quiz
-- Simpan hasil quiz per user
CREATE TABLE IF NOT EXISTS quiz_results (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
lead_id UUID REFERENCES leads(id) ON DELETE CASCADE,
email TEXT,
quiz_name TEXT NOT NULL, -- nama quiz (misal: 'tes-jalur-penghasilan')
result_type TEXT, -- tipe hasil: 'arsitek' | 'mercusuar' | 'navigator' | 'patron'
scores JSONB DEFAULT '{}', -- { arsitek: 12, mercusuar: 8, navigator: 6, patron: 10 }
answers JSONB DEFAULT '[]', -- array jawaban per pertanyaan
completed_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS quiz_lead_idx ON quiz_results(lead_id);
CREATE INDEX IF NOT EXISTS quiz_type_idx ON quiz_results(result_type);
ALTER TABLE quiz_results ENABLE ROW LEVEL SECURITY;
β£
Tabel: ebook_downloads β Untuk Delivery eBook
-- Tracking unduhan ebook (lead magnet & produk berbayar)
CREATE TABLE IF NOT EXISTS ebook_downloads (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
lead_id UUID REFERENCES leads(id) ON DELETE SET NULL,
email TEXT NOT NULL,
ebook_name TEXT NOT NULL, -- 'lead-magnet' | 'produk-utama' | 'tutorial'
is_paid BOOLEAN DEFAULT FALSE, -- false = lead magnet, true = produk berbayar
purchase_id UUID REFERENCES purchases(id) ON DELETE SET NULL,
download_token TEXT UNIQUE, -- token unik untuk link download (expire setelah X jam)
token_expires_at TIMESTAMPTZ,
download_count INTEGER DEFAULT 0,
last_downloaded_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE ebook_downloads ENABLE ROW LEVEL SECURITY;
β€
Tabel: email_log β Untuk Email Marketing
-- Log email yang dikirim (untuk audit & de-duplicate)
CREATE TABLE IF NOT EXISTS email_log (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
lead_id UUID REFERENCES leads(id) ON DELETE SET NULL,
email TEXT NOT NULL,
template TEXT NOT NULL, -- 'welcome' | 'nurture_d1' | 'nurture_d3' | 'purchase_confirm'
subject TEXT,
status TEXT DEFAULT 'sent', -- 'sent' | 'failed' | 'bounced'
provider TEXT DEFAULT 'brevo',
sent_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS email_log_lead_idx ON email_log(lead_id);
CREATE INDEX IF NOT EXISTS email_log_template_idx ON email_log(template);
β₯
RLS Policies β Akses yang Aman
Policy ini memungkinkan edge functions (pakai service_role) menulis ke tabel, tapi frontend (pakai anon) tidak bisa baca data sensitif.
-- Izinkan service_role (edge functions) melakukan semua operasi
-- Frontend hanya bisa INSERT (bukan SELECT data orang lain)
-- leads: frontend bisa insert, tapi tidak bisa baca semua rows
CREATE POLICY "leads_insert" ON leads FOR INSERT
WITH CHECK (true);
-- Untuk baca data sendiri (by email) β opsional jika ada auth
-- CREATE POLICY "leads_select_own" ON leads FOR SELECT
-- USING (email = auth.jwt() ->> 'email');
-- purchases: hanya service_role yang bisa insert (via webhook)
-- quiz_results: service_role insert, user bisa baca hasil sendiri
CREATE POLICY "quiz_insert" ON quiz_results FOR INSERT
WITH CHECK (true);
-- ebook_downloads: frontend bisa insert (untuk tracking), baca via token
CREATE POLICY "ebook_dl_insert" ON ebook_downloads FOR INSERT
WITH CHECK (true);
βοΈ Tabel di bawah ini khusus untuk fitur Simpan/Muat ke Cloud di tiap 18 halaman modul blueprint (bukan untuk website produksi). Setiap modul dapat 1 tabel sendiri, struktur generik: id, label, data JSONB, timestamps.
β₯
18 Tabel bp_* β Cloud Sync per Modul Blueprint
create extension if not exists "pgcrypto";
create or replace function bp_update_updated_at()
returns trigger language plpgsql as $$
begin
new.updated_at = now();
return new;
end;
$$;
-- Template generik untuk 18 tabel modul blueprint:
-- bp_content_calendar, bp_workbook, bp_hooks_copywriting, bp_followup_wa,
-- bp_roadmap, bp_rencana_pemasaran, bp_quiz, bp_pseo_planner, bp_paid_ads,
-- bp_landing_page, bp_email_marketing, bp_ebook_builder, bp_ebook_tutorial,
-- bp_crm, bp_cold_wa, bp_ebook_produk, bp_ebook_lead_magnet, bp_kpi_tracker
--
-- create table if not exists bp_NAMA_TABEL (
-- id uuid primary key default gen_random_uuid(),
-- label text,
-- data jsonb not null default '{}',
-- created_at timestamptz default now(),
-- updated_at timestamptz default now()
-- );
--
-- Lihat file lengkap: blueprint/assets/blueprint_tables.sql
-- (berisi CREATE TABLE + trigger + RLS untuk ke-18 tabel sekaligus,
-- tinggal copy-paste seluruh isi file itu ke SQL Editor Supabase)
π‘ Edge Functions berjalan di server Supabase (Deno runtime). Aman untuk simpan API keys dan memanggil layanan eksternal seperti Brevo, Meta CAPI, Mayar.id, dll.
1
save-lead β Landing Page & Lead Magnet
Dipanggil saat user submit form di landing page atau unduh lead magnet.
# Deploy semua functions sekaligus
supabase functions deploy --no-verify-jwt
# Set secrets (WAJIB sebelum deploy ke production)
supabase secrets set META_ACCESS_TOKEN=xxx
supabase secrets set BREVO_API_KEY=xxx
supabase secrets set MAYAR_SECRET_KEY=xxx
# Cek semua secrets yang sudah diset
supabase secrets list
# URL endpoint setelah deploy:
# https://[PROJECT_REF].supabase.co/functions/v1/save-lead
# https://[PROJECT_REF].supabase.co/functions/v1/payment-webhook
# https://[PROJECT_REF].supabase.co/functions/v1/quiz-result
# https://[PROJECT_REF].supabase.co/functions/v1/generate-download
π Storage β Delivery File eBook
β‘ Supabase Storage untuk simpan file PDF ebook. File tidak bisa diakses langsung β harus via signed URL yang expire dalam beberapa menit. Aman dari download ilegal.
1
Buat Bucket Storage
Dashboard Supabase β Storage β New Bucket
-- Atau via SQL Editor:
INSERT INTO storage.buckets (id, name, public)
VALUES ('ebooks', 'ebooks', false); -- false = private, harus pakai signed URL
-- Bucket 'ebooks' untuk:
-- ebooks/lead-magnet.pdf β ebook gratis (lead magnet)
-- ebooks/produk-utama.pdf β ebook berbayar
-- ebooks/tutorial.pdf β ebook tutorial
-- ebooks/workbook.pdf β workbook PDF
2
Storage RLS Policies
-- Hanya service_role (edge functions) yang bisa akses bucket 'ebooks'
-- User tidak bisa download langsung tanpa signed URL
CREATE POLICY "ebooks_service_only" ON storage.objects
FOR SELECT USING (
bucket_id = 'ebooks' AND auth.role() = 'service_role'
);
-- Upload hanya bisa via dashboard atau CLI
CREATE POLICY "ebooks_upload_auth" ON storage.objects
FOR INSERT WITH CHECK (
bucket_id = 'ebooks' AND auth.role() = 'service_role'
);
3
Upload File via CLI
-- Via Supabase Dashboard: Storage β Bucket 'ebooks' β Upload Files
-- Via CLI (belum stabil, lebih baik pakai dashboard)
-- supabase storage cp ./ebook.pdf ss:///ebooks/produk-utama.pdf
-- Cara alternatif: upload via script Node.js
import { createClient } from '@supabase/supabase-js'
import fs from 'fs'
const supabase = createClient(SUPABASE_URL, SERVICE_ROLE_KEY)
const file = fs.readFileSync('./produk-utama.pdf')
const { error } = await supabase.storage
.from('ebooks')
.upload('produk-utama.pdf', file, { contentType: 'application/pdf', upsert: true })
if (!error) console.log('Upload berhasil!')
π Integrasi Supabase per Modul Blueprint
Setiap modul blueprint bisa membaca config Supabase dari localStorage.getItem('bp_supabase') yang kamu simpan di halaman ini. Sekarang ke-18 modul di bawah sudah terhubung langsung β tiap halaman punya badge status koneksi + tombol β¬οΈ Simpan Cloud / β¬οΈ Muat Cloud di topbar, tersambung ke tabelnya masing-masing.
π Cara Kerja Cloud Sync di Tiap Modul (assets/supabase-connector.js)
Setiap modul cukup include 1 script dan panggil BPCloud.initCloudSync(table, DATA_KEY) β badge status & tombol Simpan/Muat otomatis aktif.
<!-- 1. Tambahkan di <head> -->
<script src="assets/supabase-connector.js"></script>
<!-- 2. Tambahkan di topbar -->
<span class="cloud-badge" id="cloud-badge">βοΈ ...</span>
<button class="btn-cloud" onclick="doCloudSave()">β¬οΈ Simpan Cloud</button>
<button class="btn-cloud" onclick="doCloudLoad()">β¬οΈ Muat Cloud</button>
// 3. Panggil di akhir <script> modul (setelah load())
BPCloud.initCloudSync('bp_nama_tabel', DATA_KEY)
// DATA_KEY = localStorage key yang sudah dipakai modul tsb (mis. 'bp_crm')
// Save akan meng-upsert seluruh isi localStorage[DATA_KEY] sebagai JSONB
// ke kolom `data` di tabelnya. Load akan mengambil baris terakhir
// (atau baris yang sama jika sudah pernah disimpan) dan replace localStorage,
// lalu memanggil ulang function load() milik modul.