feat: ship creator journey v2 and profile updates
This commit is contained in:
@@ -6,6 +6,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\ProfileUpdateRequest;
|
||||
use App\Http\Requests\Settings\RequestEmailChangeRequest;
|
||||
use App\Http\Requests\Settings\UpdateAccountSectionRequest;
|
||||
use App\Http\Requests\Settings\UpdateContentPreferencesRequest;
|
||||
use App\Http\Requests\Settings\UpdateNotificationsSectionRequest;
|
||||
use App\Http\Requests\Settings\UpdatePersonalSectionRequest;
|
||||
use App\Http\Requests\Settings\UpdateProfileSectionRequest;
|
||||
@@ -35,10 +36,11 @@ use App\Services\FollowAnalyticsService;
|
||||
use App\Services\LeaderboardService;
|
||||
use App\Services\UserSuggestionService;
|
||||
use App\Services\Countries\CountryCatalogService;
|
||||
use App\Services\Maturity\ArtworkMaturityService;
|
||||
use App\Services\ThumbnailPresenter;
|
||||
use App\Services\ThumbnailService;
|
||||
use App\Services\XPService;
|
||||
use App\Services\UsernameApprovalService;
|
||||
use App\Services\Profile\CreatorJourneyService;
|
||||
use App\Services\UserStatsService;
|
||||
use App\Support\AvatarUrl;
|
||||
use App\Support\CoverUrl;
|
||||
@@ -84,6 +86,7 @@ class ProfileController extends Controller
|
||||
private readonly LeaderboardService $leaderboards,
|
||||
private readonly CountryCatalogService $countryCatalog,
|
||||
private readonly UserSuggestionService $userSuggestions,
|
||||
private readonly CreatorJourneyService $creatorJourney,
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -312,6 +315,10 @@ class ProfileController extends Controller
|
||||
$followerNotifications = (bool) ($profileData['follower_notifications'] ?? true);
|
||||
$commentNotifications = (bool) ($profileData['comment_notifications'] ?? true);
|
||||
$newsletter = (bool) ($profileData['newsletter'] ?? $profileData['mlist'] ?? $user->mlist ?? false);
|
||||
$matureContentVisibility = (string) ($profileData['mature_content_visibility'] ?? config('maturity.viewer.default_mode', 'blur'));
|
||||
$matureContentWarningEnabled = array_key_exists('mature_content_warning_enabled', $profileData)
|
||||
? (bool) $profileData['mature_content_warning_enabled']
|
||||
: (bool) config('maturity.viewer.default_warn_on_detail', true);
|
||||
|
||||
return Inertia::render('Settings/ProfileEdit', [
|
||||
'user' => [
|
||||
@@ -332,6 +339,8 @@ class ProfileController extends Controller
|
||||
'follower_notifications' => $followerNotifications,
|
||||
'comment_notifications' => $commentNotifications,
|
||||
'newsletter' => $newsletter,
|
||||
'mature_content_visibility' => $matureContentVisibility,
|
||||
'mature_content_warning_enabled' => $matureContentWarningEnabled,
|
||||
'last_username_change_at' => $user->last_username_change_at,
|
||||
'username_changed_at' => $user->username_changed_at,
|
||||
],
|
||||
@@ -576,6 +585,18 @@ class ProfileController extends Controller
|
||||
return $this->settingsResponse($request, 'Notification settings saved successfully.');
|
||||
}
|
||||
|
||||
public function updateContentPreferencesSection(UpdateContentPreferencesRequest $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
$this->persistProfileUpdates((int) $request->user()->id, [
|
||||
'mature_content_visibility' => (string) $validated['mature_content_visibility'],
|
||||
'mature_content_warning_enabled' => (bool) $validated['mature_content_warning_enabled'],
|
||||
]);
|
||||
|
||||
return $this->settingsResponse($request, 'Content preferences saved successfully.');
|
||||
}
|
||||
|
||||
public function updateSecurityPassword(UpdateSecurityPasswordRequest $request): RedirectResponse|JsonResponse
|
||||
{
|
||||
$validated = $request->validated();
|
||||
@@ -918,7 +939,7 @@ class ProfileController extends Controller
|
||||
$perPage = 24;
|
||||
|
||||
// ── Artworks (cursor-paginated) ──────────────────────────────────────
|
||||
$artworks = $this->artworkService->getArtworksByUser($user->id, $isOwner, $perPage)
|
||||
$artworks = $this->artworkService->getArtworksByUser($user->id, $isOwner, $perPage, $viewer)
|
||||
->through(function (Artwork $art) {
|
||||
return (object) $this->mapArtworkCardPayload($art);
|
||||
});
|
||||
@@ -926,34 +947,38 @@ class ProfileController extends Controller
|
||||
// ── Featured artworks for this user ─────────────────────────────────
|
||||
$featuredArtworks = collect();
|
||||
if (Schema::hasTable('artwork_features')) {
|
||||
$featuredArtworks = DB::table('artwork_features as af')
|
||||
->join('artworks as a', 'a.id', '=', 'af.artwork_id')
|
||||
->where('a.user_id', $user->id)
|
||||
$featuredQuery = Artwork::query()
|
||||
->with([
|
||||
'user:id,name,username,level,rank',
|
||||
'user.profile:user_id,avatar_hash',
|
||||
'group:id,name,slug,avatar_path',
|
||||
'stats:artwork_id,views,downloads,favorites',
|
||||
'categories' => function ($query) {
|
||||
$query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
|
||||
->with(['parent:id,parent_id,content_type_id,name,slug', 'contentType:id,slug,name']);
|
||||
},
|
||||
])
|
||||
->join('artwork_features as af', 'af.artwork_id', '=', 'artworks.id')
|
||||
->where('artworks.user_id', $user->id)
|
||||
->where('af.is_active', true)
|
||||
->whereNull('af.deleted_at')
|
||||
->whereNull('a.deleted_at')
|
||||
->where('a.is_public', true)
|
||||
->where('a.is_approved', true)
|
||||
->whereNull('artworks.deleted_at')
|
||||
->where('artworks.is_public', true)
|
||||
->where('artworks.is_approved', true)
|
||||
->whereNotNull('artworks.published_at')
|
||||
->select(['artworks.*', 'af.label as featured_label', 'af.featured_at as featured_slot_at'])
|
||||
->orderByDesc('af.featured_at')
|
||||
->limit(3)
|
||||
->select([
|
||||
'a.id', 'a.title as name', 'a.hash', 'a.thumb_ext',
|
||||
'a.width', 'a.height', 'af.label', 'af.featured_at',
|
||||
])
|
||||
->limit(3);
|
||||
|
||||
app(ArtworkMaturityService::class)->applyViewerFilter($featuredQuery, $viewer);
|
||||
|
||||
$featuredArtworks = $featuredQuery
|
||||
->get()
|
||||
->map(function ($row) {
|
||||
$thumbUrl = ($row->hash && $row->thumb_ext)
|
||||
? ThumbnailService::fromHash($row->hash, $row->thumb_ext, 'md')
|
||||
: '/images/placeholder.jpg';
|
||||
return (object) [
|
||||
'id' => $row->id,
|
||||
'name' => $row->name,
|
||||
'thumb' => $thumbUrl,
|
||||
'label' => $row->label,
|
||||
'featured_at' => $row->featured_at,
|
||||
'width' => $row->width,
|
||||
'height' => $row->height,
|
||||
];
|
||||
->map(function (Artwork $artwork) {
|
||||
return (object) array_merge($this->mapArtworkCardPayload($artwork), [
|
||||
'label' => $artwork->featured_label,
|
||||
'featured_at' => $this->formatIsoDate($artwork->featured_slot_at),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -972,6 +997,10 @@ class ProfileController extends Controller
|
||||
->where('a.is_public', true)
|
||||
->where('a.is_approved', true)
|
||||
->whereNotNull('a.published_at')
|
||||
->when(app(ArtworkMaturityService::class)->viewerPreferences($viewer)['visibility'] === ArtworkMaturityService::VIEW_HIDE, function ($query): void {
|
||||
$query->whereRaw('COALESCE(a.is_mature, 0) = 0')
|
||||
->whereRaw("COALESCE(a.maturity_status, 'clear') != ?", [ArtworkMaturityService::STATUS_SUSPECTED]);
|
||||
})
|
||||
->orderByDesc('af.created_at')
|
||||
->orderByDesc('af.artwork_id')
|
||||
->limit($favouriteLimit + 1)
|
||||
@@ -981,7 +1010,16 @@ class ProfileController extends Controller
|
||||
$hasMore = $favIds->count() > $favouriteLimit;
|
||||
$favIds = $favIds->take($favouriteLimit);
|
||||
|
||||
$indexed = Artwork::with('user:id,name,username')
|
||||
$indexed = Artwork::with([
|
||||
'user:id,name,username,level,rank',
|
||||
'user.profile:user_id,avatar_hash',
|
||||
'group:id,name,slug,avatar_path',
|
||||
'stats:artwork_id,views,downloads,favorites',
|
||||
'categories' => function ($query) {
|
||||
$query->select('categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order')
|
||||
->with(['contentType:id,slug,name']);
|
||||
},
|
||||
])
|
||||
->whereIn('id', $favIds)
|
||||
->get()
|
||||
->keyBy('id');
|
||||
@@ -1056,18 +1094,38 @@ class ProfileController extends Controller
|
||||
->count();
|
||||
}
|
||||
|
||||
$liveAwardsReceivedCount = 0;
|
||||
if (Schema::hasTable('artwork_awards') && Schema::hasTable('artworks')) {
|
||||
$liveAwardsReceivedCount = (int) DB::table('artwork_awards as aw')
|
||||
->join('artworks as a', 'a.id', '=', 'aw.artwork_id')
|
||||
$medalTotals = [
|
||||
'gold' => 0,
|
||||
'silver' => 0,
|
||||
'bronze' => 0,
|
||||
'count' => 0,
|
||||
'score_total' => 0,
|
||||
];
|
||||
|
||||
if (Schema::hasTable('artwork_medal_stats') && Schema::hasTable('artworks')) {
|
||||
$totals = DB::table('artwork_medal_stats as aas')
|
||||
->join('artworks as a', 'a.id', '=', 'aas.artwork_id')
|
||||
->where('a.user_id', $user->id)
|
||||
->whereNull('a.deleted_at')
|
||||
->count();
|
||||
->selectRaw('COALESCE(SUM(aas.gold_count), 0) as gold_count')
|
||||
->selectRaw('COALESCE(SUM(aas.silver_count), 0) as silver_count')
|
||||
->selectRaw('COALESCE(SUM(aas.bronze_count), 0) as bronze_count')
|
||||
->selectRaw('COALESCE(SUM(aas.score_total), 0) as score_total')
|
||||
->first();
|
||||
|
||||
$medalTotals = [
|
||||
'gold' => (int) ($totals->gold_count ?? 0),
|
||||
'silver' => (int) ($totals->silver_count ?? 0),
|
||||
'bronze' => (int) ($totals->bronze_count ?? 0),
|
||||
'count' => (int) (($totals->gold_count ?? 0) + ($totals->silver_count ?? 0) + ($totals->bronze_count ?? 0)),
|
||||
'score_total' => (int) ($totals->score_total ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
$statsPayload = array_merge($stats ? (array) $stats : [], [
|
||||
'uploads_count' => $liveUploadsCount,
|
||||
'awards_received_count' => $liveAwardsReceivedCount,
|
||||
'awards_received_count' => $medalTotals['count'],
|
||||
'medal_totals' => $medalTotals,
|
||||
'followers_count' => (int) $followerCount,
|
||||
'following_count' => (int) $followingCount,
|
||||
]);
|
||||
@@ -1145,7 +1203,7 @@ class ProfileController extends Controller
|
||||
]);
|
||||
|
||||
$profileCollections = $this->collections->getProfileCollections($user, $viewer);
|
||||
$profileCollectionsPayload = $this->collections->mapCollectionCardPayloads($profileCollections, $isOwner);
|
||||
$profileCollectionsPayload = $this->collections->mapCollectionCardPayloads($profileCollections, $isOwner, $viewer);
|
||||
|
||||
// ── Profile data ─────────────────────────────────────────────────────
|
||||
$profile = $user->profile;
|
||||
@@ -1203,6 +1261,7 @@ class ProfileController extends Controller
|
||||
$achievementSummary = $this->achievements->summary((int) $user->id);
|
||||
$leaderboardRank = $this->leaderboards->creatorRankSummary((int) $user->id);
|
||||
$groupContributionHistory = $this->buildGroupContributionHistory($user);
|
||||
$journey = $this->creatorJourney->publicPayloadForUser($user);
|
||||
$resolvedInitialTab = $this->normalizeProfileTab($initialTab);
|
||||
$isTabLanding = ! $galleryOnly && $resolvedInitialTab !== null;
|
||||
$activeProfileUrl = $resolvedInitialTab !== null
|
||||
@@ -1276,6 +1335,7 @@ class ProfileController extends Controller
|
||||
'collections' => $profileCollectionsPayload,
|
||||
'achievements' => $achievementSummary,
|
||||
'leaderboardRank' => $leaderboardRank,
|
||||
'journey' => $journey,
|
||||
'groupContributionHistory' => $groupContributionHistory,
|
||||
'countryName' => $countryName,
|
||||
'isOwner' => $isOwner,
|
||||
@@ -1288,6 +1348,7 @@ class ProfileController extends Controller
|
||||
'collectionsFeaturedUrl' => route('collections.featured'),
|
||||
'collectionFeatureLimit' => (int) config('collections.featured_limit', 3),
|
||||
'profileTabUrls' => $profileTabUrls,
|
||||
'journeyApiUrl' => route('api.profile.journey', ['username' => $usernameSlug]),
|
||||
])->withViewData([
|
||||
'page_title' => $pageTitle,
|
||||
'page_canonical' => $galleryOnly ? $galleryUrl : $activeProfileUrl,
|
||||
@@ -1435,8 +1496,17 @@ class ProfileController extends Controller
|
||||
$category = $art->categories->first();
|
||||
$contentType = $category?->contentType;
|
||||
$stats = $art->stats;
|
||||
$group = $art->group;
|
||||
$isGroupPublisher = $group !== null;
|
||||
$displayName = $isGroupPublisher ? ($group->name ?? 'Skinbase') : ($art->user?->name ?? 'Skinbase');
|
||||
$username = $isGroupPublisher ? null : ($art->user?->username ?? null);
|
||||
$avatarUrl = $isGroupPublisher ? $group->avatarUrl() : ($art->user?->profile?->avatar_url ?? null);
|
||||
$profileUrl = $isGroupPublisher
|
||||
? $group->publicUrl()
|
||||
: ($username ? '/@' . $username : null);
|
||||
$publisherType = $isGroupPublisher ? 'group' : 'user';
|
||||
|
||||
return [
|
||||
return app(ArtworkMaturityService::class)->decoratePayload([
|
||||
'id' => $art->id,
|
||||
'name' => $art->title,
|
||||
'picture' => $art->file_name,
|
||||
@@ -1444,11 +1514,22 @@ class ProfileController extends Controller
|
||||
'published_at' => $this->formatIsoDate($art->published_at),
|
||||
'thumb' => $present['url'],
|
||||
'thumb_srcset' => $present['srcset'] ?? $present['url'],
|
||||
'uname' => $art->user->name ?? 'Skinbase',
|
||||
'username' => $art->user->username ?? null,
|
||||
'uname' => $displayName,
|
||||
'username' => $username,
|
||||
'avatar_url' => $avatarUrl,
|
||||
'profile_url' => $profileUrl,
|
||||
'published_as_type' => $publisherType,
|
||||
'publisher' => [
|
||||
'type' => $publisherType,
|
||||
'id' => $isGroupPublisher ? (int) $group->id : (int) ($art->user?->id ?? 0),
|
||||
'name' => $displayName,
|
||||
'username' => $username ?? '',
|
||||
'avatar_url' => $avatarUrl,
|
||||
'profile_url' => $profileUrl,
|
||||
],
|
||||
'user_id' => $art->user_id,
|
||||
'author_level' => (int) ($art->user?->level ?? 1),
|
||||
'author_rank' => (string) ($art->user?->rank ?? 'Newbie'),
|
||||
'author_level' => $isGroupPublisher ? 0 : (int) ($art->user?->level ?? 1),
|
||||
'author_rank' => $isGroupPublisher ? '' : (string) ($art->user?->rank ?? 'Newbie'),
|
||||
'content_type' => $contentType?->name,
|
||||
'content_type_slug' => $contentType?->slug,
|
||||
'category' => $category?->name,
|
||||
@@ -1458,7 +1539,7 @@ class ProfileController extends Controller
|
||||
'likes' => (int) ($stats?->favorites ?? $art->favourite_count ?? 0),
|
||||
'width' => $art->width,
|
||||
'height' => $art->height,
|
||||
];
|
||||
], $art, request()->user());
|
||||
}
|
||||
|
||||
private function formatIsoDate(mixed $value): ?string
|
||||
|
||||
Reference in New Issue
Block a user