nucleus-core-ts 0.10.55 → 0.10.57
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 +1 -1
- package/dist/fe/components/VerificationFlowPage/components/PendingTab.d.ts +4 -2
- package/dist/fe/components/VerificationFlowPage/components/PendingTab.js +90 -4
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.d.ts +1 -1
- package/dist/fe/components/VerificationFlowPage/components/VerificationFlowPage.js +2 -1
- package/dist/fe/components/VerificationFlowPage/components/decisionPlan.d.ts +42 -0
- package/dist/fe/components/VerificationFlowPage/components/decisionPlan.js +51 -0
- package/dist/fe/components/VerificationFlowPage/labels.d.ts +10 -0
- package/dist/fe/components/VerificationFlowPage/labels.js +5 -0
- package/dist/fe/components/VerificationFlowPage/types/index.d.ts +21 -0
- package/package.json +1 -1
package/dist/.build-ok
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.10.
|
|
1
|
+
0.10.57
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { ReactElement } from 'react';
|
|
2
2
|
import type { VerificationFlowLabels } from '../labels';
|
|
3
|
-
import type { VerificationDecideAction, VerificationPendingAction } from '../types';
|
|
3
|
+
import type { VerificationDecideAction, VerificationPendingAction, VerificationSignatureUploadAction } from '../types';
|
|
4
4
|
interface PendingTabProps {
|
|
5
5
|
pendingAction: VerificationPendingAction;
|
|
6
6
|
decideAction: VerificationDecideAction;
|
|
7
|
+
/** Omit it and a signature-required step says so instead of failing on Approve. */
|
|
8
|
+
uploadSignatureAction?: VerificationSignatureUploadAction;
|
|
7
9
|
onDecisionMade?: (entityName: string, entityId: string) => void;
|
|
8
10
|
/** Spread over the English defaults; omit it and the tab stays English. */
|
|
9
11
|
labels?: Partial<VerificationFlowLabels>;
|
|
10
12
|
}
|
|
11
|
-
export declare function PendingTab({ pendingAction, decideAction, onDecisionMade, labels, }: PendingTabProps): ReactElement;
|
|
13
|
+
export declare function PendingTab({ pendingAction, decideAction, uploadSignatureAction, onDecisionMade, labels, }: PendingTabProps): ReactElement;
|
|
12
14
|
export {};
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useEffect, useEffectEvent } from 'react';
|
|
3
|
+
import { useEffect, useEffectEvent, useState } from 'react';
|
|
4
4
|
import { DEFAULT_VERIFICATION_FLOW_LABELS } from '../labels';
|
|
5
5
|
import { useVerificationFlowStore } from '../store';
|
|
6
|
+
import { planDecision, signatureFileType } from './decisionPlan';
|
|
6
7
|
import { getActiveTheme } from '../theme/store';
|
|
7
|
-
export function PendingTab({ pendingAction, decideAction, onDecisionMade, labels }) {
|
|
8
|
+
export function PendingTab({ pendingAction, decideAction, uploadSignatureAction, onDecisionMade, labels }) {
|
|
9
|
+
// Local, not in the store: only one card is in deciding mode at a time, and
|
|
10
|
+
// a chosen file must not survive cancelling out of that card.
|
|
11
|
+
const [signatureFile, setSignatureFile] = useState(null);
|
|
12
|
+
const [signatureNote, setSignatureNote] = useState(null);
|
|
8
13
|
const theme = getActiveTheme();
|
|
9
14
|
const say = {
|
|
10
15
|
...DEFAULT_VERIFICATION_FLOW_LABELS,
|
|
@@ -38,22 +43,81 @@ export function PendingTab({ pendingAction, decideAction, onDecisionMade, labels
|
|
|
38
43
|
useEffect(()=>{
|
|
39
44
|
loadPending();
|
|
40
45
|
}, []);
|
|
41
|
-
const
|
|
46
|
+
const sendDecision = useEffectEvent((item, decision, signatureId)=>{
|
|
42
47
|
decideAction.start({
|
|
43
48
|
payload: {
|
|
44
49
|
entity_name: item.entity_name,
|
|
45
50
|
entity_id: item.entity_id,
|
|
46
51
|
decision,
|
|
47
|
-
reason: store.decisionReason || undefined
|
|
52
|
+
reason: store.decisionReason || undefined,
|
|
53
|
+
...signatureId ? {
|
|
54
|
+
signature_id: signatureId
|
|
55
|
+
} : {}
|
|
48
56
|
},
|
|
49
57
|
onAfterHandle: ()=>{
|
|
50
58
|
store.setDecidingId(null);
|
|
51
59
|
store.setDecisionReason('');
|
|
60
|
+
setSignatureFile(null);
|
|
61
|
+
setSignatureNote(null);
|
|
52
62
|
onDecisionMade?.(item.entity_name, item.entity_id);
|
|
53
63
|
loadPending();
|
|
54
64
|
}
|
|
55
65
|
});
|
|
56
66
|
});
|
|
67
|
+
/*
|
|
68
|
+
* A step can demand a signature, and until this existed nothing on this
|
|
69
|
+
* screen could provide one. The flow builder offers the checkbox, the node
|
|
70
|
+
* shows a "✍ Signature" badge, and Approve posted without a signature_id --
|
|
71
|
+
* so the engine answered "Signature is required for this verification step"
|
|
72
|
+
* and the step could not be completed through the product at all.
|
|
73
|
+
*
|
|
74
|
+
* The engine requires a real file OWNED BY the verifier, so the file is
|
|
75
|
+
* uploaded as the signed-in user and only its id is sent. A rejection needs
|
|
76
|
+
* no signature: you do not sign what you are refusing.
|
|
77
|
+
*/ const handleDecide = useEffectEvent((item, decision)=>{
|
|
78
|
+
setSignatureNote(null);
|
|
79
|
+
const plan = planDecision({
|
|
80
|
+
decision,
|
|
81
|
+
requireSignature: item.require_signature,
|
|
82
|
+
hasUploadAction: Boolean(uploadSignatureAction),
|
|
83
|
+
hasFile: Boolean(signatureFile)
|
|
84
|
+
});
|
|
85
|
+
if (plan.kind === 'send') {
|
|
86
|
+
sendDecision(item, decision);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (plan.kind === 'blocked') {
|
|
90
|
+
setSignatureNote(plan.reason === 'no-upload-action' ? say.signatureUnavailable : say.signatureMissing);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!uploadSignatureAction || !signatureFile) return;
|
|
94
|
+
const form = new FormData();
|
|
95
|
+
form.append('files', signatureFile);
|
|
96
|
+
form.append('data', JSON.stringify({
|
|
97
|
+
name: signatureFile.name,
|
|
98
|
+
original_name: signatureFile.name,
|
|
99
|
+
path: 'uploads/files',
|
|
100
|
+
size: signatureFile.size,
|
|
101
|
+
mime_type: signatureFile.type || 'application/octet-stream',
|
|
102
|
+
extension: signatureFile.name.split('.').pop() || '',
|
|
103
|
+
type: signatureFileType(signatureFile.type)
|
|
104
|
+
}));
|
|
105
|
+
setSignatureNote(say.signatureUploading);
|
|
106
|
+
uploadSignatureAction.start({
|
|
107
|
+
payload: form,
|
|
108
|
+
onAfterHandle: (res)=>{
|
|
109
|
+
const id = res?.data?.id;
|
|
110
|
+
if (!id) {
|
|
111
|
+
// Reported, never swallowed: a decision that silently did not happen
|
|
112
|
+
// is worse than one that failed loudly.
|
|
113
|
+
setSignatureNote(say.signatureUploadFailed);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
sendDecision(item, decision, id);
|
|
117
|
+
},
|
|
118
|
+
onErrorHandle: ()=>setSignatureNote(say.signatureUploadFailed)
|
|
119
|
+
});
|
|
120
|
+
});
|
|
57
121
|
if (store.isLoadingPending) {
|
|
58
122
|
return /*#__PURE__*/ _jsx("div", {
|
|
59
123
|
className: "flex items-center justify-center py-20",
|
|
@@ -121,6 +185,26 @@ export function PendingTab({ pendingAction, decideAction, onDecisionMade, labels
|
|
|
121
185
|
store.decidingId === item.instance_id ? /*#__PURE__*/ _jsxs("div", {
|
|
122
186
|
className: theme.pending.card.actions,
|
|
123
187
|
children: [
|
|
188
|
+
item.require_signature ? /*#__PURE__*/ _jsxs("label", {
|
|
189
|
+
className: "flex flex-col gap-1 text-xs",
|
|
190
|
+
children: [
|
|
191
|
+
/*#__PURE__*/ _jsx("span", {
|
|
192
|
+
children: say.signaturePrompt
|
|
193
|
+
}),
|
|
194
|
+
/*#__PURE__*/ _jsx("input", {
|
|
195
|
+
type: "file",
|
|
196
|
+
onChange: (e)=>{
|
|
197
|
+
setSignatureFile(e.target.files?.[0] ?? null);
|
|
198
|
+
setSignatureNote(null);
|
|
199
|
+
},
|
|
200
|
+
className: theme.pending.input
|
|
201
|
+
}),
|
|
202
|
+
signatureNote ? /*#__PURE__*/ _jsx("span", {
|
|
203
|
+
className: "text-red-600",
|
|
204
|
+
children: signatureNote
|
|
205
|
+
}) : null
|
|
206
|
+
]
|
|
207
|
+
}) : null,
|
|
124
208
|
/*#__PURE__*/ _jsx("input", {
|
|
125
209
|
type: "text",
|
|
126
210
|
placeholder: say.reasonPlaceholder,
|
|
@@ -145,6 +229,8 @@ export function PendingTab({ pendingAction, decideAction, onDecisionMade, labels
|
|
|
145
229
|
onClick: ()=>{
|
|
146
230
|
store.setDecidingId(null);
|
|
147
231
|
store.setDecisionReason('');
|
|
232
|
+
setSignatureFile(null);
|
|
233
|
+
setSignatureNote(null);
|
|
148
234
|
},
|
|
149
235
|
className: theme.pending.cancelButton,
|
|
150
236
|
children: say.cancel
|
|
@@ -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, flowDeleteAction, pendingAction, decideAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, }: VerificationFlowPageProps): ReactElement;
|
|
4
|
+
export declare function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending, showEntityStatuses, className, themeMode, labels, }: VerificationFlowPageProps): ReactElement;
|
|
@@ -280,7 +280,7 @@ function buildSavePayload(flowId, entityName, flowName, flowDescription, trigger
|
|
|
280
280
|
};
|
|
281
281
|
}
|
|
282
282
|
// ─── Main Component ───────────────────────────────────────────────
|
|
283
|
-
export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowDeleteAction, pendingAction, decideAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels }) {
|
|
283
|
+
export function VerificationFlowPage({ entityName, title, subtitle, flowListAction, flowGetAction, flowSaveAction, flowPublishAction, flowDeleteAction, pendingAction, decideAction, uploadSignatureAction, listUsersAction, listRolesAction, entityStatusesAction, onFlowSelected, onDecisionMade, onEntityClick, onBack, showPending = true, showEntityStatuses = false, className, themeMode = 'dark', labels }) {
|
|
284
284
|
const theme = getVerificationFlowTheme(themeMode);
|
|
285
285
|
setActiveTheme(theme);
|
|
286
286
|
const say = {
|
|
@@ -1205,6 +1205,7 @@ export function VerificationFlowPage({ entityName, title, subtitle, flowListActi
|
|
|
1205
1205
|
children: /*#__PURE__*/ _jsx(PendingTab, {
|
|
1206
1206
|
pendingAction: pendingAction,
|
|
1207
1207
|
decideAction: decideAction,
|
|
1208
|
+
uploadSignatureAction: uploadSignatureAction,
|
|
1208
1209
|
onDecisionMade: onDecisionMade,
|
|
1209
1210
|
labels: say
|
|
1210
1211
|
})
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What pressing Approve or Reject on a pending card should actually do.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out as a plain function because the interesting cases are the ones
|
|
5
|
+
* that must NOT send a decision, and those are the easiest to get wrong from
|
|
6
|
+
* inside a component: a step that demands a signature with no file chosen, and
|
|
7
|
+
* a screen the host wired no upload action into. Before this existed, both of
|
|
8
|
+
* them posted the decision anyway and the engine answered "Signature is
|
|
9
|
+
* required for this verification step" — a step nobody could complete through
|
|
10
|
+
* the product, with the checkbox that caused it sitting in the flow builder.
|
|
11
|
+
*
|
|
12
|
+
* Rejection never needs a signature: you do not sign what you are refusing.
|
|
13
|
+
*/
|
|
14
|
+
export type DecisionPlan = {
|
|
15
|
+
kind: 'send';
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'upload-then-send';
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'blocked';
|
|
20
|
+
reason: 'no-upload-action' | 'no-file';
|
|
21
|
+
};
|
|
22
|
+
export declare function planDecision(input: {
|
|
23
|
+
decision: 'approved' | 'rejected';
|
|
24
|
+
requireSignature: boolean;
|
|
25
|
+
hasUploadAction: boolean;
|
|
26
|
+
hasFile: boolean;
|
|
27
|
+
}): DecisionPlan;
|
|
28
|
+
/**
|
|
29
|
+
* Which `files.type` a signature upload should declare.
|
|
30
|
+
*
|
|
31
|
+
* The column is an enum the HOST configures, and a generic component cannot
|
|
32
|
+
* invent a value for it. The first version of this sent `type: 'signature'`
|
|
33
|
+
* and the install answered, from its own list: "type must be one of: image,
|
|
34
|
+
* document, video, audio, profile_picture" — a 400 the reviewer saw only as
|
|
35
|
+
* "the signature could not be uploaded".
|
|
36
|
+
*
|
|
37
|
+
* So it is derived from the file itself and lands on one of the names every
|
|
38
|
+
* install carries. `document` is the fallback because a signature that is not
|
|
39
|
+
* an image is a signed document, and because it is the safest of the five to
|
|
40
|
+
* be wrong about.
|
|
41
|
+
*/
|
|
42
|
+
export declare function signatureFileType(mimeType: string | undefined): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What pressing Approve or Reject on a pending card should actually do.
|
|
3
|
+
*
|
|
4
|
+
* Pulled out as a plain function because the interesting cases are the ones
|
|
5
|
+
* that must NOT send a decision, and those are the easiest to get wrong from
|
|
6
|
+
* inside a component: a step that demands a signature with no file chosen, and
|
|
7
|
+
* a screen the host wired no upload action into. Before this existed, both of
|
|
8
|
+
* them posted the decision anyway and the engine answered "Signature is
|
|
9
|
+
* required for this verification step" — a step nobody could complete through
|
|
10
|
+
* the product, with the checkbox that caused it sitting in the flow builder.
|
|
11
|
+
*
|
|
12
|
+
* Rejection never needs a signature: you do not sign what you are refusing.
|
|
13
|
+
*/ export function planDecision(input) {
|
|
14
|
+
if (input.decision === 'rejected') return {
|
|
15
|
+
kind: 'send'
|
|
16
|
+
};
|
|
17
|
+
if (!input.requireSignature) return {
|
|
18
|
+
kind: 'send'
|
|
19
|
+
};
|
|
20
|
+
if (!input.hasUploadAction) return {
|
|
21
|
+
kind: 'blocked',
|
|
22
|
+
reason: 'no-upload-action'
|
|
23
|
+
};
|
|
24
|
+
if (!input.hasFile) return {
|
|
25
|
+
kind: 'blocked',
|
|
26
|
+
reason: 'no-file'
|
|
27
|
+
};
|
|
28
|
+
return {
|
|
29
|
+
kind: 'upload-then-send'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Which `files.type` a signature upload should declare.
|
|
34
|
+
*
|
|
35
|
+
* The column is an enum the HOST configures, and a generic component cannot
|
|
36
|
+
* invent a value for it. The first version of this sent `type: 'signature'`
|
|
37
|
+
* and the install answered, from its own list: "type must be one of: image,
|
|
38
|
+
* document, video, audio, profile_picture" — a 400 the reviewer saw only as
|
|
39
|
+
* "the signature could not be uploaded".
|
|
40
|
+
*
|
|
41
|
+
* So it is derived from the file itself and lands on one of the names every
|
|
42
|
+
* install carries. `document` is the fallback because a signature that is not
|
|
43
|
+
* an image is a signed document, and because it is the safest of the five to
|
|
44
|
+
* be wrong about.
|
|
45
|
+
*/ export function signatureFileType(mimeType) {
|
|
46
|
+
const mime = (mimeType || '').toLowerCase();
|
|
47
|
+
if (mime.startsWith('image/')) return 'image';
|
|
48
|
+
if (mime.startsWith('video/')) return 'video';
|
|
49
|
+
if (mime.startsWith('audio/')) return 'audio';
|
|
50
|
+
return 'document';
|
|
51
|
+
}
|
|
@@ -87,6 +87,16 @@ export type VerificationFlowLabels = {
|
|
|
87
87
|
noUserAssigned: string;
|
|
88
88
|
/** The badge on a verifier node that demands a signature. */
|
|
89
89
|
signatureBadge: string;
|
|
90
|
+
/** Prompt above the file picker on a step that demands a signature. */
|
|
91
|
+
signaturePrompt: string;
|
|
92
|
+
/** Shown when Approve is pressed on such a step with no file chosen. */
|
|
93
|
+
signatureMissing: string;
|
|
94
|
+
/** Shown while the chosen file is being uploaded. */
|
|
95
|
+
signatureUploading: string;
|
|
96
|
+
/** Shown when the upload itself fails. */
|
|
97
|
+
signatureUploadFailed: string;
|
|
98
|
+
/** Shown when the host wired no upload action at all. */
|
|
99
|
+
signatureUnavailable: string;
|
|
90
100
|
recipientAllVerifiers: string;
|
|
91
101
|
recipientStepVerifier: string;
|
|
92
102
|
recipientEntityCreator: string;
|
|
@@ -72,6 +72,11 @@
|
|
|
72
72
|
noRoleAssigned: 'No role',
|
|
73
73
|
noUserAssigned: 'No user',
|
|
74
74
|
signatureBadge: 'Signature',
|
|
75
|
+
signaturePrompt: 'This step requires a signature. Choose the file you sign with.',
|
|
76
|
+
signatureMissing: 'Choose a signature file before approving.',
|
|
77
|
+
signatureUploading: 'Uploading signature…',
|
|
78
|
+
signatureUploadFailed: 'The signature could not be uploaded. The decision was not sent.',
|
|
79
|
+
signatureUnavailable: 'This step requires a signature, but signing is not available on this screen. Nobody can complete it here — remove the signature requirement from the step, or wire an upload action.',
|
|
75
80
|
recipientAllVerifiers: 'All Verifiers',
|
|
76
81
|
recipientStepVerifier: 'Step Verifier',
|
|
77
82
|
recipientEntityCreator: 'Entity Creator',
|
|
@@ -30,6 +30,19 @@ export type VerificationDecideAction = GenericAction<VerificationDecidePayload &
|
|
|
30
30
|
entity_id: string;
|
|
31
31
|
}, VerificationDecideResponse>;
|
|
32
32
|
export type VerificationPendingAction = GenericAction<undefined, VerificationPendingResponse>;
|
|
33
|
+
/**
|
|
34
|
+
* Uploads the file a verifier signs with, and hands back its id.
|
|
35
|
+
*
|
|
36
|
+
* `FormData` rather than a typed payload because that is what the entity
|
|
37
|
+
* upload route takes: a `files` part and a `data` part. The host supplies the
|
|
38
|
+
* action, so the component never learns where files live.
|
|
39
|
+
*/
|
|
40
|
+
export type VerificationSignatureUploadAction = GenericAction<FormData, {
|
|
41
|
+
success?: boolean;
|
|
42
|
+
data?: {
|
|
43
|
+
id?: string;
|
|
44
|
+
} | null;
|
|
45
|
+
}>;
|
|
33
46
|
export type VerificationStartAction = GenericAction<VerificationStartPayload, VerificationStartResponse>;
|
|
34
47
|
export type VerificationStartForEntityAction = GenericAction<VerificationStartForEntityPayload, VerificationStartResponse>;
|
|
35
48
|
export type VerificationStatusAction = GenericAction<{
|
|
@@ -185,6 +198,14 @@ export type VerificationFlowPageProps = {
|
|
|
185
198
|
flowDeleteAction?: FlowDeleteAction;
|
|
186
199
|
pendingAction: VerificationPendingAction;
|
|
187
200
|
decideAction: VerificationDecideAction;
|
|
201
|
+
/**
|
|
202
|
+
* Lets a verifier attach the signature a step demands.
|
|
203
|
+
*
|
|
204
|
+
* Optional, and its absence is SAID rather than hidden: without it a step
|
|
205
|
+
* with `require_signature` cannot be decided at all, so the card explains
|
|
206
|
+
* that instead of offering an Approve button the engine will refuse.
|
|
207
|
+
*/
|
|
208
|
+
uploadSignatureAction?: VerificationSignatureUploadAction;
|
|
188
209
|
startAction?: VerificationStartAction;
|
|
189
210
|
startForEntityAction?: VerificationStartForEntityAction;
|
|
190
211
|
statusAction?: VerificationStatusAction;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nucleus-core-ts",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.57",
|
|
4
4
|
"description": "Production-ready, enterprise-grade TypeScript framework for building multi-tenant APIs",
|
|
5
5
|
"author": "Hidayet Can Özcan <hidayetcan@gmail.com>",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|