From bf5531bb1189949aafa1369820c76e05030071b8 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Mon, 22 Jun 2026 16:11:39 +0200 Subject: [PATCH 01/13] broadcaster placeholder instance stuff --- app/Models/Broadcaster/Broadcaster.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/Models/Broadcaster/Broadcaster.php b/app/Models/Broadcaster/Broadcaster.php index d2416ca2..9b89d017 100644 --- a/app/Models/Broadcaster/Broadcaster.php +++ b/app/Models/Broadcaster/Broadcaster.php @@ -54,6 +54,28 @@ class Broadcaster extends Model implements HasAvatar, HasCurrentTenantLabel, Has use Reportable; use SoftDeletes; + /** + * Creates a placeholder instance for the given user id we can use as a proxy + * + * Will not be stored in database unless you explicitly do so with something like `->save()` + */ + public static function placeholder(?int $userId): self + { + return new self([ + 'id' => $userId, + 'consent' => [], + 'twitch_mod_permissions' => [], + 'submit_user_allowed' => false, + 'submit_mods_allowed' => false, + 'submit_vip_allowed' => false, + 'onboarded_at' => null, + 'created_at' => now(), + 'updated_at' => now(), + 'deleted_at' => null, + 'default_clip_status' => ClipStatus::Unknown, + ]); + } + public static function getFilamentInfolistEntry(string $name): FilamentSchemaComponent { return User::getFilamentInfolistEntry($name); From 6d8a5c18a6f8f49cd2c946571886d8560e959edf Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Mon, 22 Jun 2026 16:33:26 +0200 Subject: [PATCH 02/13] resolve to placeholder broadcaster if we access our own (missing) broadcaster dashboard --- app/Providers/Filament/DashboardPanelProvider.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/Providers/Filament/DashboardPanelProvider.php b/app/Providers/Filament/DashboardPanelProvider.php index 7a52acdb..834faaaa 100644 --- a/app/Providers/Filament/DashboardPanelProvider.php +++ b/app/Providers/Filament/DashboardPanelProvider.php @@ -29,6 +29,7 @@ use Illuminate\Contracts\View\View; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; +use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Http\Middleware\PreventRequestForgery; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\StartSession; @@ -104,6 +105,16 @@ public function panel(Panel $panel): Panel DispatchServingFilamentEvent::class, RequiresBroadcasterProfile::class, ]) + ->resolveTenantUsing(function (string $key) { + $tenant = app(Filament::getTenantModel()) + ->resolveRouteBinding($key); + + if (! $tenant instanceof Model && $key === (string) auth()->id()) { + return Broadcaster::placeholder(auth()->id()); + } + + return $tenant; + }) ->renderHook( PanelsRenderHook::PAGE_START, fn (): ?HtmlString => $this->renderBanNotice(), From 9b33e930d5b93668e585ea01368214e47b1d46f0 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Mon, 22 Jun 2026 16:34:31 +0200 Subject: [PATCH 03/13] use placeholder broadcaster for tenant switcher if we dont have onboarded yet --- app/Models/Traits/User/UserFilamentConfiguration.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/Models/Traits/User/UserFilamentConfiguration.php b/app/Models/Traits/User/UserFilamentConfiguration.php index 80bd7f95..8e3719c1 100644 --- a/app/Models/Traits/User/UserFilamentConfiguration.php +++ b/app/Models/Traits/User/UserFilamentConfiguration.php @@ -145,10 +145,6 @@ public function getTenants(Panel $panel): array|Collection } $broadcasterIds = $this->broadcasterTeamMembers()->pluck('broadcaster_id'); - if ($this->broadcaster) { - $broadcasterIds->add($this->broadcaster->id); - } - $twitchService = app(TwitchService::class); $twitchModChannelsIds = $twitchService @@ -162,7 +158,10 @@ public function getTenants(Panel $panel): array|Collection $broadcasterIds = $broadcasterIds->merge($allowedBroadcasters); - return Broadcaster::findMany($broadcasterIds->unique()); + return [ + $this->broadcaster ?? Broadcaster::placeholder($this->id), + ...Broadcaster::findMany($broadcasterIds->unique()), + ]; } public function getDefaultTenant(Panel $panel): ?Model From a3abe330201c485ec1e4bc022b5d77591fd0d40f Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Mon, 22 Jun 2026 16:45:45 +0200 Subject: [PATCH 04/13] forgot that we need to throw an exception for invalid tenant oops --- app/Providers/Filament/DashboardPanelProvider.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/Providers/Filament/DashboardPanelProvider.php b/app/Providers/Filament/DashboardPanelProvider.php index 834faaaa..531538d4 100644 --- a/app/Providers/Filament/DashboardPanelProvider.php +++ b/app/Providers/Filament/DashboardPanelProvider.php @@ -30,6 +30,7 @@ use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Http\Middleware\PreventRequestForgery; use Illuminate\Routing\Middleware\SubstituteBindings; use Illuminate\Session\Middleware\StartSession; @@ -113,6 +114,10 @@ public function panel(Panel $panel): Panel return Broadcaster::placeholder(auth()->id()); } + if (! $tenant instanceof Model) { + throw new ModelNotFoundException()->setModel(Filament::getTenantModel(), [$key]); + } + return $tenant; }) ->renderHook( From 1e1f74c45d707a1a3c61dfc3419855a94a071162 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Mon, 22 Jun 2026 20:59:49 +0200 Subject: [PATCH 05/13] return type for resolveTenantUsing closure --- app/Providers/Filament/DashboardPanelProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Providers/Filament/DashboardPanelProvider.php b/app/Providers/Filament/DashboardPanelProvider.php index 531538d4..61177ef8 100644 --- a/app/Providers/Filament/DashboardPanelProvider.php +++ b/app/Providers/Filament/DashboardPanelProvider.php @@ -106,7 +106,7 @@ public function panel(Panel $panel): Panel DispatchServingFilamentEvent::class, RequiresBroadcasterProfile::class, ]) - ->resolveTenantUsing(function (string $key) { + ->resolveTenantUsing(function (string $key): Model { $tenant = app(Filament::getTenantModel()) ->resolveRouteBinding($key); From 0664d31f3f0561d3fa481055b61584678665a231 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Wed, 24 Jun 2026 19:26:01 +0200 Subject: [PATCH 06/13] filament based onboarding page --- app/Filament/Dashboard/Pages/Onboarding.php | 195 ++++++++++++++++++ .../dashboard/pages/onboarding-page.blade.php | 3 + 2 files changed, 198 insertions(+) create mode 100644 app/Filament/Dashboard/Pages/Onboarding.php create mode 100644 resources/views/filament/dashboard/pages/onboarding-page.blade.php diff --git a/app/Filament/Dashboard/Pages/Onboarding.php b/app/Filament/Dashboard/Pages/Onboarding.php new file mode 100644 index 00000000..81ca2f0d --- /dev/null +++ b/app/Filament/Dashboard/Pages/Onboarding.php @@ -0,0 +1,195 @@ +getKey() === auth()->id(); + } + + public function getHeading(): string|Htmlable|null + { + return __('onboarding.heading', ['username' => auth()->user()->name]); + } + + public function getSubheading(): string|Htmlable|null + { + return __('onboarding.setup.heading'); + } + + public function mount(): void + { + $broadcaster = $this->getBroadcaster(); + + if ($broadcaster->exists && $broadcaster->onboarded_at !== null) { + $this->redirect(Dashboard::getUrl(panel: 'dashboard', tenant: $broadcaster)); + } + + $this->getSchema('form')->fill([ + 'submit_user_allowed' => true, + 'submit_mods_allowed' => true, + 'default_clip_status' => ClipStatus::NeedApproval, + 'consent' => [], + ]); + } + + public function content(Schema $schema): Schema + { + return $schema->components([ + Form::make([EmbeddedSchema::make('form')]) + ->footer([ + Actions::make([ + Action::make('save') + ->label(__('onboarding.setup.submit')) + ->keyBindings(['mod+s']) + ->submit('save'), + ]) + ->alignment(Alignment::End) + ->sticky() + ->key('form-actions'), + ]) + ->livewireSubmitHandler('save') + ->id('form'), + ]); + } + + public function form(Schema $schema): Schema + { + return $schema + ->statePath('formData') + ->schema([ + Section::make(__('onboarding.setup.consent.heading')) + ->description(__('onboarding.setup.consent.subheading')) + ->schema([ + CheckboxList::make('consent') + ->options(BroadcasterConsent::class) + ->hiddenLabel(), + ]), + + Section::make(__('onboarding.setup.default_clip_status.heading')) + ->description(__('onboarding.setup.default_clip_status.subheading')) + ->schema([ + Radio::make('default_clip_status') + ->options( + collect(ClipStatus::defaultableOptions()) + ->mapWithKeys(fn (ClipStatus $status): array => [$status->value => $status->getLabel()]) + ->toArray() + ) + ->descriptions( + collect(ClipStatus::defaultableOptions()) + ->mapWithKeys(fn (ClipStatus $status): array => [ + $status->value => __('onboarding.setup.default_clip_status.options.'.Str::snake($status->name)), + ]) + ->toArray() + ) + ->default(ClipStatus::NeedApproval) + ->hiddenLabel() + ->required(), + ]), + + Section::make(__('onboarding.setup.submissions.heading')) + ->description(__('onboarding.setup.submissions.subheading')) + ->schema([ + Toggle::make('submit_user_allowed') + ->afterStateUpdated(function (bool $state, Set $set): void { + if ($state) { + $set('submit_mods_allowed', true); + } + }) + ->extraAlpineAttributes(['x-on:change' => 'console.log(1)']) + ->helperText(__('onboarding.setup.submissions.options.everyone.description')) + ->label(__('onboarding.setup.submissions.options.everyone.label')) + ->default(true) + ->live(), + + Toggle::make('submit_mods_allowed') + ->afterStateUpdated(function (bool $state, Set $set): void { + if (! $state) { + $set('submit_user_allowed', false); + } + }) + ->helperText(__('onboarding.setup.submissions.options.mods.description')) + ->label(__('onboarding.setup.submissions.options.mods.label')) + ->default(true) + ->live(), + ]), + ]); + } + + public function save(): void + { + $state = $this->getSchema('form')->getState(); + $broadcaster = $this->getBroadcaster(); + + DB::transaction(function () use ($broadcaster, $state): void { + Broadcaster::withTrashed()->updateOrCreate(['id' => $broadcaster->id], [ + 'consent' => $state['consent'], + 'submit_user_allowed' => $state['submit_user_allowed'], + 'submit_mods_allowed' => $state['submit_mods_allowed'], + 'default_clip_status' => $state['default_clip_status'], + 'onboarded_at' => now(), + 'deleted_at' => null, + ]); + + BroadcasterConsentLog::create([ + 'broadcaster_id' => $broadcaster->id, + 'state' => collect($state['consent'])->values()->all(), + 'changed_by' => auth()->id(), + 'change_reason' => 'Self Onboarding', + 'changed_at' => now(), + ]); + }); + + $this->redirect(Dashboard::getUrl(panel: 'dashboard', tenant: $broadcaster)); + } + + /** + * just a wrapper around getTenant so we have autocompletion stuff for Broadcaster + * + * @return Broadcaster + */ + private function getBroadcaster(): Model + { + return Filament::getTenant(); + } +} diff --git a/resources/views/filament/dashboard/pages/onboarding-page.blade.php b/resources/views/filament/dashboard/pages/onboarding-page.blade.php new file mode 100644 index 00000000..a7417332 --- /dev/null +++ b/resources/views/filament/dashboard/pages/onboarding-page.blade.php @@ -0,0 +1,3 @@ + + {{ $this->content }} + From 58214985b28bdfaf00c3c75f209f2265361c8fc3 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 03:49:44 +0200 Subject: [PATCH 07/13] refactor RequiresBroadcasterProfile to use new onboarding within filament instead --- .../Middleware/RequiresBroadcasterProfile.php | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/Http/Middleware/RequiresBroadcasterProfile.php b/app/Http/Middleware/RequiresBroadcasterProfile.php index abf317c0..549ed72a 100644 --- a/app/Http/Middleware/RequiresBroadcasterProfile.php +++ b/app/Http/Middleware/RequiresBroadcasterProfile.php @@ -4,13 +4,14 @@ namespace App\Http\Middleware; +use App\Filament\Dashboard\Pages\Onboarding; use App\Models\Broadcaster\Broadcaster; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; /** - * If the user has no broadcaster profile setup this will redirect them to the onboarding instead (remembering the intended route) + * Redirect the user to their own broadcaster onboarding if they try to access their own stuff without being onboarded */ class RequiresBroadcasterProfile { @@ -19,16 +20,20 @@ class RequiresBroadcasterProfile */ public function handle(Request $request, Closure $next): Response { - if ($request->routeIs('dashboard.onboarding')) { + if ($request->routeIs(Onboarding::getRouteName())) { + return $next($request); + } + + if ($request->route('tenant') !== (string) auth()->id()) { return $next($request); } $user = $request->user(); - $tenantId = $request->route('tenant'); - $isSelfTenant = ((int) $tenantId) === $user?->id; - if ((! $tenantId || $isSelfTenant) && ! Broadcaster::where('id', $user?->id)->whereOnboarded()->exists()) { - return redirect()->guest(route('dashboard.onboarding')); + if (Broadcaster::where('id', $user->id)->whereOnboarded()->doesntExist()) { + return redirect()->guest( + Onboarding::getUrl(panel: 'dashboard', tenant: Broadcaster::placeholder($user->id)), + ); } return $next($request); From 81bcaa3e5e7927a853f2b5bdc5851600ac6e78d4 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 04:05:05 +0200 Subject: [PATCH 08/13] reusable trait to disable page navigation items in filament for onboarding requirement --- .../DisabledNavigationUntilOnboarding.php | 40 +++++++++++++++++++ lang/de/dashboard/navigation.php | 1 + lang/en/dashboard/navigation.php | 1 + 3 files changed, 42 insertions(+) create mode 100644 app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php diff --git a/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php b/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php new file mode 100644 index 00000000..120f61fe --- /dev/null +++ b/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php @@ -0,0 +1,40 @@ +user(); + $items = parent::getNavigationItems(); + + if ($user->id !== $tenant->id || $tenant->onboarded_at !== null) { + return $items; + } + + return collect($items) + ->map(fn (NavigationItem $item) => $item + ->extraAttributes([ + 'class' => 'opacity-40 cursor-not-allowed select-none [&_a]:pointer-events-none', + 'aria-label' => __('dashboard/navigation.locked'), + 'title' => __('dashboard/navigation.locked'), + 'aria-disabled' => 'true', + ]) + ->icon(LucideIcon::Lock), + ) + ->all(); + } +} diff --git a/lang/de/dashboard/navigation.php b/lang/de/dashboard/navigation.php index f4716e15..f2a7d994 100644 --- a/lang/de/dashboard/navigation.php +++ b/lang/de/dashboard/navigation.php @@ -11,4 +11,5 @@ 'single' => 'Dein Kanal', 'multiple' => 'Kanal Wechseln', ], + 'locked' => 'Bitte vorher das Onboarding abschließen', ]; diff --git a/lang/en/dashboard/navigation.php b/lang/en/dashboard/navigation.php index 683bd5f0..52809abe 100644 --- a/lang/en/dashboard/navigation.php +++ b/lang/en/dashboard/navigation.php @@ -11,4 +11,5 @@ 'single' => 'Your Channel', 'multiple' => 'Switch Channel', ], + 'locked' => 'Please finish Onboarding first', ]; From b32980f9654ea9055a015cd39c6572069f790877 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 04:05:25 +0200 Subject: [PATCH 09/13] add DisabledNavigationUntilOnboarding trait to dashboard pages --- app/Filament/Dashboard/Pages/Broadcaster/GeneralSettings.php | 2 ++ .../Dashboard/Pages/Broadcaster/ManageCategoryFilter.php | 2 ++ app/Filament/Dashboard/Pages/Broadcaster/ManageTeamMember.php | 2 ++ app/Filament/Dashboard/Pages/Broadcaster/ManageUserFilter.php | 2 ++ app/Filament/Dashboard/Pages/Dashboard.php | 3 +++ app/Filament/Dashboard/Resources/Clips/ClipResource.php | 3 +++ .../Resources/RemovalRequests/RemovalRequestResource.php | 3 +++ 7 files changed, 17 insertions(+) diff --git a/app/Filament/Dashboard/Pages/Broadcaster/GeneralSettings.php b/app/Filament/Dashboard/Pages/Broadcaster/GeneralSettings.php index 53efeb1f..cfd816d9 100644 --- a/app/Filament/Dashboard/Pages/Broadcaster/GeneralSettings.php +++ b/app/Filament/Dashboard/Pages/Broadcaster/GeneralSettings.php @@ -11,6 +11,7 @@ use App\Enums\Clips\ClipStatus; use App\Enums\Filament\LucideIcon; use App\Filament\Dashboard\Actions\ApplyDefaultStatusAction; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Models\Broadcaster\Broadcaster; use BackedEnum; use Filament\Facades\Filament; @@ -35,6 +36,7 @@ */ class GeneralSettings extends Page implements HasForms { + use DisabledNavigationUntilOnboarding; use InteractsWithForms; /** @var array|null */ diff --git a/app/Filament/Dashboard/Pages/Broadcaster/ManageCategoryFilter.php b/app/Filament/Dashboard/Pages/Broadcaster/ManageCategoryFilter.php index 09dfeb45..af2dd6d1 100644 --- a/app/Filament/Dashboard/Pages/Broadcaster/ManageCategoryFilter.php +++ b/app/Filament/Dashboard/Pages/Broadcaster/ManageCategoryFilter.php @@ -8,6 +8,7 @@ use App\Enums\Broadcaster\DashboardNavigationGroup; use App\Enums\Broadcaster\DashboardNavigationItem; use App\Enums\Filament\LucideIcon; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Filament\Resources\Categories\CategorySelect; use App\Models\Broadcaster\Broadcaster; use App\Models\Broadcaster\BroadcasterSubmissionFilter; @@ -36,6 +37,7 @@ class ManageCategoryFilter extends Page implements HasTable { + use DisabledNavigationUntilOnboarding; use InteractsWithActions; use InteractsWithSchemas; use InteractsWithTable; diff --git a/app/Filament/Dashboard/Pages/Broadcaster/ManageTeamMember.php b/app/Filament/Dashboard/Pages/Broadcaster/ManageTeamMember.php index 02b23702..ea8f4d9f 100644 --- a/app/Filament/Dashboard/Pages/Broadcaster/ManageTeamMember.php +++ b/app/Filament/Dashboard/Pages/Broadcaster/ManageTeamMember.php @@ -9,6 +9,7 @@ use App\Enums\Broadcaster\DashboardNavigationItem; use App\Enums\FeatureFlag; use App\Enums\Filament\LucideIcon; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Filament\Resources\Users\UserSelect; use App\Models\Broadcaster\Broadcaster; use App\Support\FeatureFlag\Feature; @@ -41,6 +42,7 @@ class ManageTeamMember extends Page implements HasForms, HasTable { + use DisabledNavigationUntilOnboarding; use InteractsWithActions; use InteractsWithForms; use InteractsWithTable; diff --git a/app/Filament/Dashboard/Pages/Broadcaster/ManageUserFilter.php b/app/Filament/Dashboard/Pages/Broadcaster/ManageUserFilter.php index 9da89a38..93b6d96e 100644 --- a/app/Filament/Dashboard/Pages/Broadcaster/ManageUserFilter.php +++ b/app/Filament/Dashboard/Pages/Broadcaster/ManageUserFilter.php @@ -9,6 +9,7 @@ use App\Enums\Broadcaster\DashboardNavigationItem; use App\Enums\FeatureFlag; use App\Enums\Filament\LucideIcon; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Filament\Resources\Users\UserSelect; use App\Models\Broadcaster\Broadcaster; use App\Models\User; @@ -38,6 +39,7 @@ class ManageUserFilter extends Page implements HasTable { + use DisabledNavigationUntilOnboarding; use InteractsWithActions; use InteractsWithSchemas; use InteractsWithTable; diff --git a/app/Filament/Dashboard/Pages/Dashboard.php b/app/Filament/Dashboard/Pages/Dashboard.php index 461d36f3..d416c075 100644 --- a/app/Filament/Dashboard/Pages/Dashboard.php +++ b/app/Filament/Dashboard/Pages/Dashboard.php @@ -6,6 +6,7 @@ use App\Enums\Broadcaster\BroadcasterPermission; use App\Enums\Filament\LucideIcon; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use BackedEnum; use Filament\Facades\Filament; use Filament\Pages\Dashboard as BaseDashboard; @@ -14,6 +15,8 @@ class Dashboard extends BaseDashboard { + use DisabledNavigationUntilOnboarding; + protected static string|BackedEnum|null $navigationIcon = LucideIcon::House; public static function canAccess(): bool diff --git a/app/Filament/Dashboard/Resources/Clips/ClipResource.php b/app/Filament/Dashboard/Resources/Clips/ClipResource.php index cb225067..c2ee3f0c 100644 --- a/app/Filament/Dashboard/Resources/Clips/ClipResource.php +++ b/app/Filament/Dashboard/Resources/Clips/ClipResource.php @@ -13,6 +13,7 @@ use App\Filament\Dashboard\Resources\Clips\Schemas\ClipForm; use App\Filament\Dashboard\Resources\Clips\Schemas\ClipInfolist; use App\Filament\Dashboard\Resources\Clips\Tables\ClipsTable; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Models\Clip; use BackedEnum; use Filament\Facades\Filament; @@ -24,6 +25,8 @@ class ClipResource extends Resource { + use DisabledNavigationUntilOnboarding; + protected static bool $shouldSkipAuthorization = true; protected static ?string $model = Clip::class; diff --git a/app/Filament/Dashboard/Resources/RemovalRequests/RemovalRequestResource.php b/app/Filament/Dashboard/Resources/RemovalRequests/RemovalRequestResource.php index 51b2e9cf..cde8ba8b 100644 --- a/app/Filament/Dashboard/Resources/RemovalRequests/RemovalRequestResource.php +++ b/app/Filament/Dashboard/Resources/RemovalRequests/RemovalRequestResource.php @@ -12,6 +12,7 @@ use App\Filament\Dashboard\Resources\RemovalRequests\Schemas\RemovalRequestForm; use App\Filament\Dashboard\Resources\RemovalRequests\Schemas\RemovalRequestInfolist; use App\Filament\Dashboard\Resources\RemovalRequests\Tables\RemovalRequestsTable; +use App\Filament\Dashboard\Traits\DisabledNavigationUntilOnboarding; use App\Models\Broadcaster\RemovalRequest; use App\Support\FeatureFlag\Feature; use BackedEnum; @@ -23,6 +24,8 @@ class RemovalRequestResource extends Resource { + use DisabledNavigationUntilOnboarding; + protected static bool $shouldSkipAuthorization = true; protected static ?string $model = RemovalRequest::class; From 76f6cb4d367e2be69870d7cc4ce47c296102de88 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 12:56:47 +0200 Subject: [PATCH 10/13] remove old broadcaster onboarding (with flag as this is now always on) --- app/Enums/FeatureFlag.php | 3 -- .../Broadcaster/OnboardingController.php | 26 ----------- .../OnboardingSubmitController.php | 44 ------------------- .../Broadcaster/OnboardingRequest.php | 36 --------------- .../views/components/layout/header.blade.php | 18 ++------ .../layout/shared/onboarding-alert.blade.php | 2 - routes/dashboard.php | 14 ------ routes/web.php | 1 - 8 files changed, 4 insertions(+), 140 deletions(-) delete mode 100644 app/Http/Controllers/Broadcaster/OnboardingController.php delete mode 100644 app/Http/Controllers/Broadcaster/OnboardingSubmitController.php delete mode 100644 app/Http/Requests/Broadcaster/OnboardingRequest.php delete mode 100644 routes/dashboard.php diff --git a/app/Enums/FeatureFlag.php b/app/Enums/FeatureFlag.php index 02ea0deb..5d1f5fc8 100644 --- a/app/Enums/FeatureFlag.php +++ b/app/Enums/FeatureFlag.php @@ -45,9 +45,6 @@ enum FeatureFlag: string implements HasLabel #[DefaultFeatureFlagState(true)] case UserNavigation = 'user_navigation'; - #[DefaultFeatureFlagState(true)] - case BroadcasterOnboarding = 'broadcaster_onboarding'; - #[Description('Toggles the Tenant Feature in the Broadcaster Dashboard')] #[DefaultFeatureFlagState(false)] case BroadcasterTenant = 'broadcaster_tenant'; diff --git a/app/Http/Controllers/Broadcaster/OnboardingController.php b/app/Http/Controllers/Broadcaster/OnboardingController.php deleted file mode 100644 index 16470629..00000000 --- a/app/Http/Controllers/Broadcaster/OnboardingController.php +++ /dev/null @@ -1,26 +0,0 @@ -where('id', $request->user()->id)->whereOnboarded()->exists()) { - return Feature::isActive(FeatureFlag::UserDashboard) - ? redirect()->to(Filament::getPanel('dashboard')->getUrl()) - : redirect()->route('home'); - } - - return view('broadcaster.onboarding'); - } -} diff --git a/app/Http/Controllers/Broadcaster/OnboardingSubmitController.php b/app/Http/Controllers/Broadcaster/OnboardingSubmitController.php deleted file mode 100644 index ea6b5151..00000000 --- a/app/Http/Controllers/Broadcaster/OnboardingSubmitController.php +++ /dev/null @@ -1,44 +0,0 @@ -updateOrCreate(['id' => auth()->id()], [ - 'consent' => $request->array('consent'), - 'submit_user_allowed' => $request->boolean('everyone'), - 'submit_vip_allowed' => $request->boolean('vips'), - 'submit_mods_allowed' => $request->boolean('moderators'), - 'default_clip_status' => $request->enum('default_clip_status', ClipStatus::class, ClipStatus::Unknown), - 'onboarded_at' => now(), - 'deleted_at' => null, - ]); - - BroadcasterConsentLog::create([ - 'broadcaster_id' => $broadcaster->id, - 'state' => $broadcaster->consent?->values(), - 'changed_by' => auth()->id(), - 'change_reason' => 'Self Onboarding', - 'changed_at' => now(), - ]); - - $fallbackRoute = Feature::isActive(FeatureFlag::UserDashboard) - ? Filament::getPanel('dashboard')->getUrl($broadcaster) - : route('home'); - - return redirect()->intended($fallbackRoute); - } -} diff --git a/app/Http/Requests/Broadcaster/OnboardingRequest.php b/app/Http/Requests/Broadcaster/OnboardingRequest.php deleted file mode 100644 index ad9622e4..00000000 --- a/app/Http/Requests/Broadcaster/OnboardingRequest.php +++ /dev/null @@ -1,36 +0,0 @@ -check() && Feature::isActive(FeatureFlag::BroadcasterOnboarding); - } - - /** - * @return array - */ - public function rules(): array - { - return [ - 'consent' => ['nullable', 'array'], - 'default_clip_status' => ['nullable', Rule::enum(ClipStatus::class), Rule::in(ClipStatus::defaultableOptions())], - 'consent.*' => ['required', Rule::enum(BroadcasterConsent::class)], - 'everyone' => ['nullable', 'boolean'], - 'vips' => ['nullable', 'boolean'], - 'moderators' => ['nullable', 'boolean'], - ]; - } -} diff --git a/resources/views/components/layout/header.blade.php b/resources/views/components/layout/header.blade.php index b98157a8..6a850880 100644 --- a/resources/views/components/layout/header.blade.php +++ b/resources/views/components/layout/header.blade.php @@ -67,19 +67,9 @@ class="hidden size-4 opacity-70 transition-transform duration-200 group-hover:sc @feature(FeatureFlag::UserDashboard) - @if(Broadcaster::where('id', auth()->id())->exists()) - - {{ __('navigation.dashboard') }} - - @endif - @endfeature - - @feature(FeatureFlag::BroadcasterOnboarding) - @if(! Feature::isActive(FeatureFlag::UserDashboard) || ! Broadcaster::where('id', auth()->id())->exists()) - - {{ __('navigation.onboarding') }} - - @endif + + {{ __('navigation.dashboard') }} + @endfeature @feature(FeatureFlag::UserSettings) @@ -88,7 +78,7 @@ class="hidden size-4 opacity-70 transition-transform duration-200 group-hover:sc @endfeature - @featureAny([FeatureFlag::UserDashboard, FeatureFlag::UserSettings, FeatureFlag::BroadcasterOnboarding]) + @featureAny([FeatureFlag::UserDashboard, FeatureFlag::UserSettings]) @endfeatureAny diff --git a/resources/views/components/layout/shared/onboarding-alert.blade.php b/resources/views/components/layout/shared/onboarding-alert.blade.php index 6cbc728e..63197959 100644 --- a/resources/views/components/layout/shared/onboarding-alert.blade.php +++ b/resources/views/components/layout/shared/onboarding-alert.blade.php @@ -1,4 +1,3 @@ -@feature(App\Enums\FeatureFlag::BroadcasterOnboarding) @if (session('showTwitchPermissionsPrompt'))
@endif -@endfeature diff --git a/routes/dashboard.php b/routes/dashboard.php deleted file mode 100644 index 71cd4bc5..00000000 --- a/routes/dashboard.php +++ /dev/null @@ -1,14 +0,0 @@ -group(function () { - Route::get('/onboarding', OnboardingController::class)->name('dashboard.onboarding'); - Route::post('/onboarding', OnboardingSubmitController::class)->name('dashboard.onboarding.store'); -}); diff --git a/routes/web.php b/routes/web.php index 985faf0f..72f57594 100644 --- a/routes/web.php +++ b/routes/web.php @@ -41,6 +41,5 @@ }); }); -require __DIR__.'/dashboard.php'; require __DIR__.'/settings.php'; require __DIR__.'/auth.php'; From 69811465e6cba5794a1fb8918c4f4a0826dd25b8 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 12:58:01 +0200 Subject: [PATCH 11/13] redirect dashboard/ to dashboard/tenant as a simple shortcut for own dashboard same as before just less files --- routes/web.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/routes/web.php b/routes/web.php index 72f57594..a36a505b 100644 --- a/routes/web.php +++ b/routes/web.php @@ -14,6 +14,7 @@ use App\Http\Controllers\Legal\TermsController; use App\Http\Controllers\ReportController; use App\Http\Controllers\TeamController; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; Route::get('/', IndexController::class)->name('home'); @@ -39,6 +40,8 @@ Route::feature(FeatureFlag::Reports)->group(function () { Route::post('/reports', [ReportController::class, 'store'])->name('reports.store'); }); + + Route::get('dashboard', static fn (Request $request) => redirect('dashboard/'.$request->user()->id)); }); require __DIR__.'/settings.php'; From 559b772f2a8d244de78845ed3ef01983aae39575 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 12:59:53 +0200 Subject: [PATCH 12/13] rector --- .../Dashboard/Traits/DisabledNavigationUntilOnboarding.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php b/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php index 120f61fe..641a9e5e 100644 --- a/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php +++ b/app/Filament/Dashboard/Traits/DisabledNavigationUntilOnboarding.php @@ -26,7 +26,7 @@ public static function getNavigationItems(): array } return collect($items) - ->map(fn (NavigationItem $item) => $item + ->map(fn (NavigationItem $item): NavigationItem => $item ->extraAttributes([ 'class' => 'opacity-40 cursor-not-allowed select-none [&_a]:pointer-events-none', 'aria-label' => __('dashboard/navigation.locked'), From 2d26bcf6e186774ee86deb36823d1759c7d43516 Mon Sep 17 00:00:00 2001 From: "Justin K." Date: Thu, 25 Jun 2026 13:04:32 +0200 Subject: [PATCH 13/13] move onboarding language file to dashboard namespace --- app/Filament/Dashboard/Pages/Onboarding.php | 28 ++++++++++----------- lang/de/{ => dashboard}/onboarding.php | 0 lang/en/{ => dashboard}/onboarding.php | 0 3 files changed, 14 insertions(+), 14 deletions(-) rename lang/de/{ => dashboard}/onboarding.php (100%) rename lang/en/{ => dashboard}/onboarding.php (100%) diff --git a/app/Filament/Dashboard/Pages/Onboarding.php b/app/Filament/Dashboard/Pages/Onboarding.php index 81ca2f0d..63402f95 100644 --- a/app/Filament/Dashboard/Pages/Onboarding.php +++ b/app/Filament/Dashboard/Pages/Onboarding.php @@ -49,12 +49,12 @@ public static function canAccess(): bool public function getHeading(): string|Htmlable|null { - return __('onboarding.heading', ['username' => auth()->user()->name]); + return __('dashboard/onboarding.heading', ['username' => auth()->user()->name]); } public function getSubheading(): string|Htmlable|null { - return __('onboarding.setup.heading'); + return __('dashboard/onboarding.setup.heading'); } public function mount(): void @@ -80,7 +80,7 @@ public function content(Schema $schema): Schema ->footer([ Actions::make([ Action::make('save') - ->label(__('onboarding.setup.submit')) + ->label(__('dashboard/onboarding.setup.submit')) ->keyBindings(['mod+s']) ->submit('save'), ]) @@ -98,16 +98,16 @@ public function form(Schema $schema): Schema return $schema ->statePath('formData') ->schema([ - Section::make(__('onboarding.setup.consent.heading')) - ->description(__('onboarding.setup.consent.subheading')) + Section::make(__('dashboard/onboarding.setup.consent.heading')) + ->description(__('dashboard/onboarding.setup.consent.subheading')) ->schema([ CheckboxList::make('consent') ->options(BroadcasterConsent::class) ->hiddenLabel(), ]), - Section::make(__('onboarding.setup.default_clip_status.heading')) - ->description(__('onboarding.setup.default_clip_status.subheading')) + Section::make(__('dashboard/onboarding.setup.default_clip_status.heading')) + ->description(__('dashboard/onboarding.setup.default_clip_status.subheading')) ->schema([ Radio::make('default_clip_status') ->options( @@ -118,7 +118,7 @@ public function form(Schema $schema): Schema ->descriptions( collect(ClipStatus::defaultableOptions()) ->mapWithKeys(fn (ClipStatus $status): array => [ - $status->value => __('onboarding.setup.default_clip_status.options.'.Str::snake($status->name)), + $status->value => __('dashboard/onboarding.setup.default_clip_status.options.'.Str::snake($status->name)), ]) ->toArray() ) @@ -127,8 +127,8 @@ public function form(Schema $schema): Schema ->required(), ]), - Section::make(__('onboarding.setup.submissions.heading')) - ->description(__('onboarding.setup.submissions.subheading')) + Section::make(__('dashboard/onboarding.setup.submissions.heading')) + ->description(__('dashboard/onboarding.setup.submissions.subheading')) ->schema([ Toggle::make('submit_user_allowed') ->afterStateUpdated(function (bool $state, Set $set): void { @@ -137,8 +137,8 @@ public function form(Schema $schema): Schema } }) ->extraAlpineAttributes(['x-on:change' => 'console.log(1)']) - ->helperText(__('onboarding.setup.submissions.options.everyone.description')) - ->label(__('onboarding.setup.submissions.options.everyone.label')) + ->helperText(__('dashboard/onboarding.setup.submissions.options.everyone.description')) + ->label(__('dashboard/onboarding.setup.submissions.options.everyone.label')) ->default(true) ->live(), @@ -148,8 +148,8 @@ public function form(Schema $schema): Schema $set('submit_user_allowed', false); } }) - ->helperText(__('onboarding.setup.submissions.options.mods.description')) - ->label(__('onboarding.setup.submissions.options.mods.label')) + ->helperText(__('dashboard/onboarding.setup.submissions.options.mods.description')) + ->label(__('dashboard/onboarding.setup.submissions.options.mods.label')) ->default(true) ->live(), ]), diff --git a/lang/de/onboarding.php b/lang/de/dashboard/onboarding.php similarity index 100% rename from lang/de/onboarding.php rename to lang/de/dashboard/onboarding.php diff --git a/lang/en/onboarding.php b/lang/en/dashboard/onboarding.php similarity index 100% rename from lang/en/onboarding.php rename to lang/en/dashboard/onboarding.php