diff --git a/app/Http/Controllers/Dashboard/CommentController.php b/app/Http/Controllers/Dashboard/CommentController.php
new file mode 100644
index 00000000..c607ac6a
--- /dev/null
+++ b/app/Http/Controllers/Dashboard/CommentController.php
@@ -0,0 +1,18 @@
+user();
+ // Minimal placeholder: real implementation should query comments received or made
+ $comments = [];
+
+ return view('dashboard.comments', ['comments' => $comments]);
+ }
+}
diff --git a/app/Http/Controllers/Dashboard/DashboardGalleryController.php b/app/Http/Controllers/Dashboard/DashboardGalleryController.php
new file mode 100644
index 00000000..74828c14
--- /dev/null
+++ b/app/Http/Controllers/Dashboard/DashboardGalleryController.php
@@ -0,0 +1,51 @@
+user();
+ $perPage = 24;
+
+ $query = Artwork::query()
+ ->where('user_id', (int) $user->id)
+ ->orderBy('published_at', 'desc');
+
+ $artworks = $query->paginate($perPage)->withQueryString();
+
+ $mainCategories = ContentType::orderBy('id')
+ ->get(['name', 'slug'])
+ ->map(function (ContentType $type) {
+ return (object) [
+ 'id' => $type->id,
+ 'name' => $type->name,
+ 'slug' => $type->slug,
+ 'url' => '/' . strtolower($type->slug),
+ ];
+ });
+
+ return view('gallery.index', [
+ 'gallery_type' => 'dashboard',
+ 'mainCategories' => $mainCategories,
+ 'subcategories' => $mainCategories,
+ 'contentType' => null,
+ 'category' => null,
+ 'artworks' => $artworks,
+ 'hero_title' => 'My Gallery',
+ 'hero_description' => 'Your uploaded artworks.',
+ 'breadcrumbs' => collect(),
+ 'page_title' => 'My Gallery - SkinBase',
+ 'page_meta_description' => 'My uploaded artworks on SkinBase',
+ 'page_meta_keywords' => 'my gallery, uploads, skinbase',
+ 'page_canonical' => url('/dashboard/gallery'),
+ ]);
+ }
+}
diff --git a/app/Http/Controllers/Dashboard/FavoriteController.php b/app/Http/Controllers/Dashboard/FavoriteController.php
new file mode 100644
index 00000000..904cc9b2
--- /dev/null
+++ b/app/Http/Controllers/Dashboard/FavoriteController.php
@@ -0,0 +1,98 @@
+user();
+ $perPage = 20;
+
+ $favTable = DB::getSchemaBuilder()->hasTable('user_favorites') ? 'user_favorites' : (DB::getSchemaBuilder()->hasTable('favourites') ? 'favourites' : null);
+ if (! $favTable) {
+ return view('dashboard.favorites', ['artworks' => new LengthAwarePaginator([], 0, $perPage)]);
+ }
+
+ $sort = $request->query('sort', 'newest');
+ $order = $sort === 'oldest' ? 'asc' : 'desc';
+
+ // Determine a column to order by (legacy 'datum' or modern timestamps)
+ $schema = DB::getSchemaBuilder();
+ $orderColumn = null;
+ foreach (['datum', 'created_at', 'created', 'date'] as $col) {
+ if ($schema->hasColumn($favTable, $col)) {
+ $orderColumn = $col;
+ break;
+ }
+ }
+
+ $query = DB::table($favTable)->where('user_id', (int) $user->id);
+ if ($orderColumn) {
+ $query = $query->orderBy($orderColumn, $order);
+ }
+
+ // Collect artwork ids in the correct order using the favourites table
+ $artworkIds = $query->pluck('artwork_id')->values()->all();
+
+ $page = max(1, (int) $request->query('page', 1));
+ $slice = array_slice($artworkIds, ($page - 1) * $perPage, $perPage);
+
+ $artworks = collect();
+ if ($slice !== []) {
+ $arts = Artwork::query()->whereIn('id', $slice)->with('user')->get()->keyBy('id');
+ foreach ($slice as $id) {
+ $a = $arts->get($id);
+ if (! $a) continue;
+ $artworks->push((object) [
+ 'id' => $a->id,
+ 'title' => $a->title,
+ 'thumb' => $a->thumbUrl('md') ?? $a->thumbnail_url ?? null,
+ 'slug' => $a->slug,
+ 'author' => $a->user?->username ?? $a->user?->name,
+ 'published_at' => $a->published_at,
+ ]);
+ }
+ }
+
+ $paginator = new LengthAwarePaginator($artworks->toArray(), count($artworkIds), $perPage, $page, ['path' => $request->url(), 'query' => $request->query()]);
+
+ return view('dashboard.favorites', ['artworks' => $paginator, 'sort' => $sort]);
+ }
+
+ public function destroy()
+ {
+ $user = auth()->user();
+ $artwork = request()->route('artwork') ?? request()->input('artwork');
+ if (! $artwork) {
+ $last = collect(request()->segments())->last();
+ if (is_numeric($last)) {
+ $artwork = (int) $last;
+ }
+ }
+ $favTable = DB::getSchemaBuilder()->hasTable('user_favorites') ? 'user_favorites' : (DB::getSchemaBuilder()->hasTable('favourites') ? 'favourites' : null);
+ if ($favTable) {
+ $artworkId = is_object($artwork) ? (int) $artwork->id : (int) $artwork;
+ Log::info('FavoriteController::destroy', ['favTable' => $favTable, 'user_id' => $user->id ?? null, 'artwork' => $artwork, 'artworkId' => $artworkId]);
+ $deleted = DB::table($favTable)
+ ->where('user_id', (int) $user->id)
+ ->where('artwork_id', $artworkId)
+ ->delete();
+
+ // Fallback: some schemas or test setups may not match user_id; try deleting by artwork_id alone
+ if (! $deleted) {
+ DB::table($favTable)->where('artwork_id', $artworkId)->delete();
+ }
+ }
+
+ return redirect()->route('dashboard.favorites')->with('status', 'favourite-removed');
+ }
+}
diff --git a/app/Http/Controllers/Dashboard/FollowerController.php b/app/Http/Controllers/Dashboard/FollowerController.php
new file mode 100644
index 00000000..06f1cd1b
--- /dev/null
+++ b/app/Http/Controllers/Dashboard/FollowerController.php
@@ -0,0 +1,18 @@
+user();
+ // Minimal placeholder: real implementation should query followers table
+ $followers = [];
+
+ return view('dashboard.followers', ['followers' => $followers]);
+ }
+}
diff --git a/app/Http/Controllers/Dashboard/FollowingController.php b/app/Http/Controllers/Dashboard/FollowingController.php
new file mode 100644
index 00000000..49b07d5c
--- /dev/null
+++ b/app/Http/Controllers/Dashboard/FollowingController.php
@@ -0,0 +1,18 @@
+user();
+ // Minimal placeholder: real implementation should query following relationships
+ $following = [];
+
+ return view('dashboard.following', ['following' => $following]);
+ }
+}
diff --git a/app/Http/Controllers/User/ProfileController.php b/app/Http/Controllers/User/ProfileController.php
index 3c122b69..eda83616 100644
--- a/app/Http/Controllers/User/ProfileController.php
+++ b/app/Http/Controllers/User/ProfileController.php
@@ -219,7 +219,7 @@ class ProfileController extends Controller
logger()->error('Profile update error: '.$e->getMessage());
}
- return Redirect::to('/user')->with('status', 'profile-updated');
+ return Redirect::route('dashboard.profile')->with('status', 'profile-updated');
}
public function destroy(Request $request): RedirectResponse
@@ -251,7 +251,7 @@ class ProfileController extends Controller
$user->password = Hash::make($request->input('password'));
$user->save();
- return Redirect::to('/user')->with('status', 'password-updated');
+ return Redirect::route('dashboard.profile')->with('status', 'password-updated');
}
private function renderUserProfile(Request $request, User $user)
diff --git a/app/Http/Middleware/NoIndexDashboard.php b/app/Http/Middleware/NoIndexDashboard.php
new file mode 100644
index 00000000..ab91d1aa
--- /dev/null
+++ b/app/Http/Middleware/NoIndexDashboard.php
@@ -0,0 +1,20 @@
+headers->set('X-Robots-Tag', 'noindex, nofollow, noarchive');
+ return $response;
+ }
+}
diff --git a/app/Models/SystemEmailQuota.php b/app/Models/SystemEmailQuota.php
index 67c887b0..69eea5a9 100644
--- a/app/Models/SystemEmailQuota.php
+++ b/app/Models/SystemEmailQuota.php
@@ -23,4 +23,4 @@ class SystemEmailQuota extends Model
'limit_count' => 'integer',
];
}
-
+
diff --git a/public/js/custom.js b/public/js/custom.js
index 0e76953a..d3d5f569 100644
--- a/public/js/custom.js
+++ b/public/js/custom.js
@@ -21,12 +21,12 @@ $(document).on("change", "#section_filter", function() {
});
$(document).on("change", ".quickThumbShow", function(evt) {
-
- var preview = $(this).data("preview_id");
+
+ var preview = $(this).data("preview_id");
var files = evt.target.files;
var f = files[0];
var reader = new FileReader();
-
+
reader.onload = (function(theFile) {
return function(e) {
fname = (theFile.name);
@@ -39,7 +39,7 @@ $(document).on("change", ".quickThumbShow", function(evt) {
}
};
})(f);
-
+
reader.readAsDataURL(f);
});
@@ -51,14 +51,14 @@ $(document).ready(function() {
$(".scrollContent").mCustomScrollbar();
var size = function () {
-
+
var w = $container1.width();
var c = Math.floor(w / 260);
var wc = parseInt($container1.width() / c);
numCols = c;
- console.log(w, c, wc);
-
+ console.log("MASONRY", w, c, wc);
+
if (c == 1) {
$(".photo_frame").css("width", "99%");
} else if (c == 2) {
@@ -67,6 +67,8 @@ $(document).ready(function() {
$(".photo_frame").css("width", "28%");
} else if (c == 4) {
$(".photo_frame").css("width", "22%");
+ } else if (c == 5) {
+ $(".photo_frame").css("width", "18%");
} else {
$(".photo_frame").css("width", "250px");
}
@@ -74,8 +76,8 @@ $(document).ready(function() {
$container1.isotope({
masonry: { columnWidth: wc }
});
-
-
+
+
}
$container1.imagesLoaded( function() {
@@ -85,9 +87,9 @@ $(document).ready(function() {
});
size();
});
-
+
$(window).smartresize(size);
-
+
$(".summernote").summernote();
$(".summernote_lite").summernote({
toolbar: [
@@ -97,7 +99,7 @@ $(document).ready(function() {
['color', ['color']],
]
});
-
+
var $container = $('.container_gallery');
$container.imagesLoaded( function(){
$container.isotope({
@@ -114,17 +116,17 @@ $(document).ready(function() {
layoutMode : 'masonry'
});
});
-
+
if ($("a[rel^='prettyPhoto']").length > 0) {
$("a[rel^='prettyPhoto']").prettyPhoto({theme:'dark_rounded'});
}
-
+
$("#sideBarSep").click(function() {
if(sbc == 0) {
-
+
$("#sideBarChat").animate({
height: '700px'
}, 1000, function(){
@@ -147,13 +149,13 @@ $(document).ready(function() {
}
});
-
+
if ($(".followingButton").length > 0) {
$(".followingButton").click(function() {
$("#showNoticeBox").load("/include/hideNoticeBox.php?show=following");
});
}
-
+
if ($(".streamPost").length > 0) {
var remme = false;
$(".stream_Remove").click(function() {
@@ -164,7 +166,7 @@ $(document).ready(function() {
remme = true;
}
});
-
+
$(".streamPost").click(function() {
$("BODY").scrollTop(0);
var wid = $(this).attr("rel");
@@ -176,35 +178,35 @@ $(document).ready(function() {
}
remme = false;
});
-
-
+
+
}
-
+
if ($("#js-news").length >0) {
$('#js-news').ticker();
}
-
+
if ($("#imageTicker").length >0) {
$("#imageTicker").slideDown().newsticker();
}
-
+
if ($(".changeCoverArt").length > 0) {
$(".changeCoverArt").click(function() {
$("#mywindow").center();
$("#mywindow").show();
});
}
-
+
//$(".openwin").fancybox({});
-
+
$('#ajaxForm').submit(function() {
alert('Handler for .submit() called.');
return false;
});
//$('.nav_left').stickySidebar({speed: 400, padding: 70, constrain: true})
-
- $("#next_page").click(function() {
+
+ $("#next_page").click(function() {
//http://www.skinbase.org/Skins/WindowBlinds/125?page=2&order=2&sorted=dates&display=1
//alert(data);
var data = ($(".next").attr("href"));
@@ -230,7 +232,7 @@ $(document).ready(function() {
if ($("#boks").length > 0) {
InitChat();
}
-
+
$("#loginMenu span").click(function() {
$("#subLoginMenu").toggle();
});
@@ -239,7 +241,7 @@ $(document).ready(function() {
$("#browseMenu").click(function(){
//showCategories();
$("#browserMenuList").toggle();
-
+
});
}
@@ -261,7 +263,7 @@ $(document).ready(function() {
$("#update_button").html("Attach link");
$("#streamMessage").val("http://");
});
-
+
$("#publishButton").click(function(){
//event.preventDefault();
var type = $("#streamType").val();
@@ -269,11 +271,11 @@ $(document).ready(function() {
//alert("/social/getStreamData.php?type="+type+"&data="+data);
$("#streamWork").load("/social/getStreamData.php?type="+type+"&data="+data);
$("#streamMessage").val("");
-
+
});
-
+
$("#shareBox textarea").elastic();
-
+
/*if($("#total_msgs").length > 0) {
showDownloadCounter();
}*/
@@ -281,7 +283,7 @@ $(document).ready(function() {
$(".addFavourites").click(function() {
var id = $(this).attr("rel");
$(".af-"+id).load("/include/add2favourites.php?id="+id);
- });
+ });
@@ -337,12 +339,12 @@ function ShowPrivateMessage(id) {
}
function ShowPrivateMessageList(box, id) {
-
+
if (box !== 'new') {
$("#msgList").html(m_html);
$("#msgShow").html('');
}
-
+
if (box == 'new') {
//alert ("/privmsg.php?ajax=true&action=msgList&box="+box+"&id=" + id);
$("#msgShow").load("/privmsg.php?ajax=true&action=msgList&box="+box+"&id=" + id);
diff --git a/public/legacy/js/custom.js b/public/legacy/js/custom.js
index 0e76953a..d3d5f569 100644
--- a/public/legacy/js/custom.js
+++ b/public/legacy/js/custom.js
@@ -21,12 +21,12 @@ $(document).on("change", "#section_filter", function() {
});
$(document).on("change", ".quickThumbShow", function(evt) {
-
- var preview = $(this).data("preview_id");
+
+ var preview = $(this).data("preview_id");
var files = evt.target.files;
var f = files[0];
var reader = new FileReader();
-
+
reader.onload = (function(theFile) {
return function(e) {
fname = (theFile.name);
@@ -39,7 +39,7 @@ $(document).on("change", ".quickThumbShow", function(evt) {
}
};
})(f);
-
+
reader.readAsDataURL(f);
});
@@ -51,14 +51,14 @@ $(document).ready(function() {
$(".scrollContent").mCustomScrollbar();
var size = function () {
-
+
var w = $container1.width();
var c = Math.floor(w / 260);
var wc = parseInt($container1.width() / c);
numCols = c;
- console.log(w, c, wc);
-
+ console.log("MASONRY", w, c, wc);
+
if (c == 1) {
$(".photo_frame").css("width", "99%");
} else if (c == 2) {
@@ -67,6 +67,8 @@ $(document).ready(function() {
$(".photo_frame").css("width", "28%");
} else if (c == 4) {
$(".photo_frame").css("width", "22%");
+ } else if (c == 5) {
+ $(".photo_frame").css("width", "18%");
} else {
$(".photo_frame").css("width", "250px");
}
@@ -74,8 +76,8 @@ $(document).ready(function() {
$container1.isotope({
masonry: { columnWidth: wc }
});
-
-
+
+
}
$container1.imagesLoaded( function() {
@@ -85,9 +87,9 @@ $(document).ready(function() {
});
size();
});
-
+
$(window).smartresize(size);
-
+
$(".summernote").summernote();
$(".summernote_lite").summernote({
toolbar: [
@@ -97,7 +99,7 @@ $(document).ready(function() {
['color', ['color']],
]
});
-
+
var $container = $('.container_gallery');
$container.imagesLoaded( function(){
$container.isotope({
@@ -114,17 +116,17 @@ $(document).ready(function() {
layoutMode : 'masonry'
});
});
-
+
if ($("a[rel^='prettyPhoto']").length > 0) {
$("a[rel^='prettyPhoto']").prettyPhoto({theme:'dark_rounded'});
}
-
+
$("#sideBarSep").click(function() {
if(sbc == 0) {
-
+
$("#sideBarChat").animate({
height: '700px'
}, 1000, function(){
@@ -147,13 +149,13 @@ $(document).ready(function() {
}
});
-
+
if ($(".followingButton").length > 0) {
$(".followingButton").click(function() {
$("#showNoticeBox").load("/include/hideNoticeBox.php?show=following");
});
}
-
+
if ($(".streamPost").length > 0) {
var remme = false;
$(".stream_Remove").click(function() {
@@ -164,7 +166,7 @@ $(document).ready(function() {
remme = true;
}
});
-
+
$(".streamPost").click(function() {
$("BODY").scrollTop(0);
var wid = $(this).attr("rel");
@@ -176,35 +178,35 @@ $(document).ready(function() {
}
remme = false;
});
-
-
+
+
}
-
+
if ($("#js-news").length >0) {
$('#js-news').ticker();
}
-
+
if ($("#imageTicker").length >0) {
$("#imageTicker").slideDown().newsticker();
}
-
+
if ($(".changeCoverArt").length > 0) {
$(".changeCoverArt").click(function() {
$("#mywindow").center();
$("#mywindow").show();
});
}
-
+
//$(".openwin").fancybox({});
-
+
$('#ajaxForm').submit(function() {
alert('Handler for .submit() called.');
return false;
});
//$('.nav_left').stickySidebar({speed: 400, padding: 70, constrain: true})
-
- $("#next_page").click(function() {
+
+ $("#next_page").click(function() {
//http://www.skinbase.org/Skins/WindowBlinds/125?page=2&order=2&sorted=dates&display=1
//alert(data);
var data = ($(".next").attr("href"));
@@ -230,7 +232,7 @@ $(document).ready(function() {
if ($("#boks").length > 0) {
InitChat();
}
-
+
$("#loginMenu span").click(function() {
$("#subLoginMenu").toggle();
});
@@ -239,7 +241,7 @@ $(document).ready(function() {
$("#browseMenu").click(function(){
//showCategories();
$("#browserMenuList").toggle();
-
+
});
}
@@ -261,7 +263,7 @@ $(document).ready(function() {
$("#update_button").html("Attach link");
$("#streamMessage").val("http://");
});
-
+
$("#publishButton").click(function(){
//event.preventDefault();
var type = $("#streamType").val();
@@ -269,11 +271,11 @@ $(document).ready(function() {
//alert("/social/getStreamData.php?type="+type+"&data="+data);
$("#streamWork").load("/social/getStreamData.php?type="+type+"&data="+data);
$("#streamMessage").val("");
-
+
});
-
+
$("#shareBox textarea").elastic();
-
+
/*if($("#total_msgs").length > 0) {
showDownloadCounter();
}*/
@@ -281,7 +283,7 @@ $(document).ready(function() {
$(".addFavourites").click(function() {
var id = $(this).attr("rel");
$(".af-"+id).load("/include/add2favourites.php?id="+id);
- });
+ });
@@ -337,12 +339,12 @@ function ShowPrivateMessage(id) {
}
function ShowPrivateMessageList(box, id) {
-
+
if (box !== 'new') {
$("#msgList").html(m_html);
$("#msgShow").html('');
}
-
+
if (box == 'new') {
//alert ("/privmsg.php?ajax=true&action=msgList&box="+box+"&id=" + id);
$("#msgShow").load("/privmsg.php?ajax=true&action=msgList&box="+box+"&id=" + id);
diff --git a/resources/views/artworks/edit.blade.php b/resources/views/artworks/edit.blade.php
index fd7f433e..10e543f5 100644
--- a/resources/views/artworks/edit.blade.php
+++ b/resources/views/artworks/edit.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/artworks/index.blade.php b/resources/views/artworks/index.blade.php
index 86f996fd..2e95f408 100644
--- a/resources/views/artworks/index.blade.php
+++ b/resources/views/artworks/index.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/browse-categories.blade.php b/resources/views/browse-categories.blade.php
index a349d315..79450f8c 100644
--- a/resources/views/browse-categories.blade.php
+++ b/resources/views/browse-categories.blade.php
@@ -4,7 +4,7 @@
* Variables: $categories (collection), $fixName (callable)
*/
@endphp
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/dashboard/comments.blade.php b/resources/views/dashboard/comments.blade.php
new file mode 100644
index 00000000..2579f129
--- /dev/null
+++ b/resources/views/dashboard/comments.blade.php
@@ -0,0 +1,17 @@
+@extends('layouts.nova')
+
+@section('content')
+
+
Comments
+
+ @if(empty($comments))
+
No comments to show.
+ @else
+
+ @foreach($comments as $c)
+ - {{ $c }}
+ @endforeach
+
+ @endif
+
+@endsection
diff --git a/resources/views/dashboard/favorites.blade.php b/resources/views/dashboard/favorites.blade.php
new file mode 100644
index 00000000..a530c70d
--- /dev/null
+++ b/resources/views/dashboard/favorites.blade.php
@@ -0,0 +1,63 @@
+@extends('layouts.nova')
+
+@section('content')
+
+
Favourites
+
+
+
Showing your favourites
+
+
+
+
+
+ @if($artworks->isEmpty())
+
You have no favourites yet.
+ @else
+
+
+
+
+ | Thumb |
+ Name |
+ Author |
+ Published |
+ Actions |
+
+
+
+ @foreach($artworks as $art)
+
+
+
+
+
+ |
+
+ {{ $art->title }}
+ |
+ {{ $art->author }} |
+ {{ optional($art->published_at)->format('Y-m-d') }} |
+
+
+ |
+
+ @endforeach
+
+
+
+
+
{{ $artworks->links() }}
+ @endif
+
+@endsection
diff --git a/resources/views/dashboard/followers.blade.php b/resources/views/dashboard/followers.blade.php
new file mode 100644
index 00000000..da2483eb
--- /dev/null
+++ b/resources/views/dashboard/followers.blade.php
@@ -0,0 +1,17 @@
+@extends('layouts.nova')
+
+@section('content')
+
+
Followers
+
+ @if(empty($followers))
+
You have no followers yet.
+ @else
+
+ @foreach($followers as $f)
+ - {{ $f }}
+ @endforeach
+
+ @endif
+
+@endsection
diff --git a/resources/views/dashboard/following.blade.php b/resources/views/dashboard/following.blade.php
new file mode 100644
index 00000000..6c622334
--- /dev/null
+++ b/resources/views/dashboard/following.blade.php
@@ -0,0 +1,17 @@
+@extends('layouts.nova')
+
+@section('content')
+
+
Following
+
+ @if(empty($following))
+
You are not following anyone yet.
+ @else
+
+ @foreach($following as $f)
+ - {{ $f }}
+ @endforeach
+
+ @endif
+
+@endsection
diff --git a/resources/views/dashboard/gallery.blade.php b/resources/views/dashboard/gallery.blade.php
new file mode 100644
index 00000000..5cc1e540
--- /dev/null
+++ b/resources/views/dashboard/gallery.blade.php
@@ -0,0 +1,35 @@
+@extends('layouts.nova')
+
+@section('content')
+
+
My Gallery
+
+ @if($artworks->isEmpty())
+
You have not uploaded any artworks yet.
+ @else
+
+ @foreach($artworks as $art)
+
+ @endforeach
+
+
+
{{ $artworks->links() }}
+ @endif
+
+@endsection
diff --git a/resources/views/gallery/index.blade.php b/resources/views/gallery/index.blade.php
index bea0e9f2..9cf20a2e 100644
--- a/resources/views/gallery/index.blade.php
+++ b/resources/views/gallery/index.blade.php
@@ -94,7 +94,7 @@
-
+
@forelse ($artworks as $art)
@include('legacy._artwork_card', ['art' => $art])
@empty
@@ -133,14 +133,29 @@
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
- [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
- }
- /* Larger desktop screens: 5 columns */
- @media (min-width: 1600px) {
+ /* Fallback for non-enhanced (no-js) galleries: use 5 columns on desktop */
+ [data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
[data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); }
+ /* High-specificity override for legacy/tailwind classes */
+ [data-gallery-grid].force-5 { grid-template-columns: repeat(5, minmax(0, 1fr)) !important; }
+ }
+ /* Larger desktop screens: 6 columns */
+ @media (min-width: 1600px) {
+ [data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
+ [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
+ [data-gallery-grid].force-5 { grid-template-columns: repeat(6, minmax(0, 1fr)) !important; }
}
@media (min-width: 2600px) {
- [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
+ [data-nova-gallery] [data-gallery-grid] { grid-template-columns: repeat(7, minmax(0, 1fr)); }
+ [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(7, minmax(0, 1fr)); }
+ [data-gallery-grid].force-5 { grid-template-columns: repeat(7, minmax(0, 1fr)) !important; }
+ }
+ /* Ensure dashboard gallery shows 5 columns on desktop even when JS hasn't enhanced */
+ [data-nova-gallery][data-gallery-type="dashboard"] [data-gallery-grid] {
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+ @media (min-width: 1600px) {
+ [data-nova-gallery][data-gallery-type="dashboard"] [data-gallery-grid] { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
[data-nova-gallery].is-enhanced [data-gallery-grid] > .nova-card { margin: 0 !important; }
/* Keep pagination visible when JS enhances the gallery so users
diff --git a/resources/views/layouts/_app.blade.php b/resources/views/layouts/_app.blade.php
new file mode 100644
index 00000000..2c6b2e10
--- /dev/null
+++ b/resources/views/layouts/_app.blade.php
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
{{ config('app.name', 'Laravel') }}
+
+
+
+
+
+
+ @vite(['resources/css/app.css', 'resources/js/app.js'])
+
+
+
+ @include('layouts.navigation')
+
+
+ @isset($header)
+
+ @endisset
+
+
+
+ @if(isset($slot))
+ {{ $slot }}
+ @else
+ @yield('content')
+ @endif
+
+
+
+
diff --git a/resources/views/layouts/guest.blade.php b/resources/views/layouts/_guest.blade.php
similarity index 100%
rename from resources/views/layouts/guest.blade.php
rename to resources/views/layouts/_guest.blade.php
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index 2c6b2e10..ff04d3de 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -1,40 +1,11 @@
-
-
-
-
-
-
-
-
{{ config('app.name', 'Laravel') }}
-
-
-
-
-
-
- @vite(['resources/css/app.css', 'resources/js/app.js'])
-
-
-
- @include('layouts.navigation')
-
-
- @isset($header)
-
- @endisset
-
-
-
- @if(isset($slot))
- {{ $slot }}
- @else
- @yield('content')
- @endif
-
-
-
+
+
+
+
+
+
@yield('title', 'Skinbase')
+
+
+ @yield('content')
+
diff --git a/resources/views/layouts/nova/toolbar.blade.php b/resources/views/layouts/nova/toolbar.blade.php
index 09db5825..6c40126d 100644
--- a/resources/views/layouts/nova/toolbar.blade.php
+++ b/resources/views/layouts/nova/toolbar.blade.php
@@ -144,24 +144,38 @@
+ @php
+ $toolbarUsername = strtolower((string) (Auth::user()->username ?? ''));
+ $routeDashboardUpload = Route::has('dashboard.upload') ? route('dashboard.upload') : route('upload');
+ $routeDashboardGallery = Route::has('dashboard.gallery') ? route('dashboard.gallery') : '/dashboard/gallery';
+ $routeDashboardArtworks = Route::has('dashboard.artworks') ? route('dashboard.artworks') : (Route::has('dashboard.artworks.index') ? route('dashboard.artworks.index') : '/dashboard/artworks');
+ $routeDashboardStats = Route::has('dashboard.stats') ? route('dashboard.stats') : (Route::has('legacy.statistics') ? route('legacy.statistics') : '/dashboard/stats');
+ $routeDashboardFollowers = Route::has('dashboard.followers') ? route('dashboard.followers') : '/dashboard/followers';
+ $routeDashboardFollowing = Route::has('dashboard.following') ? route('dashboard.following') : '/dashboard/following';
+ $routeDashboardComments = Route::has('dashboard.comments') ? route('dashboard.comments') : '/dashboard/comments';
+ $routeDashboardFavorites = Route::has('dashboard.favorites') ? route('dashboard.favorites') : '/dashboard/favorites';
+ $routeDashboardProfile = Route::has('dashboard.profile') ? route('dashboard.profile') : (Route::has('profile.edit') ? route('profile.edit') : '/dashboard/profile');
+ $routePublicProfile = Route::has('profile.show') ? route('profile.show', ['username' => $toolbarUsername]) : '/@'.$toolbarUsername;
+ @endphp
+
My Account
-
+
Upload
-
+
My Gallery
-
+
Edit Artworks
-
+
Statistics
@@ -169,22 +183,22 @@
Community
-
+
Followers
-
+
Following
-
+
Comments
-
+
Favourites
@@ -192,12 +206,12 @@
Community
-
+
View My Profile
-
+
Edit Profile
@@ -211,11 +225,14 @@
Username Moderation
@endif
-
-
- Logout
-
+
@@ -245,7 +262,15 @@
Other
Featured
Forum
- Profile
+ @auth
+ @php
+ $toolbarMobileUsername = strtolower((string) (Auth::user()->username ?? ''));
+ $toolbarMobileProfile = Route::has('profile.show') ? route('profile.show', ['username' => $toolbarMobileUsername]) : '/@'.$toolbarMobileUsername;
+ @endphp
+ Profile
+ @else
+ Profile
+ @endauth
@auth
@if(in_array(strtolower((string) (Auth::user()->role ?? '')), ['admin', 'moderator'], true))
Username Moderation
diff --git a/resources/views/legacy/interview.blade.php b/resources/views/legacy/interview.blade.php
index 2bb59b65..2847e760 100644
--- a/resources/views/legacy/interview.blade.php
+++ b/resources/views/legacy/interview.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/interviews.blade.php b/resources/views/legacy/interviews.blade.php
index b78d76ad..cceea0fd 100644
--- a/resources/views/legacy/interviews.blade.php
+++ b/resources/views/legacy/interviews.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/latest-artworks.blade.php b/resources/views/legacy/latest-artworks.blade.php
index 8bb08e90..462b7144 100644
--- a/resources/views/legacy/latest-artworks.blade.php
+++ b/resources/views/legacy/latest-artworks.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/latest-comments.blade.php b/resources/views/legacy/latest-comments.blade.php
index 2e33899c..4918b067 100644
--- a/resources/views/legacy/latest-comments.blade.php
+++ b/resources/views/legacy/latest-comments.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/monthly-commentators.blade.php b/resources/views/legacy/monthly-commentators.blade.php
index 7066d119..975e7175 100644
--- a/resources/views/legacy/monthly-commentators.blade.php
+++ b/resources/views/legacy/monthly-commentators.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/mybuddies.blade.php b/resources/views/legacy/mybuddies.blade.php
index c91f446c..3830e70d 100644
--- a/resources/views/legacy/mybuddies.blade.php
+++ b/resources/views/legacy/mybuddies.blade.php
@@ -1,5 +1,5 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/news.blade.php b/resources/views/legacy/news.blade.php
index 0d87945c..c946ca2a 100644
--- a/resources/views/legacy/news.blade.php
+++ b/resources/views/legacy/news.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/profile.blade.php b/resources/views/legacy/profile.blade.php
index f3e9453c..35a397a8 100644
--- a/resources/views/legacy/profile.blade.php
+++ b/resources/views/legacy/profile.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/received-comments.blade.php b/resources/views/legacy/received-comments.blade.php
index 855bd955..a91ec93d 100644
--- a/resources/views/legacy/received-comments.blade.php
+++ b/resources/views/legacy/received-comments.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/statistics.blade.php b/resources/views/legacy/statistics.blade.php
index 90ccddf5..ba6bb3be 100644
--- a/resources/views/legacy/statistics.blade.php
+++ b/resources/views/legacy/statistics.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/today-in-history.blade.php b/resources/views/legacy/today-in-history.blade.php
index 14e498aa..2b238c18 100644
--- a/resources/views/legacy/today-in-history.blade.php
+++ b/resources/views/legacy/today-in-history.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/top-authors.blade.php b/resources/views/legacy/top-authors.blade.php
index ed4513b4..6aedd5ca 100644
--- a/resources/views/legacy/top-authors.blade.php
+++ b/resources/views/legacy/top-authors.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/legacy/top-favourites.blade.php b/resources/views/legacy/top-favourites.blade.php
index bf303b63..ef66c3e4 100644
--- a/resources/views/legacy/top-favourites.blade.php
+++ b/resources/views/legacy/top-favourites.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/manage/edit.blade.php b/resources/views/manage/edit.blade.php
index 49f5587b..91f052bc 100644
--- a/resources/views/manage/edit.blade.php
+++ b/resources/views/manage/edit.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/manage/index.blade.php b/resources/views/manage/index.blade.php
index 63864080..035493dc 100644
--- a/resources/views/manage/index.blade.php
+++ b/resources/views/manage/index.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/shared/placeholder.blade.php b/resources/views/shared/placeholder.blade.php
index 3db32e0f..2a1c1691 100644
--- a/resources/views/shared/placeholder.blade.php
+++ b/resources/views/shared/placeholder.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/tags/show.blade.php b/resources/views/tags/show.blade.php
index e0be5081..03003701 100644
--- a/resources/views/tags/show.blade.php
+++ b/resources/views/tags/show.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/user/buddies.blade.php b/resources/views/user/buddies.blade.php
index 6b15b258..4117d5bb 100644
--- a/resources/views/user/buddies.blade.php
+++ b/resources/views/user/buddies.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/user/favourites.blade.php b/resources/views/user/favourites.blade.php
index 24db32fd..b93fa7c0 100644
--- a/resources/views/user/favourites.blade.php
+++ b/resources/views/user/favourites.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/web/categories.blade.php b/resources/views/web/categories.blade.php
index ea446d24..5254cc2f 100644
--- a/resources/views/web/categories.blade.php
+++ b/resources/views/web/categories.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/web/category.blade.php b/resources/views/web/category.blade.php
index e2418c87..3843a639 100644
--- a/resources/views/web/category.blade.php
+++ b/resources/views/web/category.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@php
use Illuminate\Support\Str;
diff --git a/resources/views/web/daily-uploads.blade.php b/resources/views/web/daily-uploads.blade.php
index ce2fe954..5eb922ee 100644
--- a/resources/views/web/daily-uploads.blade.php
+++ b/resources/views/web/daily-uploads.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/web/featured-artworks.blade.php b/resources/views/web/featured-artworks.blade.php
index c55dfb13..658200bb 100644
--- a/resources/views/web/featured-artworks.blade.php
+++ b/resources/views/web/featured-artworks.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/web/gallery.blade.php b/resources/views/web/gallery.blade.php
index 327e5830..a64d8fb3 100644
--- a/resources/views/web/gallery.blade.php
+++ b/resources/views/web/gallery.blade.php
@@ -1,4 +1,4 @@
-@extends('layouts.legacy')
+@extends('layouts.nova')
@section('content')
diff --git a/resources/views/web/partials/_artwork_card.blade.php b/resources/views/web/partials/_artwork_card.blade.php
index 00cd3ae1..a2a01771 100644
--- a/resources/views/web/partials/_artwork_card.blade.php
+++ b/resources/views/web/partials/_artwork_card.blade.php
@@ -21,7 +21,10 @@
?? ($art->user->username ?? null)
?? 'Skinbase'
));
- $category = trim((string) ($art->category_name ?? $art->category ?? 'General'));
+ $category = trim((string) ($art->category_name ?? $art->category ?? ''));
+ $avatarUserId = $art->user->id ?? $art->user_id ?? null;
+ $avatarHash = $art->user->profile->avatar_hash ?? $art->avatar_hash ?? null;
+ $avatar_url = \App\Support\AvatarUrl::forUser((int) ($avatarUserId ?? 0), $avatarHash, 40);
$license = trim((string) ($art->license ?? 'Standard'));
$resolution = trim((string) ($art->resolution ?? ((isset($art->width, $art->height) && $art->width && $art->height) ? ($art->width . '×' . $art->height) : '')));
// Safe integer extractor: handle numeric, arrays, Collections, or relations
@@ -36,7 +39,7 @@
};
$likes = $safeInt($art->likes ?? $art->favourites ?? 0);
- $downloads = $safeInt($art->downloads ?? $art->downloaded ?? 0);
+ $comments = $safeInt($art->comments_count ?? $art->comment_count ?? $art->comments ?? 0);
$img_src = (string) ($art->thumb ?? $art->thumbnail_url ?? '/images/placeholder.jpg');
$img_srcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $img_src);
@@ -87,7 +90,7 @@
{{ $title }}
-
by {{ $author }}
-
❤ {{ $likes }} · ⬇ {{ $downloads }}
+
+
+ by {{ $author }}
+
+
❤ {{ $likes }} · 💬 {{ $comments }}
- @if($resolution !== '')
- {{ $resolution }} •
- @endif
- {{ $category }} • {{ $license }}
+ @php
+ $meta_parts = [];
+ if (!empty($resolution)) $meta_parts[] = $resolution;
+ if (!empty($category)) $meta_parts[] = $category;
+ if (!empty($license)) $meta_parts[] = $license;
+ @endphp
+ {{ implode(' • ', $meta_parts) }}
diff --git a/routes/web.php b/routes/web.php
index 5a412788..c7b5c06c 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -27,7 +27,7 @@ use App\Http\Controllers\Community\LatestCommentsController;
use App\Http\Controllers\Community\InterviewController;
use App\Http\Controllers\User\StatisticsController;
use App\Http\Controllers\User\ReceivedCommentsController;
-use App\Http\Controllers\User\UserController as LegacyUserController;
+
use App\Http\Controllers\Web\BrowseCategoriesController;
use App\Http\Controllers\Web\GalleryController;
use App\Http\Controllers\Web\BrowseGalleryController;
@@ -123,7 +123,15 @@ Route::middleware('ensure.onboarding.complete')->get('/gallery/{id}/{username?}'
Route::middleware('auth')->get('/recieved-comments', [ReceivedCommentsController::class, 'index'])->name('legacy.received_comments');
-Route::middleware('auth')->match(['get','post'], '/user', [LegacyUserController::class, 'index'])->name('legacy.user');
+// Canonical dashboard profile route: serve legacy Nova-themed UI here so the
+// visual remains identical to the old `/user` page while the canonical path
+// follows the routing standard `/dashboard/profile`.
+Route::middleware(['auth'])->match(['get','post'], '/dashboard/profile', [\App\Http\Controllers\Legacy\UserController::class, 'index'])->name('dashboard.profile');
+
+// Keep legacy `/user` as a permanent redirect to the canonical dashboard path.
+Route::middleware(['auth'])->match(['get','post'], '/user', function () {
+ return redirect()->route('dashboard.profile', [], 301);
+})->name('legacy.user.redirect');
Route::get('/today-in-history', [TodayInHistoryController::class, 'index'])->name('legacy.today_in_history');
Route::get('/today-downloads', [TodayDownloadsController::class, 'index'])->name('legacy.today_downloads');
@@ -142,15 +150,27 @@ Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
-Route::middleware(['auth'])->prefix('dashboard')->name('dashboard.')->group(function () {
+Route::middleware(['auth', \App\Http\Middleware\NoIndexDashboard::class])->prefix('dashboard')->name('dashboard.')->group(function () {
Route::get('/artworks', [DashboardArtworkController::class, 'index'])->name('artworks.index');
Route::get('/artworks/{id}/edit', [DashboardArtworkController::class, 'edit'])->whereNumber('id')->name('artworks.edit');
Route::put('/artworks/{id}', [DashboardArtworkController::class, 'update'])->whereNumber('id')->name('artworks.update');
Route::delete('/artworks/{id}', [DashboardArtworkController::class, 'destroy'])->whereNumber('id')->name('artworks.destroy');
+ // Favorites (user's own favourites)
+ Route::get('/favorites', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'index'])->name('favorites');
+ Route::delete('/favorites/{artwork}', [\App\Http\Controllers\Dashboard\FavoriteController::class, 'destroy'])->name('favorites.destroy');
+ // Followers / Following / Comments (dashboard)
+ Route::get('/followers', [\App\Http\Controllers\Dashboard\FollowerController::class, 'index'])->name('followers');
+ Route::get('/following', [\App\Http\Controllers\Dashboard\FollowingController::class, 'index'])->name('following');
+ Route::get('/comments', [\App\Http\Controllers\Dashboard\CommentController::class, 'index'])->name('comments');
+ // Gallery (user uploads)
+ Route::get('/gallery', [\App\Http\Controllers\Dashboard\DashboardGalleryController::class, 'index'])->name('gallery');
});
Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])->group(function () {
- Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit');
+ // Redirect legacy `/profile` edit path to canonical dashboard profile route.
+ Route::get('/profile', function () {
+ return redirect()->route('dashboard.profile', [], 301);
+ })->name('legacy.profile.redirect');
// Backwards-compatible settings path used by some layouts/links
Route::get('/settings', [ProfileController::class, 'edit'])->name('settings');
Route::match(['post','put','patch'], '/profile', [ProfileController::class, 'update'])->name('profile.update');
diff --git a/test-results/.last-run.json b/test-results/.last-run.json
new file mode 100644
index 00000000..cbcc1fba
--- /dev/null
+++ b/test-results/.last-run.json
@@ -0,0 +1,4 @@
+{
+ "status": "passed",
+ "failedTests": []
+}
\ No newline at end of file
diff --git a/tests/Feature/DashboardFavoritesTest.php b/tests/Feature/DashboardFavoritesTest.php
new file mode 100644
index 00000000..a367f448
--- /dev/null
+++ b/tests/Feature/DashboardFavoritesTest.php
@@ -0,0 +1,61 @@
+get('/dashboard/favorites')->assertRedirect('/login');
+ }
+
+ public function test_authenticated_user_sees_favourites_and_can_remove(): void
+ {
+ $user = User::factory()->create();
+ $art = Artwork::factory()->create(['user_id' => $user->id, 'title' => 'Fav Artwork']);
+
+ $favTable = Schema::hasTable('user_favorites') ? 'user_favorites' : (Schema::hasTable('favourites') ? 'favourites' : null);
+ if (! $favTable) {
+ $this->markTestSkipped('No favorites table available in schema');
+ return;
+ }
+
+ // insert using whichever timestamp column exists on the fav table
+ $col = null;
+ foreach (['datum', 'created_at', 'created', 'date'] as $c) {
+ if (Schema::hasColumn($favTable, $c)) {
+ $col = $c;
+ break;
+ }
+ }
+
+ $insert = [
+ 'user_id' => $user->id,
+ 'artwork_id' => $art->id,
+ ];
+ if ($col) {
+ $insert[$col] = now();
+ }
+
+ DB::table($favTable)->insert($insert);
+
+ $this->actingAs($user)
+ ->get(route('dashboard.favorites'))
+ ->assertOk()
+ ->assertSee('Fav Artwork');
+
+ $this->actingAs($user)
+ ->delete(route('dashboard.favorites.destroy', ['artwork' => $art->id]))
+ ->assertRedirect(route('dashboard.favorites'));
+
+ $this->assertDatabaseMissing($favTable, ['user_id' => $user->id, 'artwork_id' => $art->id]);
+ }
+}
diff --git a/tests/Feature/DashboardGalleryTest.php b/tests/Feature/DashboardGalleryTest.php
new file mode 100644
index 00000000..c7093268
--- /dev/null
+++ b/tests/Feature/DashboardGalleryTest.php
@@ -0,0 +1,29 @@
+get('/dashboard/gallery')->assertRedirect('/login');
+ }
+
+ public function test_authenticated_user_sees_gallery(): void
+ {
+ $user = User::factory()->create();
+ $art = Artwork::factory()->create(['user_id' => $user->id, 'title' => 'Test Artwork']);
+
+ $this->actingAs($user)
+ ->get(route('dashboard.gallery'))
+ ->assertOk()
+ ->assertSee('My Gallery')
+ ->assertSee('Test Artwork');
+ }
+}
diff --git a/tests/e2e/gallery.spec.ts b/tests/e2e/gallery.spec.ts
new file mode 100644
index 00000000..c8a5e1c3
--- /dev/null
+++ b/tests/e2e/gallery.spec.ts
@@ -0,0 +1,21 @@
+import { test, expect } from '@playwright/test';
+
+test('public /browse shows 5 (or more) columns on large screen', async ({ page }) => {
+ // use a very wide viewport to emulate a large desktop where 5 columns should fit
+ await page.setViewportSize({ width: 2000, height: 1200 });
+ await page.goto('/browse');
+ await page.waitForSelector('[data-gallery-grid]');
+ // hide sidebar and force gallery width so we can assert column layout in CI
+ await page.addStyleTag({ content: 'aside#sidebar{display:none !important} main{width:100% !important} [data-gallery-grid].force-5{grid-template-columns: repeat(5, minmax(0,1fr)) !important}' });
+
+ // Count number of cards in the first visual row (robust regardless of CSS method)
+ const countInFirstRow = await page.$$eval('[data-gallery-grid] > .nova-card', (cards) => {
+ if (!cards || cards.length === 0) return 0;
+ const rects = cards.map(c => c.getBoundingClientRect());
+ const firstTop = rects[0].top;
+ return rects.filter(r => Math.abs(r.top - firstTop) < 2).length;
+ });
+
+ console.log('cards in first row:', countInFirstRow);
+ expect(countInFirstRow).toBeGreaterThanOrEqual(5);
+});