200 lines
8.7 KiB
PHP
200 lines
8.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Studio;
|
|
|
|
use App\Models\NovaCard;
|
|
use App\Models\NovaCardChallenge;
|
|
use App\Models\NovaCardChallengeEntry;
|
|
use App\Models\User;
|
|
use Illuminate\Support\Collection;
|
|
|
|
final class CreatorStudioChallengeService
|
|
{
|
|
public function build(User $user): array
|
|
{
|
|
$entryCounts = NovaCardChallengeEntry::query()
|
|
->where('user_id', $user->id)
|
|
->whereNotIn('status', [NovaCardChallengeEntry::STATUS_HIDDEN, NovaCardChallengeEntry::STATUS_REJECTED])
|
|
->selectRaw('challenge_id, COUNT(*) as aggregate')
|
|
->groupBy('challenge_id')
|
|
->pluck('aggregate', 'challenge_id');
|
|
|
|
$recentEntriesQuery = NovaCardChallengeEntry::query()
|
|
->with(['challenge', 'card'])
|
|
->where('user_id', $user->id)
|
|
->whereNotIn('status', [NovaCardChallengeEntry::STATUS_HIDDEN, NovaCardChallengeEntry::STATUS_REJECTED])
|
|
->whereHas('challenge')
|
|
->whereHas('card');
|
|
|
|
$recentEntries = $recentEntriesQuery
|
|
->latest('created_at')
|
|
->limit(8)
|
|
->get()
|
|
->map(fn (NovaCardChallengeEntry $entry): array => $this->mapEntry($entry))
|
|
->values()
|
|
->all();
|
|
|
|
$activeChallenges = NovaCardChallenge::query()
|
|
->whereIn('status', [NovaCardChallenge::STATUS_ACTIVE, NovaCardChallenge::STATUS_COMPLETED])
|
|
->orderByRaw('CASE WHEN featured = 1 THEN 0 ELSE 1 END')
|
|
->orderByRaw("CASE WHEN status = 'active' THEN 0 ELSE 1 END")
|
|
->orderBy('ends_at')
|
|
->orderByDesc('starts_at')
|
|
->limit(10)
|
|
->get()
|
|
->map(fn (NovaCardChallenge $challenge): array => $this->mapChallenge($challenge, $entryCounts))
|
|
->values();
|
|
|
|
$spotlight = $activeChallenges->first();
|
|
|
|
$cardLeaders = NovaCard::query()
|
|
->where('user_id', $user->id)
|
|
->whereNull('deleted_at')
|
|
->where('challenge_entries_count', '>', 0)
|
|
->orderByDesc('challenge_entries_count')
|
|
->orderByDesc('published_at')
|
|
->limit(6)
|
|
->get()
|
|
->map(fn (NovaCard $card): array => [
|
|
'id' => (int) $card->id,
|
|
'title' => (string) $card->title,
|
|
'status' => (string) $card->status,
|
|
'challenge_entries_count' => (int) $card->challenge_entries_count,
|
|
'views_count' => (int) $card->views_count,
|
|
'comments_count' => (int) $card->comments_count,
|
|
'preview_url' => route('studio.cards.preview', ['id' => $card->id]),
|
|
'edit_url' => route('studio.cards.edit', ['id' => $card->id]),
|
|
'analytics_url' => route('studio.cards.analytics', ['id' => $card->id]),
|
|
])
|
|
->values()
|
|
->all();
|
|
|
|
$cardsAvailable = (int) NovaCard::query()
|
|
->where('user_id', $user->id)
|
|
->whereNull('deleted_at')
|
|
->whereNotIn('status', [NovaCard::STATUS_HIDDEN, NovaCard::STATUS_REJECTED])
|
|
->count();
|
|
|
|
$entryCount = (int) $entryCounts->sum();
|
|
$featuredEntries = (int) (clone $recentEntriesQuery)
|
|
->whereIn('status', [NovaCardChallengeEntry::STATUS_FEATURED, NovaCardChallengeEntry::STATUS_WINNER])
|
|
->count();
|
|
$winnerEntries = (int) (clone $recentEntriesQuery)
|
|
->where('status', NovaCardChallengeEntry::STATUS_WINNER)
|
|
->count();
|
|
|
|
return [
|
|
'summary' => [
|
|
'active_challenges' => (int) $activeChallenges->where('status', NovaCardChallenge::STATUS_ACTIVE)->count(),
|
|
'joined_challenges' => (int) $entryCounts->keys()->count(),
|
|
'entries_submitted' => $entryCount,
|
|
'featured_entries' => $featuredEntries,
|
|
'winner_entries' => $winnerEntries,
|
|
'cards_available' => $cardsAvailable,
|
|
],
|
|
'spotlight' => $spotlight,
|
|
'active_challenges' => $activeChallenges->all(),
|
|
'recent_entries' => $recentEntries,
|
|
'card_leaders' => $cardLeaders,
|
|
'reminders' => $this->reminders($cardsAvailable, $entryCount, $activeChallenges, $featuredEntries, $winnerEntries),
|
|
];
|
|
}
|
|
|
|
private function mapChallenge(NovaCardChallenge $challenge, Collection $entryCounts): array
|
|
{
|
|
return [
|
|
'id' => (int) $challenge->id,
|
|
'slug' => (string) $challenge->slug,
|
|
'title' => (string) $challenge->title,
|
|
'description' => $challenge->description,
|
|
'prompt' => $challenge->prompt,
|
|
'status' => (string) $challenge->status,
|
|
'official' => (bool) $challenge->official,
|
|
'featured' => (bool) $challenge->featured,
|
|
'entries_count' => (int) $challenge->entries_count,
|
|
'starts_at' => $challenge->starts_at?->toIso8601String(),
|
|
'ends_at' => $challenge->ends_at?->toIso8601String(),
|
|
'is_joined' => $entryCounts->has($challenge->id),
|
|
'submission_count' => (int) ($entryCounts->get($challenge->id) ?? 0),
|
|
'url' => route('cards.challenges.show', ['slug' => $challenge->slug]),
|
|
];
|
|
}
|
|
|
|
private function mapEntry(NovaCardChallengeEntry $entry): array
|
|
{
|
|
return [
|
|
'id' => (int) $entry->id,
|
|
'status' => (string) $entry->status,
|
|
'submitted_at' => $entry->created_at?->toIso8601String(),
|
|
'note' => $entry->note,
|
|
'challenge' => [
|
|
'id' => (int) $entry->challenge_id,
|
|
'title' => (string) ($entry->challenge?->title ?? 'Challenge'),
|
|
'status' => (string) ($entry->challenge?->status ?? ''),
|
|
'official' => (bool) ($entry->challenge?->official ?? false),
|
|
'url' => $entry->challenge ? route('cards.challenges.show', ['slug' => $entry->challenge->slug]) : route('cards.challenges'),
|
|
],
|
|
'card' => [
|
|
'id' => (int) $entry->card_id,
|
|
'title' => (string) ($entry->card?->title ?? 'Card'),
|
|
'preview_url' => $entry->card ? route('studio.cards.preview', ['id' => $entry->card->id]) : route('studio.cards.index'),
|
|
'edit_url' => $entry->card ? route('studio.cards.edit', ['id' => $entry->card->id]) : route('studio.cards.create'),
|
|
'analytics_url' => $entry->card ? route('studio.cards.analytics', ['id' => $entry->card->id]) : route('studio.cards.index'),
|
|
],
|
|
];
|
|
}
|
|
|
|
private function reminders(int $cardsAvailable, int $entryCount, Collection $activeChallenges, int $featuredEntries, int $winnerEntries): array
|
|
{
|
|
$items = [];
|
|
|
|
if ($cardsAvailable === 0) {
|
|
$items[] = [
|
|
'title' => 'Create a card to join challenges',
|
|
'body' => 'Challenge participation starts from published or ready-to-share cards inside Studio.',
|
|
'href' => route('studio.cards.create'),
|
|
'cta' => 'Create card',
|
|
];
|
|
}
|
|
|
|
if ($cardsAvailable > 0 && $entryCount === 0 && $activeChallenges->where('status', NovaCardChallenge::STATUS_ACTIVE)->isNotEmpty()) {
|
|
$items[] = [
|
|
'title' => 'You have active challenge windows open',
|
|
'body' => 'Submit an existing card to the current prompt lineup before the next window closes.',
|
|
'href' => route('studio.cards.index'),
|
|
'cta' => 'Open cards',
|
|
];
|
|
}
|
|
|
|
if ($featuredEntries > 0) {
|
|
$items[] = [
|
|
'title' => 'Featured challenge entries are live',
|
|
'body' => 'Review promoted submissions and keep those cards ready for profile, editorial, or follow-up pushes.',
|
|
'href' => route('studio.featured'),
|
|
'cta' => 'Manage featured',
|
|
];
|
|
}
|
|
|
|
if ($winnerEntries > 0) {
|
|
$items[] = [
|
|
'title' => 'Winning challenge work deserves a spotlight',
|
|
'body' => 'Use featured content and profile curation to extend the reach of cards that already placed well.',
|
|
'href' => route('studio.profile'),
|
|
'cta' => 'Open profile',
|
|
];
|
|
}
|
|
|
|
if ($activeChallenges->isNotEmpty()) {
|
|
$items[] = [
|
|
'title' => 'Public challenge archive stays one click away',
|
|
'body' => 'Use the public challenge directory to review prompts, reference past winners, and see how new runs are framed.',
|
|
'href' => route('cards.challenges'),
|
|
'cta' => 'Browse challenges',
|
|
];
|
|
}
|
|
|
|
return collect($items)->take(4)->values()->all();
|
|
}
|
|
} |