feat: implement user interaction system with like functionality

- Updated interaction models and relations for handling of user-deal interactions.
- Added frontend interactivity with `interaction.js` for toggling like buttons.
This commit is contained in:
kusowl 2026-01-15 19:09:24 +05:30
parent a82e1b6b06
commit 16c9ff3cee
13 changed files with 102 additions and 14 deletions

View File

@ -10,4 +10,5 @@ enum InteractionType: string
case Like = 'like';
case Favorite = 'favorite';
case Redirection = 'redirect';
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers;
use App\Enums\InteractionType;
use App\Models\Deal;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
class InteractionController extends Controller
{
public function like(Deal $deal)
{
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.']);
}
// Else, create a new like
$data = [
'type' => InteractionType::Like,
'user_id' => Auth::user()->id,
];
Deal::unguard();
$deal->interactions()->create($data);
Deal::reguard();
} catch (\Throwable $e) {
Log::error('Error when liked a deal: id'.$deal->id.$e->getMessage(), $e->getTrace());
return response()->json(['error' => 'Something went wrong.'], 500);
}
return response()->json(['message' => 'Successfully liked the post.']);
}
}

View File

@ -20,7 +20,6 @@ public function create()
public function store(StoreRegisterdUser $request)
{
$data = $request->validated();
try {
DB::transaction(function () use ($data) {
if ($data['role'] === UserTypes::Broker->value) {

View File

@ -4,7 +4,7 @@
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Deal extends Model
{
@ -18,8 +18,8 @@ public function category(): BelongsTo
return $this->belongsTo(DealCategory::class, 'deal_category_id');
}
public function interactions(): MorphMany
public function interactions(): HasMany
{
return $this->morphMany(Interaction::class, 'interactable');
return $this->hasMany(Interaction::class);
}
}

View File

@ -2,13 +2,26 @@
namespace App\Models;
use App\Enums\InteractionType;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Interaction extends Model
{
public function interactable(): MorphTo
public function user(): BelongsTo
{
return $this->morphTo();
return $this->belongsTo(Deal::class);
}
public function deal(): BelongsTo
{
return $this->belongsTo(Deal::class);
}
protected function casts(): array
{
return [
'type' => InteractionType::class,
];
}
}

View File

@ -68,4 +68,9 @@ public function type(): MorphTo
{
return $this->morphTo('role');
}
public function interactions(): HasMany
{
return $this->hasMany(User::class);
}
}

View File

@ -19,7 +19,8 @@ public function up(): void
$table->foreignIdFor(User::class);
$table->foreignIdFor(Deal::class);
$table->enum('type', InteractionType::values());
$table->timestamp('created_at')->useCurrent();
$table->unsignedBigInteger('count')->default(1);
$table->timestamps();
});
}

View File

@ -0,0 +1,14 @@
async function like(e, id){
try {
let response = await axios.post('/like/' + id);
if (response.status === 200) {
let likeBtns = e.querySelectorAll('.like');
likeBtns.forEach((e) => e.classList.toggle('hidden'))
}
} catch (e) {
console.error(e);
}
}
document.like = like;

View File

@ -1,6 +1,8 @@
@props(['id'])
<div class="">
<x-ui.button-sm class="text-accent-600">
<x-heroicon-o-heart class="w-4"/>
<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>
<x-ui.button-sm class="text-accent-600">

View File

@ -5,7 +5,7 @@
<x-ui.button-sm variant="neutral">
{{$deal->category->name}}
</x-ui.button-sm>
<x-dashboard.user.action-toolbar />
<x-dashboard.user.action-toolbar :id="$deal->id" />
</div>
<p class="font-bold text-lg ">{{$deal->title}}</p>

View File

@ -1,7 +1,6 @@
@props(['deals' => []])
<div class="grid md:grid-cols-2 gap-6">
@forelse($deals as $deal)
@ds($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>

View File

@ -88,5 +88,5 @@
<x-dashboard.user.listing :deals="$deals"/>
</section>
@vite('resources/js/menu.js')
@vite(['resources/js/menu.js', 'resources/js/interaction.js'])
</x-layout>

View File

@ -7,6 +7,7 @@
use App\Http\Controllers\BrokerDealController;
use App\Http\Controllers\ExplorePageController;
use App\Http\Controllers\HomeController;
use App\Http\Controllers\InteractionController;
use App\Http\Controllers\RegisteredUserController;
use App\Http\Middleware\HasRole;
use Illuminate\Support\Facades\Route;
@ -14,7 +15,9 @@
Route::get('/', HomeController::class)->name('home');
Route::middleware('guest')->group(function () {
Route::resource('/login', AuthenticatedUserController::class)->only(['create', 'store']);
Route::resource('/login', AuthenticatedUserController::class)
->middleware('throttle:6,1')
->only(['create', 'store']);
Route::resource('/register', RegisteredUserController::class)->only(['create', 'store']);
});
@ -37,4 +40,8 @@
Route::resource('profile', BrokerProfileController::class)->except('index', 'store', 'create');
});
Route::post('/like/{deal}', [InteractionController::class, 'like'])
->middleware('throttle:30,1')
->name('like');
});