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

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace App\Support\AcademyAnalytics;
final class AcademyAnalyticsContentType
{
public const HOME = 'academy_home';
public const PROMPT = 'academy_prompt';
public const LESSON = 'academy_lesson';
public const COURSE = 'academy_course';
public const PROMPT_PACK = 'academy_prompt_pack';
public const CHALLENGE = 'academy_challenge';
public const SEARCH = 'academy_search';
public const UPGRADE = 'academy_upgrade';
/**
* @return list<string>
*/
public static function values(): array
{
return [
self::HOME,
self::PROMPT,
self::LESSON,
self::COURSE,
self::PROMPT_PACK,
self::CHALLENGE,
self::SEARCH,
self::UPGRADE,
];
}
public static function requiresContentId(string $contentType): bool
{
return in_array($contentType, [
self::PROMPT,
self::LESSON,
self::COURSE,
self::PROMPT_PACK,
self::CHALLENGE,
], true);
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace App\Support\AcademyAnalytics;
final class AcademyAnalyticsEventType
{
public const PAGE_VIEW = 'academy_page_view';
public const CONTENT_VIEW = 'academy_content_view';
public const ENGAGED_VIEW = 'academy_engaged_view';
public const SCROLL_50 = 'academy_scroll_50';
public const SCROLL_75 = 'academy_scroll_75';
public const SCROLL_100 = 'academy_scroll_100';
public const PROMPT_COPY = 'academy_prompt_copy';
public const PROMPT_NEGATIVE_COPY = 'academy_prompt_negative_copy';
public const PROMPT_LIKE = 'academy_prompt_like';
public const PROMPT_SAVE = 'academy_prompt_save';
public const PROMPT_PACK_VIEW = 'academy_prompt_pack_view';
public const PROMPT_PACK_DOWNLOAD = 'academy_prompt_pack_download';
public const LESSON_VIEW = 'academy_lesson_view';
public const LESSON_STARTED = 'academy_lesson_started';
public const LESSON_COMPLETED = 'academy_lesson_completed';
public const COURSE_VIEW = 'academy_course_view';
public const COURSE_STARTED = 'academy_course_started';
public const COURSE_COMPLETED = 'academy_course_completed';
public const CHALLENGE_VIEW = 'academy_challenge_view';
public const CHALLENGE_STARTED = 'academy_challenge_started';
public const CHALLENGE_SUBMITTED = 'academy_challenge_submitted';
public const SEARCH = 'academy_search';
public const ZERO_SEARCH_RESULTS = 'academy_zero_search_results';
public const SEARCH_RESULT_CLICK = 'academy_search_result_click';
public const PREMIUM_PREVIEW_VIEW = 'academy_premium_preview_view';
public const UPGRADE_CLICK = 'academy_upgrade_click';
public const OUTBOUND_CLICK = 'academy_outbound_click';
/**
* @return list<string>
*/
public static function values(): array
{
return [
self::PAGE_VIEW,
self::CONTENT_VIEW,
self::ENGAGED_VIEW,
self::SCROLL_50,
self::SCROLL_75,
self::SCROLL_100,
self::PROMPT_COPY,
self::PROMPT_NEGATIVE_COPY,
self::PROMPT_LIKE,
self::PROMPT_SAVE,
self::PROMPT_PACK_VIEW,
self::PROMPT_PACK_DOWNLOAD,
self::LESSON_VIEW,
self::LESSON_STARTED,
self::LESSON_COMPLETED,
self::COURSE_VIEW,
self::COURSE_STARTED,
self::COURSE_COMPLETED,
self::CHALLENGE_VIEW,
self::CHALLENGE_STARTED,
self::CHALLENGE_SUBMITTED,
self::SEARCH,
self::ZERO_SEARCH_RESULTS,
self::SEARCH_RESULT_CLICK,
self::PREMIUM_PREVIEW_VIEW,
self::UPGRADE_CLICK,
self::OUTBOUND_CLICK,
];
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace App\Support\AcademyAnalytics;
final class AcademyAnalyticsProgressStatus
{
public const NOT_STARTED = 'not_started';
public const STARTED = 'started';
public const IN_PROGRESS = 'in_progress';
public const COMPLETED = 'completed';
/**
* @return list<string>
*/
public static function values(): array
{
return [
self::NOT_STARTED,
self::STARTED,
self::IN_PROGRESS,
self::COMPLETED,
];
}
}

View File

@@ -52,9 +52,11 @@ final class SeoFactory
$description = Str::limit($description !== '' ? $description : $title, 160, '…');
$image = $thumbs['xl']['url'] ?? $thumbs['lg']['url'] ?? $thumbs['md']['url'] ?? null;
$keywords = $artwork->tags->pluck('name')->filter()->unique()->values()->all();
$licenseUrl = $this->clean((string) ($artwork->license_url ?? ''));
$publisherName = (string) config('seo.site_name', 'Skinbase');
$publisherUrl = url('/');
$licensePageUrl = route('terms-of-service');
$licenseUrl = $this->clean((string) ($artwork->license_url ?? ''));
$licenseUrl = $licenseUrl !== null ? $licenseUrl : $licensePageUrl;
$imageWidth = $thumbs['xl']['width'] ?? $thumbs['lg']['width'] ?? null;
$imageHeight = $thumbs['xl']['height'] ?? $thumbs['lg']['height'] ?? null;
@@ -83,6 +85,8 @@ final class SeoFactory
'creditText' => $authorName,
'datePublished' => optional($artwork->published_at)->toAtomString(),
'license' => $licenseUrl,
'acquireLicensePage' => $licensePageUrl,
'copyrightNotice' => $authorName,
'keywords' => $keywords !== [] ? $keywords : null,
'representativeOfPage' => true,
], fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []))