diff --git a/app/Filament/Pages/Auth/Login.php b/app/Filament/Pages/Auth/Login.php new file mode 100644 index 0000000..403980b --- /dev/null +++ b/app/Filament/Pages/Auth/Login.php @@ -0,0 +1,151 @@ +components([ + TextInput::make('login') + ->label('Nama Pengguna atau Alamat Surel') + ->placeholder('johndoe@example.com') + ->required() + ->autocomplete(false) + ->autofocus(), + + TextInput::make('password') + ->label('Kata Sandi') + ->placeholder('********') + ->password() + ->revealable(filament()->arePasswordsRevealable()) + ->required() + ->autocomplete(false) + ->extraInputAttributes(['tabindex' => 2]) + ->hint( + filament()->hasPasswordReset() + ? new HtmlString(Blade::render('{{ __(\'filament-panels::auth/pages/login.actions.request_password_reset.label\') }}')) + : null + ), + ]); + } + + protected function getCredentialsFromFormData(#[SensitiveParameter] array $data): array + { + $loginType = filter_var($data['login'], FILTER_VALIDATE_EMAIL) ? 'email' : 'username'; + + return [ + $loginType => $data['login'], + 'password' => $data['password'], + ]; + } + + public function authenticate(): ?LoginResponse + { + try { + $this->rateLimit(5); + } catch (TooManyRequestsException $exception) { + $this->getRateLimitedNotification($exception)?->send(); + + return null; + } + + $data = $this->form->getState(); + + /** @var SessionGuard $authGuard */ + $authGuard = Filament::auth(); + + $authProvider = $authGuard->getProvider(); + /** @phpstan-ignore-line */ + $credentials = $this->getCredentialsFromFormData($data); + + $user = $authProvider->retrieveByCredentials($credentials); + + if ((! $user) || (! $authProvider->validateCredentials($user, $credentials))) { + $this->userUndertakingMultiFactorAuthentication = null; + + $this->fireFailedEvent($authGuard, $user, $credentials); + $this->throwFailureValidationException(); + } + + if ( + filled($this->userUndertakingMultiFactorAuthentication) && + (decrypt($this->userUndertakingMultiFactorAuthentication) === $user->getAuthIdentifier()) + ) { + $this->multiFactorChallengeForm->validate(); + } else { + foreach (Filament::getMultiFactorAuthenticationProviders() as $multiFactorAuthenticationProvider) { + if (! $multiFactorAuthenticationProvider->isEnabled($user)) { + continue; + } + + $this->userUndertakingMultiFactorAuthentication = encrypt($user->getAuthIdentifier()); + + if ($multiFactorAuthenticationProvider instanceof HasBeforeChallengeHook) { + $multiFactorAuthenticationProvider->beforeChallenge($user); + } + + break; + } + + if (filled($this->userUndertakingMultiFactorAuthentication)) { + $this->multiFactorChallengeForm->fill(); + + return null; + } + } + + if (! $authGuard->attemptWhen($credentials, function (Authenticatable $user): bool { + if (! ($user instanceof FilamentUser)) { + return true; + } + + return $user->canAccessPanel(Filament::getCurrentOrDefaultPanel()); + }, $data['remember'] ?? false)) { + $this->fireFailedEvent($authGuard, $user, $credentials); + $this->throwFailureValidationException(); + } + + session()->regenerate(); + + CheerfulNotification::success( + 'Hore! Berhasil Masuk ๐ŸŽ‰', + 'Selamat datang kembali, '.$user->name.'! Siap untuk beraksi hari ini? ๐Ÿš€โœจ' + )->send(); + + return app(LoginResponse::class); + } + + protected function throwFailureValidationException(): never + { + CheerfulNotification::danger( + 'Ups! Gagal Masuk ๐Ÿ˜…', + 'Sepertinya ada yang salah nih. Coba cek email atau kata sandi mu lagi ya! ๐Ÿค”๐Ÿ”' + )->send(); + + throw ValidationException::withMessages([]); + } +} diff --git a/app/Filament/Support/CheerfulNotification.php b/app/Filament/Support/CheerfulNotification.php new file mode 100644 index 0000000..c20943c --- /dev/null +++ b/app/Filament/Support/CheerfulNotification.php @@ -0,0 +1,159 @@ +title('Hore! Data Tersimpan ๐ŸŽ‰โœจ') + ->body('Data baru berhasil ditambahkan! Sistem sudah menyimpannya dengan aman. ๐Ÿš€๐Ÿ’ช') + ->success(); + } + + /** + * Notification for successful record update. + */ + public static function update(): Notification + { + return self::make() + ->title('Mantap! Data Diperbarui โœ…๐Ÿ”ฅ') + ->body('Perubahan berhasil disimpan! Data sekarang sudah up-to-date dan segar lagi. โœจ๐Ÿ‘Œ') + ->success(); + } + + /** + * Notification for successful record deletion (soft delete). + */ + public static function delete(): Notification + { + return self::make() + ->title('Oke, Data Dihapus ๐Ÿ—‘๏ธ๐Ÿ‘‹') + ->body('Data tersebut sudah berhasil dihapus dari sistem. Semuanya bersih dan rapi sekarang! ๐Ÿ˜Š๐Ÿšฎ') + ->success(); + } + + /** + * Notification for successful permanent deletion. + */ + public static function forceDelete(): Notification + { + return self::make() + ->title('Selamat Tinggal Selamanya ๐Ÿ‘‹๐Ÿ˜ข') + ->body('Data telah dihapus permanen dan tidak bisa kembali. Semoga ini keputusan yang tepat! ๐Ÿšฎ๐Ÿ’จ') + ->success(); + } + + /** + * Notification for successful record restoration. + */ + public static function restore(): Notification + { + return self::make() + ->title('Welcome Back! Data Pulih โ™ป๏ธโœจ') + ->body('Data berhasil dikembalikan! Hati-hati ya, jangan sampai terhapus lagi. ๐Ÿ˜‰๐Ÿ‘') + ->success(); + } + + /** + * Notification for successful status change/toggle. + */ + public static function statusUpdated(?string $title = null, ?string $body = null): Notification + { + return self::make() + ->title($title ?? 'Status Berubah! ๐Ÿ”„โœจ') + ->body($body ?? 'Status data berhasil diperbarui. Perubahan langsung aktif ya! ๐Ÿ‘') + ->success(); + } + + /** + * Notification for successful bulk deletion. + */ + public static function bulkDelete(): Notification + { + return self::make() + ->title('Oke, Banyak Data Dihapus ๐Ÿ—‘๏ธ๐Ÿ‘‹') + ->body('Semua data yang dipilih berhasil dihapus. Sistem makin lega deh! ๐Ÿ˜Š') + ->success(); + } + + /** + * Notification for successful bulk permanent deletion. + */ + public static function bulkForceDelete(): Notification + { + return self::make() + ->title('Bye Bye Semua! ๐Ÿ‘‹๐Ÿ”ฅ') + ->body('Data yang dipilih sudah dihapus permanen. Bersih total! ๐Ÿงน๐Ÿ’จ') + ->success(); + } + + /** + * Notification for successful bulk restoration. + */ + public static function bulkRestore(): Notification + { + return self::make() + ->title('Hore! Banyak Data Pulih โ™ป๏ธ๐ŸŽ‰') + ->body('Data-data tersebut sudah kembali aktif. Selamat bekerja kembali! ๐Ÿ’ชโœจ') + ->success(); + } + + /** + * Custom cheerful success notification. + */ + public static function success(string $title, string $body): Notification + { + return self::make() + ->title($title) + ->body($body) + ->success(); + } + + /** + * Custom cheerful info notification. + */ + public static function info(string $title, string $body): Notification + { + return self::make() + ->title($title) + ->body($body) + ->info(); + } + + /** + * Custom cheerful warning notification. + */ + public static function warning(string $title, string $body): Notification + { + return self::make() + ->title($title) + ->body($body) + ->warning(); + } + + /** + * Custom cheerful danger notification. + */ + public static function danger(string $title, string $body): Notification + { + return self::make() + ->title($title) + ->body($body) + ->danger(); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 8ce4eb0..d164cd6 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -2,6 +2,7 @@ namespace App\Providers\Filament; +use App\Filament\Pages\Auth\Login; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -27,7 +28,7 @@ public function panel(Panel $panel): Panel ->default() ->id('admin') ->path('admin') - ->login() + ->login(Login::class) ->colors([ 'primary' => Color::Amber, ])