Implement academy analytics, billing, and web stories updates

This commit is contained in:
2026-05-26 07:27:29 +02:00
parent 456c3d6bb0
commit 0b33a1b074
177 changed files with 27360 additions and 2685 deletions

View File

@@ -8,7 +8,10 @@ use App\Http\Controllers\Controller;
use App\Models\AcademyCourse;
use App\Models\AcademyLesson;
use App\Services\Academy\AcademyAccessService;
use App\Services\Academy\AcademyAnalyticsService;
use App\Services\Academy\AcademyCacheService;
use App\Services\Academy\AcademyInteractionService;
use App\Support\AcademyAnalytics\AcademyAnalyticsContentType;
use App\Support\Seo\SeoFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
@@ -20,6 +23,8 @@ final class AcademyLessonController extends Controller
public function __construct(
private readonly AcademyAccessService $access,
private readonly AcademyCacheService $cache,
private readonly AcademyAnalyticsService $analytics,
private readonly AcademyInteractionService $interactions,
) {}
public function index(Request $request): Response
@@ -56,6 +61,10 @@ final class AcademyLessonController extends Controller
$lessons = $query->paginate(12)->withQueryString();
$lessons->getCollection()->transform(fn (AcademyLesson $lesson): array => $this->access->lessonPayload($lesson, $request->user()));
if (filled($filters['q'] ?? null)) {
$this->analytics->trackSearch((string) $filters['q'], (int) $lessons->total(), array_filter($filters), $request);
}
$seo = app(SeoFactory::class)
->collectionListing(
'Academy Lessons — Skinbase',
@@ -73,6 +82,20 @@ final class AcademyLessonController extends Controller
'filters' => $filters,
'categories' => $this->cache->categoriesByType('lesson'),
'pricingUrl' => route('academy.pricing'),
'analytics' => [
'enabled' => true,
'contentType' => filled($filters['q'] ?? null) ? AcademyAnalyticsContentType::SEARCH : null,
'contentId' => null,
'eventUrl' => route('academy.analytics.events.store'),
'pageName' => 'academy_lessons_index',
'search' => filled($filters['q'] ?? null) ? [
'query' => (string) $filters['q'],
'resultsCount' => (int) $lessons->total(),
] : null,
'isPremium' => false,
'isGuest' => $request->user() === null,
'isSubscriber' => $request->user()?->hasAcademyCreatorAccess() || $request->user()?->hasAcademyProAccess(),
],
])->rootView('collections');
}
@@ -148,6 +171,8 @@ final class AcademyLessonController extends Controller
(string) ($lesson->series_name ?: $lesson->category?->name ?: 'Academy'),
)->toArray();
$interaction = $this->interactions->getInteractionState($request->user(), AcademyAnalyticsContentType::LESSON, (int) $lesson->id);
return Inertia::render('Academy/Show', [
'pageType' => 'lesson',
'item' => $payload,
@@ -159,6 +184,26 @@ final class AcademyLessonController extends Controller
'pricingUrl' => route('academy.pricing'),
'completeUrl' => $request->user() ? route('academy.lessons.complete', ['lesson' => $lesson->id]) : null,
'completed' => $request->user()?->academyLessonProgress()->where('lesson_id', $lesson->id)->whereNotNull('completed_at')->exists() ?? false,
'interaction' => $interaction,
'interactionRoutes' => [
'like' => route('academy.interactions.like'),
'save' => route('academy.interactions.save'),
],
'loginUrl' => route('login'),
'progressRoutes' => [
'startLesson' => $request->user() ? route('academy.progress.lesson.start') : null,
],
'analytics' => [
'enabled' => true,
'contentType' => AcademyAnalyticsContentType::LESSON,
'contentId' => (int) $lesson->id,
'eventUrl' => route('academy.analytics.events.store'),
'pageName' => 'academy_lesson_show',
'isPremium' => (string) ($lesson->access_level ?? 'free') !== 'free',
'isGuest' => $request->user() === null,
'isSubscriber' => $request->user()?->hasAcademyCreatorAccess() || $request->user()?->hasAcademyProAccess(),
'isLocked' => (bool) ($payload['locked'] ?? false),
],
])->rootView('collections');
}
}