Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}

Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -32,7 +32,7 @@
"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",
"orchestra/testbench": "^10.0.0||^9.0.0",
"pestphp/pest": "^3.0||^2.34",
"pestphp/pest-plugin-arch": "^3.0||^2.7",
"pestphp/pest-plugin-laravel": "^3.0||^2.3",
Expand Down
16 changes: 16 additions & 0 deletions config/filament-form-builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('filament_forms', function (Blueprint $table): void {
$table->json('notification_emails')->nullable()->after('description');
});
}
};
20 changes: 20 additions & 0 deletions resources/views/mail/submission-notification.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<x-mail::message>
# 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') }}

<x-mail::button :url="route('filament.admin.resources.filament-forms.edit', $form)">
View Form & Entries
</x-mail::button>

Thanks,<br>
{{ config('app.name') }}
</x-mail::message>




48 changes: 48 additions & 0 deletions src/Filament/Resources/FilamentFormResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
]);
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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');
}
}
6 changes: 6 additions & 0 deletions src/FilamentFormBuilderServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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');
Expand All @@ -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
Expand Down
49 changes: 49 additions & 0 deletions src/Mail/FormSubmissionNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Tapp\FilamentFormBuilder\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Tapp\FilamentFormBuilder\Models\FilamentForm;
use Tapp\FilamentFormBuilder\Models\FilamentFormUser;

class FormSubmissionNotification extends Mailable implements ShouldQueue
{
use Queueable;
use SerializesModels;

public function __construct(
public FilamentForm $form,
public FilamentFormUser $entry,
) {}

public function envelope(): Envelope
{
$appName = config('app.name');

return new Envelope(
subject: "New {$appName} Form Submission: ".$this->form->name,
);
}

public function content(): Content
{
return new Content(
markdown: 'filament-form-builder::mail.submission-notification',
);
}

/**
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
4 changes: 4 additions & 0 deletions src/Models/FilamentForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<int, string>|null $notification_emails
* @property-read string $form_link
*/
class FilamentForm extends Model
Expand All @@ -22,6 +25,7 @@ class FilamentForm extends Model

protected $casts = [
'permit_guest_entries' => 'boolean',
'notification_emails' => 'array',
];

public function users(): BelongsToMany
Expand Down
1 change: 1 addition & 0 deletions src/Models/FilamentFormUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
55 changes: 55 additions & 0 deletions src/Observers/FilamentFormUserObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Tapp\FilamentFormBuilder\Observers;

use Illuminate\Support\Facades\Mail;
use Tapp\FilamentFormBuilder\Mail\FormSubmissionNotification;
use Tapp\FilamentFormBuilder\Models\FilamentFormUser;

class FilamentFormUserObserver
{
public function created(FilamentFormUser $filamentFormUser): void
{
$this->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);
}
}
Loading