diff --git a/database/migrations/create_user_notification_preferences_table.php.stub b/database/migrations/create_user_notification_preferences_table.php.stub new file mode 100644 index 0000000..3b04ae4 --- /dev/null +++ b/database/migrations/create_user_notification_preferences_table.php.stub @@ -0,0 +1,31 @@ +id(); + $table->unsignedBigInteger('user_id'); + $table->string('navigation_type'); + $table->timestamps(); + + $table->unique(['user_id', 'navigation_type']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_notification_preferences'); + } +}; diff --git a/src/Concerns/Relations/HasNotificationPreferences.php b/src/Concerns/Relations/HasNotificationPreferences.php new file mode 100644 index 0000000..059a73c --- /dev/null +++ b/src/Concerns/Relations/HasNotificationPreferences.php @@ -0,0 +1,14 @@ +hasMany(UserNotificationPreference::class, 'user_id'); + } +} diff --git a/src/Concerns/Relations/HasRelations.php b/src/Concerns/Relations/HasRelations.php index 81962b7..3a71f08 100644 --- a/src/Concerns/Relations/HasRelations.php +++ b/src/Concerns/Relations/HasRelations.php @@ -7,4 +7,5 @@ trait HasRelations use HasLoginsRelation; use HasTags; use HasTraffic; + use HasNotificationPreferences; } diff --git a/src/Enums/NotificationType.php b/src/Enums/NotificationType.php new file mode 100644 index 0000000..e0be33a --- /dev/null +++ b/src/Enums/NotificationType.php @@ -0,0 +1,19 @@ + __('Email'), + self::SMS => __('SMS'), + self::InApp => __('In App'), + }; + } +} diff --git a/src/Models/UserNotificationPreference.php b/src/Models/UserNotificationPreference.php new file mode 100644 index 0000000..f8dc20c --- /dev/null +++ b/src/Models/UserNotificationPreference.php @@ -0,0 +1,24 @@ + NotificationType::class, + ]; + + public function user(): BelongsTo + { + return $this->belongsTo(config('backstage.users.eloquent.users.model', User::class), 'user_id'); + } +} diff --git a/src/Pages/Auth/Profile.php b/src/Pages/Auth/Profile.php new file mode 100644 index 0000000..3265074 --- /dev/null +++ b/src/Pages/Auth/Profile.php @@ -0,0 +1,82 @@ + $this->form( + $this->makeForm() + ->schema([ + $this->getNameFormComponent(), + $this->getEmailFormComponent(), + static::getNotificationFormComponent(), + $this->getPasswordFormComponent(), + $this->getPasswordConfirmationFormComponent(), + ]) + ->operation('edit') + ->model($this->getUser()) + ->statePath('data') + ->inlineLabel(! static::isSimple()), + ), + ]; + } + + public static function getNotificationFormComponent(): Select + { + $types = NotificationType::cases(); + + $options = []; + + foreach ($types as $type) { + $options[$type->value] = $type->label(); + } + + return Select::make('notification_preferences') + ->label(__('Notification preferences')) + ->options(fn() => $options) + ->live() + ->placeholder(fn() => ('Select notification preferences')) + ->searchingMessage(__('Searching notification types...')) + ->searchPrompt(__('Search notification types...')) + ->saveRelationshipsUsing(function (User $record, array $state) { + $state = collect($state)->map(fn($value) => NotificationType::from($value)); + + $state->each(function (NotificationType $type) use ($record) { + if (!$record->notificationPreferences->contains('navigation_type', $type->value)) { + + $record->notificationPreferences()->create([ + 'navigation_type' => $type->value, + ]); + } + }); + + $record->notificationPreferences()->whereNotIn('navigation_type', $state)->delete(); + }) + ->multiple(); + } + + protected function mutateFormDataBeforeFill(array $data): array + { + $data = parent::mutateFormDataBeforeFill($data); + + $user = $this->getUser(); + + if ($user->notificationPreferences->isNotEmpty()) { + $data['notification_preferences'] = $user->notificationPreferences->pluck('navigation_type')->map(fn(NotificationType $record) => $record->value)->toArray(); + } + + return $data; + } +} diff --git a/src/UsersPlugin.php b/src/UsersPlugin.php index e3c4b13..ec5baf0 100644 --- a/src/UsersPlugin.php +++ b/src/UsersPlugin.php @@ -5,14 +5,23 @@ use Backstage\Users\Components\ToggleSubNavigationType; use Backstage\Users\Http\Middleware\DetectUserTraffic; use Backstage\Users\Http\Middleware\RedirectUnverifiedUsers; +use Backstage\Users\Pages\Auth\Profile; +use Closure; use Filament\Contracts\Plugin; use Filament\Navigation\MenuItem; use Filament\Panel; +use Filament\Support\Concerns\EvaluatesClosures; use Filament\View\PanelsRenderHook; use Livewire\Livewire; class UsersPlugin implements Plugin { + use EvaluatesClosures; + + public bool | Closure $profile = true; + + public bool | Closure $isSimple = true; + public function getId(): string { return 'users'; @@ -63,10 +72,14 @@ public function register(Panel $panel): void $panel->userMenuItems([ MenuItem::make('api_tokens') ->label(__('API Tokens')) - ->visible(fn () => config('backstage.users.pages.manage-api-tokens', Pages\ManageApiTokens::class)::canAccess()) + ->visible(fn() => config('backstage.users.pages.manage-api-tokens', Pages\ManageApiTokens::class)::canAccess()) ->icon('heroicon-o-document-text') - ->url(fn () => config('backstage.users.pages.manage-api-tokens', Pages\ManageApiTokens::class)::getUrl()), + ->url(fn() => config('backstage.users.pages.manage-api-tokens', Pages\ManageApiTokens::class)::getUrl()), ]); + + if ($this->isProfileEnabled()) { + $panel->profile(page: Profile::class, isSimple: $this->isSimpleProfile()); + } } public function boot(Panel $panel): void @@ -93,4 +106,23 @@ protected function initSubNavigationToggle(Panel $panel) return Livewire::mount(ToggleSubNavigationType::class, []); }); } + + public function profile(bool | Closure $uses = true, bool | Closure $isSimple = true): self + { + $this->profile = $uses; + + $this->isSimple = $isSimple; + + return $this; + } + + public function isProfileEnabled(): bool + { + return $this->evaluate($this->profile); + } + + public function isSimpleProfile(): bool + { + return $this->evaluate($this->isSimple); + } } diff --git a/src/UsersServiceProvider.php b/src/UsersServiceProvider.php index 358fa1b..fbd2ffe 100644 --- a/src/UsersServiceProvider.php +++ b/src/UsersServiceProvider.php @@ -214,6 +214,7 @@ protected function getMigrations(): array 'create_users_tags_table', 'user_password_nullable', 'add_sub_navigation_preference_to_users_table', + 'create_user_notification_preferences_table' ]; } }