Embed workflow progress and status on any page with real-time polling
Display workflow execution progress inline with automatic status polling and visual feedback.WorkflowCard uses signed URLs for all interactions, allowing users without manage_workflows permission to view workflow status.
You can reload page props when a workflow completes using the onComplete callback:
Copy
<WorkflowCard title={__('Export Order')} stateUrls={exportWorkflow} onComplete={() => { // Reload specific props to get updated data router.reload({ only: ['order', 'exportWorkflow'] }); }}> {/* Children displayed when no workflow is active */}</WorkflowCard>
The onComplete callback fires only once when the workflow transitions to completed, preventing duplicate reloads.
Generate signed workflow URLs using StoredWorkflow::getSignedStateUrls():
Copy
use Inly\Core\Models\StoredWorkflow;public function show(Model $model){ return AppPage::make('models/show') ->with([ 'model' => ModelData::from($model), 'workflow' => StoredWorkflow::getSignedStateUrls($model->latest_workflow_id), ]);}
Store the workflow ID in the workflow’s static setup() method (see below) so you can retrieve it with getSignedStateUrls(). The setup() method is called synchronously when the workflow is created or restarted, ensuring the ID is always stored correctly.
WorkflowCard automatically handles retries for network errors and failed workflows.Network errors show “Failed to load workflow data” with a retry button. Failed workflows display a “Retry” button that restarts the workflow via signed URL.The restart process uses session flashing to display the new workflow immediately.
<?phpnamespace App\Http\Controllers\Actions;use App\Enums\OrderStatus;use App\Enums\Permission;use App\Models\Order;use App\Workflows\ExportOrderToBusinessCentralWorkflow;use Inly\Core\Actions\CoreAction;use Inly\Core\Enums\CoreVariant;class SubmitOrderAction extends CoreAction{ protected function defaults(): void { $this->label(__('Submit order to Business Central')); $this->variant(CoreVariant::BRAND); $this->confirm(true); $this->confirmMessage(__('Are you sure you want to submit this order to Business Central? This action cannot be undone and will lock the order.')); $this->confirmText(__('Submit order')); } public function authorize(): bool { return user()->can(Permission::MANAGE_ORDERS->value); } protected function rules(): array { return [ 'order' => ['required', 'exists:orders,id'], ]; } protected function handle(Order $order): mixed { // Check if Business Central customer is set if (! $order->customer?->businessCentralCustomer?->id) { return back()->withError(__('Order cannot be submitted. Please ensure a Business Central customer is selected.')); } // Check if already submitted with a workflow that's not failed/terminated if ($order->export_to_business_central_workflow_id) { return back()->withError(__('Order has already been submitted to Business Central.')); } // Dispatch workflow ExportOrderToBusinessCentralWorkflow::start($order); // Update status $order->status = OrderStatus::SUBMITTED; $order->save(); return back()->withSuccess(__('Starting export to Business Central...')); }}
2
Pass Action and Workflow State
Retrieve the stored workflow ID and generate signed URLs in your controller:
Copy
use Inly\Core\Models\StoredWorkflow;use App\Http\Controllers\Actions\SubmitOrderAction;class OrderController extends Controller{ public function show(Order $order) { return AppPage::make('orders/show') ->with([ 'order' => OrderData::from($order), 'exportWorkflow' => StoredWorkflow::getSignedStateUrls($order->export_to_business_central_workflow_id), 'submitAction' => SubmitOrderAction::make()->params(['order' => $order->id]), ]); }}
3
Use in Frontend Component
Add the WorkflowCard to your page component. The children slot displays when no workflow is active:
Copy
import { WorkflowCard } from '@/core/components/workflow-card';import CoreActionButton from '@/core/components/core-action-button';import { router } from '@inertiajs/react';interface Props { order: Inly.Data.OrderData; exportWorkflow: Inly.Core.Data.WorkflowStateUrlsData | null; submitAction: Inly.Core.Data.CoreActionData;}export default function OrderShow({ order, exportWorkflow, submitAction }: Props) { return ( <WorkflowCard title={__('Export to Business Central')} description={__('Submit order and create sales order')} stateUrls={exportWorkflow} onComplete={() => { // Reload workflow state after completion router.reload({ only: ['order', 'exportWorkflow'] }); }} > <p className="text-sm text-muted-foreground"> {__('Ready to submit order to Business Central')} </p> <CoreActionButton action={submitAction} /> </WorkflowCard> );}
Core Actions provide automatic authorization, validation, confirmation dialogs, and clean separation between UI and business logic. This is the recommended approach for starting workflows.
Workflows should implement a static setup() method to store the workflow ID immediately when created:
Copy
class ExportOrderToBusinessCentralWorkflow extends Workflow{ public static function setup(StoredWorkflow $storedWorkflow, Order $order): void { // Store workflow ID immediately (before job runs) $order->update(['export_to_business_central_workflow_id' => $storedWorkflow->id]); // Optionally attach the resource to the workflow $storedWorkflow->setResource($order); } public function execute(Order $order) { // Workflow logic... }}
The static setup() method receives the StoredWorkflow instance as the first argument, followed by the workflow’s execution arguments. It’s called synchronously when starting or restarting a workflow.
Always assign the workflow ID in the setup() method. This ensures the ID is stored correctly both when starting a new workflow and when restarting a failed workflow, preventing orphaned workflow references.
For workflows without a specific model relationship, you can store the workflow ID using:
ValueStore::put('workflow_key', $storedWorkflow->id) for global single-instance workflows
session(['workflow_id' => $storedWorkflow->id]) for user-specific workflows