Template website komunitas yang dapat dikustomisasi dari panel admin tanpa mengubah source code. Cocok untuk kelas kuliah, sekolah, organisasi, komunitas, usaha kecil, maupun portofolio kelompok.
Antarmukanya terinspirasi pola interaksi iOS dan susunan media Pinterest, dengan satu Dynamic Island untuk navigasi, status proses, notifikasi, dan kontrol musik. Proyek ini bukan produk resmi dan tidak berafiliasi dengan Apple atau Pinterest.
- Identitas, jenis website, istilah anggota, bahasa, kontak, tautan sosial, warna, logo, favicon, dan hero dapat diatur dari Profil Admin.
- Galeri foto/video, pin pilihan di homepage, komentar, dan unggahan publik dengan moderasi admin (termasuk moderasi komentar lintas pin).
- Pin carousel ala Instagram: satu pin berisi hingga 10 foto/video. Buat Pin memakai composer multi-pilih dengan pratinjau carousel, filmstrip yang bisa diurutkan (tahan & geser, tombol, atau Alt+panah), edit per foto, dan unggahan paralel dengan progres per item. Kartu menampilkan badge jumlah item; detail pin bisa digeser (scroll-snap native), punya page control, navigasi keyboard, dan ketuk dua kali untuk Simpan. Moderator meninjau setiap slide sebelum menyetujui.
- Album per acara (
/album) yang dikelola dari Profil Admin → Media → Album. - Agenda (
/agenda) dengan hitung mundur live: acara terdekat tampil sebagai Live Activity di Dynamic Island (H-7 sampai selesai) dan bisa disimpan ke Kalender Apple/Outlook (.ics) atau Google Calendar. - Koleksi Tersimpan (
/tersimpan) untuk pin dan artikel, tersimpan di perangkat tanpa akun dan tersinkron antar-tab. - "Kenangan hari ini" di homepage: media pada tanggal yang sama tahun lalu.
- Pencarian Spotlight full-text bahasa Indonesia (stemming + awalan kata), termasuk isi artikel, caption, dan NIM anggota.
- PWA share target: foto/video dari menu Bagikan galeri Android langsung terbuka di Buat Pin; beberapa file sekaligus langsung menjadi carousel.
- Blog dengan editor rich text, cover, kategori, tag, draft, dan publikasi.
- Profil anggota dengan URL ramah baca seperti
/profil/siti-sholehserta riwayat media dan artikel yang menyebut nama anggota. - Pesan anonim; hanya pesan yang dipin admin ditampilkan di homepage.
- Playlist audio persisten dengan putar, jeda, seek, next/previous, Media Session, dan kontrol Dynamic Island.
- Tema terang/gelap, motion lintas halaman dengan kurva pegas iOS asli
(
linear()spring), tab bar responsif, serta dukunganprefers-reduced-motion. - Sistem kedalaman "Depth" tanpa library 3D: kartu interaktif miring
mengikuti kursor dengan pantulan cahaya, tekan-dalam di layar sentuh, daftar
bangkit dalam 3D saat digulir (scroll-driven animation di compositor), hero
berlapis dengan parallax dan efek mundur saat scroll, serta transisi halaman
3D. Seluruh gerak hanya
transform/opacitydan mati otomatis untuk reduced motion. - Owner dan admin dengan izin per fitur dari tab Profil yang sama: dasbor (sapaan, antrean yang perlu ditinjau, angka utama, pratinjau kiriman dan pesan terbaru), moderasi media/komentar/IP, editor artikel, agenda, musik, pengunjung + notifikasi, akun admin, dan Pengaturan.
- SEO terpusat: site name, canonical URL, judul/deskripsi homepage, gambar sosial, sitemap (dengan gambar), robots, structured data, Search Console, Bing, Analytics, dan konfigurasi AdSense. Pengaturan → SEO menampilkan panel kesehatan SEO, endpoint siap salin, dan langkah Search Console.
- Supabase Postgres + Storage dengan RLS dan signed upload; file besar tidak melewati Server Action.
- Unggahan publik mendarat di bucket privat
media-inboxdan baru dipindah ke bucket publik saat disetujui, sehingga kiriman yang belum dimoderasi tidak pernah punya URL publik. Unggahan yang ditinggal dibersihkan otomatis. - Registry format media terpusat, pemeriksaan signature byte, normalisasi HEIC/HEIF iPhone yang lazy-loaded, serta alias format lintas iOS/Android.
src/lib/site-config/adalah pusat validasi, default, pilihan, dan runtime konfigurasi website.src/app/globals.cssadalah pusat token desain: skala teks iOS Dynamic Type (text-caption2…text-large-title), warna sistem iOS (tone-*, varian-textlolos kontras AA), satu material kaca (glass-material) dengan tepi spekular Liquid Glass (--glass-rim), elevasi kartu terang/gelap (--elevation-*), teksturgrainuntuk mesh gradient, dan masonry.src/lib/theme.tsmenurunkan warna turunan dari warna utama yang diatur admin.src/app/fonts/memuat satu font display self-hosted (Plus Jakarta Sans variabel, OFL) lewatnext/font/localuntuk semua judul; teks isi tetap memakai font sistem. Build tidak membutuhkan akses Google Fonts.src/components/public/dynamic-island/adalah satu-satunya pemilik visual Dynamic Island; feedback fitur masuk melalui kanal yang sama. Island juga memegang aksi halaman dengan sistem prioritas: selama editor (artikel, Edit postingan, Pengaturan) punya perubahan, ia memanggiluseIslandActions({ actions })dan island hanya memuat Kembali · judul · tombol utama (· musik bila lagu sedang diputar). Setelah tersimpan aksi dilepas dan island kembali normal sendiri. Tidak ada bar kaca kedua di atas atau di bawah halaman. Tombolnyasrc/components/admin/island-save.tsx; Ctrl/⌘+S memakaiuseSaveShortcut.src/lib/data/memusatkan pembacaan data publik dan fallback demo.src/lib/database/memusatkan kontrak mutasi dan pesan error database.src/lib/media-formats/memusatkan format ingest, MIME storage, signature, normalisasi, dan probe playback untuk seluruh picker serta API.src/lib/media/slides.ts+src/components/ui/media-carousel.tsxadalah satu bentuk slide dan satu renderer carousel untuk detail pin, moderasi admin, dan composer. Barismediatetap menjadi pin sekaligus sampul; slide berikutnya ada di tabelmedia_slides, sehingga kartu, pencarian, album, dan feed lama tidak berubah.src/components/motion/motion.cssmemusatkan token durasi, kurva spring (--motion-spring,--motion-spring-snappy,--motion-spring-bouncy), dan kedalaman 3D:.motion-card/[data-depth-tilt](tilt + glare, diisi satu listeneruseDepthPointer),.motion-reveal(reveal 3D berbasis scroll),.motion-parallax,.animate-rise, dan transisi rute. Daftar memakailistReveal(index)pada pembungkus item, bukan pada kartu yang ikut tilt. Primitif premium lain ada di file yang sama: judul per kata (<KineticWords>+.motion-word),.motion-blur-in, cahaya tepi[data-spotlight](posisi dari listeneruseDepthPointeryang sama),.edge-fade-xuntuk rail,.motion-live-dot,.motion-digit(hitung mundur),.motion-sheen,.motion-skeleton, dan.motion-ring-spin. Semuanya CSS murni (transform/opacity), tanpa library animasi JS.- Loading konten selalu berupa skeleton yang meniru halaman aslinya, dari
satu otak: gaya
.motion-skeleton(token--skeleton-base/-shine), blok disrc/components/ui/skeleton.tsx, dan komposisi per jenis halaman diskeleton-layouts.tsx. Route cukup menyebutskeleton: "…"di registry (src/lib/navigation/app-routes.ts,ADMIN_FEATURE_META);RouteSkeletondiloading.tsxroot & admin memilih bentuknya. Proses pratinjau (HEIC, unduh foto asli) memakaiSkeletonOverlay. Indikator aksi di tombol datang dariButton(icon+pending). src/components/admin/adalah kit admin bersama:AdminPage(kerangka halaman),AdminTabs(filter URL),AdminList/AdminRow/StatusBadge,admin-actions.tsx(aksi ikon, sheet konfirmasi, hapus, blokir IP),AdminFormDialog(semua dialog buat/edit),form-controls.tsx(semua field),ImageField,SerpPreview, danfeatures.ts(ikon/warna/deskripsi per fitur). Fitur hanya menyumbang field dan datanya.src/lib/admin/guard.tsmemvalidasi argumen Server Action (id/enum) karena action bisa dikirim langsung tanpa UI;src/lib/seo/revalidate.tsmenyegarkan sitemap & feed setiap kali URL publik bertambah/berkurang.src/lib/site-config/hero.ts(resolveHeroContent) adalah satu sumber teks hero untuk beranda dan pratinjau admin; h1 halaman depan selalu bermakna walau judul disembunyikan dari foto.src/components/ui/icon-plate.tsxadalah satu bentuk plat ikon ala Pengaturan iOS untuk menu Profil, banner notifikasi, dan kartu admin.src/components/public/destination-icons.tsadalah satu peta ikon per tujuan navigasi untuk tab bar, panel cepat island, menu Profil, hasil Spotlight, dan navigasi admin.src/lib/utils/time.tsmemusatkan format tanggal/jam di zona waktu situs. Halaman publik ber-cache memakaisrc/components/ui/relative-time.tsx: tanggal absolut saat SSR/hydration, lalu label relatif ("5 menit lalu") sesudah hydration, sehingga tidak ada hydration mismatch. Jam yang terus berdetak memakaiuseNowdisrc/lib/hooks/use-now.ts.src/lib/utils/storage.tsadalah satu daftar keylocalStoragebeserta pembaca/penulis yang aman (mode privat, kuota penuh, JSON rusak).src/lib/media/revalidate.tsadalah satu daftar halaman yang menampilkan media; moderasi, unggah, dan edit postingan memakainya agar tidak ada halaman ber-ISR yang tertinggal basi.- Satu editor per jenis konten, satu klik membuka semuanya (alamatnya di
src/lib/constants.ts): Edit postingan (pensil di kartu Media — media: edit foto item ke-n, tambah foto/video, hapus, urutkan, jadikan sampul; plus judul, caption, kategori, pengunggah, tanggal momen, komentar, album, sorotan) dan Edit artikel (tulisan, status Draf/Terbit/Arsip, cover, SEO). Batas & validasi teks pin ada disrc/lib/media/metadata.ts+metadata-schema.ts(mediaDetailsSchema), dipakai form unggah publik, API unggah, dan Edit postingan. - Buat Pin dan Edit postingan memakai satu komposer carousel
(
src/components/public/media-composer.tsx, termasuk picker file danDraftPhotoEditor), satu state draft (useMediaDrafts, bisa diisi item tersimpan) dan satu pipeline unggah (useDraftUploads). Simpan Edit postingan mengirim susunan media (bila berubah) + detail ke/api/media/[id]/edit; database menulis semuanya atomik lewat RPCupdate_media_postdengan pengecekan "masih sama dengan yang dibuka". src/lib/seo/feed.tsadalah satu sumber isi/feed.xmldan/feed.json.src/components/admin/use-admin-action.tsmemusatkan pending, konfirmasi, dan toast untuk aksi admin;useFormDirty(src/lib/hooks/use-form-dirty.ts) menyediakannotifyFormChangeuntuk kontrol yang berubah tanpa event native (Switch, editor foto).src/lib/types/database.tsadalah kontrak TypeScript untuk skema Supabase.supabase/schema.sqladalah sumber kebenaran database instalasi baru.
- Node.js 24 LTS direkomendasikan (
.nvmrcsudah disediakan); Node.js 22.12+ tetap didukung oleh rentangengines. - npm 11 direkomendasikan. Versi package manager dicatat di
package.jsonagar instalasi publik dapat direproduksi. - Project Supabase untuk mode produksi
Tanpa environment Supabase, aplikasi tetap dapat dibuka memakai data demo generik. Fitur yang menulis data memerlukan Supabase.
Seluruh dependensi berada di rilis terbaru yang kompatibel, kecuali dua major:
- TypeScript tetap 6.0.x. TypeScript 7 (compiler native) belum menyertakan
JS compiler API, padahal
typescript-eslintmensyaratkantypescript <6.1.0dan skripcheck:*memakaits.transpileModule. Next.js 16.3 sendiri sudah mendukung TypeScript 7. - ESLint tetap 9.x.
eslint-config-next16 bergantung padaeslint-plugin-react,eslint-plugin-jsx-a11y, daneslint-plugin-importyang belum mendukung ESLint 10.
npm audit masih melaporkan satu advisory braces khusus dev (lewat
@next/eslint-plugin-next → fast-glob) yang belum memiliki rilis perbaikan;
dependensi produksi bersih (npm run audit:prod).
npm install
cp .env.local.example .env.local
npm run devDi Windows PowerShell, perintah salin environment dapat ditulis sebagai:
Copy-Item .env.local.example .env.localBuka http://localhost:3000. Tab Profil akan mengarahkan instalasi baru ke setup owner pertama.
Isi .env.local dengan nilai dari dashboard Supabase:
| Variable | Kegunaan | Rahasia |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
URL project Supabase | Tidak |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Anon key yang dibatasi RLS | Tidak |
SUPABASE_SERVICE_ROLE_KEY |
Mutasi server dan signed upload | Ya |
AUTH_SECRET |
Menandatangani cookie sesi admin | Ya |
NEXT_PUBLIC_SITE_URL |
Fallback origin sebelum URL disimpan di pengaturan | Tidak |
TRUSTED_PROXY |
Sumber header IP self-hosted (cloudflare, x-real-ip, atau x-forwarded-for) |
Tidak |
NEXT_PUBLIC_SITE_TIME_ZONE |
Zona waktu agenda dan "Kenangan hari ini" (IANA, bawaan Asia/Jakarta) |
Tidak |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
Kunci publik Web Push (opsional; tanpa ini lonceng hanya in-app) | Tidak |
VAPID_PRIVATE_KEY |
Kunci privat Web Push untuk mengirim notifikasi ke perangkat | Ya |
VAPID_SUBJECT |
Kontak pengirim push, mis. mailto:admin@example.com |
Tidak |
AUTH_SECRET harus berupa string acak minimal 32 karakter. Jangan pernah
commit .env, .env.local, service role key, atau secret produksi.
Vercel terdeteksi otomatis. Pada self-hosted, biarkan TRUSTED_PROXY kosong
kecuali origin hanya menerima trafik proxy tepercaya dan proxy selalu menimpa
header IP dari klien; konfigurasi yang salah membuat IP mudah dipalsukan.
- Buat project Supabase.
- Salin
supabase/schema.sqlke SQL Editor lalu jalankan seluruh isinya. - Isi
.env.local. - Jalankan
npm run devdan buat akun owner dari tab Profil. - Buka Profil Admin → Pengaturan untuk mengganti seluruh identitas demo.
supabase/schema.sql bersifat idempoten: untuk memperbarui instalasi lama,
jalankan ulang seluruh isinya di SQL Editor (simpan backup lebih dulu). Skrip
menambahkan kolom, fungsi, trigger, dan bucket baru tanpa menghapus data.
Bila aplikasi menampilkan pesan "Skema database belum diperbarui", langkah ini
yang dibutuhkan.
Sebelum schema.sql terbaru dijalankan, fitur yang tabelnya baru (Agenda,
Album, Kenangan hari ini, pencarian full-text, slide carousel) tampil kosong
alih-alih error, sehingga deploy tidak gagal. Pin satu media tetap bisa dibuat;
pin carousel (lebih dari satu item) menampilkan pesan "Skema database belum
diperbarui" sampai SQL-nya dijalankan.
Pembaruan terbaru menambah kolom site_settings.hero_show_title. Saat
schema.sql dijalankan ulang, judul hero lama yang hanya berisi simbol
(cara lama menyembunyikan judul, mis. ".") otomatis diubah menjadi sakelar
mati + judul kosong. Sebelum SQL dijalankan pun, beranda sudah memperlakukan
judul simbol sebagai tersembunyi; menyimpan tab Beranda memerlukan kolom baru.
Catatan pembaruan keamanan terbaru:
- Kiriman pending dari versi lama tetap berada di bucket publik sampai ditinjau; menyetujui atau menolaknya memindahkan objek ke lokasi yang benar.
- Mengganti password (atau menonaktifkan akun) langsung mengeluarkan sesi admin tersebut di semua perangkat.
- Foto publik/storage: JPEG, PNG/APNG, GIF, WebP, dan AVIF.
- Sumber HEIC/HEIF iPhone dinormalisasi menjadi JPEG di Web Worker sebelum masuk editor atau Storage. BMP, TIFF, dan JPEG XL dinormalisasi bila browser perangkat dapat mendecodenya.
- Video: MP4/M4V, WebM, MOV/QuickTime, dan 3GP. MOV/HEVC tetap bergantung pada codec perangkat; untuk jangkauan penuh gunakan MP4 H.264 + AAC atau pasang adapter transcoding terpisah.
- Audio: MP3, M4A/AAC, Ogg/Opus/Vorbis, WebM Audio, FLAC, dan WAV.
- M4P rights-managed ditolak dengan pesan khusus karena FairPlay DRM tidak
boleh dilewati oleh converter website.
.imgditolak karena merupakan disk image, bukan format foto web. - GIF/APNG serta WebP/AVIF animasi tidak dibuka sebagai foto statis agar frame lain tidak hilang tanpa persetujuan.
- Satu sumber:
UPLOAD_LIMITSdisrc/lib/constants.ts, yaitu foto 100 MB serta video dan audio 500 MB per file. file_size_limitbucket disupabase/schema.sqlbernilai sama denganUPLOAD_LIMITS.serverActions.bodySizeLimitdinext.config.mjsadalah batas foto ditambah 10 MB. Server Action hanya membawa foto (pengaturan, anggota, cover artikel). Video dan audio dikirim langsung ke Storage lewat signed URL/TUS dan tidak pernah melewati server aplikasi.src/proxy.tsmengecualikan request Server Action (headernext-action). Next memotong body yang melewati proxy diproxyClientMaxBodySize(bawaan 10 MB). Setiap action admin tetap memeriksa sesi sendiri lewatrequireFeature/requireAdmin.npm run check:media-formatsgagal bila nilai-nilai di atas tidak lagi selaras.
Batas platform tetap berlaku di atas angka aplikasi:
- Supabase Free membatasi setiap file maksimal 50 MB (batas global
project). File yang lebih besar ditolak Storage meskipun limit bucket lebih
tinggi. Naikkan Global file size limit di Storage Settings dashboard
(paket Pro ke atas, hingga 500 GB), atau turunkan
UPLOAD_LIMITS. Sumber: Supabase Storage file limits. - Vercel Functions menerima body request maksimal 4,5 MB. Foto yang dikirim lewat Server Action biasanya jauh lebih kecil karena editor mengekspornya ke WebP ≤2048 px. Namun GIF/WebP animasi besar akan ditolak dengan 413 pada deploy Vercel.
Klien unggah menampilkan pesan yang jelas bila Storage menolak file karena ukurannya.
Jalankan npm run check:media-formats setelah mengubah registry, batas ukuran,
atau SQL. Daftar lisensi decoder pihak ketiga tersedia di
THIRD_PARTY_NOTICES.md.
Menu Pengaturan memusatkan konfigurasi berikut:
- nama website, nama alternatif (khusus mesin pencari), tipe website, dan locale;
- istilah tunggal/jamak anggota, nomor identitas, dan kelompok inti;
- deskripsi, tagline, logo, favicon, warna, visi, dan misi;
- hero beranda di tab Beranda: label, judul (dengan sakelar "Tampilkan judul di atas foto"), subjudul, dan foto, dengan pratinjau langsung. Judul kosong memakai nama website; saat judul disembunyikan, h1 halaman tetap memakai nama website untuk mesin pencari. Nama alternatif tidak pernah tampil di hero;
- warna utama dan aksen dengan palet cepat serta pratinjau tema; warna aksen mewarnai gradien hero, cincin anggota inti, dan cahaya latar;
- email, telepon, alamat, tautan sosial, dan teks footer (tahun © otomatis);
- canonical URL, kontrol indexing, dan gambar sosial;
- kode verifikasi Google/Bing (boleh menempel seluruh tag
<meta>), Google Analytics, dan Google AdSense.
Judul dan deskripsi hasil pencarian halaman depan disusun dari nama website, tagline, dan deskripsi (lihat pratinjau Google di tab Identitas). Mesin pencari menilai kualitas serta relevansi konten, aksesibilitas, performa, reputasi, dan banyak sinyal lain; aplikasi tidak menjanjikan peringkat nomor satu atau persetujuan AdSense.
Setelah site_url diisi dan aplikasi di-deploy, endpoint berikut dibuat
otomatis:
/sitemap.xml/robots.txt/manifest.webmanifest/ads.txt
Kirim URL absolut /sitemap.xml ke Google Search Console. Sitemap dan feed
disegarkan otomatis setiap artikel terbit, pin disetujui, atau anggota/album
berubah, dan setiap sumber konten dibaca terpisah sehingga satu tabel
bermasalah tidak membuat sitemap gagal. Bila Search Console menampilkan
"Tidak dapat mengambil peta situs" padahal /sitemap.xml terbuka normal,
hapus entri lama lalu kirim ulang, uji dengan Inspeksi URL, dan minta
pengindeksan untuk halaman penting (langkahnya juga ada di Pengaturan → SEO).
Perubahan nama situs, judul, atau deskripsi baru terlihat setelah mesin
pencari melakukan crawl ulang.
- Anon key hanya membaca data publik yang diizinkan RLS.
- Tabel admin, visitor, pesan privat, dan daftar blokir tidak memiliki policy tulis untuk browser.
- Mutasi publik divalidasi di server dan dapat dibatasi berdasarkan IP.
- Rate limit disimpan atomik di Postgres sehingga tetap konsisten antar-instance serverless; aplikasi fail-closed bila migrasi keamanannya belum tersedia.
- Audio/video diunggah langsung ke signed URL satu-path yang berumur pendek.
- Signature awal file diverifikasi kembali dari byte Storage; MIME dan ekstensi tidak diperlakukan sebagai bukti isi file.
- Sesi admin memakai cookie
HttpOnly,Secure, danSameSite=Lax. - Password disimpan sebagai hash scrypt, bukan plaintext.
- Minimisasi data: IP dan informasi perangkat pengunjung, komentar, pesan, dan
media dihapus/dianonimkan otomatis setelah 180 hari (
DATA_RETENTION_DAYS), sesuai yang dijelaskan di halaman Privasi.
Sebelum membuka repository ke publik, pastikan Git history juga tidak pernah berisi secret produksi. Menghapus secret dari commit terbaru tidak menghapusnya dari commit lama; rotasi secret yang pernah terekspos.
src/
├─ app/ # halaman, metadata, route handler
│ └─ profil/ # publik, auth, dan area admin
├─ components/
│ ├─ admin/ # UI pengelolaan
│ ├─ motion/ # motion primitives
│ ├─ public/ # halaman publik, Island, tab bar, musik
│ ├─ seo/ # renderer structured data
│ └─ ui/ # primitive reusable
└─ lib/
├─ auth/ # session, password, permission
├─ database/ # hasil mutasi dan error mapping
├─ site-config/ # pusat konfigurasi website
├─ supabase/ # client publik/browser/admin
└─ types/ # kontrak database
supabase/
└─ schema.sql # instalasi baru & upgrade (idempoten)
npm run lint
npm run typecheck
npm run check:member-slugs
npm run check:media-formats # registry ↔ SQL, termasuk batas ukuran unggah
npm run check:share-target
npm run check:schema # schema.sql 2× di Postgres (PGlite) + uji RPC/RLS
npm run audit:prod
npm run buildWorkflow GitHub Actions untuk semua pemeriksaan ini tersedia di
docs/ci-workflow.yml; salin ke .github/workflows/ci.yml agar berjalan pada
setiap pull request.
- Cari dan pakai ulang kode yang sudah ada sebelum membuat fungsi atau file baru; logika yang sama ditempatkan di satu pusat lalu dipakai banyak fitur.
- Letakkan kode sesuai kategorinya (
lib/untuk logika,components/untuk UI,supabase/schema.sqluntuk database) dan ikuti gaya file sekitarnya. - Pakai token desain (mis.
text-footnote,rounded-pin,text-tone-blue-text) alih-alih nilai px/hex arbitrer. Token kustom baru juga didaftarkan disrc/lib/utils/cn.tsagar tailwind-merge tidak membuangnya. - Jalankan seluruh pemeriksaan kualitas di atas sebelum membuka pull request.
Dirilis dengan MIT License.