[feat RoleMiddleware, update AuthControlller, app.php, BrankasList, TrayList, Login.vue, web.php

This commit is contained in:
timotiabbauftech 2025-09-08 09:48:32 +07:00
parent e1a0711082
commit 8e59b1f1f1
7 changed files with 163 additions and 86 deletions

View File

@ -29,10 +29,14 @@ class AuthController extends Controller
// buat token Sanctum
$token = $user->createToken('auth_token')->plainTextToken;
$redirectUrl = $user->role === 'owner' ? '/brankas' : '/kasir';
return response()->json([
'message' => 'Login berhasil',
'user' => $user,
'token' => $token,
'redirect' => $redirectUrl,
'role' => $user->role
]);
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class RoleMiddleware
{
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, ...$roles): Response
{
// cek apakah user login
if (!$request->user()) {
return response()->json(['message' => 'Unauthenticated'], 401);
}
// cek role user
if (!in_array($request->user()->role, $roles)) {
return response()->json(['message' => 'Forbidden'], 403);
}
return $next($request);
}
}

View File

@ -15,6 +15,10 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->validateCsrfTokens(except: [
'api/*'
]);
$middleware->alias([
'role' => \App\Http\Middleware\RoleMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions): void {
//

View File

@ -45,10 +45,14 @@ const error = ref(null);
onMounted(async () => {
try {
const res = await axios.get("/api/item"); // ganti sesuai URL backend
const res = await axios.get("/api/item",{
headers:{
Authorization: `Bearer ${localStorage.getItem("token")}`,
}
}); // ganti sesuai URL backend
items.value = res.data; // pastikan backend return array of items
console.log(res.data);
} catch (err) {
error.value = err.message || "Gagal mengambil data";
} finally {

View File

@ -139,8 +139,14 @@ const saveMove = async () => {
if (!selectedTrayId.value || !selectedItem.value) return;
try {
await axios.put(`/api/item/${selectedItem.value.id}`, {
id_nampan: selectedTrayId.value,
id_produk: selectedItem.value.id_produk, // ikutkan id_produk karena API minta
header:{
Authorization: `Bearer ${localStorage.getItem("token")}`,
},
body:{
id_nampan: selectedTrayId.value,
id_produk: selectedItem.value.id_produk, // ikutkan id_produk karena API minta
},
});
await refreshData();

View File

@ -1,48 +1,72 @@
<template>
<div class="flex items-center justify-center min-h-screen bg-[#0c4b66]">
<div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center">
<!-- Logo + Title -->
<div class="mb-6">
<img :src="logo" alt="Logo" class="mx-auto w-34 py-5">
</div>
<div class="flex items-center justify-center min-h-screen bg-[#0c4b66]">
<div class="bg-white p-8 rounded-2xl shadow-xl w-80 text-center">
<!-- Logo + Title -->
<div class="mb-6">
<img :src="logo" alt="Logo" class="mx-auto w-34 py-5" />
</div>
<!-- Input -->
<div>
<InputField v-model="username" type="text" placeholder="Username"class="mb-4"/>
<PasswordInput v-model="password" placeholder="Password" />
</div>
<!-- Input -->
<div>
<InputField
v-model="username"
type="text"
placeholder="Username"
class="mb-4"
/>
<PasswordInput v-model="password" placeholder="Password" />
</div>
<!-- Button -->
<button
@click="handleLogin"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition"
>
Login
</button>
<!-- Button -->
<button
@click="handleLogin"
:disabled="loading"
class="w-full py-2 bg-sky-400 hover:bg-sky-500 rounded font-bold text-gray-800 transition disabled:opacity-50"
>
{{ loading ? "Loading..." : "Login" }}
</button>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import logo from '@/../images/logo.png'
import InputField from "@/components/InputField.vue";
import PasswordInput from "@/components/InputPassword.vue";
import axios from "axios";
const username = ref("");
const password = ref("");
import PasswordInput from "@/components/InputPassword.vue";
// import { ref } from "vue";
const loading = ref(false);
// const username = ref("");
// const password = ref("");
const handleLogin = async () => {
if (!username.value || !password.value) {
alert("Harap isi username dan password!");
return;
}
// const handleLogin = () => {
// if (!username.value || !password.value) {
// alert("Harap isi username dan password!");
// return;
// }
// // Contoh: panggil API login
// console.log("Login dengan:", username.value, password.value);
// };
loading.value = true;
try {
const res = await axios.post("/api/login", {
nama: username.value,
password: password.value,
});
const data = res.data;
// Simpan token & role
localStorage.setItem("token", data.token);
localStorage.setItem("role", data.role);
// Redirect sesuai role
window.location.href = data.redirect;
} catch (error) {
console.error(error);
alert("Login gagal. Periksa username atau password.");
} finally {
loading.value = false;
}
};
</script>

View File

@ -1,53 +1,37 @@
<?php
use App\Http\Controllers\AuthController;
use App\Http\Controllers\FotoSementaraController;
use App\Http\Controllers\ItemController;
use App\Http\Controllers\KategoriController;
use App\Http\Controllers\NampanController;
use App\Http\Controllers\ProdukController;
use App\Http\Controllers\SalesController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\TransaksiController;
use App\Http\Controllers\LaporanController;
use Illuminate\Support\Facades\Route;
Route::prefix('api')->group(function () {
// Backend API
Route::apiResource('nampan', NampanController::class);
Route::apiResource('produk', ProdukController::class);
Route::apiResource('item', ItemController::class);
Route::apiResource('sales', SalesController::class);
Route::apiResource('user', UserController::class);
Route::apiResource('transaksi', TransaksiController::class);
Route::apiResource('kategori', KategoriController::class);
<?php
use App\Http\Controllers\AuthController;
use App\Http\Controllers\FotoSementaraController;
use App\Http\Controllers\ItemController;
use App\Http\Controllers\KategoriController;
use App\Http\Controllers\NampanController;
use App\Http\Controllers\ProdukController;
use App\Http\Controllers\SalesController;
use App\Http\Controllers\UserController;
use App\Http\Controllers\TransaksiController;
use App\Http\Controllers\LaporanController;
use App\Models\Kategori;
use Illuminate\Support\Facades\Route;
// ============================
// Backend API
// ============================
Route::prefix('api')->group(function () {
Route::apiResource('nampan', NampanController::class);
Route::apiResource('produk', ProdukController::class);
Route::apiResource('item', ItemController::class);
Route::apiResource('sales', SalesController::class);
Route::apiResource('user', UserController::class);
Route::apiResource('transaksi', TransaksiController::class);
Route::apiResource('kategori', KategoriController::class);
// Auth
Route::post('/login', [AuthController::class, 'login'])->middleware('guest')->name('login');
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum')->name('logout');
Route::get('brankas', [ItemController::class, 'brankasItem']);
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
Route::middleware(['auth:sanctum', 'role:owner'])->group(function () {
Route::apiResource('nampan', NampanController::class)->except(['index', 'show']);
Route::apiResource('produk', ProdukController::class)->except(['index', 'show']);
Route::apiResource('item', ItemController::class)->except(['index', 'show']);
Route::apiResource('sales', SalesController::class)->except(['index', 'show']);
Route::apiResource('kategori', KategoriController::class)->except(['index', 'show']);
Route::apiResource('user', UserController::class);
// Foto Sementara
Route::post('foto/upload', [FotoSementaraController::class, 'upload']);
Route::delete('foto/hapus/{id}', [FotoSementaraController::class, 'hapus']);
Route::get('foto/{user_id}', [FotoSementaraController::class, 'getAll']);
Route::delete('foto/reset/{user_id}', [FotoSementaraController::class, 'reset']);
// Custom Endpoint
// Laporan
Route::get('laporan', [LaporanController::class, 'ringkasan']);
Route::get('detail-laporan', [LaporanController::class, 'detail']);
});
Route::get('brankas', [ItemController::class, 'brankasItem']);
Route::delete('kosongkan-nampan', [NampanController::class, 'kosongkan']);
// Foto Sementara
@ -56,11 +40,32 @@ Route::prefix('api')->group(function () {
Route::get('foto/{user_id}', [FotoSementaraController::class, 'getAll']);
Route::delete('foto/reset/{user_id}', [FotoSementaraController::class, 'reset']);
Route::post('/login', [AuthController::class, 'login'])->middleware('guest')->name('login');
Route::post('/logout', [AuthController::class, 'logout'])->middleware('auth:sanctum')->name('logout');
// Laporan
Route::get('laporan', [LaporanController::class, 'ringkasan']);
Route::get('detail-laporan', [LaporanController::class, 'detail']);
});
// Frontend SPA
Route::get('/{any}', function () {
return view('app');
})->where('any', '^(?!storage|api).*$');
Route::middleware(['auth:sanctum', 'role:owner,kasir'])->group(function () {
Route::apiResource('transaksi', TransaksiController::class);
Route::get('produk', [ProdukController::class, 'index']);
Route::get('produk/{id}', [ProdukController::class, 'show']);
Route::get('nampan', [NampanController::class, 'index']);
Route::get('nampan/{id}', [NampanController::class, 'show']);
Route::get('item', [ItemController::class, 'index']);
Route::get('item/{id}', [ItemController::class, 'show']);
Route::get('sales', [SalesController::class, 'index']);
Route::get('sales/{id}', [SalesController::class, 'show']);
Route::get('kategori', [KategoriController::class, 'index']);
Route::get('kategori/{id}', [KategoriController::class, 'show']);
Route::get('brankas', [ItemController::class, 'brankasItem']);
});
});
// ============================
// Frontend SPA (Vue / React dll.)
// ============================
Route::get('/{any}', function () {
return view('app');
})->where('any', '^(?!storage|api).*$');