Skip to main content
WorkflowCard displaying workflow progress with activity steps
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.

Overview

WorkflowCard provides a self-contained workflow progress display that:
  • Polls for status updates every 2 seconds
  • Shows activity steps with completion indicators
  • Handles retry logic for failed workflows
  • Displays contextual content when no workflow is active
Start workflows using Core Actions for automatic authorization, validation, and clean separation of concerns.

Basic Usage

import { WorkflowCard } from '@/core/components/workflow-card';

export default function MyPage({ stateUrls }) {
  return (
    <WorkflowCard
      title={__('Data Import')}
      description={__('Import records from external system')}
      stateUrls={stateUrls}
    >
      <p className="text-sm text-muted-foreground">
        {__('Ready to start processing')}
      </p>
    </WorkflowCard>
  );
}

Reloading Data on Completion

You can reload page props when a workflow completes using the onComplete callback:
<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.

Props

title
string
required
Card title displayed in the header.
description
string
Optional subtitle displayed below the title.
stateUrls
WorkflowStateUrlsData | null
Signed URLs from StoredWorkflow::getSignedStateUrls(). Enables automatic polling and retry functionality.
children
React.ReactNode
Content displayed when no workflow is active. Use a CoreActionButton to start the workflow.
onComplete
() => void
Callback fired when workflow completes successfully. Reload page props or update UI state here.

Backend Setup

Generate signed workflow URLs using StoredWorkflow::getSignedStateUrls():
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.

Workflow States

The card automatically adapts its display based on workflow status:
StatusDisplay
No workflowShows children slot content
Pending/StartingShows loading message
RunningActivity list with completion indicators
CompletedCollapsible activity summary
FailedActivity list with retry button
Activities show visual indicators: ✓ (completed), ⚠ (warnings), ✕ (failed), or ⟳ (running).

Retry Handling

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.

Complete Example with Core Actions

1

Create the Workflow Action

Create a Core Action to start the workflow:
<?php

namespace 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:
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:
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.

Workflow Setup Method

Workflows should implement a static setup() method to store the workflow ID immediately when created:
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

Next Steps

Core Actions

Trigger workflows with automatic authorization and validation

Workflows

Learn about workflow implementation and signed URL access