feat(channel): /@handle/{tab} aliases + cursor pagination on Videos tab
Two complementary additions that share the same bridge-not-rewrite
philosophy — reuse the existing single-page channel view instead of
splitting its 2000 lines into five route-driven partials.
/@handle/{tab}: videos / shorts / playlists / posts / about / wall
redirect to the equivalent /channel/{slug}#tab URL (302). The channel
page's existing hash-tab JS already handles landing on the right tab;
these aliases just make the URLs shareable in the YouTube style.
Channel Videos tab Load More: new JSON endpoint
GET /channel/{username}/videos/more returns the next cursor page of
server-rendered video cards. A sentinel + IntersectionObserver below
the grid fetches the next page when scrolled into view, with a Load
More button as fallback. Behavior mirrors the Phase 2 home feed —
users with thousands of uploads no longer get a 300-item grid on
first paint.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
f7c3b236ab
commit
677e8711dd
3 files changed
+99
-1
No files matched your search
@@ -188,6 +188,8 @@ Route::middleware(['auth', 'verified'])->group(function () {
|
||||
|
||||
// Channel - public for viewing. {username} is the user's slug (non-sequential, safe to share).
|
||||
Route::get('/channel/{username?}', [UserController::class, 'channel'])->name('channel');
|
||||
Route::get('/channel/{username}/videos/more', [UserController::class, 'channelVideosMore'])
|
||||
->name('channel.videos.more');
|
||||
// YouTube-style @handle alias. For now `handle` is just the existing
|
||||
// `username` slug — if the two ever diverge, flip the controller lookup
|
||||
// in UserController::channel() to prefer a dedicated `handle` column.
|
||||
@@ -197,6 +199,20 @@ Route::get('/@{handle}', [UserController::class, 'channel'])
|
||||
->where('handle', '[A-Za-z0-9][A-Za-z0-9._-]*')
|
||||
->name('channel.handle');
|
||||
|
||||
// Tab aliases: /@handle/videos lands on the Videos tab, /@handle/shorts on
|
||||
// Shorts, etc. These bridge to the single-page channel view by forwarding
|
||||
// the tab name as a hash — the channel.blade.php JS already handles
|
||||
// #wall/#videos/#shorts/#playlists/#about/#posts on load.
|
||||
Route::get('/@{handle}/{tab}', function (string $handle, string $tab) {
|
||||
$allowed = ['wall', 'videos', 'shorts', 'playlists', 'posts', 'about'];
|
||||
if (! in_array($tab, $allowed, true)) {
|
||||
abort(404);
|
||||
}
|
||||
$user = \App\Models\User::where('username', $handle)->firstOrFail();
|
||||
return redirect(route('channel', $user->channel) . '#' . $tab, 302);
|
||||
})->where(['handle' => '[A-Za-z0-9][A-Za-z0-9._-]*', 'tab' => '[a-z]+'])
|
||||
->name('channel.handle.tab');
|
||||
|
||||
// Post routes - auth required
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::post('/channel/{user}/posts', [PostController::class, 'store'])->name('posts.store');
|
||||
|
||||
Reference in new issue
Block a user