Platform kolaboratif nasional berbasis web untuk mendorong penggunaan gadget yang sehat, terukur, dan berdampak positif pada kualitas pendidikan di sekolah-sekolah Indonesia.
Tautan Produksi: gesamega.web.id
- Teknologi yang Digunakan
- Arsitektur Proyek
- Struktur Direktori
- Skema Basis Data
- Fitur Utama
- Rute API
- Autentikasi & Keamanan
- Pengaturan & Pengembangan
- Variabel Lingkungan
- Deployment
- Performa & Caching
- Catatan & Masalah yang Diketahui
| Kategori | Teknologi |
|---|---|
| Framework | Next.js 16 (App Router) |
| Bahasa | TypeScript 5 |
| Basis Data | Supabase (PostgreSQL) |
| Autentikasi | Supabase Auth |
| CSS | Tailwind CSS v4 |
| Animasi | Framer Motion |
| Peta | Leaflet + React-Leaflet |
| QR Code | qrcode.react |
| Keamanan | Google reCAPTCHA v3 |
| Ikon | Lucide React |
| jsPDF | |
| Tanda Tangan | react-signature-canvas |
| Panduan | driver.js |
| Runtime | Node.js (Vercel Edge-compatible) |
Browser (React Client)
│
├─ Next.js App Router (SSR / ISR / CSR)
│ │
│ ├─ src/middleware.ts ← Penjaga autentikasi (Edge Runtime)
│ ├─ src/app/(main)/ ← Halaman publik
│ ├─ src/app/admin/ ← Panel Admin (terlindungi)
│ └─ src/app/api/ ← Rute API (serverless)
│
└─ Supabase (PostgreSQL + Auth + Storage)
├─ Anon Key → Halaman publik (RLS diaktifkan)
└─ Service Role Key → Hanya untuk rute API admin
| Halaman | Mode | Revalidasi |
|---|---|---|
/ (Beranda) |
ISR | 1 jam |
/kuis |
CSR | - |
/survei |
CSR | - |
/peta |
CSR | - |
/artikel |
SSR | Per permintaan |
/artikel/[slug] |
CSR | - |
/studi-kasus |
SSR | Per permintaan |
/hasil |
CSR | - |
/admin/* |
SSR | revalidate = 0 |
Klik untuk melihat detail struktur direktori
sosio-web/
├── src/
│ ├── app/
│ │ ├── (main)/ # Grup rute publik (dengan Navbar + Footer)
│ │ │ ├── page.tsx # Beranda (ISR)
│ │ │ ├── kuis/page.tsx # Kuis Refleksi Digital Siswa
│ │ │ ├── survei/ # Survei sekolah
│ │ │ ├── hasil/ # Hasil statistik kuis
│ │ │ ├── peta/ # Peta sebaran sekolah (Leaflet)
│ │ │ ├── artikel/ # Daftar & detail artikel
│ │ │ ├── studi-kasus/ # Studi kasus sekolah
│ │ │ ├── komitmen/ # Formulir komitmen digital sekolah
│ │ │ ├── tentang/ # Tentang GESAMEGA
│ │ │ ├── tim/ # Tim
│ │ │ └── visi-misi/ # Visi & Misi
│ │ ├── admin/ # Panel Admin (terlindungi autentikasi)
│ │ │ ├── layout.tsx # Tata letak sidebar admin
│ │ │ ├── page.tsx # Dasbor statistik (Kuis + Survei)
│ │ │ ├── data-kuis/page.tsx # Tabel data hasil kuis
│ │ │ ├── data-survei/page.tsx # Tabel data hasil survei instansi
│ │ │ ├── qr-generator/page.tsx # Generator QR Kampanye Kuis
│ │ │ ├── qr-survei/page.tsx # Generator QR Kampanye Survei
│ │ │ └── login/page.tsx # Halaman login admin
│ │ ├── api/
│ │ │ ├── questions/route.ts # GET pertanyaan kuis (di-cache)
│ │ │ ├── submit-quiz/route.ts # POST pengiriman hasil kuis
│ │ │ ├── admin/
│ │ │ │ ├── qr-campaigns/route.ts # POST/DELETE kampanye QR
│ │ │ │ ├── qr-stats/route.ts # GET statistik QR Kuis
│ │ │ │ └── qr-stats-survei/route.ts # GET statistik QR Survei
│ │ ├── actions/
│ │ │ ├── survey.ts # Server Action: pengiriman survei
│ │ │ └── komitmen.ts # Server Action: pengiriman komitmen
│ │ ├── layout.tsx # Tata letak utama (metadata, font)
│ │ ├── robots.ts # Konfigurasi robots.txt
│ │ └── sitemap.ts # Peta situs sitemap.xml
│ ├── components/
│ │ ├── landing/ # Komponen bagian beranda
│ │ ├── layout/ # Navbar, Footer
│ │ ├── ui/ # Komponen UI yang dapat digunakan kembali
│ │ ├── PetaMap.tsx # Komponen peta Leaflet (impor dinamis)
│ │ ├── SchoolAutocomplete.tsx # Pencarian otomatis sekolah
│ │ ├── TourGuide.tsx # Panduan interaktif pengguna (driver.js)
│ │ └── HeroCarousel.tsx # Korsel bagian hero
│ ├── lib/
│ │ ├── supabase.ts # Klien anonim Supabase (umum)
│ │ ├── supabase-browser.ts # Klien peramban Supabase (@supabase/ssr)
│ │ ├── supabase-server.ts # Klien server Supabase (dengan dukungan kuki)
│ │ ├── supabase-admin.ts # Klien peran layanan Supabase (hanya server)
│ │ ├── recaptcha.ts # Verifikasi reCAPTCHA v3 di sisi server
│ │ ├── pdfGenerator.ts # Pembuat PDF hasil kuis (jsPDF)
│ │ ├── utils.ts # Fungsi utilitas pembantu
│ │ ├── repositories/
│ │ │ └── quizRepository.ts # Lapisan akses data kuis
│ │ └── services/
│ │ └── quizService.ts # Logika bisnis kuis & penilaian
│ ├── middleware.ts # Penjaga autentikasi (Auth guard)
│ └── proxy.ts # (DEPRECATED - sudah dihapus)
│
├── supabase/
│ ├── schema.sql # Skema DDL lengkap
│ ├── seed.sql # Data awal sekolah
│ ├── seed_kuis_siswa.sql # Pertanyaan & opsi kuis siswa
│ ├── seed_articles.sql # Data awal artikel
│ └── seed_gesamega.sql # Data GESAMEGA
├── public/ # Aset statis
├── next.config.ts # Konfigurasi Next.js
├── tsconfig.json # Konfigurasi TypeScript
└── .env.local # Variabel lingkungan
schools ──────────┬── survey_results
├── commitments
├── case_studies
└── quiz_results ─── quiz_answers ─── question_options ─── questions
articles (berdiri sendiri)
qr_campaigns (berdiri sendiri)
| Kolom | Tipe | Keterangan |
|---|---|---|
id |
UUID | Primary Key |
nama_sekolah |
TEXT | Nama lengkap sekolah |
wilayah |
TEXT | Kota/Kabupaten |
latitude |
FLOAT | Koordinat peta |
longitude |
FLOAT | Koordinat peta |
status |
ENUM | belum / survei / komitmen |
status_validasi |
ENUM | pending / valid / flagged |
ip_address |
TEXT | IP pengirim (pembatasan laju) |
submit_timestamp |
TIMESTAMPTZ | Waktu pengiriman |
| Kolom | Tipe | Keterangan |
|---|---|---|
id |
UUID | Primary Key |
school_id |
UUID FK | Referensi ke tabel sekolah |
nama |
TEXT | Nama responden instansi |
jawaban |
JSONB | Data mentah formulir survei |
source |
TEXT | Sumber QR kampanye survei (nullable) |
created_at |
TIMESTAMPTZ | Waktu pengiriman |
| Kolom | Tipe | Keterangan |
|---|---|---|
id |
UUID | Primary Key |
school_id |
UUID FK | Referensi ke tabel sekolah (nullable) |
user_name |
TEXT | Nama pengisi |
answers |
JSONB | Array skor mentah |
result_category |
TEXT | Kategori hasil (contoh: "Bijak") |
qualification |
VARCHAR | Sama dengan result_category |
indicator_color |
VARCHAR | Warna indikator (emerald/teal/amber/orange/red) |
description |
TEXT | Deskripsi hasil |
source |
TEXT | Sumber QR kampanye (nullable) |
| Kolom | Tipe | Keterangan |
|---|---|---|
questions.category |
VARCHAR | "Kuis Siswa" atau "Survei" |
question_options.score |
INT | 1-3 (skor per opsi jawaban) |
| Kolom | Tipe | Keterangan |
|---|---|---|
id |
UUID | Primary Key |
name |
TEXT | Nama kampanye |
source |
TEXT UNIQUE | Slug QR (digunakan sebagai parameter URL) |
Catatan: Tabel
qr_campaignstidak tersedia di berkasschema.sql. Pembuatan dilakukan secara manual melalui SQL Editor Supabase:ALTER TABLE quiz_results ADD COLUMN IF NOT EXISTS source TEXT DEFAULT NULL; ALTER TABLE survey_results ADD COLUMN IF NOT EXISTS source TEXT DEFAULT NULL; CREATE TABLE IF NOT EXISTS qr_campaigns ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, name TEXT NOT NULL, source TEXT NOT NULL UNIQUE, created_at TIMESTAMPTZ DEFAULT NOW() );
-- Pencarian teks penuh nama sekolah (trigram)
CREATE INDEX idx_schools_nama_trgm ON schools USING gin (nama_sekolah gin_trgm_ops);
-- Indeks komposit untuk dasbor dan peta
CREATE INDEX idx_schools_status_validation ON schools(status, status_validasi);
-- Indeks Foreign Key
CREATE INDEX idx_survey_results_school_id ON survey_results(school_id);
CREATE INDEX idx_quiz_results_school_id ON quiz_results(school_id);-
Akses Tersembunyi Berdasarkan Desain: Halaman hanya dapat dibuka melalui parameter
?source=slugdari pemindaian kode QR. Akses langsung diblokir (Kesalahan 404). -
Alur Pengguna: Registrasi (nama dan sekolah) dilanjutkan dengan menjawab pertanyaan, dan diakhiri dengan halaman hasil beserta kategori.
-
Sistem Penilaian: Akumulasi skor dari
question_options.score(1-3 per soal). -
Kategori Hasil:
Rentang Skor Kategori Warna ≥ 60 Sangat Bijak Emerald 50-59 Bijak Teal 38-49 Cukup Bijak Amber 25-37 Perlu Pendampingan Orange < 25 Darurat Digital Red -
Pencegahan Duplikasi Ketat: Penyimpanan lokal browser mencatat status penyelesaian
kuis_completed = true. Pengisian ulang dari perangkat yang sama diblokir secara penuh. -
Keamanan: Memanfaatkan reCAPTCHA v3 dengan mekanisme pengulangan hingga tiga kali untuk mengatasi masalah inisialisasi waktu.
- Akses Tersembunyi Berdasarkan Desain: Hanya dapat diakses melalui tautan QR kampanye (
?source=slug). - Data formulir JSONB disimpan di kolom
survey_results.jawaban. - Pelacakan Sumber QR: Data kampanye direkam langsung ke
survey_results.sourcesecara terstandardisasi. - Pencegahan Duplikasi Ketat: Penyimpanan lokal mencatat status penyelesaian
survei_completed = truesetelah keberhasilan pengiriman. - Validasi instansi: Hanya sekolah yang terdaftar di basis data yang diizinkan mengirimkan survei.
- Status sekolah secara otomatis diperbarui dari
belummenjadisurvei.
- Implementasi tanda tangan digital menggunakan pustaka
react-signature-canvas, dengan penyimpanan pada Supabase Storage. - Pengiriman formulir mengubah atribut status
schools.statusmenjadikomitmendan memvalidasi entri.
- Menggunakan peta Leaflet dengan penanda warna yang merepresentasikan status (belum/survei/komitmen).
- Eksklusivitas Panggilan Aksi (CTA): Tombol menuju halaman
/komitmenhanya muncul saat pengunjung dialihkan secara otomatis pasca-penyelesaian survei. Pengunjung umum tidak diberikan akses visibilitas terhadap CTA ini. - Implementasi impor dinamis guna mencegah kesalahan Server-Side Rendering (SSR) pada komponen Leaflet.
- Fasilitas pembuatan kampanye QR terpisah untuk Kuis Siswa dan Survei Instansi.
- Penambahan awalan otomatis
survei-untuk memisahkan jalur pelacakan data. - Kemampuan mengunduh kode QR dalam format gambar PNG dengan rancangan yang disesuaikan.
- Representasi statistik peserta per kampanye yang ditampilkan secara seketika (real-time).
- Antarmuka Pengguna Modern: Struktur navigasi dikelompokkan secara visual untuk mengoptimalkan pengalaman pengguna (Gambaran Umum, Kuis Siswa, Survei Instansi).
- Dasbor Utama: Menyediakan metrik analitik tingkat tinggi mengenai tingkat partisipasi kuis dan survei.
- Data Kuis & Survei: Tabel pemantauan data spesifik untuk masing-masing tipe kuesioner.
- QR Kuis & Survei: Antarmuka pembuat kode QR khusus untuk mendukung kampanye pengguna.
Mendapatkan daftar pertanyaan kuis yang tersedia.
Parameter Kueri:
| Parameter | Nilai Baku | Keterangan |
|---|---|---|
category |
- | Filter kategori (misal: Kuis%20Siswa) |
Contoh Respons:
{
"success": true,
"data": [
{
"id": 101,
"question_text": "...",
"category": "Kuis Siswa",
"question_options": [
{ "id": 1, "option_text": "...", "score": 1, "question_id": 101 }
]
}
]
}Konfigurasi Cache: Cache-Control: public, s-maxage=3600, stale-while-revalidate=3600
Merekam hasil kuis peserta ke dalam basis data.
Badan Permintaan (Request Body):
{
"user_name": "Budi",
"school_id": "uuid-sekolah",
"answers": [{ "question_id": 101, "option_id": 1, "score": 2 }],
"source": "sman_4_sidoarjo",
"captchaToken": "token-dari-recaptcha-v3"
}Contoh Respons:
{
"success": true,
"data": {
"totalScore": 52,
"category": "Bijak",
"indicator_color": "teal",
"description": "...",
"motivation_message": "..."
}
}Keamanan: Melakukan verifikasi reCAPTCHA v3 (skor minimal 0.5) sebelum eksekusi penyimpanan.
Seluruh rute pada direktori /api/admin/* dilindungi oleh middleware keamanan. Permintaan tanpa sesi yang valid akan ditolak dengan status kode 401 Unauthorized.
Menampilkan statistik komprehensif kampanye QR dan metrik partisipasi.
Pendekatan Teknis: Mengeksekusi 2 kueri paralel untuk optimalisasi performa.
- Mengambil seluruh data
qr_campaigns - Mengambil seluruh data
quiz_results.sourceyang memiliki nilai - Melakukan perhitungan jumlah partisipan pada memori sistem dengan struktur data
Map
Membuat entri kampanye QR baru.
{ "name": "SMAN 4 Sidoarjo", "source": "sman_4_sidoarjo" }Menghapus kampanye QR spesifik berdasarkan parameter ID.
Aturan Pencocokan: ["/admin/:path*", "/api/admin/:path*"]
Mekanisme:
- Seluruh permintaan HTTP menuju
/admin/*atau/api/admin/*ditangkap oleh middleware. - Melakukan pembaruan sesi Supabase melalui kuki.
- Apabila sesi pengguna tidak terdeteksi:
- Akses Halaman akan dialihkan menuju
/admin/login - Akses API akan mengembalikan respons
401 { success: false, error: "Unauthorized" }
- Akses Halaman akan dialihkan menuju
- Apabila pengguna sudah terautentikasi dan mengakses
/admin/login, sistem akan mengalihkan pengguna kembali ke dasbor utama/admin.
PENTING: Middleware hanya berjalan jika berkas bernama
middleware.tsdan berada di direktorisrc/. Berkasproxy.tstidak akan dieksekusi oleh Next.js.
| Klien | Berkas | Lingkup Penggunaan | Kunci Akses |
|---|---|---|---|
| Klien Anonim | supabase.ts |
Komponen Server, Aksi Server | ANON_KEY |
| Klien Peramban | supabase-browser.ts |
Komponen Klien (Autentikasi) | ANON_KEY |
| Klien Server | supabase-server.ts |
Middleware, Komponen Server (Sesi) | ANON_KEY |
| Klien Admin | supabase-admin.ts |
Hanya pada Rute API Admin | SERVICE_ROLE_KEY |
PERINGATAN: Jangan mengekspos
SERVICE_ROLE_KEYke sisi klien. Kunci akses ini akan melewati seluruh lapis keamanan Row Level Security (RLS).
Verifikasi keamanan dijalankan pada sisi server melalui:
- Rute
POST /api/submit-quiz(Pengiriman kuis) - Aksi Server
submitSurvey(Pengiriman survei) - Aksi Server
submitKomitmen(Pengiriman komitmen)
Batas ambang skor minimal adalah 0.5. Permintaan akan ditolak jika skor berada di bawah batas tersebut.
- Node.js versi 18 atau lebih baru
- npm versi 9 atau lebih baru
- Akun Supabase
- Akun Google reCAPTCHA v3
# 1. Klon repositori
git clone https://github.com/your-org/sosio-web.git
cd sosio-web
# 2. Instalasi dependensi
npm install
# 3. Konfigurasi variabel lingkungan
cp .env.example .env.local
# Lakukan konfigurasi .env.local sesuai dengan bagian Variabel Lingkungan di bawah
# 4. Pengaturan basis data Supabase
# Akses Dasbor Supabase -> SQL Editor
# Jalankan skrip berkas secara berurutan:
# 1. supabase/schema.sql
# 2. supabase/seed.sql
# 3. supabase/seed_kuis_siswa.sql
# 4. supabase/seed_articles.sql (Opsional)
# 5. Eksekusi skrip tambahan qr_campaigns (lihat bagian Skema Basis Data)
# 5. Jalankan peladen (server) pengembangan lokal
npm run devPeladen pengembangan dapat diakses melalui tautan http://localhost:3000.
Catatan: Fitur reCAPTCHA v3 tidak akan berfungsi secara penuh pada domain
localhostsecara bawaan. Anda perlu mendaftarkan domainlocalhostke daftar domain yang diizinkan pada konsol administrasi reCAPTCHA.
npm run build
npm run startBuat berkas .env.local pada direktori root proyek:
# Supabase
NEXT_PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
# Supabase Service Role (RAHASIA - Hanya untuk server, jangan ekspor ke klien!)
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...
# Google reCAPTCHA v3
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=6Lxxxxxxxxxxxxxxxx
RECAPTCHA_SECRET_KEY=6Lxxxxxxxxxxxxxxxx| Variabel | Hak Akses | Keterangan |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Publik | Tautan utama proyek Supabase |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Publik | Kunci anonim publik (dilindungi RLS) |
SUPABASE_SERVICE_ROLE_KEY |
Server saja | Melewati RLS (Hanya untuk API admin) |
NEXT_PUBLIC_RECAPTCHA_SITE_KEY |
Publik | Kunci situs reCAPTCHA (Untuk antarmuka) |
RECAPTCHA_SECRET_KEY |
Server saja | Kunci rahasia reCAPTCHA (Verifikasi) |
- Lakukan pembaruan kode ke repositori GitHub.
- Impor proyek pada platform vercel.com.
- Atur variabel lingkungan pada menu Pengaturan (Settings) -> Variabel Lingkungan (Environment Variables).
- Pembaruan akan diterapkan secara otomatis saat terjadi dorongan (push) pada cabang
main.
Konfigurasi Domain Khusus:
- Tetapkan domain
gesamega.web.idpada bagian Domain di panel kontrol Vercel. - Pastikan pencatatan DNS (DNS Record) mengarah ke server Vercel.
Konfigurasi Tambahan reCAPTCHA:
- Tambahkan domain
gesamega.web.idke dalam daftar domain yang diizinkan pada Konsol Admin Google reCAPTCHA.
const nextConfig: NextConfig = {
allowedDevOrigins: ["192.168.x.x"], // Alamat IP lokal untuk pengujian perangkat genggam
};| Titik Akhir / Halaman | Metode Cache | Durasi Berlaku (TTL) |
|---|---|---|
GET /api/questions |
CDN (s-maxage) |
1 jam + 1 jam SWR |
/ (Beranda) |
ISR Next.js | 1 jam |
/admin/* |
Tanpa Cache | - |
/artikel, /studi-kasus |
SSR (Setiap permintaan) | - |
| Komponen Sistem | Pendekatan Teknis |
|---|---|
| Dasbor Admin | 1 kueri agregat, pemrosesan di JS |
| Data Kuis | Pemilihan kolom spesifik + batas (200) |
| Statistik QR | 2 kueri eksekusi paralel |
| Halaman Beranda | Pengambilan data asinkron paralel |
| Pencarian Sekolah | Pemanfaatan indeks GIN trigram |
API Route (/api/submit-quiz)
└─ quizService.submitQuiz() ← Logika bisnis, penilaian akhir
└─ quizRepository.saveQuizResult() ← Lapisan akses data (Data Access Layer)
└─ supabase.from("quiz_results").insert(...)
- Aktivitas reCAPTCHA pada Localhost: Proses submisi dapat mengalami kegagalan pada lingkungan pengembangan jika domain
localhosttidak didaftarkan pada konsol Google. Hal ini adalah fungsi perilaku standar dan tidak memengaruhi kinerja pada peladen produksi. - Peringatan Hidrasi (Hydration Warning): Ekstensi peramban pihak ketiga (seperti ekstensi terjemahan atau pemblokir iklan) dapat memicu peringatan ketidakcocokan hidrasi pada Next.js. Hal ini tidak mengindikasikan kerusakan aplikasi.
- Halaman Artikel Dinamis: Halaman detail
artikel/[slug]saat ini mengimplementasikan CSR ("use client"). Proses konversi menuju Komponen Server sedang dikaji untuk optimalisasi indeks mesin pencari (SEO). - Tabel
qr_campaigns: Konstruksi tabel ini tidak termasuk ke dalam cetak biruschema.sqldan memerlukan pembuatan manual pada panel Editor SQL Supabase sesuai dengan instruksi.
