From f43f92f365369bd6d0aca2afd76ea664eac7249e Mon Sep 17 00:00:00 2001 From: kusowl Date: Mon, 12 Jan 2026 17:48:07 +0530 Subject: [PATCH] feat(Create deals): broker can create deals - add deal category migration - add deals migration and model - add form to create deal - add image preview modal when uploading the image - refactor UI components to support `required` attribute - refactor input component to support description - fix some UI components does not support old values - fix some UI components does not show error messages --- app/Http/Controllers/BrokerDealController.php | 78 +++++++++++++++++++ app/Http/Requests/StoreBrokerDeal.php | 40 ++++++++++ app/Models/Deal.php | 10 +++ app/Models/DealCategory.php | 10 +++ app/Models/User.php | 6 ++ ...12_074532_create_deal_categories_table.php | 32 ++++++++ .../2026_01_12_085032_create_deals_table.php | 37 +++++++++ resources/js/image-input.js | 40 ++++++++++ .../components/dashboard/navbar.blade.php | 2 +- .../dashboard/page-heading.blade.php | 17 ++++ .../views/components/ui/image-input.blade.php | 44 +++++++++++ resources/views/components/ui/input.blade.php | 25 ++++-- .../views/components/ui/select.blade.php | 41 ++++++---- .../views/components/ui/textarea.blade.php | 20 ++++- .../dashboards/broker/deals/create.blade.php | 37 +++++++++ routes/web.php | 4 +- 16 files changed, 417 insertions(+), 26 deletions(-) create mode 100644 app/Http/Controllers/BrokerDealController.php create mode 100644 app/Http/Requests/StoreBrokerDeal.php create mode 100644 app/Models/Deal.php create mode 100644 app/Models/DealCategory.php create mode 100644 database/migrations/2026_01_12_074532_create_deal_categories_table.php create mode 100644 database/migrations/2026_01_12_085032_create_deals_table.php create mode 100644 resources/js/image-input.js create mode 100644 resources/views/components/dashboard/page-heading.blade.php create mode 100644 resources/views/components/ui/image-input.blade.php create mode 100644 resources/views/dashboards/broker/deals/create.blade.php diff --git a/app/Http/Controllers/BrokerDealController.php b/app/Http/Controllers/BrokerDealController.php new file mode 100644 index 0000000..9d7fbe4 --- /dev/null +++ b/app/Http/Controllers/BrokerDealController.php @@ -0,0 +1,78 @@ +with('categories', DealCategory::all('id', 'name')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(StoreBrokerDeal $request) + { + $data = $request->validated(); + $data['slug'] = Str::slug($data['title']); + $data['user_id'] = $request->user()->id; + + Deal::unguard(); + Deal::create($data); + Deal::reguard(); + + return to_route('broker.dashboard')->with('success', 'Deal has been created.'); + } + + /** + * Display the specified resource. + */ + public function show(string $id) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(string $id) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, string $id) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(string $id) + { + // + } +} diff --git a/app/Http/Requests/StoreBrokerDeal.php b/app/Http/Requests/StoreBrokerDeal.php new file mode 100644 index 0000000..29674b8 --- /dev/null +++ b/app/Http/Requests/StoreBrokerDeal.php @@ -0,0 +1,40 @@ +user()->isBroker(); + } + + /** + * Get the validation rules that apply to the request. + * + * @return array|string> + */ + public function rules(): array + { + return [ + 'title' => 'required|min:10|max:255', + 'description' => 'required|min:10|max:300', + 'image' => 'required|image|mimes:jpeg,png,jpg|max:10240', + 'link' => 'nullable|url', + 'deal_category_id' => 'required|exists:deal_categories,id', + ]; + } + + public function messages(): array + { + return [ + 'category_id.required' => 'The category field is required.', + 'category_id.exists' => 'The category does not exist.', + ]; + } +} diff --git a/app/Models/Deal.php b/app/Models/Deal.php new file mode 100644 index 0000000..44c827a --- /dev/null +++ b/app/Models/Deal.php @@ -0,0 +1,10 @@ + 'hashed', ]; } + + public function isBroker(): bool + { + return $this->role === UserTypes::Broker->value; + } } diff --git a/database/migrations/2026_01_12_074532_create_deal_categories_table.php b/database/migrations/2026_01_12_074532_create_deal_categories_table.php new file mode 100644 index 0000000..de15eb5 --- /dev/null +++ b/database/migrations/2026_01_12_074532_create_deal_categories_table.php @@ -0,0 +1,32 @@ +id(); + $table->string('name'); + $table->text('description')->nullable(); + $table->string('slug')->unique(); + $table->boolean('active')->default(true); + $table->integer('order')->default(0); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('deal_categories'); + } +}; diff --git a/database/migrations/2026_01_12_085032_create_deals_table.php b/database/migrations/2026_01_12_085032_create_deals_table.php new file mode 100644 index 0000000..547fd85 --- /dev/null +++ b/database/migrations/2026_01_12_085032_create_deals_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('title'); + $table->string('slug'); + $table->text('description'); + $table->string('image')->nullable(); + $table->string('link')->nullable(); + $table->boolean('active')->default(false); + $table->foreignIdFor(DealCategory::class); + $table->foreignIdFor(User::class); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('deals'); + } +}; diff --git a/resources/js/image-input.js b/resources/js/image-input.js new file mode 100644 index 0000000..39f6153 --- /dev/null +++ b/resources/js/image-input.js @@ -0,0 +1,40 @@ +function upload(size) { + const imageInput = document.getElementById("image-input"); + const closeModalBtn = document.getElementById("close-modal"); + const cancelBtn = document.getElementById("cancel-modal"); + const modal = document.getElementById("image-modal"); + + closeModalBtn.addEventListener('click', () => { + // this closes modal but does not remove the image from file input + modal.close(); + }) + + cancelBtn.addEventListener('click', () => { + // clears the file from image input field and closes the modal + imageInput.value = ""; + modal.close(); + }) + + const image = imageInput.files[0]; + + if (!image || !image.type.includes("image")) { + alert("Please upload a valid image"); + return; + } + if (image.size > size * 1000000) { + alert(`Max size of image is ${size} MB`); + return; + } + + // Creating a FileReader class to convert image blob to base64 + const fileReader = new FileReader(); + fileReader.readAsDataURL(image); + + fileReader.onload = (e) => { + const imagePlaceholder = document.getElementById("image-placeholder"); + imagePlaceholder.src = e.target.result; + modal.showModal(); + } +} + +document.upload = upload; diff --git a/resources/views/components/dashboard/navbar.blade.php b/resources/views/components/dashboard/navbar.blade.php index d6b08a0..8b6b78c 100644 --- a/resources/views/components/dashboard/navbar.blade.php +++ b/resources/views/components/dashboard/navbar.blade.php @@ -14,7 +14,7 @@

Profile

- +

Create Deal

diff --git a/resources/views/components/dashboard/page-heading.blade.php b/resources/views/components/dashboard/page-heading.blade.php new file mode 100644 index 0000000..a6a42c5 --- /dev/null +++ b/resources/views/components/dashboard/page-heading.blade.php @@ -0,0 +1,17 @@ +@props(['title' => '', 'description' => '', 'backLink' => '']) +
+ + @if($backLink !== '') +
+ + + +
+ @endif +
+

{{$title}}

+ @if($description !== '') +

{{$description}}

+ @endif +
+
diff --git a/resources/views/components/ui/image-input.blade.php b/resources/views/components/ui/image-input.blade.php new file mode 100644 index 0000000..efe2c58 --- /dev/null +++ b/resources/views/components/ui/image-input.blade.php @@ -0,0 +1,44 @@ +@props(['name' => '', 'label' => '', 'allowed' => '', 'size' => '1', 'required' => false]) + +
+ @if($label !== '') + + @endif +
+
+ +

Click to upload or drag and drop

+

{{strtoupper($allowed)}} upto {{$size}}MB

+
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ + +@vite('resources/js/image-input.js') diff --git a/resources/views/components/ui/input.blade.php b/resources/views/components/ui/input.blade.php index 8ae164a..34b920f 100644 --- a/resources/views/components/ui/input.blade.php +++ b/resources/views/components/ui/input.blade.php @@ -1,10 +1,21 @@ -@props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text']) +@props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text', 'description' => '', 'required' => false])
- @if($label !== '') - + @endif - - -
+ + @if($description !== '') +

{{$description}}

+ @endif + + diff --git a/resources/views/components/ui/select.blade.php b/resources/views/components/ui/select.blade.php index 7dae67f..1a669a7 100644 --- a/resources/views/components/ui/select.blade.php +++ b/resources/views/components/ui/select.blade.php @@ -1,13 +1,28 @@ -@props(['options' => [], 'name' => '', 'placeholder' => '', 'labelKey' => 'label', 'valueKey' => 'value', 'label' => '']) -@if($label !== '') - -@endif - - +@props(['options' => [], 'name' => '', 'placeholder' => '', 'labelKey' => 'label', 'valueKey' => 'value', 'label' => '', 'required' => false]) +
+ + @if($label !== '') + + @endif + + + + +
diff --git a/resources/views/components/ui/textarea.blade.php b/resources/views/components/ui/textarea.blade.php index dd76c31..378b7ee 100644 --- a/resources/views/components/ui/textarea.blade.php +++ b/resources/views/components/ui/textarea.blade.php @@ -1,9 +1,21 @@ -@props(['label' => '', 'name' => '', 'placeholder' => '']) +@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false]) @if($label !== '')
- - + + + + +
@endif diff --git a/resources/views/dashboards/broker/deals/create.blade.php b/resources/views/dashboards/broker/deals/create.blade.php new file mode 100644 index 0000000..034de0c --- /dev/null +++ b/resources/views/dashboards/broker/deals/create.blade.php @@ -0,0 +1,37 @@ + + +
+ + +

Deal Information

+
+ @csrf + + + + + + + + + + +
+ Submit + Cancel +
+ +
+
+
diff --git a/routes/web.php b/routes/web.php index 451380b..09db03c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -3,6 +3,7 @@ use App\Enums\UserTypes; use App\Http\Controllers\AuthenticatedUserController; use App\Http\Controllers\Broker\BrokerDashboardController; +use App\Http\Controllers\BrokerDealController; use App\Http\Controllers\HomeController; use App\Http\Controllers\RegisteredUserController; use App\Http\Middleware\HasRole; @@ -30,7 +31,8 @@ ->name('broker.') ->middleware(HasRole::class.':'.UserTypes::Broker->value) ->group(function () { - Route::get('dashboard', [BrokerDashboardController::class, 'index']); Route::get('dashboard', [BrokerDashboardController::class, 'index'])->name('dashboard'); + + Route::resource('deals', BrokerDealController::class); }); });