In the last lesson, we created a MenuController
, which will just show the table of categories/products. To actually manage those, we will create separate CategoryController
and ProductController
with full CRUDs.
php artisan make:controller Vendor/CategoryControllerphp artisan make:controller Vendor/ProductController
And add those as resources into the routes:
routes/vendor.php:
use App\Http\Controllers\Vendor\CategoryController;use App\Http\Controllers\Vendor\ProductController; Route::group([ 'prefix' => 'vendor', 'as' => 'vendor.', 'middleware' => ['auth'],], function () { Route::get('menu', [MenuController::class, 'index'])->name('menu'); Route::resource('categories', CategoryController::class); Route::resource('products', ProductController::class); });
Now, let's fill in those Controllers with actual logic.
Let's start with the Categories and the Create form.
app/Http/Controllers/Vendor/CategoryController.php:
use Inertia\Inertia;use Inertia\Response; class CategoryController extends Controller{ public function create(): Response { $this->authorize('category.create'); return Inertia::render('Vendor/Categories/Create'); }}
Here's how the Create form will look like in the Vue:
resources/js/Vendor/Categories/Create.vue:
<script setup>import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'import { Head, useForm } from '@inertiajs/vue3'import InputError from '@/Components/InputError.vue'import InputLabel from '@/Components/InputLabel.vue'import PrimaryButton from '@/Components/PrimaryButton.vue'import TextInput from '@/Components/TextInput.vue'const form = useForm({ name: ''})const submit = () => { form.post(route('vendor.categories.store'))}</script> <template> <Head title="Add New Product Category" /> <AuthenticatedLayout> <template #header> <h2 class="font-semibold text-xl text-gray-800 leading-tight">Add New Product Category</h2> </template> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900 overflow-x-scroll"> <div class="p-6 text-gray-900 overflow-x-scroll"> <form @submit.prevent="submit" class="flex flex-col gap-4"> <div class="form-group"> <InputLabel for="name" value="Name" /> <TextInput id="name" type="text" v-model="form.name" :disabled="form.processing" /> <InputError :message="form.errors.name" /> </div> <div> <PrimaryButton :processing="form.processing"> Create New Product Category </PrimaryButton> </div> </form> </div> </div> </div> </div> </div> </AuthenticatedLayout></template>
Really similar to the previous create forms in this course, reusing the same Vue components with nothing to add.
Visual result:
Finally, let's add the button to add a new category above the Menu table.
resources/js/Pages/Vendor/Menu.vue:
import { Head, Link } from '@inertiajs/vue3' // ... <div class="p-6" v-if="can('category.create')"> <Link class="btn btn-primary" :href="route('vendor.categories.create')"> Add New Product Category </Link> </div><div class="p-6 text-gray-900 overflow-x-scroll flex flex-col gap-8"> <div v-for="category in categories" :key="category.id" class="flex flex-col gap-4">
Visual result:
To store the result, we generate the Form Request class first:
php artisan make:request Vendor/StoreCategoryRequest
This is the content:
app/Http/Requests/Vendor/StoreCategoryRequest.php:
namespace App\Http\Requests\Vendor; use Illuminate\Foundation\Http\FormRequest;use Illuminate\Support\Facades\Gate; class StoreCategoryRequest extends FormRequest{ public function authorize(): bool { return Gate::allows('category.create'); } public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], ]; }}
And now, we can use that Form Request class in the Controller, save a new Category, and redirect back to the Menu:
app/Http/Controllers/Vendor/CategoryController.php:
use App\Http\Requests\Vendor\StoreCategoryRequest;use Illuminate\Http\RedirectResponse; class CategoryController extends Controller{ // ... public function store(StoreCategoryRequest $request): RedirectResponse { $request->user()->restaurant->categories()->create($request->only('name')); return to_route('vendor.menu') ->withStatus('Product Category created successfully.'); }}
One more thing: see that ->categories()
relationship? We actually forgot to create this hasMany
function in the Restaurant model.
app/Models/Restaurant.php:
class Restaurant extends Model{ // ... public function categories() { return $this->hasMany(Category::class); }}
Also, see that withStatus()
? We're reusing the same Status variable from the session as we did in the earlier lessons of the course.
And now we're good to go. We can save new categories!
Let's add a button to Edit categories into our list of categories in the Menu component.
resources/js/Pages/Vendor/Menu.vue:
<div class="flex justify-between"><div class=""> <div class="text-2xl font-bold">{{ category.name }}</div></div><div class="flex gap-4 items-center"> // [tl! ++:1,6] <Link :href="route('vendor.categories.edit', category)" class="btn btn-secondary btn-sm" > Edit </Link></div></div>
Then we fill in the edit()
method of the Controller:
app/Http/Controllers/Vendor/CategoryController.php:
use App\Models\Category; class CategoryController extends Controller{ // ... public function edit(Category $category): Response { return Inertia::render('Vendor/Categories/Edit', [ 'category' => $category, ]); }}
Finally, the Edit component. It will be very similar to other Edit forms in the previous lessons.
resources/js/Pages/Vendor/Categories/Edit.vue:
<script setup>import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'import { Head, useForm } from '@inertiajs/vue3'import InputError from '@/Components/InputError.vue'import InputLabel from '@/Components/InputLabel.vue'import PrimaryButton from '@/Components/PrimaryButton.vue'import TextInput from '@/Components/TextInput.vue'const props = defineProps({ category: { type: Object }})const form = useForm({ name: props.category.name})const submit = () => { form.patch(route('vendor.categories.update', props.category))}</script> <template> <Head :title="'Edit ' + category.name" /> <AuthenticatedLayout> <template #header> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ 'Edit ' + category.name }} </h2> </template> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 text-gray-900 overflow-x-scroll"> <div class="p-6 text-gray-900 overflow-x-scroll"> <form @submit.prevent="submit" class="flex flex-col gap-4"> <div class="form-group"> <InputLabel for="name" value="Name" /> <TextInput id="name" type="text" v-model="form.name" :disabled="form.processing" /> <InputError :message="form.errors.name" /> </div> <div> <PrimaryButton :processing="form.processing"> Update Product Category </PrimaryButton> </div> </form> </div> </div> </div> </div> </div> </AuthenticatedLayout></template>
We click the Edit button, and here's the visual result:
Now, to update the Category, we generate another validation Form Request class.
php artisan make:request Vendor/UpdateCategoryRequest
This is the content:
namespace App\Http\Requests\Vendor; use Illuminate\Foundation\Http\FormRequest;use Illuminate\Support\Facades\Gate; class UpdateCategoryRequest extends FormRequest{ public function authorize(): bool { return Gate::allows('category.update'); } public function rules(): array { return [ 'name' => ['required', 'string', 'max:255'], ]; }}
And now, we can use that class to update the Category and redirect back to the list:
app/Http/Controllers/Vendor/CategoryController.php:
use App\Http\Requests\Vendor\UpdateCategoryRequest; class CategoryController extends Controller{ // ... public function update(UpdateCategoryRequest $request, Category $category): RedirectResponse { $category->update($request->only('name')); return to_route('vendor.menu')->withStatus('Product Category updated successfully.'); }}
The final step for Categories CRUD is the Delete button. Let's add it:
resources/js/Pages/Vendor/Menu.vue:
<div class="flex gap-4 items-center"> <Link :href="route('vendor.categories.edit', category)" class="btn btn-secondary btn-sm" > Edit </Link> // [tl! ++:1,6] <Link :href="route('vendor.categories.destroy', category)" class="btn btn-danger btn-sm" method="delete" as="button" > Delete </Link></div>
We also need to introduce the btn-danger
class to the CSS:
resources/css/app.css:
@layer components { // ... .btn-danger { @apply bg-danger-500 text-white hover:bg-danger-600 focus:bg-danger-600 active:bg-danger-500 focus:ring-danger-500 }}
Finally, we fill in the code for the Controller's destroy()
method.
app/Http/Controllers/Vendor/CategoryController.php:
class CategoryController extends Controller{ // ... public function destroy(Category $category) { $category->delete(); return to_route('vendor.menu') ->withStatus('Product Category deleted successfully.'); }}
And that's it. Now we have a complete CRUD for categories! In the next lesson, we will repeat the same for products.