Add all files
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user