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;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Actions\AddRecentSearchAction;
|
||||||
use App\Enums\ExplorePageFilters;
|
use App\Enums\ExplorePageFilters;
|
||||||
use App\Enums\UserTypes;
|
use App\Enums\UserTypes;
|
||||||
|
use App\Http\Requests\ExploreSearchSortRequest;
|
||||||
use App\Models\Deal;
|
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\Support\Facades\Auth;
|
||||||
use Illuminate\Validation\Rule;
|
|
||||||
|
|
||||||
class ExplorePageController extends Controller
|
class ExplorePageController extends Controller
|
||||||
{
|
{
|
||||||
public function __invoke(Request $request)
|
public function __invoke(
|
||||||
{
|
ExploreSearchSortRequest $request,
|
||||||
$sortBy = $request->validate([
|
ExplorePageDealsQuery $query,
|
||||||
'sortBy' => ['nullable', 'string', Rule::in(ExplorePageFilters::values())],
|
AddRecentSearchAction $addRecentSearchAction
|
||||||
]);
|
) {
|
||||||
|
|
||||||
return view('explore')
|
return view('explore')
|
||||||
->with('profileLink', $this->profileLink())
|
->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()
|
// Add a search query
|
||||||
->select([
|
if ($request->has('search') && $request->get('search') !== null) {
|
||||||
'id', 'title', 'description', 'image', 'active', 'slug', 'link',
|
$query->tap(fn ($q) => (new Deal)->search($q, $request->search));
|
||||||
'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 filters
|
\Illuminate\Support\defer(function () use ($action, $request) {
|
||||||
if ($sortBy === ExplorePageFilters::Like) {
|
$action->execute($request->user(), ['query' => $request->search]);
|
||||||
$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'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $query->latest()
|
// Add category sorting filter
|
||||||
->paginate();
|
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.
|
* @return string The URL for the user's dashboard.
|
||||||
*/
|
*/
|
||||||
protected function profileLink()
|
protected function profileLink(): string
|
||||||
{
|
{
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
if ($user->role === UserTypes::Broker->value) {
|
if ($user->role === UserTypes::Broker->value) {
|
||||||
return route('broker.profile.show', $user);
|
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;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\InteractionType;
|
use App\Enums\InteractionType;
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Scope;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
@ -27,18 +28,25 @@ public function interactions(): HasMany
|
|||||||
return $this->hasMany(Interaction::class);
|
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);
|
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([
|
return $query->withExists([
|
||||||
'interactions as is_liked' => function ($query) {
|
'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([
|
return $query->withCount([
|
||||||
'interactions as total_likes' => function ($query) {
|
'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([
|
return $query->withSum([
|
||||||
'interactions as total_redirection' => function ($query) {
|
'interactions as total_redirection' => function ($query) {
|
||||||
@ -70,8 +86,21 @@ public function scopeWithRedirectionPerDeal(Builder $query): Builder
|
|||||||
], 'count');
|
], '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);
|
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){
|
function showMenu(e){
|
||||||
const menu = e.nextElementSibling;
|
const menu = e.nextElementSibling;
|
||||||
menu.classList.toggle('invisible');
|
menu.classList.toggle('opacity-100');
|
||||||
|
menu.classList.toggle('scale-100');
|
||||||
}
|
}
|
||||||
|
|
||||||
document.showMenu = showMenu;
|
document.showMenu = showMenu;
|
||||||
|
|||||||
@ -3,6 +3,6 @@
|
|||||||
@forelse($deals as $deal)
|
@forelse($deals as $deal)
|
||||||
<x-dashboard.user.listing-card :deal="$deal" :broker="$deal->broker"/>
|
<x-dashboard.user.listing-card :deal="$deal" :broker="$deal->broker"/>
|
||||||
@empty
|
@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
|
@endforelse
|
||||||
</div>
|
</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
|
@php
|
||||||
$variants = [
|
$variants = [
|
||||||
'neutral' => 'bg-primary-600 text-white',
|
'neutral' => 'bg-primary-600 text-white',
|
||||||
@ -7,13 +7,18 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
$variantClass = $variants[$variant] ?? '';
|
$variantClass = $variants[$variant] ?? '';
|
||||||
|
$roundedClass = $round ? ' rounded-full p-3' : ' rounded-lg magnifying-glass px-4 py-2 ';
|
||||||
|
$variantClass.= $roundedClass;
|
||||||
@endphp
|
@endphp
|
||||||
@if($link !== '')
|
@if($link !== '')
|
||||||
<a
|
<a
|
||||||
@if($external)
|
@if($external)
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@endif
|
@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">
|
<div class="flex justify-center items-center space-x-2">
|
||||||
@if($icon !=='')
|
@if($icon !=='')
|
||||||
@svg("heroicon-o-$icon", 'w-5 h-5')
|
@svg("heroicon-o-$icon", 'w-5 h-5')
|
||||||
@ -22,13 +27,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@else
|
@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">
|
<div class="flex justify-center items-center space-x-2">
|
||||||
@if($icon !=='')
|
@if($icon !=='')
|
||||||
@svg("heroicon-o-$icon", 'w-5 h-5')
|
@svg("heroicon-o-$icon", 'w-5 h-5')
|
||||||
@endif
|
@endif
|
||||||
|
@if(filled($slot))
|
||||||
<p>{{$slot}}</p>
|
<p>{{$slot}}</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
@props(['label' => '', 'name' => '', 'placeholder' => '', 'type' => 'text', 'description' => '', 'required' => false, 'value' => ''])
|
@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 !== '')
|
@if($label !== '')
|
||||||
<label class="text-sm font-bold" for="{{$name}}">
|
<label class="text-sm font-bold mb-2" for="{{$name}}">
|
||||||
{{$label}}
|
{{$label}}
|
||||||
@if($required)
|
@if($required)
|
||||||
*
|
*
|
||||||
@ -10,10 +10,11 @@
|
|||||||
@endif
|
@endif
|
||||||
<input class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
<input class="bg-[#F3F3F5] py-2 px-4 rounded-lg"
|
||||||
type="{{$type}}" placeholder="{{$placeholder}}"
|
type="{{$type}}" placeholder="{{$placeholder}}"
|
||||||
name="{{$name}}" value="{{old($name, $value)}}"
|
name="{{$name}}" value="{{old($name, request($name, $value))}}"
|
||||||
{{$required?'required':''}}
|
{{$required?'required':''}}
|
||||||
{{$attributes}}
|
{{$attributes}}
|
||||||
>
|
>
|
||||||
|
{{$slot}}
|
||||||
@if($description !== '')
|
@if($description !== '')
|
||||||
<p class="text-accent-600 text-xs">{{$description}}</p>
|
<p class="text-accent-600 text-xs">{{$description}}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
'valueKey' => 'value',
|
'valueKey' => 'value',
|
||||||
'label' => '',
|
'label' => '',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'selected' => ''
|
'selected' => '',
|
||||||
|
'selectablePlaceholder' => false
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
<div class="flex flex-col space-y-2">
|
<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"
|
class="bg-[#F3F3F5] py-2 px-4 rounded-lg text-sm font-bold invalid:text-accent-600 text-black h-full"
|
||||||
>
|
>
|
||||||
@if($placeholder !== '')
|
@if($placeholder !== '')
|
||||||
<option {{old($name) === '' || $selected === '' ? 'selected' : ''}} disabled>{{$placeholder}}</option>
|
<option
|
||||||
|
value=" "
|
||||||
|
{{old($name, request($name, $selected)) === '' || $selected === '' ? 'selected' : ''}} {{$selectablePlaceholder ? '' : 'disabled'}} >{{$placeholder}}</option>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@foreach($options as $option)
|
@foreach($options as $option)
|
||||||
<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
|
@endforeach
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@props(['activeColor' => 'bg-white'])
|
@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}}
|
{{$slot}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
@aware(['activeColor' => 'bg-white'])
|
@aware(['activeColor' => 'bg-white'])
|
||||||
<div
|
<div
|
||||||
{{$attributes}}
|
{{$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 }}
|
{{ $slot }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,51 +1,15 @@
|
|||||||
@php
|
|
||||||
$categories = [0 => ['name' => 'All Categories', 'value' => '0']];
|
|
||||||
@endphp
|
|
||||||
<x-layout title="Deals">
|
<x-layout title="Deals">
|
||||||
<x-dashboard.page-heading
|
<x-dashboard.page-heading
|
||||||
title="Explore Deals"
|
title="Explore Deals"
|
||||||
description="Discover trusted recommendation"
|
description="Discover trusted recommendation"
|
||||||
>
|
>
|
||||||
<x-slot:end>
|
<x-slot:end>
|
||||||
<div class="flex items-center">
|
<x-explore.profile-menu :profile-link="$profileLink"/>
|
||||||
<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-slot:end>
|
</x-slot:end>
|
||||||
</x-dashboard.page-heading>
|
</x-dashboard.page-heading>
|
||||||
<section class="flex flex-col space-y-8 bg-[#F9FAFB] wrapper mt-2 pb-6">
|
<section class="flex flex-col space-y-8 bg-[#F9FAFB] wrapper mt-2 pb-6">
|
||||||
|
|
||||||
|
<!-- Session messages -->
|
||||||
<div>
|
<div>
|
||||||
@session('success')
|
@session('success')
|
||||||
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
|
<x-ui.alert variant="success">{{$value}}</x-ui.alert>
|
||||||
@ -56,39 +20,14 @@
|
|||||||
@endsession
|
@endsession
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-dashboard.card>
|
<x-explore.search :recent-searches="$recentSearches" :categories="$categories" />
|
||||||
<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-ui.toggle-button-group>
|
<!-- Sorting buttons -->
|
||||||
<x-ui.toggle-button :active="request('sortBy') == null">
|
<x-explore.toggle-buttons />
|
||||||
<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>
|
|
||||||
|
|
||||||
<x-dashboard.user.listing :deals="$deals"/>
|
<x-dashboard.user.listing :deals="$deals"/>
|
||||||
<x-dashboard.user.report-modal />
|
<x-dashboard.user.report-modal/>
|
||||||
</section>
|
</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'])
|
@vite(['resources/js/menu.js', 'resources/js/interaction.js', 'resources/js/report-deal.js', 'resources/js/toast.js'])
|
||||||
</x-layout>
|
</x-layout>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user