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'); Route::get('/top-rated', [DiscoverController::class, 'topRated'])->name('top-rated'); Route::get('/most-downloaded', [DiscoverController::class, 'mostDownloaded'])->name('most-downloaded'); Route::get('/on-this-day', [DiscoverController::class, 'onThisDay'])->name('on-this-day'); // Artworks from people you follow (auth required) Route::middleware('auth')->get('/following', [DiscoverController::class, 'following'])->name('following'); // Personalised "For You" feed (auth required; guests → redirect) 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 Route::get('/top', [\App\Http\Controllers\User\TopAuthorsController::class, 'index'])->name('top'); // Rising Creators → newest creators with recent uploads Route::get('/rising', [\App\Http\Controllers\Web\DiscoverController::class, 'risingCreators'])->name('rising'); }); // Creator Stories → canonical rename of /interviews Route::get('/stories', [\App\Http\Controllers\Community\InterviewController::class, 'index'])->name('stories'); // Tags listing page Route::get('/tags', [\App\Http\Controllers\Web\TagController::class, 'index'])->name('tags.index'); // Following redirect (convenience shortcut for authenticated users) Route::middleware('auth')->get('/following', function () { return redirect()->route('dashboard.following'); })->name('following.redirect'); // Legacy route set migrated from routes/legacy.php into this file. Route::get('/', [HomeController::class, 'index'])->name('legacy.home'); Route::get('/home', [HomeController::class, 'index']); Route::get('/art/{id}/{slug?}', [ArtworkPageController::class, 'show'])->where('id', '\\d+')->name('art.show'); Route::match(['get','post'], '/art/{id}/comment', [ArtController::class, 'show'])->where('id', '\\d+'); Route::get('/avatar/{id}/{name?}', [LegacyAvatarController::class, 'show'])->where('id', '\\d+')->name('legacy.avatar'); Route::middleware('ensure.onboarding.complete')->prefix('forum')->name('forum.')->group(function () { Route::get('/', [ForumController::class, 'index'])->name('index'); Route::get('/thread/{thread}-{slug?}', [ForumController::class, 'showThread'])->name('thread.show'); Route::get('/{category:slug}', [ForumController::class, 'showCategory'])->name('category.show'); Route::middleware('auth')->group(function () { Route::get('/{category:slug}/new', [ForumController::class, 'createThreadForm'])->name('thread.create'); Route::post('/{category:slug}/new', [ForumController::class, 'storeThread'])->name('thread.store'); Route::post('/thread/{thread}/reply', [ForumController::class, 'reply'])->name('thread.reply'); Route::post('/post/{post}/report', [ForumController::class, 'reportPost'])->name('post.report'); Route::get('/post/{post}/edit', [ForumController::class, 'editPostForm'])->name('post.edit'); Route::put('/post/{post}', [ForumController::class, 'updatePost'])->name('post.update'); }); Route::middleware(['auth', 'can:moderate-forum'])->group(function () { Route::post('/thread/{thread}/lock', [ForumController::class, 'lockThread'])->name('thread.lock'); Route::post('/thread/{thread}/unlock', [ForumController::class, 'unlockThread'])->name('thread.unlock'); Route::post('/thread/{thread}/pin', [ForumController::class, 'pinThread'])->name('thread.pin'); Route::post('/thread/{thread}/unpin', [ForumController::class, 'unpinThread'])->name('thread.unpin'); }); }); Route::middleware('ensure.onboarding.complete')->get('/forum.php', function (\Illuminate\Http\Request $request) { $threadId = (int) ($request->query('topic') ?? $request->query('tid') ?? 0); if ($threadId < 1) { return redirect()->route('forum.index', [], 301); } $thread = \App\Models\ForumThread::query()->find($threadId); $slug = $thread?->slug ?: ('thread-' . $threadId); return redirect()->route('forum.thread.show', ['thread' => $threadId, 'slug' => $slug], 301); })->name('forum.legacy.redirect'); // News/Announcements listing — redirect to forum index until a dedicated page exists Route::get('/news', function () { return redirect()->route('forum.index', [], 301); })->name('news.index'); Route::get('/news/{id}/{slug?}', [NewsController::class, 'show'])->where('id', '\\d+')->name('legacy.news.show'); Route::get('/categories', [CategoryController::class, 'index'])->name('legacy.categories'); Route::get('/sections', [\App\Http\Controllers\Web\SectionsController::class, 'index'])->name('sections'); // Clean SEO-friendly URL aliases Route::get('/uploads/latest', [LatestController::class, 'index'])->name('uploads.latest'); Route::get('/uploads/daily', [DailyUploadsController::class, 'index'])->name('uploads.daily'); Route::get('/members/photos', [MembersController::class, 'photos'])->name('members.photos'); Route::get('/authors/top', [TopAuthorsController::class, 'index'])->name('authors.top'); Route::get('/comments/latest', [LatestCommentsController::class, 'index'])->name('comments.latest'); Route::get('/comments/monthly', [MonthlyCommentatorsController::class, 'index'])->name('comments.monthly'); Route::get('/downloads/today', [TodayDownloadsController::class, 'index'])->name('downloads.today'); Route::get('/category/{group}/{slug?}/{id?}', [BrowseGalleryController::class, 'legacyCategory'])->name('legacy.category'); // §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'); Route::get('/chat', [ChatController::class, 'index'])->name('legacy.chat'); Route::post('/chat_post', [ChatController::class, 'post'])->name('legacy.chat.post'); Route::get('/browse-categories', [BrowseCategoriesController::class, 'index'])->name('browse.categories'); Route::get('/@{username}', [ProfileController::class, 'showByUsername']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.show'); Route::middleware('auth')->post('/@{username}/follow', [ProfileController::class, 'toggleFollow']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.follow'); Route::middleware('auth')->post('/@{username}/comment', [ProfileController::class, 'storeComment']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('profile.comment'); Route::get('/user/{username}', [ProfileController::class, 'legacyByUsername']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('legacy.user.profile'); Route::get('/profile/{id}/{username?}', [ProfileController::class, 'legacyById']) ->where('id', '\\d+') ->name('legacy.profile.id'); Route::get('/profile/{username}', [ProfileController::class, 'legacyByUsername']) ->where('username', '[A-Za-z0-9_-]{3,20}') ->name('legacy.profile'); Route::get('/top-favourites', [TopFavouritesController::class, 'index'])->name('legacy.top_favourites'); // /top-authors → 301 redirect to canonical /creators/top Route::get('/top-authors', function () { return redirect('/creators/top', 301); })->name('legacy.top_authors'); Route::middleware('auth')->get('/mybuddies.php', [\App\Http\Controllers\User\MyBuddiesController::class, 'index'])->name('legacy.mybuddies.php'); Route::middleware('auth')->get('/mybuddies', [\App\Http\Controllers\User\MyBuddiesController::class, 'index'])->name('legacy.mybuddies'); Route::middleware('auth')->delete('/mybuddies/{id}', [\App\Http\Controllers\User\MyBuddiesController::class, 'destroy'])->name('legacy.mybuddies.delete'); Route::middleware('auth')->get('/buddies.php', [\App\Http\Controllers\User\BuddiesController::class, 'index'])->name('legacy.buddies.php'); Route::middleware('auth')->get('/buddies', [\App\Http\Controllers\User\BuddiesController::class, 'index'])->name('legacy.buddies'); Route::get('/favourites/{id?}/{username?}', [FavouritesController::class, 'index'])->name('legacy.favourites'); Route::post('/favourites/{userId}/delete/{artworkId}', [FavouritesController::class, 'destroy'])->name('legacy.favourites.delete'); Route::middleware('ensure.onboarding.complete')->get('/gallery/{id}/{username?}', [GalleryController::class, 'show'])->name('legacy.gallery'); Route::middleware('auth')->get('/recieved-comments', [ReceivedCommentsController::class, 'index'])->name('legacy.received_comments'); // 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 () { 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'); Route::get('/monthly-commentators', [MonthlyCommentatorsController::class, 'index'])->name('legacy.monthly_commentators'); Route::get('/members', [MembersController::class, 'index'])->name('legacy.members'); Route::get('/latest', [LatestController::class, 'index'])->name('legacy.latest'); Route::get('/latest-comments', [LatestCommentsController::class, 'index'])->name('legacy.latest_comments'); // /interviews → 301 redirect to canonical /stories Route::get('/interviews', function () { return redirect('/stories', 301); })->name('legacy.interviews'); Route::get('/authors/top', [\App\Http\Controllers\User\TopAuthorsController::class, 'index'])->name('authors.top'); Route::middleware(['auth'])->group(function () { Route::get('/statistics', [StatisticsController::class, 'index'])->name('legacy.statistics'); }); Route::get('/dashboard', function () { return view('dashboard'); })->middleware(['auth', 'verified'])->name('dashboard'); 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'); // Awards received on the user's own artworks Route::get('/awards', [\App\Http\Controllers\Dashboard\DashboardAwardsController::class, 'index'])->name('awards'); }); // ── Studio Pro (Creator Artwork Manager) ──────────────────────────────────── use App\Http\Controllers\Studio\StudioController; Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('studio')->name('studio.')->group(function () { Route::get('/', [StudioController::class, 'index'])->name('index'); Route::get('/artworks', [StudioController::class, 'artworks'])->name('artworks'); Route::get('/artworks/drafts', [StudioController::class, 'drafts'])->name('drafts'); Route::get('/artworks/archived', [StudioController::class, 'archived'])->name('archived'); Route::get('/artworks/{id}/edit', [StudioController::class, 'edit'])->whereNumber('id')->name('artworks.edit'); Route::get('/artworks/{id}/analytics', [StudioController::class, 'analytics'])->whereNumber('id')->name('artworks.analytics'); Route::get('/analytics', [StudioController::class, 'analyticsOverview'])->name('analytics'); }); Route::middleware(['auth', 'normalize.username', 'ensure.onboarding.complete'])->group(function () { // 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'); // 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) Route::match(['post', 'put'], '/profile/password', [ProfileController::class, 'password'])->name('profile.password'); // Avatar upload (backend only) - processes and stores avatars Route::post('/avatar/upload', [AvatarController::class, 'upload'])->middleware('throttle:20,1')->name('avatar.upload'); }); Route::middleware(['auth', 'ensure.onboarding.complete'])->group(function () { Route::get('/upload', function () { $contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) { return [ 'id' => $ct->id, 'name' => $ct->name, 'categories' => $ct->rootCategories->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'children' => $c->children->map(function ($ch) { return ['id' => $ch->id, 'name' => $ch->name]; })->values()->all(), ]; })->values()->all(), ]; })->values()->all(); return Inertia::render('Upload/Index', [ 'draftId' => null, 'content_types' => $contentTypes, 'suggested_tags' => [], 'filesCdnUrl' => config('cdn.files_url'), 'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880), 'feature_flags' => [ 'uploads_v2' => (bool) config('features.uploads_v2', false), ], ]); })->name('upload'); Route::get('/upload/draft/{id}', function (string $id) { $contentTypes = ContentType::with(['rootCategories.children'])->get()->map(function ($ct) { return [ 'id' => $ct->id, 'name' => $ct->name, 'categories' => $ct->rootCategories->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'children' => $c->children->map(function ($ch) { return ['id' => $ch->id, 'name' => $ch->name]; })->values()->all(), ]; })->values()->all(), ]; })->values()->all(); return Inertia::render('Upload/Index', [ 'draftId' => $id, 'content_types' => $contentTypes, 'suggested_tags' => [], 'filesCdnUrl' => config('cdn.files_url'), 'chunkSize' => (int) config('uploads.chunk.max_bytes', 5242880), 'feature_flags' => [ 'uploads_v2' => (bool) config('features.uploads_v2', false), ], ]); })->whereUuid('id')->name('upload.draft'); }); require __DIR__.'/auth.php'; Route::get('/search', [\App\Http\Controllers\Web\SearchController::class, 'index']) ->name('search'); Route::get('/tag/{tag:slug}', [\App\Http\Controllers\Web\TagController::class, 'show']) ->where('tag', '[a-z0-9\-]+') ->name('tags.show'); 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) { // 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. // Keep the explicit /photography route above (if present) so the legacy controller can continue // to serve photography's root page. This catch-all route delegates to a controller that // will forward to the appropriate existing controller (artwork or category handlers). // Provide a named route alias for legacy artwork URL generation used in tests. Route::get('/{contentTypeSlug}/{categoryPath}/{artwork}', [\App\Http\Controllers\Web\BrowseGalleryController::class, 'showArtwork']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('categoryPath', '[^/]+(?:/[^/]+)*') ->name('artworks.show'); Route::get('/{contentTypeSlug}/{path?}', [\App\Http\Controllers\Web\BrowseGalleryController::class, 'content']) ->where('contentTypeSlug', 'photography|wallpapers|skins|other') ->where('path', '.*') ->name('content.route'); Route::middleware(['auth'])->group(function () { Route::get('/manage', [ManageController::class, 'index'])->name('manage'); Route::get('/manage/edit/{id}', [ManageController::class, 'edit'])->name('manage.edit'); Route::post('/manage/update/{id}', [ManageController::class, 'update'])->name('manage.update'); Route::post('/manage/delete/{id}', [ManageController::class, 'destroy'])->name('manage.destroy'); }); // Admin routes for artworks (separated from public routes) Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () { Route::get('uploads/moderation', function () { return Inertia::render('Admin/UploadQueue'); })->middleware('admin.moderation')->name('uploads.moderation'); Route::get('usernames/moderation', function () { return Inertia::render('Admin/UsernameQueue'); })->middleware('admin.moderation')->name('usernames.moderation'); Route::resource('artworks', \App\Http\Controllers\Admin\ArtworkController::class)->except(['show']); 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']) ->get('/messages/attachments/{id}', [\App\Http\Controllers\Api\Messaging\AttachmentController::class, 'show']) ->whereNumber('id') ->name('messages.attachments.show'); // ── Messages ────────────────────────────────────────────────────────────────── Route::middleware(['auth', 'ensure.onboarding.complete'])->prefix('messages')->name('messages.')->group(function () { Route::get('/', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'index'])->name('index'); Route::get('/{id}', [\App\Http\Controllers\Messaging\MessagesPageController::class, 'show'])->whereNumber('id')->name('show'); }); // ── Community Activity Feed ─────────────────────────────────────────────────── Route::get('/community/activity', [\App\Http\Controllers\Web\CommunityActivityController::class, 'index']) ->name('community.activity'); // ── Posts / Following Feed ──────────────────────────────────────────────────── // /feed/following – Inertia page for the ranked, diversified following feed Route::middleware(['auth', 'ensure.onboarding.complete']) ->get('/feed/following', [\App\Http\Controllers\Web\Posts\FollowingFeedController::class, 'index']) ->name('feed.following'); // ── Feed 2.0: Trending Feed ─────────────────────────────────────────────────── Route::get('/feed/trending', [\App\Http\Controllers\Web\Posts\TrendingFeedController::class, 'index']) ->name('feed.trending'); // ── Feed 2.0: Hashtag Feed ──────────────────────────────────────────────────── Route::get('/tags/{tag}', [\App\Http\Controllers\Web\Posts\HashtagFeedController::class, 'index']) ->where('tag', '[A-Za-z][A-Za-z0-9_]{1,63}') ->name('feed.hashtag'); // ── Feed 2.0: Saved Posts ───────────────────────────────────────────────────── Route::middleware(['auth']) ->get('/feed/saved', [\App\Http\Controllers\Web\Posts\SavedFeedController::class, 'index']) ->name('feed.saved'); // ── 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');