qp-workflow-react
v1.0.15
Published
React designer, inbox, and runner for the Chd.Workflow .NET engine. Open-source sample that hosts both: https://github.com/mehmet-yoldas/Chd.Examples
Maintainers
Readme
qp-workflow-react
qp-workflow-react is the React + TypeScript companion package for the Chd.Workflow backend engine. It is React-only (no Vue). It provides ready-to-use UI pieces for both workflow design and workflow execution.
The package is designed for teams that want to move quickly without rebuilding workflow UI from scratch for every project.
It includes:
- a visual workflow designer (
TreeDesigner), - a runtime workflow runner (
WorkflowRunner), - an instance inbox (
WorkflowInbox), - a TypeScript API client,
- React hooks for custom experiences,
- localization (English / Turkish),
- external form helpers,
- named host form components (
FormType.Component), - an admin modal for workflow notification settings.
Although it pairs naturally with Chd.Workflow, the client and components can also work with another backend if the same REST contract is respected.
Chd.Workflow is the engine. This package is the UI. Chd.Examples is the host that runs both. See Architecture.
Table of Contents
- Architecture
- Why this package exists
- Main capabilities
- Installation
- Package exports
- Quick start
- Main components
WorkflowInbox- Hook usage
- Client API
- TypeScript model overview
- Field system and validation
- External form integration
- Localization
- Backend contract
- Build and publish
- Troubleshooting
- License
Architecture
Chd.Workflow, this package, and Chd.Examples are separate projects on one contract.
| Piece | Role | Start here | |---|---|---| | Chd.Workflow | .NET engine. Definitions, instances, actions, guards, and the REST API. This package calls that API. | NuGet · source | | qp-workflow-react | React designer, inbox, and runner. It does not store workflow state. | Installation · Quick start | | Chd.Examples | Open sample that hosts both. Leave, purchase, and support flows you can click through. | API · UI · how to run |
TreeDesigner edits a definition, WorkflowInbox lists instances, and WorkflowRunner executes one instance. Chd.Examples hosts those components against a running API.
Why this package exists
A workflow UI is usually more than just a form.
A real workflow screen often needs to:
- load the current workflow state,
- render fields based on the current node,
- validate inputs,
- show available transitions,
- support confirmation or comment requirements,
- handle guard failures,
- load dropdown options from the backend,
- support external forms,
- support multiple languages,
- give designers a way to edit the workflow visually.
If every project builds this from zero, teams spend a lot of time repeating the same work.
qp-workflow-react solves that by packaging the repeated parts into reusable building blocks.
This lets teams focus on:
- business logic,
- design customization,
- backend actions and rules,
- project-specific workflows.
Main capabilities
Visual workflow design
TreeDesigner provides a visual tree-based editing experience.
Runtime workflow execution
WorkflowRunner renders the active node, fields, history, and available actions.
Inbox
WorkflowInbox lists instances from GET /instances (running by default).
Custom workflow UX with hooks
The React hook export lets you build your own UI while reusing state loading and transition logic.
API abstraction
WorkflowClient wraps the REST API in a simple TypeScript class.
External form support
Helpers make it easier to connect non-React or external pages to the same workflow.
Localization
English and Turkish translations are available out of the box.
Notification admin UI
A ready modal exists for notification settings management when the backend exposes those endpoints.
Installation
npm install qp-workflow-reactRunnable host + UI: Chd.Examples. See Architecture.
Peer dependencies:
react >= 18react-dom >= 18
This package builds around a backend that follows the Chd.Workflow REST contract.
Package exports
The package currently exports the following main items:
Core API and types
WorkflowClientWorkflowClientError- all public workflow types from
src/types
React hook export
useWorkflowReact
Designer exports
TreeDesignercreateDefaultWorkflowDefinition
Runtime exports
WorkflowRunnerWorkflowInbox
External form helpers
buildExternalFormUrlparseWorkflowParamsuseWorkflowParamsuseWorkflowExternalForm
Host/admin export
WorkflowHostNotificationSettingsModal
Localization exports
LocaleProvideruseLocalegetLocalelocales- locale-related types
Quick start
A basic end-to-end scenario usually has two parts.
1. Design a workflow
import { TreeDesigner } from 'qp-workflow-react';
<TreeDesigner
apiUrl="http://localhost:5088/api/workflow"
headers={{ 'X-Workflow-User': 'admin', 'X-Workflow-Roles': 'Admin' }}
initialDefinitionId="leave-request"
locale="en"
enableFormEditor
onSaved={(definition) => console.log('saved', definition.id)}
/>2. Run a workflow instance
import { WorkflowRunner, WorkflowClient } from 'qp-workflow-react';
const client = new WorkflowClient({
apiUrl: 'http://localhost:5088/api/workflow',
headers: {
'X-Workflow-User': 'employee',
'X-Workflow-Roles': 'Employee',
},
});
const instance = await client.createInstance('leave-request', {
employeeId: '123'
});
<WorkflowRunner
apiUrl="http://localhost:5088/api/workflow"
headers={{ 'X-Workflow-User': 'employee', 'X-Workflow-Roles': 'Employee' }}
instanceId={instance.id}
locale="en"
theme="dark"
onCompleted={(result) => console.log('completed', result.id)}
/>Main components
TreeDesigner
TreeDesigner is the main visual workflow editor.
It is useful when you want to:
- create nodes visually,
- manage transitions,
- choose action handlers,
- choose guard handlers,
- set allowed roles/groups,
- edit form fields (palette, resize, optional
Splittwo-column layout), - set node timeout + timeout action,
- save definitions through the REST API.
Basic usage
<TreeDesigner
apiUrl="http://localhost:5088/api/workflow"
headers={{ 'X-Workflow-User': 'admin', 'X-Workflow-Roles': 'Admin' }}
initialDefinitionId="leave-request"
locale="en"
enableFormEditor
onSaved={(definition) => console.log(definition.id)}
/>Main props
| Prop | Type | Description |
|---|---|---|
| apiUrl | string | Base workflow API URL |
| headers? | Record<string, string> | Forwarded on designer fetches (auth, X-Workflow-User, X-Workflow-Roles) |
| initialDefinitionId? | string | Definition id to load first |
| onSaved? | (saved) => void | Called after successful save |
| locale? | 'en' \| 'tr' | UI language |
| enableFormEditor? | boolean | Enables field editing UI |
Practical notes
- The component loads action metadata from
/actions. - It loads guard metadata from
/guards. - It loads participant group data from
/admin/participant-groups. - It can create a default definition if the requested definition does not exist.
Because the designer reads action metadata from the backend, both modern class-based actions and legacy controller-style actions can appear in the same action picker when the host exposes them.
This makes it suitable for both new workflow design and editing existing definitions.
WorkflowRunner
WorkflowRunner is the runtime execution component.
It is useful when you want a ready-made UI that:
- loads the workflow state,
- renders the current step fields,
- validates values,
- displays available transitions,
- handles comments and confirmations,
- supports dark and light themes,
- refreshes state after transitions.
Basic usage
<WorkflowRunner
apiUrl="http://localhost:5088/api/workflow"
headers={{ 'X-Workflow-User': 'manager', 'X-Workflow-Roles': 'Manager' }}
instanceId="instance-abc123"
locale="en"
theme="dark"
formComponents={{ HostBudgetForm }}
onTransition={(action, state) => console.log(action, state)}
onCompleted={(instance) => console.log(instance.id)}
onError={(error) => console.error(error)}
/>Main props
| Prop | Type | Description |
|---|---|---|
| apiUrl | string | Base workflow API URL |
| headers? | Record<string, string> | Forwarded on API calls |
| instanceId | string | Instance to render |
| formComponents? | Record<string, Component> | Named React forms for FormType.Component |
| theme? | 'dark' \| 'light' | Runner theme |
| locale? | 'en' \| 'tr' | UI language |
| debug? | boolean | Shows extra debug data |
| onCompleted? | (instance) => void | Called when the workflow reaches completion |
| onTransition? | (action, newState) => void | Called after successful transition |
| onError? | (error) => void | Called on workflow errors |
| loadingComponent? | ReactNode | Custom loading UI |
| errorComponent? | (error, retry) => ReactNode | Custom error UI |
| completedComponent? | (node, instance) => ReactNode | Custom completion UI |
| className? | string | Custom CSS class |
| style? | CSSProperties | Inline style override |
Built-in behavior
The runner already handles many common tasks:
- loading
GET /instances/{id}/state, - rendering fields by type,
- preserving and updating form values,
- showing validation errors,
- calling transition endpoints,
- handling
requiresConfirmation, - handling
requiresComment, - handling guard failures returned by the backend,
- loading field options when needed,
- grouping fields after a
Split, - persisting
File/Imageas a small data URL on the instance, - rendering
RichTextwith the built-in editor, - showing a history strip,
- redirecting
FormType.Externaland mountingFormType.Component.
This is often enough for production usage without writing a custom runtime screen.
WorkflowInbox
Lists instance summaries from GET /instances.
Default filter is Running. Change refreshToken after create / transition so the list reloads.
import { WorkflowInbox } from 'qp-workflow-react';
<WorkflowInbox
apiUrl="http://localhost:5088/api/workflow"
headers={{ 'X-Workflow-User': 'manager', 'X-Workflow-Roles': 'Manager' }}
locale="en"
theme="dark"
selectedId={instanceId}
refreshToken={inboxKey}
onSelect={(row) => setInstanceId(row.id)}
/>| Prop | Type | Description |
|---|---|---|
| apiUrl | string | Base workflow API URL |
| headers? | Record<string, string> | Identity / auth headers |
| locale? | 'en' \| 'tr' | UI language |
| theme? | 'dark' \| 'light' | Theme |
| selectedId? | string | Highlighted row |
| definitionId? | string | Optional definition filter |
| refreshToken? | number \| string | Change to reload |
| onSelect? | (row) => void | Row click |
WorkflowHostNotificationSettingsModal
This modal is intended for host/admin screens.
It communicates with:
GET /admin/notification-settingsPUT /admin/notification-settings
It helps manage workflow notification-related settings such as:
- SMTP host,
- SMTP port,
- SMTP user,
- password presence,
- from email,
- SSL usage,
- subject and body templates,
- group member resolution SQL,
- email column name.
Example
import { WorkflowHostNotificationSettingsModal } from 'qp-workflow-react';
<WorkflowHostNotificationSettingsModal
open={open}
onClose={() => setOpen(false)}
apiUrl="http://localhost:5088/api/workflow"
locale="en"
/>A 503 response usually means the backend host has not enabled the required persistence service for this admin area.
Hook usage
The package exports the React hook as useWorkflowReact.
This hook is useful when:
WorkflowRunneris too opinionated for your screen,- you want your own layout,
- you still want to reuse workflow loading and transition logic.
Example
import { useWorkflowReact, WorkflowClient } from 'qp-workflow-react';
const client = new WorkflowClient({
apiUrl: 'http://localhost:5088/api/workflow',
headers: { 'X-Workflow-User': 'employee', 'X-Workflow-Roles': 'Employee' },
});
function CustomRunner({ instanceId }: { instanceId: string }) {
const {
state,
currentNode,
fields,
availableActions,
values,
errors,
isLoading,
error,
setValue,
transition,
reload,
} = useWorkflowReact({ client, instanceId });
if (isLoading) return <p>Loading...</p>;
if (error) return <p>{error.message}</p>;
return (
<div>
<h2>{currentNode?.title ?? currentNode?.name}</h2>
{fields.map(field => (
<div key={field.name}>
<label>{field.label ?? field.name}</label>
<input
value={String(values[field.name] ?? '')}
onChange={e => setValue(field.name, e.target.value)}
/>
{errors[field.name] && <div>{errors[field.name]}</div>}
</div>
))}
{availableActions.map(action => (
<button key={action.id} onClick={() => transition(action.action)}>
{action.label ?? action.action}
</button>
))}
<button onClick={() => reload()}>Reload</button>
</div>
);
}What the hook returns
The hook exposes:
- current workflow state,
- current node,
- fields,
- available actions,
- history,
- breadcrumb,
- form values,
- validation errors,
- loading/error state,
- helpers such as
setValue,setValues,transition,validate,reload,createInstance,cancel.
This is a good option when you need a custom UI but do not want to rebuild the workflow data layer yourself.
Client API
WorkflowClient is a TypeScript wrapper around the workflow REST API.
It is framework-agnostic at the data level and can be used outside the ready React components.
Basic setup
import { WorkflowClient } from 'qp-workflow-react';
const client = new WorkflowClient({
apiUrl: 'http://localhost:5088/api/workflow',
headers: {
'X-Workflow-User': 'employee',
'X-Workflow-Roles': 'Employee',
},
onError: (error) => console.error(error)
});Helpful runtime methods
client.setAuthToken('Bearer token');
client.clearAuthToken();
client.setApiUrl('https://example.com/api/workflow');Main methods
Definitions
getDefinitions(tenantId?)getDefinition(id)createDefinition(definition)updateDefinition(id, definition)deleteDefinition(id)
Instances
getInstances({ status?, definitionId?, tenantId? })getInstance(id)getState(instanceId)createInstance(definitionId, data?, userId?)transition(instanceId, action, data?, userId?, comment?)validate(instanceId, data)getFieldOptions(instanceId, fieldName)cancel(instanceId, request?)
Admin
getNotificationAdminSettings()updateNotificationAdminSettings(body)
Error model
The client throws WorkflowClientError.
Important properties include:
messagestatuserrors?
This is useful when you want to surface backend validation details directly in your UI.
Working with legacy backend actions
The React package does not need a separate legacy mode.
If the backend exposes legacy controller-style actions through the normal /actions metadata endpoint, they are treated the same way as other workflow actions in the designer and runtime flow.
In practice this means:
- the designer can select a legacy action by name,
- external forms can submit the same field payload,
- the backend decides whether the action is implemented by
IWorkflowActionor by a legacy annotated controller/service method.
TypeScript model overview
The package exports a broad public type model that mirrors the backend contract.
Important examples include:
Enums and union types
NodeTypeFieldTypeFormTypeWorkflowStatusRuleTypeButtonStyle
Core models
FieldOptionFieldTransitionRuleRuleMappingNodeWorkflowDefinitionWorkflowInstanceWorkflowInstanceSummaryHistoryEntryWorkflowState
Metadata models
WorkflowActionInfoWorkflowInputPropertyWorkflowOutputPropertyWorkflowGuardInfoWorkflowRuleInfo
Client models
WorkflowClientOptionsCreateInstanceRequestTransitionRequestCancelRequestValidationResultWorkflowNotificationAdminSettings
This makes the package pleasant to use in TypeScript-heavy projects because both component usage and API usage remain strongly typed.
Field system and validation
The field system supports both backend-aligned field types and frontend-friendly validation behavior.
Supported field types
Current public field type support includes:
TextTextAreaNumberDecimalDateDateTimeTimeCheckboxRadioDropdownMultiSelectFileImageRichTextHiddenEmailPhoneCurrencySectionSplit(starts an optional two-column row; without it, fields stay single column)
File and Image persist as { name, contentType, dataUrl } on instance data (keep under about 4 MB).
Validation behavior
The runtime supports practical validation such as:
- required checks,
- min and max values,
- max length,
- regex validation,
- type-based validation for number/date/email/phone/currency.
Field options
For selectable fields, options can come from:
- static field configuration,
- backend field option endpoints.
Visibility support
The field model also includes support for visibility expressions and guard-related visibility metadata.
This helps advanced forms stay dynamic without requiring every project to write custom visibility logic from zero.
External form integration
External forms are useful when the workflow step should be completed in another page or system.
Examples:
- a legacy MVC/Razor page,
- an older admin form,
- a third-party hosted screen,
- a specialized custom data entry module.
Helper: buildExternalFormUrl
This helper creates a URL with workflow context parameters.
Typical values added include:
workflowInstanceIdworkflowNodeIdworkflowDefinitionIdworkflowActionsworkflowCallback
Helper: parseWorkflowParams
Reads workflow context from query string.
Hook: useWorkflowParams
Convenient React wrapper over parseWorkflowParams().
Hook: useWorkflowExternalForm
A higher-level helper for external form pages.
It helps with:
- reading workflow context,
- calling transition APIs,
- managing loading and error state,
- returning to the callback URL after submission.
Example
import { useWorkflowExternalForm } from 'qp-workflow-react';
function ExternalApprovalPage() {
const workflow = useWorkflowExternalForm({
apiUrl: '/api/workflow'
});
async function submitApprove() {
await workflow.submit('approve', {
note: 'Approved from external page'
});
}
return (
<div>
<button onClick={submitApprove} disabled={workflow.loading}>
Approve
</button>
{workflow.error && <p>{workflow.error}</p>}
{workflow.success && <p>{workflow.success}</p>}
</div>
);
}This is especially useful when not every workflow step should stay inside the main React runner screen.
Localization
The package currently includes English and Turkish locale support.
Basic usage
import { getLocale } from 'qp-workflow-react';
const t = getLocale('en');
console.log(t.common.save);Provider-based usage
import { LocaleProvider } from 'qp-workflow-react';
<LocaleProvider locale="tr">
<App />
</LocaleProvider>Supported locale codes
'en''tr'
Where localization is used
Localization is used in areas such as:
- common labels,
- runner UI,
- designer UI,
- validation messages,
- action labels,
- host notification settings modal.
This makes the package easier to adopt in multilingual internal systems.
Backend contract
This package is designed around the Chd.Workflow backend contract.
Expected endpoints
| Method | Path |
|---|---|
| GET | /definitions |
| GET | /definitions/{id} |
| POST | /definitions |
| PUT | /definitions/{id} |
| DELETE | /definitions/{id} |
| GET | /instances |
| GET | /instances/{id} |
| GET | /instances/{id}/state |
| GET | /instances/{id}/field-options/{fieldName} |
| POST | /instances |
| POST | /instances/{id}/transition |
| POST | /instances/{id}/validate |
| POST | /instances/{id}/cancel |
| GET | /actions |
| GET | /actions/grouped |
| GET | /guards |
| GET | /rules |
| GET | /rules/grouped |
| GET | /rules/{name} |
| GET | /admin/notification-settings |
| PUT | /admin/notification-settings |
| GET | /admin/participant-groups |
JSON style expectations
The package assumes a backend that behaves similarly to the current Chd.Workflow implementation.
In practice, that means:
- camelCase JSON is preferred,
- enum values should be serialized in a compatible way,
- error payloads should include
errorand optionallyerrors.
Identity headers used by the sample and demo:
X-Workflow-UserX-Workflow-Roles
A real host should send a JWT instead. Empty AllowedRoles on a node means anyone can act.
If you build your own compatible backend, the React package can still be reused.
Build and publish
Important package information from the current project setup:
- package name:
qp-workflow-react - build tool:
tsup - output:
dist - peer dependencies: React 18+
Common commands
npm install
npm run build
npm run dev
npm run typecheckPublish notes
When publishing, keep these fields correct in package.json:
nameversiondescriptionmainmoduletypesrepositorylicensefiles
These details matter because the package is intended to be consumed as a reusable npm library, not only as a local project.
Runnable host + UI: Chd.Examples. After changing this package, run npm run build so file: consumers see the new dist.
Troubleshooting
Designer cannot load actions or guards
Check that the backend API is running and that:
/actionsworks,/guardsworks,- CORS is configured correctly,
- the API URL is correct.
Participant groups are empty
Check /admin/participant-groups. If the backend does not implement or register participant directory support, the list may be empty.
Transition fails with validation messages
The backend may be returning field-level validation details. Catch WorkflowClientError and inspect error.errors.
Guard failure is returned from the backend
A denied guard usually comes back with code = "GUARD_FAILED". This means the transition was blocked intentionally by workflow logic.
Notification settings modal returns not available
A 503 usually means the backend host has not enabled the notification admin settings service.
Inbox stays empty
Confirm GET /instances?status=Running and that the current user headers are sent. Rebuild this package if a local file: consumer still shows an old UI.
External form flow does not return properly
Check:
- query string parameters,
- callback URL value,
- transition call success,
- whether the page is opened in the same window or inside an iframe.
License
MIT License. See LICENSE.
