diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 8b2b714..fd7fff4 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -17,16 +17,14 @@ jobs:
fail-fast: true
matrix:
os: [ubuntu-latest]
- php: [8.4, 8.3, 8.2]
- laravel: [12.*, 11.*, 10.*]
+ php: [8.4, 8.3]
+ laravel: [12.*, 11.*]
stability: [prefer-lowest, prefer-stable]
include:
- laravel: 12.*
testbench: 10.*
- laravel: 11.*
testbench: 9.*
- - laravel: 10.*
- testbench: 8.*
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} - ${{ matrix.os }}
diff --git a/composer.json b/composer.json
index 9ff92fe..7568340 100644
--- a/composer.json
+++ b/composer.json
@@ -20,9 +20,9 @@
}
],
"require": {
- "php": "^8.2",
+ "php": "^8.3",
"filament/filament": "^4.0",
- "illuminate/contracts": "^10.0||^11.0||^12.0",
+ "illuminate/contracts": "^11.0||^12.0",
"maatwebsite/excel": "^3.1",
"spatie/eloquent-sortable": "^4.3",
"spatie/laravel-medialibrary": "^11.12",
@@ -30,15 +30,15 @@
},
"require-dev": {
"laravel/pint": "^1.14",
- "nunomaduro/collision": "^8.1.1||^7.10.0",
- "larastan/larastan": "^2.9||^3.0",
- "orchestra/testbench": "^10.0.0||^9.0.0||^8.22.0",
- "pestphp/pest": "^3.0||^2.34",
- "pestphp/pest-plugin-arch": "^3.0||^2.7",
- "pestphp/pest-plugin-laravel": "^3.0||^2.3",
- "phpstan/extension-installer": "^1.3||^2.0",
- "phpstan/phpstan-deprecation-rules": "^1.1||^2.0",
- "phpstan/phpstan-phpunit": "^1.3||^2.0",
+ "larastan/larastan": "^3.0",
+ "nunomaduro/collision": "^8.1.1",
+ "orchestra/testbench": "^10.0.0||^9.0.0",
+ "pestphp/pest": "^3.0",
+ "pestphp/pest-plugin-arch": "^3.0",
+ "pestphp/pest-plugin-laravel": "^3.0",
+ "phpstan/extension-installer": "^1.3",
+ "phpstan/phpstan-deprecation-rules": "^2.0",
+ "phpstan/phpstan-phpunit": "^2.0",
"spatie/laravel-ray": "^1.35"
},
"autoload": {
diff --git a/config/filament-form-builder.php b/config/filament-form-builder.php
index 2ea07cc..09c67b1 100644
--- a/config/filament-form-builder.php
+++ b/config/filament-form-builder.php
@@ -32,4 +32,20 @@
'admin-panel-filament-form-field-name-plural' => 'Fields',
'preview-route' => 'filament-form-builder.show',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Notification Emails Configuration
+ |--------------------------------------------------------------------------
+ |
+ | Configure the notification emails field in the form builder.
+ |
+ | 'user_model': The User model class to use for the select field.
+ | Set to null to use TagsInput for manual email entry.
+ | Default: 'App\Models\User'
+ |
+ | Example: 'user_model' => null, // Use TagsInput instead
+ |
+ */
+ 'user_model' => 'App\Models\User',
];
diff --git a/database/migrations/add_notification_emails_to_filament_forms_table.php.stub b/database/migrations/add_notification_emails_to_filament_forms_table.php.stub
new file mode 100644
index 0000000..f3dd9e7
--- /dev/null
+++ b/database/migrations/add_notification_emails_to_filament_forms_table.php.stub
@@ -0,0 +1,17 @@
+json('notification_emails')->nullable()->after('description');
+ });
+ }
+};
diff --git a/resources/views/mail/submission-notification.blade.php b/resources/views/mail/submission-notification.blade.php
new file mode 100644
index 0000000..60c0382
--- /dev/null
+++ b/resources/views/mail/submission-notification.blade.php
@@ -0,0 +1,20 @@
+
+# New Form Submission
+
+A new submission has been received for **{{ $form->name }}**.
+
+**Submitted by:** {{ $entry->user?->name ?? 'Guest' }}
+
+**Submitted at:** {{ $entry->created_at->format('F j, Y g:i A') }}
+
+
+View Form & Entries
+
+
+Thanks,
+{{ config('app.name') }}
+
+
+
+
+
diff --git a/src/Filament/Resources/FilamentFormResource.php b/src/Filament/Resources/FilamentFormResource.php
index a6bc76a..a5731df 100644
--- a/src/Filament/Resources/FilamentFormResource.php
+++ b/src/Filament/Resources/FilamentFormResource.php
@@ -6,11 +6,15 @@
use Filament\Actions\BulkActionGroup;
use Filament\Actions\DeleteBulkAction;
use Filament\Actions\EditAction;
+use Filament\Forms\Components\Select;
+use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
+use Filament\Schemas\Components\Component;
+use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Tables\Columns\IconColumn;
use Filament\Tables\Columns\TextColumn;
@@ -68,6 +72,13 @@ public static function form(Schema $schema): Schema
->hint('(optional) complete this field to provide a custom redirect url on form completion. Use a fully qualified URL including "https://" to redirect to an external link, otherwise url will be relative to this sites domain'),
Textarea::make('description')
->columnSpanFull(),
+ Section::make('Notifications')
+ ->description('Configure email notifications for form submissions')
+ ->schema([
+ static::getNotificationEmailsField(),
+ ])
+ ->collapsible()
+ ->collapsed(),
]);
}
@@ -116,6 +127,7 @@ public static function table(Table $table): Table
'permit_guest_entries' => $record->permit_guest_entries,
'redirect_url' => $record->redirect_url,
'description' => $record->description,
+ 'notification_emails' => $record->notification_emails,
]);
$record->filamentFormFields->each(function ($field) use ($formCopy) {
@@ -164,4 +176,40 @@ public static function getPages(): array
'edit' => EditFilamentForm::route('/{record}/edit'),
];
}
+
+ protected static function getNotificationEmailsField(): Component
+ {
+ $userModel = config('filament-form-builder.user_model');
+
+ // If user model is configured, use Select with user search
+ if ($userModel && class_exists($userModel)) {
+ return Select::make('notification_emails')
+ ->label('Notification Recipients')
+ ->helperText('Select users who should receive notifications when this form is submitted.')
+ ->multiple()
+ ->searchable()
+ ->getSearchResultsUsing(function ($search) use ($userModel) {
+ return $userModel::query()
+ ->where(function ($query) use ($search) {
+ $query->where('name', 'like', "%{$search}%")
+ ->orWhere('email', 'like', "%{$search}%");
+ })
+ ->limit(50)
+ ->get()
+ ->mapWithKeys(fn ($user) => [$user->email => $user->name.' ('.$user->email.')']);
+ })
+ ->getOptionLabelsUsing(function (array $values) use ($userModel): array {
+ return $userModel::whereIn('email', $values)
+ ->get()
+ ->mapWithKeys(fn ($user) => [$user->email => $user->name.' ('.$user->email.')'])
+ ->toArray();
+ });
+ }
+
+ // Default: TagsInput for manual email entry
+ return TagsInput::make('notification_emails')
+ ->label('Notification Email Addresses')
+ ->helperText('Enter email addresses that should receive notifications when this form is submitted. Press Enter after each email.')
+ ->placeholder('email@example.com');
+ }
}
diff --git a/src/FilamentFormBuilderServiceProvider.php b/src/FilamentFormBuilderServiceProvider.php
index 18ccf22..ccafbef 100644
--- a/src/FilamentFormBuilderServiceProvider.php
+++ b/src/FilamentFormBuilderServiceProvider.php
@@ -9,6 +9,8 @@
use Tapp\FilamentFormBuilder\Livewire\FilamentForm\Show as FilamentFormShow;
use Tapp\FilamentFormBuilder\Livewire\FilamentFormUser\Entry as FilamentFormUserEntry;
use Tapp\FilamentFormBuilder\Livewire\FilamentFormUser\Show as FilamentFormUserShow;
+use Tapp\FilamentFormBuilder\Models\FilamentFormUser;
+use Tapp\FilamentFormBuilder\Observers\FilamentFormUserObserver;
class FilamentFormBuilderServiceProvider extends PackageServiceProvider
{
@@ -23,6 +25,7 @@ public function configurePackage(Package $package): void
$package->name('filament-form-builder')
->hasMigration('create_dynamic_filament_form_tables')
->hasMigration('add_schema_to_filament_form_fields')
+ ->hasMigration('add_notification_emails_to_filament_forms_table')
->hasConfigFile('filament-form-builder')
->hasRoute('routes')
->hasViews('filament-form-builder');
@@ -39,6 +42,9 @@ public function boot()
// Register the new layout components
Livewire::component('tapp.filament-form-builder.livewire.filament-form.form', FilamentForm::class);
Livewire::component('tapp.filament-form-builder.livewire.filament-form-user.entry', FilamentFormUserEntry::class);
+
+ // Register observer for form submission notifications
+ FilamentFormUser::observe(FilamentFormUserObserver::class);
}
public function packageBooted(): void
diff --git a/src/Mail/FormSubmissionNotification.php b/src/Mail/FormSubmissionNotification.php
new file mode 100644
index 0000000..9743195
--- /dev/null
+++ b/src/Mail/FormSubmissionNotification.php
@@ -0,0 +1,49 @@
+form->name,
+ );
+ }
+
+ public function content(): Content
+ {
+ return new Content(
+ markdown: 'filament-form-builder::mail.submission-notification',
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function attachments(): array
+ {
+ return [];
+ }
+}
diff --git a/src/Models/FilamentForm.php b/src/Models/FilamentForm.php
index f726e24..ecbb124 100644
--- a/src/Models/FilamentForm.php
+++ b/src/Models/FilamentForm.php
@@ -10,8 +10,11 @@
/**
* @property int $id
+ * @property string $name
+ * @property string|null $description
* @property string|null $redirect_url
* @property bool $permit_guest_entries
+ * @property array|null $notification_emails
* @property-read string $form_link
*/
class FilamentForm extends Model
@@ -22,6 +25,7 @@ class FilamentForm extends Model
protected $casts = [
'permit_guest_entries' => 'boolean',
+ 'notification_emails' => 'array',
];
public function users(): BelongsToMany
diff --git a/src/Models/FilamentFormUser.php b/src/Models/FilamentFormUser.php
index 9045af8..6684224 100644
--- a/src/Models/FilamentFormUser.php
+++ b/src/Models/FilamentFormUser.php
@@ -13,6 +13,7 @@
* @property array $entry
* @property array|null $firstEntry
* @property-read array $key_value_entry
+ * @property-read FilamentForm $filamentForm
*/
class FilamentFormUser extends Model implements HasMedia
{
diff --git a/src/Observers/FilamentFormUserObserver.php b/src/Observers/FilamentFormUserObserver.php
new file mode 100644
index 0000000..ac153ea
--- /dev/null
+++ b/src/Observers/FilamentFormUserObserver.php
@@ -0,0 +1,55 @@
+sendNotifications($filamentFormUser);
+ }
+
+ public function updated(FilamentFormUser $filamentFormUser): void
+ {
+ // Log that the updated event fired
+ \Log::info('FilamentFormUserObserver::updated() fired for entry ID: '.$filamentFormUser->id);
+
+ $this->sendNotifications($filamentFormUser);
+ }
+
+ protected function sendNotifications(FilamentFormUser $filamentFormUser): void
+ {
+ /** @var \Tapp\FilamentFormBuilder\Models\FilamentForm|null $form */
+ $form = $filamentFormUser->filamentForm;
+
+ // Check if notification emails are configured for this form
+ if (! $form || ! $form->notification_emails) {
+ \Log::info('No notification emails configured for form ID: '.($form->id ?? 'null'));
+
+ return;
+ }
+
+ // Filter out empty email addresses and send notifications
+ $emails = array_filter($form->notification_emails);
+
+ if (empty($emails)) {
+ \Log::info('Notification emails array is empty after filtering');
+
+ return;
+ }
+
+ \Log::info('Sending notifications for entry ID: '.$filamentFormUser->id.' to: '.json_encode($emails));
+
+ foreach ($emails as $email) {
+ Mail::to($email)->queue(new FormSubmissionNotification($form, $filamentFormUser));
+ }
+
+ \Log::info('Queued '.count($emails).' notification emails for entry ID: '.$filamentFormUser->id);
+ }
+}