Fix dashboard route overwritten by home redirect when using tenant slug attribute (#19584)

* Fix dashboard route overwritten by home redirect when using tenant slug attribute

`Route::getLastGroupPrefix()` returns the raw group prefix including
binding syntax (e.g. `admin/{tenant:slug}`), but Laravel's
`RouteCollection` indexes routes by the normalized URI without binding
syntax (e.g. `admin/{tenant}`). The `isset()` check on line 182 fails
due to this key mismatch when a tenant slug attribute is configured,
causing the home redirect to be created at `/` and overwrite the
Dashboard route.

The fix strips binding syntax from the root URI before looking it up
in the route collection.

Fixes filamentphp/filament#19582

* update snapshots

---------

Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
Chris Jones
2026-03-30 05:12:39 -04:00
committed by GitHub
parent 1d78f0123e
commit 7e4b8a5b0e
10 changed files with 84 additions and 1 deletions
+1 -1
View File
@@ -175,7 +175,7 @@ Route::name('filament.')
Filament::setCurrentResourceConfigurationKey(null);
}
$rootUri = trim(Route::getLastGroupPrefix(), '/') ?: '/';
$rootUri = preg_replace('/\{(\w+):\w+\}/', '{$1}', trim(Route::getLastGroupPrefix(), '/')) ?: '/';
$groupStack = Route::getGroupStack();
$rootKey = (end($groupStack)['domain'] ?? '') . $rootUri;
@@ -26,6 +26,7 @@ class ManageUserTeams extends ManageRelatedRecords
->components([
Forms\Components\TextInput::make('name')
->required(),
Forms\Components\TextInput::make('slug'),
Forms\Components\Textarea::make('description')
->columnSpanFull(),
Forms\Components\TextInput::make('budget')
@@ -44,6 +45,8 @@ class ManageUserTeams extends ManageRelatedRecords
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('slug')
->searchable(),
Tables\Columns\TextColumn::make('budget')
->numeric()
->sortable(),
@@ -19,6 +19,7 @@ class TeamsRelationManager extends RelationManager
->components([
Forms\Components\TextInput::make('name')
->required(),
Forms\Components\TextInput::make('slug'),
Forms\Components\Textarea::make('description')
->columnSpanFull(),
Forms\Components\TextInput::make('budget')
@@ -37,6 +38,8 @@ class TeamsRelationManager extends RelationManager
->columns([
Tables\Columns\TextColumn::make('name')
->searchable(),
Tables\Columns\TextColumn::make('slug')
->searchable(),
Tables\Columns\TextColumn::make('budget')
->numeric()
->sortable(),
@@ -35,6 +35,7 @@ class ManageUserTeams extends ManageRelatedRecords
->components([
TextInput::make('name')
->required(),
TextInput::make('slug'),
Textarea::make('description')
->columnSpanFull(),
TextInput::make('budget')
@@ -53,6 +54,8 @@ class ManageUserTeams extends ManageRelatedRecords
->columns([
TextColumn::make('name')
->searchable(),
TextColumn::make('slug')
->searchable(),
TextColumn::make('budget')
->numeric()
->sortable(),
@@ -28,6 +28,7 @@ class TeamsRelationManager extends RelationManager
->components([
TextInput::make('name')
->required(),
TextInput::make('slug'),
Textarea::make('description')
->columnSpanFull(),
TextInput::make('budget')
@@ -46,6 +47,8 @@ class TeamsRelationManager extends RelationManager
->columns([
TextColumn::make('name')
->searchable(),
TextColumn::make('slug')
->searchable(),
TextColumn::make('budget')
->numeric()
->sortable(),
+1
View File
@@ -13,6 +13,7 @@ class TeamFactory extends Factory
{
return [
'name' => $this->faker->company(),
'slug' => $this->faker->unique()->slug(),
'description' => $this->faker->sentence(),
];
}
@@ -11,6 +11,7 @@ return new class extends Migration
Schema::create('teams', function (Blueprint $table): void {
$table->id();
$table->string('name');
$table->string('slug')->unique()->nullable();
$table->text('description')->nullable();
$table->decimal('budget', 10, 2)->default(0);
$table->foreignId('company_id')->nullable();
@@ -0,0 +1,50 @@
<?php
namespace Filament\Tests\Fixtures\Providers;
use Filament\Http\Middleware\Authenticate;
use Filament\Http\Middleware\AuthenticateSession;
use Filament\Http\Middleware\DisableBladeIconComponents;
use Filament\Http\Middleware\DispatchServingFilamentEvent;
use Filament\Http\Middleware\IdentifyTenant;
use Filament\Pages;
use Filament\Panel;
use Filament\PanelProvider;
use Filament\Tests\Fixtures\Models\Team;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Session\Middleware\StartSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
class SlugTenancyPanelProvider extends PanelProvider
{
public function panel(Panel $panel): Panel
{
return $panel
->id('slug-tenancy')
->path('slug-tenancy')
->tenant(Team::class, 'slug')
->login()
->pages([
Pages\Dashboard::class,
])
->middleware([
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartSession::class,
AuthenticateSession::class,
ShareErrorsFromSession::class,
class_exists(PreventRequestForgery::class) ? PreventRequestForgery::class : VerifyCsrfToken::class,
SubstituteBindings::class,
DisableBladeIconComponents::class,
DispatchServingFilamentEvent::class,
IdentifyTenant::class,
])
->authMiddleware([
Authenticate::class,
]);
}
}
@@ -0,0 +1,17 @@
<?php
use Filament\Tests\Panels\Pages\TestCase;
use Illuminate\Support\Facades\Route;
uses(TestCase::class);
it('registers the dashboard route when using a tenant slug attribute', function (): void {
$route = Route::getRoutes()->getByName('filament.slug-tenancy.pages.dashboard');
expect($route)->not->toBeNull()
->and($route->uri())->toBe('slug-tenancy/{tenant}');
});
it('does not register the home redirect when the dashboard occupies the root path with a tenant slug attribute', function (): void {
expect(Route::getRoutes()->getByName('filament.slug-tenancy.home'))->toBeNull();
});
+2
View File
@@ -29,6 +29,7 @@ use Filament\Tests\Fixtures\Providers\Fixtures\Providers\SingleDomainPanel;
use Filament\Tests\Fixtures\Providers\MultiDomainPanel;
use Filament\Tests\Fixtures\Providers\RequiredMultiFactorAuthenticationPanelProvider;
use Filament\Tests\Fixtures\Providers\SlugsPanelProvider;
use Filament\Tests\Fixtures\Providers\SlugTenancyPanelProvider;
use Filament\Tests\Fixtures\Providers\TenancyPanelProvider;
use Filament\Widgets\WidgetsServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -72,6 +73,7 @@ abstract class TestCase extends BaseTestCase
MultiDomainPanel::class,
SingleDomainPanel::class,
SlugsPanelProvider::class,
SlugTenancyPanelProvider::class,
TenancyPanelProvider::class,
PowerJoinsServiceProvider::class,
];