Skip to content

Commit

Permalink
Merge pull request #470 from pinkary-project/feat/easy-follow
Browse files Browse the repository at this point in the history
Feat: easy follow
  • Loading branch information
nunomaduro authored Sep 21, 2024
2 parents 995a276 + 813bf25 commit 7bac2d8
Show file tree
Hide file tree
Showing 15 changed files with 545 additions and 48 deletions.
63 changes: 63 additions & 0 deletions app/Livewire/Concerns/Followable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Concerns;

use App\Models\User;
use Livewire\Attributes\Renderless;

trait Followable
{
/**
* Follows the given user.
*/
#[Renderless]
public function follow(int $id): void
{
if (! auth()->check()) {
$this->redirectRoute('login', navigate: true);

return;
}

$user = type(auth()->user())->as(User::class);
$user->following()->attach($id);

if ($this->shouldHandleFollowingCount()) {
$this->dispatch('following.updated');
}

$this->dispatch('user.followed', id: $id);
}

/**
* Unfollows the given user.
*/
#[Renderless]
public function unfollow(int $id): void
{
if (! auth()->check()) {
$this->redirectRoute('login', navigate: true);

return;
}

$user = type(auth()->user())->as(User::class);
$user->following()->detach($id);

if ($this->shouldHandleFollowingCount()) {
$this->dispatch('following.updated');
}

$this->dispatch('user.unfollowed', id: $id);
}

/**
* Indicates if the following count should be handled.
*/
protected function shouldHandleFollowingCount(): bool
{
return false;
}
}
21 changes: 18 additions & 3 deletions app/Livewire/Followers/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Livewire\Followers;

use App\Livewire\Concerns\Followable;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
Expand All @@ -15,7 +16,7 @@

final class Index extends Component
{
use WithoutUrlPagination, WithPagination;
use Followable, WithoutUrlPagination, WithPagination;

/**
* The component's user ID.
Expand All @@ -38,13 +39,27 @@ public function render(): View
return view('livewire.followers.index', [
'user' => $user,
'followers' => $this->isOpened ? $user->followers()
->when(auth()->user()?->isNot($user), function (Builder|BelongsToMany $query): void {
->unless(auth()->user()?->is($user), function (Builder|BelongsToMany $query): void {
$query->withExists([
'following as is_follower' => function (Builder $query): void {
$query->where('user_id', auth()->id());
},
]);
})->latest('followers.id')->simplePaginate(10) : collect(),
})
->withExists([
'followers as is_following' => function (Builder $query): void {
$query->where('follower_id', auth()->id());
},
])
->latest('followers.id')->simplePaginate(10) : collect(),
]);
}

/**
* Indicates if the following count should be handled.
*/
protected function shouldHandleFollowingCount(): bool
{
return $this->userId === auth()->id();
}
}
31 changes: 25 additions & 6 deletions app/Livewire/Following/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace App\Livewire\Following;

use App\Livewire\Concerns\Followable;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\View\View;
use Livewire\Attributes\Locked;
use Livewire\Component;
Expand All @@ -14,7 +16,7 @@

final class Index extends Component
{
use WithoutUrlPagination, WithPagination;
use Followable, WithoutUrlPagination, WithPagination;

/**
* The component's user ID.
Expand All @@ -36,11 +38,28 @@ public function render(): View

return view('livewire.following.index', [
'user' => $user,
'following' => $this->isOpened ? $user->following()->withExists([
'following as is_follower' => function (Builder $query): void {
$query->where('user_id', auth()->id());
},
])->latest('followers.id')->simplePaginate(10) : collect(),
'following' => $this->isOpened ? $user->following()
->withExists([
'following as is_follower' => function (Builder $query): void {
$query->where('user_id', auth()->id());
},
])
->unless(auth()->user()?->is($user), function (Builder|BelongsToMany $query): void {
$query->withExists([
'followers as is_following' => function (Builder $query): void {
$query->where('follower_id', auth()->id());
},
]);
})
->latest('followers.id')->simplePaginate(10) : collect(),
]);
}

/**
* Indicates if the following count should be handled.
*/
protected function shouldHandleFollowingCount(): bool
{
return $this->userId === auth()->id();
}
}
25 changes: 24 additions & 1 deletion app/Livewire/Home/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace App\Livewire\Home;

use App\Livewire\Concerns\Followable;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
Expand All @@ -14,6 +15,8 @@

final class Users extends Component
{
use Followable;

/**
* The component's search query.
*/
Expand Down Expand Up @@ -51,6 +54,16 @@ private function usersByQuery(): Collection
$query->whereNotNull('answer');
}])
->orderBy('answered_questions_count', 'desc')
->when(auth()->check(), function (Builder $query): void {
$query->withExists([
'following as is_follower' => function (Builder $query): void {
$query->where('user_id', auth()->id());
},
'followers as is_following' => function (Builder $query): void {
$query->where('follower_id', auth()->id());
},
]);
})
->limit(10)
->get();
}
Expand All @@ -67,7 +80,17 @@ private function defaultUsers(): Collection
return $this->famousUsers($verifiedUsers)
->merge($verifiedUsers)
->shuffle()
->load('links');
->load('links')
->when(auth()->check(), function (Collection $users): void {
$users->loadExists([ // @phpstan-ignore-line
'following as is_follower' => function (Builder $query): void {
$query->where('user_id', auth()->id());
},
'followers as is_following' => function (Builder $query): void {
$query->where('follower_id', auth()->id());
},
]);
});
}

/**
Expand Down
1 change: 1 addition & 0 deletions app/Livewire/Links/Index.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ public function unfollow(int $targetId): void
#[On('link.created')]
#[On('link.updated')]
#[On('link-settings.updated')]
#[On('following.updated')]
public function refresh(): void
{
//
Expand Down
2 changes: 2 additions & 0 deletions resources/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Alpine.data('likeButton', likeButton);

import { bookmarkButton } from './bookmark-button.js';
Alpine.data('bookmarkButton', bookmarkButton);
import { followButton } from './follow-button.js'
Alpine.data('followButton', followButton)

import { viewCreate } from './view-cerate.js';
Alpine.data('viewCreate', viewCreate);
Expand Down
50 changes: 50 additions & 0 deletions resources/js/follow-button.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const followButton = (id, isFollowing, isFollower, isAuthenticated) => ({
id,
isFollowing,
isFollower,
isAuthenticated,
buttonText: '',

init() {
this.setButtonText();
this.initEventListeners();
},

toggleFollow() {
if (!this.isAuthenticated) {
window.Livewire.navigate('/login');
return;
}

if (this.isFollowing) {
this.$wire.unfollow(id);
this.$dispatch('user.unfollowed', { id: id });
} else {
this.$wire.follow(id);
this.$dispatch('user.followed', { id: id });
}
},

setButtonText() {
this.buttonText = this.isFollowing ? 'Unfollow' : (this.isFollower ? 'Follow Back' : 'Follow');
},

initEventListeners() {
window.addEventListener('user.followed', (event) => {
if (event.detail.id == this.id) {
this.isFollowing = true;
this.setButtonText();
}
});
window.addEventListener('user.unfollowed', (event) => {
if (event.detail.id == id) {
this.isFollowing = false;
this.setButtonText();
}
});
}
});

export { followButton }


21 changes: 21 additions & 0 deletions resources/views/components/follow-button.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@props([
'id',
'isFollower' => false,
'isFollowing' => false,
])

@if(auth()->id() !== $id)
<div {{ $attributes }}
x-data="followButton({{ $id }}, @js($isFollowing), @js($isFollower), @js(auth()->check()))"
>
<x-secondary-button
wire:loading.attr="disabled"
data-navigate-ignore="true"
type="button"
x-on:click="toggleFollow()"
class="text-xs md:text-sm text-nowrap"
>
<span x-text='buttonText' :title='buttonText' ></span>
</x-secondary-button>
</div>
@endif
35 changes: 23 additions & 12 deletions resources/views/livewire/followers/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name="followers"
maxWidth="2xl"
>
<div class="p-10" x-on:open-modal.window="$event.detail == 'followers' ? $wire.set('isOpened', true) : null">
<div class="p-4 md:p-10" x-on:open-modal.window="$event.detail == 'followers' ? $wire.set('isOpened', true) : null">
<div>
@if ($followers->isEmpty())
<strong> <span>@</span>{{ $user->username }} does not have any followers </strong>
Expand All @@ -15,22 +15,27 @@
<section class="mt-10 max-w-2xl max-h-96 overflow-y-auto">
<ul class="flex flex-col gap-2">
@foreach ($followers as $follower)
<li>
<a
href="{{ route('profile.show', ['username' => $follower->username]) }}"
class="group flex items-center gap-3 rounded-2xl border dark:border-slate-900 border-slate-200 dark:bg-slate-950 bg-slate-100 bg-opacity-80 p-4 transition-colors dark:hover:bg-slate-900 hover:bg-slate-200"
wire:navigate
>
<li
data-parent=true
x-data="clickHandler"
x-on:click="handleNavigation($event)"
>
<div class="group flex items-center gap-3 rounded-2xl border dark:border-slate-900 border-slate-200 dark:bg-slate-950 bg-slate-100 bg-opacity-80 p-4 transition-colors dark:hover:bg-slate-900 hover:bg-slate-200">
<figure class="{{ $follower->is_company_verified ? 'rounded-md' : 'rounded-full' }} h-12 w-12 flex-shrink-0 overflow-hidden bg-slate-800 transition-opacity group-hover:opacity-90">
<img
class="{{ $follower->is_company_verified ? 'rounded-md' : 'rounded-full' }} h-12 w-12"
src="{{ $follower->avatar_url }}"
alt="{{ $follower->username }}"
/>
</figure>
<div class="flex flex-col overflow-hidden text-sm">
<div class="flex items-center space-x-2">
<p class="truncate font-medium">
<div class="flex flex-col overflow-hidden text-sm text-left">
<a
class="flex items-center space-x-2"
href="{{ route('profile.show', ['username' => $follower->username]) }}"
wire:navigate
x-ref="parentLink"
>
<p class="text-wrap truncate font-medium">
{{ $follower->name }}
</p>

Expand All @@ -45,7 +50,7 @@ class="size-4"
class="size-4"
/>
@endif
</div>
</a>
<p class="truncate text-left text-slate-500 transition-colors group-hover:text-slate-400">
{{ '@'.$follower->username }}
@if (auth()->user()?->isNot($user) && $follower->is_follower)
Expand All @@ -55,7 +60,13 @@ class="size-4"
@endif
</p>
</div>
</a>
<x-follow-button
:id="$follower->id"
:isFollower="$user->is(auth()->user()) || $follower->is_follower"
:isFollowing="$follower->is_following"
class="ml-auto"
/>
</div>
</li>
@endforeach
</ul>
Expand Down
Loading

0 comments on commit 7bac2d8

Please sign in to comment.