diff --git a/app/Enum/OrganisationStatus.php b/app/Enum/OrganisationStatus.php index 19b735d..c22cd9c 100644 --- a/app/Enum/OrganisationStatus.php +++ b/app/Enum/OrganisationStatus.php @@ -16,4 +16,10 @@ enum OrganisationStatus: string case active = 'active'; case inactive = 'inactive'; + case invited = 'invited'; + + protected function labelKeyPrefix(): ?string + { + return 'organisation.status'; + } } diff --git a/app/Filament/Resources/OrganisationResource.php b/app/Filament/Resources/OrganisationResource.php index abf9c2d..e8d19a6 100644 --- a/app/Filament/Resources/OrganisationResource.php +++ b/app/Filament/Resources/OrganisationResource.php @@ -6,6 +6,7 @@ use App\Enum\NGOType; use App\Enum\OrganisationAreaType; +use App\Enum\OrganisationStatus; use App\Enum\OrganisationType; use App\Filament\Forms\Components\Location; use App\Filament\Resources\OrganisationResource\Pages; @@ -379,21 +380,13 @@ public static function table(Table $table): Table ->sortable() ->toggleable(), - IconColumn::make('status') - ->options([ - 'heroicon-o-x-circle', - 'heroicon-o-x' => 'inactive', - 'heroicon-o-check' => 'active', - ]) + Tables\Columns\BadgeColumn::make('status') ->colors([ - 'secondary', - 'danger' => 'inactive', + 'secondary' => 'inactive', + 'warning' => 'guest', 'success' => 'active', ]) - ->label(__('organisation.field.status')) - ->searchable() - ->sortable() - ->toggleable(), + ->enum(OrganisationStatus::options()), TextColumn::make('county.name') ->label(__('organisation.field.hq')) diff --git a/app/Filament/Resources/OrganisationResource/Actions/ResendInvitationAction.php b/app/Filament/Resources/OrganisationResource/Actions/ResendInvitationAction.php new file mode 100644 index 0000000..69bb332 --- /dev/null +++ b/app/Filament/Resources/OrganisationResource/Actions/ResendInvitationAction.php @@ -0,0 +1,61 @@ +color('secondary'); + + $this->action(function (Organisation $record) { + $key = $this->getRateLimiterKey($record); + $maxAttempts = 1; + + if (RateLimiter::tooManyAttempts($key, $maxAttempts)) { + return $this->failure(); + } + + RateLimiter::increment($key, 3600); // 1h + + $record->users->first()->sendWelcomeNotification(); + $this->success(); + }); + + $this->requiresConfirmation(); + + $this->label(__('organisation.action.resend_invitation.button')); + + $this->modalHeading(__('organisation.action.resend_invitation.heading')); + $this->modalSubheading(__('organisation.action.resend_invitation.subheading')); + $this->modalButton(__('organisation.action.resend_invitation.button')); + + $this->successNotificationTitle(__('organisation.action.resend_invitation.success')); + + $this->failureNotification( + fn (Notification $notification) => $notification + ->danger() + ->title(__('organisation.action.resend_invitation.failure_title')) + ->body(__('organisation.action.resend_invitation.failure_body')) + ); + } + + private function getRateLimiterKey(Organisation $organisation): string + { + return 'resend-invitation:' . $organisation->id; + } +} diff --git a/app/Filament/Resources/OrganisationResource/Pages/ViewOrganisation.php b/app/Filament/Resources/OrganisationResource/Pages/ViewOrganisation.php index b858566..7520342 100644 --- a/app/Filament/Resources/OrganisationResource/Pages/ViewOrganisation.php +++ b/app/Filament/Resources/OrganisationResource/Pages/ViewOrganisation.php @@ -7,6 +7,7 @@ use App\Filament\Resources\OrganisationResource; use App\Filament\Resources\OrganisationResource\Actions\ActivateOrganisationAction; use App\Filament\Resources\OrganisationResource\Actions\DeactivateOrganisationAction; +use App\Filament\Resources\OrganisationResource\Actions\ResendInvitationAction; use App\Models\Organisation; use Filament\Pages\Actions\DeleteAction; use Filament\Pages\Actions\EditAction; @@ -28,6 +29,10 @@ protected function getActions(): array ->visible(fn (Organisation $record) => auth()->user()->isPlatformAdmin() && $record->isInactive()) ->record($this->getRecord()), + ResendInvitationAction::make() + ->visible(fn (Organisation $record) => auth()->user()->isPlatformAdmin() && $record->isInvited()) + ->record($this->getRecord()), + DeactivateOrganisationAction::make() ->visible(fn (Organisation $record) => auth()->user()->isPlatformAdmin() && $record->isActive()) ->record($this->getRecord()), diff --git a/app/Http/Livewire/Welcome.php b/app/Http/Livewire/Welcome.php index dd96d63..d21d341 100644 --- a/app/Http/Livewire/Welcome.php +++ b/app/Http/Livewire/Welcome.php @@ -71,6 +71,8 @@ public function handle(): ?LoginResponse data_get($this->form->getState(), 'password') ); + $this->user->activateOrganisation(); + Filament::auth()->login($this->user); return app(LoginResponse::class); diff --git a/app/Models/Organisation.php b/app/Models/Organisation.php index c3b4acd..d887453 100644 --- a/app/Models/Organisation.php +++ b/app/Models/Organisation.php @@ -182,6 +182,11 @@ public function isInactive(): bool return $this->status->is(OrganisationStatus::inactive); } + public function isInvited(): bool + { + return $this->status->is(OrganisationStatus::invited); + } + public function routeNotificationForMail(?Notification $notification = null): string { return data_get($this->contact_person, ['email'], $this->email); diff --git a/app/Models/User.php b/app/Models/User.php index a1f9951..b3f136d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -8,6 +8,7 @@ use App\Concerns\HasRole; use App\Concerns\LimitsVisibility; use App\Concerns\MustSetInitialPassword; +use App\Enum\OrganisationStatus; use App\Enum\UserRole; use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\HasName; @@ -83,4 +84,13 @@ public function getFilamentName(): string { return "{$this->first_name} {$this->last_name}"; } + + public function activateOrganisation(): void + { + $organisation = $this->organisation; + if ($this->isOrgAdmin($organisation) && $this->organisation->status === OrganisationStatus::invited) + { + $organisation->setActive(); + } + } } diff --git a/composer.json b/composer.json index c4df8f1..58ded64 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ "sentry/sentry-laravel": "^4.1" }, "require-dev": { - "barryvdh/laravel-debugbar": "^3.9", + "barryvdh/laravel-debugbar": "^3.13", "barryvdh/laravel-ide-helper": "^2.13", "fakerphp/faker": "^1.23", "friendsofphp/php-cs-fixer": "^3.39", diff --git a/composer.lock b/composer.lock index b3f69af..a09240d 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": "79a19b0d407e1357a655d548caaa5847", + "content-hash": "7a70662ab504734cb42fc3e00403f5de", "packages": [ { "name": "akaunting/laravel-money", @@ -2501,20 +2501,20 @@ }, { "name": "laravel/framework", - "version": "v10.40.0", + "version": "v10.48.4", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "7a9470071dac9579ebf29ad1b9d73e4b8eb586fc" + "reference": "7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/7a9470071dac9579ebf29ad1b9d73e4b8eb586fc", - "reference": "7a9470071dac9579ebf29ad1b9d73e4b8eb586fc", + "url": "https://api.github.com/repos/laravel/framework/zipball/7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72", + "reference": "7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", @@ -2558,6 +2558,8 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "mockery/mockery": "1.6.8", + "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, "provide": { @@ -2613,7 +2615,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^8.18", + "orchestra/testbench-core": "^8.23.4", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", @@ -2702,7 +2704,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-01-09T11:46:47+00:00" + "time": "2024-03-21T13:36:36+00:00" }, { "name": "laravel/prompts", @@ -9187,36 +9189,36 @@ }, { "name": "barryvdh/laravel-debugbar", - "version": "v3.9.2", + "version": "v3.13.3", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1" + "reference": "241e9bddb04ab42a04a5fe8b2b9654374c864229" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/bfd0131c146973cab164e50f5cdd8a67cc60cab1", - "reference": "bfd0131c146973cab164e50f5cdd8a67cc60cab1", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/241e9bddb04ab42a04a5fe8b2b9654374c864229", + "reference": "241e9bddb04ab42a04a5fe8b2b9654374c864229", "shasum": "" }, "require": { - "illuminate/routing": "^9|^10", - "illuminate/session": "^9|^10", - "illuminate/support": "^9|^10", - "maximebf/debugbar": "^1.18.2", + "illuminate/routing": "^9|^10|^11", + "illuminate/session": "^9|^10|^11", + "illuminate/support": "^9|^10|^11", + "maximebf/debugbar": "~1.22.0", "php": "^8.0", - "symfony/finder": "^6" + "symfony/finder": "^6|^7" }, "require-dev": { "mockery/mockery": "^1.3.3", - "orchestra/testbench-dusk": "^5|^6|^7|^8", - "phpunit/phpunit": "^8.5.30|^9.0", + "orchestra/testbench-dusk": "^5|^6|^7|^8|^9", + "phpunit/phpunit": "^9.6|^10.5", "squizlabs/php_codesniffer": "^3.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.8-dev" + "dev-master": "3.13-dev" }, "laravel": { "providers": [ @@ -9255,7 +9257,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.9.2" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.13.3" }, "funding": [ { @@ -9267,7 +9269,7 @@ "type": "github" } ], - "time": "2023-08-25T18:43:57+00:00" + "time": "2024-04-04T02:42:49+00:00" }, { "name": "barryvdh/laravel-ide-helper", @@ -11244,25 +11246,27 @@ }, { "name": "maximebf/debugbar", - "version": "v1.19.1", + "version": "v1.22.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523" + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/03dd40a1826f4d585ef93ef83afa2a9874a00523", - "reference": "03dd40a1826f4d585ef93ef83afa2a9874a00523", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", + "reference": "7aa9a27a0b1158ed5ad4e7175e8d3aee9a818b96", "shasum": "" }, "require": { - "php": "^7.1|^8", + "php": "^7.2|^8", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^4|^5|^6" + "symfony/var-dumper": "^4|^5|^6|^7" }, "require-dev": { - "phpunit/phpunit": ">=7.5.20 <10.0", + "dbrekelmans/bdi": "^1", + "phpunit/phpunit": "^8|^9", + "symfony/panther": "^1|^2.1", "twig/twig": "^1.38|^2.7|^3.0" }, "suggest": { @@ -11273,7 +11277,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.18-dev" + "dev-master": "1.22-dev" } }, "autoload": { @@ -11304,9 +11308,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.19.1" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.22.3" }, - "time": "2023-10-12T08:10:52+00:00" + "time": "2024-04-03T19:39:26+00:00" }, { "name": "mockery/mockery", @@ -13799,5 +13803,5 @@ "php": "^8.1" }, "platform-dev": [], - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2024_04_05_085356_change_status_column_from_orgaisations.php b/database/migrations/2024_04_05_085356_change_status_column_from_orgaisations.php new file mode 100644 index 0000000..175dfc9 --- /dev/null +++ b/database/migrations/2024_04_05_085356_change_status_column_from_orgaisations.php @@ -0,0 +1,35 @@ +string('status') + ->default(OrganisationStatus::invited->value) + ->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('organisations', function (Blueprint $table) { + $table->string('status') + ->default(OrganisationStatus::inactive->value) + ->change(); + }); + } +}; diff --git a/lang/ro/organisation.php b/lang/ro/organisation.php index ec33f04..7973438 100644 --- a/lang/ro/organisation.php +++ b/lang/ro/organisation.php @@ -127,5 +127,19 @@ 'success' => 'Organizația a fost dezactivată cu succes.', ], ], + 'resend_invitation' => [ + 'button' => 'Retrimite invitație', + 'heading' => 'Retrimite invitație', + 'subheading' => 'Ești sigur că vrei să retrimit invitația?', + 'success' => 'Invitația a fost retimisă.', + 'failure_title' => 'Invitația nu a putut fi trimisă.', + 'failure_body' => 'Acestei organizații i-a fost retrimisă invitația recent. Te rugăm să mai încerci peste o oră.', + ], + ], + + 'status' => [ + 'active' => 'Activ', + 'inactive' => 'Inactiv', + 'invited' => 'Invitat', ], ];