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\PerfumeFeature;
use App\Models\Testimonial; use App\Models\Testimonial;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Layout; use Livewire\Attributes\Layout;
use Livewire\Component; use Livewire\Component;
@ -20,6 +19,8 @@
])] ])]
class Index extends Component class Index extends Component
{ {
public array $bestSellingPerfumes = [];
public array $brands = []; public array $brands = [];
public array $perfumeFeatures = []; public array $perfumeFeatures = [];
@ -36,6 +37,23 @@ class Index extends Component
public function mount(): void 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') $this->brands = Brand::orderBy('sort_order')
->get() ->get()
->map(function (Brand $brand) { ->map(function (Brand $brand) {
@ -45,6 +63,14 @@ public function mount(): void
}) })
->toArray(); ->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') $this->perfumeFeatures = PerfumeFeature::orderBy('sort_order')
->get() ->get()
->map(fn (PerfumeFeature $perfumeFeature) => [ ->map(fn (PerfumeFeature $perfumeFeature) => [
@ -61,14 +87,6 @@ public function mount(): void
]) ])
->toArray(); ->toArray();
$this->whyChooseUs = ChooseUs::orderBy('sort_order')
->get()
->map(fn (ChooseUs $chooseUs) => [
'name' => $chooseUs->name,
'description' => $chooseUs->description,
])
->toArray();
$this->totalMembersCount = Customer::count(); $this->totalMembersCount = Customer::count();
$this->latestMembers = Customer::whereNotNull('user_id') $this->latestMembers = Customer::whereNotNull('user_id')
@ -82,23 +100,6 @@ public function mount(): void
->take(5) ->take(5)
->get() ->get()
->toArray(); ->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 public function render(): View
@ -107,16 +108,4 @@ public function render(): View
'pageTitle' => 'Beranda', '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 = [ $header = [
[ [
'title' => 'Beranda', 'title' => 'Beranda',
'type' => 'link',
'route' => 'homepage', 'route' => 'homepage',
'match' => 'homepage', 'match' => 'homepage',
], ],
[ [
'title' => 'Outlet', 'title' => 'Outlet',
'type' => 'link',
'route' => 'outlet', 'route' => 'outlet',
'match' => 'outlet', 'match' => 'outlet',
], ],
[ [
'title' => 'Produk', 'title' => 'Produk',
'type' => 'link',
'route' => 'product.index', 'route' => 'product.index',
'match' => 'product.*', 'match' => 'product.*',
], ],
[ [
'title' => 'Voucher', 'title' => 'Voucher',
'type' => 'link',
'route' => 'voucher', 'route' => 'voucher',
'match' => 'voucher', 'match' => 'voucher',
], ],
[ [
'title' => 'Artikel', 'title' => 'Artikel',
'type' => 'link',
'route' => 'article.index', 'route' => 'article.index',
'match' => 'article.*', 'match' => 'article.*',
], ],
[ [
'title' => 'FAQ', 'title' => 'FAQ',
'type' => 'link',
'route' => 'faq', 'route' => 'faq',
'match' => '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" 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"> aria-label="Main Navigation">
@foreach ($header as $item) @foreach ($header as $item)
@if ($item['type'] === 'link') <a href="{{ route($item['route']) }}"
<a href="{{ route($item['route']) }}" class="{{ request()->routeIs($item['route']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity"
class="{{ request()->routeIs($item['route']) ? 'font-bold text-home-primary' : 'hover:opacity-80' }} transition-opacity" wire:navigate>
wire:navigate> {{ $item['title'] }}
{{ $item['title'] }} </a>
</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
@endforeach @endforeach
</nav> </nav>
<section class="flex items-center lg:gap-2 gap-1 z-50"> <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"> class="flex items-center rounded-full transition-all duration-200 border border-home-foreground lg:p-0 hover:scale-110">
<div id="cart-icon" <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"> 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> </svg>
</div> </div>
<span class="ms-2 me-3 transition-all duration-200 hidden lg:block text-home-foreground">Keranjang</span> <span class="ms-2 me-3 transition-all duration-200 hidden lg:block text-home-foreground">Keranjang</span>
</button> </button> --}}
<button id="login" @guest
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"> <a href="{{ route('login') }}" id="login" wire:navigate
Masuk 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">
</button> Masuk
</a>
<button id="profile" <button id="profile-mobile" wire:navigate
class="flex items-center rounded-full transition-all duration-200 lg:p-0 lg:border-0 hover:scale-110"> 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" <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"> 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" <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"> stroke="currentColor" class="size-4 lg:size-6 transition-all duration-200">
<path stroke-linecap="round" stroke-linejoin="round" <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" /> 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> </svg>
</div> </div>
</button> </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> </section>
<nav id="mobile-menu" <nav id="mobile-menu"

View File

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