diff --git a/app/Http/Controllers/ExplorePageController.php b/app/Http/Controllers/ExplorePageController.php index 1f0e8d9..3906ca3 100644 --- a/app/Http/Controllers/ExplorePageController.php +++ b/app/Http/Controllers/ExplorePageController.php @@ -18,18 +18,11 @@ public function __invoke() protected function deals() { return Deal::query() - ->where('active', true) ->select([ - 'id', - 'title', - 'description', - 'image', - 'active', - 'slug', - 'link', - 'deal_category_id', - 'user_id', + 'id', 'title', 'description', 'image', 'active', 'slug', 'link', + 'deal_category_id', 'user_id', ]) + // Select additional details ->with([ 'category:id,name', 'broker' => function ($query) { @@ -37,6 +30,11 @@ protected function deals() ->with('type'); }, ]) + // Select only admin-approved deals + ->withActiveDeals() + // Check if the current user interacted with the deal + ->withCurrentUserInteractions() + ->withLikes() ->latest() ->paginate(); } diff --git a/app/Http/Controllers/InteractionController.php b/app/Http/Controllers/InteractionController.php index a73c74d..9d77b72 100644 --- a/app/Http/Controllers/InteractionController.php +++ b/app/Http/Controllers/InteractionController.php @@ -4,44 +4,63 @@ use App\Enums\InteractionType; use App\Models\Deal; +use App\Models\Interaction; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Log; class InteractionController extends Controller { - public function like(Deal $deal) + /** + * Interact to a deal by Like or Favorite state + * @param Deal $deal + * @param InteractionType $type [InteractionType::Like, InteractionType::Favorite] + * @return \Illuminate\Http\JsonResponse + */ + public function togglesState(Deal $deal, InteractionType $type) { + if (!in_array($type, [InteractionType::Like, InteractionType::Favorite])) { + return response()->json(['error' => 'This interaction is not supported'], 400); + } + try { // Check for existing like of the user with deal - $user = Auth::user(); - $existingLike = $deal->interactions() - ->where('user_id', $user->id) - ->where('type', InteractionType::Like) + $existingInteraction = $deal->interactions() + ->where('user_id', Auth::id()) + ->where('type', $type) ->first(); - // Delete the existing like if exists - if ($existingLike) { - $existingLike->delete(); + // Delete the existing like if exists, else add the like + $message = ''; + if ($existingInteraction) { + $existingInteraction->delete(); + $message = "{$type->value} removed from deal"; + } else { + $data = [ + 'type' => $type, + 'user_id' => Auth::id(), + ]; - return response()->json(['message' => 'Successfully unliked the post.']); + Interaction::unguard(); + $deal->interactions()->create($data); + Interaction::reguard(); + + $message = "{$type->value} added to deal"; } - - // Else, create a new like - $data = [ - 'type' => InteractionType::Like, - 'user_id' => Auth::user()->id, - ]; - - Deal::unguard(); - $deal->interactions()->create($data); - Deal::reguard(); + return response()->json(['message' => $message]); } catch (\Throwable $e) { - Log::error('Error when liked a deal: id'.$deal->id.$e->getMessage(), $e->getTrace()); + Log::error('Error when liked a deal', + [ + 'deal_id' => $deal->id, + 'use_id' => Auth::id(), + 'type' => $type, + 'error' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ] + ); return response()->json(['error' => 'Something went wrong.'], 500); } - - return response()->json(['message' => 'Successfully liked the post.']); } + } diff --git a/app/Models/Deal.php b/app/Models/Deal.php index 342e58b..5a80ed5 100644 --- a/app/Models/Deal.php +++ b/app/Models/Deal.php @@ -2,9 +2,12 @@ namespace App\Models; +use App\Enums\InteractionType; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Facades\Auth; class Deal extends Model { @@ -22,4 +25,42 @@ public function interactions(): HasMany { return $this->hasMany(Interaction::class); } + + /** + * Get deals that are active + * @param Builder $query + * @return Builder + */ + public function scopeWithActiveDeals(Builder $query): Builder + { + return $query->where('active', true); + } + + /** + * Get if the current user has liked or favorite the deal + * @param Builder $query + * @return Builder + */ + public function scopeWithCurrentUserInteractions(Builder $query): Builder + { + return $query->withExists([ + 'interactions as is_liked' => function ($query) { + $query->where('user_id', Auth::id()) + ->where('type', InteractionType::Like); + }, + 'interactions as is_favorite' => function ($query) { + $query->where('user_id', Auth::id()) + ->where('type', InteractionType::Favorite); + } + ]); + } + + public function scopeWithLikes(Builder $query): Builder + { + return $query->withCount([ + 'interactions as total_likes' => function ($query) { + $query->where('type', InteractionType::Like); + } + ]); + } } diff --git a/resources/js/interaction.js b/resources/js/interaction.js index d778326..30d6793 100644 --- a/resources/js/interaction.js +++ b/resources/js/interaction.js @@ -1,14 +1,58 @@ -async function like(e, id){ +async function like(button, id) { + // Instant feedback for user + let likeBtns = button.querySelectorAll('.like'); + let likeBadge = document.getElementById("likeBadge".concat(id)); + toggleHidden(likeBtns); + + // Check if user liked the deal + let isLiked = button.classList.contains('liked'); + updateLikeCount(likeBadge, isLiked ? -1 : 1) ; + button.classList.toggle('liked') try { let response = await axios.post('/like/' + id); - if (response.status === 200) { - let likeBtns = e.querySelectorAll('.like'); - likeBtns.forEach((e) => e.classList.toggle('hidden')) + if (response.status !== 200) { + // Revert the ui + toggleHidden(likeBtns); } } catch (e) { + toggleHidden(likeBtns); + console.error(e); + } +} + +async function favorite(e, id) { + // Instant feedback for user + let favoriteBtns = e.querySelectorAll('.favorite'); + toggleHidden(favoriteBtns); + + try { + let response = await axios.post('/favorite/' + id); + + if (response.status !== 200) { + // Revert the ui + toggleHidden(favoriteBtns); + } + } catch (e) { + toggleHidden(favoriteBtns); + console.error(e); + } +} + +function toggleHidden(nodelist) { + nodelist.forEach((node) => node.classList.toggle('hidden')) +} + +function updateLikeCount(badge, change){ + try{ + let likeCount = Math.max(parseInt( badge.dataset.count) + change, 0) + badge.querySelector('p').innerText = likeCount; + badge.dataset.count = likeCount.toString(); + } + catch(e) { console.error(e); } } document.like = like; +document.favorite = favorite; diff --git a/resources/views/components/dashboard/user/action-toolbar.blade.php b/resources/views/components/dashboard/user/action-toolbar.blade.php index 2d7135c..e9b9b40 100644 --- a/resources/views/components/dashboard/user/action-toolbar.blade.php +++ b/resources/views/components/dashboard/user/action-toolbar.blade.php @@ -1,12 +1,35 @@ -@props(['id']) +@props(['id', 'like' => false, 'favourite' => false])
{{$deal->title}}
@@ -16,7 +17,7 @@{{$count}}