Wire admin studio SSR and search infrastructure
This commit is contained in:
@@ -23,6 +23,58 @@
|
||||
'my' => 'My Activity',
|
||||
default => 'All Activity',
|
||||
};
|
||||
|
||||
$serverActivities = $props['initialActivities'] ?? [];
|
||||
$serverMeta = $props['initialMeta'] ?? [];
|
||||
$serverFilter = $props['initialFilter'] ?? 'all';
|
||||
$serverUserId = $props['initialUserId'] ?? null;
|
||||
$serverIsAuthenticated = (bool) ($props['isAuthenticated'] ?? false);
|
||||
$serverResultsLabel = ((int) ($serverMeta['total'] ?? count($serverActivities))) > 0
|
||||
? number_format((int) ($serverMeta['total'] ?? count($serverActivities))) . ' events'
|
||||
: 'No recent activity';
|
||||
$serverFilterTabs = [
|
||||
['key' => 'all', 'label' => 'All Activity', 'auth_required' => false],
|
||||
['key' => 'comments', 'label' => 'Comments', 'auth_required' => false],
|
||||
['key' => 'replies', 'label' => 'Replies', 'auth_required' => false],
|
||||
['key' => 'following', 'label' => 'Following', 'auth_required' => true],
|
||||
['key' => 'my', 'label' => 'My Activity', 'auth_required' => true],
|
||||
];
|
||||
$buildFilterUrl = static function (string $filterKey, ?int $userId): string {
|
||||
return route('community.activity', array_filter([
|
||||
'filter' => $filterKey !== 'all' ? $filterKey : null,
|
||||
'user_id' => $userId,
|
||||
], static fn (mixed $value): bool => $value !== null && $value !== ''));
|
||||
};
|
||||
$describeActivity = static function (array $activity): array {
|
||||
$type = (string) ($activity['type'] ?? 'activity');
|
||||
$artworkTitle = (string) data_get($activity, 'artwork.title', 'an artwork');
|
||||
$artworkUrl = data_get($activity, 'artwork.url');
|
||||
$storyTitle = (string) data_get($activity, 'story.title', 'a story');
|
||||
$storyUrl = data_get($activity, 'story.url');
|
||||
$targetUsername = (string) (data_get($activity, 'target_user.username') ?: data_get($activity, 'target_user.name', 'another creator'));
|
||||
$targetUrl = data_get($activity, 'target_user.profile_url');
|
||||
$mentionedUsername = (string) (data_get($activity, 'mentioned_user.username') ?: data_get($activity, 'mentioned_user.name', 'someone'));
|
||||
$mentionedUrl = data_get($activity, 'mentioned_user.profile_url');
|
||||
$commentAuthor = (string) (data_get($activity, 'comment.author.name') ?: data_get($activity, 'comment.author.username', 'a creator'));
|
||||
$commentAuthorUrl = data_get($activity, 'comment.author.profile_url');
|
||||
$reactionLabel = trim((string) data_get($activity, 'reaction.emoji', '')) . ' ' . (string) data_get($activity, 'reaction.label', 'Like');
|
||||
|
||||
return match ($type) {
|
||||
'upload' => $storyUrl || data_get($activity, 'story.title')
|
||||
? ['verb' => 'published', 'subject' => $storyTitle, 'subject_url' => $storyUrl, 'context' => null, 'context_url' => null]
|
||||
: ['verb' => 'published', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
'favorite' => ['verb' => 'favorited', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
'follow' => ['verb' => 'followed', 'subject' => '@' . ltrim($targetUsername, '@'), 'subject_url' => $targetUrl, 'context' => null, 'context_url' => null],
|
||||
'award' => ['verb' => 'awarded', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
'story_like' => ['verb' => 'liked', 'subject' => $storyTitle, 'subject_url' => $storyUrl, 'context' => null, 'context_url' => null],
|
||||
'story_comment' => ['verb' => 'commented on', 'subject' => $storyTitle, 'subject_url' => $storyUrl, 'context' => null, 'context_url' => null],
|
||||
'comment' => ['verb' => 'commented on', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
'reply' => ['verb' => 'replied on', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
'reaction' => ['verb' => 'reacted ' . trim($reactionLabel), 'subject' => $commentAuthor, 'subject_url' => $commentAuthorUrl, 'context' => $artworkTitle, 'context_url' => $artworkUrl],
|
||||
'mention' => ['verb' => 'mentioned', 'subject' => '@' . ltrim($mentionedUsername, '@'), 'subject_url' => $mentionedUrl, 'context' => $artworkTitle, 'context_url' => $artworkUrl],
|
||||
default => ['verb' => 'shared new activity on', 'subject' => $artworkTitle, 'subject_url' => $artworkUrl, 'context' => null, 'context_url' => null],
|
||||
};
|
||||
};
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
@@ -58,15 +110,136 @@
|
||||
|
||||
<div id="community-activity-root" class="min-h-[480px]">
|
||||
<div class="mx-auto max-w-6xl px-6 pt-8 pb-20 md:px-10">
|
||||
<div class="mb-6 rounded-[28px] border border-white/[0.06] bg-white/[0.025] p-5 shadow-[0_18px_45px_rgba(0,0,0,0.22)]">
|
||||
<div class="h-3 w-40 animate-pulse rounded bg-white/[0.08]"></div>
|
||||
<div class="mt-3 h-3 w-2/3 animate-pulse rounded bg-white/[0.06]"></div>
|
||||
<div class="mt-5 flex gap-2">
|
||||
<div class="h-10 w-28 animate-pulse rounded-full bg-white/[0.06]"></div>
|
||||
<div class="h-10 w-24 animate-pulse rounded-full bg-white/[0.05]"></div>
|
||||
<div class="h-10 w-24 animate-pulse rounded-full bg-white/[0.05]"></div>
|
||||
<div class="mb-6 flex flex-col gap-4 rounded-[28px] border border-white/[0.06] bg-[linear-gradient(180deg,rgba(10,16,26,0.92),rgba(6,10,18,0.88))] p-5 shadow-[0_20px_60px_rgba(0,0,0,0.25)]">
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.22em] text-white/35">Live community pulse</p>
|
||||
<p class="mt-2 max-w-2xl text-sm leading-6 text-white/55">
|
||||
Comments, replies, reactions, and mentions from across Skinbase in one scrolling Nova feed.
|
||||
</p>
|
||||
</div>
|
||||
<div class="text-sm font-medium text-white/45">{{ $serverResultsLabel }}</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
@foreach ($serverFilterTabs as $tab)
|
||||
@php
|
||||
$isDisabled = $tab['auth_required'] && ! $serverIsAuthenticated;
|
||||
$isActive = $serverFilter === $tab['key'];
|
||||
@endphp
|
||||
@if ($isDisabled)
|
||||
<span class="cursor-not-allowed rounded-full border border-white/[0.06] bg-white/[0.03] px-4 py-2 text-sm font-medium text-white/35 opacity-60" aria-disabled="true">
|
||||
{{ $tab['label'] }}
|
||||
</span>
|
||||
@else
|
||||
<a
|
||||
href="{{ $buildFilterUrl($tab['key'], $serverUserId) }}"
|
||||
class="rounded-full border px-4 py-2 text-sm font-medium transition-all {{ $isActive ? 'border-sky-400/30 bg-sky-500/14 text-sky-200 shadow-[0_0_0_1px_rgba(56,189,248,0.08)]' : 'border-white/[0.06] bg-white/[0.03] text-white/55 hover:border-white/15 hover:bg-white/[0.05] hover:text-white/85' }}"
|
||||
>
|
||||
{{ $tab['label'] }}
|
||||
</a>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (count($serverActivities) === 0)
|
||||
<div class="rounded-[28px] border border-white/[0.06] bg-white/[0.025] px-6 py-16 text-center">
|
||||
<div class="mx-auto mb-4 flex h-14 w-14 items-center justify-center rounded-2xl border border-white/[0.06] bg-white/[0.03] text-white/35">
|
||||
<i class="fa-solid fa-wave-square text-xl"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-white/80">No activity yet</h3>
|
||||
<p class="mx-auto mt-2 max-w-md text-sm leading-6 text-white/45">
|
||||
When creators and members interact around artworks, their activity will appear here.
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
<div class="space-y-4">
|
||||
@foreach ($serverActivities as $activity)
|
||||
@php
|
||||
$activityUser = data_get($activity, 'user', []);
|
||||
$activityArtwork = data_get($activity, 'artwork', []);
|
||||
$activityStory = data_get($activity, 'story', []);
|
||||
$activityCommentBody = trim((string) data_get($activity, 'comment.body', ''));
|
||||
$activityHeadline = $describeActivity($activity);
|
||||
$activityAvatarUrl = data_get($activityUser, 'avatar_url') ?: '/images/avatar_default.webp';
|
||||
$activityProfileUrl = data_get($activityUser, 'profile_url');
|
||||
$activityName = data_get($activityUser, 'name') ?: data_get($activityUser, 'username', 'Skinbase creator');
|
||||
$activityUsername = data_get($activityUser, 'username');
|
||||
$activityPreviewUrl = data_get($activityArtwork, 'thumb_url') ?: data_get($activityStory, 'cover_url');
|
||||
$activityPreviewAlt = data_get($activityArtwork, 'title') ?: data_get($activityStory, 'title') ?: 'Activity preview';
|
||||
$activityPreviewLink = data_get($activityArtwork, 'url') ?: data_get($activityStory, 'url');
|
||||
@endphp
|
||||
<article class="rounded-[28px] border border-white/[0.06] bg-[linear-gradient(180deg,rgba(11,16,26,0.96),rgba(7,11,19,0.92))] p-4 shadow-[0_18px_45px_rgba(0,0,0,0.28)] backdrop-blur-xl sm:p-5">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start">
|
||||
<div class="sm:w-[220px] sm:shrink-0">
|
||||
<div class="flex items-start gap-3">
|
||||
<a href="{{ $activityProfileUrl ?: '#' }}" class="shrink-0 {{ $activityProfileUrl ? '' : 'pointer-events-none' }}">
|
||||
<img src="{{ $activityAvatarUrl }}" alt="{{ $activityName }}" class="h-11 w-11 rounded-2xl object-cover" loading="lazy">
|
||||
</a>
|
||||
<div class="min-w-0">
|
||||
<p class="text-sm font-semibold text-white">
|
||||
@if ($activityProfileUrl)
|
||||
<a href="{{ $activityProfileUrl }}" class="hover:text-sky-200">{{ $activityName }}</a>
|
||||
@else
|
||||
{{ $activityName }}
|
||||
@endif
|
||||
</p>
|
||||
@if ($activityUsername)
|
||||
<p class="text-xs uppercase tracking-[0.18em] text-white/35">@{{ $activityUsername }}</p>
|
||||
@endif
|
||||
<p class="mt-2 text-[11px] uppercase tracking-[0.18em] text-white/25">{{ data_get($activity, 'time_ago', '') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1">
|
||||
<p class="text-sm leading-6 text-white/70">
|
||||
<span class="font-medium text-white">{{ $activityHeadline['verb'] }}</span>
|
||||
@if (!empty($activityHeadline['subject']))
|
||||
<span> </span>
|
||||
@if (!empty($activityHeadline['subject_url']))
|
||||
<a href="{{ $activityHeadline['subject_url'] }}" class="text-sky-300 hover:text-sky-200">{{ $activityHeadline['subject'] }}</a>
|
||||
@else
|
||||
<span class="text-white">{{ $activityHeadline['subject'] }}</span>
|
||||
@endif
|
||||
@endif
|
||||
@if (!empty($activityHeadline['context']))
|
||||
<span> on </span>
|
||||
@if (!empty($activityHeadline['context_url']))
|
||||
<a href="{{ $activityHeadline['context_url'] }}" class="text-sky-300 hover:text-sky-200">{{ $activityHeadline['context'] }}</a>
|
||||
@else
|
||||
<span class="text-white">{{ $activityHeadline['context'] }}</span>
|
||||
@endif
|
||||
@endif
|
||||
</p>
|
||||
|
||||
@if ($activityCommentBody !== '')
|
||||
<div class="mt-3 rounded-2xl border border-white/[0.06] bg-white/[0.025] px-4 py-3">
|
||||
<p class="whitespace-pre-line break-words text-sm leading-6 text-white/80">{{ \Illuminate\Support\Str::limit($activityCommentBody, 240) }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (($activity['type'] ?? null) === 'mention' && data_get($activity, 'mentioned_user.username'))
|
||||
<div class="mt-3 inline-flex items-center gap-2 rounded-full border border-sky-400/20 bg-sky-500/10 px-3 py-1 text-xs text-sky-200">
|
||||
<i class="fa-solid fa-at"></i>
|
||||
Mentioned @{{ data_get($activity, 'mentioned_user.username') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if ($activityPreviewUrl)
|
||||
<div class="sm:ml-auto sm:w-[120px] sm:shrink-0">
|
||||
<a href="{{ $activityPreviewLink ?: '#' }}" class="block overflow-hidden rounded-2xl border border-white/[0.06] bg-white/[0.03] {{ $activityPreviewLink ? '' : 'pointer-events-none' }}">
|
||||
<img src="{{ $activityPreviewUrl }}" alt="{{ $activityPreviewAlt }}" class="h-[132px] w-full object-cover" loading="lazy">
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</article>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user