feat: Inertia profile settings page, Studio edit redesign, EGS, Nova UI components\n\n- Redesign /dashboard/profile as Inertia React page (Settings/ProfileEdit)\n with SettingsLayout sidebar, Nova UI components (TextInput, Textarea,\n Toggle, Select, RadioGroup, Modal, Button), avatar drag-and-drop,\n password change, and account deletion sections\n- Redesign Studio artwork edit page with two-column layout, Nova components,\n integrated TagPicker, and version history modal\n- Add shared MarkdownEditor component\n- Add Early-Stage Growth System (EGS): SpotlightEngine, FeedBlender,\n GridFiller, AdaptiveTimeWindow, ActivityLayer, admin panel\n- Fix upload category/tag persistence (V1+V2 paths)\n- Fix tag source enum, category tree display, binding resolution\n- Add settings.jsx Vite entry, settings.blade.php wrapper\n- Update ProfileController with JSON response support for API calls\n- Various route fixes (profile.edit, toolbar settings link)"

This commit is contained in:
2026-03-03 20:57:43 +01:00
parent dc51d65440
commit b9c2d8597d
114 changed files with 8760 additions and 693 deletions

View File

@@ -6,6 +6,8 @@ use App\Http\Controllers\Controller;
use App\Models\Artwork;
use App\Services\ArtworkSearchService;
use App\Services\ArtworkService;
use App\Services\EarlyGrowth\FeedBlender;
use App\Services\EarlyGrowth\GridFiller;
use App\Services\Recommendation\RecommendationService;
use App\Services\ThumbnailPresenter;
use Illuminate\Http\Request;
@@ -27,17 +29,21 @@ use Illuminate\Support\Facades\Schema;
final class DiscoverController extends Controller
{
public function __construct(
private readonly ArtworkService $artworkService,
private readonly ArtworkSearchService $searchService,
private readonly ArtworkService $artworkService,
private readonly ArtworkSearchService $searchService,
private readonly RecommendationService $recoService,
private readonly FeedBlender $feedBlender,
private readonly GridFiller $gridFiller,
) {}
// ─── /discover/trending ──────────────────────────────────────────────────
public function trending(Request $request)
{
$perPage = 24;
$results = $this->searchService->discoverTrending($perPage);
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$results = $this->searchService->discoverTrending($perPage);
$results = $this->gridFiller->fill($results, 0, $page);
$this->hydrateDiscoverSearchResults($results);
return view('web.discover.index', [
@@ -53,8 +59,10 @@ final class DiscoverController extends Controller
public function rising(Request $request)
{
$perPage = 24;
$results = $this->searchService->discoverRising($perPage);
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$results = $this->searchService->discoverRising($perPage);
$results = $this->gridFiller->fill($results, 0, $page);
$this->hydrateDiscoverSearchResults($results);
return view('web.discover.index', [
@@ -70,8 +78,12 @@ final class DiscoverController extends Controller
public function fresh(Request $request)
{
$perPage = 24;
$results = $this->searchService->discoverFresh($perPage);
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$results = $this->searchService->discoverFresh($perPage);
// EGS: blend fresh feed with curated + spotlight on page 1
$results = $this->feedBlender->blend($results, $perPage, $page);
$results = $this->gridFiller->fill($results, 0, $page);
$this->hydrateDiscoverSearchResults($results);
return view('web.discover.index', [
@@ -87,8 +99,10 @@ final class DiscoverController extends Controller
public function topRated(Request $request)
{
$perPage = 24;
$results = $this->searchService->discoverTopRated($perPage);
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$results = $this->searchService->discoverTopRated($perPage);
$results = $this->gridFiller->fill($results, 0, $page);
$this->hydrateDiscoverSearchResults($results);
return view('web.discover.index', [
@@ -104,8 +118,10 @@ final class DiscoverController extends Controller
public function mostDownloaded(Request $request)
{
$perPage = 24;
$results = $this->searchService->discoverMostDownloaded($perPage);
$perPage = 24;
$page = max(1, (int) $request->query('page', 1));
$results = $this->searchService->discoverMostDownloaded($perPage);
$results = $this->gridFiller->fill($results, 0, $page);
$this->hydrateDiscoverSearchResults($results);
return view('web.discover.index', [
@@ -180,7 +196,8 @@ final class DiscoverController extends Controller
$creators = DB::table(DB::raw('(' . $sub->toSql() . ') as t'))
->mergeBindings($sub->getQuery())
->join('users as u', 'u.id', '=', 't.user_id')
->select('u.id as user_id', 'u.name as uname', 'u.username', 't.recent_views', 't.latest_published')
->leftJoin('user_profiles as up', 'up.user_id', '=', 't.user_id')
->select('u.id as user_id', 'u.name as uname', 'u.username', 't.recent_views', 't.latest_published', 'up.avatar_hash')
->orderByDesc('t.recent_views')
->orderByDesc('t.latest_published')
->paginate($perPage)
@@ -188,11 +205,12 @@ final class DiscoverController extends Controller
$creators->getCollection()->transform(function ($row) {
return (object) [
'user_id' => $row->user_id,
'uname' => $row->uname,
'username' => $row->username,
'total' => (int) $row->recent_views,
'metric' => 'views',
'user_id' => $row->user_id,
'uname' => $row->uname,
'username' => $row->username,
'total' => (int) $row->recent_views,
'metric' => 'views',
'avatar_hash' => $row->avatar_hash ?? null,
];
});