feat: Implement role-based profile redirection, simplify header navigation, and refactor homepage data loading.

This commit is contained in:
Yoga Pangestu 2025-12-20 14:17:18 +07:00
parent 2c84184bf9
commit 72750090aa
6 changed files with 120 additions and 93 deletions

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers;
use App\Traits\Components\WithRoleRedirect;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
class ProfileRedirectController extends Controller
{
use WithRoleRedirect;
/**
* Handle the incoming request.
*/
public function __invoke(Request $request): RedirectResponse
{
return $this->goToProfile();
}
}

View File

@ -10,7 +10,6 @@
use App\Models\PerfumeFeature;
use App\Models\Testimonial;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Layout;
use Livewire\Component;
@ -20,6 +19,8 @@
])]
class Index extends Component
{
public array $bestSellingPerfumes = [];
public array $brands = [];
public array $perfumeFeatures = [];
@ -36,6 +37,23 @@ class Index extends Component
public function mount(): void
{
$this->bestSellingPerfumes = Perfume::withSum(['items' => function ($query) {
$query->whereHas('order', function ($q) {
$q->paid();
});
}], 'quantity')
->orderByDesc('items_sum_quantity')
->take(2)
->get()
->map(function (Perfume $perfume) {
$perfume->image = $perfume->getFirstMediaUrl('image') ? Storage::url($perfume->getFirstMediaUrl('image')) : asset('assets/images/logo.png');
$perfume->concentration_label = $perfume->concentration->label();
return $perfume;
})
->filter(fn (Perfume $perfume) => $perfume->items_sum_quantity > 0)
->toArray();
$this->brands = Brand::orderBy('sort_order')
->get()
->map(function (Brand $brand) {
@ -45,6 +63,14 @@ public function mount(): void
})
->toArray();
$this->whyChooseUs = ChooseUs::orderBy('sort_order')
->get()
->map(fn (ChooseUs $chooseUs) => [
'name' => $chooseUs->name,
'description' => $chooseUs->description,
])
->toArray();
$this->perfumeFeatures = PerfumeFeature::orderBy('sort_order')
->get()
->map(fn (PerfumeFeature $perfumeFeature) => [
@ -61,14 +87,6 @@ public function mount(): void
])
->toArray();
$this->whyChooseUs = ChooseUs::orderBy('sort_order')
->get()
->map(fn (ChooseUs $chooseUs) => [
'name' => $chooseUs->name,
'description' => $chooseUs->description,
])
->toArray();
$this->totalMembersCount = Customer::count();
$this->latestMembers = Customer::whereNotNull('user_id')
@ -82,23 +100,6 @@ public function mount(): void
->take(5)
->get()
->toArray();
$this->bestSellingPerfumes = Perfume::withSum(['items' => function ($query) {
$query->whereHas('order', function ($q) {
$q->paid();
});
}], 'quantity')
->orderByDesc('items_sum_quantity')
->take(2)
->get()
->map(function (Perfume $perfume) {
$perfume->image = $perfume->getFirstMediaUrl('image') ? Storage::url($perfume->getFirstMediaUrl('image')) : asset('assets/images/logo.png');
$perfume->concentration_label = $perfume->concentration->label();
return $perfume;
})
->filter(fn (Perfume $perfume) => $perfume->items_sum_quantity > 0)
->toArray();
}
public function render(): View
@ -107,16 +108,4 @@ public function render(): View
'pageTitle' => 'Beranda',
]);
}
public function goToProduct(): RedirectResponse
{
return redirect('/product');
}
public function goToVoucher(): RedirectResponse
{
return redirect('/voucher');
}
public $bestSellingPerfumes = [];
}

View File

@ -293,37 +293,31 @@ public function boot(): void
$header = [
[
'title' => 'Beranda',
'type' => 'link',
'route' => 'homepage',
'match' => 'homepage',
],
[
'title' => 'Outlet',
'type' => 'link',
'route' => 'outlet',
'match' => 'outlet',
],
[
'title' => 'Produk',
'type' => 'link',
'route' => 'product.index',
'match' => 'product.*',
],
[
'title' => 'Voucher',
'type' => 'link',
'route' => 'voucher',
'match' => 'voucher',
],
[
'title' => 'Artikel',
'type' => 'link',
'route' => 'article.index',
'match' => 'article.*',
],
[
'title' => 'FAQ',
'type' => 'link',
'route' => 'faq',
'match' => 'faq',
],

View File

@ -0,0 +1,32 @@
<?php
namespace App\Traits\Components;
use Illuminate\Http\RedirectResponse;
trait WithRoleRedirect
{
public function goToProfile(): RedirectResponse
{
if (! auth()->check()) {
return redirect('/login');
}
$user = auth()->user();
$roles = $user->roles->pluck('name')->toArray();
if (array_intersect($roles, ['Developer', 'Owner', 'Leader', 'Admin'])) {
return redirect('/studio/dashboard/overview');
}
if (in_array('Partner', $roles, true)) {
return redirect('/partner/overview');
}
if (in_array('Customer', $roles, true)) {
return redirect('/member/overview');
}
return redirect('/login');
}
}

View File

@ -28,43 +28,17 @@ class="w-10 h-8 lg:w-16 lg:h-12 transition-all duration-200">
class="hidden lg:flex items-center gap-6 font-medium px-8 py-3 rounded-full transition-all duration-200 border text-home-foreground"
aria-label="Main Navigation">
@foreach ($header as $item)
@if ($item['type'] === 'link')
<a href="{{ route($item['route']) }}"
class="{{ request()->routeIs($item['route']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity"
wire:navigate>
{{ $item['title'] }}
</a>
@endif
@if ($item['type'] === 'dropdown')
<div class="relative group">
<button
class="flex items-center gap-2 hover:opacity-80 transition-opacity {{ request()->routeIs($item['match']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }}">
<span>{{ $item['title'] }}</span>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor"
class="size-4 group-hover:rotate-180 transition-transform duration-300">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</button>
<div class="absolute left-0 top-full hidden group-hover:block pt-2 w-40">
<div class="bg-white/95 backdrop-blur-md shadow-lg rounded-lg py-2 border border-gray-200">
@foreach ($item['items'] as $sub)
<a href="{{ route($sub['route']) }}" wire:navigate
class="{{ request()->routeIs($sub['match']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} block px-4 py-2 hover:bg-gray-50 transition-colors">
{{ $sub['title'] }}
</a>
@endforeach
</div>
</div>
</div>
@endif
<a href="{{ route($item['route']) }}"
class="{{ request()->routeIs($item['route']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity"
wire:navigate>
{{ $item['title'] }}
</a>
@endforeach
</nav>
<section class="flex items-center lg:gap-2 gap-1 z-50">
<button id="cart"
{{-- not used yet --}}
{{-- <button id="cart"
class="flex items-center rounded-full transition-all duration-200 border border-home-foreground lg:p-0 hover:scale-110">
<div id="cart-icon"
class="w-8 h-8 lg:w-10 lg:h-10 flex justify-center items-center rounded-full transition-all duration-200 text-white bg-home-foreground">
@ -75,24 +49,40 @@ class="w-8 h-8 lg:w-10 lg:h-10 flex justify-center items-center rounded-full tra
</svg>
</div>
<span class="ms-2 me-3 transition-all duration-200 hidden lg:block text-home-foreground">Keranjang</span>
</button>
</button> --}}
<button id="login"
class="hidden lg:flex items-center border px-4 py-2 rounded-full transition-all duration-200 hover:scale-105 font-medium border-home-foreground text-home-foreground">
Masuk
</button>
@guest
<a href="{{ route('login') }}" id="login" wire:navigate
class="hidden lg:flex items-center border px-4 py-2 rounded-full transition-all duration-200 hover:scale-105 font-medium border-home-foreground text-home-foreground">
Masuk
</a>
<button id="profile"
class="flex items-center rounded-full transition-all duration-200 lg:p-0 lg:border-0 hover:scale-110">
<div id="profile-icon"
class="w-8 h-8 lg:w-10 lg:h-10 flex justify-center items-center rounded-full transition-all duration-200 text-white bg-home-foreground">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="size-4 lg:size-6 transition-all duration-200" id="profile-svg">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
</button>
<button id="profile-mobile" wire:navigate
class="flex lg:hidden items-center rounded-full transition-all duration-200 lg:p-0 lg:border-0 hover:scale-110">
<div id="profile-icon-mobile"
class="w-8 h-8 lg:w-10 lg:h-10 flex justify-center items-center rounded-full transition-all duration-200 text-white bg-home-foreground">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="size-4 lg:size-6 transition-all duration-200">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
</button>
@endguest
@auth
<a href="{{ route('profile.redirect') }}" id="profile"
class="flex items-center rounded-full transition-all duration-200 lg:p-0 lg:border-0 hover:scale-110">
<div id="profile-icon"
class="w-8 h-8 lg:w-10 lg:h-10 flex justify-center items-center rounded-full transition-all duration-200 text-white bg-home-foreground">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
stroke="currentColor" class="size-4 lg:size-6 transition-all duration-200" id="profile-svg">
<path stroke-linecap="round" stroke-linejoin="round"
d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
</a>
@endauth
</section>
<nav id="mobile-menu"

View File

@ -27,3 +27,5 @@
Route::get('/faq', FaqComponent::class)->name('faq');
Route::get('/cart', CartComponent::class)->name('cart');
Route::get('/profile/redirect', \App\Http\Controllers\ProfileRedirectController::class)->name('profile.redirect');