diff --git a/app/Actions/AddRecentSearchAction.php b/app/Actions/AddRecentSearchAction.php
new file mode 100644
index 0000000..c3d5e4a
--- /dev/null
+++ b/app/Actions/AddRecentSearchAction.php
@@ -0,0 +1,30 @@
+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(),
+ ]);
+ }
+ }
+}
diff --git a/app/Http/Controllers/ExplorePageController.php b/app/Http/Controllers/ExplorePageController.php
index fc79e14..81feea8 100644
--- a/app/Http/Controllers/ExplorePageController.php
+++ b/app/Http/Controllers/ExplorePageController.php
@@ -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');
}
}
diff --git a/app/Http/Requests/ExploreSearchSortRequest.php b/app/Http/Requests/ExploreSearchSortRequest.php
new file mode 100644
index 0000000..f5368da
--- /dev/null
+++ b/app/Http/Requests/ExploreSearchSortRequest.php
@@ -0,0 +1,32 @@
+|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'],
+ ];
+ }
+}
diff --git a/app/Models/Deal.php b/app/Models/Deal.php
index 9ceff4d..207db51 100644
--- a/app/Models/Deal.php
+++ b/app/Models/Deal.php
@@ -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);
}
}
diff --git a/app/Models/RecentSearch.php b/app/Models/RecentSearch.php
new file mode 100644
index 0000000..c215570
--- /dev/null
+++ b/app/Models/RecentSearch.php
@@ -0,0 +1,16 @@
+belongsTo(User::class);
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 09d60d8..3f67aab 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -73,4 +73,9 @@ public function interactions(): HasMany
{
return $this->hasMany(User::class);
}
+
+ public function recentSearches(): HasMany
+ {
+ return $this->hasMany(RecentSearch::class);
+ }
}
diff --git a/app/Queries/ExplorePageDealsQuery.php b/app/Queries/ExplorePageDealsQuery.php
new file mode 100644
index 0000000..0f5f749
--- /dev/null
+++ b/app/Queries/ExplorePageDealsQuery.php
@@ -0,0 +1,35 @@
+
+ */
+ 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));
+ }
+}
diff --git a/database/migrations/2026_01_20_064552_create_recent_searches_table.php b/database/migrations/2026_01_20_064552_create_recent_searches_table.php
new file mode 100644
index 0000000..9bd3012
--- /dev/null
+++ b/database/migrations/2026_01_20_064552_create_recent_searches_table.php
@@ -0,0 +1,30 @@
+id();
+ $table->foreignIdFor(User::class);
+ $table->string('query');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('recent_searches');
+ }
+};
diff --git a/resources/js/menu.js b/resources/js/menu.js
index 9aa48f1..50b26c9 100644
--- a/resources/js/menu.js
+++ b/resources/js/menu.js
@@ -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;
diff --git a/resources/views/components/dashboard/user/listing.blade.php b/resources/views/components/dashboard/user/listing.blade.php
index 7bcb3de..ee2ce87 100644
--- a/resources/views/components/dashboard/user/listing.blade.php
+++ b/resources/views/components/dashboard/user/listing.blade.php
@@ -3,6 +3,6 @@
@forelse($deals as $deal)
No Deals found till now !
+No Deals found !
@endforelse diff --git a/resources/views/components/dashboard/user/recent-search/index.blade.php b/resources/views/components/dashboard/user/recent-search/index.blade.php new file mode 100644 index 0000000..7b3dcfc --- /dev/null +++ b/resources/views/components/dashboard/user/recent-search/index.blade.php @@ -0,0 +1,6 @@ +@props(['searches']) +All Deals
+ +Most Liked
+ +Most Clicked
+ +