datakeen-session-react 1.1.195-dev.9 → 1.1.195-rc.0
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/README.md +11 -0
- package/dist/cjs/components/document-collection/DocumentError.js +4 -1
- package/dist/cjs/components/jdi/JDIError.js +4 -1
- package/dist/cjs/components/session/UserInputForm/CustomFormFields.js +2 -10
- package/dist/cjs/components/session/UserInputForm.js +4 -1
- package/dist/cjs/hooks/useStepNavigation.js +18 -0
- package/dist/cjs/hooks/useUserInputForm.js +38 -0
- package/dist/cjs/services/sessionCredentials.js +2 -1
- package/dist/cjs/utils/customFieldLocks.js +27 -0
- package/dist/esm/components/document-collection/DocumentError.js +4 -1
- package/dist/esm/components/jdi/JDIError.js +4 -1
- package/dist/esm/components/session/UserInputForm/CustomFormFields.js +1 -9
- package/dist/esm/components/session/UserInputForm.js +4 -1
- package/dist/esm/hooks/useStepNavigation.js +18 -0
- package/dist/esm/hooks/useUserInputForm.js +38 -0
- package/dist/esm/services/sessionCredentials.js +2 -1
- package/dist/esm/utils/customFieldLocks.js +24 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -549,6 +549,17 @@ Voir [docs/JOURNEY_NODE_BUILDER.md](./docs/JOURNEY_NODE_BUILDER.md) pour la chec
|
|
|
549
549
|
|
|
550
550
|
---
|
|
551
551
|
|
|
552
|
+
## Conformité des licences et SBOM
|
|
553
|
+
|
|
554
|
+
La CI refuse toute dépendance de production sous licence AGPL (**bloquant**) ou GPL (warning) qui n'est pas listée avec justification dans `license-allowlist.json`, et archive un SBOM CycloneDX (`sbom.cdx.json`) à chaque release (`dev`, `staging`, `main`).
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
npm ci
|
|
558
|
+
node scripts/check-licenses.cjs # contrôle local
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Procédure complète (ajouter une exception, régénérer le SBOM) : [docs/LICENSE_COMPLIANCE.md](docs/LICENSE_COMPLIANCE.md).
|
|
562
|
+
|
|
552
563
|
## Support
|
|
553
564
|
|
|
554
565
|
- **Email** : support@datakeen.co
|
|
@@ -14,6 +14,9 @@ var analysisErrors = require('../../utils/analysisErrors.js');
|
|
|
14
14
|
var DocumentError = function (_a) {
|
|
15
15
|
var documentType = _a.documentType, documentTypeLabel = _a.documentTypeLabel, onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _b = _a.retryCount, retryCount = _b === void 0 ? 0 : _b, _c = _a.predictions, predictions = _c === void 0 ? [] : _c; _a.errorDetails; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _d = _a.isRetryAllowed, isRetryAllowed = _d === void 0 ? true : _d, errorCode = _a.errorCode, _e = _a.blockContinue, blockContinue = _e === void 0 ? false : _e;
|
|
16
16
|
var t = useI18n.useI18n().t;
|
|
17
|
+
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
18
|
+
// whatever node.allowContinueOnFailure says.
|
|
19
|
+
var continueAnyway = analysisErrors.isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
17
20
|
var _f = React.useState([]), validationPoints = _f[0], setValidationPoints = _f[1];
|
|
18
21
|
React.useEffect(function () {
|
|
19
22
|
// Use shared utility to get validation points
|
|
@@ -62,7 +65,7 @@ var DocumentError = function (_a) {
|
|
|
62
65
|
: t("errors.resubmission.no_resubmission_allowed") })] })), !blockContinue && getUserGuidance() && (jsxRuntime.jsxs("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-yellow-900 mb-2", children: analysisErrors.isServiceErrorCode(errorCode)
|
|
63
66
|
? // Quota / out of credits: nothing for the end user to improve.
|
|
64
67
|
t("errors.service_refusal_title")
|
|
65
|
-
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsxRuntime.jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxRuntime.jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsxRuntime.jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsxRuntime.jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed &&
|
|
68
|
+
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsxRuntime.jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxRuntime.jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsxRuntime.jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsxRuntime.jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsxRuntime.jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }), jsxRuntime.jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsxRuntime.jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "continue", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }) })] }) })] }));
|
|
66
69
|
};
|
|
67
70
|
|
|
68
71
|
exports.default = DocumentError;
|
|
@@ -18,6 +18,9 @@ var JDIError = function (_a) {
|
|
|
18
18
|
var _b;
|
|
19
19
|
_a.documentType; var onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _c = _a.retryCount, retryCount = _c === void 0 ? 0 : _c, _d = _a.predictions, predictions = _d === void 0 ? DEFAULT_PREDICTIONS : _d, errorCode = _a.errorCode; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _e = _a.isRetryAllowed, isRetryAllowed = _e === void 0 ? true : _e, _f = _a.blockContinue, blockContinue = _f === void 0 ? false : _f;
|
|
20
20
|
var t = useI18n.useI18n().t;
|
|
21
|
+
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
22
|
+
// whatever node.allowContinueOnFailure says.
|
|
23
|
+
var continueAnyway = analysisErrors.isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
21
24
|
var _g = React.useState([]), validationPoints = _g[0], setValidationPoints = _g[1];
|
|
22
25
|
// Détail de mismatch d'identité (code 7.0), calculé côté backend et porté
|
|
23
26
|
// par la première prédiction. Absent pour tous les autres codes.
|
|
@@ -33,7 +36,7 @@ var JDIError = function (_a) {
|
|
|
33
36
|
var guidance = t("user_guidance.".concat(errorCode), "");
|
|
34
37
|
return guidance || null;
|
|
35
38
|
};
|
|
36
|
-
return (jsxRuntime.jsx(MobilePageLayout.default, { footer: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed &&
|
|
39
|
+
return (jsxRuntime.jsx(MobilePageLayout.default, { footer: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsxRuntime.jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsxRuntime.jsx(Button.default, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })), !isRetryAllowed && continueAnyway && (jsxRuntime.jsx(Button.default, { onClick: continueAnyway, className: "w-full py-3", children: t("errors.continue_anyway") }))] })) }), jsxRuntime.jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsxRuntime.jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [onBackToUserInput && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onBackToUserInput, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })), !isRetryAllowed && continueAnyway && (jsxRuntime.jsx(ButtonDesktop.default, { onClick: continueAnyway, type: "continue", children: t("errors.continue_anyway") }))] })) }) })] }), children: jsxRuntime.jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxRuntime.jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsxRuntime.jsx("div", { className: "text-center", children: jsxRuntime.jsx("div", { className: "mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4", children: jsxRuntime.jsx("div", { className: "w-8 h-8 bg-red-500 rounded-full flex items-center justify-center", children: jsxRuntime.jsx("span", { className: "text-white text-lg", children: "\u2715" }) }) }) }), jsxRuntime.jsxs("div", { className: "text-center space-y-4", children: [jsxRuntime.jsx(Title.default, { className: "text-xl md:text-2xl lg:text-3xl text-red-600", children: blockContinue
|
|
37
40
|
? t("errors.upload_not_confirmed.title")
|
|
38
41
|
: t("errors.verification_failed") }), jsxRuntime.jsx(Subtitle.default, { className: "text-sm text-gray-600 leading-relaxed", children: blockContinue
|
|
39
42
|
? t("errors.upload_not_confirmed.subtitle")
|
|
@@ -13,6 +13,7 @@ var AddressFields = require('./AddressFields.js');
|
|
|
13
13
|
var listFieldUtils = require('../../../utils/listFieldUtils.js');
|
|
14
14
|
var customFieldValidation = require('../../../utils/customFieldValidation.js');
|
|
15
15
|
var DatePickerField = require('./DatePickerField.js');
|
|
16
|
+
var customFieldLocks = require('../../../utils/customFieldLocks.js');
|
|
16
17
|
|
|
17
18
|
function _interopNamespaceDefault(e) {
|
|
18
19
|
var n = Object.create(null);
|
|
@@ -189,15 +190,6 @@ var CustomFormFields = function (_a) {
|
|
|
189
190
|
// - hard lock (lockedFromApi sans editableIfMissing) → toujours masqué ;
|
|
190
191
|
// - soft lock (editableIfMissing) → masqué uniquement si l'API a fourni une
|
|
191
192
|
// valeur ; sinon le champ redevient éditable par l'utilisateur final.
|
|
192
|
-
var isValuePresent = function (value) {
|
|
193
|
-
if (value === null || value === undefined)
|
|
194
|
-
return false;
|
|
195
|
-
if (typeof value === "string")
|
|
196
|
-
return value.trim().length > 0;
|
|
197
|
-
if (Array.isArray(value))
|
|
198
|
-
return value.length > 0;
|
|
199
|
-
return true;
|
|
200
|
-
};
|
|
201
193
|
// La décision de visibilité d'un soft lock se fige à l'OUVERTURE du nœud,
|
|
202
194
|
// pas à chaque frappe : on capture une seule fois (au mount, via ref) les
|
|
203
195
|
// soft locks dont l'API n'avait fourni AUCUNE valeur. Ceux-là restent
|
|
@@ -210,7 +202,7 @@ var CustomFormFields = function (_a) {
|
|
|
210
202
|
var ids = new Set();
|
|
211
203
|
for (var _i = 0, customFields_1 = customFields; _i < customFields_1.length; _i++) {
|
|
212
204
|
var f = customFields_1[_i];
|
|
213
|
-
if (f.lockedFromApi && f.editableIfMissing && !isValuePresent(formData[f.id])) {
|
|
205
|
+
if (f.lockedFromApi && f.editableIfMissing && !customFieldLocks.isValuePresent(formData[f.id])) {
|
|
214
206
|
ids.add(f.id);
|
|
215
207
|
}
|
|
216
208
|
}
|
|
@@ -23,7 +23,10 @@ var UserInputForm = function (props) {
|
|
|
23
23
|
var t = useI18n.useI18n().t;
|
|
24
24
|
var hasListCustomField = props.node.informationType === "custom" &&
|
|
25
25
|
(props.node.customFields || []).some(function (field) { return field.valueType === "list"; });
|
|
26
|
-
var _b = useUserInputForm.useUserInputForm(props), form = _b.form, errors = _b.errors, informationType = _b.informationType, requestedFields = _b.requestedFields, pageTitle = _b.pageTitle, pageDescription = _b.pageDescription, addressSuggestions = _b.addressSuggestions, showSuggestions = _b.showSuggestions, handleFieldChange = _b.handleFieldChange, handleAddressChange = _b.handleAddressChange, handleAddressFocus = _b.handleAddressFocus, handleAddressBlur = _b.handleAddressBlur, applyAddressSuggestion = _b.applyAddressSuggestion, goOnNextStep = _b.goOnNextStep, goOnPreviousStep = _b.goOnPreviousStep, customFormData = _b.customFormData, customFormErrors = _b.customFormErrors, customFormCellErrors = _b.customFormCellErrors, handleCustomFieldChange = _b.handleCustomFieldChange, customFields = _b.customFields;
|
|
26
|
+
var _b = useUserInputForm.useUserInputForm(props), form = _b.form, errors = _b.errors, informationType = _b.informationType, requestedFields = _b.requestedFields, pageTitle = _b.pageTitle, pageDescription = _b.pageDescription, addressSuggestions = _b.addressSuggestions, showSuggestions = _b.showSuggestions, handleFieldChange = _b.handleFieldChange, handleAddressChange = _b.handleAddressChange, handleAddressFocus = _b.handleAddressFocus, handleAddressBlur = _b.handleAddressBlur, applyAddressSuggestion = _b.applyAddressSuggestion, goOnNextStep = _b.goOnNextStep, goOnPreviousStep = _b.goOnPreviousStep, customFormData = _b.customFormData, customFormErrors = _b.customFormErrors, customFormCellErrors = _b.customFormCellErrors, handleCustomFieldChange = _b.handleCustomFieldChange, customFields = _b.customFields, isAutoSkipping = _b.isAutoSkipping;
|
|
27
|
+
// Nœud sauté (tous les champs verrouillés) : rien à afficher pendant la transition.
|
|
28
|
+
if (isAutoSkipping)
|
|
29
|
+
return null;
|
|
27
30
|
return (jsxRuntime.jsxs(MobilePageLayout.default, { contentClassName: "overflow-y-auto", footerClassName: "md:sticky md:bottom-0 md:bg-white md:pt-4 md:z-20", footer: jsxRuntime.jsx(PageActions.default, { primary: jsxRuntime.jsx(Button.default, { onClick: goOnNextStep, children: t("user_input_form.buttons.continue") }), secondary: props.stepObject.canGoBack ? (jsxRuntime.jsx(Button.default, { variant: "secondary", onClick: goOnPreviousStep, children: t("user_input_form.buttons.back") })) : undefined }), children: [typeof process !== "undefined" &&
|
|
28
31
|
((_a = process.env) === null || _a === void 0 ? void 0 : _a.NODE_ENV) === "development" && (jsxRuntime.jsx("div", { className: "fixed right-0 top-0 z-50 bg-green-500 p-2 text-xs text-white", children: "UserInputForm Rendered \u2713" })), jsxRuntime.jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxRuntime.jsxs("div", { className: "mx-auto w-full space-y-6 ".concat(hasListCustomField
|
|
29
32
|
? "max-w-[98vw] md:max-w-[1300px] lg:max-w-[1500px]"
|
|
@@ -125,6 +125,22 @@ var useStepNavigation = function (sessionId, sessionStatus$1, initialStep, templ
|
|
|
125
125
|
console.warn("[useStepNavigation] Could not find next step for node ".concat(currentNodeId, ". Staying on current step."));
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
|
+
/**
|
|
129
|
+
* Saute le nœud courant : il est remplacé dans l'historique par le suivant
|
|
130
|
+
* (pas empilé), donc goBack() revient au nœud d'avant celui sauté.
|
|
131
|
+
*/
|
|
132
|
+
var skipToNextStep = function (currentNodeId, template, handle) {
|
|
133
|
+
var nextStepIndex = sessionService.getNextStepIndex(currentNodeId, template, handle);
|
|
134
|
+
if (nextStepIndex === null) {
|
|
135
|
+
console.warn("[useStepNavigation] Could not find next step for node ".concat(currentNodeId, ". Staying on current step."));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
setHistory(function (prev) {
|
|
139
|
+
var base = prev[prev.length - 1] === step ? prev.slice(0, -1) : prev;
|
|
140
|
+
return tslib_es6.__spreadArray(tslib_es6.__spreadArray([], base, true), [nextStepIndex], false);
|
|
141
|
+
});
|
|
142
|
+
setStep(nextStepIndex, true);
|
|
143
|
+
};
|
|
128
144
|
/**
|
|
129
145
|
* Navigates through the graph and makes the destination a point of no return.
|
|
130
146
|
* The destination remains persisted by setStep, while prior history is discarded.
|
|
@@ -175,6 +191,7 @@ var useStepNavigation = function (sessionId, sessionStatus$1, initialStep, templ
|
|
|
175
191
|
goBack: goBack,
|
|
176
192
|
goBackToStep: goBackToStep,
|
|
177
193
|
goToNextStep: goToNextStep,
|
|
194
|
+
skipToNextStep: skipToNextStep,
|
|
178
195
|
goToNextStepCommitted: goToNextStepCommitted,
|
|
179
196
|
step: step,
|
|
180
197
|
canGoBack: history.length > 1,
|
|
@@ -185,6 +202,7 @@ var useStepNavigation = function (sessionId, sessionStatus$1, initialStep, templ
|
|
|
185
202
|
goBack: goBack,
|
|
186
203
|
goBackToStep: goBackToStep,
|
|
187
204
|
goToNextStep: goToNextStep,
|
|
205
|
+
skipToNextStep: skipToNextStep,
|
|
188
206
|
goToNextStepCommitted: goToNextStepCommitted,
|
|
189
207
|
stepObject: stepObject,
|
|
190
208
|
history: history,
|
|
@@ -5,6 +5,7 @@ var React = require('react');
|
|
|
5
5
|
var userInputForm$1 = require('../constants/userInputForm.js');
|
|
6
6
|
var env = require('../config/env.js');
|
|
7
7
|
var customFieldIds = require('../utils/customFieldIds.js');
|
|
8
|
+
var customFieldLocks = require('../utils/customFieldLocks.js');
|
|
8
9
|
var userInputForm = require('../utils/userInputForm.js');
|
|
9
10
|
var listFieldUtils = require('../utils/listFieldUtils.js');
|
|
10
11
|
var customFieldValidation = require('../utils/customFieldValidation.js');
|
|
@@ -87,6 +88,16 @@ var useUserInputForm = function (_a) {
|
|
|
87
88
|
});
|
|
88
89
|
return initial;
|
|
89
90
|
}), customFormData = _d[0], setCustomFormData = _d[1];
|
|
91
|
+
// Figé à l'ouverture du nœud (le formulaire est remonté par nœud) : sinon la
|
|
92
|
+
// saisie dans un soft lock vide le ferait passer pour « masqué » en cours de frappe.
|
|
93
|
+
var isAutoSkippingRef = React.useRef(null);
|
|
94
|
+
if (isAutoSkippingRef.current === null) {
|
|
95
|
+
isAutoSkippingRef.current =
|
|
96
|
+
informationType === "custom" &&
|
|
97
|
+
customFields.length > 0 &&
|
|
98
|
+
!customFieldLocks.hasVisibleCustomFields(customFields, customFormData);
|
|
99
|
+
}
|
|
100
|
+
var isAutoSkipping = isAutoSkippingRef.current;
|
|
90
101
|
var _e = React.useState({}), customFormErrors = _e[0], setCustomFormErrors = _e[1];
|
|
91
102
|
var _f = React.useState({}), customFormCellErrors = _f[0], setCustomFormCellErrors = _f[1];
|
|
92
103
|
var _g = React.useState([]), addressSuggestions = _g[0], setAddressSuggestions = _g[1];
|
|
@@ -668,6 +679,32 @@ var useUserInputForm = function (_a) {
|
|
|
668
679
|
var goOnPreviousStep = function () {
|
|
669
680
|
goBack();
|
|
670
681
|
};
|
|
682
|
+
// Aucun champ visible (tous verrouillés depuis l'API) : on n'affiche pas de
|
|
683
|
+
// page blanche, on reporte les valeurs préremplies puis on saute le nœud.
|
|
684
|
+
var hasAutoSkippedRef = React.useRef(false);
|
|
685
|
+
React.useEffect(function () {
|
|
686
|
+
var _a;
|
|
687
|
+
if (!isAutoSkipping || hasAutoSkippedRef.current)
|
|
688
|
+
return;
|
|
689
|
+
hasAutoSkippedRef.current = true;
|
|
690
|
+
var topLevelEntries = {};
|
|
691
|
+
var customFormDataEntries = {};
|
|
692
|
+
customFields.forEach(function (field) {
|
|
693
|
+
var _a;
|
|
694
|
+
topLevelEntries[(_a = field.userInputKey) !== null && _a !== void 0 ? _a : labelToKey.labelToKey(field.label)] =
|
|
695
|
+
customFormData[field.id];
|
|
696
|
+
customFormDataEntries[field.id] = customFormData[field.id];
|
|
697
|
+
});
|
|
698
|
+
setUserInput(function (prev) {
|
|
699
|
+
var _a;
|
|
700
|
+
return (tslib_es6.__assign(tslib_es6.__assign(tslib_es6.__assign({}, prev), topLevelEntries), { customFormData: tslib_es6.__assign(tslib_es6.__assign({}, ((_a = prev.customFormData) !== null && _a !== void 0 ? _a : {})), customFormDataEntries) }));
|
|
701
|
+
});
|
|
702
|
+
if (onContinueCallback)
|
|
703
|
+
onContinueCallback();
|
|
704
|
+
else
|
|
705
|
+
((_a = stepObject.skipToNextStep) !== null && _a !== void 0 ? _a : goToNextStep)(node.id, template);
|
|
706
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- une seule fois, à l'ouverture du nœud
|
|
707
|
+
}, []);
|
|
671
708
|
var pageTitle = node.pageTitle || formTitles[informationType];
|
|
672
709
|
var pageDescription = node.pageDescription || formDescriptions[informationType];
|
|
673
710
|
return {
|
|
@@ -675,6 +712,7 @@ var useUserInputForm = function (_a) {
|
|
|
675
712
|
errors: errors,
|
|
676
713
|
informationType: informationType,
|
|
677
714
|
customFields: customFields,
|
|
715
|
+
isAutoSkipping: isAutoSkipping,
|
|
678
716
|
requestedFields: requestedFields,
|
|
679
717
|
pageTitle: pageTitle,
|
|
680
718
|
pageDescription: pageDescription,
|
|
@@ -14,7 +14,8 @@ function getSessionCredential(sessionId) {
|
|
|
14
14
|
function credentialForRequest(url) {
|
|
15
15
|
if (/^[a-z]+:/i.test(url) || url.startsWith('//'))
|
|
16
16
|
return undefined;
|
|
17
|
-
|
|
17
|
+
// Routes SDK et biométrie Unissey : toutes adressent une seule session par son id.
|
|
18
|
+
var match = url.match(/^\/?session\/(?:sdk|unissey)\/([^/?]+)/);
|
|
18
19
|
return match ? credentials.get(decodeURIComponent(match[1])) : undefined;
|
|
19
20
|
}
|
|
20
21
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var isValuePresent = function (value) {
|
|
4
|
+
if (value === null || value === undefined)
|
|
5
|
+
return false;
|
|
6
|
+
if (typeof value === "string")
|
|
7
|
+
return value.trim().length > 0;
|
|
8
|
+
if (Array.isArray(value))
|
|
9
|
+
return value.length > 0;
|
|
10
|
+
return true;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Vrai si au moins un champ sera affiché à l'ouverture du nœud :
|
|
14
|
+
* - champ libre → affiché ;
|
|
15
|
+
* - hard lock (lockedFromApi) → jamais affiché ;
|
|
16
|
+
* - soft lock (lockedFromApi + editableIfMissing) → affiché seulement si l'API
|
|
17
|
+
* n'a fourni aucune valeur.
|
|
18
|
+
* Un nœud sans aucun champ visible doit être sauté (sinon page blanche).
|
|
19
|
+
*/
|
|
20
|
+
var hasVisibleCustomFields = function (fields, values) {
|
|
21
|
+
return fields.some(function (f) {
|
|
22
|
+
return !f.lockedFromApi || (f.editableIfMissing && !isValuePresent(values[f.id]));
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
exports.hasVisibleCustomFields = hasVisibleCustomFields;
|
|
27
|
+
exports.isValuePresent = isValuePresent;
|
|
@@ -10,6 +10,9 @@ import { isServiceErrorCode } from '../../utils/analysisErrors.js';
|
|
|
10
10
|
var DocumentError = function (_a) {
|
|
11
11
|
var documentType = _a.documentType, documentTypeLabel = _a.documentTypeLabel, onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _b = _a.retryCount, retryCount = _b === void 0 ? 0 : _b, _c = _a.predictions, predictions = _c === void 0 ? [] : _c; _a.errorDetails; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _d = _a.isRetryAllowed, isRetryAllowed = _d === void 0 ? true : _d, errorCode = _a.errorCode, _e = _a.blockContinue, blockContinue = _e === void 0 ? false : _e;
|
|
12
12
|
var t = useI18n().t;
|
|
13
|
+
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
14
|
+
// whatever node.allowContinueOnFailure says.
|
|
15
|
+
var continueAnyway = isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
13
16
|
var _f = useState([]), validationPoints = _f[0], setValidationPoints = _f[1];
|
|
14
17
|
useEffect(function () {
|
|
15
18
|
// Use shared utility to get validation points
|
|
@@ -58,7 +61,7 @@ var DocumentError = function (_a) {
|
|
|
58
61
|
: t("errors.resubmission.no_resubmission_allowed") })] })), !blockContinue && getUserGuidance() && (jsxs("div", { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-yellow-900 mb-2", children: isServiceErrorCode(errorCode)
|
|
59
62
|
? // Quota / out of credits: nothing for the end user to improve.
|
|
60
63
|
t("errors.service_refusal_title")
|
|
61
|
-
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed &&
|
|
64
|
+
: "\uD83D\uDCA1 ".concat(t("errors.improvement_tips", "Conseils d'amélioration")) }), jsx("p", { className: "text-sm text-yellow-800", children: getUserGuidance() })] })), !blockContinue && isRetryAllowed && (jsxs("div", { className: "bg-blue-50 border border-blue-200 rounded-lg p-4", children: [jsx("h3", { className: "font-medium text-blue-900 mb-2", children: t("errors.next_steps", "Prochaines étapes") }), jsx("p", { className: "text-sm text-blue-800", children: t("errors.next_steps_description", "Veuillez reprendre les photos de votre document en suivant les conseils d'amélioration pour une meilleure qualité.") })] }))] }) }), jsx("div", { className: "sticky bottom-0 md:static bg-white border-t md:border-t-0 p-4 md:p-0 md:pb-8", children: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }), jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", children: t("errors.back_to_user_input", "Retour aux informations d'identité") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "back", children: t("errors.continue_anyway", "Poursuivre tout de même") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification", "Recommencer la vérification") })), !isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "continue", children: t("errors.continue_anyway", "Poursuivre tout de même") }))] })) }) })] }) })] }));
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
export { DocumentError as default };
|
|
@@ -14,6 +14,9 @@ var JDIError = function (_a) {
|
|
|
14
14
|
var _b;
|
|
15
15
|
_a.documentType; var onRetry = _a.onRetry, onContinueAnyway = _a.onContinueAnyway, onBackToUserInput = _a.onBackToUserInput, _c = _a.retryCount, retryCount = _c === void 0 ? 0 : _c, _d = _a.predictions, predictions = _d === void 0 ? DEFAULT_PREDICTIONS : _d, errorCode = _a.errorCode; _a.allowResubmission; var maxResubmissionAttempts = _a.maxResubmissionAttempts, _e = _a.isRetryAllowed, isRetryAllowed = _e === void 0 ? true : _e, _f = _a.blockContinue, blockContinue = _f === void 0 ? false : _f;
|
|
16
16
|
var t = useI18n().t;
|
|
17
|
+
// APP3-1955: a service refusal (no credits / quota) blocks the journey,
|
|
18
|
+
// whatever node.allowContinueOnFailure says.
|
|
19
|
+
var continueAnyway = isServiceErrorCode(errorCode) ? undefined : onContinueAnyway;
|
|
17
20
|
var _g = useState([]), validationPoints = _g[0], setValidationPoints = _g[1];
|
|
18
21
|
// Détail de mismatch d'identité (code 7.0), calculé côté backend et porté
|
|
19
22
|
// par la première prédiction. Absent pour tous les autres codes.
|
|
@@ -29,7 +32,7 @@ var JDIError = function (_a) {
|
|
|
29
32
|
var guidance = t("user_guidance.".concat(errorCode), "");
|
|
30
33
|
return guidance || null;
|
|
31
34
|
};
|
|
32
|
-
return (jsx(MobilePageLayout, { footer: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed &&
|
|
35
|
+
return (jsx(MobilePageLayout, { footer: jsxs("div", { className: "w-full max-w-md mx-auto", children: [jsx("div", { className: "flex flex-col space-y-3 md:hidden", children: blockContinue ? (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "back", className: "w-full text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsx(Button, { onClick: onRetry, className: "w-full py-3", children: t("errors.retry_verification") })), !isRetryAllowed && continueAnyway && (jsx(Button, { onClick: continueAnyway, className: "w-full py-3", children: t("errors.continue_anyway") }))] })) }), jsx("div", { className: "hidden md:flex flex-col space-y-3", children: jsx("div", { className: "flex justify-center space-x-3", children: blockContinue ? (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })) : (jsxs(Fragment, { children: [onBackToUserInput && (jsx(ButtonDesktop, { onClick: onBackToUserInput, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.back_to_user_input") })), retryCount >= 1 && isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "back", className: "text-black text-center font-poppins text-sm font-medium py-2", children: t("errors.continue_anyway") })), isRetryAllowed && (jsx(ButtonDesktop, { onClick: onRetry, type: "continue", children: t("errors.retry_verification") })), !isRetryAllowed && continueAnyway && (jsx(ButtonDesktop, { onClick: continueAnyway, type: "continue", children: t("errors.continue_anyway") }))] })) }) })] }), children: jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxs("div", { className: "w-full max-w-md mx-auto space-y-6", children: [jsx("div", { className: "text-center", children: jsx("div", { className: "mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4", children: jsx("div", { className: "w-8 h-8 bg-red-500 rounded-full flex items-center justify-center", children: jsx("span", { className: "text-white text-lg", children: "\u2715" }) }) }) }), jsxs("div", { className: "text-center space-y-4", children: [jsx(Title, { className: "text-xl md:text-2xl lg:text-3xl text-red-600", children: blockContinue
|
|
33
36
|
? t("errors.upload_not_confirmed.title")
|
|
34
37
|
: t("errors.verification_failed") }), jsx(Subtitle, { className: "text-sm text-gray-600 leading-relaxed", children: blockContinue
|
|
35
38
|
? t("errors.upload_not_confirmed.subtitle")
|
|
@@ -9,6 +9,7 @@ import AddressFields from './AddressFields.js';
|
|
|
9
9
|
import { normalizeListColumns, getListFieldMinRows, ensureMinimumListRows, createEmptyListRow } from '../../../utils/listFieldUtils.js';
|
|
10
10
|
import { cellErrorKey } from '../../../utils/customFieldValidation.js';
|
|
11
11
|
import { DatePickerField } from './DatePickerField.js';
|
|
12
|
+
import { isValuePresent } from '../../../utils/customFieldLocks.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Sous-composant dédié au rendu d'une cellule du tableau dynamique.
|
|
@@ -166,15 +167,6 @@ var CustomFormFields = function (_a) {
|
|
|
166
167
|
// - hard lock (lockedFromApi sans editableIfMissing) → toujours masqué ;
|
|
167
168
|
// - soft lock (editableIfMissing) → masqué uniquement si l'API a fourni une
|
|
168
169
|
// valeur ; sinon le champ redevient éditable par l'utilisateur final.
|
|
169
|
-
var isValuePresent = function (value) {
|
|
170
|
-
if (value === null || value === undefined)
|
|
171
|
-
return false;
|
|
172
|
-
if (typeof value === "string")
|
|
173
|
-
return value.trim().length > 0;
|
|
174
|
-
if (Array.isArray(value))
|
|
175
|
-
return value.length > 0;
|
|
176
|
-
return true;
|
|
177
|
-
};
|
|
178
170
|
// La décision de visibilité d'un soft lock se fige à l'OUVERTURE du nœud,
|
|
179
171
|
// pas à chaque frappe : on capture une seule fois (au mount, via ref) les
|
|
180
172
|
// soft locks dont l'API n'avait fourni AUCUNE valeur. Ceux-là restent
|
|
@@ -19,7 +19,10 @@ var UserInputForm = function (props) {
|
|
|
19
19
|
var t = useI18n().t;
|
|
20
20
|
var hasListCustomField = props.node.informationType === "custom" &&
|
|
21
21
|
(props.node.customFields || []).some(function (field) { return field.valueType === "list"; });
|
|
22
|
-
var _b = useUserInputForm(props), form = _b.form, errors = _b.errors, informationType = _b.informationType, requestedFields = _b.requestedFields, pageTitle = _b.pageTitle, pageDescription = _b.pageDescription, addressSuggestions = _b.addressSuggestions, showSuggestions = _b.showSuggestions, handleFieldChange = _b.handleFieldChange, handleAddressChange = _b.handleAddressChange, handleAddressFocus = _b.handleAddressFocus, handleAddressBlur = _b.handleAddressBlur, applyAddressSuggestion = _b.applyAddressSuggestion, goOnNextStep = _b.goOnNextStep, goOnPreviousStep = _b.goOnPreviousStep, customFormData = _b.customFormData, customFormErrors = _b.customFormErrors, customFormCellErrors = _b.customFormCellErrors, handleCustomFieldChange = _b.handleCustomFieldChange, customFields = _b.customFields;
|
|
22
|
+
var _b = useUserInputForm(props), form = _b.form, errors = _b.errors, informationType = _b.informationType, requestedFields = _b.requestedFields, pageTitle = _b.pageTitle, pageDescription = _b.pageDescription, addressSuggestions = _b.addressSuggestions, showSuggestions = _b.showSuggestions, handleFieldChange = _b.handleFieldChange, handleAddressChange = _b.handleAddressChange, handleAddressFocus = _b.handleAddressFocus, handleAddressBlur = _b.handleAddressBlur, applyAddressSuggestion = _b.applyAddressSuggestion, goOnNextStep = _b.goOnNextStep, goOnPreviousStep = _b.goOnPreviousStep, customFormData = _b.customFormData, customFormErrors = _b.customFormErrors, customFormCellErrors = _b.customFormCellErrors, handleCustomFieldChange = _b.handleCustomFieldChange, customFields = _b.customFields, isAutoSkipping = _b.isAutoSkipping;
|
|
23
|
+
// Nœud sauté (tous les champs verrouillés) : rien à afficher pendant la transition.
|
|
24
|
+
if (isAutoSkipping)
|
|
25
|
+
return null;
|
|
23
26
|
return (jsxs(MobilePageLayout, { contentClassName: "overflow-y-auto", footerClassName: "md:sticky md:bottom-0 md:bg-white md:pt-4 md:z-20", footer: jsx(PageActions, { primary: jsx(Button, { onClick: goOnNextStep, children: t("user_input_form.buttons.continue") }), secondary: props.stepObject.canGoBack ? (jsx(Button, { variant: "secondary", onClick: goOnPreviousStep, children: t("user_input_form.buttons.back") })) : undefined }), children: [typeof process !== "undefined" &&
|
|
24
27
|
((_a = process.env) === null || _a === void 0 ? void 0 : _a.NODE_ENV) === "development" && (jsx("div", { className: "fixed right-0 top-0 z-50 bg-green-500 p-2 text-xs text-white", children: "UserInputForm Rendered \u2713" })), jsx("div", { className: "px-4 py-6 pt-11 md:px-8 md:py-8", children: jsxs("div", { className: "mx-auto w-full space-y-6 ".concat(hasListCustomField
|
|
25
28
|
? "max-w-[98vw] md:max-w-[1300px] lg:max-w-[1500px]"
|
|
@@ -123,6 +123,22 @@ var useStepNavigation = function (sessionId, sessionStatus, initialStep, templat
|
|
|
123
123
|
console.warn("[useStepNavigation] Could not find next step for node ".concat(currentNodeId, ". Staying on current step."));
|
|
124
124
|
}
|
|
125
125
|
};
|
|
126
|
+
/**
|
|
127
|
+
* Saute le nœud courant : il est remplacé dans l'historique par le suivant
|
|
128
|
+
* (pas empilé), donc goBack() revient au nœud d'avant celui sauté.
|
|
129
|
+
*/
|
|
130
|
+
var skipToNextStep = function (currentNodeId, template, handle) {
|
|
131
|
+
var nextStepIndex = getNextStepIndex(currentNodeId, template, handle);
|
|
132
|
+
if (nextStepIndex === null) {
|
|
133
|
+
console.warn("[useStepNavigation] Could not find next step for node ".concat(currentNodeId, ". Staying on current step."));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
setHistory(function (prev) {
|
|
137
|
+
var base = prev[prev.length - 1] === step ? prev.slice(0, -1) : prev;
|
|
138
|
+
return __spreadArray(__spreadArray([], base, true), [nextStepIndex], false);
|
|
139
|
+
});
|
|
140
|
+
setStep(nextStepIndex, true);
|
|
141
|
+
};
|
|
126
142
|
/**
|
|
127
143
|
* Navigates through the graph and makes the destination a point of no return.
|
|
128
144
|
* The destination remains persisted by setStep, while prior history is discarded.
|
|
@@ -173,6 +189,7 @@ var useStepNavigation = function (sessionId, sessionStatus, initialStep, templat
|
|
|
173
189
|
goBack: goBack,
|
|
174
190
|
goBackToStep: goBackToStep,
|
|
175
191
|
goToNextStep: goToNextStep,
|
|
192
|
+
skipToNextStep: skipToNextStep,
|
|
176
193
|
goToNextStepCommitted: goToNextStepCommitted,
|
|
177
194
|
step: step,
|
|
178
195
|
canGoBack: history.length > 1,
|
|
@@ -183,6 +200,7 @@ var useStepNavigation = function (sessionId, sessionStatus, initialStep, templat
|
|
|
183
200
|
goBack: goBack,
|
|
184
201
|
goBackToStep: goBackToStep,
|
|
185
202
|
goToNextStep: goToNextStep,
|
|
203
|
+
skipToNextStep: skipToNextStep,
|
|
186
204
|
goToNextStepCommitted: goToNextStepCommitted,
|
|
187
205
|
stepObject: stepObject,
|
|
188
206
|
history: history,
|
|
@@ -3,6 +3,7 @@ import { useMemo, useState, useRef, useEffect } from 'react';
|
|
|
3
3
|
import { DEFAULT_FIELDS, FALLBACK_ADDRESS_SUGGESTIONS, GEOCODING_AUTOCOMPLETE_ENDPOINT } from '../constants/userInputForm.js';
|
|
4
4
|
import { API_BASE_URL } from '../config/env.js';
|
|
5
5
|
import { dedupeCustomFieldIds } from '../utils/customFieldIds.js';
|
|
6
|
+
import { hasVisibleCustomFields } from '../utils/customFieldLocks.js';
|
|
6
7
|
import { resolveInformationType, parseBirthDate, checkIsMajor } from '../utils/userInputForm.js';
|
|
7
8
|
import { getListFieldMinRows, normalizeListColumns, countNonEmptyListRows } from '../utils/listFieldUtils.js';
|
|
8
9
|
import { cellErrorKey, resolveColumnRegex, isRegexMatch, isDateValid, isEmailValid } from '../utils/customFieldValidation.js';
|
|
@@ -85,6 +86,16 @@ var useUserInputForm = function (_a) {
|
|
|
85
86
|
});
|
|
86
87
|
return initial;
|
|
87
88
|
}), customFormData = _d[0], setCustomFormData = _d[1];
|
|
89
|
+
// Figé à l'ouverture du nœud (le formulaire est remonté par nœud) : sinon la
|
|
90
|
+
// saisie dans un soft lock vide le ferait passer pour « masqué » en cours de frappe.
|
|
91
|
+
var isAutoSkippingRef = useRef(null);
|
|
92
|
+
if (isAutoSkippingRef.current === null) {
|
|
93
|
+
isAutoSkippingRef.current =
|
|
94
|
+
informationType === "custom" &&
|
|
95
|
+
customFields.length > 0 &&
|
|
96
|
+
!hasVisibleCustomFields(customFields, customFormData);
|
|
97
|
+
}
|
|
98
|
+
var isAutoSkipping = isAutoSkippingRef.current;
|
|
88
99
|
var _e = useState({}), customFormErrors = _e[0], setCustomFormErrors = _e[1];
|
|
89
100
|
var _f = useState({}), customFormCellErrors = _f[0], setCustomFormCellErrors = _f[1];
|
|
90
101
|
var _g = useState([]), addressSuggestions = _g[0], setAddressSuggestions = _g[1];
|
|
@@ -666,6 +677,32 @@ var useUserInputForm = function (_a) {
|
|
|
666
677
|
var goOnPreviousStep = function () {
|
|
667
678
|
goBack();
|
|
668
679
|
};
|
|
680
|
+
// Aucun champ visible (tous verrouillés depuis l'API) : on n'affiche pas de
|
|
681
|
+
// page blanche, on reporte les valeurs préremplies puis on saute le nœud.
|
|
682
|
+
var hasAutoSkippedRef = useRef(false);
|
|
683
|
+
useEffect(function () {
|
|
684
|
+
var _a;
|
|
685
|
+
if (!isAutoSkipping || hasAutoSkippedRef.current)
|
|
686
|
+
return;
|
|
687
|
+
hasAutoSkippedRef.current = true;
|
|
688
|
+
var topLevelEntries = {};
|
|
689
|
+
var customFormDataEntries = {};
|
|
690
|
+
customFields.forEach(function (field) {
|
|
691
|
+
var _a;
|
|
692
|
+
topLevelEntries[(_a = field.userInputKey) !== null && _a !== void 0 ? _a : labelToKey(field.label)] =
|
|
693
|
+
customFormData[field.id];
|
|
694
|
+
customFormDataEntries[field.id] = customFormData[field.id];
|
|
695
|
+
});
|
|
696
|
+
setUserInput(function (prev) {
|
|
697
|
+
var _a;
|
|
698
|
+
return (__assign(__assign(__assign({}, prev), topLevelEntries), { customFormData: __assign(__assign({}, ((_a = prev.customFormData) !== null && _a !== void 0 ? _a : {})), customFormDataEntries) }));
|
|
699
|
+
});
|
|
700
|
+
if (onContinueCallback)
|
|
701
|
+
onContinueCallback();
|
|
702
|
+
else
|
|
703
|
+
((_a = stepObject.skipToNextStep) !== null && _a !== void 0 ? _a : goToNextStep)(node.id, template);
|
|
704
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- une seule fois, à l'ouverture du nœud
|
|
705
|
+
}, []);
|
|
669
706
|
var pageTitle = node.pageTitle || formTitles[informationType];
|
|
670
707
|
var pageDescription = node.pageDescription || formDescriptions[informationType];
|
|
671
708
|
return {
|
|
@@ -673,6 +710,7 @@ var useUserInputForm = function (_a) {
|
|
|
673
710
|
errors: errors,
|
|
674
711
|
informationType: informationType,
|
|
675
712
|
customFields: customFields,
|
|
713
|
+
isAutoSkipping: isAutoSkipping,
|
|
676
714
|
requestedFields: requestedFields,
|
|
677
715
|
pageTitle: pageTitle,
|
|
678
716
|
pageDescription: pageDescription,
|
|
@@ -12,7 +12,8 @@ function getSessionCredential(sessionId) {
|
|
|
12
12
|
function credentialForRequest(url) {
|
|
13
13
|
if (/^[a-z]+:/i.test(url) || url.startsWith('//'))
|
|
14
14
|
return undefined;
|
|
15
|
-
|
|
15
|
+
// Routes SDK et biométrie Unissey : toutes adressent une seule session par son id.
|
|
16
|
+
var match = url.match(/^\/?session\/(?:sdk|unissey)\/([^/?]+)/);
|
|
16
17
|
return match ? credentials.get(decodeURIComponent(match[1])) : undefined;
|
|
17
18
|
}
|
|
18
19
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
var isValuePresent = function (value) {
|
|
2
|
+
if (value === null || value === undefined)
|
|
3
|
+
return false;
|
|
4
|
+
if (typeof value === "string")
|
|
5
|
+
return value.trim().length > 0;
|
|
6
|
+
if (Array.isArray(value))
|
|
7
|
+
return value.length > 0;
|
|
8
|
+
return true;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Vrai si au moins un champ sera affiché à l'ouverture du nœud :
|
|
12
|
+
* - champ libre → affiché ;
|
|
13
|
+
* - hard lock (lockedFromApi) → jamais affiché ;
|
|
14
|
+
* - soft lock (lockedFromApi + editableIfMissing) → affiché seulement si l'API
|
|
15
|
+
* n'a fourni aucune valeur.
|
|
16
|
+
* Un nœud sans aucun champ visible doit être sauté (sinon page blanche).
|
|
17
|
+
*/
|
|
18
|
+
var hasVisibleCustomFields = function (fields, values) {
|
|
19
|
+
return fields.some(function (f) {
|
|
20
|
+
return !f.lockedFromApi || (f.editableIfMissing && !isValuePresent(values[f.id]));
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export { hasVisibleCustomFields, isValuePresent };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datakeen-session-react",
|
|
3
|
-
"version": "1.1.195-
|
|
3
|
+
"version": "1.1.195-rc.0",
|
|
4
4
|
"description": "React SDK component to manage and render Datakeen session experiences easily.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"@types/react": "^18.0.0",
|
|
75
75
|
"@types/react-dom": "^18.0.0",
|
|
76
76
|
"@types/testing-library__react": "^10.0.1",
|
|
77
|
+
"@types/ua-parser-js": "0.7.39",
|
|
77
78
|
"autoprefixer": "^10.4.21",
|
|
78
79
|
"axios": "^1.12.0",
|
|
79
80
|
"babel-jest": "^30.1.2",
|
|
@@ -120,7 +121,7 @@
|
|
|
120
121
|
"react-day-picker": "^9.14.0",
|
|
121
122
|
"react-i18next": "^15.7.3",
|
|
122
123
|
"tailwind-merge": "^3.3.1",
|
|
123
|
-
"ua-parser-js": "^
|
|
124
|
+
"ua-parser-js": "^1.0.41"
|
|
124
125
|
},
|
|
125
126
|
"peerDependencies": {
|
|
126
127
|
"axios": "^1.8.0",
|