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:
kusowl 2026-01-14 12:19:20 +05:30
parent 1275a74506
commit ccb5a2ed5e
18 changed files with 102 additions and 40 deletions

View File

@ -3,8 +3,10 @@
namespace App\Http\Controllers\Broker;
use App\Http\Controllers\Controller;
use App\Models\Broker;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class BrokerProfileController extends Controller
{
@ -13,10 +15,31 @@ class BrokerProfileController extends Controller
*/
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')
->with('name', $profile->name)
->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);
}
/**

View File

@ -5,7 +5,10 @@
use App\Enums\UserStatus;
use App\Enums\UserTypes;
use App\Http\Requests\StoreRegisterdUser;
use App\Models\Broker;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class RegisteredUserController extends Controller
{
@ -18,17 +21,30 @@ public function store(StoreRegisterdUser $request)
{
$data = $request->validated();
$status = UserStatus::Active->value;
try {
DB::transaction(function () use ($data) {
if ($data['role'] === UserTypes::Broker->value) {
$status = UserStatus::Pending->value;
}
$data['status'] = UserStatus::Pending->value;
$data['status'] = $status;
// Create Broker first, then the User linked to it
$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.');
}
}
}

View File

@ -11,12 +11,11 @@ protected function casts(): array
{
return [
'verified' => 'boolean',
'active' => 'boolean',
];
}
public function user(): MorphOne
{
return $this->morphOne(User::class, 'type');
return $this->morphOne(User::class, 'role');
}
}

View File

@ -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
{
return $this->morphTo();
return $this->morphTo('role');
}
}

View File

@ -9,7 +9,10 @@ class FileService
{
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

View File

@ -11,7 +11,7 @@
health: '/up',
)
->withMiddleware(function (Middleware $middleware): void {
//
$middleware->redirectGuestsTo(fn () => route('login.create'));
})
->withExceptions(function (Exceptions $exceptions): void {
//

View File

@ -13,11 +13,10 @@ public function up(): void
{
Schema::create('brokers', function (Blueprint $table) {
$table->id();
$table->text('bio');
$table->string('location');
$table->text('bio')->nullable();
$table->string('location')->nullable();
$table->string('phone')->nullable();
$table->boolean('verified')->default(false);
$table->boolean('active')->default(true);
$table->timestamps();
});
}

View File

@ -12,7 +12,7 @@
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->nullableMorphs('type');
$table->nullableMorphs('role');
});
}
@ -22,7 +22,7 @@ public function up(): void
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropMorphs('type');
$table->dropMorphs('role');
});
}
};

5
resources/js/alert.js Normal file
View File

@ -0,0 +1,5 @@
function removeAlert(child){
const parentAlert = child.closest('.alert');
parentAlert.style.display = "none";
}
document.removeAlert = removeAlert;

View File

@ -31,6 +31,7 @@ function upload(size) {
closeModalBtn.addEventListener('click', () => {
// this closes then modal and sets the preview image
previewImage.src = imageUrl;
previewImage.style.display = "block";
modal.close();
})

View File

@ -15,15 +15,15 @@ class="bg-linear-135 h-screen from-[#EFF6FF] to-[#FCF3F8] flex flex-col justify-
</div>
@session('error')
<x-ui.alert-error>
<x-ui.alert variant="error">
{{$value}}
</x-ui.alert-error>
</x-ui.alert>
@endsession
@session('userRegistered')
<x-ui.alert-success>
<x-ui.alert variant="success">
{{$value}}
</x-ui.alert-success>
</x-ui.alert>
@endsession
<form action="{{route('login.store')}}" method="post" class="flex flex-col space-y-5">

View File

@ -33,7 +33,7 @@ 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"
icon="pencil-square">Edit
</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
@method("DELETE")
<x-ui.button variant="red" class="w-full" icon="trash" >Delete</x-ui.button>

View File

@ -1,3 +0,0 @@
<div class="py-2 px-4 bg-red-100 rounded-lg text-xs font-bold text-red-950">
{{$slot}}
</div>

View File

@ -1,3 +0,0 @@
<div class="py-2 px-4 bg-green-100 rounded-lg text-xs font-bold text-green-950">
{{$slot}}
</div>

View 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')

View File

@ -13,7 +13,7 @@
<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">
<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">
<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
name="{{$name}}"
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"
accept="{{ $allowed ? '.' . str_replace(',', ',.', $allowed) : 'image/*' }}"
onchange="upload(10)"

View File

@ -4,11 +4,11 @@
<div class="wrapper">
@session('success')
<x-ui.alert-success>{{$value}}</x-ui.alert-success>
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
@endsession
@session('error')
<x-ui.alert-error>{{$value}}</x-ui.alert-error>
<x-ui.alert variant="error">{{$value}}</x-ui.alert>
@endsession
</div>

View File

@ -16,7 +16,8 @@
<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="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>
@ -25,7 +26,7 @@
<div class="">
<p class="text-3xl font-bold">{{$name ?? 'Name'}}</p>
<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"/>
<p>4.8</p>
</div>