Portal Community

Example 1 — Approval Flow (Sending Side)

A component that triggers an approval interaction from the frontend and shows real-time status:

import { useInteraction } from 'edge-interact-react';
import { InteractionTimeoutError } from 'edge-interact-core';

function InvoiceApprovalButton({ invoice, managerId }: Props) {
  const { sendInteraction, isSending } = useInteraction();
  const [decision, setDecision] = useState<string | null>(null);
  const [decidedBy, setDecidedBy] = useState<string>('');

  const requestApproval = async () => {
    setDecision(null);
    try {
      const response = await sendInteraction({
        type: 'approval',
        targetUserId: managerId,
        title: `Approve Invoice ${invoice.id}`,
        payload: {
          context: `Invoice ${invoice.id} from ${invoice.vendor} for ${invoice.amount}.`,
          fields: [
            { key: 'vendor', label: 'Vendor',   value: invoice.vendor },
            { key: 'amount', label: 'Amount',   value: invoice.amount },
            { key: 'due',    label: 'Due Date', value: invoice.dueDate }
          ]
        },
        timeoutMs: 86_400_000,
        priority: 'high'
      });

      setDecision(response.outcome);
      setDecidedBy(response.respondedBy);
    } catch (err) {
      if (err instanceof InteractionTimeoutError) {
        setDecision('timeout');
      } else {
        setDecision('error');
      }
    }
  };

  return (
    <div>
      <button onClick={requestApproval} disabled={isSending}>
        {isSending ? 'Waiting for approval...' : 'Request Approval'}
      </button>
      {decision === 'approved' && <p>Approved by {decidedBy}</p>}
      {decision === 'rejected' && <p>Rejected by {decidedBy}</p>}
      {decision === 'timeout' && <p>No response received — approval timed out.</p>}
    </div>
  );
}

Example 2 — Confirmation Dialog (Self-Targeted)

The user confirms their own destructive action before it is executed:

function DeleteAccountButton({ userId }: { userId: string }) {
  const { sendInteraction } = useInteraction();

  const handleDelete = async () => {
    const response = await sendInteraction({
      type: 'confirmation',
      targetUserId: userId,   // targeting the current user themselves
      title: 'Delete Your Account',
      payload: {
        message: 'Are you sure you want to permanently delete your account? All your data will be lost.',
        confirmLabel: 'Yes, Delete My Account',
        cancelLabel: 'Cancel',
        severity: 'danger'
      },
      timeoutMs: 60_000 // 1 minute
    });

    if (response.outcome === 'confirmed') {
      await accountApi.deleteAccount(userId);
      navigate('/goodbye');
    }
  };

  return <button onClick={handleDelete}>Delete Account</button>;
}

Example 3 — WorkDesk HIL Inbox (Receiving Side)

A full HIL inbox that renders all interaction types with the appropriate components:

import { useInteractionReceiver } from 'edge-interact-react';
import {
  InteractionContainer,
  ApprovalComponent,
  ConfirmationComponent,
  NotificationComponent,
  PickerComponent,
  FormComponent
} from 'edge-interact-ui';

const RENDERERS: Record<string, React.ComponentType<any>> = {
  approval: ApprovalComponent,
  confirmation: ConfirmationComponent,
  notification: NotificationComponent,
  picker: PickerComponent,
  form: FormComponent
};

function WorkDeskInbox() {
  const { queue, respond, queueLength } = useInteractionReceiver();

  return (
    <div className="workdesk-inbox">
      <header>
        <h1>My Inbox</h1>
        {queueLength > 0 && <span className="count">{queueLength}</span>}
      </header>
      {queue.map(interaction => {
        const Renderer = RENDERERS[interaction.type];
        if (!Renderer) return null;
        return (
          <Renderer
            key={interaction.interactionId}
            interaction={interaction}
            respond={(outcome, data) => respond(interaction.interactionId, outcome, data)}
          />
        );
      })}
      {queueLength === 0 && <p className="empty">All caught up!</p>}
    </div>
  );
}