diff --git a/PROGRESS.md b/PROGRESS.md deleted file mode 100644 index 682a116..0000000 --- a/PROGRESS.md +++ /dev/null @@ -1,56 +0,0 @@ -# Progress - -Catatan sementara untuk melanjutkan pekerjaan di komputer lain. Hapus file ini kalau halaman -login sudah selesai. - -Terakhir diperbarui: 2026-10-20. - -## Sudah selesai - -- Drizzle ORM + `pg` ke Postgres (`db/`, `drizzle.config.ts`, script `db:*`). -- Better Auth 1.7: login pakai username, tabel auth sudah di-migrate, sign-up publik ditutup. - Endpoint di `app/api/auth/[...all]/route.ts`. -- shadcn/ui preset `base-nova`, komponen: button, input, label, card, field, separator. -- `old-source/` di-commit tanpa `db_login.inc.php` (kredensial). -- Halaman login, langkah 1 sampai 3 dari 7: - 1. `zod` terpasang. - 2. `data/auth.ts`: `getCurrentUser()` cek session, redirect ke `/login` kalau belum login. - 3. `app/(auth)/login/actions.ts`: server action `login` (validasi zod, lalu - `auth.api.signInUsername`, lalu `redirect("/")`). - -## Berikutnya: halaman login, langkah 4 sampai 7 - -4. **Form login (client component)** - `app/(auth)/login/_components/login-form.tsx`, diawali `"use client"`. - Pakai `useActionState(login, {})`. Isinya `Card` berisi `FieldGroup` dengan dua `Field` - (username dan password, `name="username"` / `name="password"`), `FieldError` untuk - `state.error`, dan `Button type="submit"` yang `disabled` saat `pending`. - Input username diberi `defaultValue={state.username}` supaya tidak kosong setelah gagal login. - -5. **Halaman login** - `app/(auth)/login/page.tsx`: Server Component yang menampilkan `` di tengah layar. - -6. **Pindahkan halaman utama ke grup dashboard** - Hapus `app/page.tsx` (bawaan create-next-app), lalu buat `app/(dashboard)/page.tsx`. - Komponen yang memanggil `getCurrentUser()` harus dibungkus `` karena - `cacheComponents` aktif (tanpa itu build gagal). Tampilkan nama user yang login. - -7. **Logout** - `app/(dashboard)/actions.ts` dengan `"use server"`: action `logout` memanggil - `auth.api.signOut({ headers: await headers() })` lalu `redirect("/login")`. - Pasang lewat `
` di - halaman dashboard. - -Cek akhir: buka `/` saat belum login, harus diarahkan ke `/login`. Login sebagai `admin`, -nama tampil di `/`. Klik Keluar, kembali ke `/login`. Lalu `pnpm exec tsc --noEmit` dan `pnpm lint`. - -Setelah itu: commit, lalu rencana berikutnya (belum dimulai): - -- User read-only (hanya `SELECT`) di MariaDB untuk `AMIGO_DATABASE_URL`, menggantikan `root`. -- Koneksi Drizzle ke MariaDB di `db/amigo/` dengan `drizzle-kit pull` (jangan pernah migrate/push). -- `proxy.ts` untuk redirect cepat ke `/login` kalau tidak ada cookie session. - -## Menyiapkan komputer lain - -Ikuti bagian "Setup di laptop baru" di `README.md` (`docker compose up -d`, `pnpm db:migrate`, -`pnpm db:seed`). diff --git a/app/(auth)/login/_components/login-form.tsx b/app/(auth)/login/_components/login-form.tsx new file mode 100644 index 0000000..2176b81 --- /dev/null +++ b/app/(auth)/login/_components/login-form.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { useActionState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { + Field, + FieldError, + FieldGroup, + FieldLabel, +} from "@/components/ui/field"; +import { Input } from "@/components/ui/input"; +import { login, type LoginState } from "../actions"; + +const initialState: LoginState = {}; + +export function LoginForm() { + const [state, formAction, pending] = useActionState(login, initialState); + + return ( + + + Masuk + Cleo Minimarket back-office + + +
+ + + Username + + + + Password + + + {state.error && {state.error}} + + +
+
+
+ ); +} diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx new file mode 100644 index 0000000..800582f --- /dev/null +++ b/app/(auth)/login/page.tsx @@ -0,0 +1,16 @@ +import type { Metadata } from "next"; +import { LoginForm } from "./_components/login-form"; + +export const metadata: Metadata = { + title: "Masuk", +}; + +export default function LoginPage() { + return ( +
+
+ +
+
+ ); +} diff --git a/app/(dashboard)/actions.ts b/app/(dashboard)/actions.ts new file mode 100644 index 0000000..c61d876 --- /dev/null +++ b/app/(dashboard)/actions.ts @@ -0,0 +1,10 @@ +"use server"; + +import { headers } from "next/headers"; +import { redirect } from "next/navigation"; +import { auth } from "@/lib/auth"; + +export async function logout() { + await auth.api.signOut({ headers: await headers() }); + redirect("/login"); +} diff --git a/app/(dashboard)/page.tsx b/app/(dashboard)/page.tsx new file mode 100644 index 0000000..f3a383c --- /dev/null +++ b/app/(dashboard)/page.tsx @@ -0,0 +1,25 @@ +import { Suspense } from "react"; +import { getCurrentUser } from "@/data/auth"; +import { logout } from "./actions"; +import { Button } from "@/components/ui/button"; + +export default function DashboardPage() { + return ( +
+ Loading...

}> + +
+
+ +
+
+ ); +} + +async function Greeting() { + const user = await getCurrentUser(); + + return
Halo, {user.name}!
; +} diff --git a/app/page.tsx b/app/page.tsx deleted file mode 100644 index c887311..0000000 --- a/app/page.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import Image from "next/image"; - -export default function Home() { - return ( -
-
- Next.js logo -
-

- To get started, edit the{" "} - - page.tsx - {" "} - file. -

-

- Looking for a starting point or more instructions? Head over to{" "} - - Templates - {" "} - or the{" "} - - Learning - {" "} - center. -

-
-
- - Vercel logomark - Deploy Now - - - Documentation - -
-
-
- ); -}