more fixes

This commit is contained in:
2026-03-12 07:22:38 +01:00
parent 547215cbe8
commit 4f576ceb04
226 changed files with 14380 additions and 4453 deletions

View File

@@ -32,23 +32,33 @@ final class ExploreController extends Controller
/** Meilisearch sort-field arrays per sort alias. */
private const SORT_MAP = [
'trending' => ['trending_score_24h:desc', 'trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
'fresh' => ['trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
'top-rated' => ['awards_received_count:desc', 'favorites_count:desc'],
'latest' => ['created_at:desc'],
// Legacy aliases kept for backward compatibility.
'new-hot' => ['trending_score_7d:desc', 'favorites_count:desc', 'created_at:desc'],
'best' => ['awards_received_count:desc', 'favorites_count:desc'],
'latest' => ['created_at:desc'],
];
private const SORT_TTL = [
'trending' => 300,
'fresh' => 120,
'top-rated'=> 600,
'latest' => 120,
'new-hot' => 120,
'best' => 600,
'latest' => 120,
];
private const SORT_OPTIONS = [
['value' => 'trending', 'label' => '🔥 Trending'],
['value' => 'new-hot', 'label' => '🚀 New & Hot'],
['value' => 'best', 'label' => '⭐ Best'],
['value' => 'latest', 'label' => '🕐 Latest'],
['value' => 'trending', 'label' => '🔥 Trending'],
['value' => 'fresh', 'label' => '🚀 New & Hot'],
['value' => 'top-rated', 'label' => '⭐ Best'],
['value' => 'latest', 'label' => '🕐 Latest'],
];
private const SORT_ALIASES = [
'new-hot' => 'fresh',
'best' => 'top-rated',
];
public function __construct(
@@ -81,23 +91,25 @@ final class ExploreController extends Controller
? $this->spotlight->getSpotlight(6)->map(fn ($a) => $this->presentArtwork($a))
: collect();
$contentTypes = $this->contentTypeLinks();
$mainCategories = $this->mainCategories();
$seo = $this->paginationSeo($request, url('/explore'), $artworks);
return view('web.explore.index', [
'artworks' => $artworks,
'spotlight' => $spotlightItems,
'contentTypes' => $contentTypes,
'activeType' => null,
'current_sort' => $sort,
'sort_options' => self::SORT_OPTIONS,
'hero_title' => 'Explore',
'hero_description' => 'Browse the full Skinbase catalog — wallpapers, skins, photography and more.',
'breadcrumbs' => collect([
(object) ['name' => 'Explore', 'url' => '/explore'],
]),
'page_title' => 'Explore Artworks — Skinbase',
return view('gallery.index', [
'gallery_type' => 'browse',
'mainCategories' => $mainCategories,
'subcategories' => $mainCategories,
'contentType' => null,
'category' => null,
'artworks' => $artworks,
'spotlight' => $spotlightItems,
'current_sort' => $sort,
'sort_options' => self::SORT_OPTIONS,
'hero_title' => 'Explore',
'hero_description' => 'Browse the full Skinbase catalog — wallpapers, skins, photography and more.',
'breadcrumbs' => collect([(object) ['name' => 'Explore', 'url' => '/explore']]),
'page_title' => 'Explore Artworks - Skinbase',
'page_meta_description' => 'Explore the full catalog of wallpapers, skins, photography and other artworks on Skinbase.',
'page_meta_keywords' => 'explore, wallpapers, skins, photography, artworks, skinbase',
'page_canonical' => $seo['canonical'],
'page_rel_prev' => $seo['prev'],
'page_rel_next' => $seo['next'],
@@ -117,6 +129,11 @@ final class ExploreController extends Controller
// "artworks" is the umbrella — search all types
$isAll = $type === 'artworks';
// Canonical URLs for content types are /skins, /wallpapers, /photography, /other.
if (! $isAll) {
return redirect()->to($this->canonicalTypeUrl($request, $type), 301);
}
$sort = $this->resolveSort($request);
$perPage = $this->resolvePerPage($request);
$page = max(1, (int) $request->query('page', 1));
@@ -142,26 +159,44 @@ final class ExploreController extends Controller
? $this->spotlight->getSpotlight(6)->map(fn ($a) => $this->presentArtwork($a))
: collect();
$contentTypes = $this->contentTypeLinks();
$mainCategories = $this->mainCategories();
$contentType = null;
$subcategories = $mainCategories;
if (! $isAll) {
$contentType = ContentType::where('slug', $type)->first();
$subcategories = $contentType
? $contentType->rootCategories()->orderBy('sort_order')->orderBy('name')->get()
: collect();
}
if ($isAll) {
$humanType = 'Artworks';
} else {
$humanType = $contentType?->name ?? ucfirst($type);
}
$baseUrl = url('/explore/' . $type);
$seo = $this->paginationSeo($request, $baseUrl, $artworks);
$humanType = ucfirst($type);
return view('web.explore.index', [
'artworks' => $artworks,
'spotlight' => $spotlightItems,
'contentTypes' => $contentTypes,
'activeType' => $type,
'current_sort' => $sort,
'sort_options' => self::SORT_OPTIONS,
'hero_title' => $humanType,
'hero_description' => "Browse {$humanType} on Skinbase.",
'breadcrumbs' => collect([
return view('gallery.index', [
'gallery_type' => $isAll ? 'browse' : 'content-type',
'mainCategories' => $mainCategories,
'subcategories' => $subcategories,
'contentType' => $contentType,
'category' => null,
'artworks' => $artworks,
'spotlight' => $spotlightItems,
'current_sort' => $sort,
'sort_options' => self::SORT_OPTIONS,
'hero_title' => $humanType,
'hero_description' => "Browse {$humanType} on Skinbase.",
'breadcrumbs' => collect([
(object) ['name' => 'Explore', 'url' => '/explore'],
(object) ['name' => $humanType, 'url' => "/explore/{$type}"],
]),
'page_title' => "{$humanType} Explore Skinbase",
'page_title' => "{$humanType} - Explore - Skinbase",
'page_meta_description' => "Discover the best {$humanType} artworks on Skinbase. Browse trending, new and top-rated.",
'page_meta_keywords' => strtolower($type) . ', explore, skinbase, artworks, wallpapers, skins, photography',
'page_canonical' => $seo['canonical'],
'page_rel_prev' => $seo['prev'],
'page_rel_next' => $seo['next'],
@@ -173,6 +208,14 @@ final class ExploreController extends Controller
public function byTypeMode(Request $request, string $type, string $mode)
{
$type = strtolower($type);
if ($type !== 'artworks') {
$query = $request->query();
$query['sort'] = $this->normalizeSort((string) $mode);
return redirect()->to($this->canonicalTypeUrl($request, $type, $query), 301);
}
// Rewrite the sort via the URL segment and delegate
$request->query->set('sort', $mode);
return $this->byType($request, $type);
@@ -180,24 +223,49 @@ final class ExploreController extends Controller
// ── Helpers ──────────────────────────────────────────────────────────
private function contentTypeLinks(): Collection
private function mainCategories(): Collection
{
return collect([
(object) ['name' => 'All Artworks', 'slug' => 'artworks', 'url' => '/explore/artworks'],
...ContentType::orderBy('id')->get(['name', 'slug'])->map(fn ($ct) => (object) [
$categories = ContentType::orderBy('id')
->get(['name', 'slug'])
->map(fn ($ct) => (object) [
'name' => $ct->name,
'slug' => $ct->slug,
'url' => '/explore/' . strtolower($ct->slug),
]),
'url' => '/' . strtolower($ct->slug),
]);
return $categories->push((object) [
'name' => 'Members',
'slug' => 'members',
'url' => '/members',
]);
}
private function resolveSort(Request $request): string
{
$s = (string) $request->query('sort', 'trending');
$s = $this->normalizeSort((string) $request->query('sort', 'trending'));
return array_key_exists($s, self::SORT_MAP) ? $s : 'trending';
}
private function normalizeSort(string $sort): string
{
$sort = strtolower($sort);
return self::SORT_ALIASES[$sort] ?? $sort;
}
private function canonicalTypeUrl(Request $request, string $type, ?array $query = null): string
{
$query = $query ?? $request->query();
if (isset($query['sort'])) {
$query['sort'] = $this->normalizeSort((string) $query['sort']);
if ($query['sort'] === 'trending') {
unset($query['sort']);
}
}
return url('/' . $type) . ($query ? ('?' . http_build_query($query)) : '');
}
private function resolvePerPage(Request $request): int
{
$v = (int) ($request->query('per_page') ?: $request->query('limit') ?: 24);