feat(user interaction): add favorite interaction
- refactor controller to handle favorite - show like count and update when liked a deal - maintain like and favorite state with server
This commit is contained in:
parent
16c9ff3cee
commit
f33f68cd3e
@ -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();
|
||||
}
|
||||
|
||||
@ -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)
|
||||
{
|
||||
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)
|
||||
->first();
|
||||
|
||||
// Delete the existing like if exists
|
||||
if ($existingLike) {
|
||||
$existingLike->delete();
|
||||
|
||||
return response()->json(['message' => 'Successfully unliked the post.']);
|
||||
if (!in_array($type, [InteractionType::Like, InteractionType::Favorite])) {
|
||||
return response()->json(['error' => 'This interaction is not supported'], 400);
|
||||
}
|
||||
|
||||
// Else, create a new like
|
||||
try {
|
||||
// Check for existing like of the user with deal
|
||||
$existingInteraction = $deal->interactions()
|
||||
->where('user_id', Auth::id())
|
||||
->where('type', $type)
|
||||
->first();
|
||||
|
||||
// Delete the existing like if exists, else add the like
|
||||
$message = '';
|
||||
if ($existingInteraction) {
|
||||
$existingInteraction->delete();
|
||||
$message = "{$type->value} removed from deal";
|
||||
} else {
|
||||
$data = [
|
||||
'type' => InteractionType::Like,
|
||||
'user_id' => Auth::user()->id,
|
||||
'type' => $type,
|
||||
'user_id' => Auth::id(),
|
||||
];
|
||||
|
||||
Deal::unguard();
|
||||
Interaction::unguard();
|
||||
$deal->interactions()->create($data);
|
||||
Deal::reguard();
|
||||
Interaction::reguard();
|
||||
|
||||
$message = "{$type->value} added to deal";
|
||||
}
|
||||
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.']);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -1,12 +1,35 @@
|
||||
@props(['id'])
|
||||
@props(['id', 'like' => false, 'favourite' => false])
|
||||
<div class="">
|
||||
<x-ui.button-sm class="text-accent-600" onclick="like(this, {{$id}})">
|
||||
<x-heroicon-o-heart class="like w-4"/>
|
||||
<x-heroicon-s-heart class="like w-4 hidden text-red-500"/>
|
||||
<x-ui.button-sm @class(["text-accent-600", 'liked' => $like]) onclick="like(this, {{$id}})">
|
||||
<x-heroicon-o-heart
|
||||
@class([
|
||||
"like w-4",
|
||||
'hidden' => $like
|
||||
])
|
||||
/>
|
||||
<x-heroicon-s-heart
|
||||
@class([
|
||||
"like w-4 text-red-500",
|
||||
'hidden' => !$like
|
||||
])
|
||||
/>
|
||||
</x-ui.button-sm>
|
||||
|
||||
<x-ui.button-sm class="text-accent-600">
|
||||
<x-heroicon-o-star class="w-4"/>
|
||||
<x-ui.button-sm class="text-accent-600" onclick="favorite(this, {{$id}})">
|
||||
<x-heroicon-o-star
|
||||
@class([
|
||||
"favorite w-4",
|
||||
'hidden' => $favourite
|
||||
])
|
||||
/>
|
||||
|
||||
<x-heroicon-s-star
|
||||
@class([
|
||||
"favorite w-4 text-yellow-500",
|
||||
'hidden' => !$favourite
|
||||
])
|
||||
/>
|
||||
|
||||
</x-ui.button-sm>
|
||||
|
||||
<x-ui.button-sm class="text-accent-600">
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
<x-ui.button-sm variant="neutral">
|
||||
{{$deal->category->name}}
|
||||
</x-ui.button-sm>
|
||||
<x-dashboard.user.action-toolbar :id="$deal->id" />
|
||||
@ds($deal)
|
||||
<x-dashboard.user.action-toolbar :id="$deal->id" :like="$deal->is_liked" :favourite="$deal->is_favorite" />
|
||||
</div>
|
||||
|
||||
<p class="font-bold text-lg ">{{$deal->title}}</p>
|
||||
@ -16,7 +17,7 @@
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex space-x-3">
|
||||
<x-dashboard.user.stat-badge :count="200">
|
||||
<x-dashboard.user.stat-badge id="{{'likeBadge'.$deal->id}}" :count="$deal->total_likes">
|
||||
<x-heroicon-o-heart class="w-4"/>
|
||||
</x-dashboard.user.stat-badge>
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
@props(['count' => 0])
|
||||
<div class="flex text-accent-600 space-x-1">
|
||||
<div {{$attributes->merge(['class' =>"flex text-accent-600 space-x-1"])}} data-count="{{$count}}">
|
||||
{{$slot}}
|
||||
<p>{{$count}}</p>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use App\Enums\InteractionType;
|
||||
use App\Enums\UserTypes;
|
||||
use App\Http\Controllers\AuthenticatedUserController;
|
||||
use App\Http\Controllers\Broker\BrokerDashboardController;
|
||||
@ -41,7 +42,13 @@
|
||||
Route::resource('profile', BrokerProfileController::class)->except('index', 'store', 'create');
|
||||
});
|
||||
|
||||
Route::post('/like/{deal}', [InteractionController::class, 'like'])
|
||||
Route::post('/like/{deal}', [InteractionController::class, 'togglesState'])
|
||||
->defaults('type', InteractionType::Like)
|
||||
->middleware('throttle:30,1')
|
||||
->name('like');
|
||||
|
||||
Route::post('/favorite/{deal}', [InteractionController::class, 'togglesState'])
|
||||
->defaults('type', InteractionType::Favorite)
|
||||
->middleware('throttle:30,1')
|
||||
->name('like');
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user