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

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Services\EarlyGrowth\ActivityLayer;
use App\Services\EarlyGrowth\AdaptiveTimeWindow;
use App\Services\EarlyGrowth\EarlyGrowth;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\View\View;
/**
* EarlyGrowthAdminController (§14)
*
* Admin panel for the Early-Stage Growth System.
* All toggles are ENV-driven; updating .env requires a deploy.
* This panel provides a read-only status view plus a cache-flush action.
*
* Future v2: wire to a `settings` DB table so admins can toggle without
* a deploy. The EarlyGrowth::enabled() contract already supports this.
*/
final class EarlyGrowthAdminController extends Controller
{
public function __construct(
private readonly AdaptiveTimeWindow $timeWindow,
private readonly ActivityLayer $activityLayer,
) {}
/**
* GET /admin/early-growth
* Status dashboard: shows current config, live stats, toggle instructions.
*/
public function index(): View
{
$uploadsPerDay = $this->timeWindow->getUploadsPerDay();
return view('admin.early-growth.index', [
'status' => EarlyGrowth::status(),
'mode' => EarlyGrowth::mode(),
'uploads_per_day' => $uploadsPerDay,
'window_days' => $this->timeWindow->getTrendingWindowDays(30),
'activity' => $this->activityLayer->getSignals(),
'cache_keys' => [
'egs.uploads_per_day',
'egs.auto_disable_check',
'egs.spotlight.*',
'egs.curated.*',
'egs.grid_filler.*',
'egs.activity_signals',
'homepage.fresh.*',
'discover.trending.*',
'discover.rising.*',
],
'env_toggles' => [
['key' => 'NOVA_EARLY_GROWTH_ENABLED', 'current' => env('NOVA_EARLY_GROWTH_ENABLED', 'false')],
['key' => 'NOVA_EARLY_GROWTH_MODE', 'current' => env('NOVA_EARLY_GROWTH_MODE', 'off')],
['key' => 'NOVA_EGS_ADAPTIVE_WINDOW', 'current' => env('NOVA_EGS_ADAPTIVE_WINDOW', 'true')],
['key' => 'NOVA_EGS_GRID_FILLER', 'current' => env('NOVA_EGS_GRID_FILLER', 'true')],
['key' => 'NOVA_EGS_SPOTLIGHT', 'current' => env('NOVA_EGS_SPOTLIGHT', 'true')],
['key' => 'NOVA_EGS_ACTIVITY_LAYER', 'current' => env('NOVA_EGS_ACTIVITY_LAYER', 'false')],
],
]);
}
/**
* DELETE /admin/early-growth/cache
* Flush all EGS-related cache keys so new config changes take effect immediately.
*/
public function flushCache(Request $request): RedirectResponse
{
$keys = [
'egs.uploads_per_day',
'egs.auto_disable_check',
'egs.activity_signals',
];
// Flush the EGS daily spotlight caches for today
$today = now()->format('Y-m-d');
foreach ([6, 12, 18, 24] as $n) {
Cache::forget("egs.spotlight.{$today}.{$n}");
Cache::forget("egs.curated.{$today}.{$n}.7");
}
// Flush fresh/trending homepage sections
foreach ([6, 8, 10, 12] as $limit) {
foreach (['off', 'light', 'aggressive'] as $mode) {
Cache::forget("homepage.fresh.{$limit}.egs-{$mode}");
Cache::forget("homepage.fresh.{$limit}.std");
}
Cache::forget("homepage.trending.{$limit}");
Cache::forget("homepage.rising.{$limit}");
}
// Flush key keys
foreach ($keys as $key) {
Cache::forget($key);
}
return redirect()->route('admin.early-growth.index')
->with('success', 'Early Growth System cache flushed. Changes will take effect on next page load.');
}
/**
* GET /admin/early-growth/status (JSON for monitoring/healthcheck)
*/
public function status(): JsonResponse
{
return response()->json([
'egs' => EarlyGrowth::status(),
'uploads_per_day' => $this->timeWindow->getUploadsPerDay(),
'window_days' => $this->timeWindow->getTrendingWindowDays(30),
]);
}
}