diff --git a/app/Http/Controllers/Api/BrowseController.php b/app/Http/Controllers/Api/BrowseController.php index 3f9cd902..e68e4136 100644 --- a/app/Http/Controllers/Api/BrowseController.php +++ b/app/Http/Controllers/Api/BrowseController.php @@ -24,8 +24,9 @@ class BrowseController extends Controller public function index(Request $request) { $perPage = min(max((int) $request->get('per_page', 24), 1), 100); + $sort = (string) $request->get('sort', 'latest'); - $paginator = $this->service->browsePublicArtworks($perPage); + $paginator = $this->service->browsePublicArtworks($perPage, $sort); return ArtworkListResource::collection($paginator); } @@ -37,9 +38,10 @@ class BrowseController extends Controller public function byContentType(Request $request, string $contentTypeSlug) { $perPage = min(max((int) $request->get('per_page', 24), 1), 100); + $sort = (string) $request->get('sort', 'latest'); try { - $paginator = $this->service->getArtworksByContentType($contentTypeSlug, $perPage); + $paginator = $this->service->getArtworksByContentType($contentTypeSlug, $perPage, $sort); } catch (ModelNotFoundException $e) { abort(404); } @@ -58,13 +60,14 @@ class BrowseController extends Controller public function byCategoryPath(Request $request, string $contentTypeSlug, string $categoryPath) { $perPage = min(max((int) $request->get('per_page', 24), 1), 100); + $sort = (string) $request->get('sort', 'latest'); $slugs = array_merge([ strtolower($contentTypeSlug), ], array_values(array_filter(explode('/', trim($categoryPath, '/'))))); try { - $paginator = $this->service->getArtworksByCategoryPath($slugs, $perPage); + $paginator = $this->service->getArtworksByCategoryPath($slugs, $perPage, $sort); } catch (ModelNotFoundException $e) { abort(404); } diff --git a/app/Http/Controllers/CategoryPageController.php b/app/Http/Controllers/CategoryPageController.php index 3bee4994..38045619 100644 --- a/app/Http/Controllers/CategoryPageController.php +++ b/app/Http/Controllers/CategoryPageController.php @@ -4,13 +4,15 @@ namespace App\Http\Controllers; use App\Models\Category; use App\Models\ContentType; -use App\Models\Artwork; use App\Services\ArtworkService; use Illuminate\Http\Request; -use Illuminate\Pagination\LengthAwarePaginator; class CategoryPageController extends Controller { + public function __construct(private ArtworkService $artworkService) + { + } + public function show(Request $request, string $contentTypeSlug, ?string $categoryPath = null) { $contentType = ContentType::where('slug', strtolower($contentTypeSlug))->first(); @@ -18,6 +20,8 @@ class CategoryPageController extends Controller abort(404); } + $sort = (string) $request->get('sort', 'latest'); + if ($categoryPath === null || $categoryPath === '') { // No category path: show content-type landing page (e.g., /wallpapers) @@ -27,20 +31,7 @@ class CategoryPageController extends Controller // Load artworks for this content type (show gallery on the root page) $perPage = 40; - $artworks = Artwork::whereHas('categories', function ($q) use ($contentType) { - $q->where('categories.content_type_id', $contentType->id); - }) - ->published()->public() - ->with([ - 'user:id,name', - 'categories' => function ($q) { - $q->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']); - }, - ]) - ->orderBy('published_at', 'desc') - ->paginate($perPage) - ->withQueryString(); + $artworks = $this->artworkService->getArtworksByContentType($contentType->slug, $perPage, $sort); return view('legacy.content-type', compact( 'contentType', @@ -88,10 +79,9 @@ class CategoryPageController extends Controller // Load artworks via ArtworkService to support arbitrary-depth category paths $perPage = 40; try { - $service = app(ArtworkService::class); // service expects an array with contentType slug first, then category slugs $pathSlugs = array_merge([strtolower($contentTypeSlug)], $slugs); - $artworks = $service->getArtworksByCategoryPath($pathSlugs, $perPage); + $artworks = $this->artworkService->getArtworksByCategoryPath($pathSlugs, $perPage, $sort); } catch (\Throwable $e) { abort(404); } diff --git a/app/Http/Controllers/Legacy/BrowseController.php b/app/Http/Controllers/Legacy/BrowseController.php index 28268eb2..e44ecc76 100644 --- a/app/Http/Controllers/Legacy/BrowseController.php +++ b/app/Http/Controllers/Legacy/BrowseController.php @@ -4,12 +4,12 @@ namespace App\Http\Controllers\Legacy; use App\Http\Controllers\Controller; use App\Models\Artwork; +use App\Models\ContentType; use App\Services\ArtworkService; use Illuminate\Http\Request; use Illuminate\Pagination\CursorPaginator; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Log; -use Illuminate\Support\Facades\Storage; class BrowseController extends Controller { @@ -27,18 +27,22 @@ class BrowseController extends Controller $page_meta_keywords = 'photography, wallpapers, skins, stock, browse, social, community, artist, picture, photo'; $perPage = (int) $request->get('per_page', 24); + $sort = (string) $request->get('sort', 'latest'); - $categoryPath = trim((string) $request->query('category', ''), '/'); + // Canonical browse routes are slug-based (/photography, /wallpapers, /skins, /other, /{type}/{path}). + // Prevent duplicate query-driven browse URLs. + $legacyCategory = trim((string) $request->query('category', ''), '/'); + if ($legacyCategory !== '') { + return redirect('/' . strtolower($legacyCategory), 301); + } + $legacyContentType = trim((string) $request->query('content_type', ''), '/'); + if ($legacyContentType !== '') { + return redirect('/' . strtolower($legacyContentType), 301); + } try { - if ($categoryPath !== '') { - $slugs = array_values(array_filter(explode('/', $categoryPath))); - /** @var CursorPaginator $artworks */ - $artworks = $this->artworks->getArtworksByCategoryPath($slugs, $perPage); - } else { - /** @var CursorPaginator $artworks */ - $artworks = $this->artworks->browsePublicArtworks($perPage); - } + /** @var CursorPaginator $artworks */ + $artworks = $this->artworks->browsePublicArtworks($perPage, $sort); } catch (ModelNotFoundException $e) { abort(404); } @@ -46,7 +50,6 @@ class BrowseController extends Controller if (count($artworks) === 0) { Log::warning('browse.missing_artworks', [ 'url' => $request->fullUrl(), - 'category_path' => $categoryPath ?: null, ]); abort(410); } @@ -54,7 +57,16 @@ class BrowseController extends Controller // Shape data for the legacy Blade while using authoritative tables only. $artworks->getCollection()->transform(fn (Artwork $artwork) => $this->mapArtwork($artwork)); - return view('legacy.browse', compact('page_title', 'page_meta_description', 'page_meta_keywords', 'artworks')); + $rootCategories = ContentType::orderBy('id') + ->get(['name', 'slug']) + ->map(fn (ContentType $type) => (object) [ + 'name' => $type->name, + 'url' => '/' . strtolower($type->slug), + ]); + + $page_canonical = url('/browse'); + + return view('legacy.browse', compact('page_title', 'page_meta_description', 'page_meta_keywords', 'page_canonical', 'artworks', 'rootCategories')); } private function mapArtwork(Artwork $artwork): object diff --git a/app/Http/Controllers/Legacy/CategoryController.php b/app/Http/Controllers/Legacy/CategoryController.php index 43268367..66478f17 100644 --- a/app/Http/Controllers/Legacy/CategoryController.php +++ b/app/Http/Controllers/Legacy/CategoryController.php @@ -49,9 +49,10 @@ class CategoryController extends Controller $slugs = array_merge([$contentTypeSlug], $parts); $perPage = (int) $request->get('per_page', 40); + $sort = (string) $request->get('sort', 'latest'); try { - $artworks = $this->artworkService->getArtworksByCategoryPath($slugs, $perPage); + $artworks = $this->artworkService->getArtworksByCategoryPath($slugs, $perPage, $sort); } catch (ModelNotFoundException $e) { abort(404); } diff --git a/app/Http/Controllers/Legacy/PhotographyController.php b/app/Http/Controllers/Legacy/PhotographyController.php index 4c0d1d3b..c735061e 100644 --- a/app/Http/Controllers/Legacy/PhotographyController.php +++ b/app/Http/Controllers/Legacy/PhotographyController.php @@ -53,24 +53,11 @@ class PhotographyController extends Controller $tidy = $category->description ?? ($ct->description ?? null); $perPage = 40; + $sort = (string) $request->get('sort', 'latest'); // Load artworks for the requested content type using standard pagination try { - $artQuery = \App\Models\Artwork::public() - ->published() - ->whereHas('categories', function ($q) use ($ct) { - $q->where('categories.content_type_id', $ct->id); - }) - ->with([ - 'user:id,name', - 'categories' => function ($q) { - $q->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']); - }, - ]) - ->orderByDesc('published_at'); - - $artworks = $artQuery->paginate($perPage)->withQueryString(); + $artworks = $this->artworks->getArtworksByContentType($contentSlug, $perPage, $sort); } catch (\Throwable $e) { // Return an empty paginator so views using ->links() / ->firstItem() work $artworks = new \Illuminate\Pagination\LengthAwarePaginator([], 0, $perPage, 1, [ diff --git a/app/Services/ArtworkService.php b/app/Services/ArtworkService.php index b03d43ff..dec7289b 100644 --- a/app/Services/ArtworkService.php +++ b/app/Services/ArtworkService.php @@ -8,6 +8,7 @@ use App\Models\ArtworkFeature; use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\CursorPaginator; use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Collection; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Facades\Cache; @@ -23,6 +24,29 @@ class ArtworkService { protected int $cacheTtl = 3600; // seconds + /** + * Shared browse query used by /browse, content-type pages, and category pages. + */ + private function browseQuery(string $sort = 'latest'): Builder + { + $query = Artwork::public() + ->published() + ->with([ + 'user:id,name', + 'categories' => function ($q) { + $q->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']); + }, + ]); + + $normalizedSort = strtolower(trim($sort)); + if ($normalizedSort === 'oldest') { + return $query->orderBy('published_at', 'asc'); + } + + return $query->orderByDesc('published_at'); + } + /** * Fetch a single public artwork by slug. * Applies visibility rules (public + approved + not-deleted). @@ -115,18 +139,9 @@ class ArtworkService * Uses new authoritative tables only (no legacy joins) and eager-loads * lightweight relations needed for presentation. */ - public function browsePublicArtworks(int $perPage = 24): CursorPaginator + public function browsePublicArtworks(int $perPage = 24, string $sort = 'latest'): CursorPaginator { - $query = Artwork::public() - ->published() - ->with([ - 'user:id,name', - 'categories' => function ($q) { - $q->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']); - }, - ]) - ->orderByDesc('published_at'); + $query = $this->browseQuery($sort); // Use cursor pagination for high-load browse feeds (SEO handled via canonical URLs). return $query->cursorPaginate($perPage); @@ -136,7 +151,7 @@ class ArtworkService * Browse artworks scoped to a content type slug using keyset pagination. * Applies public + approved + published filters. */ - public function getArtworksByContentType(string $slug, int $perPage): CursorPaginator + public function getArtworksByContentType(string $slug, int $perPage, string $sort = 'latest'): CursorPaginator { $contentType = ContentType::where('slug', strtolower($slug))->first(); @@ -146,19 +161,10 @@ class ArtworkService throw $e; } - $query = Artwork::public() - ->published() + $query = $this->browseQuery($sort) ->whereHas('categories', function ($q) use ($contentType) { $q->where('categories.content_type_id', $contentType->id); - }) - ->with([ - 'user:id,name', - 'categories' => function ($q) { - $q->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']); - }, - ]) - ->orderByDesc('published_at'); + }); return $query->cursorPaginate($perPage); } @@ -169,7 +175,7 @@ class ArtworkService * * @param array $slugs */ - public function getArtworksByCategoryPath(array $slugs, int $perPage): CursorPaginator + public function getArtworksByCategoryPath(array $slugs, int $perPage, string $sort = 'latest'): CursorPaginator { if (empty($slugs)) { $e = new ModelNotFoundException(); @@ -214,19 +220,10 @@ class ArtworkService } } - $query = Artwork::public() - ->published() + $query = $this->browseQuery($sort) ->whereHas('categories', function ($q) use ($current) { $q->where('categories.id', $current->id); - }) - ->with([ - 'user:id,name', - 'categories' => function ($q) { - $q->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']); - }, - ]) - ->orderByDesc('published_at'); + }); return $query->cursorPaginate($perPage); } diff --git a/public/css/toolbar.css b/public/css/toolbar.css index fed756bd..0590f0f6 100644 --- a/public/css/toolbar.css +++ b/public/css/toolbar.css @@ -167,7 +167,7 @@ text-align:left; margin-top:30px; padding-left:100px; - display:block; + display:block; } #browserMenuList li a{ @@ -216,7 +216,7 @@ text-align:left; margin-top:30px; padding-left:100px; - display:block; + display:block; } #browserMenuListMain li a{ @@ -363,7 +363,7 @@ subLoginMenu ul { #1f262d 75%, #1f262d); background: -webkit-gradient( - linear, left top, left bottom, + linear, left top, left bottom, from(#161a20), color-stop(0.50, #1f262d), color-stop(0.50, #1f262d), @@ -533,7 +533,7 @@ subLoginMenu ul { /*! * Yamm!3 - Yet another megamenu for Bootstrap 3 * http://geedmo.github.com/yamm3 - * + * * @geedmo - Licensed under the MIT license */ .yamm .nav, @@ -564,9 +564,7 @@ subLoginMenu ul { margin:0; padding:0; height:50px; - background:rgba(30,30,30,0.2); - -webkit-backdrop-filter: blur(4px); - backdrop-filter: blur(4px); + background:rgba(30,30,30,0.2); } .nav>li.menu_notice>a { @@ -585,20 +583,18 @@ subLoginMenu ul { } .navbar-skinbase { - background: rgba(16, 25, 33, 0.6); - -webkit-backdrop-filter: blur(6px); - backdrop-filter: blur(6px); - border-bottom:solid 1px #000; - box-shadow:0 0 14px #333; - z-index:1000; + background:rgba(16, 25, 33, 0.9); + border-bottom:solid 1px #000; + box-shadow:0 0 14px #333; + z-index:1000; } .dropdown:hover .dropdown-menu { display: block; } - - - + + + .navbar-skinbase .navbar-brand { color: #333; } @@ -675,12 +671,10 @@ subLoginMenu ul { } .navbar-skinbase .dropdown-menu { - background: rgba(16, 25, 33, 0.6); - -webkit-backdrop-filter: blur(6px); - backdrop-filter: blur(6px); - border-bottom:solid 1px #000; - box-shadow:0 0 14px #333; - color:#fff; + background:rgba(16, 25, 33, 0.9); + border-bottom:solid 1px #000; + box-shadow:0 0 14px #333; + color:#fff; } diff --git a/resources/js/Pages/Upload/Index.jsx b/resources/js/Pages/Upload/Index.jsx index 515351d1..dfa3be18 100644 --- a/resources/js/Pages/Upload/Index.jsx +++ b/resources/js/Pages/Upload/Index.jsx @@ -580,7 +580,7 @@ export default function UploadPage({ draftId, filesCdnUrl, chunkSize }) { if (uploadsV2Enabled) { return ( -
+
Drop screenshots here or click to browse

@@ -69,7 +69,7 @@ export default function UploadActions({ disabled={disabled} title={disabled ? disableReason : 'Continue to Publish'} onClick={() => onContinue?.()} - className={`rounded-lg px-4 py-2 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/75 ${disabled ? 'cursor-not-allowed bg-sky-700/45 text-sky-100/75' : 'bg-sky-400 text-slate-950 hover:bg-sky-300 shadow-[0_10px_28px_rgba(56,189,248,0.28)] ring-1 ring-sky-100/45'}`} + className={`btn-primary text-sm ${disabled ? 'cursor-not-allowed opacity-60' : ''}`} > Continue to Publish @@ -83,7 +83,7 @@ export default function UploadActions({ disabled={disabled} title={disabled ? disableReason : 'Publish artwork'} onClick={() => onPublish?.()} - className={`rounded-lg px-4 py-2 text-sm font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-emerald-300/75 ${disabled ? 'cursor-not-allowed bg-emerald-700/45 text-emerald-100/75' : 'bg-emerald-400 text-slate-950 shadow-[0_0_0_1px_rgba(167,243,208,0.85),0_0_24px_rgba(52,211,153,0.45)] hover:bg-emerald-300'}`} + className={`btn-primary text-sm ${disabled ? 'cursor-not-allowed opacity-60' : ''}`} > {isPublishing ? 'Publishing…' : 'Publish'} @@ -91,13 +91,14 @@ export default function UploadActions({ } return ( -
+
+
{canGoBack && ( @@ -107,7 +108,7 @@ export default function UploadActions({ @@ -119,7 +120,7 @@ export default function UploadActions({ onClick={handleCancel} disabled={isCancelling} title={confirmCancel ? 'Click again to confirm cancel' : 'Cancel current upload'} - className="rounded-lg border border-amber-300/45 bg-amber-500/20 px-3.5 py-2 text-sm font-medium text-amber-50 transition hover:bg-amber-500/30 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-300/75 disabled:cursor-not-allowed disabled:opacity-60" + className="btn-secondary text-sm disabled:cursor-not-allowed disabled:opacity-60" > {isCancelling ? 'Cancelling…' : confirmCancel ? 'Cancel upload?' : 'Cancel'} @@ -129,7 +130,7 @@ export default function UploadActions({ @@ -139,7 +140,7 @@ export default function UploadActions({ @@ -147,6 +148,7 @@ export default function UploadActions({ {renderPrimary()}
+
) } diff --git a/resources/js/components/upload/UploadDropzone.jsx b/resources/js/components/upload/UploadDropzone.jsx index c2be4db1..8c5f4cc5 100644 --- a/resources/js/components/upload/UploadDropzone.jsx +++ b/resources/js/components/upload/UploadDropzone.jsx @@ -98,20 +98,27 @@ export default function UploadDropzone({ const droppedFile = event.dataTransfer?.files?.[0] if (droppedFile) emitFile(droppedFile) }} - animate={prefersReducedMotion ? undefined : { - scale: dragging ? 1.01 : 1, - borderColor: invalid ? 'rgba(252,165,165,0.7)' : dragging ? 'rgba(103,232,249,0.9)' : 'rgba(56,189,248,0.35)', - backgroundColor: invalid ? 'rgba(23,68,68,0.10)' : dragging ? 'rgba(6,182,212,0.20)' : 'rgba(14,165,233,0.05)', - }} + animate={prefersReducedMotion ? undefined : { scale: dragging ? 1.01 : 1 }} transition={dragTransition} className={`group rounded-xl border-2 border-dashed border-white/50 py-6 px-4 text-center transition hover:border-accent/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-300/70 ${locked ? 'cursor-default bg-white/5 opacity-75' : 'cursor-pointer'} ${invalid ? 'border-red-300/70 bg-red-500/10 shadow-[0_0_0_1px_rgba(248,113,113,0.2)]' : dragging ? 'border-cyan-300 bg-cyan-500/20 shadow-[0_0_0_1px_rgba(103,232,249,0.35)]' : locked ? 'bg-white/5' : 'bg-sky-500/5 hover:bg-sky-500/12'}`} > +

{title}

+

{description}

+ {previewUrl ? ( -
-
- Selected preview -
Click to replace
+
+
+ Selected preview
+
Click to replace
) : ( <> @@ -123,8 +130,6 @@ export default function UploadDropzone({
-

{title}

-

{description}

Accepted: JPG, JPEG, PNG, WEBP, ZIP, RAR, 7Z, TAR, GZ

Max size: images 50MB · archives 200MB

diff --git a/resources/js/components/upload/UploadProgress.jsx b/resources/js/components/upload/UploadProgress.jsx index dc76a739..48644264 100644 --- a/resources/js/components/upload/UploadProgress.jsx +++ b/resources/js/components/upload/UploadProgress.jsx @@ -51,20 +51,13 @@ export default function UploadProgress({ Error: 'border-red-400/35 bg-red-400/15 text-red-100', } - const statusColors = { - Idle: { borderColor: 'rgba(148,163,184,0.35)', backgroundColor: 'rgba(148,163,184,0.15)', color: 'rgb(226,232,240)' }, - Uploading: { borderColor: 'rgba(56,189,248,0.35)', backgroundColor: 'rgba(56,189,248,0.15)', color: 'rgb(224,242,254)' }, - Processing: { borderColor: 'rgba(251,191,36,0.35)', backgroundColor: 'rgba(251,191,36,0.15)', color: 'rgb(254,243,199)' }, - Ready: { borderColor: 'rgba(52,211,153,0.35)', backgroundColor: 'rgba(52,211,153,0.15)', color: 'rgb(209,250,229)' }, - Error: { borderColor: 'rgba(248,113,113,0.35)', backgroundColor: 'rgba(248,113,113,0.15)', color: 'rgb(254,226,226)' }, - } - const quickTransition = prefersReducedMotion ? { duration: 0 } : { duration: 0.2, ease: 'easeOut' } const stepLabels = ['Preload', 'Details', 'Publish'] const stepIndex = progress >= 100 ? 2 : progress >= 34 ? 1 : 0 + const progressValue = Math.max(0, Math.min(100, Number(progress) || 0)) return (
@@ -75,13 +68,11 @@ export default function UploadProgress({

{description}

- {resolvedStatus} - +
@@ -100,16 +91,14 @@ export default function UploadProgress({
-
-

{Math.round(progress)}%

+

{Math.round(progressValue)}%

diff --git a/resources/js/components/upload/UploadWizard.jsx b/resources/js/components/upload/UploadWizard.jsx index 72dcd7c1..120dc40e 100644 --- a/resources/js/components/upload/UploadWizard.jsx +++ b/resources/js/components/upload/UploadWizard.jsx @@ -406,9 +406,26 @@ export default function UploadWizard({ if (!selected) return [] return categoryTreeByType[selected] || [] }, [categoryTreeByType, metadata.contentType]) + const allRootCategoryOptions = useMemo(() => { + const items = [] + Object.entries(categoryTreeByType).forEach(([contentTypeValue, roots]) => { + roots.forEach((root) => { + items.push({ + ...root, + contentTypeValue, + }) + }) + }) + return items + }, [categoryTreeByType]) + const selectedRootFromAnyType = useMemo(() => { + const selectedId = String(metadata.rootCategoryId || '') + if (!selectedId) return null + return allRootCategoryOptions.find((root) => String(root.id) === selectedId) || null + }, [allRootCategoryOptions, metadata.rootCategoryId]) const selectedRootCategory = useMemo(() => { - return filteredCategoryTree.find((root) => String(root.id) === String(metadata.rootCategoryId || '')) || null - }, [filteredCategoryTree, metadata.rootCategoryId]) + return filteredCategoryTree.find((root) => String(root.id) === String(metadata.rootCategoryId || '')) || selectedRootFromAnyType || null + }, [filteredCategoryTree, metadata.rootCategoryId, selectedRootFromAnyType]) const requiresSubCategory = Boolean(selectedRootCategory && Array.isArray(selectedRootCategory.children) && selectedRootCategory.children.length > 0) const stepProgressPercent = useMemo(() => { if (activeStep === 1) return 33 @@ -851,6 +868,12 @@ export default function UploadWizard({ description: String(metadata.description || '').trim() || null, } + if (resolvedArtworkId && resolvedArtworkId > 0) { + dispatchMachine({ type: 'PUBLISH_SUCCESS' }) + emitUploadEvent('upload_publish', { id: publishTargetId }) + return + } + if (!machine.sessionId) { if (!publishTargetId) throw new Error('Missing publish id.') const publishController = registerController() @@ -1023,17 +1046,27 @@ export default function UploadWizard({
-

Add details

+

Artwork details

Complete required metadata and rights confirmation before publishing.

Uploaded asset

-
+
{primaryPreviewUrl && !isArchive ? ( - Uploaded artwork thumbnail +
+ Uploaded artwork thumbnail +
) : ( -
📦
+
📦
)}

{primaryFile?.name || 'Primary file selected'}

@@ -1081,6 +1114,10 @@ export default function UploadWizard({ src={iconPath} alt={`${ct.name || 'Content type'} mascot`} className={`h-full w-full object-contain transition-all duration-150 ${active ? 'grayscale-0 opacity-100' : 'grayscale opacity-40 group-hover:grayscale-0 group-hover:opacity-90'}`} + loading="lazy" + decoding="async" + width="80" + height="80" onError={(event) => { if (event.currentTarget.src.includes('mascot_other.webp')) return event.currentTarget.src = '/gfx/mascot_other.webp' @@ -1132,6 +1169,29 @@ export default function UploadWizard({ })}
)} + +
+ + +
{requiresSubCategory && ( @@ -1160,6 +1220,26 @@ export default function UploadWizard({ ) })}
+ +
+ + +
)} @@ -1195,21 +1275,32 @@ export default function UploadWizard({ } return ( -
-
-

Review & publish

-

Review your submission and publish when all checks are ready.

-
+
+
+
+

Review & publish

+

Review your submission and publish when all checks are ready.

+
-
-
-
+
+
+
{primaryPreviewUrl && !isArchive ? ( - Review preview - ) : ( -
Archive
- )} -
+
+ Review preview +
+ ) : ( +
Archive
+ )} +

{metadata.title || 'Untitled artwork'}

@@ -1237,11 +1328,12 @@ export default function UploadWizard({

+
) } return ( -
+
{showRestoredBanner && (
diff --git a/resources/views/layouts/nova.blade.php b/resources/views/layouts/nova.blade.php index 09fd740e..68bcb2f1 100644 --- a/resources/views/layouts/nova.blade.php +++ b/resources/views/layouts/nova.blade.php @@ -7,6 +7,9 @@ + @isset($page_canonical) + + @endisset @@ -15,7 +18,7 @@ @vite(['resources/css/app.css','resources/scss/nova.scss','resources/js/nova.js']) @stack('head') - +
diff --git a/resources/views/legacy/browse.blade.php b/resources/views/legacy/browse.blade.php index 1b1a2306..d0722307 100644 --- a/resources/views/legacy/browse.blade.php +++ b/resources/views/legacy/browse.blade.php @@ -1,45 +1,101 @@ @extends('layouts.nova') @php - use Illuminate\Support\Str; + use App\Banner; @endphp @section('content')
- @php - // legacy responsive ad block - \App\Banner::ShowResponsiveAd(); - @endphp + @php Banner::ShowResponsiveAd(); @endphp - +
+
+
- @if ($artworks->count()) - + + +
+
+
+ +
+
Browse
+ +

Browse Artworks

+ +
+
+
All categories
+

List of all uploaded artworks across Skins, Wallpapers, Photography, and Other.

+
+
+ +
+
+ +
+
+ @forelse ($artworks as $art) + +
+ {{ $art->name ?? 'Artwork' }} +
+
{{ $art->name ?? 'Artwork' }}
+
+ @empty +
+
No Artworks Yet
+
+

Once uploads arrive they will appear here. Check back soon.

+
+
+ @endforelse +
+ +
+ {{ $artworks->withQueryString()->links() }} +
+
+
- @endif - -
- {{-- Use paginator's default view (cursor vs length-aware) to avoid missing $elements in bootstrap view --}} - {{ $artworks->withQueryString()->links() }}
@endsection -@push('scripts') - + +@push('styles') + @endpush diff --git a/tailwind.config.js b/tailwind.config.js index 838c8585..0dd4d0bc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,8 @@ export default { './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', './storage/framework/views/*.php', './resources/views/**/*.blade.php', + // Include frontend JS/TS files so Tailwind picks up dynamic/JSX classes + './resources/js/**/*.{js,jsx,ts,tsx,vue}', ], theme: {