From 12b315b170465579a3bd3de3966a4bf3a929ccd0 Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 16:24:01 -0300 Subject: [PATCH 1/9] feat(tenants): add tenant-user relationship and support multi-tenancy --- .../src/Actions/AuthenticateAction.php | 2 + ...11_08_161609_create_tenant_users_table.php | 30 +++++++++++++ .../Models/Concerns/InteractsWithTenants.php | 45 +++++++++++++++++++ app-modules/tenant/src/Models/Tenant.php | 10 +++++ app-modules/user/src/Models/User.php | 5 ++- app/Providers/Filament/UserPanelProvider.php | 2 + database/seeders/DatabaseSeeder.php | 21 ++++----- 7 files changed, 102 insertions(+), 13 deletions(-) create mode 100644 app-modules/tenant/database/migrations/2025_11_08_161609_create_tenant_users_table.php create mode 100644 app-modules/tenant/src/Models/Concerns/InteractsWithTenants.php diff --git a/app-modules/authentication/src/Actions/AuthenticateAction.php b/app-modules/authentication/src/Actions/AuthenticateAction.php index 2cf25fb13..4ac27932b 100644 --- a/app-modules/authentication/src/Actions/AuthenticateAction.php +++ b/app-modules/authentication/src/Actions/AuthenticateAction.php @@ -49,6 +49,8 @@ private function registerNewUser(OAuthUserDTO $userDTO, Tenant $tenant): Provide 'is_donator' => false, ]); + $user->tenants()->attach($tenant); + /** @var Provider $provider */ $provider = $user->providers()->updateOrCreate([ 'tenant_id' => $tenant->getKey(), diff --git a/app-modules/tenant/database/migrations/2025_11_08_161609_create_tenant_users_table.php b/app-modules/tenant/database/migrations/2025_11_08_161609_create_tenant_users_table.php new file mode 100644 index 000000000..0c707cebc --- /dev/null +++ b/app-modules/tenant/database/migrations/2025_11_08_161609_create_tenant_users_table.php @@ -0,0 +1,30 @@ +foreignId('tenant_id')->constrained('tenants')->cascadeOnDelete(); + $table->foreignUuid('user_id')->constrained('users')->cascadeOnDelete(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('tenant_users'); + } +}; diff --git a/app-modules/tenant/src/Models/Concerns/InteractsWithTenants.php b/app-modules/tenant/src/Models/Concerns/InteractsWithTenants.php new file mode 100644 index 000000000..f0846ab21 --- /dev/null +++ b/app-modules/tenant/src/Models/Concerns/InteractsWithTenants.php @@ -0,0 +1,45 @@ + + */ + public function tenants(): BelongsToMany + { + return $this->belongsToMany(Tenant::class, 'tenant_users'); + } + + public function canAccessTenant(Model $tenant): bool + { + return $this->tenants()->whereKey($tenant)->exists(); + } + + /** + * @return array | Collection + */ + public function getTenants(Panel $panel): array|Collection + { + return $this->tenants; + } + + /** + * @return HasMany + */ + public function ownedTenants(): HasMany + { + return $this->hasMany(Tenant::class, 'owner_id'); + } +} diff --git a/app-modules/tenant/src/Models/Tenant.php b/app-modules/tenant/src/Models/Tenant.php index 863a1ee09..7c8ac75c2 100644 --- a/app-modules/tenant/src/Models/Tenant.php +++ b/app-modules/tenant/src/Models/Tenant.php @@ -10,7 +10,9 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Database\Eloquent\Relations\Pivot; use Illuminate\Database\Eloquent\SoftDeletes; class Tenant extends Model @@ -34,6 +36,14 @@ public function owner(): BelongsTo return $this->belongsTo(User::class, 'owner_id'); } + /** + * @return BelongsToMany + */ + public function members(): BelongsToMany + { + return $this->belongsToMany(User::class, 'tenant_users'); + } + /** * @return MorphMany */ diff --git a/app-modules/user/src/Models/User.php b/app-modules/user/src/Models/User.php index e08a2aead..0f95a2979 100644 --- a/app-modules/user/src/Models/User.php +++ b/app-modules/user/src/Models/User.php @@ -5,8 +5,10 @@ namespace He4rt\User\Models; use Filament\Models\Contracts\HasName; +use Filament\Models\Contracts\HasTenants; use He4rt\Character\Models\Character; use He4rt\Provider\Models\Provider; +use He4rt\Tenant\Models\Concerns\InteractsWithTenants; use He4rt\User\Database\Factories\UserFactory; use He4rt\User\Observers\UserObserver; use Illuminate\Database\Eloquent\Attributes\ObservedBy; @@ -23,10 +25,11 @@ * @property bool $is_donator */ #[ObservedBy(UserObserver::class)] -final class User extends Authenticatable implements HasName +final class User extends Authenticatable implements HasName, HasTenants { use HasFactory; use HasUuids; + use InteractsWithTenants; protected $table = 'users'; diff --git a/app/Providers/Filament/UserPanelProvider.php b/app/Providers/Filament/UserPanelProvider.php index 30894ea55..2953a8426 100644 --- a/app/Providers/Filament/UserPanelProvider.php +++ b/app/Providers/Filament/UserPanelProvider.php @@ -14,6 +14,7 @@ use Filament\Support\Colors\Color; use Filament\Widgets\AccountWidget; use Filament\Widgets\FilamentInfoWidget; +use He4rt\Tenant\Models\Tenant; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; @@ -30,6 +31,7 @@ public function panel(Panel $panel): Panel ->id('user') ->path('app') ->login() + ->tenant(Tenant::class, 'slug', 'ownedTenants') ->colors([ 'primary' => Color::Purple, ]) diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 6088e3d06..276f797a3 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -5,7 +5,7 @@ namespace Database\Seeders; // use Illuminate\Database\Console\Seeds\WithoutModelEvents; -use He4rt\Meeting\Models\MeetingType; +use He4rt\Tenant\Models\Tenant; use He4rt\User\Models\User; use Illuminate\Database\Seeder; use Illuminate\Support\Facades\Hash; @@ -19,23 +19,20 @@ public function run(): void { // \App\Models\User::factory(10)->create(); - User::factory()->create([ + $user = User::factory()->create([ 'username' => 'admin', 'name' => 'admin', 'email' => 'admin@admin.com', 'password' => Hash::make('admin'), ]); - MeetingType::query()->create([ - 'name' => 'Reunião Semanal', - 'week_day' => 1, - 'start_at' => '20:30', - ]); + Tenant::factory() + ->for($user, 'owner') + ->afterCreating(fn (Tenant $tenant) => $tenant->members()->attach($user)) + ->create([ + 'name' => 'He4rt Developers', + 'slug' => 'he4rt', + ]); - MeetingType::query()->create([ - 'name' => 'Reunião Semanal', - 'week_day' => 2, - 'start_at' => '20:00', - ]); } } From 6f26168e0f5eef0a3aee65d966f9d48fe41159a8 Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:17:35 -0300 Subject: [PATCH 2/9] feat(user): migrate `UserSettings` to `UserProfile` with extended functionality in Filament --- .../src/Filament/User/Pages/UserProfile.php | 478 ++++++++++++++++++ app/Filament/Pages/UserSettings.php | 43 -- 2 files changed, 478 insertions(+), 43 deletions(-) create mode 100644 app-modules/user/src/Filament/User/Pages/UserProfile.php delete mode 100644 app/Filament/Pages/UserSettings.php diff --git a/app-modules/user/src/Filament/User/Pages/UserProfile.php b/app-modules/user/src/Filament/User/Pages/UserProfile.php new file mode 100644 index 000000000..de3023869 --- /dev/null +++ b/app-modules/user/src/Filament/User/Pages/UserProfile.php @@ -0,0 +1,478 @@ + | null + */ + public ?array $data = []; + + protected static bool $isDiscovered = false; + + protected string $view; + + public static function isSimple(): bool + { + return true; + } + + public static function getLabel(): string + { + return self::$title ?? __('filament-panels::auth/pages/edit-profile.label'); + } + + public static function getRelativeRouteName(Panel $panel): string + { + return 'profile'; + } + + public static function isTenantSubscriptionRequired(Panel $panel): bool + { + return false; + } + + public static function getSlug(?Panel $panel = null): string + { + return self::$slug ?? 'profile'; + } + + public function getLayout(): string + { + return self::$layout ?? (self::isSimple() ? 'filament-panels::components.layout.simple' : 'filament-panels::components.layout.index'); + } + + public function getView(): string + { + return $this->view ?? 'filament-panels::auth.pages.edit-profile'; + } + + public function mount(): void + { + $this->fillForm(); + } + + public function getUser(): Authenticatable&Model + { + $user = Filament::auth()->user(); + + throw_unless($user instanceof Model, LogicException::class, 'The authenticated user object must be an Eloquent model to allow the profile page to update it.'); + + return $user; + } + + public function save(): void + { + try { + $this->beginDatabaseTransaction(); + + $this->callHook('beforeValidate'); + + $data = $this->form->getState(); + + $this->callHook('afterValidate'); + + $data = $this->mutateFormDataBeforeSave($data); + + $this->callHook('beforeSave'); + + $this->handleRecordUpdate($this->getUser(), $data); + + $this->callHook('afterSave'); + } catch (Halt $exception) { + $exception->shouldRollbackDatabaseTransaction() + ? $this->rollBackDatabaseTransaction() + : $this->commitDatabaseTransaction(); + + return; + } catch (Throwable $exception) { + $this->rollBackDatabaseTransaction(); + + throw $exception; + } + + $this->commitDatabaseTransaction(); + + if (request()->hasSession() && array_key_exists('password', $data)) { + request()->session()->put([ + 'password_hash_'.Filament::getAuthGuard() => $data['password'], + ]); + } + + $this->data['password'] = null; + $this->data['passwordConfirmation'] = null; + + $this->getSavedNotification()?->send(); + + if ($redirectUrl = $this->getRedirectUrl()) { + $this->redirect($redirectUrl, navigate: FilamentView::hasSpaMode($redirectUrl)); + } + } + + public function defaultForm(Schema $schema): Schema + { + return $schema + ->inlineLabel(! self::isSimple()) + ->model($this->getUser()) + ->operation('edit') + ->statePath('data'); + } + + public function form(Schema $schema): Schema + { + return $schema + ->components([ + $this->getNameFormComponent(), + $this->getEmailFormComponent(), + $this->getPasswordFormComponent(), + $this->getPasswordConfirmationFormComponent(), + $this->getCurrentPasswordFormComponent(), + ]); + } + + public function getFormActionsAlignment(): Alignment + { + return Alignment::Start; + } + + public function getMaxWidth(): Width + { + return Width::ScreenLarge; + } + + public function getTitle(): string + { + return self::getLabel(); + } + + public function hasLogo(): bool + { + return false; + } + + /** + * @deprecated Use `getCancelFormAction()` instead. + */ + public function backAction(): Action + { + $url = filament()->getUrl(); + + return Action::make('back') + ->label(__('filament-panels::auth/pages/edit-profile.actions.cancel.label')) + ->alpineClickHandler( + FilamentView::hasSpaMode($url) + ? 'document.referrer ? window.history.back() : Livewire.navigate('.Js::from($url).')' + : 'document.referrer ? window.history.back() : (window.location.href = '.Js::from($url).')', + ) + ->color('gray'); + } + + public function content(Schema $schema): Schema + { + return $schema + ->components([ + Tabs::make('tabs') + ->tabs([ + Tab::make('General') + ->schema([ + $this->getFormContentComponent(), + ]), + Tab::make('Connections') + ->schema([ + Livewire::make(ConnectionHub::class), + ]), + ]), + ...Arr::wrap($this->getMultiFactorAuthenticationContentComponent()), + ]); + } + + public function getFormContentComponent(): Component + { + return Form::make([EmbeddedSchema::make('form')]) + ->id('form') + ->livewireSubmitHandler('save') + ->footer([ + Actions::make($this->getFormActions()) + ->alignment($this->getFormActionsAlignment()) + ->fullWidth($this->hasFullWidthFormActions()) + ->sticky((! self::isSimple()) && $this->areFormActionsSticky()) + ->key('form-actions'), + ]); + } + + public function getMultiFactorAuthenticationContentComponent(): ?Component + { + if (! Filament::hasMultiFactorAuthentication()) { + return null; + } + + $user = Filament::auth()->user(); + + return Section::make() + ->label(__('filament-panels::auth/pages/edit-profile.multi_factor_authentication.label')) + ->compact() + ->divided() + ->secondary() + ->schema(collect(Filament::getMultiFactorAuthenticationProviders()) + ->sort(fn (MultiFactorAuthenticationProvider $multiFactorAuthenticationProvider): int => $multiFactorAuthenticationProvider->isEnabled($user) ? 0 : 1) + ->map(fn (MultiFactorAuthenticationProvider $multiFactorAuthenticationProvider): Component => Group::make($multiFactorAuthenticationProvider->getManagementSchemaComponents()) + ->statePath($multiFactorAuthenticationProvider->getId())) + ->all()); + } + + private function fillForm(): void + { + $data = $this->getUser()->attributesToArray(); + + $this->callHook('beforeFill'); + + $data = $this->mutateFormDataBeforeFill($data); + + $this->form->fill($data); + + $this->callHook('afterFill'); + } + + /** + * @param array $data + * @return array + */ + private function mutateFormDataBeforeFill(array $data): array + { + return $data; + } + + /** + * @param array $data + * @return array + */ + private function mutateFormDataBeforeSave(array $data): array + { + return $data; + } + + /** + * @param array $data + */ + private function handleRecordUpdate(Model $record, array $data): Model + { + if (Filament::hasEmailChangeVerification() && array_key_exists('email', $data)) { + $this->sendEmailChangeVerification($record, $data['email']); + + unset($data['email']); + } + + $record->update($data); + + return $record; + } + + private function sendEmailChangeVerification(Model $record, string $newEmail): void + { + if ($record->getAttributeValue('email') === $newEmail) { + return; + } + + $notification = app(VerifyEmailChange::class); + $notification->url = Filament::getVerifyEmailChangeUrl($record, $newEmail); + + $verificationSignature = Query::new($notification->url)->get('signature'); + + cache()->put($verificationSignature, true, ttl: now()->addHour()); + + $record->notify(app(NoticeOfEmailChangeRequest::class, [/** @phpstan-ignore-line */ + 'blockVerificationUrl' => Filament::getBlockEmailChangeVerificationUrl($record, $newEmail, $verificationSignature), + 'newEmail' => $newEmail, + ])); + + Notification::route('mail', $newEmail) + ->notify($notification); + + $this->getEmailChangeVerificationSentNotification($newEmail)?->send(); + + $this->data['email'] = $record->getAttributeValue('email'); + } + + private function getSavedNotification(): ?FilamentNotification + { + $title = $this->getSavedNotificationTitle(); + + if (blank($title)) { + return null; + } + + return FilamentNotification::make() + ->success() + ->title($title); + } + + private function getEmailChangeVerificationSentNotification(string $newEmail): FilamentNotification + { + return FilamentNotification::make() + ->success() + ->title(__('filament-panels::auth/pages/edit-profile.notifications.email_change_verification_sent.title', ['email' => $newEmail])) + ->body(__('filament-panels::auth/pages/edit-profile.notifications.email_change_verification_sent.body', ['email' => $newEmail])); + } + + private function getSavedNotificationTitle(): ?string + { + return __('filament-panels::auth/pages/edit-profile.notifications.saved.title'); + } + + private function getRedirectUrl(): ?string + { + return null; + } + + private function getNameFormComponent(): Component + { + return TextInput::make('name') + ->label(__('filament-panels::auth/pages/edit-profile.form.name.label')) + ->required() + ->maxLength(255) + ->autofocus(); + } + + private function getEmailFormComponent(): Component + { + return TextInput::make('email') + ->label(__('filament-panels::auth/pages/edit-profile.form.email.label')) + ->email() + ->required() + ->maxLength(255) + ->unique(ignoreRecord: true) + ->live(debounce: 500); + } + + private function getPasswordFormComponent(): Component + { + return TextInput::make('password') + ->label(__('filament-panels::auth/pages/edit-profile.form.password.label')) + ->validationAttribute(__('filament-panels::auth/pages/edit-profile.form.password.validation_attribute')) + ->password() + ->revealable(filament()->arePasswordsRevealable()) + ->rule(Password::default()) + ->showAllValidationMessages() + ->autocomplete('new-password') + ->dehydrated(fn ($state): bool => filled($state)) + ->dehydrateStateUsing(fn ($state): string => Hash::make($state)) + ->live(debounce: 500) + ->same('passwordConfirmation'); + } + + private function getPasswordConfirmationFormComponent(): Component + { + return TextInput::make('passwordConfirmation') + ->label(__('filament-panels::auth/pages/edit-profile.form.password_confirmation.label')) + ->validationAttribute(__('filament-panels::auth/pages/edit-profile.form.password_confirmation.validation_attribute')) + ->password() + ->autocomplete('new-password') + ->revealable(filament()->arePasswordsRevealable()) + ->required() + ->visible(fn (Get $get): bool => filled($get('password'))) + ->dehydrated(false); + } + + private function getCurrentPasswordFormComponent(): Component + { + return TextInput::make('currentPassword') + ->label(__('filament-panels::auth/pages/edit-profile.form.current_password.label')) + ->validationAttribute(__('filament-panels::auth/pages/edit-profile.form.current_password.validation_attribute')) + ->belowContent(__('filament-panels::auth/pages/edit-profile.form.current_password.below_content')) + ->password() + ->autocomplete('current-password') + ->currentPassword(guard: Filament::getAuthGuard()) + ->revealable(filament()->arePasswordsRevealable()) + ->required() + ->visible(fn (Get $get): bool => filled($get('password')) || ($get('email') !== $this->getUser()->getAttributeValue('email'))) + ->dehydrated(false); + } + + /** + * @return array + */ + private function getFormActions(): array + { + return [ + $this->getSaveFormAction(), + $this->getCancelFormAction(), + ]; + } + + private function getCancelFormAction(): Action + { + return $this->backAction(); + } + + private function getSaveFormAction(): Action + { + return Action::make('save') + ->label(__('filament-panels::auth/pages/edit-profile.form.actions.save.label')) + ->submit('save') + ->keyBindings(['mod+s']); + } + + private function hasFullWidthFormActions(): bool + { + return false; + } + + protected function getLayoutData(): array + { + return [ + 'hasTopbar' => $this->hasTopbar(), + 'maxContentWidth' => $maxContentWidth = $this->getMaxWidth() ?? $this->getMaxContentWidth(), + 'maxWidth' => $maxContentWidth, + ]; + } +} diff --git a/app/Filament/Pages/UserSettings.php b/app/Filament/Pages/UserSettings.php deleted file mode 100644 index 91e8abafa..000000000 --- a/app/Filament/Pages/UserSettings.php +++ /dev/null @@ -1,43 +0,0 @@ -components([ - Tabs::make('Tabs') - ->activeTab(2) - ->schema([ - Tab::make('Profile') - ->schema([ - $this->getNameFormComponent(), - $this->getEmailFormComponent(), - $this->getPasswordFormComponent(), - $this->getPasswordConfirmationFormComponent(), - $this->getCurrentPasswordFormComponent(), - ]), - Tab::make('Connections') - ->schema([ - Livewire::make(ConnectionHub::class), - ]), - ]), - ]); - } -} From 56bf8a0f5f1b72a8bb30338c41b6822afb952e75 Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:17:47 -0300 Subject: [PATCH 3/9] feat(user): add `AppUserPanelPlugin` and integrate into `UserServiceProvider` --- .../user/src/Plugins/AppUserPanelPlugin.php | 27 +++++++++++++++++++ .../src/Providers/UserServiceProvider.php | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 app-modules/user/src/Plugins/AppUserPanelPlugin.php diff --git a/app-modules/user/src/Plugins/AppUserPanelPlugin.php b/app-modules/user/src/Plugins/AppUserPanelPlugin.php new file mode 100644 index 000000000..3345558ea --- /dev/null +++ b/app-modules/user/src/Plugins/AppUserPanelPlugin.php @@ -0,0 +1,27 @@ +moduleName('user'); + } + + public function register(Panel $panel): void + { + $panel->pages([ + UserProfile::class, + ]); + } + + public function boot(Panel $panel): void {} +} diff --git a/app-modules/user/src/Providers/UserServiceProvider.php b/app-modules/user/src/Providers/UserServiceProvider.php index 3f2d80627..bd17496d9 100644 --- a/app-modules/user/src/Providers/UserServiceProvider.php +++ b/app-modules/user/src/Providers/UserServiceProvider.php @@ -8,6 +8,7 @@ use Filament\Panel; use He4rt\User\Contracts\UserRepository; use He4rt\User\Plugins\AdminUserPanelPlugin; +use He4rt\User\Plugins\AppUserPanelPlugin; use He4rt\User\Repositories\UserEloquentRepository; use Illuminate\Support\ServiceProvider; @@ -20,6 +21,7 @@ public function register(): void Panel::configureUsing(function (Panel $panel): void { match ($panel->currentPanel()) { FilamentPanel::Admin => $panel->plugin(new AdminUserPanelPlugin()), + FilamentPanel::User => $panel->plugin(new AppUserPanelPlugin()), default => null, }; }); From f261f46f622ff7470a54ddb7e46b252c134ac614 Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:17:56 -0300 Subject: [PATCH 4/9] feat(authentication): enhance multi-tenancy support and add state to OAuth redirect - Added `tenant_id` condition to provider query in `AuthenticateAction`. - Improved authorization checks and refined user registration logic. - Updated `TwitchOAuthClient` to include encrypted state in OAuth redirect URL. - Modified `OAuthClientContract` to support optional `state` parameter. --- .../authentication/src/Actions/AuthenticateAction.php | 11 +++++++---- .../src/Contracts/OAuthClientContract.php | 2 +- .../src/Twitch/OAuth/Client/TwitchOAuthClient.php | 9 ++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app-modules/authentication/src/Actions/AuthenticateAction.php b/app-modules/authentication/src/Actions/AuthenticateAction.php index 4ac27932b..dfa1096c9 100644 --- a/app-modules/authentication/src/Actions/AuthenticateAction.php +++ b/app-modules/authentication/src/Actions/AuthenticateAction.php @@ -26,6 +26,7 @@ public function withOAuth(string $tenantSlug, OAuthProviderEnum $oauthProvider, $user = $clientProvider->getAuthenticatedUser($accessData); $provider = Provider::query() + ->where('tenant_id', $tenant->getKey()) ->where('provider', $user->provider) ->where('provider_id', $user->providerId) ->first(); @@ -34,14 +35,16 @@ public function withOAuth(string $tenantSlug, OAuthProviderEnum $oauthProvider, $provider = $this->registerNewUser($user, $tenant); } - Auth::logout(); - Auth::login($provider->user); - filament()->auth()->setUser($provider->user); + if (! auth()->check()) { + Auth::logout(); + Auth::login($provider->user); + filament()->auth()->setUser($provider->user); + } } private function registerNewUser(OAuthUserDTO $userDTO, Tenant $tenant): Provider { - $user = User::query()->firstOrCreate(['email' => $userDTO->email], [ + $user = auth()->check() ? auth()->user() : User::query()->firstOrCreate(['email' => $userDTO->email], [ 'id' => Uuid::uuid4()->toString(), 'username' => $userDTO->username, 'name' => $userDTO->name, diff --git a/app-modules/authentication/src/Contracts/OAuthClientContract.php b/app-modules/authentication/src/Contracts/OAuthClientContract.php index 4ab91338f..dafcf14d9 100644 --- a/app-modules/authentication/src/Contracts/OAuthClientContract.php +++ b/app-modules/authentication/src/Contracts/OAuthClientContract.php @@ -9,7 +9,7 @@ interface OAuthClientContract { - public function redirectUrl(): string; + public function redirectUrl(?string $state = null): string; public function auth(string $code): OAuthAccessDTO; diff --git a/app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php b/app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php index 38f0fdd99..94d4676de 100644 --- a/app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php +++ b/app-modules/integrations/src/Twitch/OAuth/Client/TwitchOAuthClient.php @@ -9,18 +9,21 @@ use He4rt\Integrations\Twitch\OAuth\Contracts\TwitchOAuthService; use He4rt\Integrations\Twitch\OAuth\DTO\TwitchOAuthAccessDTO; use He4rt\Integrations\Twitch\OAuth\DTO\TwitchOAuthDTO; +use Illuminate\Support\Facades\Crypt; +use Illuminate\Support\Facades\Date; final readonly class TwitchOAuthClient implements TwitchOAuthService { public function __construct(private Client $client) {} - public function redirectUrl(): string + public function redirectUrl(?string $state = null): string { return sprintf( - 'https://id.twitch.tv/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=%s', + 'https://id.twitch.tv/oauth2/authorize?client_id=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s', config('services.twitch.client_id'), config('services.twitch.redirect_uri'), - config('services.twitch.scopes') + config('services.twitch.scopes'), + Crypt::encryptString($state ?? Date::now()->getTimestamp()) ); } From 9c17d65225558d7d7b4764578e71500e0afe49ea Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:18:07 -0300 Subject: [PATCH 5/9] feat(panels): update user and admin panel configuration for improved customization - Added `UserProfile` page and updated user menu items. - Adjusted admin panel to remove deprecated `UserSettings`. - Enhanced theming and colors with `viteTheme` and extended color palette. - Removed unused widgets from user panel for cleaner setup. --- app/Providers/Filament/AdminPanelProvider.php | 2 -- app/Providers/Filament/UserPanelProvider.php | 17 +++++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index b8a3262e5..4cd6fe356 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -5,7 +5,6 @@ namespace App\Providers\Filament; use App\Filament\Pages\Login; -use App\Filament\Pages\UserSettings; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -32,7 +31,6 @@ public function panel(Panel $panel): Panel ->id('admin') ->path('admin') ->login(Login::class) - ->profile(UserSettings::class) ->colors([ 'primary' => Color::Purple, ...Color::all(), diff --git a/app/Providers/Filament/UserPanelProvider.php b/app/Providers/Filament/UserPanelProvider.php index 2953a8426..b60ccc92f 100644 --- a/app/Providers/Filament/UserPanelProvider.php +++ b/app/Providers/Filament/UserPanelProvider.php @@ -4,6 +4,7 @@ namespace App\Providers\Filament; +use Filament\Actions\Action; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -12,9 +13,8 @@ use Filament\Panel; use Filament\PanelProvider; use Filament\Support\Colors\Color; -use Filament\Widgets\AccountWidget; -use Filament\Widgets\FilamentInfoWidget; use He4rt\Tenant\Models\Tenant; +use He4rt\User\Filament\User\Pages\UserProfile; use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse; use Illuminate\Cookie\Middleware\EncryptCookies; use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken; @@ -31,9 +31,18 @@ public function panel(Panel $panel): Panel ->id('user') ->path('app') ->login() + ->sidebarCollapsibleOnDesktop() + ->viteTheme('resources/css/filament/admin/theme.css') ->tenant(Tenant::class, 'slug', 'ownedTenants') + ->userMenuItems([ + Action::make('settings') + ->label(__('Profile')) + ->url(fn () => UserProfile::getUrl()), + ]) + ->topbar(false) ->colors([ 'primary' => Color::Purple, + ...Color::all(), ]) ->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources') ->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages') @@ -41,10 +50,6 @@ public function panel(Panel $panel): Panel Dashboard::class, ]) ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets') - ->widgets([ - AccountWidget::class, - FilamentInfoWidget::class, - ]) ->middleware([ EncryptCookies::class, AddQueuedCookiesToResponse::class, From f247ca17f615f58395415cb2cde7ebdfcd5f209f Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:18:21 -0300 Subject: [PATCH 6/9] feat(filament): enhance theming and multi-tenancy support - Updated `theme.css` to include new sidebar styles. - Refined `ConnectionHub` to condition provider queries and OAuth redirects by tenant. - Added `UserProfile` page to Filament views. --- app/Livewire/ConnectionHub.php | 5 +++-- resources/css/filament/admin/theme.css | 6 +++++- resources/views/filament/pages/user-profile.blade.php | 3 +++ 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 resources/views/filament/pages/user-profile.blade.php diff --git a/app/Livewire/ConnectionHub.php b/app/Livewire/ConnectionHub.php index e171cbb90..8f42be6a5 100644 --- a/app/Livewire/ConnectionHub.php +++ b/app/Livewire/ConnectionHub.php @@ -14,7 +14,7 @@ class ConnectionHub extends Component #[Computed] public function userProviders() { - return auth()->user()->providers; + return auth()->user()->providers()->where('tenant_id', filament()->getTenant()->getKey())->get(); } public function render(): View @@ -27,7 +27,8 @@ public function render(): View public function connect(OAuthProviderEnum $provider) { - $redirectUri = $provider->getClient()->redirectUrl(); + session()->put('tenant', filament()->getTenant()->slug); + $redirectUri = $provider->getClient()->redirectUrl(filament()->getTenant()->slug); return redirect()->away($redirectUri); } diff --git a/resources/css/filament/admin/theme.css b/resources/css/filament/admin/theme.css index 506739a6e..7c8c417bf 100644 --- a/resources/css/filament/admin/theme.css +++ b/resources/css/filament/admin/theme.css @@ -1,4 +1,8 @@ @import '../../../../vendor/filament/filament/resources/css/theme.css'; @source '../../../../app/Filament/**/*'; -@source '../../../../resources/views/filament/**/*'; +@source '../../../../resources/views/**/*'; + +.fi-sidebar { + @apply bg-gray-100 dark:bg-gray-900; +} diff --git a/resources/views/filament/pages/user-profile.blade.php b/resources/views/filament/pages/user-profile.blade.php new file mode 100644 index 000000000..bb8ed159a --- /dev/null +++ b/resources/views/filament/pages/user-profile.blade.php @@ -0,0 +1,3 @@ + + {{-- Page content --}} + From 5f75cfe65732028b45ae6cec59e6e0e1ef7dd33f Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Sat, 8 Nov 2025 18:18:30 -0300 Subject: [PATCH 7/9] chore(composer): update dependencies, add `laravel-debugbar`, and adjust `.gitignore` --- .gitignore | 1 + composer.json | 5 +- composer.lock | 316 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 247 insertions(+), 75 deletions(-) diff --git a/.gitignore b/.gitignore index d97b02c58..60e987f5d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /public/js /public/fonts /storage/*.key +/storage/debugbar /vendor .env .env.testing diff --git a/composer.json b/composer.json index 4eb6bd09c..52fef3ac3 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "license": "MIT", "require": { "php": "^8.3", - "filament/filament": "^4.2.0", + "filament/filament": "^v4.1.10", "filament/spatie-laravel-media-library-plugin": "^4.2.0", "guzzlehttp/guzzle": "^7.10.0", "he4rt/authentication": ">=1", @@ -37,11 +37,12 @@ "spatie/laravel-medialibrary": "^11.17.3" }, "require-dev": { + "barryvdh/laravel-debugbar": "^3.16", "barryvdh/laravel-ide-helper": "^3.6.0", "driftingly/rector-laravel": "^2.1.3", "fakerphp/faker": "^1.24.1", "larastan/larastan": "^3.8.0", - "laravel/boost": "^1.7", + "laravel/boost": "^1.7.1", "laravel/pail": "^1.2.3", "laravel/pint": "^1.25.1", "laravel/sail": "^1.47.0", diff --git a/composer.lock b/composer.lock index f12ffa635..e7507e073 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "89b904aa8d5a27202bb9141b6dcfff0a", + "content-hash": "333ffb3a83e7bbf3fb61c5c4c76cbf06", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -512,16 +512,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.5.8", + "version": "1.5.9", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "719026bb30813accb68271fee7e39552a58e9f65" + "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/719026bb30813accb68271fee7e39552a58e9f65", - "reference": "719026bb30813accb68271fee7e39552a58e9f65", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/1905981ee626e6f852448b7aaa978f8666c5bc54", + "reference": "1905981ee626e6f852448b7aaa978f8666c5bc54", "shasum": "" }, "require": { @@ -568,7 +568,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.5.8" + "source": "https://github.com/composer/ca-bundle/tree/1.5.9" }, "funding": [ { @@ -580,7 +580,7 @@ "type": "github" } ], - "time": "2025-08-20T18:49:47+00:00" + "time": "2025-11-06T11:46:17+00:00" }, { "name": "composer/class-map-generator", @@ -3217,16 +3217,16 @@ }, { "name": "justinrainbow/json-schema", - "version": "6.6.0", + "version": "6.6.1", "source": { "type": "git", "url": "https://github.com/jsonrainbow/json-schema.git", - "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3" + "reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/68ba7677532803cc0c5900dd5a4d730537f2b2f3", - "reference": "68ba7677532803cc0c5900dd5a4d730537f2b2f3", + "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/fd8e5c6b1badb998844ad34ce0abcd71a0aeb396", + "reference": "fd8e5c6b1badb998844ad34ce0abcd71a0aeb396", "shasum": "" }, "require": { @@ -3286,9 +3286,9 @@ ], "support": { "issues": "https://github.com/jsonrainbow/json-schema/issues", - "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.0" + "source": "https://github.com/jsonrainbow/json-schema/tree/6.6.1" }, - "time": "2025-10-10T11:34:09+00:00" + "time": "2025-11-07T18:30:29+00:00" }, { "name": "kirschbaum-development/eloquent-power-joins", @@ -7618,16 +7618,16 @@ }, { "name": "symfony/console", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7" + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7", - "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7", + "url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", + "reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a", "shasum": "" }, "require": { @@ -7692,7 +7692,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.5" + "source": "https://github.com/symfony/console/tree/v7.3.6" }, "funding": [ { @@ -7712,20 +7712,20 @@ "type": "tidelift" } ], - "time": "2025-10-14T15:46:26+00:00" + "time": "2025-11-04T01:21:42+00:00" }, { "name": "symfony/css-selector", - "version": "v7.3.0", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" + "reference": "84321188c4754e64273b46b406081ad9b18e8614" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", - "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/84321188c4754e64273b46b406081ad9b18e8614", + "reference": "84321188c4754e64273b46b406081ad9b18e8614", "shasum": "" }, "require": { @@ -7761,7 +7761,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.3.0" + "source": "https://github.com/symfony/css-selector/tree/v7.3.6" }, "funding": [ { @@ -7772,12 +7772,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-10-29T17:24:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7848,16 +7852,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4" + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", - "reference": "99f81bc944ab8e5dae4f21b4ca9972698bbad0e4", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/bbe40bfab84323d99dab491b716ff142410a92a8", + "reference": "bbe40bfab84323d99dab491b716ff142410a92a8", "shasum": "" }, "require": { @@ -7905,7 +7909,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.3.4" + "source": "https://github.com/symfony/error-handler/tree/v7.3.6" }, "funding": [ { @@ -7925,7 +7929,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-31T19:12:50+00:00" }, { "name": "symfony/event-dispatcher", @@ -8089,16 +8093,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.3.2", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd" + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd", - "reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a", + "reference": "e9bcfd7837928ab656276fe00464092cc9e1826a", "shasum": "" }, "require": { @@ -8135,7 +8139,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.3.2" + "source": "https://github.com/symfony/filesystem/tree/v7.3.6" }, "funding": [ { @@ -8155,7 +8159,7 @@ "type": "tidelift" } ], - "time": "2025-07-07T08:17:47+00:00" + "time": "2025-11-05T09:52:27+00:00" }, { "name": "symfony/finder", @@ -8227,16 +8231,16 @@ }, { "name": "symfony/html-sanitizer", - "version": "v7.3.3", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/html-sanitizer.git", - "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12" + "reference": "3855e827adb1b675adcb98ad7f92681e293f2d77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/8740fc48979f649dee8b8fc51a2698e5c190bf12", - "reference": "8740fc48979f649dee8b8fc51a2698e5c190bf12", + "url": "https://api.github.com/repos/symfony/html-sanitizer/zipball/3855e827adb1b675adcb98ad7f92681e293f2d77", + "reference": "3855e827adb1b675adcb98ad7f92681e293f2d77", "shasum": "" }, "require": { @@ -8276,7 +8280,7 @@ "sanitizer" ], "support": { - "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.3" + "source": "https://github.com/symfony/html-sanitizer/tree/v7.3.6" }, "funding": [ { @@ -8296,20 +8300,20 @@ "type": "tidelift" } ], - "time": "2025-08-12T10:34:03+00:00" + "time": "2025-10-30T13:22:58+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897" + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce31218c7cac92eab280762c4375fb70a6f4f897", - "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6379e490d6ecfc5c4224ff3a754b90495ecd135c", + "reference": "6379e490d6ecfc5c4224ff3a754b90495ecd135c", "shasum": "" }, "require": { @@ -8359,7 +8363,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.6" }, "funding": [ { @@ -8379,20 +8383,20 @@ "type": "tidelift" } ], - "time": "2025-10-24T21:42:11+00:00" + "time": "2025-11-06T11:05:57+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.5", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab" + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/24fd3f123532e26025f49f1abefcc01a69ef15ab", - "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9a34dc0196677250e3609c2fac9de9e1551a262", + "reference": "f9a34dc0196677250e3609c2fac9de9e1551a262", "shasum": "" }, "require": { @@ -8477,7 +8481,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.6" }, "funding": [ { @@ -8497,7 +8501,7 @@ "type": "tidelift" } ], - "time": "2025-10-28T10:19:01+00:00" + "time": "2025-11-06T20:58:12+00:00" }, { "name": "symfony/mailer", @@ -9727,16 +9731,16 @@ }, { "name": "symfony/routing", - "version": "v7.3.4", + "version": "v7.3.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c" + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/8dc648e159e9bac02b703b9fbd937f19ba13d07c", - "reference": "8dc648e159e9bac02b703b9fbd937f19ba13d07c", + "url": "https://api.github.com/repos/symfony/routing/zipball/c97abe725f2a1a858deca629a6488c8fc20c3091", + "reference": "c97abe725f2a1a858deca629a6488c8fc20c3091", "shasum": "" }, "require": { @@ -9788,7 +9792,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.3.4" + "source": "https://github.com/symfony/routing/tree/v7.3.6" }, "funding": [ { @@ -9808,20 +9812,20 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-11-05T07:57:47+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", - "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", + "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", "shasum": "" }, "require": { @@ -9875,7 +9879,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" }, "funding": [ { @@ -9886,12 +9890,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-25T09:37:31+00:00" + "time": "2025-07-15T11:30:57+00:00" }, { "name": "symfony/string", @@ -10085,16 +10093,16 @@ }, { "name": "symfony/translation-contracts", - "version": "v3.6.0", + "version": "v3.6.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" + "reference": "65a8bc82080447fae78373aa10f8d13b38338977" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", - "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/65a8bc82080447fae78373aa10f8d13b38338977", + "reference": "65a8bc82080447fae78373aa10f8d13b38338977", "shasum": "" }, "require": { @@ -10143,7 +10151,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.1" }, "funding": [ { @@ -10154,12 +10162,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-27T08:32:26+00:00" + "time": "2025-07-15T13:41:35+00:00" }, { "name": "symfony/uid", @@ -10606,6 +10618,91 @@ } ], "packages-dev": [ + { + "name": "barryvdh/laravel-debugbar", + "version": "v3.16.0", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f265cf5e38577d42311f1a90d619bcd3740bea23", + "reference": "f265cf5e38577d42311f1a90d619bcd3740bea23", + "shasum": "" + }, + "require": { + "illuminate/routing": "^9|^10|^11|^12", + "illuminate/session": "^9|^10|^11|^12", + "illuminate/support": "^9|^10|^11|^12", + "php": "^8.1", + "php-debugbar/php-debugbar": "~2.2.0", + "symfony/finder": "^6|^7" + }, + "require-dev": { + "mockery/mockery": "^1.3.3", + "orchestra/testbench-dusk": "^7|^8|^9|^10", + "phpunit/phpunit": "^9.5.10|^10|^11", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" + }, + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.16-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "dev", + "laravel", + "profiler", + "webprofiler" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-debugbar/issues", + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.0" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2025-07-14T11:56:43+00:00" + }, { "name": "barryvdh/laravel-ide-helper", "version": "v3.6.0", @@ -12796,6 +12893,79 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-debugbar/php-debugbar", + "version": "v2.2.4", + "source": { + "type": "git", + "url": "https://github.com/php-debugbar/php-debugbar.git", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-debugbar/php-debugbar/zipball/3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "reference": "3146d04671f51f69ffec2a4207ac3bdcf13a9f35", + "shasum": "" + }, + "require": { + "php": "^8", + "psr/log": "^1|^2|^3", + "symfony/var-dumper": "^4|^5|^6|^7" + }, + "replace": { + "maximebf/debugbar": "self.version" + }, + "require-dev": { + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", + "twig/twig": "^1.38|^2.7|^3.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "DebugBar\\": "src/DebugBar/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + }, + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/php-debugbar/php-debugbar", + "keywords": [ + "debug", + "debug bar", + "debugbar", + "dev" + ], + "support": { + "issues": "https://github.com/php-debugbar/php-debugbar/issues", + "source": "https://github.com/php-debugbar/php-debugbar/tree/v2.2.4" + }, + "time": "2025-07-22T14:01:30+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", From aa378c314b513326a0f975d30f8c89403827cdda Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Mon, 10 Nov 2025 13:56:13 -0300 Subject: [PATCH 8/9] fix: oauth contract --- .../integrations/src/Discord/OAuth/DiscordOAuthClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php b/app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php index fcd124094..02b49d4cb 100644 --- a/app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php +++ b/app-modules/integrations/src/Discord/OAuth/DiscordOAuthClient.php @@ -11,7 +11,7 @@ class DiscordOAuthClient implements OAuthClientContract { - public function redirectUrl(): string + public function redirectUrl(?string $state = null): string { return sprintf( 'https://discord.com/oauth2/authorize?client_id=%s&response_type=code&redirect_uri=%s&scope=%s', From 29884f96c01ab774fec20f543e2679366793a447 Mon Sep 17 00:00:00 2001 From: danielhe4rt Date: Mon, 10 Nov 2025 13:56:48 -0300 Subject: [PATCH 9/9] wip --- .../src/Filament/User/Pages/UserProfile.php | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app-modules/user/src/Filament/User/Pages/UserProfile.php b/app-modules/user/src/Filament/User/Pages/UserProfile.php index de3023869..f45b13bc1 100644 --- a/app-modules/user/src/Filament/User/Pages/UserProfile.php +++ b/app-modules/user/src/Filament/User/Pages/UserProfile.php @@ -270,6 +270,15 @@ public function getMultiFactorAuthenticationContentComponent(): ?Component ->all()); } + protected function getLayoutData(): array + { + return [ + 'hasTopbar' => $this->hasTopbar(), + 'maxContentWidth' => $maxContentWidth = $this->getMaxWidth() ?? $this->getMaxContentWidth(), + 'maxWidth' => $maxContentWidth, + ]; + } + private function fillForm(): void { $data = $this->getUser()->attributesToArray(); @@ -338,7 +347,7 @@ private function sendEmailChangeVerification(Model $record, string $newEmail): v Notification::route('mail', $newEmail) ->notify($notification); - $this->getEmailChangeVerificationSentNotification($newEmail)?->send(); + $this->getEmailChangeVerificationSentNotification($newEmail)->send(); $this->data['email'] = $record->getAttributeValue('email'); } @@ -466,13 +475,4 @@ private function hasFullWidthFormActions(): bool { return false; } - - protected function getLayoutData(): array - { - return [ - 'hasTopbar' => $this->hasTopbar(), - 'maxContentWidth' => $maxContentWidth = $this->getMaxWidth() ?? $this->getMaxContentWidth(), - 'maxWidth' => $maxContentWidth, - ]; - } }