feat(deal update and delete): broker can update and delete their deals

- show external links in listings
- refactor image-input.blade.php to display image while update
- refactor image-input.js to show selected image after user clicks submit
- refactor components to accept default value
- add FileService to handle image update and delete
This commit is contained in:
kusowl 2026-01-13 15:10:55 +05:30
parent e2fabcd8b6
commit a248d3fc79
18 changed files with 464 additions and 69 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@
Homestead.json Homestead.json
Homestead.yaml Homestead.yaml
Thumbs.db Thumbs.db
laradumps.yaml

View File

@ -19,11 +19,13 @@ protected function deals()
return Auth::user() return Auth::user()
->deals() ->deals()
->select([ ->select([
'id',
'title', 'title',
'description', 'description',
'image', 'image',
'active', 'active',
'slug', 'slug',
'link',
'deal_category_id', 'deal_category_id',
]) ])
->with('category:id,name') ->with('category:id,name')

View File

@ -5,9 +5,12 @@
use App\Http\Requests\StoreBrokerDeal; use App\Http\Requests\StoreBrokerDeal;
use App\Models\Deal; use App\Models\Deal;
use App\Models\DealCategory; use App\Models\DealCategory;
use App\Services\FileService;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use const http\Client\Curl\AUTH_ANY;
class BrokerDealController extends Controller class BrokerDealController extends Controller
{ {
@ -38,9 +41,9 @@ public function store(StoreBrokerDeal $request)
$data['user_id'] = $request->user()->id; $data['user_id'] = $request->user()->id;
$path = ''; $path = '';
if($request->hasFile('image')){ if ($request->hasFile('image')) {
$image = $request->file('image'); $image = $request->file('image');
$path = $image->storeAs('images/deals', $data['slug'] . '.' . $image->extension(), 'public'); $path = $image->storeAs('images/deals', $data['slug'].'.'.$image->extension(), 'public');
} }
$data['image'] = $path; $data['image'] = $path;
@ -62,24 +65,61 @@ public function show(string $id)
/** /**
* Show the form for editing the specified resource. * Show the form for editing the specified resource.
*/ */
public function edit(string $id) public function edit(Deal $deal)
{ {
// return view('dashboards.broker.deals.edit')
->with('deal', $deal)
->with('categories', DealCategory::all('id', 'name'));
} }
/** /**
* Update the specified resource in storage. * Update the specified resource in storage.
*/ */
public function update(Request $request, string $id) public function update(StoreBrokerDeal $request, Deal $deal, FileService $fileService)
{ {
// $data = $request->validated();
try {
DB::transaction(function () use ($deal, $data, $fileService, $request) {
$data['slug'] = Str::slug($data['title']);
if ($request->hasFile('image')) {
$image = $request->file('image');
$data['image'] = $fileService->upload($image, 'images/deals',
$data['slug'].'.'.$image->extension());
}
Deal::unguard();
$deal->update($data);
Deal::reguard();
});
} catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.');
}
return to_route('broker.dashboard')->with('success', 'Deal has been updated.');
} }
/** /**
* Remove the specified resource from storage. * Remove the specified resource from storage.
*/ */
public function destroy(string $id) public function destroy(Deal $deal, FileService $fileService)
{ {
// // remove the image of deal
try {
DB::transaction(function () use ($deal, $fileService) {
$fileService->delete($deal->image);
$deal->delete();
});
} catch (\Throwable $exception) {
Log::error($exception->getMessage(), $exception->getTrace());
return to_route('broker.dashboard')->with('error', 'Something gone wrong.');
}
return to_route('broker.dashboard')->with('success', 'Deal has been deleted.');
} }
} }

View File

@ -24,7 +24,7 @@ public function rules(): array
return [ return [
'title' => 'required|min:10|max:255', 'title' => 'required|min:10|max:255',
'description' => 'required|min:10|max:300', 'description' => 'required|min:10|max:300',
'image' => 'required|image|mimes:jpeg,png,jpg|max:10240', 'image' => [$this->isMethod('post') ? 'required' : 'nullable', 'image', 'mimes:jpeg,png,jpg', 'max:10240'],
'link' => 'nullable|url', 'link' => 'nullable|url',
'deal_category_id' => 'required|exists:deal_categories,id', 'deal_category_id' => 'required|exists:deal_categories,id',
]; ];

View File

@ -0,0 +1,21 @@
<?php
namespace App\Services;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Storage;
class FileService
{
public function upload(UploadedFile $file, string $folder, string $filename): string
{
return $file->storeAs($folder, $filename.'.'.$file->extension(), 'public');
}
public function delete(?string $path): void
{
if ($path && Storage::disk('public')->exists($path)) {
Storage::disk('public')->delete($path);
}
}
}

View File

@ -16,6 +16,7 @@
}, },
"require-dev": { "require-dev": {
"fakerphp/faker": "^1.23", "fakerphp/faker": "^1.23",
"laradumps/laradumps": "^5.0",
"laravel/pail": "^1.2.2", "laravel/pail": "^1.2.2",
"laravel/pint": "^1.24", "laravel/pint": "^1.24",
"laravel/sail": "^1.41", "laravel/sail": "^1.41",

229
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "2f3167c1eddf52a1400c85a1ac601d4d", "content-hash": "d2215c7e3da7601aa624d057baef6449",
"packages": [ "packages": [
{ {
"name": "blade-ui-kit/blade-heroicons", "name": "blade-ui-kit/blade-heroicons",
@ -1204,16 +1204,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v12.45.1", "version": "v12.46.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "1ca7e2a2ee17ae5bc435af7cb52d2f130148e2fa" "reference": "9dcff48d25a632c1fadb713024c952fec489c4ae"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/1ca7e2a2ee17ae5bc435af7cb52d2f130148e2fa", "url": "https://api.github.com/repos/laravel/framework/zipball/9dcff48d25a632c1fadb713024c952fec489c4ae",
"reference": "1ca7e2a2ee17ae5bc435af7cb52d2f130148e2fa", "reference": "9dcff48d25a632c1fadb713024c952fec489c4ae",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1422,7 +1422,7 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2026-01-07T00:50:24+00:00" "time": "2026-01-07T23:26:53+00:00"
}, },
{ {
"name": "laravel/prompts", "name": "laravel/prompts",
@ -6594,6 +6594,147 @@
}, },
"time": "2025-03-19T14:43:43+00:00" "time": "2025-03-19T14:43:43+00:00"
}, },
{
"name": "laradumps/laradumps",
"version": "v5.0.0",
"source": {
"type": "git",
"url": "https://github.com/laradumps/laradumps.git",
"reference": "7bfb9b888ce351ca151e29cacf2e2e74c7b62f72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laradumps/laradumps/zipball/7bfb9b888ce351ca151e29cacf2e2e74c7b62f72",
"reference": "7bfb9b888ce351ca151e29cacf2e2e74c7b62f72",
"shasum": ""
},
"require": {
"illuminate/mail": "^11.0|^12.0",
"illuminate/support": "^11.0|^12.0",
"laradumps/laradumps-core": "^4.0.0",
"nunomaduro/termwind": "^2.3.3",
"php": "^8.2"
},
"require-dev": {
"larastan/larastan": "^3.8",
"laravel/framework": "^11.0|^12.0",
"laravel/pint": "^1.26.0",
"livewire/livewire": "^3.7.1|^4.0",
"mockery/mockery": "^1.6.12",
"orchestra/testbench-core": "^9.4|^10.0",
"pestphp/pest": "^3.7.0|^4.0.0",
"symfony/var-dumper": "^7.1.3|^8.0"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"LaraDumps\\LaraDumps\\LaraDumpsServiceProvider"
]
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"LaraDumps\\LaraDumps\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Luan Freitas",
"email": "luanfreitas10@protonmail.com",
"role": "Developer"
}
],
"description": "LaraDumps is a friendly app designed to boost your Laravel PHP coding and debugging experience.",
"homepage": "https://github.com/laradumps/laradumps",
"support": {
"issues": "https://github.com/laradumps/laradumps/issues",
"source": "https://github.com/laradumps/laradumps/tree/v5.0.0"
},
"funding": [
{
"url": "https://github.com/luanfreitasdev",
"type": "github"
}
],
"time": "2025-12-13T12:44:16+00:00"
},
{
"name": "laradumps/laradumps-core",
"version": "v4.0.0",
"source": {
"type": "git",
"url": "https://github.com/laradumps/laradumps-core.git",
"reference": "bb57c8fccb785777020b85592d718e8ff0c9a23a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laradumps/laradumps-core/zipball/bb57c8fccb785777020b85592d718e8ff0c9a23a",
"reference": "bb57c8fccb785777020b85592d718e8ff0c9a23a",
"shasum": ""
},
"require": {
"ext-curl": "*",
"nunomaduro/termwind": "^2.0",
"php": "^8.2",
"ramsey/uuid": "^4.9.1",
"spatie/backtrace": "^1.5",
"symfony/console": "^6.4|^7.0|^8.0",
"symfony/finder": "^6.4|^7.0|^8.0",
"symfony/process": "^6.4|^7.0|^8.0",
"symfony/var-dumper": "^6.4|^7.0|^8.0",
"symfony/yaml": "^6.4|^7.0|^8.0"
},
"require-dev": {
"illuminate/support": "^10.46",
"laravel/pint": "^1.26.0",
"pestphp/pest": "^3.0|^4.0",
"phpstan/phpstan": "^1.10.50"
},
"bin": [
"bin/laradumps"
],
"type": "library",
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"LaraDumps\\LaraDumpsCore\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Luan Freitas",
"email": "luanfreitas10@protonmail.com",
"role": "Developer"
}
],
"description": "LaraDumps is a friendly app designed to boost your Laravel / PHP coding and debugging experience.",
"homepage": "https://github.com/laradumps/laradumps-core",
"support": {
"issues": "https://github.com/laradumps/laradumps-core/issues",
"source": "https://github.com/laradumps/laradumps-core/tree/v4.0.0"
},
"funding": [
{
"url": "https://github.com/luanfreitasdev",
"type": "github"
}
],
"time": "2025-12-12T22:10:38+00:00"
},
{ {
"name": "laravel/pail", "name": "laravel/pail",
"version": "v1.2.4", "version": "v1.2.4",
@ -9183,6 +9324,70 @@
], ],
"time": "2025-02-07T05:00:38+00:00" "time": "2025-02-07T05:00:38+00:00"
}, },
{
"name": "spatie/backtrace",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/spatie/backtrace.git",
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/backtrace/zipball/8c0f16a59ae35ec8c62d85c3c17585158f430110",
"reference": "8c0f16a59ae35ec8c62d85c3c17585158f430110",
"shasum": ""
},
"require": {
"php": "^7.3 || ^8.0"
},
"require-dev": {
"ext-json": "*",
"laravel/serializable-closure": "^1.3 || ^2.0",
"phpunit/phpunit": "^9.3 || ^11.4.3",
"spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6",
"symfony/var-dumper": "^5.1 || ^6.0 || ^7.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\Backtrace\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van de Herten",
"email": "freek@spatie.be",
"homepage": "https://spatie.be",
"role": "Developer"
}
],
"description": "A better backtrace",
"homepage": "https://github.com/spatie/backtrace",
"keywords": [
"Backtrace",
"spatie"
],
"support": {
"issues": "https://github.com/spatie/backtrace/issues",
"source": "https://github.com/spatie/backtrace/tree/1.8.1"
},
"funding": [
{
"url": "https://github.com/sponsors/spatie",
"type": "github"
},
{
"url": "https://spatie.be/open-source/support-us",
"type": "other"
}
],
"time": "2025-08-26T08:22:30+00:00"
},
{ {
"name": "staabm/side-effects-detector", "name": "staabm/side-effects-detector",
"version": "1.0.5", "version": "1.0.5",
@ -9422,16 +9627,16 @@
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "2.0.0", "version": "2.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/webmozarts/assert.git", "url": "https://github.com/webmozarts/assert.git",
"reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54" "reference": "bdbabc199a7ba9965484e4725d66170e5711323b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/webmozarts/assert/zipball/1b34b004e35a164bc5bb6ebd33c844b2d8069a54", "url": "https://api.github.com/repos/webmozarts/assert/zipball/bdbabc199a7ba9965484e4725d66170e5711323b",
"reference": "1b34b004e35a164bc5bb6ebd33c844b2d8069a54", "reference": "bdbabc199a7ba9965484e4725d66170e5711323b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -9478,9 +9683,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/webmozarts/assert/issues", "issues": "https://github.com/webmozarts/assert/issues",
"source": "https://github.com/webmozarts/assert/tree/2.0.0" "source": "https://github.com/webmozarts/assert/tree/2.1.1"
}, },
"time": "2025-12-16T21:36:00+00:00" "time": "2026-01-08T11:28:40+00:00"
} }
], ],
"aliases": [], "aliases": [],

View File

@ -1,19 +1,11 @@
const previewImage = document.getElementById('preview-image');
function upload(size) { function upload(size) {
const imageInput = document.getElementById("image-input"); const imageInput = document.getElementById("image-input");
const closeModalBtn = document.getElementById("close-modal"); const closeModalBtn = document.getElementById("close-modal");
const cancelBtn = document.getElementById("cancel-modal"); const cancelBtn = document.getElementById("cancel-modal");
const modal = document.getElementById("image-modal"); const modal = document.getElementById("image-modal");
let imageUrl = '';
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]; const image = imageInput.files[0];
@ -32,9 +24,21 @@ function upload(size) {
fileReader.onload = (e) => { fileReader.onload = (e) => {
const imagePlaceholder = document.getElementById("image-placeholder"); const imagePlaceholder = document.getElementById("image-placeholder");
imagePlaceholder.src = e.target.result; imagePlaceholder.src = imageUrl = e.target.result;
modal.showModal(); modal.showModal();
} }
closeModalBtn.addEventListener('click', () => {
// this closes then modal and sets the preview image
previewImage.src = imageUrl;
modal.close();
})
cancelBtn.addEventListener('click', () => {
// clears the file from image input field and closes the modal
imageInput.value = "";
modal.close();
})
} }
document.upload = upload; document.upload = upload;

View File

@ -1,15 +1,43 @@
@props(['image' => '', 'title' => '', 'category' => '', 'impressions' => 0, 'likes' => 0, 'clicks' => 0, 'status' => false]) @props(['id' => '', 'image' => '', 'title' => '', 'category' => '', 'impressions' => 0, 'likes' => 0, 'clicks' => 0, 'status' => false, 'external_link' => ''])
<x-ui.image-card :image="$image"> <x-ui.image-card :image="$image">
<div class="bg-white pt-8 px-4 space-y-4"> <div class="bg-white pt-8 p-4 space-y-4">
<div class="flex justify-between items-baseline"> <div class="flex justify-between items-center">
<p class="font-bold text-lg">{{$title}}</p>
<div class="flex items-center space-x-1 mr-2">
<p class="font-bold text-lg">{{$title}}</p>
@if($external_link !== '')
<a href="{{$external_link}}" target="_blank" title="{{$external_link}}"
class="text-xs underline text-accent-601">
<x-heroicon-o-arrow-top-right-on-square class="w-4 stroke-2 "/>
</a>
@endif
</div>
@if($status == 1) @if($status == 1)
<x-ui.badge title="Active"/> <x-ui.badge title="Active"/>
@else @else
<x-ui.badge title="Pending" variant="ghost"/> <x-ui.badge title="Pending" variant="ghost"/>
@endif @endif
</div> </div>
<p class="text-accent-600">{{$category}}</p> <p class="text-accent-600">{{$category}}</p>
<x-dashboard.listing-stats :impression="$impressions" :likes="$likes" :clicks="$clicks"/> <x-dashboard.listing-stats :impression="$impressions" :likes="$likes" :clicks="$clicks"/>
<div class="flex justify-between space-x-4">
<x-ui.button :link="route('broker.deals.edit', $id)" class="w-full border border-accent-600/30"
icon="pencil-square">Edit
</x-ui.button>
<form class="w-full" method="post" action="{{route('broker.deals.destroy', $id)}}">
@csrf
@method("DELETE")
<x-ui.button variant="red" class="w-full" icon="trash">Delete</x-ui.button>
</form>
</div>
</div> </div>
</x-ui.image-card> </x-ui.image-card>

View File

@ -3,9 +3,9 @@
<x-dashboard.card class="bg-white"> <x-dashboard.card class="bg-white">
<p class="font-bold mb-6">My Listings</p> <p class="font-bold mb-6">My Listings</p>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
@foreach($deals as $deal) @forelse($deals as $deal)
<x-dashboard.listing-card <x-dashboard.listing-card
:id="$deal->id"
:image="asset('storage/'.$deal->image)" :image="asset('storage/'.$deal->image)"
:title="$deal->title" :title="$deal->title"
:category="$deal->category->name" :category="$deal->category->name"
@ -13,8 +13,11 @@
impressions="1245" impressions="1245"
likes="89" likes="89"
class="156" class="156"
:external_link="$deal->link"
/> />
@endforeach @empty
<p class="text-center text-xs text-accent-600 lg:col-span-3">No Deals created</p>
@endforelse
</div> </div>
</x-dashboard.card> </x-dashboard.card>
</div> </div>

View File

@ -1,11 +1,29 @@
@props(['variant' => '']) @props(['variant' => '', 'icon' => '', 'link' => ''])
@php @php
$variants = [ $variants = [
'neutral' => 'bg-primary-600 text-white' 'neutral' => 'bg-primary-600 text-white',
]; 'red' => 'bg-red-500 text-white'
];
$variantClass = $variants[$variant] ?? ''; $variantClass = $variants[$variant] ?? '';
@endphp @endphp
<button {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 $variantClass"])}}> @if($link !== '')
{{$slot}} <a {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 $variantClass", 'href' => $link])}}>
</button> <div class="flex justify-center items-center space-x-2">
@if($icon !=='')
@svg("heroicon-o-$icon", 'w-5 h-5')
@endif
<p>{{$slot}}</p>
</div>
</a>
@else
<button {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 $variantClass"])}}>
<div class="flex justify-center items-center space-x-2">
@if($icon !=='')
@svg("heroicon-o-$icon", 'w-5 h-5')
@endif
<p>{{$slot}}</p>
</div>
</button>
@endif

View File

@ -1,9 +1,11 @@
@props(['image' => '', 'alt' => '']) @props(['image' => '', 'alt' => ''])
<div class="border border-gray-200 rounded-xl overflow-clip"> <div class="border border-gray-200 rounded-xl overflow-clip">
<div class="grid grid-rows-2"> <div class="flex flex-col">
<div class="rounded-t-xl h-40"> <div class="rounded-t-xl h-40">
<img src="{{$image}}" alt="" class="object-cover"> <img src="{{$image}}" alt="" class="object-cover">
</div> </div>
{{$slot}} <div>
{{$slot}}
</div>
</div> </div>
</div> </div>

View File

@ -1,32 +1,40 @@
@props(['name' => '', 'label' => '', 'allowed' => '', 'size' => '1', 'required' => false]) @props(['name' => '', 'label' => '', 'allowed' => '', 'size' => '1', 'required' => false, 'value' => ''])
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
@if($label !== '') @if($label !== '')
<label class="text-sm font-bold" for="{{$name}}"> <label class="text-sm font-bold" for="{{$name}}">
{{$label}} {{$label}} @if($required && !$value)
@if($required)
* *
@endif @endif
</label> </label>
@endif @endif
<div class="relative">
<div class="relative group">
<div <div
class="p-8 border-2 border-dashed border-accent-600/70 rounded-lg flex flex-col space-y-2 justify-center items-center"> class="p-8 border-2 border-dashed border-accent-600/70 rounded-lg flex flex-col space-y-2 justify-center items-center overflow-hidden min-h-40 bg-gray-50 relative">
<x-heroicon-o-arrow-up-tray class="w-8 text-accent-600/70"/>
<p class="text-sm text-accent-600/90 font-bold">Click to upload or drag and drop</p> <img src="{{ $value }}" id="preview-image"
<p class="text-xs text-accent-600/70">{{strtoupper($allowed)}} upto {{$size}}MB</p> class="absolute inset-0 w-full h-full object-cover opacity-60 group-hover:opacity-10 transition-opacity">
<x-heroicon-o-arrow-up-tray class="w-8 text-accent-600/70 z-10"/>
<p class="text-sm text-accent-600/90 font-bold z-10">
{{ $value ? 'Click to replace current image' : 'Click to upload or drag and drop' }}
</p>
<p class="text-xs text-accent-600/70 z-10">{{strtoupper($allowed)}} up to {{$size}}MB</p>
</div> </div>
<input <input
name="{{$name}}" name="{{$name}}"
id="image-input" id="image-input"
class="opacity-0 absolute w-full h-full top-0 left-0" class="opacity-0 absolute w-full h-full top-0 left-0 cursor-pointer"
type="file" type="file"
accept="{{ $allowed ? '.' . str_replace(',', ',.', $allowed) : 'image/*' }}"
onchange="upload(10)" onchange="upload(10)"
accept="image/{{$allowed}}" {{ $required && !$value ? 'required' : '' }}
/> />
<x-ui.inline-error :name="$name"/>
</div> </div>
<x-ui.inline-error :name="$name"/>
</div> </div>
<dialog id="image-modal" <dialog id="image-modal"
@ -40,5 +48,4 @@ class="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white rounded
</div> </div>
</dialog> </dialog>
@vite('resources/js/image-input.js') @vite('resources/js/image-input.js')

View File

@ -1,4 +1,4 @@
@props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text', 'description' => '', 'required' => false]) @props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text', 'description' => '', 'required' => false, 'value' => ''])
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
@if($label !== '') @if($label !== '')
<label class="text-sm font-bold" for="{{$name}}"> <label class="text-sm font-bold" for="{{$name}}">
@ -10,7 +10,7 @@
@endif @endif
<input class="bg-[#F3F3F5] py-2 px-4 rounded-lg" <input class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
type="{{$type}}" placeholder="{{$placeholder}}" type="{{$type}}" placeholder="{{$placeholder}}"
name="{{$name}}" value="{{old($name)}}" name="{{$name}}" value="{{old($name, $value)}}"
{{$required?'required':''}} {{$required?'required':''}}
{{$attributes}} {{$attributes}}
> >

View File

@ -1,4 +1,14 @@
@props(['options' => [], 'name' => '', 'placeholder' => '', 'labelKey' => 'label', 'valueKey' => 'value', 'label' => '', 'required' => false]) @props([
'options' => [],
'name' => '',
'placeholder' => '',
'labelKey' => 'label',
'valueKey' => 'value',
'label' => '',
'required' => false,
'selected' => ''
]
)
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
@if($label !== '') @if($label !== '')
@ -20,7 +30,8 @@ class="bg-[#F3F3F5] py-2 px-4 rounded-lg text-sm font-bold invalid:text-accent-6
@endif @endif
@foreach($options as $option) @foreach($options as $option)
<option value="{{$option[$valueKey]}}" {{$option[$valueKey] == old($name) ? 'selected' : ''}}> {{$option[$labelKey]}} </option> <option
value="{{$option[$valueKey]}}" {{$option[$valueKey] == old($name, $selected) ? 'selected' : ''}}> {{$option[$labelKey]}} </option>
@endforeach @endforeach
</select> </select>

View File

@ -1,4 +1,4 @@
@props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false]) @props(['label' => '', 'name' => '', 'placeholder' => '', 'required' => false, 'value' => ''])
@if($label !== '') @if($label !== '')
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
@ -14,7 +14,7 @@
class="bg-[#F3F3F5] py-2 px-4 rounded-lg" class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
name="{{$name}}" placeholder="{{$placeholder}}" name="{{$name}}" placeholder="{{$placeholder}}"
required="{{$required?'required':''}}" required="{{$required?'required':''}}"
>{{old($name)}}</textarea> >{{old($name, $value)}}</textarea>
<x-ui.inline-error :name="$name"/> <x-ui.inline-error :name="$name"/>
</div> </div>

View File

@ -0,0 +1,41 @@
<x-layout title="Edit deal">
<x-dashboard.page-heading
title="Edit Deal"
description="Modify your existing deal"
:back-link="route('broker.dashboard')"
/>
<div class="flex items-center justify-center mt-8">
<x-dashboard.card class="w-8/12">
<h3 class="text-md font-bold">Deal Information</h3>
<form method="post" enctype="multipart/form-data" action="{{route('broker.deals.update', $deal)}}" class="flex flex-col space-y-8 mt-4">
@csrf
@method('PATCH')
<x-ui.input name="title" label="Deal Title" :value="$deal->title" required placeholder="e.g., Luxury Apartment Downtown"/>
<x-ui.select :options="$categories" :selected="$deal->deal_category_id" name="deal_category_id" label-key="name" value-key="id" label="Category"
placeholder="Select a category" required/>
<x-ui.textarea :value="$deal->description" name="description" label="Description" required
placeholder="Describe your deal in detail..."/>
<x-ui.image-input name="image" label="Upload image" allowed="jpg,png" size="10" :value="asset('storage/'.$deal->image)"/>
<x-ui.input
:value="$deal->link"
name="link"
label="External Link (Optional)"
placeholder="https://example.com"
description="Add a link to your website, listing page or contact form"
/>
<div class="grid grid-cols-12 w-full space-x-4">
<x-ui.button variant="neutral" class="col-span-10">Update</x-ui.button>
<a href="{{route('broker.dashboard')}}" class="ui-btn border border-accent-600/20 col-span-2">Cancel</a>
</div>
</form>
</x-dashboard.card>
</div>
</x-layout>

View File

@ -1,6 +1,17 @@
<x-layout title="Broker Dashboard"> <x-layout title="Broker Dashboard">
<section class="flex flex-col space-y-8 bg-[#F9FAFB]"> <section class="flex flex-col space-y-8 bg-[#F9FAFB]">
<x-dashboard.navbar/> <x-dashboard.navbar/>
<div class="wrapper">
@session('success')
<x-ui.alert-success>{{$value}}</x-ui.alert-success>
@endsession
@session('error')
<x-ui.alert-error>{{$value}}</x-ui.alert-error>
@endsession
</div>
<x-dashboard.stats :list_count="$deals->count()"/> <x-dashboard.stats :list_count="$deals->count()"/>
<x-dashboard.listing :deals="$deals"/> <x-dashboard.listing :deals="$deals"/>
</section> </section>