Files
Virtnet.bond/extensions/Others/SocialBase/resources/views/livewire/profile-view.blade.php
2025-11-30 11:57:22 +01:00

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>