# Modul Auth

File terkait:
- Pages: [Login.jsx](../../src/pages/auth/Login.jsx), [Register.jsx](../../src/pages/auth/Register.jsx), [ForgotPassword.jsx](../../src/pages/auth/ForgotPassword.jsx), [ResetPassword.jsx](../../src/pages/auth/ResetPassword.jsx), [ChangePassword.jsx](../../src/pages/auth/ChangePassword.jsx)
- Service: [authService.js](../../src/services/authService.js)
- Store: [authStore.js](../../src/store/authStore.js) (Zustand, persist ke localStorage key `autopos_auth`)

---

## 1. Login

**Form:** `react-hook-form` + `zod`. Field: `username` (wajib, bisa email/username), `password` (wajib). Validasi cuma "tidak boleh kosong" — tidak ada cek format di FE.

**Flow:**
1. User isi username + password → submit.
2. Zod validasi client-side dulu (kalau kosong, langsung tampil error di bawah field, **tidak** sampai ke server).
3. `authService.login({ username, password })`:
   - **Dummy mode:** terima kalau `username==='demo' && password==='password'`, selain itu throw error "Email/username atau password salah".
   - **Mode real:**
     - `POST /auth/login` body `{ username, password }`
     - Response sukses: `data.data = { token: { access_token }, user: {...tenant/branches mungkin kosong} }`
     - **Langkah tambahan:** FE langsung memanggil `GET /auth/me` dengan token yang baru didapat (karena endpoint login **tidak** mengembalikan roles/branches lengkap)
     - Hasil akhir yang dikembalikan ke halaman: `{ token, user: { id, name, username, email, role: roles[0].name, avatar, must_change_password }, tenant, branches }`
4. Jika "Ingat saya" dicentang → username & password disimpan **plaintext** di localStorage (`autopos_remembered_username`, `autopos_remembered_password`) — sengaja, bukan bug, tapi catatan keamanan disengaja didokumentasikan di kode.
5. `authStore.setAuth(data)` → simpan `token` ke localStorage (`autopos_token`), set `user`, `tenant`, `branches`, dan `branch` default = `branches[0]`.
6. Toast sukses "Selamat datang, {nama}!"
7. **Redirect kondisional:**
   - Jika `user.must_change_password === true` → `/change-password` (akun baru dibuat admin, wajib ganti password dulu)
   - Selain itu → `/dashboard`

**Error handling:** Semua error (termasuk 401 salah password — karena belum ada token, jadi tidak masuk perilaku "sesi habis" global) ditangkap di `catch` lokal, tampil sebagai toast: `err.response.data.message` atau fallback "Login gagal, coba lagi".

**Endpoint:** `POST /auth/login` → lalu `GET /auth/me`
**Expected response login:** `{ success, message, data: { token: { access_token }, user: {...} } }`
**Expected response /auth/me:** `{ success, message, data: { id, name, username, email, roles: [{name}], avatar, must_change_password, tenant, branches } }`

---

## 2. Register

**Form:** `react-hook-form` + `zod`. Field:
- `business_name` (wajib, max 255)
- `business_type` (opsional — select: restaurant/cafe/warung/bakery/other)
- `name` (wajib, max 255)
- `username` (wajib, 3-50 char, regex `^[a-zA-Z0-9_-]+$`)
- `email` (wajib, format email)
- `phone` (opsional, max 20)
- `password` (wajib, min 8)
- `password_confirmation` (wajib, harus match `password` — divalidasi via `.refine()`)

**Flow:**
1. Validasi zod client-side (semua aturan di atas dicek sebelum submit ke server).
2. `authService.register(payload)`:
   - **Dummy:** selalu sukses, return data dummy yang sama dengan login.
   - **Real:** `POST /auth/register` dengan seluruh payload form → lalu sama seperti login, panggil `GET /auth/me` untuk lengkapi role/tenant/branches.
3. `setAuth(data)` → langsung login (tidak perlu langkah verifikasi email).
4. Toast sukses "Selamat datang, {nama}! Toko berhasil didaftarkan." → `navigate('/dashboard')`.

**Error handling khusus:** Register punya penanganan 422 yang lebih detail dari Login — kalau response punya `err.response.data.errors` (object per-field dari Laravel validator), FE ambil **field pertama** dan tampilkan pesan pertamanya via toast. Kalau tidak ada `errors`, fallback ke `message` umum.

**Endpoint:** `POST /auth/register` body `{ business_name, business_type, name, username, email, phone, password, password_confirmation }` → lalu `GET /auth/me`
**Expected response:** sama format dengan login (token + data user, tanpa role/branch lengkap → perlu `/auth/me`)

---

## 3. Forgot Password

**Form:** manual `useState` (bukan react-hook-form), cuma 1 field `email`.

**Flow:**
1. Validasi manual: kalau `email` kosong → toast "Email wajib diisi", stop.
2. `authService.forgotPassword(email)` → `POST /auth/forgot-password` body `{ email }`.
3. Sukses → set state `sent = true`, ganti UI ke pesan "Email terkirim!" (tidak ada redirect, user tetap di halaman, ada link "Kirim ulang" untuk reset state `sent`).
4. Gagal → toast `err.response.data.message` atau fallback "Gagal mengirim email, coba lagi".

**Endpoint:** `POST /auth/forgot-password` body `{ email }`
**Expected response:** `{ success, message }` — tidak ada data spesifik yang dipakai FE, cuma cek sukses/gagal.

**Catatan:** tidak ada validasi format email di FE — backend yang menentukan apakah email valid/terdaftar.

---

## 4. Reset Password

**Akses:** via link email berisi query param `?token=...&email=...` (diambil dari URL, **bukan** dari form input — user tidak mengetik token/email manual).

**Form:** manual `useState`, field: `password`, `password_confirmation` (FE menyebutnya "confirmation").

**Flow & validasi manual sebelum submit (urutan cek):**
1. `password.length < 8` → toast "Kata sandi minimal 8 karakter"
2. `password !== confirmation` → toast "Konfirmasi kata sandi tidak cocok"
3. `!token || !email` (artinya link rusak/diakses tanpa query param) → toast "Link tidak valid"
4. Kalau lolos semua → `authService.resetPassword({ token, email, password, password_confirmation })` → `POST /auth/reset-password`
5. Sukses → toast "Kata sandi berhasil direset!" → `navigate('/login')`
6. Gagal → toast `err.response.data.message` atau fallback.

**Endpoint:** `POST /auth/reset-password` body `{ token, email, password, password_confirmation }`
**Expected response:** `{ success, message }`

---

## 5. Change Password

**Konteks penggunaan:** dipanggil otomatis setelah login kalau `user.must_change_password === true` (akun baru dibuat admin dengan password default), **atau** user mengakses langsung untuk ganti password sendiri.

**Form:** `react-hook-form` + `zod`. Field: `current_password` (wajib), `password` (min 8), `password_confirmation` (harus match `password`).

**Flow:**
1. Validasi zod client-side.
2. `authService.changePassword(values)` → `PATCH /auth/change-password` body `{ current_password, password, password_confirmation }`
3. Sukses → `authStore.updateUser({ must_change_password: false })` (update local state, supaya tidak diarahkan ke sini lagi), toast "Kata sandi berhasil diubah!", `navigate('/dashboard')`.
4. Gagal → toast `err.response.data.message` (mis. current_password salah) atau fallback "Gagal mengubah kata sandi".

**Endpoint:** `PATCH /auth/change-password` body `{ current_password, password, password_confirmation }`
**Expected response:** `{ success, message }` — tidak ada data dikembalikan, FE update state lokal sendiri.

---

## 6. Logout (tidak punya halaman sendiri, dipanggil dari layout/topbar)

`authService.logout()` → `POST /auth/logout` (dummy mode: no-op). Setelah itu biasanya pemanggil (komponen layout) memanggil `authStore.logout()` untuk hapus token & semua state lokal, lalu redirect ke `/login`.

---

## 7. Refresh token

`authService.refresh()` → `POST /auth/refresh`, return `data.data.token.access_token`. **Catatan:** tidak ditemukan pemanggilan otomatis (mis. di interceptor saat token expired) di kode saat ini — kemungkinan fitur ini disiapkan tapi belum diaktifkan/dipakai secara otomatis. Perlu dicek manual kalau mau dipakai untuk auto-refresh sebelum token expired.

---

## Ringkasan endpoint Auth

| Aksi | Method | Endpoint | Body | Catatan |
|---|---|---|---|---|
| Login | POST | `/auth/login` | `{ username, password }` | diikuti `GET /auth/me` |
| Register | POST | `/auth/register` | `{ business_name, business_type, name, username, email, phone, password, password_confirmation }` | diikuti `GET /auth/me` |
| Me (profil) | GET | `/auth/me` | — | header `Authorization: Bearer <token>` manual (bukan lewat interceptor karena dipanggil sebelum token tersimpan di localStorage) |
| Forgot password | POST | `/auth/forgot-password` | `{ email }` | |
| Reset password | POST | `/auth/reset-password` | `{ token, email, password, password_confirmation }` | token & email dari query param link email |
| Change password | PATCH | `/auth/change-password` | `{ current_password, password, password_confirmation }` | butuh login (token di header otomatis) |
| Logout | POST | `/auth/logout` | — | |
| Refresh token | POST | `/auth/refresh` | — | belum dipakai otomatis di kode saat ini |
