nucleus-core-ts 0.10.117 → 0.10.118

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/.build-ok CHANGED
@@ -1 +1 @@
1
- 0.10.117
1
+ 0.10.118
@@ -1,5 +1,5 @@
1
1
  import type { VerificationFlowLabels } from '../labels';
2
- import type { VerificationBulkStatusItem, VerificationEntityStatusesAction } from '../types';
2
+ import type { VerificationBulkStatusItem, VerificationCancelAction, VerificationEntityStatusesAction } from '../types';
3
3
  export type VerificationEntityListTheme = {
4
4
  container: string;
5
5
  header: string;
@@ -46,5 +46,11 @@ export type VerificationEntityListProps = {
46
46
  pageSize?: number;
47
47
  /** Spread over the English defaults; omit it and the list stays English. */
48
48
  labels?: Partial<VerificationFlowLabels>;
49
+ /**
50
+ * Ends a chain that cannot finish. This list is the only screen that knows
51
+ * which chains are still `active`, so it is where the exit belongs — until
52
+ * now the API had one and the product offered none.
53
+ */
54
+ cancelAction?: VerificationCancelAction;
49
55
  };
50
- export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
56
+ export declare function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode, onEntityClick, pageSize, labels, cancelAction, }: VerificationEntityListProps): import("react/jsx-runtime").JSX.Element;
@@ -97,7 +97,7 @@ const lightTheme = {
97
97
  };
98
98
  return map[status] ?? status;
99
99
  }
100
- export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels }) {
100
+ export function VerificationEntityList({ entityName, entityStatusesAction, theme: themeOverrides, themeMode = 'dark', onEntityClick, pageSize = 20, labels, cancelAction }) {
101
101
  const baseTheme = themeMode === 'light' ? lightTheme : darkTheme;
102
102
  const t = {
103
103
  ...baseTheme,
@@ -109,6 +109,10 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
109
109
  };
110
110
  const [activeTab, setActiveTab] = useState('all');
111
111
  const [currentPage, setCurrentPage] = useState(1);
112
+ // Which row is asking for confirmation, and what it is being told.
113
+ const [cancellingId, setCancellingId] = useState(null);
114
+ const [cancelReason, setCancelReason] = useState('');
115
+ const [cancelNote, setCancelNote] = useState(null);
112
116
  const loadStatuses = useEffectEvent((status, page)=>{
113
117
  entityStatusesAction.start({
114
118
  payload: {
@@ -128,6 +132,39 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
128
132
  setCurrentPage(page);
129
133
  loadStatuses(activeTab === 'all' ? undefined : activeTab, page);
130
134
  });
135
+ const sendCancel = useEffectEvent((item)=>{
136
+ if (!cancelAction) return;
137
+ setCancelNote(null);
138
+ cancelAction.start({
139
+ payload: {
140
+ entity_name: item.entity_name,
141
+ entity_id: item.entity_id,
142
+ ...cancelReason ? {
143
+ reason: cancelReason
144
+ } : {}
145
+ },
146
+ onAfterHandle: (res)=>{
147
+ /*
148
+ * A refusal — "only the person who started this verification, or a
149
+ * godmin, can cancel it" — arrives as a body, not always as a status,
150
+ * so it is read from the body. The row is NOT closed on a refusal:
151
+ * closing it here is the bug that made every other decision screen
152
+ * look like it had worked.
153
+ */ const response = res;
154
+ if (response?.success === false) {
155
+ setCancelNote(response.message || say.cancelChainFailed);
156
+ return;
157
+ }
158
+ setCancellingId(null);
159
+ setCancelReason('');
160
+ loadStatuses(activeTab === 'all' ? undefined : activeTab, currentPage);
161
+ },
162
+ onErrorHandle: (err)=>{
163
+ const msg = err?.message;
164
+ setCancelNote(msg || say.cancelChainFailed);
165
+ }
166
+ });
167
+ });
131
168
  useEffect(()=>{
132
169
  loadStatuses(undefined, 1);
133
170
  }, []);
@@ -294,7 +331,68 @@ export function VerificationEntityList({ entityName, entityStatusesAction, theme
294
331
  /*#__PURE__*/ _jsx("span", {
295
332
  className: `${t.statusBadge} ${getStatusBadgeClass(item.status)}`,
296
333
  children: statusLabel(say, item.status)
297
- })
334
+ }),
335
+ cancelAction && item.status === 'active' ? cancellingId === item.instance_id ? // No handlers on this wrapper: the row above is a button
336
+ // when `onEntityClick` is set, so each INTERACTIVE child
337
+ // stops the bubble itself rather than a bare div doing it
338
+ // for them.
339
+ /*#__PURE__*/ _jsxs("div", {
340
+ className: "flex flex-col items-stretch gap-2 shrink-0",
341
+ children: [
342
+ /*#__PURE__*/ _jsx("p", {
343
+ className: t.metaLabel,
344
+ children: say.cancelChainConfirm
345
+ }),
346
+ /*#__PURE__*/ _jsx("input", {
347
+ type: "text",
348
+ value: cancelReason,
349
+ placeholder: say.cancelChainReasonPlaceholder,
350
+ onClick: (e)=>e.stopPropagation(),
351
+ onKeyDown: (e)=>e.stopPropagation(),
352
+ onChange: (e)=>setCancelReason(e.target.value),
353
+ className: themeMode === 'light' ? 'rounded-lg border border-gray-200 bg-gray-50 px-2 py-1 text-[11px] text-gray-800 outline-none focus:border-gray-400' : 'rounded-lg border border-white/[0.08] bg-white/[0.04] px-2 py-1 text-[11px] text-white/80 outline-none focus:border-white/20'
354
+ }),
355
+ /*#__PURE__*/ _jsxs("div", {
356
+ className: "flex items-center gap-2",
357
+ children: [
358
+ /*#__PURE__*/ _jsx("button", {
359
+ type: "button",
360
+ onClick: (e)=>{
361
+ e.stopPropagation();
362
+ sendCancel(item);
363
+ },
364
+ className: themeMode === 'light' ? 'rounded-lg border border-red-200 bg-red-50 px-2.5 py-1 text-[11px] font-medium text-red-600 hover:bg-red-100' : 'rounded-lg border border-red-500/30 bg-red-500/20 px-2.5 py-1 text-[11px] font-medium text-red-400 hover:bg-red-500/30',
365
+ children: say.cancelChain
366
+ }),
367
+ /*#__PURE__*/ _jsx("button", {
368
+ type: "button",
369
+ onClick: (e)=>{
370
+ e.stopPropagation();
371
+ setCancellingId(null);
372
+ setCancelReason('');
373
+ setCancelNote(null);
374
+ },
375
+ className: themeMode === 'light' ? 'rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-1 text-[11px] font-medium text-gray-500 hover:bg-gray-100' : 'rounded-lg border border-white/[0.08] bg-white/[0.06] px-2.5 py-1 text-[11px] font-medium text-white/50 hover:bg-white/[0.1]',
376
+ children: say.cancel
377
+ })
378
+ ]
379
+ }),
380
+ cancelNote ? /*#__PURE__*/ _jsx("p", {
381
+ role: "alert",
382
+ className: themeMode === 'light' ? 'text-[11px] leading-4 text-amber-900' : 'text-[11px] leading-4 text-amber-200',
383
+ children: cancelNote
384
+ }) : null
385
+ ]
386
+ }) : /*#__PURE__*/ _jsx("button", {
387
+ type: "button",
388
+ onClick: (e)=>{
389
+ e.stopPropagation();
390
+ setCancellingId(item.instance_id);
391
+ setCancelNote(null);
392
+ },
393
+ className: themeMode === 'light' ? 'shrink-0 rounded-lg border border-gray-200 bg-white px-2.5 py-1 text-[11px] font-medium text-gray-600 hover:bg-gray-100' : 'shrink-0 rounded-lg border border-white/[0.08] bg-white/[0.04] px-2.5 py-1 text-[11px] font-medium text-white/60 hover:bg-white/[0.1]',
394
+ children: say.cancelChain
395
+ }) : null
298
396
  ]
299
397
  }, item.instance_id);
300
398
  })
@@ -1,4 +1,4 @@
1
1
  import '@xyflow/react/dist/style.css';
2
2
  import type { ReactElement } from 'react';
3
3
  import type { VerificationFlowPageProps } from '../types';
4
- export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, }: VerificationFlowPageProps): ReactElement;
4
+ export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction, }: VerificationFlowPageProps): ReactElement;
@@ -297,7 +297,7 @@ function buildSavePayload(flowId, entityName, flowName, flowDescription, trigger
297
297
  };
298
298
  }
299
299
  // ─── Main Component ───────────────────────────────────────────────
300
- export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab }) {
300
+ export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowUnpublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels, describeEntity, entityHref, availableChannels, initialTab, cancelAction }) {
301
301
  const theme = getVerificationFlowTheme(themeMode);
302
302
  setActiveTheme(theme);
303
303
  const say = {
@@ -1323,7 +1323,8 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
1323
1323
  entityStatusesAction: entityStatusesAction,
1324
1324
  onEntityClick: onEntityClick,
1325
1325
  themeMode: themeMode,
1326
- labels: say
1326
+ labels: say,
1327
+ cancelAction: cancelAction
1327
1328
  })
1328
1329
  })
1329
1330
  ]
@@ -192,6 +192,11 @@ export type VerificationFlowLabels = {
192
192
  openRecord: string;
193
193
  /** Shown beside a channel this deployment cannot deliver on. */
194
194
  channelUnavailable: string;
195
+ /** Ends a chain nobody can finish. Starter or godmin only. */
196
+ cancelChain: string;
197
+ cancelChainConfirm: string;
198
+ cancelChainReasonPlaceholder: string;
199
+ cancelChainFailed: string;
195
200
  verificationStatusTitle: string;
196
201
  /** "12 records for orders" — count and entity, both inside the label. */
197
202
  recordCount: (count: number, entityName: string) => string;
@@ -152,6 +152,10 @@
152
152
  pendingRecordLabel: 'Record',
153
153
  openRecord: 'Open the record',
154
154
  channelUnavailable: 'not available on this installation',
155
+ cancelChain: 'End this review',
156
+ cancelChainConfirm: 'End this review? The record goes back to pending and a new chain can be started. The decisions already made are kept.',
157
+ cancelChainReasonPlaceholder: 'Why (optional)',
158
+ cancelChainFailed: 'The review could not be ended.',
155
159
  verificationStatusTitle: 'Verification Status',
156
160
  recordCount: (count, entityName)=>`${count} record${count !== 1 ? 's' : ''} for ${entityName}`,
157
161
  noRecordsIconTitle: 'No records',
@@ -33,6 +33,23 @@ export type VerificationDecideAction = GenericAction<VerificationDecidePayload &
33
33
  entity_id: string;
34
34
  }, VerificationDecideResponse>;
35
35
  export type VerificationPendingAction = GenericAction<undefined, VerificationPendingResponse>;
36
+ /**
37
+ * Closes a chain nobody can finish any more.
38
+ *
39
+ * The API has had this since the engine did; no screen in the product ever
40
+ * called it. So a chain that stranded — an emptied role, a withdrawn flow, a
41
+ * step whose only candidate the chain itself bars — could be ended by nothing
42
+ * a person can press, and the record stayed `pending` for good. The route
43
+ * allows the person who started the chain, or a godmin, and nobody else.
44
+ */
45
+ export type VerificationCancelAction = GenericAction<{
46
+ entity_name: string;
47
+ entity_id: string;
48
+ reason?: string;
49
+ }, {
50
+ success: boolean;
51
+ message?: string;
52
+ }>;
36
53
  /**
37
54
  * Uploads the file a verifier signs with, and hands back its id.
38
55
  *
@@ -267,6 +284,11 @@ export type VerificationFlowPageProps = {
267
284
  * arrived says so here.
268
285
  */
269
286
  initialTab?: 'flow' | 'pending' | 'statuses';
287
+ /**
288
+ * Lets the starter (or a godmin) end a chain that cannot finish. Omit it and
289
+ * the records list has no cancel control, which is what shipped until now.
290
+ */
291
+ cancelAction?: VerificationCancelAction;
270
292
  };
271
293
  export type DraggedNodeType = NodeType | null;
272
294
  export type VerificationFlowPageState = {