feature(users can follow a broker)
- add schema and endpoints to make follows relationship with customer and broker - show and update states of follow button on ui
This commit is contained in:
parent
a06fac4fef
commit
5cae04884a
43
app/Http/Controllers/FollowController.php
Normal file
43
app/Http/Controllers/FollowController.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Broker;
|
||||
use App\Models\Follow;
|
||||
|
||||
class FollowController extends Controller
|
||||
{
|
||||
public function __invoke(Broker $broker)
|
||||
{
|
||||
$follow = $this->checkFollow($broker);
|
||||
if ($follow === null) {
|
||||
return $this->store($broker);
|
||||
} else {
|
||||
return $this->destroy($follow);
|
||||
}
|
||||
}
|
||||
|
||||
public function store(Broker $broker)
|
||||
{
|
||||
Follow::create([
|
||||
'broker_id' => $broker->id,
|
||||
'customer_id' => auth()->id(),
|
||||
]);
|
||||
|
||||
return response()->json(['message' => 'Followed successfully.']);
|
||||
}
|
||||
|
||||
public function destroy(Follow $follow)
|
||||
{
|
||||
$follow->delete();
|
||||
|
||||
return response()->json(['message' => 'Unfollowed successfully.']);
|
||||
}
|
||||
|
||||
protected function checkFollow(Broker $broker)
|
||||
{
|
||||
return Follow::where('broker_id', $broker->id)
|
||||
->where('customer_id', auth()->id())
|
||||
->first();
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ public function toArray(Request $request): array
|
||||
'totalRedirection' => $this->total_redirection,
|
||||
'isLiked' => $this->is_liked,
|
||||
'isFavorite' => $this->is_favorite,
|
||||
'isFollowed' => $this->is_followed
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
/**
|
||||
@ -43,4 +44,9 @@ public function user(): MorphOne
|
||||
{
|
||||
return $this->morphOne(User::class, 'role');
|
||||
}
|
||||
|
||||
public function followers(): HasMany
|
||||
{
|
||||
return $this->hasMany(Follow::class, 'broker_id');
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||
|
||||
/**
|
||||
@ -34,4 +35,9 @@ public function user(): MorphOne
|
||||
{
|
||||
return $this->morphOne(User::class, 'role');
|
||||
}
|
||||
|
||||
public function followings(): HasMany
|
||||
{
|
||||
return $this->hasMany(Follow::class, 'customer_id');
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,4 +167,30 @@ public function filterByCategory(Builder $query, string $category): Builder
|
||||
{
|
||||
return $query->where('deal_category_id', $category);
|
||||
}
|
||||
|
||||
// Add this to App\Models\Deal.php
|
||||
|
||||
/**
|
||||
* Scope a query to check if the current user follows the deal's broker
|
||||
*/
|
||||
#[Scope]
|
||||
public function withIsFollowedByCurrentUser(Builder $query): Builder
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
if (! $user || $user->role_type !== \App\Models\Customer::class) {
|
||||
return $query->withExists(['broker as is_followed' => fn ($q) => $q->whereRaw('1 = 0')]);
|
||||
}
|
||||
|
||||
return $query->withExists([
|
||||
'broker as is_followed' => function ($query) use ($user) {
|
||||
$query->where('role_type', \App\Models\Broker::class)
|
||||
->whereHasMorph('type', [\App\Models\Broker::class], function ($query) use ($user) {
|
||||
$query->whereHas('followers', function ($query) use ($user) {
|
||||
$query->where('customer_id', $user->id);
|
||||
});
|
||||
});
|
||||
},
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
24
app/Models/Follow.php
Normal file
24
app/Models/Follow.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class Follow extends Model
|
||||
{
|
||||
protected $fillable = [
|
||||
'customer_id',
|
||||
'broker_id',
|
||||
];
|
||||
|
||||
public function customer(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Customer::class);
|
||||
}
|
||||
|
||||
public function broker(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Broker::class);
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,7 @@ public function builder(): Builder
|
||||
// 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)->withIsFollowedByCurrentUser($q))
|
||||
->tap(fn ($q) => (new Deal)->withRedirectionPerDeal($q));
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Broker;
|
||||
use App\Models\Customer;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('follows', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->foreignIdFor(Customer::class);
|
||||
$table->foreignIdFor(Broker::class);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('follows');
|
||||
}
|
||||
};
|
||||
@ -14,6 +14,7 @@ import {loadModalFromQuery} from "./explore-page.js";
|
||||
import {deleteRecentSearch} from "./deleteRecentSearch.js";
|
||||
import {initNavMenu} from "./nav-menu.js";
|
||||
import {toggleShimmer} from "./shimmer.js";
|
||||
import {follow} from "./interaction.js";
|
||||
|
||||
document.deleteSearch = deleteRecentSearch;
|
||||
document.like = like;
|
||||
@ -21,6 +22,7 @@ document.favorite = favorite;
|
||||
document.redirect = redirect;
|
||||
document.showReportModal = showReportModal;
|
||||
window.toggleShimmer = toggleShimmer;
|
||||
window.follow = follow;
|
||||
|
||||
window.addEventListener('load', async () => {
|
||||
const preloader = document.getElementById('preloader');
|
||||
|
||||
@ -88,6 +88,10 @@ function setDealDetails(dealDetails) {
|
||||
dealLink.classList.remove('flex');
|
||||
dealLink.classList.add('hidden');
|
||||
}
|
||||
|
||||
// set follow state
|
||||
const followBtn = dealModal.querySelector('.followBtn');
|
||||
followBtn.dataset.followed = deal.isFollowed;
|
||||
}
|
||||
|
||||
async function setComments(dealId, dealModal) {
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
import {showToast} from "./toast.js";
|
||||
|
||||
/**
|
||||
* Like a deal
|
||||
* @param button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function like(button) {
|
||||
const activeClasses = ['fill-current', 'text-red-500']
|
||||
let isLiked = button.dataset.liked === 'true';
|
||||
@ -18,7 +23,7 @@ export async function like(button) {
|
||||
showToast(response.data.message)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 401){
|
||||
if (e.response.status === 401) {
|
||||
window.location.href = '/login/create';
|
||||
return;
|
||||
}
|
||||
@ -29,6 +34,11 @@ export async function like(button) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark favorite a deal
|
||||
* @param button
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function favorite(button) {
|
||||
const activeClasses = ['fill-current', 'text-yellow-500']
|
||||
let isFavorite = button.dataset.favorite === 'true';
|
||||
@ -42,7 +52,7 @@ export async function favorite(button) {
|
||||
showToast(response.data.message)
|
||||
|
||||
} catch (e) {
|
||||
if (e.response.status === 401){
|
||||
if (e.response.status === 401) {
|
||||
window.location.href = '/login/create';
|
||||
return;
|
||||
}
|
||||
@ -52,6 +62,37 @@ export async function favorite(button) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow a broker
|
||||
* @param button
|
||||
* @param brokerId
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
export async function follow(button, brokerId) {
|
||||
let isFollowed = button.dataset.followed === 'true';
|
||||
try {
|
||||
button.dataset.followed = isFollowed ? 'false' : 'true';
|
||||
// Update other buttons
|
||||
const selector = `button[onclick="follow(this, ${brokerId})"]`;
|
||||
|
||||
document.querySelectorAll(selector).forEach(btn => {
|
||||
btn.dataset.followed = isFollowed ? 'false' : 'true';
|
||||
});
|
||||
|
||||
let response = await axios.post(`/api/follow/${brokerId}`);
|
||||
showToast(response.data.message);
|
||||
} catch (e) {
|
||||
button.dataset.followed = isFollowed ? 'true' : 'false';
|
||||
showToast('Something went wrong!')
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment visit count of a deal's external link
|
||||
* @param url
|
||||
* @param id
|
||||
*/
|
||||
export function redirect(url, id) {
|
||||
window.open(url, '_blank');
|
||||
let redirectBadge = document.getElementById("redirectBadge".concat(id));
|
||||
@ -96,3 +137,4 @@ function updateRedirectCount(badge, change) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
@props(['broker' => ''])
|
||||
@props(['broker' => '', 'is_followed' => false])
|
||||
<div {{$attributes->merge(['class' => "p-4 text-sm bg-gray-100 border-gray-200 border rounded-xl"])}}>
|
||||
<p class="font-bold mb-2">Broker Contact</p>
|
||||
<div class="flex space-x-4 items-baseline">
|
||||
<p class="font-bold mb-2">Broker Contact</p>
|
||||
<x-ui.button-sm data-is-loading="false" data-followed="{{$is_followed ? 'true' : 'false'}}" onclick="follow(this, {{$broker->role_id ?? ''}})" class="followBtn group p-0! mt-0.5">
|
||||
<span class="group-data-[followed=true]:hidden text-blue-600">Follow</span>
|
||||
<span class="group-data-[followed=false]:hidden text-accent-600">Unfollow</span>
|
||||
</x-ui.button-sm>
|
||||
</div>
|
||||
<div class="text-accent-600 space-y-1">
|
||||
<p data-is-loading="false" class="broker-name">{{$broker->name ?? ''}}</p>
|
||||
<p data-is-loading="false" class="broker-email">{{$broker->email ?? ''}}</p>
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
</a>
|
||||
@endguest
|
||||
@auth
|
||||
<x-dashboard.user.broker-contact :broker="$broker"/>
|
||||
<x-dashboard.user.broker-contact :broker="$broker" :is_followed="$deal->is_followed"/>
|
||||
@endauth
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\CommentController;
|
||||
use App\Http\Controllers\FollowController;
|
||||
use App\Http\Controllers\Interaction\InteractionController;
|
||||
use App\Http\Controllers\Interaction\ReportController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
@ -15,3 +16,4 @@
|
||||
});
|
||||
|
||||
Route::apiResource('deals.comments', CommentController::class)->except(['update', 'edit', 'destroy']);
|
||||
Route::post('/follow/{broker}', FollowController::class);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user