feat: funcionalidad de red social (feed, amigos, búsqueda)
- Migraciones: posts, post_likes, post_comments - Modelos Post, PostLike, PostComment con relaciones - User: métodos friendshipWith, isFriendWith, friendIds, posts - PostController: feed, crear publicación con imagen, like toggle, comentarios, eliminar - FriendshipController: listar solicitudes, enviar, aceptar, rechazar, toggle AJAX - SocialProfileController: perfil público con publicaciones y estado de amistad - UserSearchController: búsqueda AJAX por nombre con estado de amistad - FriendRequestNotification: notificación al recibir solicitud - Vistas: feed/index, feed/_post, friendships/index, social/profile - Header: barra de búsqueda con autocomplete AJAX (agregar amigo / mensaje) - Menú lateral: Comunidad, Amigos, Mensajes Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
@extends('layouts.landing')
|
||||
@section('title', $user->name)
|
||||
|
||||
@section('content')
|
||||
<div class="container" style="max-width:680px">
|
||||
|
||||
{{-- Tarjeta de perfil --}}
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center">
|
||||
<img src="{{ $user->profile_photo_url }}"
|
||||
class="rounded-circle mr-4" width="72" height="72" style="object-fit:cover">
|
||||
<div class="flex-grow-1">
|
||||
<h5 class="mb-0 font-weight-bold">
|
||||
{{ $user->name }} {{ $user->apellidoPaterno }} {{ $user->apellidoMaterno }}
|
||||
</h5>
|
||||
<small class="text-muted">{{ $user->email }}</small>
|
||||
</div>
|
||||
@if($user->id !== auth()->id())
|
||||
<div class="d-flex flex-column align-items-end" style="gap:.5rem">
|
||||
@php
|
||||
$me = auth()->user();
|
||||
$status = $friendship?->status ?? 'none';
|
||||
$isSender = $friendship?->sender_id === $me->id;
|
||||
@endphp
|
||||
|
||||
{{-- Botón de amistad --}}
|
||||
@if($status === 'accepted')
|
||||
<form action="{{ route('friendships.destroy', $friendship) }}" method="POST">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-user-check mr-1"></i> Amigos
|
||||
</button>
|
||||
</form>
|
||||
@elseif($status === 'pending' && $isSender)
|
||||
<form action="{{ route('friendships.destroy', $friendship) }}" method="POST">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn btn-outline-secondary btn-sm">
|
||||
<i class="fas fa-user-clock mr-1"></i> Cancelar solicitud
|
||||
</button>
|
||||
</form>
|
||||
@elseif($status === 'pending' && !$isSender)
|
||||
<div class="d-flex" style="gap:.4rem">
|
||||
<form action="{{ route('friendships.accept', $friendship) }}" method="POST">
|
||||
@csrf
|
||||
<button class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-check mr-1"></i> Aceptar
|
||||
</button>
|
||||
</form>
|
||||
<form action="{{ route('friendships.destroy', $friendship) }}" method="POST">
|
||||
@csrf @method('DELETE')
|
||||
<button class="btn btn-outline-secondary btn-sm">Rechazar</button>
|
||||
</form>
|
||||
</div>
|
||||
@else
|
||||
<form action="{{ route('friendships.send', $user) }}" method="POST">
|
||||
@csrf
|
||||
<button class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-user-plus mr-1"></i> Agregar amigo
|
||||
</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
{{-- Botón de mensaje (solo si son amigos) --}}
|
||||
@if($status === 'accepted')
|
||||
<a href="{{ route('chat.show', $user) }}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-comment mr-1"></i> Mensaje
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Publicaciones del usuario --}}
|
||||
@forelse($posts as $post)
|
||||
@include('feed._post', ['post' => $post])
|
||||
@empty
|
||||
<div class="text-center text-muted py-4">
|
||||
<i class="fas fa-stream fa-2x mb-2 d-block"></i>
|
||||
Este usuario no tiene publicaciones aún.
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
<div class="d-flex justify-content-center mt-3">
|
||||
{{ $posts->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
// Like toggle (mismo que feed)
|
||||
document.querySelectorAll('.like-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
const postId = this.dataset.postId;
|
||||
axios.post(`/posts/${postId}/like`).then(res => {
|
||||
this.querySelector('.like-count').textContent = res.data.count;
|
||||
document.getElementById('like-count-' + postId).textContent = res.data.count;
|
||||
this.classList.toggle('text-danger', res.data.liked);
|
||||
this.classList.toggle('text-muted', !res.data.liked);
|
||||
this.querySelector('i').classList.toggle('fas', res.data.liked);
|
||||
this.querySelector('i').classList.toggle('far', !res.data.liked);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.toggle-comments').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
document.getElementById('comments-' + this.dataset.postId).classList.toggle('d-none');
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.comment-form').forEach(form => {
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
const postId = this.dataset.postId;
|
||||
const input = this.querySelector('input[name="body"]');
|
||||
if (!input.value.trim()) return;
|
||||
|
||||
axios.post(`/posts/${postId}/comments`, { body: input.value }).then(res => {
|
||||
const list = document.getElementById('comment-list-' + postId);
|
||||
const item = document.createElement('div');
|
||||
item.className = 'd-flex align-items-start mb-2';
|
||||
item.innerHTML = `
|
||||
<img src="${res.data.avatar}" class="rounded-circle mr-2" width="28" height="28" style="object-fit:cover">
|
||||
<div class="bg-light rounded px-2 py-1 flex-grow-1">
|
||||
<strong class="small">${res.data.author}</strong>
|
||||
<div class="small">${res.data.body}</div>
|
||||
</div>`;
|
||||
list.appendChild(item);
|
||||
input.value = '';
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user