@AGENTS.md # Cleo Minimarket Aplikasi Next.js 16 (App Router) untuk menggantikan aplikasi back-office minimarket lama berbasis PHP di `old-source/`. Kode baru ditulis ulang dari nol; `old-source/` hanya referensi. ## Stack - Next.js 16.4 + React 19, TypeScript strict, Tailwind CSS 4, pnpm 11. - Drizzle ORM + `pg` untuk database aplikasi (Postgres). - Better Auth 1.7 untuk login: Drizzle adapter ke Postgres, plugin `username`, sign-up publik ditutup (`disableSignUp`). CLI-nya package `auth` (bukan `@better-auth/cli` yang berhenti di 1.4). - shadcn/ui 4 preset `base-nova` (di atas Base UI, bukan Radix). `cn()` dari package `cn` milik shadcn, pengganti `clsx` + `tailwind-merge`. ## Dua database | Env var | Database | Isi | | -------------------- | ------------------------- | ----------------------------------------- | | `DATABASE_URL` | Postgres (Docker lokal) | Milik aplikasi ini: user, session, auth. | | `AMIGO_DATABASE_URL` | MariaDB 10.4 `cleo` (LAN) | Data bisnis lama: barang, transaksi, dll. | - Postgres dikelola penuh oleh Drizzle migration (`db:generate` lalu `db:migrate`). - MariaDB `cleo` masih dipakai aplikasi PHP lama. **Jangan pernah** `generate`, `migrate`, atau `push` ke database ini. Schema Drizzle-nya hanya boleh berasal dari `drizzle-kit pull` (introspeksi), dengan config terpisah. - Server MariaDB 10.4, bukan MySQL 8: hindari fitur yang tidak didukung (mis. sebagian fungsi JSON dan CTE tertentu). Driver: `mysql2`. ## Struktur folder Tanpa folder `src/`. Kode aplikasi di root, sejajar config. ``` app/ (auth)/actions.ts Server Actions auth (login, logout), dipakai lintas route group (auth)/login/page.tsx halaman publik (belum login) (dashboard)/layout.tsx kerangka UI halaman yang wajib login (bukan tempat cek session) (dashboard)// page.tsx Server Component: ambil data lewat data/, render actions.ts Server Actions fitur ini ('use server') _components/ komponen khusus fitur ini api/auth/[...all]/route.ts handler Better Auth (satu-satunya route handler yang wajar) components/ ui/ komponen shadcn (generated, jangan diedit manual kecuali perlu) .tsx komponen yang dipakai lintas fitur data/ Data Access Layer: fungsi baca/tulis DB, selalu 'server-only' db/ index.ts, schema.ts koneksi + schema Postgres amigo/index.ts, amigo/schema.ts koneksi + schema MariaDB (hasil `drizzle-kit pull`) lib/ auth.ts config Better Auth (server) auth-client.ts client Better Auth utils.ts helper umum (dibuat shadcn) drizzle/ file migrasi Postgres (generated, ikut di-commit) proxy.ts pengganti middleware di Next 16; hanya cek ringan (redirect) ``` - Nama folder route dan file: kebab-case. Komponen React: PascalCase untuk nama fungsinya. - Folder berawalan `_` tidak menjadi route; pakai untuk komponen yang dikolokasikan. - Route group `(nama)` hanya untuk mengelompokkan layout, tidak muncul di URL. - Import pakai alias `@/` (mis. `@/db`, `@/data/barang`). - `actions.ts` dikolokasikan dengan fitur yang memakainya. Action yang dipakai lintas route group (mis. `logout` dipanggil dari `(dashboard)`) ditaruh di level route group pemiliknya, lalu di-import lewat alias (`@/app/(auth)/actions`), bukan path relatif `../(auth)/...`. ## Aturan data dan Server Actions - **Mutasi (create/update/delete, submit form) wajib lewat Server Action.** Tidak membuat route handler `app/api/*` untuk hal yang bisa dikerjakan Server Action. - **Membaca data tidak lewat Server Action.** Server Action adalah POST dan dijalankan berurutan, jadi tidak cocok untuk fetch. Data dibaca di Server Component dengan memanggil fungsi di `data/`. - Setiap file `actions.ts` diawali `'use server'`. Setiap action: 1. Cek session dan hak akses di dalam action itu sendiri. Menyembunyikan tombol di UI bukan pengaman, karena action bisa dipanggil langsung lewat POST. 2. Validasi input (`FormData`/argumen) dengan zod sebelum menyentuh DB. 3. Panggil fungsi di `data/`, bukan query langsung dari action. 4. Kembalikan objek kecil yang dibutuhkan UI (mis. `{ ok: true }` atau `{ ok: false, error: string }`), bukan baris DB mentah. 5. Segarkan cache dengan `revalidatePath`/`updateTag` sebelum `redirect`, karena `redirect` melempar exception dan kode sesudahnya tidak jalan. - Form di client memakai `useActionState` untuk menampilkan error/pending dari action. - File di `data/` diawali `import "server-only"` supaya tidak bisa ter-import ke client component. `db/` dan `lib/auth.ts` sengaja tanpa itu karena dibaca CLI `auth` di luar Next.js (package `server-only` tidak di-install; Next.js menanganinya sendiri). Hanya `data/`, `db/`, dan `lib/auth.ts` yang membaca `process.env` untuk secret. - Session dicek lewat `getCurrentUser()` di `data/auth.ts` (redirect ke `/login` kalau tidak ada), dipanggil oleh page dan action yang butuh user. Layout tidak dipakai untuk cek session: layout tidak di-render ulang saat navigasi antar halaman di dalamnya. - `cacheComponents` aktif: komponen yang membaca session (`headers()`/`cookies()`) wajib berada di dalam ``, kalau tidak build gagal. Jangan `await` session di level atas layout. - `proxy.ts` hanya untuk redirect cepat berdasarkan ada/tidaknya cookie session, bukan pengganti `getCurrentUser()`. - Query selalu lewat Drizzle query builder atau `sql` template tag (parameterized). Jangan menyusun SQL dengan string concatenation. ## Kode lama (`old-source/`) Read-only, jangan diedit dan jangan di-import. Dipakai untuk memahami fitur dan aturan bisnis. - PHP prosedural + jQuery, akses DB lewat `mysqli`/`mysql_*` langsung di tiap halaman. Banyak query disusun dengan string concatenation (rentan SQL injection); jangan tiru polanya. - Peta fitur ada di `old-source/menu.php`: Data Master (pelanggan, karyawan, group/sub group product, rak, supplier, satuan), Barang, Pengadaan, Barcode, Retur Pembelian, Transfer Barang, Revisi, Laporan Persediaan/Beli/Jual, BON, Pembayaran. - Helper bisnis di `old-source/func_lib.php`. Hal yang tidak terlihat dari namanya: - Login lama mencocokkan `karyawan.username` dan `karyawan.password` (hash MD5), lalu mengisi session `useracc`, `nik`, `job` (`id_pekerjaan`), `namatoko`. - Jabatan dibaca dari `karyawan.id_pekerjaan` berupa teks: `Pemimpin Toko`, `Asisten Pemimpin Toko`, `Petugas Administrasi`. - Harga pokok ditampilkan dalam kode huruf: `convHrgP`/`DeconvHrgP` memetakan angka 1-0 ke `B R A W N F O X E S`; profit dipetakan `convProfit` ke `D J U N G K A R E T`. - Format tanggal tampilan `dd-mm-yyyy` (`encTgl`), DB `yyyy-mm-dd` (`decTgl`). - `bukaKoneksiPusat()` merujuk konstanta `*_PUSAT` yang tidak didefinisikan di `db_login.inc.php`; fitur koneksi ke DB pusat kemungkinan sudah mati. - File `*~` dan `(copy)` adalah sisa backup editor, abaikan. ## Perintah ``` pnpm dev jalankan dev server pnpm build build production pnpm lint eslint pnpm exec tsc --noEmit type-check pnpm db:generate buat migrasi dari db/schema.ts (Postgres) pnpm db:migrate jalankan migrasi ke Postgres pnpm db:studio lihat isi Postgres di https://local.drizzle.studio pnpm db:seed buat akun admin dari SEED_ADMIN_* di .env (idempotent) docker compose up -d nyalakan Postgres lokal (docker-compose.yml) ``` Postgres lokal: service di `docker-compose.yml`, container `cleo-postgres` (image `postgres:17`, port 5432, volume `cleo-pgdata`). Setup laptop baru ada di `README.md`.