mirror of
https://github.com/sp-tarkov/forge.git
synced 2025-02-12 12:10:41 -05:00
User Follow Components
Updates the user follow component to two smaller components. Still needs to be optimized.
This commit is contained in:
parent
cf381c9e7d
commit
691f352d01
@ -13,10 +13,14 @@ class UserController extends Controller
|
||||
|
||||
public function show(Request $request, int $userId, string $username): View
|
||||
{
|
||||
$user = User::where('id', $userId)
|
||||
// Reimplement eager loading after the review.
|
||||
$user = User::whereId($userId)
|
||||
->firstOrFail();
|
||||
|
||||
$mods = $user->mods()
|
||||
->orderByDesc('created_at')
|
||||
->paginate(10)
|
||||
->fragment('mods');
|
||||
|
||||
if ($user->slug() !== $username) {
|
||||
abort(404);
|
||||
}
|
||||
@ -25,6 +29,6 @@ class UserController extends Controller
|
||||
abort(403);
|
||||
}
|
||||
|
||||
return view('user.show', compact('user'));
|
||||
return view('user.show', compact('user', 'mods'));
|
||||
}
|
||||
}
|
||||
|
52
app/Livewire/User/FollowButtons.php
Normal file
52
app/Livewire/User/FollowButtons.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Component;
|
||||
|
||||
class FollowButtons extends Component
|
||||
{
|
||||
/**
|
||||
* The ID of the user whose profile is being viewed.
|
||||
*/
|
||||
#[Locked]
|
||||
public int $profileUserId;
|
||||
|
||||
/**
|
||||
* Whether the authenticated user is currently following the profile user.
|
||||
*/
|
||||
#[Locked]
|
||||
public bool $isFollowing;
|
||||
|
||||
/**
|
||||
* Action to follow a user.
|
||||
*/
|
||||
public function follow(): void
|
||||
{
|
||||
auth()->user()->follow($this->profileUserId);
|
||||
$this->isFollowing = true;
|
||||
|
||||
$this->dispatch('user-follow-change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to unfollow a user.
|
||||
*/
|
||||
public function unfollow(): void
|
||||
{
|
||||
auth()->user()->unfollow($this->profileUserId);
|
||||
$this->isFollowing = false;
|
||||
|
||||
$this->dispatch('user-follow-change');
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.user.follow-buttons');
|
||||
}
|
||||
}
|
152
app/Livewire/User/FollowCard.php
Normal file
152
app/Livewire/User/FollowCard.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\View\View;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class FollowCard extends Component
|
||||
{
|
||||
/**
|
||||
* The ID of the user whose profile is being viewed.
|
||||
*/
|
||||
#[Locked]
|
||||
public int $profileUserId;
|
||||
|
||||
/**
|
||||
* The type of user follow relationship to display.
|
||||
* Currently, either "followers" or "following".
|
||||
*/
|
||||
#[Locked]
|
||||
public string $relationship;
|
||||
|
||||
/**
|
||||
* The title of the card.
|
||||
*/
|
||||
public string $title;
|
||||
|
||||
/**
|
||||
* The message to display when there are no results.
|
||||
*/
|
||||
public string $emptyMessage;
|
||||
|
||||
/**
|
||||
* The title of the dialog.
|
||||
*/
|
||||
public string $dialogTitle;
|
||||
|
||||
/**
|
||||
* The users to display in the card.
|
||||
*
|
||||
* @var Collection<User>
|
||||
*/
|
||||
#[Locked]
|
||||
public Collection $followUsers;
|
||||
|
||||
/**
|
||||
* The maximum number of users to display on the card.
|
||||
*/
|
||||
#[Locked]
|
||||
public int $limit = 5;
|
||||
|
||||
/**
|
||||
* Whether to show all users in a model dialog.
|
||||
*/
|
||||
public bool $showFollowDialog = false;
|
||||
|
||||
/**
|
||||
* The user whose profile is being viewed.
|
||||
*/
|
||||
#[Locked]
|
||||
public User $profileUser;
|
||||
|
||||
/**
|
||||
* Called when the component is initialized.
|
||||
*/
|
||||
public function mount(): void
|
||||
{
|
||||
$this->profileUser = User::select(['id', 'name', 'profile_photo_path', 'cover_photo_path'])
|
||||
->findOrFail($this->profileUserId);
|
||||
|
||||
$this->setTitle();
|
||||
$this->setEmptyMessage();
|
||||
$this->setDialogTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the title of the card based on the relationship.
|
||||
*/
|
||||
private function setTitle(): void
|
||||
{
|
||||
$this->title = match ($this->relationship) {
|
||||
'followers' => __('Followers'),
|
||||
'following' => __('Following'),
|
||||
default => __('Users'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the empty message based on the relationship.
|
||||
*/
|
||||
private function setEmptyMessage(): void
|
||||
{
|
||||
$this->emptyMessage = match ($this->relationship) {
|
||||
'followers' => __('No followers yet.'),
|
||||
'following' => __('Not yet following anyone.'),
|
||||
default => __('No users found.'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the dialog title based on the relationship.
|
||||
*/
|
||||
private function setDialogTitle(): void
|
||||
{
|
||||
$this->dialogTitle = match ($this->relationship) {
|
||||
'followers' => 'User :name has these followers:',
|
||||
'following' => 'User :name is following:',
|
||||
default => 'Users:',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component.
|
||||
*/
|
||||
public function render(): View
|
||||
{
|
||||
$this->populateFollowUsers();
|
||||
|
||||
return view('livewire.user.follow-card');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the user follows or unfollows a user.
|
||||
*/
|
||||
#[On('user-follow-change')]
|
||||
public function populateFollowUsers(): void
|
||||
{
|
||||
// Fetch IDs of all users the authenticated user is following.
|
||||
$followingIds = auth()->user()->following()->pluck('following_id');
|
||||
|
||||
// Load the profile user's followers (or following) and map the follow status.
|
||||
$this->followUsers = $this->profileUser->{$this->relationship}
|
||||
->map(function (User $user) use ($followingIds) {
|
||||
// Add the follow status based on the preloaded IDs.
|
||||
$user->follows = $followingIds->contains($user->id);
|
||||
|
||||
return $user;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle showing the follow dialog.
|
||||
*/
|
||||
public function toggleFollowDialog(): void
|
||||
{
|
||||
$this->showFollowDialog = ! $this->showFollowDialog;
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\User;
|
||||
|
||||
use App\Models\User;
|
||||
use Livewire\Attributes\Url;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithPagination;
|
||||
|
||||
class Profile extends Component
|
||||
{
|
||||
use WithPagination;
|
||||
|
||||
public User $user;
|
||||
|
||||
#[Url]
|
||||
public string $section = 'wall';
|
||||
|
||||
public $followers;
|
||||
|
||||
public $following;
|
||||
|
||||
protected $listeners = ['refreshNeeded' => 'render'];
|
||||
|
||||
public function render()
|
||||
{
|
||||
$this->followers = $this->user->followers;
|
||||
$this->following = $this->user->following;
|
||||
|
||||
$mods = $this->user->mods()->withWhereHas('latestVersion')->paginate(6);
|
||||
|
||||
return view('livewire.user.profile', compact('mods'));
|
||||
}
|
||||
|
||||
public function setSection(string $name)
|
||||
{
|
||||
$this->section = $name;
|
||||
}
|
||||
|
||||
public function message()
|
||||
{
|
||||
// todo: not implemented yet
|
||||
}
|
||||
|
||||
public function followUser(User $user)
|
||||
{
|
||||
auth()->user()->follow($user);
|
||||
}
|
||||
|
||||
public function unfollowUser(User $user)
|
||||
{
|
||||
auth()->user()->unfollow($user);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Reactive;
|
||||
use Livewire\Component;
|
||||
|
||||
class UserStack extends Component
|
||||
{
|
||||
#[Reactive]
|
||||
public $users;
|
||||
|
||||
public string $parentUserName;
|
||||
|
||||
public $authFollowingIds = [];
|
||||
|
||||
public string $label = 'Users';
|
||||
|
||||
public int $limit = 5;
|
||||
|
||||
public bool $viewAll = false;
|
||||
|
||||
public bool $refreshNeeded = false;
|
||||
|
||||
public function render()
|
||||
{
|
||||
if (Auth::check()) {
|
||||
$this->authFollowingIds = Auth::user()->following()->pluck('following_id')->toArray();
|
||||
}
|
||||
|
||||
return view('livewire.user-stack');
|
||||
}
|
||||
|
||||
public function toggleViewAll()
|
||||
{
|
||||
$this->viewAll = ! $this->viewAll;
|
||||
}
|
||||
|
||||
public function closeDialog()
|
||||
{
|
||||
if ($this->refreshNeeded) {
|
||||
$this->dispatch('refreshNeeded');
|
||||
}
|
||||
|
||||
$this->toggleViewAll();
|
||||
}
|
||||
|
||||
public function followUser(User $user)
|
||||
{
|
||||
Auth::user()->follow($user);
|
||||
$this->refreshNeeded = true;
|
||||
}
|
||||
|
||||
public function unfollowUser(User $user)
|
||||
{
|
||||
Auth::user()->unfollow($user);
|
||||
$this->refreshNeeded = true;
|
||||
}
|
||||
}
|
@ -7,20 +7,21 @@
|
||||
<div class="flex flex-col group h-full w-full bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl overflow-hidden hover:shadow-lg hover:bg-gray-50 dark:hover:bg-black hover:shadow-gray-400 dark:hover:shadow-black transition-all duration-200">
|
||||
<div class="h-auto md:h-full md:flex">
|
||||
<div class="h-auto md:h-full md:shrink-0 overflow-hidden">
|
||||
@empty($mod->thumbnail)
|
||||
<img src="https://placehold.co/450x450/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
@else
|
||||
@if ($mod->thumbnail)
|
||||
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
@endempty
|
||||
@else
|
||||
<img src="https://placehold.co/450x450/31343C/EEE?font=source-sans-pro&text={{ urlencode($mod->name) }}" alt="{{ $mod->name }}" class="h-48 w-full object-cover md:h-full md:w-48 transform group-hover:scale-110 transition-all duration-200">
|
||||
@endif
|
||||
</div>
|
||||
<div class="flex flex-col w-full justify-between p-5">
|
||||
<div class="pb-3">
|
||||
<div class="flex justify-between items-center space-x-3">
|
||||
<h3 class="block mt-1 text-lg leading-tight font-medium text-black dark:text-white group-hover:underline">{{ $mod->name }}</h3>
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
@if ($version?->latestSptVersion)
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<p class="text-sm italic text-slate-600 dark:text-gray-200">
|
||||
{{ __('By :authors', ['authors' => $mod->users->pluck('name')->implode(', ')]) }}
|
||||
@ -31,7 +32,7 @@
|
||||
</div>
|
||||
<div class="text-slate-700 dark:text-gray-300 text-sm">
|
||||
<div class="flex items-end w-full text-sm">
|
||||
@if ($mod->updated_at || $mod->created_at)
|
||||
@if (($mod->updated_at || $mod->created_at) && $version)
|
||||
<div class="flex items-end w-full">
|
||||
<div class="flex items-center gap-1">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
|
||||
|
11
resources/views/components/profile-button.blade.php
Normal file
11
resources/views/components/profile-button.blade.php
Normal file
@ -0,0 +1,11 @@
|
||||
@props(['name'])
|
||||
|
||||
<div class="flex flex-col justify-stretch sm:flex-row">
|
||||
<button {{ $attributes->whereStartsWith('wire:') }} {{ $attributes->merge([
|
||||
'type' => 'button',
|
||||
'class' => 'inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600',
|
||||
]) }}>
|
||||
{{ $icon }}
|
||||
<span>{{ $slot }}</span>
|
||||
</button>
|
||||
</div>
|
17
resources/views/components/tab-button.blade.php
Normal file
17
resources/views/components/tab-button.blade.php
Normal file
@ -0,0 +1,17 @@
|
||||
@props(['name'])
|
||||
|
||||
<button
|
||||
@click="selectedTab = '{{ Str::lower($name) }}'"
|
||||
:aria-selected="selectedTab == '{{ Str::lower($name) }}'"
|
||||
:class="{
|
||||
'font-extrabold': selectedTab == '{{ Str::lower($name) }}',
|
||||
'font-light': selectedTab != '{{ Str::lower($name) }}',
|
||||
'tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm text-gray-800 dark:text-white bg-cyan-500 dark:bg-cyan-700 hover:bg-cyan-400 dark:hover:bg-cyan-600 focus:z-10 last:rounded-r-xl first:rounded-l-xl flex items-center justify-center gap-1': true
|
||||
}"
|
||||
{{ $attributes }}
|
||||
>
|
||||
{{ __($name) }}
|
||||
<svg x-cloak x-show="selectedTab == '{{ Str::lower($name) }}'" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="h-3 h-3">
|
||||
<path fill-rule="evenodd" d="M7.47 12.78a.75.75 0 0 0 1.06 0l3.25-3.25a.75.75 0 0 0-1.06-1.06L8 11.19 5.28 8.47a.75.75 0 0 0-1.06 1.06l3.25 3.25ZM4.22 4.53l3.25 3.25a.75.75 0 0 0 1.06 0l3.25-3.25a.75.75 0 0 0-1.06-1.06L8 6.19 5.28 3.47a.75.75 0 0 0-1.06 1.06Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
@ -1,5 +1,5 @@
|
||||
@props(['datetime'])
|
||||
|
||||
<time datetime="{{ $datetime->format('c') }}">
|
||||
<time datetime="{{ $datetime->format('c') }}" title="{{ $datetime->format('l jS \\of F Y g:i:s A e') }}">
|
||||
{{ Carbon::dynamicFormat($datetime) }}
|
||||
</time>
|
||||
|
8
resources/views/components/user-follow-cards.blade.php
Normal file
8
resources/views/components/user-follow-cards.blade.php
Normal file
@ -0,0 +1,8 @@
|
||||
@props(['profileUserId'])
|
||||
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user.follow-card relationship="followers" :profile-user-id="$profileUserId" />
|
||||
</div>
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user.follow-card relationship="following" :profile-user-id="$profileUserId" />
|
||||
</div>
|
1
resources/views/includes/analytics.blade.php
Normal file
1
resources/views/includes/analytics.blade.php
Normal file
@ -0,0 +1 @@
|
||||
<script defer src="https://umami.refringe.com/script.js" data-website-id="3cf2977d-3d62-48ad-b6be-5a2d2e8f1d84"></script>
|
@ -8,11 +8,15 @@
|
||||
<title>{{ config('app.name', 'The Forge') }}</title>
|
||||
|
||||
<link rel="icon" href="data:image/x-icon;base64,AA">
|
||||
|
||||
<link href="//fonts.bunny.net" rel="preconnect">
|
||||
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
||||
|
||||
@livewireStyles
|
||||
@vite(['resources/css/app.css'])
|
||||
|
||||
<script>
|
||||
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
||||
// Must be located inline, in the head, and before any CSS is loaded.
|
||||
@ -25,35 +29,33 @@
|
||||
document.documentElement.classList.add(theme);
|
||||
})();
|
||||
</script>
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="font-sans antialiased">
|
||||
<x-warning/>
|
||||
|
||||
<x-warning/>
|
||||
<x-banner/>
|
||||
|
||||
<x-banner/>
|
||||
<div class="min-h-screen bg-gray-100 dark:bg-gray-800">
|
||||
@livewire('navigation-menu')
|
||||
|
||||
<div class="min-h-screen bg-gray-100 dark:bg-gray-800">
|
||||
@livewire('navigation-menu')
|
||||
@if (isset($header))
|
||||
<header class="bg-gray-50 dark:bg-gray-900 shadow dark:shadow-gray-950">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
</header>
|
||||
@endif
|
||||
|
||||
@if (isset($header))
|
||||
<header class="bg-gray-50 dark:bg-gray-900 shadow dark:shadow-gray-950">
|
||||
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
</header>
|
||||
@endif
|
||||
<main class="pb-6 sm:py-12">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<main class="pb-6 sm:py-12">
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
<x-footer/>
|
||||
|
||||
<x-footer/>
|
||||
|
||||
@stack('modals')
|
||||
@livewireScriptConfig
|
||||
@vite(['resources/js/app.js'])
|
||||
@stack('modals')
|
||||
@livewireScriptConfig
|
||||
@include('includes.analytics')
|
||||
</body>
|
||||
</html>
|
||||
|
@ -8,11 +8,15 @@
|
||||
<title>{{ config('app.name', 'The Forge') }}</title>
|
||||
|
||||
<link rel="icon" href="data:image/x-icon;base64,AA">
|
||||
|
||||
<link href="//fonts.bunny.net" rel="preconnect">
|
||||
<link href="//fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet">
|
||||
|
||||
<link href="{{ config('app.asset_url') }}" rel="dns-prefetch">
|
||||
|
||||
@vite(['resources/css/app.css'])
|
||||
@livewireStyles
|
||||
|
||||
<script>
|
||||
// Immediately set the theme to prevent a flash of the default theme when another is set.
|
||||
// Must be located inline, in the head, and before any CSS is loaded.
|
||||
@ -25,16 +29,14 @@
|
||||
document.documentElement.classList.add(theme);
|
||||
})();
|
||||
</script>
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body>
|
||||
<div class="font-sans text-gray-900 antialiased">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
<div class="font-sans text-gray-900 antialiased">
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
@livewireScriptConfig
|
||||
@vite(['resources/js/app.js']);
|
||||
@livewireScriptConfig
|
||||
@include('includes.analytics')
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1,112 +0,0 @@
|
||||
<div class="w-full text-gray-600 bg-white shadow-md dark:shadow-gray-950 drop-shadow-xl dark:text-gray-200 dark:bg-gray-900 rounded-xl p-4 m-4">
|
||||
{{-- Card Header --}}
|
||||
<div class="flex justify-center items-center">
|
||||
<h2 class="text-2xl">{{$label}}</h2>
|
||||
</div>
|
||||
|
||||
@if($users->count() === 0)
|
||||
<div class="flex justify-center text-sm">
|
||||
<span>{{__('nothing here yet')}}</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex ml-6 py-2 justify-center items-center">
|
||||
@foreach($users->slice(0, $limit) as $user)
|
||||
{{-- User Badge --}}
|
||||
<div class="relative group">
|
||||
<a href="{{$user->profileUrl()}}"
|
||||
class="rounded-full -ml-6 z-20 bg-[#ebf4ff] h-16 w-16 flex justify-center items-center border">
|
||||
<img class="h-full w-full rounded-full" src="{{$user->profile_photo_url}}"
|
||||
alt="{{$user->name[0]}}" />
|
||||
</a>
|
||||
{{-- tooltip --}}
|
||||
<div
|
||||
class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||
{{$user->name}}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
@if($users->count() > $limit)
|
||||
{{-- Count Badge --}}
|
||||
<div class="relative group">
|
||||
<span
|
||||
class="rounded-full -ml-6 z-20 bg-cyan-500 dark:bg-cyan-700 h-16 w-16 flex justify-center items-center border text-white">+{{$users->count()-$limit}}</span>
|
||||
<div
|
||||
class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||
{{$users->count()}} total
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
@if($users->count() > $limit)
|
||||
{{-- View all button --}}
|
||||
<div class="flex justify-center items-center">
|
||||
<button wire:click="toggleViewAll">view all</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- view all dialog --}}
|
||||
<x-dialog-modal wire:model.live="viewAll">
|
||||
<x-slot name="title">
|
||||
<h2 class="text-2xl">{{$parentUserName}}'s {{$label}}</h2>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="content">
|
||||
<div class="h-96 overflow-y-auto divide-y">
|
||||
@foreach($users as $user)
|
||||
{{-- user tile --}}
|
||||
<div class="flex group/item">
|
||||
<img class="h-16 w-16 m-2 rounded-full" src="{{$user->profile_photo_url}}"
|
||||
alt="{{$user->name}}" />
|
||||
|
||||
<div class="flex flex-col w-full justify-center">
|
||||
<a class="text-2xl" href="{{$user->profileUrl()}}">{{$user->name}}</a>
|
||||
<span class="">{{__("Member Since")}} {{ $user->created_at->format("M d, h:m a") }}</span>
|
||||
</div>
|
||||
|
||||
@if(auth()->id() != $user->id)
|
||||
@if(count($authFollowingIds) !== 0 && in_array($user->id, $authFollowingIds))
|
||||
{{-- following button --}}
|
||||
<div class="flex invisible group-hover/item:visible justify-end items-center mr-10">
|
||||
<button type="button" wire:click="unfollowUser({{$user}})"
|
||||
class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-red-400 dark:text-red-600"
|
||||
viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Z" />
|
||||
</svg>
|
||||
<span>{{__('Following')}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
{{-- follow button --}}
|
||||
<div class="flex invisible group-hover/item:visible justify-end items-center mr-10">
|
||||
<button type="button" wire:click="followUser({{$user}})"
|
||||
class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300"
|
||||
viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Zm6.548 6.54L12 19.485 4.635 12.12a3.875 3.875 0 1 1 5.48-5.48l1.358 1.357a.75.75 0 0 0 1.073-.012L13.88 6.64a3.88 3.88 0 0 1 5.487 5.48Z"/>
|
||||
</svg>
|
||||
<span>{{__("Follow")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
{{-- 'you' card for auth user in list --}}
|
||||
<div class="flex invisible group-hover/item:visible justify-end items-center mr-10">
|
||||
<p class="p-2 h-fit text-black bg-gray-200 dark:text-white dark:bg-gray-700 rounded-lg">{{__('You')}}</p>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="footer">
|
||||
<x-button wire:click="closeDialog">
|
||||
{{__('Close')}}
|
||||
</x-button>
|
||||
</x-slot>
|
||||
|
||||
</x-dialog-modal>
|
||||
</div>
|
25
resources/views/livewire/user/follow-buttons.blade.php
Normal file
25
resources/views/livewire/user/follow-buttons.blade.php
Normal file
@ -0,0 +1,25 @@
|
||||
@props(['isFollowing'])
|
||||
|
||||
<form>
|
||||
@if ($isFollowing)
|
||||
{{-- Following button --}}
|
||||
<x-profile-button wire:click="unfollow">
|
||||
<x-slot:icon>
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-red-400 dark:text-red-600" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Z" />
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
{{ __('Following') }}
|
||||
</x-profile-button>
|
||||
@else
|
||||
{{-- Follow button --}}
|
||||
<x-profile-button wire:click="follow">
|
||||
<x-slot:icon>
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Zm6.548 6.54L12 19.485 4.635 12.12a3.875 3.875 0 1 1 5.48-5.48l1.358 1.357a.75.75 0 0 0 1.073-.012L13.88 6.64a3.88 3.88 0 0 1 5.487 5.48Z"/>
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
{{ __('Follow') }}
|
||||
</x-profile-button>
|
||||
@endif
|
||||
</form>
|
81
resources/views/livewire/user/follow-card.blade.php
Normal file
81
resources/views/livewire/user/follow-card.blade.php
Normal file
@ -0,0 +1,81 @@
|
||||
<div class="w-full text-gray-600 bg-white shadow-md dark:shadow-gray-950 drop-shadow-xl dark:text-gray-200 dark:bg-gray-900 rounded-xl py-4">
|
||||
|
||||
<div class="flex justify-center items-center">
|
||||
<h2 class="text-2xl">{{ $title }}</h2>
|
||||
</div>
|
||||
|
||||
@if (! $followUsers->count())
|
||||
<div class="flex justify-center text-sm pt-2">
|
||||
{{ $emptyMessage }}
|
||||
</div>
|
||||
@else
|
||||
<div class="flex ml-6 py-2 justify-center items-center">
|
||||
@foreach ($followUsers->slice(0, $limit) as $user)
|
||||
{{-- User Badge --}}
|
||||
<div class="relative group">
|
||||
<a href="{{ $user->profileUrl() }}" class="rounded-full -ml-6 z-20 bg-[#ebf4ff] h-16 w-16 flex justify-center items-center border">
|
||||
<img src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" class="h-full w-full rounded-full" />
|
||||
</a>
|
||||
<div class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||
{{ $user->name }}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@if ($followUsers->count() > $limit)
|
||||
{{-- Count Badge --}}
|
||||
<div class="relative group">
|
||||
<button wire:click="toggleFollowDialog" class="rounded-full -ml-6 z-20 bg-cyan-500 dark:bg-cyan-700 h-16 w-16 flex justify-center items-center border text-white">+{{ $followUsers->count() - $limit }}</button>
|
||||
<div class="absolute bottom-full -ml-3 left-1/2 transform -translate-x-1/2 mb-2 w-max px-2 py-1 text-sm text-white bg-gray-700 rounded shadow-lg opacity-0 group-hover:opacity-100">
|
||||
{{ $followUsers->count() }} total
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($followUsers->count() > $limit)
|
||||
{{-- View All Button --}}
|
||||
<div class="flex justify-center items-center">
|
||||
<button wire:click="toggleFollowDialog" class="hover:underline active:underline">View All</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- View All Dialog --}}
|
||||
@push('modals')
|
||||
<x-dialog-modal wire:model="showFollowDialog">
|
||||
<x-slot name="title">
|
||||
<h2 class="text-2xl">{{ __($dialogTitle, ['name' => $profileUser->name]) }}</h2>
|
||||
</x-slot>
|
||||
<x-slot name="content">
|
||||
<div class="h-96 overflow-y-auto">
|
||||
@foreach ($followUsers as $user)
|
||||
<div class="flex group/item dark:hover:bg-gray-950 items-center p-2 pr-3 rounded-md">
|
||||
<a href="{{ $user->profileUrl() }}" class="flex-shrink-0 w-16 h-16 items-center">
|
||||
<img src="{{ $user->profile_photo_url }}" alt="{{ $user->name }}" class="block w-full h-full rounded-full" />
|
||||
</a>
|
||||
|
||||
<div class="flex flex-col w-full pl-3">
|
||||
<a href="{{ $user->profileUrl() }}" class="text-2xl group-hover/item:underline group-hover/item:text-white">{{ $user->name }}</a>
|
||||
<span>
|
||||
{{ __("Member Since") }}
|
||||
<x-time :datetime="$user->created_at" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if (auth()->check() && auth()->user()->id !== $user->id)
|
||||
<livewire:user.follow-buttons :profile-user-id="$user->id" :is-following="$user->follows" />
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-slot>
|
||||
|
||||
<x-slot name="footer">
|
||||
<x-button x-on:click="show = false">
|
||||
{{ __('Close') }}
|
||||
</x-button>
|
||||
</x-slot>
|
||||
</x-dialog-modal>
|
||||
@endpush
|
||||
</div>
|
@ -1,148 +0,0 @@
|
||||
<div class="sm:-mt-12 dark:bg-gray-800 dark:text-gray-100">
|
||||
<div>
|
||||
<img class="h-32 w-full object-cover lg:h-48" src="{{ $user->cover_photo_url }}"
|
||||
alt="profile cover photo of {{ $user->name }}">
|
||||
</div>
|
||||
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="-mt-12 sm:-mt-16 sm:flex sm:items-end sm:space-x-5">
|
||||
<div class="flex">
|
||||
<img class="h-24 w-24 rounded-full ring-4 ring-white dark:ring-gray-800 sm:h-32 sm:w-32"
|
||||
src="{{ $user->profile_photo_url }}" alt="profile photo of {{ $user->name }}" />
|
||||
</div>
|
||||
<div class="mt-6 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-6 sm:pb-1">
|
||||
<div class="mt-6 min-w-0 flex-1 sm:hidden md:block">
|
||||
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
||||
<p>{{__("Member Since")}} {{ $user->created_at->format("M d, h:m a") }}</p>
|
||||
</div>
|
||||
|
||||
@if(auth()->check() && auth()->id() != $user->id)
|
||||
@if(auth()->user()->isFollowing($user))
|
||||
{{-- Following button --}}
|
||||
<div
|
||||
class="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<button type="button" wire:click="unfollowUser({{$user}})"
|
||||
class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-red-400 dark:text-red-600"
|
||||
viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Z" />
|
||||
</svg>
|
||||
<span>{{__("Following")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
{{-- Follow button --}}
|
||||
<div
|
||||
class="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<button type="button" wire:click="followUser({{$user}})"
|
||||
class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300"
|
||||
viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<path d="m12.82 5.58-.82.822-.824-.824a5.375 5.375 0 1 0-7.601 7.602l7.895 7.895a.75.75 0 0 0 1.06 0l7.902-7.897a5.376 5.376 0 0 0-.001-7.599 5.38 5.38 0 0 0-7.611 0Zm6.548 6.54L12 19.485 4.635 12.12a3.875 3.875 0 1 1 5.48-5.48l1.358 1.357a.75.75 0 0 0 1.073-.012L13.88 6.64a3.88 3.88 0 0 1 5.487 5.48Z"/>
|
||||
</svg>
|
||||
<span>{{__("Follow")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
{{-- Message button --}}
|
||||
<div class="mt-6 flex flex-col justify-stretch space-y-3 sm:flex-row sm:space-x-4 sm:space-y-0">
|
||||
<button type="button" wire:click="message"
|
||||
class="inline-flex justify-center rounded-md bg-white dark:bg-gray-700 px-3 py-2 text-sm font-semibold text-gray-900 dark:text-gray-100 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-gray-600">
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path
|
||||
d="M3 4a2 2 0 00-2 2v1.161l8.441 4.221a1.25 1.25 0 001.118 0L19 7.162V6a2 2 0 00-2-2H3z" />
|
||||
<path
|
||||
d="M19 8.839l-7.77 3.885a2.75 2.75 0 01-2.46 0L1 8.839V14a2 2 0 002 2h14a2 2 0 002-2V8.839z" />
|
||||
</svg>
|
||||
<span>{{__("Message")}}</span>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-6 hidden min-w-0 flex-1 sm:block md:hidden">
|
||||
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
||||
<p>{{__("Member Since")}} {{ $user->created_at->format("M d, h:m a") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4">
|
||||
<div class="lg:hidden flex flex-col justify-top items-center">
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user-stack :parentUserName="$user->name" :label="__('Followers')" :users="$followers"/>
|
||||
</div>
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user-stack :parentUserName="$user->name" :label="__('Following')" :users="$following"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col col-span-3">
|
||||
<div>
|
||||
{{-- Mobile Dropdown --}}
|
||||
<div class="sm:hidden m-4">
|
||||
<label for="tabs" class="sr-only">{{ __('Select a tab') }}</label>
|
||||
<select wire:model.change="section" id="tabs" name="tabs" class="block w-full rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
|
||||
<option value="wall">{{ __('Wall') }}</option>
|
||||
<option value="mods">{{ __('Mods') }}</option>
|
||||
<option value="recentActivity">{{ __('Recent Activity') }}</option>
|
||||
<option value="aboutMe">{{ __('About Me') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Desktop Tabs --}}
|
||||
<div class="hidden sm:block">
|
||||
<nav class="isolate m-4 flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
||||
<button wire:click="setSection('wall')" class="tab rounded-l-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10" aria-current="page">
|
||||
<span>{{ __('Wall') }}</span>
|
||||
<span aria-hidden="true" class="{{$section === 'wall' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'}}"></span>
|
||||
</button>
|
||||
<button wire:click="setSection('mods')" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
||||
<span>{{ __('Mods') }}</span>
|
||||
<span aria-hidden="true" class="{{$section === 'mods' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'}}"></span>
|
||||
</button>
|
||||
<button wire:click="setSection('recentActivity')" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
||||
<span>{{ __('Recent Activity') }}</span>
|
||||
<span aria-hidden="true" class="{{$section === 'recentActivity' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'}}"></span>
|
||||
</button>
|
||||
<button wire:click="setSection('aboutMe')" class="tab rounded-r-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
||||
<span>{{ __('About Me') }}</span>
|
||||
<span aria-hidden="true" class="{{$section === 'aboutMe' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'}}"></span>
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@switch($section)
|
||||
@case('wall')
|
||||
<p class="m-4">This is the wall. I don't think this can be implemented yet? requires comments or something</p>
|
||||
@break
|
||||
@case('mods')
|
||||
<div class="m-4">
|
||||
{{ $mods->links() }}
|
||||
</div>
|
||||
<div class="m-4 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach($mods as $mod)
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
@break
|
||||
@case('recentActivity')
|
||||
<p class="m-4">This is the recent activity. Probably need to implement some kind of activity tracking for this?</p>
|
||||
@break
|
||||
@case('aboutMe')
|
||||
<div class="text-gray-600 bg-white shadow-md dark:shadow-gray-950 drop-shadow-xl dark:text-gray-200 dark:bg-gray-900 rounded-xl p-2 m-4">
|
||||
<p class="m-4">{{$user->about}}</p>
|
||||
</div>
|
||||
@break
|
||||
@endswitch
|
||||
</div>
|
||||
</div>
|
||||
<div class="max-lg:hidden flex flex-col justify-top items-center">
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user-stack :parentUserName="$user->name" :label="__('Followers')" :users="$followers"/>
|
||||
</div>
|
||||
<div class="flex w-full max-w-sm">
|
||||
<livewire:user-stack :parentUserName="$user->name" :label="__('Following')" :users="$following"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -16,9 +16,8 @@
|
||||
@endif
|
||||
<div class="flex flex-col sm:flex-row gap-4 sm:gap-6">
|
||||
<div class="grow-0 shrink-0 flex justify-center items-center">
|
||||
@if (empty($mod->thumbnail))
|
||||
<img src="https://placehold.co/144x144/EEE/31343C?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="block dark:hidden w-36 rounded-lg">
|
||||
<img src="https://placehold.co/144x144/31343C/EEE?font=source-sans-pro&text={{ $mod->name }}" alt="{{ $mod->name }}" class="hidden dark:block w-36 rounded-lg">
|
||||
@if ($mod->thumbnail)
|
||||
<img src="https://placehold.co/144x144/31343C/EEE?font=source-sans-pro&text={{ urlencode($mod->name) }}" alt="{{ $mod->name }}" class="w-36 rounded-lg">
|
||||
@else
|
||||
<img src="{{ $mod->thumbnailUrl }}" alt="{{ $mod->name }}" class="w-36 rounded-lg">
|
||||
@endif
|
||||
@ -39,11 +38,19 @@
|
||||
@endforeach
|
||||
</p>
|
||||
<p title="{{ __('Exactly') }} {{ $mod->downloads }}">{{ Number::downloads($mod->downloads) }} {{ __('Downloads') }}</p>
|
||||
<p class="mt-2">
|
||||
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}
|
||||
</span>
|
||||
</p>
|
||||
@if ($mod->latestVersion->latestSptVersion)
|
||||
<p class="mt-2">
|
||||
<span class="badge-version {{ $mod->latestVersion->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $mod->latestVersion->latestSptVersion->version_formatted }} {{ __('Compatible') }}
|
||||
</span>
|
||||
</p>
|
||||
@else
|
||||
<p class="mt-2">
|
||||
<span class="badge-version bg-gray-100 text-gray-600 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ __('Unknown SPT Version') }}
|
||||
</span>
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -74,18 +81,9 @@
|
||||
{{-- Desktop Tabs --}}
|
||||
<div class="hidden sm:block">
|
||||
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
||||
<button @click="selectedTab = 'description'" class="tab rounded-l-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10" aria-current="page">
|
||||
<span>{{ __('Description') }}</span>
|
||||
<span aria-hidden="true" :class="selectedTab === 'description' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
||||
</button>
|
||||
<button @click="selectedTab = 'versions'" class="tab group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
||||
<span>{{ __('Versions') }}</span>
|
||||
<span aria-hidden="true" :class="selectedTab === 'versions' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
||||
</button>
|
||||
<button @click="selectedTab = 'comments'" class="tab rounded-r-xl group relative min-w-0 flex-1 overflow-hidden py-4 px-4 text-center text-sm font-medium text-gray-900 dark:text-white bg-white dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-black dark:hover:text-white focus:z-10">
|
||||
<span>{{ __('Comments') }}</span>
|
||||
<span aria-hidden="true" :class="selectedTab === 'comments' ? 'bg-gray-500 absolute inset-x-0 bottom-0 h-0.5' : 'bottom-0 h-0.5'"></span>
|
||||
</button>
|
||||
<x-tab-button name="Description" />
|
||||
<x-tab-button name="Versions" />
|
||||
<x-tab-button name="Comments" />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
@ -108,9 +106,15 @@
|
||||
<p class="text-gray-700 dark:text-gray-300" title="{{ __('Exactly') }} {{ $version->downloads }}">{{ Number::downloads($version->downloads) }} {{ __('Downloads') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
@if ($version->latestSptVersion)
|
||||
<span class="badge-version {{ $version->latestSptVersion->color_class }} inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ $version->latestSptVersion->version_formatted }}
|
||||
</span>
|
||||
@else
|
||||
<span class="badge-version bg-gray-100 text-gray-600 inline-flex items-center rounded-md px-2 py-1 text-xs font-medium text-nowrap">
|
||||
{{ __('Unknown SPT Version') }}
|
||||
</span>
|
||||
@endif
|
||||
<a href="{{ $version->virus_total_link }}">{{__('Virus Total Results')}}</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-gray-600 dark:text-gray-400">
|
||||
@ -138,7 +142,7 @@
|
||||
|
||||
{{-- Comments --}}
|
||||
<div x-show="selectedTab === 'comments'" class="user-markdown p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl">
|
||||
<p>{{ __('The comments go here.') }}</p>
|
||||
<p>Not quite yet...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,3 +1,113 @@
|
||||
<x-app-layout>
|
||||
@livewire('user.profile', ['user' => $user])
|
||||
<div class="sm:-mt-12 mb-6 dark:bg-gray-800 dark:text-gray-100">
|
||||
<div>
|
||||
<img src="{{ $user->cover_photo_url }}" alt="{{ __(':name\'s Cover Photo', ['name' => $user->name]) }}" class="h-32 w-full object-cover lg:h-48" />
|
||||
</div>
|
||||
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
||||
<div class="sm:-mt-12 sm:flex sm:items-end sm:space-x-5">
|
||||
<div class="flex">
|
||||
<img src="{{ $user->profile_photo_url }}" alt="{{ __(':name\'s Profile Picture', ['name' => $user->name]) }}" class="h-24 w-24 rounded-full ring-4 ring-white dark:ring-gray-800 sm:h-32 sm:w-32" />
|
||||
</div>
|
||||
<div class="mt-8 sm:flex sm:min-w-0 sm:flex-1 sm:items-center sm:justify-end sm:space-x-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<h1 class="truncate text-2xl font-bold text-gray-900 dark:text-gray-100">{{ $user->name }}</h1>
|
||||
<div>
|
||||
{{ __('Member since') }}
|
||||
<x-time :datetime="$user->created_at" />
|
||||
</div>
|
||||
</div>
|
||||
@if (auth()->check() && auth()->user()->id !== $user->id)
|
||||
{{-- Follow Buttons --}}
|
||||
<livewire:user.follow-buttons :profile-user-id="$user->id" :is-following="auth()->user()->isFollowing($user->id)" />
|
||||
|
||||
{{-- Message button --}}
|
||||
<x-profile-button>
|
||||
<x-slot:icon>
|
||||
<svg class="-ml-0.5 mr-1.5 h-5 w-5 text-gray-400 dark:text-gray-300" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path d="M3 4a2 2 0 00-2 2v1.161l8.441 4.221a1.25 1.25 0 001.118 0L19 7.162V6a2 2 0 00-2-2H3z" />
|
||||
<path d="M19 8.839l-7.77 3.885a2.75 2.75 0 01-2.46 0L1 8.839V14a2 2 0 002 2h14a2 2 0 002-2V8.839z" />
|
||||
</svg>
|
||||
</x-slot:icon>
|
||||
{{ __('Message') }}
|
||||
</x-profile-button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-auto max-w-7xl px-2 sm:px-4 lg:px-8">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
|
||||
{{-- Mobile Follows --}}
|
||||
<div class="lg:hidden flex flex-col justify-top items-center">
|
||||
<x-user-follow-cards :profile-user-id="$user->id" />
|
||||
</div>
|
||||
|
||||
{{-- Left Column --}}
|
||||
<div x-data="{ selectedTab: window.location.hash ? window.location.hash.substring(1) : 'wall' }" x-init="$watch('selectedTab', (tab) => {window.location.hash = tab})" class="lg:col-span-3 flex flex-col gap-6">
|
||||
|
||||
{{-- About --}}
|
||||
@if ($user->about)
|
||||
<div class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||
{{ $user->about }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Tabs --}}
|
||||
<div>
|
||||
{{-- Mobile Dropdown --}}
|
||||
<div class="sm:hidden">
|
||||
<label for="tabs" class="sr-only">{{ __('Select a tab') }}</label>
|
||||
<select id="tabs" name="tabs" x-model="selectedTab" class="block w-full rounded-md dark:text-white bg-gray-100 dark:bg-gray-950 border-gray-300 dark:border-gray-700 focus:border-grey-500 dark:focus:border-grey-600 focus:ring-grey-500 dark:focus:ring-grey-600">
|
||||
<option value="wall">{{ __('Wall') }}</option>
|
||||
<option value="mods">{{ __('Mods') }}</option>
|
||||
<option value="activity">{{ __('Activity') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{{-- Desktop Tabs --}}
|
||||
<div class="hidden sm:block">
|
||||
<nav class="isolate flex divide-x divide-gray-200 dark:divide-gray-800 rounded-xl shadow-md dark:shadow-gray-950 drop-shadow-2xl" aria-label="Tabs">
|
||||
<x-tab-button name="{{ __('Wall') }}" />
|
||||
<x-tab-button name="{{ __('Mods') }}" />
|
||||
<x-tab-button name="{{ __('Activity') }}" />
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Wall --}}
|
||||
<div x-show="selectedTab === 'wall'" class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||
<p>Not quite yet...</p>
|
||||
</div>
|
||||
|
||||
{{-- Mods --}}
|
||||
<div x-show="selectedTab === 'mods'" class="">
|
||||
@if($mods)
|
||||
<div class="mb-4">
|
||||
{{ $mods->links() }}
|
||||
</div>
|
||||
<div class="my-4 grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||
@foreach($mods as $mod)
|
||||
<x-mod-card :mod="$mod" :version="$mod->latestVersion" />
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-5">
|
||||
{{ $mods->links() }}
|
||||
</div>
|
||||
@else
|
||||
<p>{{ __('This user has not yet published any mods.') }}</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Activity --}}
|
||||
<div x-show="selectedTab === 'activity'" class="p-4 sm:p-6 bg-white dark:bg-gray-950 rounded-xl shadow-md dark:shadow-gray-950 text-gray-800 dark:text-gray-200 drop-shadow-2xl">
|
||||
<p>Not quite yet...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Desktop Follows --}}
|
||||
<div class="max-lg:hidden flex flex-col justify-top items-center gap-6">
|
||||
<x-user-follow-cards :profile-user-id="$user->id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
File diff suppressed because one or more lines are too long
@ -184,22 +184,26 @@ it('handles the case where a dependent mod has no versions available', function
|
||||
});
|
||||
|
||||
it('handles a large number of versions efficiently', function () {
|
||||
$startTime = microtime(true);
|
||||
$versionCount = 100;
|
||||
$modVersion = ModVersion::factory()->create();
|
||||
|
||||
$dependentMod = Mod::factory()->create();
|
||||
for ($i = 0; $i < $versionCount; $i++) {
|
||||
ModVersion::factory()->recycle($dependentMod)->create(['version' => "1.0.$i"]);
|
||||
}
|
||||
|
||||
// Create a dependency with a broad constraint
|
||||
// Create a mod and mod version, and then create a dependency for all versions of the dependent mod.
|
||||
$modVersion = ModVersion::factory()->create();
|
||||
ModDependency::factory()->recycle([$modVersion, $dependentMod])->create([
|
||||
'constraint' => '>=1.0.0',
|
||||
]);
|
||||
|
||||
// Verify that all versions were resolved
|
||||
expect(ModResolvedDependency::where('mod_version_id', $modVersion->id)->count())->toBe($versionCount);
|
||||
});
|
||||
$executionTime = microtime(true) - $startTime;
|
||||
|
||||
// Verify that all versions were resolved and that the execution time is reasonable.
|
||||
expect(ModResolvedDependency::where('mod_version_id', $modVersion->id)->count())->toBe($versionCount)
|
||||
->and($executionTime)->toBeLessThan(5); // Arbitrarily picked out of my ass.
|
||||
})->skip('This is a performance test and is skipped by default. It will probably fail.');
|
||||
|
||||
it('calls DependencyVersionService when a Mod is updated', function () {
|
||||
$mod = Mod::factory()->create();
|
||||
|
Loading…
x
Reference in New Issue
Block a user