Skip to content

Repository files navigation

πŸ“‹ Formatic

Otomatisasi buat Formulir rapi kirm langsung ke pesan WhatsApp

Next.js TypeScript Supabase Tailwind CSS License: MIT PRs Welcome


Platform otomatisasi pemesanan dan komunikasi bagi UMKM di Indonesia β€” mentransformasi interaksi WhatsApp yang tidak terstruktur menjadi data yang sistematis, profesional, dan siap ditindaklanjuti.

Proposisi Nilai:

  • Efisiensi Operasional β€” Memangkas waktu respons dan pencatatan pesanan secara manual.
  • Akurasi Data β€” Eliminasi risiko kesalahan perhitungan melalui Mesin Struk Otomatis.
  • Profesionalisme β€” Format pesan WhatsApp yang rapi dan terstruktur di hadapan pelanggan.

✨ Fitur

  • Form Builder β€” antarmuka drag-and-drop untuk menyusun dan mengurutkan field (Teks, Angka, Pilihan, Tanggal, Area Teks), lengkap dengan konfigurasi label, placeholder, dan status wajib isi.
  • WhatsApp Template Editor β€” editor templat dengan placeholder dinamis ({{Nama}}, {{Total}}), dukungan sintaks Markdown WhatsApp (tebal, miring), dan preview real-time dalam bentuk chat bubble.
  • Mesin Struk Otomatis (Auto-Receipt Engine) β€” kalkulasi otomatis untuk field bertipe angka/perkalian, rendering data respons ke format teks Markdown yang rapi.
  • Formulir Publik & Distribusi β€” halaman formulir publik tanpa autentikasi, langsung terintegrasi ke WhatsApp melalui tautan wa.me dan fungsi Copy to Clipboard.
  • Dashboard Dasar β€” daftar formulir yang telah dibuat dan tabel respons masuk untuk pemantauan.
  • Rate Limiting β€” maksimal 20 pengiriman per IP per jam untuk mencegah spam.
  • Autentikasi β€” register & login dengan email + password via Supabase Auth.
  • Mobile-First β€” formulir publik dioptimalkan untuk layar seluler, karena mayoritas responden UMKM mengakses via ponsel.
  • Watermark β€” footer "Dibuat dengan Formatic" pada formulir publik sebagai strategi akuisisi organik.

πŸ›  Tech Stack

Layer Technology
Framework Next.js 16 (App Router, React 19)
Language TypeScript
Styling Tailwind CSS 4 + shadcn/ui
Auth & Database Supabase (PostgreSQL, Auth, RLS)
Reorder Tombol β–²/β–Ό (tanpa library)
Validation Zod 4
Icons Lucide React
Hosting Vercel (frontend) + Supabase (backend)

πŸš€ Quick Start

Prerequisites

1. Clone & Install

git clone https://github.com/your-username/formatic.git
cd formatic
npm install

2. Setup Environment

cp .env.example .env.local

Edit .env.local dengan credentials dari Supabase Dashboard:

NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-publishable-key

3. Setup Database

Jalankan SQL berikut di Supabase SQL Editor untuk membuat tabel dan RLS policies:

Click to expand full SQL schema
-- ENUM untuk tipe field
CREATE TYPE field_type AS ENUM ('text', 'number', 'select', 'date', 'textarea');

-- Tabel forms
CREATE TABLE forms (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id       UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  title         TEXT NOT NULL,
  slug          TEXT NOT NULL UNIQUE,
  description   TEXT DEFAULT '',
  wa_phone      TEXT DEFAULT '',
  is_active     BOOLEAN DEFAULT TRUE,
  created_at    TIMESTAMPTZ DEFAULT now(),
  updated_at    TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_forms_user_id ON forms(user_id);
CREATE INDEX idx_forms_slug ON forms(slug);

-- Tabel form_fields
CREATE TABLE form_fields (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  form_id       UUID NOT NULL REFERENCES forms(id) ON DELETE CASCADE,
  label         TEXT NOT NULL,
  field_type    field_type NOT NULL DEFAULT 'text',
  options       JSONB DEFAULT NULL,
  is_required   BOOLEAN DEFAULT FALSE,
  placeholder   TEXT DEFAULT '',
  sort_order    INT NOT NULL DEFAULT 0
);

CREATE INDEX idx_form_fields_form_id ON form_fields(form_id);

-- Tabel wa_templates
CREATE TABLE wa_templates (
  id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  form_id       UUID NOT NULL REFERENCES forms(id) ON DELETE CASCADE UNIQUE,
  body          TEXT NOT NULL DEFAULT ''
);

-- Tabel responses
CREATE TABLE responses (
  id                UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  form_id           UUID NOT NULL REFERENCES forms(id) ON DELETE CASCADE,
  data              JSONB NOT NULL DEFAULT '{}',
  rendered_message  TEXT NOT NULL DEFAULT '',
  ip_address        INET,
  submitted_at      TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_responses_form_id ON responses(form_id);
CREATE INDEX idx_responses_submitted_at ON responses(submitted_at DESC);

-- Auto-update updated_at
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
  NEW.updated_at = now();
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER forms_updated_at
  BEFORE UPDATE ON forms
  FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- Enable RLS
ALTER TABLE forms ENABLE ROW LEVEL SECURITY;
ALTER TABLE form_fields ENABLE ROW LEVEL SECURITY;
ALTER TABLE wa_templates ENABLE ROW LEVEL SECURITY;
ALTER TABLE responses ENABLE ROW LEVEL SECURITY;

-- RLS: Owner CRUD
CREATE POLICY "Users can manage own forms"
  ON forms FOR ALL
  USING (user_id = auth.uid())
  WITH CHECK (user_id = auth.uid());

CREATE POLICY "Users can manage own form fields"
  ON form_fields FOR ALL
  USING (form_id IN (SELECT id FROM forms WHERE user_id = auth.uid()))
  WITH CHECK (form_id IN (SELECT id FROM forms WHERE user_id = auth.uid()));

CREATE POLICY "Users can manage own templates"
  ON wa_templates FOR ALL
  USING (form_id IN (SELECT id FROM forms WHERE user_id = auth.uid()))
  WITH CHECK (form_id IN (SELECT id FROM forms WHERE user_id = auth.uid()));

-- RLS: Public access (form publik)
CREATE POLICY "Public can read active forms"
  ON forms FOR SELECT
  USING (is_active = TRUE);

CREATE POLICY "Public can read fields of active forms"
  ON form_fields FOR SELECT
  USING (form_id IN (SELECT id FROM forms WHERE is_active = TRUE));

CREATE POLICY "Public can read templates of active forms"
  ON wa_templates FOR SELECT
  USING (form_id IN (SELECT id FROM forms WHERE is_active = TRUE));

-- RLS: Responses
CREATE POLICY "Anyone can submit responses"
  ON responses FOR INSERT
  WITH CHECK (TRUE);

CREATE POLICY "Form owners can read responses"
  ON responses FOR SELECT
  USING (form_id IN (SELECT id FROM forms WHERE user_id = auth.uid()));

4. Enable Auth

Di Supabase Dashboard, aktifkan Email provider di Authentication > Providers.

5. Run

npm run dev

Buka http://localhost:3000 πŸŽ‰

πŸ“ Project Structure

β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ (auth)/                 # Login & register (route group)
β”‚   β”œβ”€β”€ dashboard/              # Authenticated: form builder, responses
β”‚   β”‚   └── forms/
β”‚   β”‚       β”œβ”€β”€ new/            # Buat form baru
β”‚   β”‚       └── [id]/           # Edit form + WA template
β”‚   β”‚           └── responses/  # Lihat response
β”‚   β”œβ”€β”€ f/[slug]/               # Public form (responden)
β”‚   β”‚   └── success/            # Submit success + WA actions
β”‚   β”œβ”€β”€ actions.ts              # Server actions (CRUD)
β”‚   └── page.tsx                # Landing page
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ui/                     # shadcn/ui primitives
β”‚   β”œβ”€β”€ form-builder/           # Drag-drop form editor
β”‚   β”œβ”€β”€ wa-template/            # WA template editor + preview
β”‚   β”œβ”€β”€ form-public/            # Public form renderer + WA buttons
β”‚   └── dashboard/              # Dashboard cards & tables
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ supabase/               # Client, server, public helpers
β”‚   β”œβ”€β”€ wa-template.ts          # Render {{placeholder}} template
β”‚   β”œβ”€β”€ receipt-engine.ts       # Auto-receipt calculation & rendering
β”‚   β”œβ”€β”€ rate-limit.ts           # Per-IP rate limiter
β”‚   └── utils.ts                # cn(), generateSlug()
β”œβ”€β”€ types/                      # Shared TypeScript types
└── proxy.ts                    # Supabase session refresh (middleware)

πŸ“– Cara Kerja

flowchart LR
  A[Buat Formulir\n+ Template WA] --> B[Bagikan Link\n/f/[slug]]
  B --> C[Responden Isi\nForm Publik]
  C --> D[Mesin Struk\nOtomatis]
  D --> E[Pesan WhatsApp\n+ Kirim via WA\n+ Salin Pesan]
Loading

⚠️ Kendala & Batasan (MVP)

  • Integrasi WhatsApp β€” tidak menggunakan WhatsApp Business API resmi. Hanya mengandalkan deep link (wa.me) dan clipboard API.
  • Free Tier Supabase β€” basis data dibatasi 500 MB, bandwidth 5 GB/bulan. Diimplementasikan kebijakan retensi: respons otomatis dihapus setelah > 90 hari.
  • Performa β€” waktu muat halaman formulir publik harus di bawah 2 detik (LCP < 2s).
  • Keamanan β€” Row Level Security (RLS) diterapkan secara ketat di Supabase untuk isolasi data antar-pengguna.
  • Fitur yang dikecualikan dari MVP β€” monetisasi, notifikasi real-time, logika kondisional, integrasi API pihak ketiga.

🎯 Target Bisnis (MVP)

  • Validasi Product-Market Fit dalam waktu 30 hari pasca-peluncuran.
  • Minimal 20 pengguna awal (beta testers) dari segmen UMKM lokal.
  • Biaya operasional di bawah Rp 50.000/bulan dengan memanfaatkan free tier infrastruktur.

🀝 Contributing

Kontribusi sangat welcome! Cara berkontribusi:

  1. Fork repo ini
  2. Buat branch baru (git checkout -b feature/amazing-feature)
  3. Commit perubahan (git commit -m 'Add amazing feature')
  4. Push ke branch (git push origin feature/amazing-feature)
  5. Buka Pull Request

Dibuat oleh milikbersama.com

About

Create Form Automatically convert to WhatsApp

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages