getUser(); $user->load('settings'); $data = $user->toArray(); $data['settings'] = $user->settings?->toArray() ?? []; $this->form->fill($data); } 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 form(Schema $schema): Schema { return $schema ->components([ Section::make(CheerfulNotification::getByKey('profile.detail.title')) ->description(CheerfulNotification::getByKey('profile.detail.description')) ->icon(CheerfulNotification::getNotifStyle() === NotifStyle::CHEERFUL ? 'heroicon-o-user' : null) ->schema([ Grid::make(2) ->schema([ TextInput::make('name') ->label('Nama Lengkap') ->placeholder('Masukkan nama lengkap Anda') ->required() ->maxLength(100) ->columnSpanFull(), TextInput::make('email') ->label('Alamat Surel') ->placeholder('contoh@email.com') ->autocomplete(false) ->required() ->maxLength(254) ->unique(ignoreRecord: true) ->email() ->prefixIcon('heroicon-o-envelope') ->live(), TextInput::make('username') ->label('Nama Pengguna') ->placeholder('nama_pengguna') ->autocomplete(false) ->required() ->minLength(5) ->maxLength(20) ->regex('/^[a-zA-Z0-9_.-]+$/') ->unique(ignoreRecord: true) ->prefixIcon('heroicon-o-at-symbol'), ]), ]), Section::make(CheerfulNotification::getByKey('profile.ux.title')) ->description(CheerfulNotification::getByKey('profile.ux.description')) ->icon(CheerfulNotification::getNotifStyle() === NotifStyle::CHEERFUL ? 'heroicon-o-swatch' : null) ->schema([ Grid::make(2) ->schema([ Select::make('settings.notif_style') ->label('Gaya Bahasa') ->options(NotifStyle::class) ->native(false) ->required(), Select::make('settings.primary_color') ->label('Tema Warna') ->options([ 'blue' => '🔵 Biru (Default)', 'sky' => '💎 Biru Langit', 'cyan' => '🌊 Biru Tosca (Cyan)', 'emerald' => '🟢 Emerald (Hijau)', 'teal' => '🍃 Teal (Hijau Keunguan)', 'lime' => '🍋 Lime (Hijah Muda)', 'amber' => '🟡 Amber (Kuning)', 'orange' => '🟠 Orange', 'rose' => '🔴 Rose (Merah)', 'fuchsia' => '🌸 Fuchsia (Pink Cerah)', 'violet' => '🟣 Violet (Ungu)', 'indigo' => '🌌 Indigo (Ungu Gelap)', ]) ->placeholder('Pilih warna tema') ->native(false), Select::make('settings.font') ->label('Jenis Huruf (Font)') ->options([ 'Inter' => 'Inter (Modern)', 'Roboto' => 'Roboto (Clean)', 'Poppins' => 'Poppins (Rounder)', 'Outfit' => 'Outfit (Premium)', 'Montserrat' => 'Montserrat (Classic)', 'Lexend' => 'Lexend (Readable)', ]) ->native(false), Select::make('settings.content_width') ->label('Lebar Konten') ->options([ 'full' => '↔️ Lebar Penuh (Full)', 'centered' => '🏢 Terpusat (Centered)', ]) ->native(false), Select::make('settings.border_radius') ->label('Radius Sudut (Border)') ->options([ 'none' => '📐 Tegas (Sharp)', 'md' => '📱 Modern (Default)', 'lg' => '🎉 Rounded (Cheerful)', 'xl' => '🎈 Extra Round', ]) ->native(false), Toggle::make('settings.top_navigation') ->label('Navigasi Atas (Top Nav)') ->helperText('Pindahkan menu navigasi dari samping ke bagian atas layar.') ->onIcon('heroicon-m-window') ->offIcon('heroicon-m-arrow-top-right-on-square') ->inline(false), ]), ]), Section::make(CheerfulNotification::getByKey('profile.security.title')) ->description(CheerfulNotification::getByKey('profile.security.description')) ->icon(CheerfulNotification::getNotifStyle() === NotifStyle::CHEERFUL ? 'heroicon-o-lock-closed' : null) ->schema([ Grid::make(2) ->schema([ TextInput::make('password') ->label('Kata Sandi Baru') ->placeholder('********') ->password() ->revealable(filament()->arePasswordsRevealable()) ->rule(Password::default()) ->showAllValidationMessages() ->dehydrated(fn ($state) => filled($state)) ->dehydrateStateUsing(fn ($state) => Hash::make($state)) ->same('passwordConfirmation') ->validationAttribute(__('filament-panels::auth/pages/register.form.password.validation_attribute')) ->prefixIcon('heroicon-o-lock-closed'), TextInput::make('passwordConfirmation') ->label('Konfirmasi Kata Sandi') ->placeholder('********') ->password() ->revealable(filament()->arePasswordsRevealable()) ->dehydrated(false) ->prefixIcon('heroicon-o-lock-closed'), ]), ]), ]); } protected function handleRecordUpdate(Model $record, array $data): Model { $settingsData = $data['settings'] ?? []; unset($data['settings']); $record->update($data); if ($record->settings) { $record->settings->update($settingsData); } else { $record->settings()->create($settingsData); } return $record; } protected function afterSave(): void { CheerfulNotification::success( CheerfulNotification::getByKey('profile.saved.title'), CheerfulNotification::getByKey('profile.saved.body') )->send(); if (! filled($this->form->getState()['password'] ?? null)) { $this->redirect($this->getUrl()); return; } if (filled($this->form->getState()['password'] ?? null)) { Filament::auth()->logout(); session()->invalidate(); session()->regenerateToken(); CheerfulNotification::info( CheerfulNotification::getByKey('profile.password_changed.title'), CheerfulNotification::getByKey('profile.password_changed.body') )->send(); $this->redirect(filament()->getLoginUrl()); } } protected function getSavedNotification(): ?Notification { return null; } }