298 lines
19 KiB
PHP
298 lines
19 KiB
PHP
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
|
<!-- CSRF Token for AJAX requests -->
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<input type="hidden" name="_token" value="{{ csrf_token() }}" id="csrf-token-input">
|
|
<!-- Profile Header -->
|
|
<div class="bg-gray-100/50 backdrop-blur-sm border border-gray-700/50 rounded-xl shadow-xl mb-8 overflow-hidden">
|
|
<!-- Banner -->
|
|
<div class="h-64 bg-gradient-to-r from-blue-600 to-purple-600 relative">
|
|
@if($profile->banner_url)
|
|
<img src="{{ $profile->banner_url }}" alt="Profile Banner" class="w-full h-full object-cover">
|
|
@endif
|
|
|
|
@if($isOwner)
|
|
<div class="absolute top-4 right-4">
|
|
<a href="{{ route('socialbase.profile.edit') }}" class="bg-primary/50 backdrop-blur-sm text-white px-4 py-2 rounded-xl hover:bg-black/70 transition-all duration-300 hover:scale-105 inline-flex items-center">Change Banner</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Profile Info -->
|
|
<div class="px-6 py-6 relative" style="overflow: visible;"
|
|
x-data="{
|
|
open: false,
|
|
currentReaction: @js(Auth::check() ? $profile->getUserReaction(Auth::user())?->type : null),
|
|
loading: false,
|
|
reactionCounts: {},
|
|
totalReactions: @js($profile->total_reactions),
|
|
canReact: @js(!$isOwner && Auth::check()),
|
|
|
|
init() {
|
|
// Initialize reaction counts properly
|
|
const initialCounts = @js($profile->reaction_counts);
|
|
Object.keys(initialCounts).forEach(type => {
|
|
this.reactionCounts[type] = initialCounts[type];
|
|
});
|
|
},
|
|
|
|
async toggleReaction(type) {
|
|
if (this.loading || !this.canReact) return;
|
|
this.loading = true;
|
|
|
|
try {
|
|
const csrfToken = document.querySelector('input[name=\'_token\']')?.value ||
|
|
document.querySelector('meta[name=\'csrf-token\']')?.getAttribute('content') || '';
|
|
|
|
const response = await fetch('{{ route('socialbase.profile.profile.reaction.toggle', $user) }}', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken,
|
|
'Accept': 'application/json'
|
|
},
|
|
body: JSON.stringify({ type: type })
|
|
});
|
|
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
const oldReaction = this.currentReaction;
|
|
this.currentReaction = data.user_reaction;
|
|
|
|
// Update reaction counts with proper Alpine.js reactivity
|
|
if (data.action === 'added') {
|
|
// Add new reaction
|
|
const newCounts = { ...this.reactionCounts };
|
|
newCounts[type] = (newCounts[type] || 0) + 1;
|
|
this.reactionCounts = newCounts;
|
|
this.totalReactions++;
|
|
|
|
// Remove previous reaction if it existed
|
|
if (oldReaction && oldReaction !== type) {
|
|
newCounts[oldReaction] = Math.max(0, (newCounts[oldReaction] || 0) - 1);
|
|
this.reactionCounts = newCounts;
|
|
this.totalReactions--;
|
|
}
|
|
} else if (data.action === 'removed') {
|
|
// Remove reaction
|
|
const newCounts = { ...this.reactionCounts };
|
|
newCounts[type] = Math.max(0, (newCounts[type] || 0) - 1);
|
|
this.reactionCounts = newCounts;
|
|
this.totalReactions = Math.max(0, this.totalReactions - 1);
|
|
} else if (data.action === 'updated') {
|
|
// Changed reaction type
|
|
const newCounts = { ...this.reactionCounts };
|
|
if (oldReaction) {
|
|
newCounts[oldReaction] = Math.max(0, (newCounts[oldReaction] || 0) - 1);
|
|
}
|
|
newCounts[type] = (newCounts[type] || 0) + 1;
|
|
this.reactionCounts = newCounts;
|
|
}
|
|
|
|
this.open = false;
|
|
|
|
// Show success toast
|
|
const toast = document.createElement('div');
|
|
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded shadow-lg z-[10000] transition-opacity';
|
|
toast.textContent = data.action === 'added' ? 'Reaction added!' : 'Reaction removed!';
|
|
document.body.appendChild(toast);
|
|
setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 300); }, 2000);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Reaction error:', error);
|
|
}
|
|
|
|
this.loading = false;
|
|
}
|
|
}">
|
|
<div class="flex flex-col md:flex-row md:items-start md:space-x-6">
|
|
<!-- Avatar -->
|
|
<div class="relative -mt-20 mb-4 md:mb-0">
|
|
<div class="w-40 h-40 rounded-full shadow-2xl shadow-blue-500/20 overflow-hidden bg-gradient-to-r from-blue-500/20 to-purple-500/20">
|
|
<img src="{{ $profile->avatar_url }}" alt="{{ $profile->display_name }}"
|
|
class="w-full h-full object-cover">
|
|
</div>
|
|
|
|
@if($isOwner)
|
|
|
|
@endif
|
|
</div>
|
|
|
|
<!-- Profile Details -->
|
|
<div class="flex-1 md:mt-4">
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between">
|
|
<div class="flex-1">
|
|
<div class="flex items-center justify-between">
|
|
<h1 class="text-4xl font-bold bg-gradient-to-r from-white to-gray-300 bg-clip-text text-transparent">{{ $profile->display_name }}</h1>
|
|
|
|
<div class="flex items-center gap-2">
|
|
<!-- Send Message Button -->
|
|
@if(!$isOwner && Auth::check())
|
|
<a href="{{ route('socialbase.messages.user', ['recipientId' => $user->id]) }}"
|
|
class="flex items-center gap-2 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-4 py-2 rounded-xl transition-all duration-300 hover:scale-105 shadow-lg shadow-blue-500/25">
|
|
<i class="ri-message-3-line"></i>
|
|
<span>Send Message</span>
|
|
</a>
|
|
@endif
|
|
|
|
<!-- Profile Actions Dropdown -->
|
|
<div x-show="canReact">
|
|
<div class="relative" style="overflow: visible;">
|
|
<button @click="open = !open"
|
|
class="p-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
|
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
|
|
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z"></path>
|
|
</svg>
|
|
</button>
|
|
|
|
<div x-show="open"
|
|
@click.away="open = false"
|
|
x-transition:enter="transition ease-out duration-100"
|
|
x-transition:enter-start="transform opacity-0 scale-95"
|
|
x-transition:enter-end="transform opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-75"
|
|
x-transition:leave-start="transform opacity-100 scale-100"
|
|
x-transition:leave-end="transform opacity-0 scale-95"
|
|
class="absolute right-0 mt-2 w-56 bg-white dark:bg-gray-800 rounded-md shadow-xl border border-gray-200 dark:border-gray-700 z-[9999]"
|
|
style="min-width: 200px;"
|
|
|
|
<!-- React Section -->
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
|
<p class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">React to Profile</p>
|
|
<div class="flex space-x-1 justify-center">
|
|
@foreach($availableReactions as $type => $emoji)
|
|
<button type="button"
|
|
@click="toggleReaction('{{ $type }}')"
|
|
:class="currentReaction === '{{ $type }}' ? 'bg-blue-100 dark:bg-blue-900 ring-2 ring-blue-500' : 'hover:bg-gray-100 dark:hover:bg-gray-700'"
|
|
class="p-2 rounded-lg text-2xl transition-all transform hover:scale-110"
|
|
:disabled="loading"
|
|
title="{{ ucfirst($type) }}">
|
|
{{ $emoji }}
|
|
</button>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Other Actions -->
|
|
<div class="py-1">
|
|
<button class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center transition-colors">
|
|
<i class="ri-eye-line mr-2"></i>
|
|
<span>View Activity</span>
|
|
</button>
|
|
<a href="{{ route('socialbase.messages.user', ['recipientId' => $user->id]) }}"
|
|
class="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center transition-colors">
|
|
<i class="ri-message-line mr-2"></i>
|
|
<span>Send Message</span>
|
|
</a>
|
|
</div>
|
|
|
|
@if(!$isOwner)
|
|
<!-- Report Section (Separated) -->
|
|
<div class="border-t border-gray-200 dark:border-gray-700 py-1">
|
|
<button
|
|
@click="open = false; $dispatch('open-report-modal')"
|
|
class="w-full text-left px-4 py-2 text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 flex items-center transition-colors rounded-b-md">
|
|
<i class="ri-flag-line mr-2"></i>
|
|
<span>Report User</span>
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if($profile->show_email && $profile->user->email)
|
|
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
|
<i class="ri-mail-line mr-2"></i>{{ $profile->user->email }}
|
|
</p>
|
|
@endif
|
|
|
|
@if($profile->location)
|
|
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
|
<i class="ri-map-pin-line mr-2"></i>{{ $profile->location }}
|
|
</p>
|
|
@endif
|
|
|
|
@if($profile->website)
|
|
<p class="text-gray-600 dark:text-gray-400 mt-1">
|
|
<i class="ri-link mr-2"></i>
|
|
<a href="{{ $profile->website }}" target="_blank" class="text-blue-500 hover:text-blue-600">
|
|
{{ $profile->website }}
|
|
</a>
|
|
</p>
|
|
@endif
|
|
|
|
@if($profile->show_joined_date && $profile->user->created_at)
|
|
<p class="text-gray-500 dark:text-gray-400 text-sm mt-2">
|
|
<i class="ri-calendar-line mr-2"></i>Joined {{ $profile->user->created_at->format('F Y') }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
|
|
@if($isOwner)
|
|
<div class="mt-4 md:mt-0 flex gap-3">
|
|
<a href="{{ route('socialbase.profile.edit') }}"
|
|
class="bg-primary hover:from-blue-700 hover:to-purple-700 text-white px-6 py-3 rounded-xl transition-all duration-300 hover:scale-105 inline-flex items-center shadow-lg shadow-blue-500/25">
|
|
<i class="ri-edit-line mr-2"></i>Edit Profile
|
|
</a>
|
|
<a href="{{ route('socialbase.profile.settings') }}"
|
|
class="bg-primary text-white px-6 py-3 rounded-xl transition-all duration-300 hover:scale-105 inline-flex items-center border border-gray-600/50">
|
|
<i class="ri-settings-line mr-2"></i>Settings
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
@if($profile->bio)
|
|
<div class="mt-4">
|
|
<p class="">{{ $profile->bio }}</p>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Always add consistent bottom spacing -->
|
|
<div class="mt-6 pt-6 border-t border-gray-700/50">
|
|
<!-- Profile Reactions Display -->
|
|
<div class="flex items-center flex-wrap gap-2 mb-4"
|
|
x-show="totalReactions > 0"
|
|
x-cloak>
|
|
<!-- Dynamic reaction counts -->
|
|
<template x-for="[type, count] in Object.entries(reactionCounts)" :key="type">
|
|
<div x-show="count > 0"
|
|
class="flex items-center space-x-1 bg-gradient-to-r from-blue-600/20 to-purple-600/20 hover:from-blue-600/30 hover:to-purple-600/30 border border-blue-500/30 hover:border-blue-400/50 px-4 py-2 rounded-xl cursor-pointer transition-all duration-200 hover:scale-105"
|
|
:title="count + ' ' + type.charAt(0).toUpperCase() + type.slice(1) + ' reactions'">
|
|
<span class="text-xl" x-text="@js($availableReactions)[type] || '👍'"></span>
|
|
<span class="text-sm font-semibold text-blue-300" x-text="count"></span>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
<div class="text-sm mb-4 bg-gray-700/30 px-4 py-3 rounded-xl"
|
|
x-show="totalReactions <= 0"
|
|
x-cloak>
|
|
<i class="ri-emotion-line mr-2"></i>No reactions yet
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profile Tabs -->
|
|
<livewire:socialbase.profile-tabs :user="$user" :profile="$profile" />
|
|
|
|
<!-- Profile Comments -->
|
|
@if($profile->allow_comments)
|
|
<livewire:socialbase.profile-comments :profile="$profile" :can-comment="$canComment" />
|
|
@endif
|
|
|
|
<!-- Report User Component -->
|
|
@if(!$isOwner)
|
|
<div id="report-user-container">
|
|
<livewire:socialbase.report-user :userId="$user->id" />
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|