diff --git a/app/Concerns/MustSetInitialPassword.php b/app/Concerns/MustSetInitialPassword.php new file mode 100644 index 00000000..4db9a342 --- /dev/null +++ b/app/Concerns/MustSetInitialPassword.php @@ -0,0 +1,51 @@ +password) { + $user->password = Hash::make(Str::random(128)); + } + }); + + static::created(function (self $user) { + if (! app()->runningInConsole()) { + $user->sendWelcomeNotification(); + } + }); + } + + public function hasSetPassword(): bool + { + return ! \is_null($this->password_set_at); + } + + public function markPasswordAsSet(): bool + { + return $this->forceFill([ + 'password_set_at' => $this->freshTimestamp(), + ])->save(); + } + + public function sendWelcomeNotification(): void + { + if ($this->role===UserRole::ngo_admin) + { + $this->notify(new WelcomeNotification()); + return; + } + $this->notify(new AdminWelcomeNotification()); + } +} diff --git a/app/Enums/UserRole.php b/app/Enums/UserRole.php index f2ba12d2..878d3712 100644 --- a/app/Enums/UserRole.php +++ b/app/Enums/UserRole.php @@ -4,10 +4,19 @@ namespace App\Enums; +use App\Concerns\ArrayableEnum; + enum UserRole: string { + use ArrayableEnum; case donor = 'donor'; case ngo_admin = 'ngo-admin'; case bb_manager = 'bb-manager'; case bb_admin = 'bb-admin'; + + public function translationKeyPrefix(): string + { + return 'user.roles'; + } + } diff --git a/app/Filament/Resources/UserResource.php b/app/Filament/Resources/UserResource.php index 65cfc574..af523cb7 100644 --- a/app/Filament/Resources/UserResource.php +++ b/app/Filament/Resources/UserResource.php @@ -4,12 +4,16 @@ namespace App\Filament\Resources; +use App\Enums\UserRole; use App\Filament\Resources\UserResource\Pages; use App\Models\User; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\TextInput; use Filament\Resources\Form; use Filament\Resources\Resource; use Filament\Resources\Table; use Filament\Tables; +use Filament\Tables\Columns\TextColumn; class UserResource extends Resource { @@ -23,17 +27,15 @@ class UserResource extends Resource public static function form(Form $form): Form { - return $form - ->schema([ - // - ]); + return $form; } public static function table(Table $table): Table { return $table ->columns([ - // + TextColumn::make('name')->searchable()->sortable(), + TextColumn::make('email')->searchable()->sortable(), ]) ->filters([ // diff --git a/app/Filament/Resources/UserResource/Pages/CreateUser.php b/app/Filament/Resources/UserResource/Pages/CreateUser.php index 72b81bb0..4b834538 100644 --- a/app/Filament/Resources/UserResource/Pages/CreateUser.php +++ b/app/Filament/Resources/UserResource/Pages/CreateUser.php @@ -4,10 +4,51 @@ namespace App\Filament\Resources\UserResource\Pages; +use App\Enums\UserRole; use App\Filament\Resources\UserResource; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\TextInput; +use Filament\Resources\Form; use Filament\Resources\Pages\CreateRecord; class CreateUser extends CreateRecord { protected static string $resource = UserResource::class; + protected static bool $canCreateAnother = false; + + public function form(Form $form): Form + { + return $form + ->schema([ + TextInput::make('name') + ->label(__('user.name')) + ->required(), + TextInput::make('email') + ->label(__('user.email')) + ->email() + ->unique('users', 'email') + ->required(), + Select::make('role') + ->label(__('user.role')) + ->options(collect( + UserRole::options())->only([ + UserRole::bb_admin->value, + UserRole::bb_manager->value, + UserRole::ngo_admin->value + ] + )->toArray() + )->reactive() + ->required(), + Select::make('organization') + ->label(__('user.organization')) + ->relationship('organization', 'name') + ->hidden(function (callable $get) { + return $get('role') !== UserRole::ngo_admin->value; + }) + ->searchable() + ->preload() + ->required(), + + ]); + } } diff --git a/app/Filament/Resources/UserResource/Pages/EditUser.php b/app/Filament/Resources/UserResource/Pages/EditUser.php index 2e14c622..3d151732 100644 --- a/app/Filament/Resources/UserResource/Pages/EditUser.php +++ b/app/Filament/Resources/UserResource/Pages/EditUser.php @@ -4,8 +4,12 @@ namespace App\Filament\Resources\UserResource\Pages; +use App\Enums\UserRole; use App\Filament\Resources\UserResource; +use Filament\Forms\Components\Select; +use Filament\Forms\Components\TextInput; use Filament\Pages\Actions; +use Filament\Resources\Form; use Filament\Resources\Pages\EditRecord; class EditUser extends EditRecord @@ -18,4 +22,40 @@ protected function getActions(): array Actions\DeleteAction::make(), ]; } + public function form(Form $form): Form + { + return $form + ->schema([ + TextInput::make('name') + ->label(__('user.name')) + ->required(), + TextInput::make('email') + ->label(__('user.email')) + ->email() + ->unique('users', 'email') + ->required(), + Select::make('role') + ->label(__('user.role')) + ->options(collect( + UserRole::options())->only([ + UserRole::bb_admin->value, + UserRole::bb_manager->value, + UserRole::ngo_admin->value + ] + )->toArray() + )->reactive() + ->required(), + Select::make('organization') + ->label(__('user.organization')) + ->relationship('organization', 'name') + ->hidden(function (callable $get) { + return $get('role') !== UserRole::ngo_admin->value; + }) + ->searchable() + ->preload() + ->required(), + + ]); + } + } diff --git a/app/Filament/Resources/UsersResource/Pages/CreateUsers.php b/app/Filament/Resources/UsersResource/Pages/CreateUsers.php deleted file mode 100644 index 78288dc9..00000000 --- a/app/Filament/Resources/UsersResource/Pages/CreateUsers.php +++ /dev/null @@ -1,13 +0,0 @@ -hasValidSignature()) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.invalid_signature')); + } + + if (\is_null($user)) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.no_user')); + } + + if ($user->hasSetPassword()) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.already_used')); + } + return Inertia::render('Auth/SetInitialPassword', [ + 'user' => $user, + 'token' => sha1($user->email), + ]); + } + public function storeInitialPassword(Request $request, User $user): RedirectResponse + { + if ($request->token !== sha1($user->email)) { + abort(401); + } + $validated = $request->validate([ + 'password' => ['required', Password::defaults(), 'confirmed'], + ]); + + $user->update([ + 'password' => Hash::make($validated['password']), + ]); + $user->markPasswordAsSet(); + + return redirect()->route('login')->with('success_message', __('user.messages.set_initial_password_success')); + } } diff --git a/app/Http/Livewire/Welcome.php b/app/Http/Livewire/Welcome.php new file mode 100644 index 00000000..09d6f140 --- /dev/null +++ b/app/Http/Livewire/Welcome.php @@ -0,0 +1,112 @@ +check()) { + redirect()->intended(Filament::getUrl()); + } + + if (! $request->hasValidSignature()) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.invalid_signature')); + } + + $this->user = User::find($user)->first(); + + if (\is_null($this->user)) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.no_user')); + } + + if ($this->user->hasSetPassword()) { + abort(Response::HTTP_FORBIDDEN, __('auth.welcome.already_used')); + } + + $this->form->fill([ + 'email' => $this->user?->email, + ]); + } + + public function handle(): ?LoginResponse + { + try { + $this->rateLimit(5); + } catch (TooManyRequestsException $exception) { + throw ValidationException::withMessages([ + 'email' => __('filament::login.messages.throttled', [ + 'seconds' => $exception->secondsUntilAvailable, + 'minutes' => ceil($exception->secondsUntilAvailable / 60), + ]), + ]); + } + + $this->user->update([ + 'password' => Hash::make(data_get($this->form->getState(), 'password')), + ]); + + $this->user->markPasswordAsSet(); + + Filament::auth()->login($this->user); + + return app(LoginResponse::class); + } + + protected function getFormSchema(): array + { + return [ + TextInput::make('email') + ->label(__('filament::login.fields.email.label')) + ->email() + ->disabled(), + + TextInput::make('password') + ->label(__('filament::login.fields.password.label')) + ->password() + ->required() + ->confirmed(), + + TextInput::make('password_confirmation') + ->label(__('filament::login.fields.password.label')) + ->password() + ->required(), + ]; + } + + public function render(): View + { + return view('auth.welcome') + ->layout('filament::components.layouts.card', [ + 'title' => __('filament::login.title'), + ]); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 46b6d26f..b5252622 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,6 +5,7 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Concerns\MustSetInitialPassword; use App\Traits\HasRole; use Filament\Models\Contracts\FilamentUser; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -20,6 +21,7 @@ class User extends Authenticatable implements FilamentUser use HasFactory; use Notifiable; use HasRole; + use MustSetInitialPassword; /** * The attributes that are mass assignable. diff --git a/app/Notifications/Admin/WelcomeNotification.php b/app/Notifications/Admin/WelcomeNotification.php new file mode 100644 index 00000000..9b9c8685 --- /dev/null +++ b/app/Notifications/Admin/WelcomeNotification.php @@ -0,0 +1,44 @@ + + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject(__('auth.welcome.subject', ['app' => config('app.name')])) + ->greeting(__('auth.welcome.greeting', [ + 'name' => $notifiable->name, + ])) + ->line(__('auth.welcome.intro', [ + 'app' => config('app.name'), + ])) + ->action(__('auth.welcome.submit'), URL::signedRoute( + 'filament.auth.welcome', + ['user' => $notifiable->id] + )); + } +} diff --git a/app/Notifications/Ngo/WelcomeNotification.php b/app/Notifications/Ngo/WelcomeNotification.php new file mode 100644 index 00000000..64f52113 --- /dev/null +++ b/app/Notifications/Ngo/WelcomeNotification.php @@ -0,0 +1,44 @@ + + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + */ + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject(__('auth.welcome.subject', ['app' => config('app.name')])) + ->greeting(__('auth.welcome.greeting', [ + 'name' => $notifiable->name, + ])) + ->line(__('auth.welcome.intro', [ + 'app' => config('app.name'), + ])) + ->action(__('auth.welcome.submit'), URL::signedRoute( + 'ngo.user.welcome', + ['user' => $notifiable->id] + )); + } +} diff --git a/composer.json b/composer.json index 0d579ec2..389d56b3 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "filament/spatie-laravel-translatable-plugin": "^2.17", "guzzlehttp/guzzle": "^7.8", "inertiajs/inertia-laravel": "^0.6", + "jeffgreco13/filament-breezy": "^1.5", "laravel/framework": "^10.20", "laravel/sanctum": "^3.2", "laravel/tinker": "^2.8", diff --git a/composer.lock b/composer.lock index 93048a97..d74f6a7f 100644 --- a/composer.lock +++ b/composer.lock @@ -303,6 +303,60 @@ }, "time": "2023-08-25T18:07:43+00:00" }, + { + "name": "bacon/bacon-qr-code", + "version": "2.0.8", + "source": { + "type": "git", + "url": "https://github.com/Bacon/BaconQrCode.git", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Bacon/BaconQrCode/zipball/8674e51bb65af933a5ffaf1c308a660387c35c22", + "reference": "8674e51bb65af933a5ffaf1c308a660387c35c22", + "shasum": "" + }, + "require": { + "dasprid/enum": "^1.0.3", + "ext-iconv": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phly/keep-a-changelog": "^2.1", + "phpunit/phpunit": "^7 | ^8 | ^9", + "spatie/phpunit-snapshot-assertions": "^4.2.9", + "squizlabs/php_codesniffer": "^3.4" + }, + "suggest": { + "ext-imagick": "to generate QR code images" + }, + "type": "library", + "autoload": { + "psr-4": { + "BaconQrCode\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "BaconQrCode is a QR code generator for PHP.", + "homepage": "https://github.com/Bacon/BaconQrCode", + "support": { + "issues": "https://github.com/Bacon/BaconQrCode/issues", + "source": "https://github.com/Bacon/BaconQrCode/tree/2.0.8" + }, + "time": "2022-12-07T17:46:57+00:00" + }, { "name": "blade-ui-kit/blade-heroicons", "version": "1.4.0", @@ -688,6 +742,56 @@ ], "time": "2023-03-12T12:17:29+00:00" }, + { + "name": "dasprid/enum", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/DASPRiD/Enum.git", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/6faf451159fb8ba4126b925ed2d78acfce0dc016", + "reference": "6faf451159fb8ba4126b925ed2d78acfce0dc016", + "shasum": "" + }, + "require": { + "php": ">=7.1 <9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7 | ^8 | ^9", + "squizlabs/php_codesniffer": "*" + }, + "type": "library", + "autoload": { + "psr-4": { + "DASPRiD\\Enum\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Ben Scholzen 'DASPRiD'", + "email": "mail@dasprids.de", + "homepage": "https://dasprids.de/", + "role": "Developer" + } + ], + "description": "PHP 7.1 enum implementation", + "keywords": [ + "enum", + "map" + ], + "support": { + "issues": "https://github.com/DASPRiD/Enum/issues", + "source": "https://github.com/DASPRiD/Enum/tree/1.0.5" + }, + "time": "2023-08-25T16:18:39+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -2116,6 +2220,86 @@ ], "time": "2022-05-21T17:30:32+00:00" }, + { + "name": "jeffgreco13/filament-breezy", + "version": "v1.5.9", + "source": { + "type": "git", + "url": "https://github.com/jeffgreco13/filament-breezy.git", + "reference": "20a853f55b5170a65ee1a61682141a7918ee594c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jeffgreco13/filament-breezy/zipball/20a853f55b5170a65ee1a61682141a7918ee594c", + "reference": "20a853f55b5170a65ee1a61682141a7918ee594c", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "^2.0", + "filament/filament": "^2.15", + "php": "^8.0|^8.1", + "pragmarx/google2fa": "^7.0|^8.0", + "spatie/laravel-package-tools": "^1.9.2" + }, + "require-dev": { + "nunomaduro/collision": "^5.0|^6.0", + "nunomaduro/larastan": "^1.0", + "orchestra/testbench": "^6.0|^7.0|^8.0", + "pestphp/pest": "^1.21", + "pestphp/pest-plugin-laravel": "^1.1", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5", + "spatie/laravel-ray": "^1.26" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "JeffGreco13\\FilamentBreezy\\FilamentBreezyServiceProvider" + ], + "aliases": { + "FilamentBreezy": "JeffGreco13\\FilamentBreezy\\Facades\\FilamentBreezy" + } + } + }, + "autoload": { + "psr-4": { + "JeffGreco13\\FilamentBreezy\\": "src", + "JeffGreco13\\FilamentBreezy\\Database\\Factories\\": "database/factories" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeff Greco", + "email": "jeff@jeffpgreco.com", + "role": "Developer" + } + ], + "description": "A custom package for Filament with login flow, profile and teams support.", + "homepage": "https://github.com/jeffgreco13/filament-breezy", + "keywords": [ + "filament-breezy", + "jeffgreco13", + "laravel" + ], + "support": { + "issues": "https://github.com/jeffgreco13/filament-breezy/issues", + "source": "https://github.com/jeffgreco13/filament-breezy/tree/v1.5.9" + }, + "funding": [ + { + "url": "https://github.com/jeffgreco13", + "type": "github" + } + ], + "time": "2023-07-22T16:01:05+00:00" + }, { "name": "laravel/framework", "version": "v10.20.0", @@ -3936,6 +4120,73 @@ ], "time": "2023-02-08T01:06:31+00:00" }, + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, { "name": "phpoption/phpoption", "version": "1.9.1", @@ -4011,6 +4262,106 @@ ], "time": "2023-02-25T19:38:58+00:00" }, + { + "name": "pragmarx/google2fa", + "version": "v8.0.1", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa.git", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "reference": "80c3d801b31fe165f8fe99ea085e0a37834e1be3", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1.0|^2.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "phpunit/phpunit": "^7.5.15|^8.5|^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "PragmaRX\\Google2FA\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "email": "acr@antoniocarlosribeiro.com", + "role": "Creator & Designer" + } + ], + "description": "A One Time Password Authentication package, compatible with Google Authenticator.", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa" + ], + "support": { + "issues": "https://github.com/antonioribeiro/google2fa/issues", + "source": "https://github.com/antonioribeiro/google2fa/tree/v8.0.1" + }, + "time": "2022-06-13T21:57:56+00:00" + }, + { + "name": "psr/clock", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/clock.git", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/clock/zipball/e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "reference": "e41a24703d4560fd0acb709162f73b8adfc3aa0d", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Clock\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for reading the clock.", + "homepage": "https://github.com/php-fig/clock", + "keywords": [ + "clock", + "now", + "psr", + "psr-20", + "time" + ], + "support": { + "issues": "https://github.com/php-fig/clock/issues", + "source": "https://github.com/php-fig/clock/tree/1.0.0" + }, + "time": "2022-11-25T14:36:26+00:00" + }, { "name": "psr/clock", "version": "1.0.0", @@ -5411,6 +5762,69 @@ ], "time": "2023-07-19T19:21:38+00:00" }, + { + "name": "spatie/shiki-php", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/shiki-php.git", + "reference": "34fe61405b405c735c82a9c56feffd3f7c5544ff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/shiki-php/zipball/34fe61405b405c735c82a9c56feffd3f7c5544ff", + "reference": "34fe61405b405c735c82a9c56feffd3f7c5544ff", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^v3.0", + "pestphp/pest": "^1.8", + "phpunit/phpunit": "^9.5", + "spatie/pest-plugin-snapshots": "^1.1", + "spatie/ray": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\ShikiPhp\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Rias Van der Veken", + "email": "rias@spatie.be", + "role": "Developer" + }, + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "role": "Developer" + } + ], + "description": "Highlight code using Shiki in PHP", + "homepage": "https://github.com/spatie/shiki-php", + "keywords": [ + "shiki", + "spatie" + ], + "support": { + "source": "https://github.com/spatie/shiki-php/tree/1.3.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-07-19T19:21:38+00:00" + }, { "name": "spatie/shiki-php", "version": "1.3.0", diff --git a/database/migrations/2023_05_05_142245_update_users.php b/database/migrations/2023_05_05_142245_update_users.php index 4e1f2f10..2dfd4485 100644 --- a/database/migrations/2023_05_05_142245_update_users.php +++ b/database/migrations/2023_05_05_142245_update_users.php @@ -18,6 +18,7 @@ public function up(): void $table->enum('role', ['donor', 'ngo-admin', 'bb-manager', 'bb-admin']); $table->string('phone')->nullable(); $table->string('source_of_information')->nullable(); + $table->timestamp('password_set_at')->nullable(); $table->foreignIdFor(Organization::class)->nullable()->constrained(); }); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 89cb400d..6711e2a5 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -78,9 +78,9 @@ public function run(): void ->count(10) ->hasVolunteers(10) ) - ->has( - Ticket::factory() - ) +// ->has( +//// Ticket::factory() +// ) ->create(); Badge::factory() diff --git a/lang/ro/auth.php b/lang/ro/auth.php new file mode 100644 index 00000000..10b506df --- /dev/null +++ b/lang/ro/auth.php @@ -0,0 +1,12 @@ +[ + 'greeting'=>'Bun venit, :name!', + 'subject'=>'Bun venit pe :app', + 'intro'=>'Bine ai venit pe platforma :app. Pentru a-ți seta parola, te rugăm să apeși pe butonul de mai jos.', + 'submit' =>'Setează parola', + 'invalid_signature'=>'Link-ul de setare a parolei este invalid.', + 'no_user'=>'Nu există un utilizator cu acest email.', + 'already_used'=>'Parola a fost deja setată.', + ] +]; diff --git a/lang/ro/user.php b/lang/ro/user.php new file mode 100644 index 00000000..afa0080e --- /dev/null +++ b/lang/ro/user.php @@ -0,0 +1,16 @@ +[ + 'donor'=>'Donator', + 'ngo-admin'=>'Administrator ONG', + 'bb-manager'=>'Manager Bursa Binelui', + 'bb-admin'=>'Administrator Bursa Binelui', + ], + 'name'=>'Nume', + 'email'=>'Email', + 'role'=>'Rol', + 'organization'=>'Organizație', + 'messages'=>[ + 'set_initial_password_success' => 'Parola a fost setată cu succes!', + ] +]; diff --git a/resources/js/Pages/Auth/SetInitialPassword.vue b/resources/js/Pages/Auth/SetInitialPassword.vue new file mode 100644 index 00000000..9ad4c5d8 --- /dev/null +++ b/resources/js/Pages/Auth/SetInitialPassword.vue @@ -0,0 +1,94 @@ + + + diff --git a/resources/js/locales/ro.js b/resources/js/locales/ro.js index 1d127be6..198ea0fa 100644 --- a/resources/js/locales/ro.js +++ b/resources/js/locales/ro.js @@ -20,6 +20,7 @@ export default { "log_in": "Intră în cont", "google_log_in": "Continuă cu Google", "password_reset": "Resetează parola", + 'set_password': 'Setează parola', "no_account": "Nu ai cont pe Bursa Binelui?", "header_confirm_password": "Confirm Password", "confirm_password_info": "This is a secure area of the application. Please confirm your password before continuing.", diff --git a/resources/views/auth/welcome.blade.php b/resources/views/auth/welcome.blade.php new file mode 100644 index 00000000..1ed1765a --- /dev/null +++ b/resources/views/auth/welcome.blade.php @@ -0,0 +1,14 @@ +
+ {{ $this->form }} + + + {{ __('auth.welcome.submit') }} + +
diff --git a/resources/views/vendor/filament-breezy/.gitkeep b/resources/views/vendor/filament-breezy/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/resources/views/vendor/filament-breezy/components/auth-card.blade.php b/resources/views/vendor/filament-breezy/components/auth-card.blade.php new file mode 100644 index 00000000..1d122eaf --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/auth-card.blade.php @@ -0,0 +1,22 @@ +@props(['action']) +
config('filament.dark_mode'), +])> + +
+
config('filament.dark_mode'), + ])> + {{ $slot }} +
+ + {{ $this->modal }} + +
+ + @livewire('notifications') + +
diff --git a/resources/views/vendor/filament-breezy/components/grid-section.blade.php b/resources/views/vendor/filament-breezy/components/grid-section.blade.php new file mode 100644 index 00000000..fd289175 --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/grid-section.blade.php @@ -0,0 +1,16 @@ +@props(['title','description']) +
class(["grid grid-cols-2 gap-6 filament-breezy-grid-section"])}}> + +
+
+

config('filament.dark_mode')])>{{$title}}

+ +

config('filament.dark_mode')])> + {{$description}} +

+
+
+ + {{$slot}} + +
diff --git a/resources/views/vendor/filament-breezy/components/sections/2fa.blade.php b/resources/views/vendor/filament-breezy/components/sections/2fa.blade.php new file mode 100644 index 00000000..7f20e437 --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/sections/2fa.blade.php @@ -0,0 +1,77 @@ + + + + {{ __('filament-breezy::default.profile.2fa.title') }} + + + + {{ __('filament-breezy::default.profile.2fa.description') }} + + + + + @if($this->user->has_enabled_two_factor) + + @if ($this->user->has_confirmed_two_factor) +

{{ __('filament-breezy::default.profile.2fa.enabled.title') }}

+ {{ __('filament-breezy::default.profile.2fa.enabled.description') ?? __('filament-breezy::default.profile.2fa.enabled.store_codes') }} + @else +

{{ __('filament-breezy::default.profile.2fa.finish_enabling.title') }}

+ {{ __('filament-breezy::default.profile.2fa.finish_enabling.description') }} + @endif + +
+ {!! $this->twoFactorQrCode() !!} +

{{ __('filament-breezy::default.profile.2fa.setup_key') }} {{ decrypt($this->user->two_factor_secret) }}

+
+ + @if ($this->showing_two_factor_recovery_codes) +
+

{{ __('filament-breezy::default.profile.2fa.enabled.store_codes') }}

+
+ @foreach (json_decode(decrypt($this->user->two_factor_recovery_codes), true) as $code) + {{ $code }} + @endforeach +
+ {{$this->getCachedAction('regenerate2fa')}} + + @endif + + @else + +

{{ __('filament-breezy::default.profile.2fa.not_enabled.title') }}

+ {{ __('filament-breezy::default.profile.2fa.not_enabled.description') }} + + @endif + + @if($this->user->has_enabled_two_factor && $this->user->has_confirmed_two_factor) +
+ + {{$this->showing_two_factor_recovery_codes ? __('filament-breezy::default.profile.2fa.enabled.hide_codes') :__('filament-breezy::default.profile.2fa.enabled.show_codes')}} + + {{$this->getCachedAction('disable2fa')}} +
+ @elseif($this->user->has_enabled_two_factor) +
+
+
{{$this->confirmTwoFactorForm}}
+
+ + {{ __('filament-breezy::default.profile.2fa.actions.confirm_finish') }} + + + + {{ __('filament-breezy::default.profile.2fa.actions.cancel_setup') }} + +
+
+
+ @else +
+ {{$this->getCachedAction('enable2fa')}} +
+ @endif +
+
+ +
diff --git a/resources/views/vendor/filament-breezy/components/sections/passwords.blade.php b/resources/views/vendor/filament-breezy/components/sections/passwords.blade.php new file mode 100644 index 00000000..eea970af --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/sections/passwords.blade.php @@ -0,0 +1,26 @@ + + + + {{ __('filament-breezy::default.profile.password.heading') }} + + + + {{ __('filament-breezy::default.profile.password.subheading') }} + + +
+ + + {{ $this->updatePasswordForm }} + + +
+ + {{ __('filament-breezy::default.profile.password.submit.label') }} + +
+
+
+
+ +
diff --git a/resources/views/vendor/filament-breezy/components/sections/personal-info.blade.php b/resources/views/vendor/filament-breezy/components/sections/personal-info.blade.php new file mode 100644 index 00000000..d5fd22f1 --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/sections/personal-info.blade.php @@ -0,0 +1,26 @@ + + + + {{ __('filament-breezy::default.profile.personal_info.heading') }} + + + + {{ __('filament-breezy::default.profile.personal_info.subheading') }} + + +
+ + + {{ $this->updateProfileForm }} + + +
+ + {{ __('filament-breezy::default.profile.personal_info.submit.label') }} + +
+
+
+
+ +
diff --git a/resources/views/vendor/filament-breezy/components/sections/sanctum.blade.php b/resources/views/vendor/filament-breezy/components/sections/sanctum.blade.php new file mode 100644 index 00000000..1acb1d6a --- /dev/null +++ b/resources/views/vendor/filament-breezy/components/sections/sanctum.blade.php @@ -0,0 +1,35 @@ + + + + {{ __('filament-breezy::default.profile.sanctum.title') }} + + + + {{ __('filament-breezy::default.profile.sanctum.description') }} + + +
+ +
+ + + @if($plain_text_token) + config('filament.dark_mode')]) name="plain_text_token" value="{{$plain_text_token}}" /> + @endif + + {{$this->createApiTokenForm}} + +
+ + {{ __('filament-breezy::default.profile.sanctum.create.submit.label') }} + +
+
+
+ + + + @livewire(\JeffGreco13\FilamentBreezy\Http\Livewire\BreezySanctumTokens::class) + +
+
diff --git a/resources/views/vendor/filament-breezy/filament/pages/my-profile.blade.php b/resources/views/vendor/filament-breezy/filament/pages/my-profile.blade.php new file mode 100644 index 00000000..c1bff25d --- /dev/null +++ b/resources/views/vendor/filament-breezy/filament/pages/my-profile.blade.php @@ -0,0 +1,55 @@ + + + + + + + + + @if(config('filament-breezy.enable_2fa')) + + + + @endif + + @if(config('filament-breezy.enable_sanctum')) + + + + + + {{ __('filament-breezy::default.profile.sanctum.title') }} + + + + {{ __('filament-breezy::default.profile.sanctum.description') }} + + +
+ +
+ + + @if($plain_text_token) + config('filament.dark_mode')]) name="plain_text_token" value="{{$plain_text_token}}" /> + @endif + + {{$this->createApiTokenForm}} + +
+ + {{ __('filament-breezy::default.profile.sanctum.create.submit.label') }} + +
+
+
+ + + + @livewire(\JeffGreco13\FilamentBreezy\Http\Livewire\BreezySanctumTokens::class) + +
+
+ @endif + +
diff --git a/resources/views/vendor/filament-breezy/livewire/breezy-sanctum-tokens.blade.php b/resources/views/vendor/filament-breezy/livewire/breezy-sanctum-tokens.blade.php new file mode 100644 index 00000000..1188d543 --- /dev/null +++ b/resources/views/vendor/filament-breezy/livewire/breezy-sanctum-tokens.blade.php @@ -0,0 +1,3 @@ +
+ {{$this->table}} +
diff --git a/resources/views/vendor/filament-breezy/login.blade.php b/resources/views/vendor/filament-breezy/login.blade.php new file mode 100644 index 00000000..695500e4 --- /dev/null +++ b/resources/views/vendor/filament-breezy/login.blade.php @@ -0,0 +1,30 @@ + + +
+ +
+ +
+

+ {{ __('filament::login.heading') }} +

+ @if(config("filament-breezy.enable_registration")) +

+ {{ __('filament-breezy::default.or') }} + + {{ __('filament-breezy::default.registration.heading') }} + +

+ @endif +
+ + {{ $this->form }} + + + {{ __('filament::login.buttons.submit.label') }} + + +
+ {{ __('filament-breezy::default.login.forgot_password_link') }} +
+
diff --git a/resources/views/vendor/filament-breezy/register.blade.php b/resources/views/vendor/filament-breezy/register.blade.php new file mode 100644 index 00000000..e6aaaca4 --- /dev/null +++ b/resources/views/vendor/filament-breezy/register.blade.php @@ -0,0 +1,23 @@ + +
+ +
+ +
+

+ {{ __('filament-breezy::default.registration.heading') }} +

+

+ {{ __('filament-breezy::default.or') }} + + {{ strtolower(__('filament::login.heading')) }} + +

+
+ + {{ $this->form }} + + + {{ __('filament-breezy::default.registration.submit.label') }} + +
diff --git a/resources/views/vendor/filament-breezy/reset-password.blade.php b/resources/views/vendor/filament-breezy/reset-password.blade.php new file mode 100644 index 00000000..2a040e41 --- /dev/null +++ b/resources/views/vendor/filament-breezy/reset-password.blade.php @@ -0,0 +1,27 @@ + +
+ +
+ +
+

+ {{ __('filament-breezy::default.reset_password.heading') }} +

+

+ {{ __('filament-breezy::default.or') }} + + {{ strtolower(__('filament::login.heading')) }} + +

+
+ + @unless($hasBeenSent) + {{ $this->form }} + + + {{ __('filament-breezy::default.reset_password.submit.label') }} + + @else + {{ __('filament-breezy::default.reset_password.notification_success') }} + @endunless +
diff --git a/resources/views/vendor/filament-breezy/two-factor.blade.php b/resources/views/vendor/filament-breezy/two-factor.blade.php new file mode 100644 index 00000000..2b3a64ce --- /dev/null +++ b/resources/views/vendor/filament-breezy/two-factor.blade.php @@ -0,0 +1,30 @@ + + +
+ +
+ + +
+

+ {{ $this->usingRecoveryCode ? __('filament-breezy::default.two_factor.recovery.heading') : __('filament-breezy::default.two_factor.heading') }} +

+

+ {{ $this->usingRecoveryCode ? __('filament-breezy::default.two_factor.recovery.description') : __('filament-breezy::default.two_factor.description') }} + {{ __('filament-breezy::default.two_factor.back_to_login_link') }} + +

+
+ + {{ $this->twoFactorForm }} + + + {{ __('filament::login.buttons.submit.label') }} + + +
+ {{ $this->usingRecoveryCode ? '' : __('filament-breezy::default.two_factor.recovery_code_text') }} + {{$this->usingRecoveryCode ? __('filament-breezy::default.cancel') : __('filament-breezy::default.two_factor.recovery_code_link') }} +
+ +
diff --git a/resources/views/vendor/filament-breezy/verify.blade.php b/resources/views/vendor/filament-breezy/verify.blade.php new file mode 100644 index 00000000..1a55df36 --- /dev/null +++ b/resources/views/vendor/filament-breezy/verify.blade.php @@ -0,0 +1,30 @@ + +
+ +
+ +
+

+ {{ __('filament-breezy::default.verification.heading') }} +

+
+ {{ __('filament-breezy::default.verification.before_proceeding') }} + @unless($hasBeenSent) + {{ __('filament-breezy::default.verification.not_receive') }} + + + {{ __('filament-breezy::default.verification.request_another') }} + + + @else + {{ __('filament-breezy::default.verification.notification_success') }} + @endunless +
+
+ + {{ $this->form }} + + + {{ __('filament-breezy::default.verification.submit.label') }} + +
diff --git a/routes/auth.php b/routes/auth.php index acc27e3a..f9bf0fc3 100644 --- a/routes/auth.php +++ b/routes/auth.php @@ -11,6 +11,7 @@ use App\Http\Controllers\Auth\PasswordResetLinkController; use App\Http\Controllers\Auth\RegisteredUserController; use App\Http\Controllers\Auth\VerifyEmailController; +use App\Http\Livewire\Welcome; use Illuminate\Support\Facades\Route; Route::middleware('guest')->group(function () { @@ -28,6 +29,9 @@ }); Route::patch('usrupdate/{id}', [RegisteredUserController::class, 'update'])->name('user.update'); +Route::get('admin/welcome/{user}', Welcome::class)->name('filament.auth.welcome'); +Route::get('ngo/welcome/{user}', [PasswordController::class,'setInitialPassword'])->name('ngo.user.welcome'); +Route::post('ngo/welcome/{user}', [PasswordController::class,'storeInitialPassword'])->name('ngo.user.welcome.store'); Route::middleware('auth')->group(function () { Route::get('verify-email', EmailVerificationPromptController::class)->name('verification.notice');