usamamuneerchaudhary /
filament-notifier
| 1 | <?php |
||
| 2 | |||
| 3 | namespace Usamamuneerchaudhary\Notifier\Filament\Pages; |
||
| 4 | |||
| 5 | use Filament\Forms\Components\Select; |
||
| 6 | use Filament\Forms\Components\TextInput; |
||
| 7 | use Filament\Forms\Components\Toggle; |
||
| 8 | use Filament\Forms\Concerns\InteractsWithForms; |
||
| 9 | use Filament\Notifications\Notification; |
||
| 10 | use Filament\Pages\Page; |
||
| 11 | use Filament\Schemas\Components\Section; |
||
| 12 | use Filament\Schemas\Components\Tabs; |
||
| 13 | use Filament\Schemas\Components\Tabs\Tab; |
||
| 14 | use Filament\Schemas\Components\Utilities\Get; |
||
| 15 | use Filament\Schemas\Schema; |
||
| 16 | use Usamamuneerchaudhary\Notifier\Filament\Widgets\NotificationStatsOverview; |
||
| 17 | use Usamamuneerchaudhary\Notifier\Models\NotificationSetting; |
||
| 18 | use Usamamuneerchaudhary\Notifier\Models\NotificationChannel; |
||
| 19 | |||
| 20 | class NotificationSettings extends Page |
||
| 21 | { |
||
| 22 | use InteractsWithForms; |
||
|
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
| 23 | |||
| 24 | protected static string|null|\BackedEnum $navigationIcon = 'heroicon-o-cog-6-tooth'; |
||
|
0 ignored issues
–
show
The type
BackedEnum was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||
| 25 | protected static string|null|\UnitEnum $navigationGroup = 'Notifier'; |
||
|
0 ignored issues
–
show
The type
UnitEnum was not found. Maybe you did not declare it correctly or list all dependencies?
The issue could also be caused by a filter entry in the build configuration.
If the path has been excluded in your configuration, e.g. filter:
dependency_paths: ["lib/*"]
For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths Loading history...
|
|||
| 26 | protected static ?string $title = 'Notification Settings'; |
||
| 27 | protected static ?string $navigationLabel = 'Settings'; |
||
| 28 | protected static ?int $navigationSort = 1; |
||
| 29 | protected string $view = 'notifier::pages.settings'; |
||
| 30 | |||
| 31 | public array $data; |
||
| 32 | |||
| 33 | public function mount(): void |
||
| 34 | { |
||
| 35 | $channels = NotificationChannel::all(); |
||
| 36 | $channelsData = []; |
||
| 37 | |||
| 38 | foreach ($channels as $channel) { |
||
| 39 | $settings = $channel->settings ?? []; |
||
| 40 | $channelsData[$channel->type] = array_merge([ |
||
| 41 | 'enabled' => $channel->is_active, |
||
| 42 | ], $settings); |
||
| 43 | } |
||
| 44 | |||
| 45 | // Load preferences settings |
||
| 46 | $preferences = NotificationSetting::get('preferences', config('notifier.settings.preferences', [])); |
||
| 47 | // Load analytics settings |
||
| 48 | $analytics = NotificationSetting::get('analytics', config('notifier.settings.analytics', [])); |
||
| 49 | // Load rate limiting settings |
||
| 50 | $rateLimiting = NotificationSetting::get('rate_limiting', config('notifier.settings.rate_limiting', [])); |
||
| 51 | |||
| 52 | $this->form->fill([ |
||
|
0 ignored issues
–
show
The property
form does not exist on Usamamuneerchaudhary\Not...es\NotificationSettings. Since you implemented __get, consider adding a @property annotation.
Loading history...
|
|||
| 53 | 'enabled' => NotificationSetting::get('enabled', true), |
||
| 54 | 'queue_name' => NotificationSetting::get('queue_name', 'default'), |
||
| 55 | 'default_channel' => NotificationSetting::get('default_channel', 'email'), |
||
| 56 | 'channels' => $channelsData, |
||
| 57 | 'preferences' => [ |
||
| 58 | 'enabled' => $preferences['enabled'] ?? true, |
||
| 59 | 'default_channels' => $preferences['default_channels'] ?? ['email'], |
||
| 60 | 'allow_override' => $preferences['allow_override'] ?? true, |
||
| 61 | ], |
||
| 62 | 'analytics' => [ |
||
| 63 | 'enabled' => $analytics['enabled'] ?? true, |
||
| 64 | 'track_opens' => $analytics['track_opens'] ?? true, |
||
| 65 | 'track_clicks' => $analytics['track_clicks'] ?? true, |
||
| 66 | 'retention_days' => $analytics['retention_days'] ?? 90, |
||
| 67 | ], |
||
| 68 | 'rate_limiting' => [ |
||
| 69 | 'enabled' => $rateLimiting['enabled'] ?? true, |
||
| 70 | 'max_per_minute' => $rateLimiting['max_per_minute'] ?? 60, |
||
| 71 | 'max_per_hour' => $rateLimiting['max_per_hour'] ?? 1000, |
||
| 72 | 'max_per_day' => $rateLimiting['max_per_day'] ?? 10000, |
||
| 73 | ], |
||
| 74 | ]); |
||
| 75 | } |
||
| 76 | |||
| 77 | public function form(Schema $schema): Schema |
||
| 78 | { |
||
| 79 | $channels = NotificationChannel::all(); |
||
| 80 | $tabs = [ |
||
| 81 | Tab::make('General') |
||
| 82 | ->icon('heroicon-o-cog') |
||
| 83 | ->schema([ |
||
| 84 | Toggle::make('enabled') |
||
| 85 | ->label('Enable Notifications') |
||
| 86 | ->default(true), |
||
| 87 | TextInput::make('queue_name') |
||
| 88 | ->label('Queue Name') |
||
| 89 | ->default('default') |
||
| 90 | ->required(), |
||
| 91 | Select::make('default_channel') |
||
| 92 | ->label('Default Channel') |
||
| 93 | ->options(NotificationChannel::pluck('title', 'type')->toArray()) |
||
| 94 | ->required(), |
||
| 95 | ]), |
||
| 96 | Tab::make('Preferences') |
||
| 97 | ->icon('heroicon-o-user-circle') |
||
| 98 | ->schema([ |
||
| 99 | Section::make('User Preferences') |
||
| 100 | ->description('Configure default user notification preferences') |
||
| 101 | ->schema([ |
||
| 102 | Toggle::make('preferences.enabled') |
||
| 103 | ->label('Enable User Preferences') |
||
| 104 | ->default(true), |
||
| 105 | Select::make('preferences.default_channels') |
||
| 106 | ->label('Default Channels') |
||
| 107 | ->multiple() |
||
| 108 | ->options(NotificationChannel::pluck('title', 'type')->toArray()) |
||
| 109 | ->default(['email']) |
||
| 110 | ->required(), |
||
| 111 | Toggle::make('preferences.allow_override') |
||
| 112 | ->label('Allow Users to Override Preferences') |
||
| 113 | ->default(true) |
||
| 114 | ->helperText('If enabled, users can customize their notification preferences'), |
||
| 115 | ]), |
||
| 116 | ]), |
||
| 117 | Tab::make('Analytics') |
||
| 118 | ->icon('heroicon-o-chart-bar') |
||
| 119 | ->schema([ |
||
| 120 | Section::make('Analytics Settings') |
||
| 121 | ->description('Configure notification analytics and tracking') |
||
| 122 | ->schema([ |
||
| 123 | Toggle::make('analytics.enabled') |
||
| 124 | ->label('Enable Analytics') |
||
| 125 | ->default(true), |
||
| 126 | Toggle::make('analytics.track_opens') |
||
| 127 | ->label('Track Email Opens') |
||
| 128 | ->default(true) |
||
| 129 | ->visible(fn(Get $get): bool => $get('analytics.enabled')), |
||
| 130 | Toggle::make('analytics.track_clicks') |
||
| 131 | ->label('Track Link Clicks') |
||
| 132 | ->default(true) |
||
| 133 | ->visible(fn(Get $get): bool => $get('analytics.enabled')), |
||
| 134 | TextInput::make('analytics.retention_days') |
||
| 135 | ->label('Data Retention (Days)') |
||
| 136 | ->numeric() |
||
| 137 | ->default(90) |
||
| 138 | ->required() |
||
| 139 | ->visible(fn(Get $get): bool => $get('analytics.enabled')), |
||
| 140 | ]), |
||
| 141 | ]), |
||
| 142 | Tab::make('Rate Limiting') |
||
| 143 | ->icon('heroicon-o-clock') |
||
| 144 | ->schema([ |
||
| 145 | Section::make('Rate Limiting Settings') |
||
| 146 | ->description('Configure rate limits for notifications to prevent abuse') |
||
| 147 | ->schema([ |
||
| 148 | Toggle::make('rate_limiting.enabled') |
||
| 149 | ->label('Enable Rate Limiting') |
||
| 150 | ->default(true), |
||
| 151 | TextInput::make('rate_limiting.max_per_minute') |
||
| 152 | ->label('Max Per Minute') |
||
| 153 | ->numeric() |
||
| 154 | ->default(60) |
||
| 155 | ->required() |
||
| 156 | ->visible(fn(Get $get): bool => $get('rate_limiting.enabled')), |
||
| 157 | TextInput::make('rate_limiting.max_per_hour') |
||
| 158 | ->label('Max Per Hour') |
||
| 159 | ->numeric() |
||
| 160 | ->default(1000) |
||
| 161 | ->required() |
||
| 162 | ->visible(fn(Get $get): bool => $get('rate_limiting.enabled')), |
||
| 163 | TextInput::make('rate_limiting.max_per_day') |
||
| 164 | ->label('Max Per Day') |
||
| 165 | ->numeric() |
||
| 166 | ->default(10000) |
||
| 167 | ->required() |
||
| 168 | ->visible(fn(Get $get): bool => $get('rate_limiting.enabled')), |
||
| 169 | ]), |
||
| 170 | ]), |
||
| 171 | ]; |
||
| 172 | |||
| 173 | // Dynamically create tabs for each channel |
||
| 174 | foreach ($channels as $channel) { |
||
| 175 | $tabs[] = Tab::make($channel->title) |
||
| 176 | ->icon($channel->icon ?? 'heroicon-o-bell') |
||
| 177 | ->schema(self::getChannelSchema($channel)); |
||
| 178 | } |
||
| 179 | |||
| 180 | return $schema |
||
| 181 | ->schema([ |
||
| 182 | Tabs::make('Settings') |
||
| 183 | ->tabs($tabs) |
||
| 184 | ->columnSpanFull() |
||
| 185 | ]) |
||
| 186 | ->statePath('data'); |
||
| 187 | } |
||
| 188 | |||
| 189 | protected static function getChannelSchema(NotificationChannel $channel): array |
||
| 190 | { |
||
| 191 | $schema = [ |
||
| 192 | Toggle::make("channels.{$channel->type}.enabled") |
||
| 193 | ->label("Enable {$channel->title}"), |
||
| 194 | ]; |
||
| 195 | |||
| 196 | $fields = match ($channel->type) { |
||
| 197 | 'email' => [ |
||
| 198 | TextInput::make("channels.{$channel->type}.from_address") |
||
| 199 | ->label('From Address') |
||
| 200 | ->email() |
||
| 201 | ->required() |
||
| 202 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 203 | TextInput::make("channels.{$channel->type}.from_name") |
||
| 204 | ->label('From Name') |
||
| 205 | ->required() |
||
| 206 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 207 | ], |
||
| 208 | 'slack' => [ |
||
| 209 | TextInput::make("channels.{$channel->type}.webhook_url") |
||
| 210 | ->label('Webhook URL') |
||
| 211 | ->url() |
||
| 212 | ->required() |
||
| 213 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 214 | TextInput::make("channels.{$channel->type}.channel") |
||
| 215 | ->label('Slack Channel') |
||
| 216 | ->default('#notifications') |
||
| 217 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 218 | TextInput::make("channels.{$channel->type}.username") |
||
| 219 | ->label('Bot Username') |
||
| 220 | ->default('Notification Bot') |
||
| 221 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 222 | ], |
||
| 223 | 'sms' => [ |
||
| 224 | Select::make("channels.{$channel->type}.provider") |
||
| 225 | ->label('SMS Provider') |
||
| 226 | ->options([ |
||
| 227 | 'twilio' => 'Twilio', |
||
| 228 | 'vonage' => 'Vonage (Nexmo)', |
||
| 229 | 'generic' => 'Generic API', |
||
| 230 | ]) |
||
| 231 | ->required() |
||
| 232 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 233 | TextInput::make("channels.{$channel->type}.twilio_account_sid") |
||
| 234 | ->label('Twilio Account SID') |
||
| 235 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && $get("channels.{$channel->type}.provider") === 'twilio'), |
||
| 236 | TextInput::make("channels.{$channel->type}.twilio_auth_token") |
||
| 237 | ->label('Twilio Auth Token') |
||
| 238 | ->password() |
||
| 239 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && $get("channels.{$channel->type}.provider") === 'twilio'), |
||
| 240 | TextInput::make("channels.{$channel->type}.twilio_phone_number") |
||
| 241 | ->label('Twilio Phone Number') |
||
| 242 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && $get("channels.{$channel->type}.provider") === 'twilio'), |
||
| 243 | TextInput::make("channels.{$channel->type}.api_url") |
||
| 244 | ->label('API URL') |
||
| 245 | ->url() |
||
| 246 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && $get("channels.{$channel->type}.provider") === 'generic'), |
||
| 247 | TextInput::make("channels.{$channel->type}.api_key") |
||
| 248 | ->label('API Key') |
||
| 249 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && ($get("channels.{$channel->type}.provider") === 'generic' || $get("channels.{$channel->type}.provider") === 'vonage')), |
||
| 250 | TextInput::make("channels.{$channel->type}.api_secret") |
||
| 251 | ->label('API Secret') |
||
| 252 | ->password() |
||
| 253 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled") && ($get("channels.{$channel->type}.provider") === 'generic' || $get("channels.{$channel->type}.provider") === 'vonage')), |
||
| 254 | ], |
||
| 255 | 'push' => [ |
||
| 256 | TextInput::make("channels.{$channel->type}.firebase_server_key") |
||
| 257 | ->label('Firebase Server Key') |
||
| 258 | ->password() |
||
| 259 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 260 | TextInput::make("channels.{$channel->type}.firebase_project_id") |
||
| 261 | ->label('Firebase Project ID') |
||
| 262 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 263 | ], |
||
| 264 | 'discord' => [ |
||
| 265 | TextInput::make("channels.{$channel->type}.webhook_url") |
||
| 266 | ->label('Discord Webhook URL') |
||
| 267 | ->url() |
||
| 268 | ->required() |
||
| 269 | ->helperText('Get this from your Discord server settings > Integrations > Webhooks') |
||
| 270 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 271 | TextInput::make("channels.{$channel->type}.username") |
||
| 272 | ->label('Bot Username') |
||
| 273 | ->helperText('Optional: Custom username for the webhook') |
||
| 274 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 275 | TextInput::make("channels.{$channel->type}.avatar_url") |
||
| 276 | ->label('Avatar URL') |
||
| 277 | ->url() |
||
| 278 | ->helperText('Optional: URL for the webhook avatar') |
||
| 279 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 280 | TextInput::make("channels.{$channel->type}.color") |
||
| 281 | ->label('Embed Color') |
||
| 282 | ->helperText('Optional: Decimal color code for embed (e.g., 3447003 for blue)') |
||
| 283 | ->numeric() |
||
| 284 | ->visible(fn(Get $get): bool => $get("channels.{$channel->type}.enabled")), |
||
| 285 | ], |
||
| 286 | default => [], |
||
| 287 | }; |
||
| 288 | |||
| 289 | return array_merge($schema, $fields); |
||
| 290 | } |
||
| 291 | |||
| 292 | public function save(): void |
||
| 293 | { |
||
| 294 | $settings = $this->data; |
||
| 295 | |||
| 296 | NotificationSetting::set('enabled', $settings['enabled'] ?? true, 'general'); |
||
| 297 | NotificationSetting::set('queue_name', $settings['queue_name'] ?? 'default', 'general'); |
||
| 298 | NotificationSetting::set('default_channel', $settings['default_channel'] ?? 'email', 'general'); |
||
| 299 | |||
| 300 | // Save preferences settings |
||
| 301 | if (isset($settings['preferences'])) { |
||
| 302 | NotificationSetting::set('preferences', $settings['preferences'], 'preferences'); |
||
| 303 | } |
||
| 304 | |||
| 305 | // Save analytics settings |
||
| 306 | if (isset($settings['analytics'])) { |
||
| 307 | NotificationSetting::set('analytics', $settings['analytics'], 'analytics'); |
||
| 308 | } |
||
| 309 | |||
| 310 | // Save rate limiting settings |
||
| 311 | if (isset($settings['rate_limiting'])) { |
||
| 312 | NotificationSetting::set('rate_limiting', $settings['rate_limiting'], 'rate_limiting'); |
||
| 313 | } |
||
| 314 | |||
| 315 | // Save channel settings to channel records |
||
| 316 | if (isset($settings['channels']) && is_array($settings['channels'])) { |
||
| 317 | foreach ($settings['channels'] as $channelType => $channelData) { |
||
| 318 | $channel = NotificationChannel::where('type', $channelType)->first(); |
||
| 319 | |||
| 320 | if ($channel) { |
||
| 321 | $isActive = $channelData['enabled'] ?? false; |
||
| 322 | unset($channelData['enabled']); |
||
| 323 | |||
| 324 | $channel->is_active = $isActive; |
||
| 325 | |||
| 326 | $existingSettings = $channel->settings ?? []; |
||
| 327 | $channel->settings = array_merge($existingSettings, $channelData); |
||
| 328 | |||
| 329 | $channel->save(); |
||
| 330 | } |
||
| 331 | } |
||
| 332 | } |
||
| 333 | |||
| 334 | Notification::make() |
||
| 335 | ->title('Settings saved successfully') |
||
| 336 | ->success() |
||
| 337 | ->send(); |
||
| 338 | } |
||
| 339 | |||
| 340 | protected function getHeaderWidgets(): array |
||
| 341 | { |
||
| 342 | return [ |
||
| 343 | NotificationStatsOverview::class, |
||
| 344 | ]; |
||
| 345 | } |
||
| 346 | } |
||
| 347 |