refactor: consolidate alert components and improve functionality
- replaced `alert-error` and `alert-success` components with a single reusable `alert` component - added JS functionality for dismissible alerts - updated related views to use the new `alert` component - adjusted broker profile logic to display initials and verification status dynamically - refactored morph relations from `type` to `role` - enhanced image preview behavior for file inputs - made broker migration fields nullable and added safeguards against registration errors - Added confirmation when a user wants delete deal - Add dynamic initials for user profile picture - make image file name non-overidding with timestamp
This commit is contained in:
parent
1275a74506
commit
ccb5a2ed5e
@ -3,8 +3,10 @@
|
|||||||
namespace App\Http\Controllers\Broker;
|
namespace App\Http\Controllers\Broker;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Broker;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
class BrokerProfileController extends Controller
|
class BrokerProfileController extends Controller
|
||||||
{
|
{
|
||||||
@ -13,10 +15,31 @@ class BrokerProfileController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function show(User $profile)
|
public function show(User $profile)
|
||||||
{
|
{
|
||||||
|
// Get the broker profile
|
||||||
|
$broker = $profile->type;
|
||||||
|
|
||||||
|
// TODO: move this to middleware
|
||||||
|
if (! $broker instanceof Broker) {
|
||||||
|
abort(403, 'This user is not a broker.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the initials from a full name (e.g. John Doe, Alex Mark, jane clerk)
|
||||||
|
* to display on profile page (e.g. JD, AM, JC).
|
||||||
|
*/
|
||||||
|
$initials = Str::of($profile->name)
|
||||||
|
->explode(' ')
|
||||||
|
->map(fn ($word) => Str::substr(ucfirst($word), 0, 1))
|
||||||
|
->join('');
|
||||||
|
|
||||||
return view('dashboards.broker.profile.show')
|
return view('dashboards.broker.profile.show')
|
||||||
->with('name', $profile->name)
|
->with('name', $profile->name)
|
||||||
->with('joinDate', $profile->created_at->format('F Y'))
|
->with('joinDate', $profile->created_at->format('F Y'))
|
||||||
->with('email', $profile->email);
|
->with('email', $profile->email)
|
||||||
|
->with('initials', $initials)
|
||||||
|
->with('verified', $broker->verified)
|
||||||
|
->with('location', $broker->location)
|
||||||
|
->with('phone', $broker->phone);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,7 +5,10 @@
|
|||||||
use App\Enums\UserStatus;
|
use App\Enums\UserStatus;
|
||||||
use App\Enums\UserTypes;
|
use App\Enums\UserTypes;
|
||||||
use App\Http\Requests\StoreRegisterdUser;
|
use App\Http\Requests\StoreRegisterdUser;
|
||||||
|
use App\Models\Broker;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class RegisteredUserController extends Controller
|
class RegisteredUserController extends Controller
|
||||||
{
|
{
|
||||||
@ -18,17 +21,30 @@ public function store(StoreRegisterdUser $request)
|
|||||||
{
|
{
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
|
|
||||||
$status = UserStatus::Active->value;
|
try {
|
||||||
|
DB::transaction(function () use ($data) {
|
||||||
|
if ($data['role'] === UserTypes::Broker->value) {
|
||||||
|
$data['status'] = UserStatus::Pending->value;
|
||||||
|
|
||||||
if ($data['role'] === UserTypes::Broker->value) {
|
// Create Broker first, then the User linked to it
|
||||||
$status = UserStatus::Pending->value;
|
$broker = Broker::create();
|
||||||
|
$broker->user()->create($data);
|
||||||
|
} else {
|
||||||
|
$data['status'] = UserStatus::Active->value;
|
||||||
|
|
||||||
|
User::create($data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return to_route('login.create')
|
||||||
|
->with('userRegistered', 'User registered successfully.');
|
||||||
|
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error('Registration Failed: '.$e->getMessage());
|
||||||
|
|
||||||
|
return back()
|
||||||
|
->withInput()
|
||||||
|
->with('error', 'Something went wrong during registration.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$data['status'] = $status;
|
|
||||||
|
|
||||||
User::create($data);
|
|
||||||
|
|
||||||
return to_route('login.create')
|
|
||||||
->with('userRegistered', 'User registered successfully.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,12 +11,11 @@ protected function casts(): array
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'verified' => 'boolean',
|
'verified' => 'boolean',
|
||||||
'active' => 'boolean',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function user(): MorphOne
|
public function user(): MorphOne
|
||||||
{
|
{
|
||||||
return $this->morphOne(User::class, 'type');
|
return $this->morphOne(User::class, 'role');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -63,10 +63,10 @@ public function deals(): HasMany
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns model of User's type
|
* Returns model of User's role type
|
||||||
*/
|
*/
|
||||||
public function type(): MorphTo
|
public function type(): MorphTo
|
||||||
{
|
{
|
||||||
return $this->morphTo();
|
return $this->morphTo('role');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,10 @@ class FileService
|
|||||||
{
|
{
|
||||||
public function upload(UploadedFile $file, string $folder, string $filename): string
|
public function upload(UploadedFile $file, string $folder, string $filename): string
|
||||||
{
|
{
|
||||||
return $file->storeAs($folder, $filename.'.'.$file->extension(), 'public');
|
// This prevents overriding of same image name
|
||||||
|
$filename = time().$filename.'.'.$file->extension();
|
||||||
|
|
||||||
|
return $file->storeAs($folder, $filename, 'public');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(?string $path): void
|
public function delete(?string $path): void
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
health: '/up',
|
health: '/up',
|
||||||
)
|
)
|
||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
//
|
$middleware->redirectGuestsTo(fn () => route('login.create'));
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
//
|
//
|
||||||
|
|||||||
@ -13,11 +13,10 @@ public function up(): void
|
|||||||
{
|
{
|
||||||
Schema::create('brokers', function (Blueprint $table) {
|
Schema::create('brokers', function (Blueprint $table) {
|
||||||
$table->id();
|
$table->id();
|
||||||
$table->text('bio');
|
$table->text('bio')->nullable();
|
||||||
$table->string('location');
|
$table->string('location')->nullable();
|
||||||
$table->string('phone')->nullable();
|
$table->string('phone')->nullable();
|
||||||
$table->boolean('verified')->default(false);
|
$table->boolean('verified')->default(false);
|
||||||
$table->boolean('active')->default(true);
|
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
$table->nullableMorphs('type');
|
$table->nullableMorphs('role');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ public function up(): void
|
|||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table) {
|
||||||
$table->dropMorphs('type');
|
$table->dropMorphs('role');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
5
resources/js/alert.js
Normal file
5
resources/js/alert.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
function removeAlert(child){
|
||||||
|
const parentAlert = child.closest('.alert');
|
||||||
|
parentAlert.style.display = "none";
|
||||||
|
}
|
||||||
|
document.removeAlert = removeAlert;
|
||||||
@ -31,6 +31,7 @@ function upload(size) {
|
|||||||
closeModalBtn.addEventListener('click', () => {
|
closeModalBtn.addEventListener('click', () => {
|
||||||
// this closes then modal and sets the preview image
|
// this closes then modal and sets the preview image
|
||||||
previewImage.src = imageUrl;
|
previewImage.src = imageUrl;
|
||||||
|
previewImage.style.display = "block";
|
||||||
modal.close();
|
modal.close();
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@ -15,15 +15,15 @@ class="bg-linear-135 h-screen from-[#EFF6FF] to-[#FCF3F8] flex flex-col justify-
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@session('error')
|
@session('error')
|
||||||
<x-ui.alert-error>
|
<x-ui.alert variant="error">
|
||||||
{{$value}}
|
{{$value}}
|
||||||
</x-ui.alert-error>
|
</x-ui.alert>
|
||||||
@endsession
|
@endsession
|
||||||
|
|
||||||
@session('userRegistered')
|
@session('userRegistered')
|
||||||
<x-ui.alert-success>
|
<x-ui.alert variant="success">
|
||||||
{{$value}}
|
{{$value}}
|
||||||
</x-ui.alert-success>
|
</x-ui.alert>
|
||||||
@endsession
|
@endsession
|
||||||
|
|
||||||
<form action="{{route('login.store')}}" method="post" class="flex flex-col space-y-5">
|
<form action="{{route('login.store')}}" method="post" class="flex flex-col space-y-5">
|
||||||
|
|||||||
@ -33,10 +33,10 @@ class="text-xs underline text-accent-601 mt-1">
|
|||||||
<x-ui.button :link="route('broker.deals.edit', $id)" class="w-full border border-accent-600/30"
|
<x-ui.button :link="route('broker.deals.edit', $id)" class="w-full border border-accent-600/30"
|
||||||
icon="pencil-square">Edit
|
icon="pencil-square">Edit
|
||||||
</x-ui.button>
|
</x-ui.button>
|
||||||
<form class="w-full" method="post" action="{{route('broker.deals.destroy', $id)}}">
|
<form class="w-full" onsubmit="return confirm('Are you sure to delete this ?')" method="post" action="{{route('broker.deals.destroy', $id)}}">
|
||||||
@csrf
|
@csrf
|
||||||
@method("DELETE")
|
@method("DELETE")
|
||||||
<x-ui.button variant="red" class="w-full" icon="trash">Delete</x-ui.button>
|
<x-ui.button variant="red" class="w-full" icon="trash" >Delete</x-ui.button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
<div class="py-2 px-4 bg-red-100 rounded-lg text-xs font-bold text-red-950">
|
|
||||||
{{$slot}}
|
|
||||||
</div>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
<div class="py-2 px-4 bg-green-100 rounded-lg text-xs font-bold text-green-950">
|
|
||||||
{{$slot}}
|
|
||||||
</div>
|
|
||||||
21
resources/views/components/ui/alert.blade.php
Normal file
21
resources/views/components/ui/alert.blade.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@props(['variant' => 'neutral'])
|
||||||
|
@php
|
||||||
|
$variants = [
|
||||||
|
'neutral' => 'bg-black text-white',
|
||||||
|
'success' => 'bg-green-100 text-green-950',
|
||||||
|
'error' => 'bg-red-100 text-red-950'
|
||||||
|
];
|
||||||
|
|
||||||
|
$variantClasses = $variants[$variant];
|
||||||
|
@endphp
|
||||||
|
<div class="alert flex items-center space-x-4 py-2 px-4 rounded-lg text-xs font-bold {{$variantClasses}} ">
|
||||||
|
<div class="flex-1">
|
||||||
|
{{$slot}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<x-ui.button-sm onclick="removeAlert(this)">
|
||||||
|
<x-heroicon-o-x-mark class="w-4"/>
|
||||||
|
</x-ui.button-sm>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@vite('resources/js/alert.js')
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<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 overflow-hidden min-h-40 bg-gray-50 relative">
|
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">
|
||||||
|
|
||||||
<img src="{{ $value }}" id="preview-image"
|
<img src="{{ $value }}" id="preview-image" alt="" onerror="this.style.display='none'"
|
||||||
class="absolute inset-0 w-full h-full object-cover opacity-60 group-hover:opacity-10 transition-opacity">
|
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"/>
|
<x-heroicon-o-arrow-up-tray class="w-8 text-accent-600/70 z-10"/>
|
||||||
@ -26,7 +26,7 @@ class="absolute inset-0 w-full h-full object-cover opacity-60 group-hover:opacit
|
|||||||
<input
|
<input
|
||||||
name="{{$name}}"
|
name="{{$name}}"
|
||||||
id="image-input"
|
id="image-input"
|
||||||
class="opacity-0 absolute w-full h-full top-0 left-0 cursor-pointer"
|
class="opacity-0 absolute w-full h-full top-0 left-0 cursor-pointer z-20"
|
||||||
type="file"
|
type="file"
|
||||||
accept="{{ $allowed ? '.' . str_replace(',', ',.', $allowed) : 'image/*' }}"
|
accept="{{ $allowed ? '.' . str_replace(',', ',.', $allowed) : 'image/*' }}"
|
||||||
onchange="upload(10)"
|
onchange="upload(10)"
|
||||||
|
|||||||
@ -4,11 +4,11 @@
|
|||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
@session('success')
|
@session('success')
|
||||||
<x-ui.alert-success>{{$value}}</x-ui.alert-success>
|
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
|
||||||
@endsession
|
@endsession
|
||||||
|
|
||||||
@session('error')
|
@session('error')
|
||||||
<x-ui.alert-error>{{$value}}</x-ui.alert-error>
|
<x-ui.alert variant="error">{{$value}}</x-ui.alert>
|
||||||
@endsession
|
@endsession
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
<div class="grid grid-cols-8 gap-6">
|
<div class="grid grid-cols-8 gap-6">
|
||||||
|
|
||||||
<div class="col-span-8 place-self-start md:col-span-1 flex items-center justify-center w-full">
|
<div class="col-span-8 place-self-start md:col-span-1 flex items-center justify-center w-full">
|
||||||
<div class="w-25 h-25 rounded-xl bg-linear-150 from-[#305afc] to-[#941dfb] text-5xl text-white flex justify-center items-center">UR
|
<div class="w-25 h-25 rounded-xl bg-linear-150 from-[#305afc] to-[#941dfb] text-5xl text-white flex justify-center items-center">
|
||||||
|
{{$initials}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -25,7 +26,7 @@
|
|||||||
<div class="">
|
<div class="">
|
||||||
<p class="text-3xl font-bold">{{$name ?? 'Name'}}</p>
|
<p class="text-3xl font-bold">{{$name ?? 'Name'}}</p>
|
||||||
<div class="flex space-x-1 mt-2">
|
<div class="flex space-x-1 mt-2">
|
||||||
<x-ui.button-sm variant="neutral">Verified Broker</x-ui.button-sm>
|
<x-ui.button-sm variant="neutral">{{$verified ? 'Verified Broker' : 'Not Verified'}}</x-ui.button-sm>
|
||||||
<x-heroicon-s-star class="ml-2 w-4 fill-amber-300"/>
|
<x-heroicon-s-star class="ml-2 w-4 fill-amber-300"/>
|
||||||
<p>4.8</p>
|
<p>4.8</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user