feature(search deals)
- make deals reachable - add recent search feature - add animation in profile menu - refactor blade markup of explore page
This commit is contained in:
parent
673915887c
commit
985dd967e4
30
app/Actions/AddRecentSearchAction.php
Normal file
30
app/Actions/AddRecentSearchAction.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
final readonly class AddRecentSearchAction
|
||||
{
|
||||
public function execute(User $user, array $data): void
|
||||
{
|
||||
try {
|
||||
DB::transaction(function () use ($user, $data) {
|
||||
$user->recentSearches()->updateOrcreate($data);
|
||||
$recentSearchCount = $user->recentSearches()->count();
|
||||
if ($recentSearchCount > 5) {
|
||||
$user->recentSearches()->oldest()->limit(1)->delete();
|
||||
}
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Error adding recent search',
|
||||
[
|
||||
'user_id' => $user->id,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTrace(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,61 +2,61 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Actions\AddRecentSearchAction;
|
||||
use App\Enums\ExplorePageFilters;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Requests\ExploreSearchSortRequest;
|
||||
use App\Models\Deal;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\DealCategory;
|
||||
use App\Queries\ExplorePageDealsQuery;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ExplorePageController extends Controller
|
||||
{
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
$sortBy = $request->validate([
|
||||
'sortBy' => ['nullable', 'string', Rule::in(ExplorePageFilters::values())],
|
||||
]);
|
||||
|
||||
public function __invoke(
|
||||
ExploreSearchSortRequest $request,
|
||||
ExplorePageDealsQuery $query,
|
||||
AddRecentSearchAction $addRecentSearchAction
|
||||
) {
|
||||
return view('explore')
|
||||
->with('profileLink', $this->profileLink())
|
||||
->with('deals', $this->deals(ExplorePageFilters::tryFrom($sortBy['sortBy'] ?? null)));
|
||||
->with('categories', $this->categories())
|
||||
->with('recentSearches', $this->recentSearches())
|
||||
->with('deals', $this->deals($request, $query->builder(), $addRecentSearchAction));
|
||||
}
|
||||
|
||||
protected function deals(?ExplorePageFilters $sortBy)
|
||||
protected function deals(FormRequest $request, Builder $query, AddRecentSearchAction $action): LengthAwarePaginator
|
||||
{
|
||||
$query = Deal::query()
|
||||
->select([
|
||||
'id', 'title', 'description', 'image', 'active', 'slug', 'link',
|
||||
'deal_category_id', 'user_id',
|
||||
])
|
||||
// Select additional details
|
||||
->with([
|
||||
'category:id,name',
|
||||
'broker' => function ($query) {
|
||||
$query->select('id', 'name', 'email', 'role_type', 'role_id')
|
||||
->with('type');
|
||||
},
|
||||
])
|
||||
// Select only admin-approved deals
|
||||
->withActiveDeals()
|
||||
// Check if the current user interacted with the deal
|
||||
->withCurrentUserInteractions()
|
||||
->withLikePerDeal()
|
||||
->withRedirectionPerDeal();
|
||||
// Add a search query
|
||||
if ($request->has('search') && $request->get('search') !== null) {
|
||||
$query->tap(fn ($q) => (new Deal)->search($q, $request->search));
|
||||
|
||||
// Add filters
|
||||
if ($sortBy === ExplorePageFilters::Like) {
|
||||
$query->orderBy('total_likes', 'desc');
|
||||
} elseif ($sortBy === ExplorePageFilters::Click) {
|
||||
$query->orderBy('total_redirection', 'desc');
|
||||
} else {
|
||||
$query->orderByRaw(
|
||||
'((COALESCE(total_likes, 0) * 70.0)/100.0) + ((COALESCE(total_redirection, 0) * 30.0)/100.0) DESC'
|
||||
);
|
||||
\Illuminate\Support\defer(function () use ($action, $request) {
|
||||
$action->execute($request->user(), ['query' => $request->search]);
|
||||
});
|
||||
}
|
||||
|
||||
return $query->latest()
|
||||
->paginate();
|
||||
// Add category sorting filter
|
||||
if ($request->has('category') && $request->get('category') !== null) {
|
||||
$query->tap(fn ($q) => (new Deal)->filterByCategory($q, $request->category));
|
||||
}
|
||||
|
||||
// Add sorting filters
|
||||
if ($request->has('sortBy')) {
|
||||
$query = match (ExplorePageFilters::tryFrom($request->sortBy)) {
|
||||
ExplorePageFilters::Like => $query->orderBy('total_likes', 'desc'),
|
||||
ExplorePageFilters::Click => $query->orderBy('total_redirection', 'desc'),
|
||||
default => $query->orderByRaw(
|
||||
'((COALESCE(total_likes, 0) * 70.0) / 100.0) + ((COALESCE(total_redirection, 0) * 30.0) / 100.0) DESC'
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
return $query->latest()->paginate();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,11 +65,23 @@ protected function deals(?ExplorePageFilters $sortBy)
|
||||
*
|
||||
* @return string The URL for the user's dashboard.
|
||||
*/
|
||||
protected function profileLink()
|
||||
protected function profileLink(): string
|
||||
{
|
||||
$user = Auth::user();
|
||||
if ($user->role === UserTypes::Broker->value) {
|
||||
return route('broker.profile.show', $user);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function categories(): Collection
|
||||
{
|
||||
return DealCategory::all(['id', 'name']);
|
||||
}
|
||||
|
||||
protected function recentSearches(): Collection
|
||||
{
|
||||
return Auth::user()->recentSearches()->latest()->pluck('query');
|
||||
}
|
||||
}
|
||||
|
||||
32
app/Http/Requests/ExploreSearchSortRequest.php
Normal file
32
app/Http/Requests/ExploreSearchSortRequest.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Enums\ExplorePageFilters;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ExploreSearchSortRequest extends FormRequest
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'sortBy' => ['nullable', 'string', Rule::in(ExplorePageFilters::values())],
|
||||
'search' => ['nullable', 'string', 'min:1', 'max:255'],
|
||||
'category' => ['nullable', 'exists:deal_categories,id'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\InteractionType;
|
||||
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
@ -27,18 +28,25 @@ public function interactions(): HasMany
|
||||
return $this->hasMany(Interaction::class);
|
||||
}
|
||||
|
||||
public function reports(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(Report::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get deals that are active
|
||||
* Scope a query to only include active deals
|
||||
*/
|
||||
public function scopeWithActiveDeals(Builder $query): Builder
|
||||
#[Scope]
|
||||
public function WithActiveDeals(Builder $query): Builder
|
||||
{
|
||||
return $query->where('active', true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the current user has liked or favorite the deal
|
||||
* Scope a query to determine if the current user has liked and favorite a deal
|
||||
*/
|
||||
public function scopeWithCurrentUserInteractions(Builder $query): Builder
|
||||
#[Scope]
|
||||
public function WithCurrentUserInteractions(Builder $query): Builder
|
||||
{
|
||||
return $query->withExists([
|
||||
'interactions as is_liked' => function ($query) {
|
||||
@ -52,7 +60,11 @@ public function scopeWithCurrentUserInteractions(Builder $query): Builder
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeWithLikePerDeal(Builder $query): Builder
|
||||
/**
|
||||
* Scope a query to get total like count per deal
|
||||
*/
|
||||
#[Scope]
|
||||
public function WithLikePerDeal(Builder $query): Builder
|
||||
{
|
||||
return $query->withCount([
|
||||
'interactions as total_likes' => function ($query) {
|
||||
@ -61,7 +73,11 @@ public function scopeWithLikePerDeal(Builder $query): Builder
|
||||
]);
|
||||
}
|
||||
|
||||
public function scopeWithRedirectionPerDeal(Builder $query): Builder
|
||||
/**
|
||||
* Scope a query to get click count per deal
|
||||
*/
|
||||
#[Scope]
|
||||
public function WithRedirectionPerDeal(Builder $query): Builder
|
||||
{
|
||||
return $query->withSum([
|
||||
'interactions as total_redirection' => function ($query) {
|
||||
@ -70,8 +86,21 @@ public function scopeWithRedirectionPerDeal(Builder $query): Builder
|
||||
], 'count');
|
||||
}
|
||||
|
||||
public function reports(): BelongsToMany
|
||||
/**
|
||||
* Scope a search in a query
|
||||
*/
|
||||
#[Scope]
|
||||
public function search(Builder $query, string $search): Builder
|
||||
{
|
||||
return $this->belongsToMany(Report::class);
|
||||
return $query->where('title', 'LIKE', "%$search%");
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope a category filter in a query
|
||||
*/
|
||||
#[Scope]
|
||||
public function filterByCategory(Builder $query, string $category): Builder
|
||||
{
|
||||
return $query->where('deal_category_id', $category);
|
||||
}
|
||||
}
|
||||
|
||||
16
app/Models/RecentSearch.php
Normal file
16
app/Models/RecentSearch.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class RecentSearch extends Model
|
||||
{
|
||||
protected $fillable = ['query'];
|
||||
|
||||
public function user(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@ -73,4 +73,9 @@ public function interactions(): HasMany
|
||||
{
|
||||
return $this->hasMany(User::class);
|
||||
}
|
||||
|
||||
public function recentSearches(): HasMany
|
||||
{
|
||||
return $this->hasMany(RecentSearch::class);
|
||||
}
|
||||
}
|
||||
|
||||
35
app/Queries/ExplorePageDealsQuery.php
Normal file
35
app/Queries/ExplorePageDealsQuery.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Queries;
|
||||
|
||||
use App\Models\Deal;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
final readonly class ExplorePageDealsQuery
|
||||
{
|
||||
/**
|
||||
* @return Builder<Deal>
|
||||
*/
|
||||
public function builder(): Builder
|
||||
{
|
||||
return Deal::query()
|
||||
->select([
|
||||
'id', 'title', 'description', 'image', 'active', 'slug', 'link',
|
||||
'deal_category_id', 'user_id',
|
||||
])
|
||||
// Select additional details
|
||||
->with([
|
||||
'category:id,name',
|
||||
'broker' => function ($query) {
|
||||
$query->select('id', 'name', 'email', 'role_type', 'role_id')
|
||||
->with('type');
|
||||
},
|
||||
])
|
||||
// Select only admin-approved deals
|
||||
->tap(fn ($q) => (new Deal)->withActiveDeals($q))
|
||||
// Check if the current user interacted with the deal
|
||||
->tap(fn ($q) => (new Deal)->withCurrentUserInteractions($q))
|
||||
->tap(fn ($q) => (new Deal)->withLikePerDeal($q))
|
||||
->tap(fn ($q) => (new Deal)->withRedirectionPerDeal($q));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('recent_searches', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(User::class);
|
||||
$table->string('query');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('recent_searches');
|
||||
}
|
||||
};
|
||||
@ -1,6 +1,7 @@
|
||||
function showMenu(e){
|
||||
const menu = e.nextElementSibling;
|
||||
menu.classList.toggle('invisible');
|
||||
menu.classList.toggle('opacity-100');
|
||||
menu.classList.toggle('scale-100');
|
||||
}
|
||||
|
||||
document.showMenu = showMenu;
|
||||
|
||||
@ -3,6 +3,6 @@
|
||||
@forelse($deals as $deal)
|
||||
<x-dashboard.user.listing-card :deal="$deal" :broker="$deal->broker"/>
|
||||
@empty
|
||||
<p class="col-span-2 text-sm text-center text-accent-600">No Deals found till now !</p>
|
||||
<p class="col-span-2 text-sm text-center text-accent-600 mt-12">No Deals found !</p>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
@props(['searches'])
|
||||
<ol class="absolute bg-white shadow-xl border border-gray-300 -top-15 w-full rounded-xl py-4 px-4 flex flex-col opacity-0 scale-y-17 group-focus-within:scale-y-100 group-focus-within:top-15 group-focus-within:opacity-100 transition-all duration-300 ease-out">
|
||||
@foreach($searches as $search)
|
||||
<x-dashboard.user.recent-search.recent-search-item :item="$search" />
|
||||
@endforeach
|
||||
</ol>
|
||||
@ -0,0 +1,7 @@
|
||||
@props(['item'])
|
||||
<li>
|
||||
<a class="p-2 md:p-4 flex items-center text-gray-600 hover:bg-gray-100 hover:font-bold hover:text-gray-900 rounded-xl" href="{{route('explore', ['search' => $item])}}">
|
||||
{{$item}}
|
||||
<x-heroicon-o-arrow-up-right class="w-3 ml-2" />
|
||||
</a>
|
||||
</li>
|
||||
37
resources/views/components/explore/profile-menu.blade.php
Normal file
37
resources/views/components/explore/profile-menu.blade.php
Normal file
@ -0,0 +1,37 @@
|
||||
@props(['profileLink' => ''])
|
||||
<div class="flex items-center">
|
||||
<div class="relative group">
|
||||
<x-ui.button icon="user-circle" class="cursor-pointer" onclick="showMenu(this)"></x-ui.button>
|
||||
<ul class="menu opacity-0 z-10 scale-10 group-hover:scale-100 group-hover:opacity-100 transition-all duration-300 ease-in-out w-48 absolute right-0 bg-white border border-gray-300 rounded-md shadow-xl py-2 text-accent-600">
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{$profileLink}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
<x-heroicon-o-user class="w-4"/>
|
||||
</div>
|
||||
<p>Profile</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@if(auth()->user()->role === \App\Enums\UserTypes::Broker->value)
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('broker.dashboard')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
<x-heroicon-o-adjustments-horizontal class="w-4"/>
|
||||
</div>
|
||||
<p>Control Panel</p>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<form method="post" action="{{route('logout')}}">
|
||||
@csrf
|
||||
@method('delete')
|
||||
<x-ui.button
|
||||
class="flex space-x-3 hover:bg-red-50 hover:border-red-100 hover:text-red-500 border border-white">
|
||||
<x-heroicon-o-arrow-right-start-on-rectangle class="w-4 stroke-2 mr-2"/>
|
||||
<p class="hidden sm:block">Logout</p>
|
||||
</x-ui.button>
|
||||
</form>
|
||||
</div>
|
||||
14
resources/views/components/explore/search.blade.php
Normal file
14
resources/views/components/explore/search.blade.php
Normal file
@ -0,0 +1,14 @@
|
||||
@props(['recentSearches' => [], 'categories' => []])
|
||||
<x-dashboard.card>
|
||||
<form action="{{route('explore')}}" method="get">
|
||||
<div class="flex gap-4 flex-col sm:flex-row">
|
||||
<x-ui.input class="flex-1 relative group " name="search"
|
||||
placeholder="Search deals, services, places">
|
||||
<x-dashboard.user.recent-search :searches="$recentSearches"/>
|
||||
</x-ui.input>
|
||||
<x-ui.select name="category" :options="$categories" value-key="id" label-key="name"
|
||||
placeholder="All Categories" selectable-placeholder/>
|
||||
<x-ui.button icon="magnifying-glass" type="submit" variant="neutral" round/>
|
||||
</div>
|
||||
</form>
|
||||
</x-dashboard.card>
|
||||
22
resources/views/components/explore/toggle-buttons.blade.php
Normal file
22
resources/views/components/explore/toggle-buttons.blade.php
Normal file
@ -0,0 +1,22 @@
|
||||
<x-ui.toggle-button-group>
|
||||
<x-ui.toggle-button :active="request('sortBy') == null">
|
||||
<a href="{{route('explore')}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-clock class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">All Deals</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
|
||||
<x-ui.toggle-button :active="request('sortBy') === \App\Enums\ExplorePageFilters::Like->value">
|
||||
<a href="{{route('explore', ['sortBy' => 'like'])}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-arrow-trending-up class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">Most Liked</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
|
||||
<x-ui.toggle-button :active="request('sortBy') === \App\Enums\ExplorePageFilters::Click->value">
|
||||
<a href="{{route('explore', ['sortBy'=>'click'])}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-star class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">Most Clicked</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
</x-ui.toggle-button-group>
|
||||
@ -1,4 +1,4 @@
|
||||
@props(['variant' => '', 'icon' => '', 'link' => '', 'external' => false])
|
||||
@props(['variant' => '', 'icon' => '', 'link' => '', 'external' => false, 'round' => false])
|
||||
@php
|
||||
$variants = [
|
||||
'neutral' => 'bg-primary-600 text-white',
|
||||
@ -7,13 +7,18 @@
|
||||
];
|
||||
|
||||
$variantClass = $variants[$variant] ?? '';
|
||||
$roundedClass = $round ? ' rounded-full p-3' : ' rounded-lg magnifying-glass px-4 py-2 ';
|
||||
$variantClass.= $roundedClass;
|
||||
@endphp
|
||||
@if($link !== '')
|
||||
<a
|
||||
@if($external)
|
||||
target="_blank"
|
||||
@endif
|
||||
{{$attributes->merge(['class' => "block px-4 py-2 rounded-lg font-medium hover:opacity-80 active:scale-80 transition-all ease-in-out duration-300 $variantClass", 'href' => $link])}}>
|
||||
{{$attributes->merge([
|
||||
'class' => "block px-4 py-2 font-medium hover:opacity-80 active:scale-80 transition-all ease-in-out duration-300 $variantClass",
|
||||
'href' => $link]
|
||||
)}}>
|
||||
<div class="flex justify-center items-center space-x-2">
|
||||
@if($icon !=='')
|
||||
@svg("heroicon-o-$icon", 'w-5 h-5')
|
||||
@ -22,13 +27,14 @@
|
||||
</div>
|
||||
</a>
|
||||
@else
|
||||
<button {{$attributes->merge(['class' => "px-4 py-2 rounded-lg font-medium hover:opacity-80 active:scale-80 transition-all ease-in-out duration-300 $variantClass", 'type'=>'submit'])}}>
|
||||
<button {{$attributes->merge(['class' => "font-medium hover:opacity-80 active:scale-80 transition-all ease-in-out duration-300 $variantClass", 'type'=>'submit'])}}>
|
||||
<div class="flex justify-center items-center space-x-2">
|
||||
@if($icon !=='')
|
||||
@svg("heroicon-o-$icon", 'w-5 h-5')
|
||||
@endif
|
||||
|
||||
<p>{{$slot}}</p>
|
||||
@if(filled($slot))
|
||||
<p>{{$slot}}</p>
|
||||
@endif
|
||||
</div>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
@props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text', 'description' => '', 'required' => false, 'value' => ''])
|
||||
<div {{$attributes->merge(['class' => 'flex flex-col space-y-2'])}}>
|
||||
<div {{$attributes->merge(['class' => 'flex flex-col'])}}>
|
||||
@if($label !== '')
|
||||
<label class="text-sm font-bold" for="{{$name}}">
|
||||
<label class="text-sm font-bold mb-2" for="{{$name}}">
|
||||
{{$label}}
|
||||
@if($required)
|
||||
*
|
||||
@ -10,10 +10,11 @@
|
||||
@endif
|
||||
<input class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
||||
type="{{$type}}" placeholder="{{$placeholder}}"
|
||||
name="{{$name}}" value="{{old($name, $value)}}"
|
||||
{{$required?'required':''}}
|
||||
name="{{$name}}" value="{{old($name, request($name, $value))}}"
|
||||
{{$required?'required':''}}
|
||||
{{$attributes}}
|
||||
>
|
||||
{{$slot}}
|
||||
@if($description !== '')
|
||||
<p class="text-accent-600 text-xs">{{$description}}</p>
|
||||
@endif
|
||||
|
||||
@ -6,7 +6,8 @@
|
||||
'valueKey' => 'value',
|
||||
'label' => '',
|
||||
'required' => false,
|
||||
'selected' => ''
|
||||
'selected' => '',
|
||||
'selectablePlaceholder' => false
|
||||
]
|
||||
)
|
||||
<div class="flex flex-col space-y-2">
|
||||
@ -26,12 +27,14 @@
|
||||
class="bg-[#F3F3F5] py-2 px-4 rounded-lg text-sm font-bold invalid:text-accent-600 text-black h-full"
|
||||
>
|
||||
@if($placeholder !== '')
|
||||
<option {{old($name) === '' || $selected === '' ? 'selected' : ''}} disabled>{{$placeholder}}</option>
|
||||
<option
|
||||
value=" "
|
||||
{{old($name, request($name, $selected)) === '' || $selected === '' ? 'selected' : ''}} {{$selectablePlaceholder ? '' : 'disabled'}} >{{$placeholder}}</option>
|
||||
@endif
|
||||
|
||||
@foreach($options as $option)
|
||||
<option
|
||||
value="{{$option[$valueKey]}}" {{$option[$valueKey] == old($name, $selected) ? 'selected' : ''}}> {{$option[$labelKey]}} </option>
|
||||
value="{{$option[$valueKey]}}" {{$option[$valueKey] == old($name, request($name, $selected)) ? 'selected' : ''}}> {{$option[$labelKey]}} </option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
@props(['activeColor' => 'bg-white'])
|
||||
<div {{$attributes->merge(['class' => 'flex bg-[#ececf0] p-1 rounded-full w-fit'])}}>
|
||||
<div {{$attributes->merge(['class' => 'flex gap-2 bg-[#ececf0] p-1 rounded-full w-fit'])}}>
|
||||
{{$slot}}
|
||||
</div>
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
@aware(['activeColor' => 'bg-white'])
|
||||
<div
|
||||
{{$attributes}}
|
||||
{{$attributes->class(["rounded-full", $activeColor => $active])}}
|
||||
{{$attributes->class(["rounded-full hover:bg-gray-100 hover:border-gray-300 border border-transparent transition-colors duration-300 ease-in-out", $activeColor => $active])}}
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@ -1,51 +1,15 @@
|
||||
@php
|
||||
$categories = [0 => ['name' => 'All Categories', 'value' => '0']];
|
||||
@endphp
|
||||
<x-layout title="Deals">
|
||||
<x-dashboard.page-heading
|
||||
title="Explore Deals"
|
||||
description="Discover trusted recommendation"
|
||||
>
|
||||
<x-slot:end>
|
||||
<div class="flex items-center">
|
||||
<div class="relative group">
|
||||
<x-ui.button icon="user-circle" class="cursor-pointer" onclick="showMenu(this)"></x-ui.button>
|
||||
<ul class="menu invisible group-hover:visible transition-all duration-300 ease-in-out w-48 absolute right-0 bg-white border border-gray-300 rounded-md shadow-xl py-2 text-accent-600">
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{$profileLink}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
<x-heroicon-o-user class="w-4"/>
|
||||
</div>
|
||||
<p>Profile</p>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@if(auth()->user()->role === \App\Enums\UserTypes::Broker->value)
|
||||
<li class="py-2 px-4 hover:bg-gray-100 hover:text-gray-900 hover:cursor-pointer hover:font-bold">
|
||||
<a href="{{route('broker.dashboard')}}" class="flex space-x-4">
|
||||
<div class="p-1 bg-gray-200 rounded-xl text-gray-900">
|
||||
<x-heroicon-o-adjustments-horizontal class="w-4"/>
|
||||
</div>
|
||||
<p>Control Panel</p>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
<form method="post" action="{{route('logout')}}">
|
||||
@csrf
|
||||
@method('delete')
|
||||
<x-ui.button class="flex space-x-3 hover:bg-red-50 hover:border-red-100 hover:text-red-500 border border-white">
|
||||
<x-heroicon-o-arrow-right-start-on-rectangle class="w-4 stroke-2 mr-2"/>
|
||||
<p class="hidden sm:block">Logout</p>
|
||||
</x-ui.button>
|
||||
</form>
|
||||
</div>
|
||||
<x-explore.profile-menu :profile-link="$profileLink"/>
|
||||
</x-slot:end>
|
||||
</x-dashboard.page-heading>
|
||||
<section class="flex flex-col space-y-8 bg-[#F9FAFB] wrapper mt-2 pb-6">
|
||||
|
||||
<!-- Session messages -->
|
||||
<div>
|
||||
@session('success')
|
||||
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
|
||||
@ -56,39 +20,14 @@
|
||||
@endsession
|
||||
</div>
|
||||
|
||||
<x-dashboard.card>
|
||||
<div class="flex gap-4 flex-col sm:flex-row">
|
||||
<x-ui.input class="flex-1" name="search" placeholder="Search deals, services, places"/>
|
||||
<x-ui.select name="category" :options="$categories" value-key="value" label-key="name"/>
|
||||
</div>
|
||||
</x-dashboard.card>
|
||||
<x-explore.search :recent-searches="$recentSearches" :categories="$categories" />
|
||||
|
||||
<x-ui.toggle-button-group>
|
||||
<x-ui.toggle-button :active="request('sortBy') == null">
|
||||
<a href="{{route('explore')}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-clock class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">All Deals</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
|
||||
<x-ui.toggle-button :active="request('sortBy') === \App\Enums\ExplorePageFilters::Like->value" >
|
||||
<a href="{{route('explore', ['sortBy' => 'like'])}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-arrow-trending-up class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">Most Liked</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
|
||||
<x-ui.toggle-button :active="request('sortBy') === \App\Enums\ExplorePageFilters::Click->value" >
|
||||
<a href="{{route('explore', ['sortBy'=>'click'])}}" class="flex items-center px-2 py-1 space-x-2">
|
||||
<x-heroicon-o-star class="w-4 stroke-2"/>
|
||||
<p class="font-bold text-xs sm:text-sm md:text-md">Most Clicked</p>
|
||||
</a>
|
||||
</x-ui.toggle-button>
|
||||
</x-ui.toggle-button-group>
|
||||
<!-- Sorting buttons -->
|
||||
<x-explore.toggle-buttons />
|
||||
|
||||
<x-dashboard.user.listing :deals="$deals"/>
|
||||
<x-dashboard.user.report-modal />
|
||||
<x-dashboard.user.report-modal/>
|
||||
</section>
|
||||
<x-ui.toast />
|
||||
<x-ui.toast/>
|
||||
@vite(['resources/js/menu.js', 'resources/js/interaction.js', 'resources/js/report-deal.js', 'resources/js/toast.js'])
|
||||
</x-layout>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user