From invoices to contracts, get notified on time, every time, across email, SMS, and WhatsApp Get Started in Minutes

Creating Dynamic Modals in FilamentPHP with Livewire Action Arguments

Creating Dynamic Modals in FilamentPHP with Livewire Action Arguments
Creating Dynamic Modals in FilamentPHP with Livewire Action Arguments

When developing the storefront for Mondula, an IT asset management solution (https://filamentapps.dev/products/mondula-it-asset-management-system), I encountered an interesting challenge. One of the key features we wanted to implement was the ability for customers to place bids on products. The goal was to create a seamless user experience where clicking a "Place a Bid" button would open a modal pop-up, allowing the customer to enter their bid amount. Additionally, we needed to validate the bid amount against a minimum threshold.

In this blog post, I will walk you through how to set up this functionality, explaining each part of the code step-by-step and how you can pass action arguments to a Livewire component in FilamentPHP.

Place Bid modal pop-up
1. Blade Component for Triggering the Action

First, we need to trigger the bidding action programmatically. In the Blade component, we use Livewire's wire:click directive to call a function that loads the product:

<button
  class='px-4 py-2 bg-blue-600 text-white rounded-md capitalize inline-flex items-center gap-x-3'
  wire:click="loadProduct({{$product->id}})"
>
  <svg><!-- SVG code for the icon --></svg>
  <span>Make a bid</span>
</button>

This button, when clicked, will call the loadProduct method in our Livewire component, passing the product's ID as an argument.

2. Livewire Component: Index Class


Now, let's dive into the Livewire component that handles the bidding logic:

<?php

namespace App\Livewire\Shop;

use Filament\Forms;
use Livewire\Component;
use App\Models\Shop\Product;
use App\Models\Shop\Bidding;
use Illuminate\Contracts\View\View;
use Filament\Forms\Contracts\HasForms;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Notifications\Notification;
use Filament\Actions\Contracts\HasActions;
use Filament\Actions\Action;
use Filament\Actions\Concerns\InteractsWithActions;

class Index extends Component implements HasForms, HasActions
{
    use InteractsWithForms;
    use InteractsWithActions;

    public ?array $data = [];
    public ?Product $product = null;

    // ... (other methods)
}

This component uses the InteractsWithForms and InteractsWithActions traits, which provide the necessary functionality for working with Filament Forms and Actions.

3. The placeBidding Method

The heart of our bidding system lies in the placeBidding method:

public function placeBidding(): Action
{
    return Action::make('placeBidding')
        ->requiresConfirmation()
        ->modalIcon('heroicon-o-banknotes')
        ->modalHeading('Place a Bid for ' . ($this->product->name ?? 'Product'))
        ->modalDescription('Once selected, you will get an email notification on how to proceed with payment')
        ->modalSubmitActionLabel('Place Bid')
        ->form([
            Forms\Components\TextInput::make('amount')
                ->label('Bid Amount')
                ->prefix(Currency::getCurrency(setting('site.currency'))->symbol)
                ->numeric()
                ->required()
                ->minValue($this->product->minimum_bid_amount ?? '0')
                ->maxValue(10000)
                ->hint('Minimum Bid Amount: ' . ($this->product->minimum_bid_amount ?? '0'))
        ])
        ->action(function (array $data, array $arguments){
            $product = Product::find($arguments['product_id']);
            if ($product) {
                Bidding::updateOrCreate(
                    [
                        'shop_customer_id' => auth()->id(),
                        'shop_product_id' => $product->id,
                    ],
                    [
                        'bid_amount' => (int)$data['amount']
                    ]
                );

                Notification::make()
                    ->title('Bid Placed Successfully')
                    ->success()
                    ->body('Your bid has successfully been sent')
                    ->send();
            }
        });
}

This method does several important things:

  1. It creates a Filament Action for placing a bid.
  2. It sets up a confirmation modal with a custom icon, heading, and description.
  3. It defines a form within the modal, including a text input for the bid amount.
  4. It sets validation rules for the bid amount, including a minimum value based on the product's minimum bid amount.
  5. It defines the action to be taken when the form is submitted, which includes creating or updating a Bidding record and sending a success notification.

4. The loadProduct Method

The loadProduct method is called when the user clicks the "Make a bid" button:

public function loadProduct($productId)
{
    $this->product = Product::find($productId);

    if ($this->product) {
        $this->mountAction('placeBidding', ['product_id' => $productId]);
    } else {
        Notification::make()
            ->title('Product Not Found')
            ->danger()
            ->body('Unable to load the product. Please try again.')
            ->send();
    }
}

This method:

  1. Finds the product by ID.
  2. If the product is found, it mounts the placeBidding action, passing the product ID as an argument.
  3. If the product is not found, it sends an error notification.

5. The getActions Method

This method simply returns an array of available actions for the component:

protected function getActions(): array
{
    return [
        $this->placeBidding(),
    ];
}

Conclusion

By leveraging Filament's Action API and Livewire's reactivity, we've created a dynamic and user-friendly bidding system. This approach allows for real-time interaction and validation, enhancing the overall user experience.

The key to making this work smoothly is passing the product ID from the Blade template to the Livewire component, and then using that ID to mount the appropriate action with the necessary context. This pattern can be adapted for various other scenarios where you need to trigger complex actions based on user interaction with specific items in a list or grid.

Stay on Top of Every Deadline

Never worry about missing an important date again—track your contracts, licenses, and warranties with ease!