feature(admin-panel): manage broker section
- admin can edit, approve or reject broker registration - admin can edit, delete or impersonate as broker
This commit is contained in:
parent
aa3056e1d1
commit
c087126080
30
app/Actions/UpdateBrokerAction.php
Normal file
30
app/Actions/UpdateBrokerAction.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Throwable;
|
||||
|
||||
final readonly class UpdateBrokerAction
|
||||
{
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function execute(array $data, User $profile): void
|
||||
{
|
||||
/**
|
||||
* Separate the user fields from the broker fields
|
||||
*/
|
||||
$userFields = ['name', 'email'];
|
||||
$data = collect($data);
|
||||
$profileData = $data->only($userFields)->toArray();
|
||||
$userData = $data->except($userFields)->toArray();
|
||||
|
||||
DB::transaction(function () use ($profileData, $profile, $userData) {
|
||||
$profile->update($profileData);
|
||||
$user = $profile->type;
|
||||
$user->update($userData);
|
||||
});
|
||||
}
|
||||
}
|
||||
79
app/Http/Controllers/Admin/BrokerController.php
Normal file
79
app/Http/Controllers/Admin/BrokerController.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Actions\UpdateBrokerAction;
|
||||
use App\Enums\UserStatus;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreBrokerProfileRequest;
|
||||
use App\Models\Broker;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class BrokerController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('dashboards.admin.brokers.index')
|
||||
->with('activeBrokers', Broker::select(['id', 'location'])
|
||||
->whereRelation('user', 'status', UserStatus::Active->value)
|
||||
->with('user:id,name,email,role_id,role_type')
|
||||
->get()
|
||||
)
|
||||
->with('pendingBrokers', Broker::select(['id', 'location'])
|
||||
->whereRelation('user', 'status', UserStatus::Pending->value)
|
||||
->with('user:id,name,email,role_id,role_type')
|
||||
->get()
|
||||
);
|
||||
}
|
||||
|
||||
public function edit(Broker $broker)
|
||||
{
|
||||
return view('dashboards.admin.brokers.edit')
|
||||
->with('profile', $broker->user)
|
||||
->with('backLink', route('admin.brokers.index'))
|
||||
->with('actionLink', route('admin.brokers.update', $broker));
|
||||
}
|
||||
|
||||
public function update(StoreBrokerProfileRequest $request, Broker $broker, UpdateBrokerAction $action)
|
||||
{
|
||||
try {
|
||||
$action->execute($request->validated(), $broker->user);
|
||||
|
||||
return to_route('admin.brokers.index')
|
||||
->with('success', 'Profile updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Broker Profile Update Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
return back()->withInput()->with('error', 'Something went wrong.');
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Broker $broker)
|
||||
{
|
||||
try {
|
||||
\DB::transaction(function () use ($broker) {
|
||||
$broker->user->delete();
|
||||
$broker->delete();
|
||||
});
|
||||
|
||||
return back()->with('success', 'Broker deleted successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Broker Delete Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
return back()->with('error', 'Something went wrong.');
|
||||
}
|
||||
}
|
||||
|
||||
public function approve(Broker $broker)
|
||||
{
|
||||
try {
|
||||
$broker->user->update(['status' => UserStatus::Active->value]);
|
||||
|
||||
return to_route('admin.brokers.index')->with('success', 'Broker approved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Broker Approval Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
return back()->with('error', 'Something went wrong.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,7 +35,7 @@ public function update(StoreCustomerProfileRequest $request, Customer $customer,
|
||||
return to_route('admin.customers.index')
|
||||
->with('success', 'Profile updated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Customer Profile Update Failed: '.$e->getMessage(), $e->getTrace());
|
||||
Log::error('Customer Profile Update Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
return back()->withInput()->with('error', 'Something went wrong.');
|
||||
}
|
||||
@ -51,7 +51,7 @@ public function destroy(Customer $customer)
|
||||
|
||||
return back()->with('success', 'Customer deleted successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Customer Delete Failed: '.$e->getMessage(), $e->getTrace());
|
||||
Log::error('Customer Delete Failed: ', [$e->getMessage(), $e->getTrace()]);
|
||||
|
||||
return back()->with('error', 'Something went wrong.');
|
||||
}
|
||||
|
||||
@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use AllowDynamicProperties;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
#[AllowDynamicProperties]
|
||||
class StoreBrokerProfileRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
@ -12,7 +14,17 @@ class StoreBrokerProfileRequest extends FormRequest
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()->isBroker();
|
||||
// If this request is by a broker profile, then only allow the owner to update it.
|
||||
if (isset($this->profile)) {
|
||||
$this->user = $this->profile;
|
||||
|
||||
return $this->user()->id === $this->profile->id;
|
||||
}
|
||||
|
||||
// If this request is by an admin, then allow them to update any profile.
|
||||
$this->user = $this->broker->user;
|
||||
|
||||
return $this->user()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +37,7 @@ public function rules(): array
|
||||
return [
|
||||
'name' => 'required|string|min:3|max:255',
|
||||
'bio' => 'required|string|min:10|max:255',
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($this->user()->id)],
|
||||
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($this->user->id)],
|
||||
'phone' => 'required|string|min:10|max:255',
|
||||
'location' => 'required|string|min:3|max:255',
|
||||
];
|
||||
|
||||
@ -7,6 +7,8 @@
|
||||
|
||||
class Broker extends Model
|
||||
{
|
||||
protected $fillable = ['bio', 'location', 'phone'];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
|
||||
@ -16,7 +16,6 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.23",
|
||||
"laradumps/laradumps": "^5.0",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"laravel/sail": "^1.41",
|
||||
|
||||
484
composer.lock
generated
484
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -69,3 +69,7 @@ dialog {
|
||||
dialog[open] {
|
||||
animation: appear 300ms ease-in-out;
|
||||
}
|
||||
|
||||
.ui-table tr td,th {
|
||||
@apply py-1
|
||||
}
|
||||
|
||||
@ -0,0 +1,44 @@
|
||||
@props(['activeBrokers'])
|
||||
<x-dashboard.card class="w-full">
|
||||
|
||||
<h3 class="text-md font-bold mb-4">Active Brokers</h3>
|
||||
<x-ui.table>
|
||||
<x-ui.table.head>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Location</th>
|
||||
</x-ui.table.head>
|
||||
|
||||
@forelse($activeBrokers as $broker)
|
||||
<x-ui.table.row>
|
||||
<td class="text-sm px-4 text-center">{{$broker->user->name}}</td>
|
||||
<td class="text-sm px-4 text-center">{{$broker->user->email}}</td>
|
||||
<td class="text-sm px-4 text-center">{{$broker->location}}</td>
|
||||
<x-slot:actions>
|
||||
<div class="flex items-center justify-center space-x-2 py-1 px-4">
|
||||
<x-ui.button-sm tooltip="Edit Broker" :link="route('admin.brokers.edit', $broker)" variant="ghost">
|
||||
<x-heroicon-o-pencil-square class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
<form action="{{route('admin.brokers.destroy', $broker)}}"
|
||||
onsubmit="return confirm('Are you sure to delete this ?')" method="post"
|
||||
class=" h-full items-center flex justify-center">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-ui.button-sm tooltip="Remove Broker" variant="red">
|
||||
<x-heroicon-o-trash class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</form>
|
||||
<x-ui.button-sm tooltip="Login as this user" :link="route('impersonate', ['user' => $broker->user->id])"
|
||||
variant="neutral">
|
||||
<x-heroicon-o-camera class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</div>
|
||||
</x-slot:actions>
|
||||
</x-ui.table.row>
|
||||
@empty
|
||||
<x-ui.table.row>
|
||||
<td colspan="4" class="text-center text-sm text-accent-600 py-2">No Broker found</td>
|
||||
</x-ui.table.row>
|
||||
@endforelse
|
||||
</x-ui.table>
|
||||
</x-dashboard.card>
|
||||
@ -0,0 +1,49 @@
|
||||
@props(['pendingBrokers'])
|
||||
<x-dashboard.card class="w-full">
|
||||
|
||||
<h3 class="text-md font-bold mb-4">Broker Approvals</h3>
|
||||
<x-ui.table>
|
||||
<x-ui.table.head>
|
||||
<th>Name</th>
|
||||
<th>Email</th>
|
||||
<th>Location</th>
|
||||
</x-ui.table.head>
|
||||
|
||||
@forelse($pendingBrokers as $broker)
|
||||
<x-ui.table.row>
|
||||
<td class="text-sm px-4 text-center">{{$broker->user->name}}</td>
|
||||
<td class="text-sm px-4 text-center">{{$broker->user->email}}</td>
|
||||
<td class="text-sm px-4 text-center">{{$broker->location}}</td>
|
||||
<x-slot:actions>
|
||||
<div class="flex items-center justify-center space-x-2 py-1 px-4">
|
||||
<form action="{{route('admin.brokers.approve', $broker)}}"
|
||||
method="post"
|
||||
class=" h-full items-center flex justify-center">
|
||||
@csrf
|
||||
<x-ui.button-sm variant="green" tooltip="Approve Broker">
|
||||
<x-heroicon-o-check class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</form>
|
||||
<form action="{{route('admin.brokers.destroy', $broker)}}"
|
||||
onsubmit="return confirm('Are you sure to reject this ?')" method="post"
|
||||
class=" h-full items-center flex justify-center">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-ui.button-sm tooltip="Reject Broker" variant="red">
|
||||
<x-heroicon-o-x-mark class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</form>
|
||||
<x-ui.button-sm tooltip="Edit Details" :link="route('admin.brokers.edit', $broker)"
|
||||
variant="ghost">
|
||||
<x-heroicon-o-pencil-square class="w-4"/>
|
||||
</x-ui.button-sm>
|
||||
</div>
|
||||
</x-slot:actions>
|
||||
</x-ui.table.row>
|
||||
@empty
|
||||
<x-ui.table.row>
|
||||
<td colspan="4" class="text-center text-sm text-accent-600 py-2">No Broker found</td>
|
||||
</x-ui.table.row>
|
||||
@endforelse
|
||||
</x-ui.table>
|
||||
</x-dashboard.card>
|
||||
@ -20,13 +20,16 @@ class="flex flex-col p-4 pt-6 justify-between font-medium h-full w-full overflow
|
||||
<p class="sidebar-text transition-opacity duration-300 ease-in-out ">Dashboard</p>
|
||||
</x-dashboard.broker.sidebar.item>
|
||||
|
||||
<x-dashboard.broker.sidebar.item :active="\Illuminate\Support\Facades\Route::is('admin.customers.*')" :link="route('admin.customers.index')">
|
||||
<x-dashboard.broker.sidebar.item
|
||||
:active="\Illuminate\Support\Facades\Route::is('admin.customers.*')"
|
||||
:link="route('admin.customers.index')">
|
||||
<x-heroicon-o-users class="min-w-5 w-5"/>
|
||||
<p class="sidebar-text transition-opacity duration-300 ease-in-out ">Manage Customers</p>
|
||||
</x-dashboard.broker.sidebar.item>
|
||||
|
||||
<x-dashboard.broker.sidebar.item :link="route('broker.deals.index')">
|
||||
<svg class="w-5 min-w-5" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000" stroke="#000000" stroke-width="0.2"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path d="M512.536516 506.562145l-112.606908 197.063113 112.606908 225.21484 112.607932-225.21484z" fill="#000000"></path><path d="M680.856096 476.172205c36.824036-40.482378 59.488761-94.060249 59.488761-152.967444 0-125.613323-102.193994-227.807318-227.807317-227.807318s-227.807318 102.193994-227.807318 227.807318c0 58.906171 22.664726 112.485066 59.488761 152.967444-58.595933 32.817572-187.285008 138.139536-187.285008 430.442414 0 12.273313 9.951141 22.225479 22.225479 22.225479 12.273313 0 22.225479-9.951141 22.225479-22.225479 0-304.35708 145.515606-384.465868 178.379253-398.671253 37.433247 26.981426 83.210136 43.069736 132.773354 43.069737 49.564241 0 95.342154-16.08831 132.775401-43.070761 32.907674 14.226887 178.376182 94.379701 178.376182 398.673301 0 12.273313 9.951141 22.225479 22.225479 22.225479s22.225479-9.951141 22.225478-22.225479c-0.001024-292.303902-128.690099-397.625866-187.283984-430.443438zM329.179132 323.204761c0-101.098437 82.258947-183.357384 183.357384-183.357384s183.357384 82.258947 183.357384 183.357384-82.258947 183.357384-183.357384 183.357384-183.357384-82.258947-183.357384-183.357384z" fill="#020b07"></path></g></svg>
|
||||
<x-dashboard.broker.sidebar.item :active="\Illuminate\Support\Facades\Route::is('admin.brokers.*')"
|
||||
:link="route('admin.brokers.index')">
|
||||
<x-heroicon-o-user class="min-w-5 w-5"/>
|
||||
<p class="sidebar-text transition-opacity duration-300 ease-in-out">Manage Brokers</p>
|
||||
</x-dashboard.broker.sidebar.item>
|
||||
|
||||
|
||||
@ -1,19 +1,30 @@
|
||||
@props(['variant' => '', 'link' => '', 'external' => false])
|
||||
@props(['variant' => '', 'link' => '', 'external' => false, 'tooltip' => ''])
|
||||
@php
|
||||
$variants = [
|
||||
'neutral' => 'bg-primary-600 text-white',
|
||||
'ghost' => 'bg-gray-100 text-black text-sm',
|
||||
'red' => 'bg-red-500 text-red-100 text-sm'
|
||||
];
|
||||
$variants = [
|
||||
'neutral' => 'bg-primary-600 text-white',
|
||||
'ghost' => 'bg-gray-200 text-gray-700 text-sm',
|
||||
'red' => 'bg-red-100 text-red-500 text-sm',
|
||||
'green' => 'bg-green-100 text-green-700 text-sm',
|
||||
];
|
||||
|
||||
$variantClass = $variants[$variant] ?? '';
|
||||
$variantClass = $variants[$variant] ?? '';
|
||||
@endphp
|
||||
@if($link !== '')
|
||||
<a href="{{$link}}" @if($external) target="_blank" @endif {{$attributes->merge(['class' => "inline-flex px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover: active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
|
||||
{{$slot}}
|
||||
</a>
|
||||
@else
|
||||
<button {{$attributes->merge(['class' => "px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover:scale-110 active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
|
||||
<p>{{$slot}}</p>
|
||||
</button>
|
||||
@endif
|
||||
<div class="relative group w-fit hover:z-1">
|
||||
@if($link !== '')
|
||||
<a href="{{$link}}"
|
||||
@if($external) target="_blank" @endif {{$attributes->merge(['class' => "inline-flex px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover: active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
|
||||
{{$slot}}
|
||||
</a>
|
||||
@else
|
||||
<button {{$attributes->merge(['class' => "px-2 py-1 text-xs rounded-md font-medium hover:opacity-80 hover:scale-110 active:scale-80 transition-all duration-300 ease-in-out $variantClass"])}}>
|
||||
<p>{{$slot}}</p>
|
||||
|
||||
</button>
|
||||
@endif
|
||||
@if($tooltip !== '')
|
||||
<span
|
||||
class="absolute top-full mt-2 hidden group-hover:block right-0 py-1 px-2 rounded-lg bg-gray-900 text-xs whitespace-nowrap text-white">
|
||||
{{$tooltip}}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<div class="pt-2 rounded-lg border border-gray-200">
|
||||
<table class="table-auto w-full ">
|
||||
<table class="ui-table table-auto w-full ">
|
||||
{{$slot}}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<tr class=" border-t border-t-gray-200 ">
|
||||
{{$slot}}
|
||||
@if(($actions ?? '') !== '')
|
||||
<td class="border-l border-l-gray-200">
|
||||
<td class="border-l border-l-gray-200 w-8">
|
||||
{{$actions ?? ''}}
|
||||
</td>
|
||||
@endif
|
||||
|
||||
13
resources/views/dashboards/admin/brokers/edit.blade.php
Normal file
13
resources/views/dashboards/admin/brokers/edit.blade.php
Normal file
@ -0,0 +1,13 @@
|
||||
<x-dashboard.admin.layout title="Edit Broker">
|
||||
<x-slot:heading>
|
||||
<x-dashboard.page-heading
|
||||
title="Edit Broker Profile"
|
||||
description="Update broker profile information."
|
||||
:back-link="$backLink"
|
||||
/>
|
||||
</x-slot:heading>
|
||||
|
||||
<div class="flex items-center justify-center px-4 pb-4 pt-0 md:px-8 md:pb-8">
|
||||
<x-dashboard.user.edit-profile-card :back-link="$backLink" :action-link="$actionLink" :profile="$profile" />
|
||||
</div>
|
||||
</x-dashboard.admin.layout>
|
||||
12
resources/views/dashboards/admin/brokers/index.blade.php
Normal file
12
resources/views/dashboards/admin/brokers/index.blade.php
Normal file
@ -0,0 +1,12 @@
|
||||
<x-dashboard.admin.layout title="Manage Brokers">
|
||||
<x-slot:heading>
|
||||
<x-dashboard.page-heading
|
||||
title="Manage Brokers"
|
||||
description="Edit, Delete and Login as Broker"
|
||||
/>
|
||||
</x-slot:heading>
|
||||
<div class="flex flex-col items-center justify-center space-y-4 md:space-y-8 px-4 pb-4 pt-0 md:px-8 md:pb-8">
|
||||
<x-dashboard.admin.broker-approval :pending-brokers="$pendingBrokers" />
|
||||
<x-dashboard.admin.active-broker :active-brokers="$activeBrokers" />
|
||||
</div>
|
||||
</x-dashboard.admin.layout>
|
||||
@ -44,7 +44,7 @@ class=" h-full items-center flex justify-center">
|
||||
</x-ui.table.row>
|
||||
@empty
|
||||
<x-ui.table.row>
|
||||
<td colspan="2" class="text-center text-sm text-accent-600 py-2">No Deals found</td>
|
||||
<td colspan="2" class="text-center text-sm text-accent-600 py-2">No Customer found</td>
|
||||
</x-ui.table.row>
|
||||
@endforelse
|
||||
</x-ui.table>
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\Admin\AdminDashboardController;
|
||||
use App\Http\Controllers\Admin\BrokerController;
|
||||
use App\Http\Controllers\Admin\CustomerController;
|
||||
use App\Http\Middleware\HasRole;
|
||||
|
||||
@ -11,4 +12,7 @@
|
||||
->group(function () {
|
||||
Route::get('dashboard', AdminDashboardController::class)->name('dashboard');
|
||||
Route::resource('customers', CustomerController::class)->except('show', 'create', 'store');
|
||||
Route::resource('brokers', BrokerController::class)->except('show', 'create', 'store');
|
||||
|
||||
Route::post('/brokers/approve/{broker}', [BrokerController::class, 'approve'])->name('brokers.approve');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user