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:
108
routes/web.php
108
routes/web.php
@@ -33,10 +33,21 @@ use App\Http\Controllers\Web\BrowseCategoriesController;
|
||||
use App\Http\Controllers\Web\GalleryController;
|
||||
use App\Http\Controllers\Web\BrowseGalleryController;
|
||||
use App\Http\Controllers\Web\DiscoverController;
|
||||
use App\Http\Controllers\Web\ExploreController;
|
||||
use App\Http\Controllers\Web\BlogController;
|
||||
use App\Http\Controllers\Web\PageController;
|
||||
use App\Http\Controllers\Web\FooterController;
|
||||
use App\Http\Controllers\Web\StaffController;
|
||||
use App\Http\Controllers\Web\RssFeedController;
|
||||
use App\Http\Controllers\Web\ApplicationController;
|
||||
use App\Http\Controllers\Web\StaffApplicationAdminController;
|
||||
use Inertia\Inertia;
|
||||
|
||||
// ── DISCOVER routes (/discover/*) ─────────────────────────────────────────────
|
||||
// ── DISCOVER routes (/discover/*) — DiscoverLayout ────────────────────────────
|
||||
Route::prefix('discover')->name('discover.')->group(function () {
|
||||
// /discover → redirect to /discover/trending (§6.2 canonical)
|
||||
Route::get('/', fn () => redirect('/discover/trending', 301));
|
||||
|
||||
Route::get('/trending', [DiscoverController::class, 'trending'])->name('trending');
|
||||
Route::get('/rising', [DiscoverController::class, 'rising'])->name('rising');
|
||||
Route::get('/fresh', [DiscoverController::class, 'fresh'])->name('fresh');
|
||||
@@ -51,6 +62,73 @@ Route::prefix('discover')->name('discover.')->group(function () {
|
||||
Route::middleware('auth')->get('/for-you', [DiscoverController::class, 'forYou'])->name('for-you');
|
||||
});
|
||||
|
||||
// ── EXPLORE routes (/explore/*) — ExploreLayout ───────────────────────────────
|
||||
Route::prefix('explore')->name('explore.')->group(function () {
|
||||
Route::get('/', [ExploreController::class, 'index'])->name('index');
|
||||
Route::get('/{type}', [ExploreController::class, 'byType'])
|
||||
->where('type', 'artworks|wallpapers|skins|photography|other')
|
||||
->name('type');
|
||||
Route::get('/{type}/{mode}', [ExploreController::class, 'byTypeMode'])
|
||||
->where('type', 'artworks|wallpapers|skins|photography|other')
|
||||
->where('mode', 'trending|new-hot|best|latest')
|
||||
->name('type.mode');
|
||||
});
|
||||
|
||||
// ── BLOG routes (/blog/*) — ContentLayout ─────────────────────────────────────
|
||||
Route::prefix('blog')->name('blog.')->group(function () {
|
||||
Route::get('/', [BlogController::class, 'index'])->name('index');
|
||||
Route::get('/{slug}', [BlogController::class, 'show'])->where('slug', '[a-z0-9\-]+')
|
||||
->name('show');
|
||||
});
|
||||
|
||||
// ── PAGES (DB-driven static pages) — ContentLayout ────────────────────────────
|
||||
Route::get('/pages/{slug}', [PageController::class, 'show'])->where('slug', '[a-z0-9\-]+')
|
||||
->name('pages.show');
|
||||
|
||||
// Root-level marketing pages (About, Help, Contact)
|
||||
Route::get('/about', [PageController::class, 'marketing'])->defaults('slug', 'about')->name('about');
|
||||
Route::get('/help', [PageController::class, 'marketing'])->defaults('slug', 'help')->name('help');
|
||||
Route::get('/contact', [PageController::class, 'marketing'])->defaults('slug', 'contact')->name('contact');
|
||||
|
||||
// Legal pages
|
||||
Route::get('/legal/{section}', [PageController::class, 'legal'])
|
||||
->where('section', 'terms|privacy|cookies')
|
||||
->name('legal');
|
||||
|
||||
// ── FOOTER pages ─────────────────────────────────────────────────────────────
|
||||
// Legacy /bug-report now redirects to the universal contact form
|
||||
Route::match(['get','post'], '/bug-report', fn() => redirect('/contact', 301))->name('bug-report.redirect');
|
||||
Route::get('/rss-feeds', [RssFeedController::class, 'index'])->name('rss-feeds');
|
||||
Route::get('/faq', [FooterController::class, 'faq'])->name('faq');
|
||||
Route::get('/rules-and-guidelines', [FooterController::class, 'rules'])->name('rules');
|
||||
Route::get('/privacy-policy', [FooterController::class, 'privacyPolicy'])->name('privacy-policy');
|
||||
Route::get('/terms-of-service', [FooterController::class, 'termsOfService'])->name('terms-of-service');
|
||||
Route::get('/staff', [StaffController::class, 'index'])->name('staff');
|
||||
// Contact form (formerly /apply)
|
||||
Route::get('/contact', [ApplicationController::class, 'show'])->name('contact.show');
|
||||
Route::post('/contact', [ApplicationController::class, 'submit'])->middleware('throttle:6,1')->name('contact.submit');
|
||||
|
||||
// Backwards-compatibility: redirect old /apply to /contact
|
||||
Route::get('/apply', fn() => redirect('/contact', 301))->name('legacy.apply.redirect');
|
||||
|
||||
// Admin: staff application submissions
|
||||
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
|
||||
Route::get('/applications', [StaffApplicationAdminController::class, 'index'])->name('applications.index');
|
||||
Route::get('/applications/{staffApplication}', [StaffApplicationAdminController::class, 'show'])->name('applications.show');
|
||||
});
|
||||
|
||||
// RSS XML feeds
|
||||
Route::get('/rss/latest-uploads.xml', [RssFeedController::class, 'latestUploads'])->name('rss.uploads');
|
||||
Route::get('/rss/latest-skins.xml', [RssFeedController::class, 'latestSkins'])->name('rss.skins');
|
||||
Route::get('/rss/latest-wallpapers.xml', [RssFeedController::class, 'latestWallpapers'])->name('rss.wallpapers');
|
||||
Route::get('/rss/latest-photos.xml', [RssFeedController::class, 'latestPhotos'])->name('rss.photos');
|
||||
|
||||
// ── 301 REDIRECTS: Legacy → Canonical URLs ────────────────────────────────────
|
||||
// §6.1 — /browse → /explore
|
||||
Route::get('/browse-redirect', fn () => redirect('/explore', 301))->name('legacy.browse.redirect');
|
||||
// §6.1 — /wallpapers as standalone → /explore/wallpapers
|
||||
Route::get('/wallpapers-redirect', fn () => redirect('/explore/wallpapers', 301))->name('legacy.wallpapers.redirect');
|
||||
|
||||
// ── CREATORS routes (/creators/*) ─────────────────────────────────────────────
|
||||
Route::prefix('creators')->name('creators.')->group(function () {
|
||||
// Top Creators → reuse existing top-authors controller
|
||||
@@ -134,7 +212,8 @@ Route::get('/downloads/today', [TodayDownloadsController::class, 'index'])->nam
|
||||
|
||||
Route::get('/category/{group}/{slug?}/{id?}', [BrowseGalleryController::class, 'legacyCategory'])->name('legacy.category');
|
||||
|
||||
Route::get('/browse', [BrowseGalleryController::class, 'browse'])->name('legacy.browse');
|
||||
// §6.1 — /browse → 301 to /explore (canonical)
|
||||
Route::get('/browse', fn () => redirect('/explore', 301))->name('legacy.browse');
|
||||
Route::get('/featured', [FeaturedArtworksController::class, 'index'])->name('legacy.featured');
|
||||
Route::get('/featured-artworks', [FeaturedArtworksController::class, 'index'])->name('legacy.featured_artworks');
|
||||
Route::get('/daily-uploads', [DailyUploadsController::class, 'index'])->name('legacy.daily_uploads');
|
||||
@@ -187,10 +266,8 @@ Route::middleware('ensure.onboarding.complete')->get('/gallery/{id}/{username?}'
|
||||
|
||||
Route::middleware('auth')->get('/recieved-comments', [ReceivedCommentsController::class, 'index'])->name('legacy.received_comments');
|
||||
|
||||
// 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');
|
||||
// Canonical dashboard profile route: Inertia-powered Settings page.
|
||||
Route::middleware(['auth'])->get('/dashboard/profile', [ProfileController::class, 'editSettings'])->name('dashboard.profile');
|
||||
|
||||
// Keep legacy `/user` as a permanent redirect to the canonical dashboard path.
|
||||
Route::middleware(['auth'])->match(['get','post'], '/user', function () {
|
||||
@@ -256,6 +333,8 @@ Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])-
|
||||
})->name('legacy.profile.redirect');
|
||||
// Backwards-compatible settings path used by some layouts/links
|
||||
Route::get('/settings', [ProfileController::class, 'edit'])->name('settings');
|
||||
// Backwards-compatible route name expected by some packages/views
|
||||
Route::get('/profile/edit', [ProfileController::class, 'edit'])->name('profile.edit');
|
||||
Route::match(['post','put','patch'], '/profile', [ProfileController::class, 'update'])->name('profile.update');
|
||||
Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy');
|
||||
// Password change endpoint (accepts POST or PUT from legacy and new forms)
|
||||
@@ -340,7 +419,9 @@ Route::view('/blank', 'blank')->name('blank');
|
||||
// Bind the artwork route parameter to a model if it exists, otherwise return null
|
||||
use App\Models\Artwork;
|
||||
Route::bind('artwork', function ($value) {
|
||||
return Artwork::where('slug', $value)->first();
|
||||
// firstOrFail triggers ModelNotFoundException → caught by exception handler
|
||||
// which renders contextual artwork-not-found or generic 404 view.
|
||||
return Artwork::where('slug', $value)->firstOrFail();
|
||||
});
|
||||
|
||||
// Universal content router: handles content-type roots, nested categories and artwork slugs.
|
||||
@@ -382,6 +463,13 @@ Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function ()
|
||||
Route::get('reports', function () {
|
||||
return view('admin.reports.queue');
|
||||
})->middleware('admin.moderation')->name('reports.queue');
|
||||
|
||||
// ── Early Growth System admin panel (§14) ─────────────────────────────
|
||||
Route::middleware('admin.moderation')->prefix('early-growth')->name('early-growth.')->group(function () {
|
||||
Route::get('/', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'index'])->name('index');
|
||||
Route::delete('/cache', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'flushCache'])->name('cache.flush');
|
||||
Route::get('/status', [\App\Http\Controllers\Admin\EarlyGrowthAdminController::class, 'status'])->name('status');
|
||||
});
|
||||
});
|
||||
|
||||
Route::middleware(['auth', 'ensure.onboarding.complete'])
|
||||
@@ -422,3 +510,9 @@ Route::middleware(['auth'])
|
||||
// ── Feed 2.0: Post Search ─────────────────────────────────────────────────────
|
||||
Route::get('/feed/search', [\App\Http\Controllers\Web\Posts\SearchFeedController::class, 'index'])
|
||||
->name('feed.search');
|
||||
|
||||
// ── Fallback: Generic 404 ─────────────────────────────────────────────────────
|
||||
// Must be last. Catches any URL not matched by a registered route.
|
||||
Route::fallback(function (\Illuminate\Http\Request $request) {
|
||||
return app(\App\Http\Controllers\Web\ErrorController::class)->handleNotFound($request);
|
||||
})->name('404.fallback');
|
||||
|
||||
Reference in New Issue
Block a user