@escapenavigator/hooks 2.1.23 → 2.1.25
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/lead-form/lead-field.js +51 -6
- package/dist/lead-form/lead-form.d.ts +4 -0
- package/dist/lead-form/lead-form.js +116 -9
- package/dist/lead-form/lib.d.ts +5 -1
- package/dist/lead-form/lib.js +20 -10
- package/dist/lead-form/lib.spec.d.ts +1 -0
- package/dist/lead-form/lib.spec.js +69 -0
- package/dist/lead-form/use-lead-t.js +24 -0
- package/package.json +5 -5
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
3
36
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
37
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
38
|
};
|
|
@@ -10,7 +43,7 @@ const PlusM_1 = __importDefault(require("@alphakits/icons/dist/PlusM"));
|
|
|
10
43
|
const ui_1 = require("@alphakits/ui");
|
|
11
44
|
const lead_field_type_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-type.enum");
|
|
12
45
|
const lead_time_constants_1 = require("@escapenavigator/types/dist/lead/lead-time.constants");
|
|
13
|
-
const react_1 =
|
|
46
|
+
const react_1 = __importStar(require("react"));
|
|
14
47
|
const input_tel_1 = require("../input-tel");
|
|
15
48
|
const index_module_css_1 = __importDefault(require("./index.module.css"));
|
|
16
49
|
const lib_1 = require("./lib");
|
|
@@ -43,6 +76,11 @@ const ChoiceGroup = ({ label, options, isSelected, onToggle, hint, error, }) =>
|
|
|
43
76
|
const LeadField = ({ field, value, error, language, profileCountry, questrooms, locations, onChange, onPhoneValidChange, }) => {
|
|
44
77
|
var _a, _b;
|
|
45
78
|
const t = (0, use_lead_t_1.useLeadT)();
|
|
79
|
+
const [textareaExpanded, setTextareaExpanded] = (0, react_1.useState)(Boolean(value || error));
|
|
80
|
+
(0, react_1.useEffect)(() => {
|
|
81
|
+
if (value || error)
|
|
82
|
+
setTextareaExpanded(true);
|
|
83
|
+
}, [error, value]);
|
|
46
84
|
const title = (0, lib_1.fieldTitle)(field, language);
|
|
47
85
|
const label = field.required ? `${title} *` : title;
|
|
48
86
|
const placeholder = (0, lib_1.fieldText)(field, language, 'placeholder');
|
|
@@ -68,7 +106,14 @@ const LeadField = ({ field, value, error, language, profileCountry, questrooms,
|
|
|
68
106
|
onPhoneValidChange(payload.isValid);
|
|
69
107
|
} }));
|
|
70
108
|
case lead_field_type_enum_1.LeadFieldTypeEnum.TEXTAREA:
|
|
71
|
-
|
|
109
|
+
if (!textareaExpanded) {
|
|
110
|
+
return (react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.commentToggle, onClick: () => setTextareaExpanded(true) },
|
|
111
|
+
react_1.default.createElement("span", { "aria-hidden": 'true' }, "+"),
|
|
112
|
+
title));
|
|
113
|
+
}
|
|
114
|
+
return withHint(react_1.default.createElement("div", { className: index_module_css_1.default.commentEditor },
|
|
115
|
+
react_1.default.createElement(ui_1.Textarea, { block: true, maxLength: 1000, label: label, placeholder: placeholder, value: String(value || ''), error: error, onChange: (_e, payload) => onChange(payload.value) }),
|
|
116
|
+
!value && !error && (react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.commentCollapse, onClick: () => setTextareaExpanded(false) }, t('lead.collapse')))));
|
|
72
117
|
case lead_field_type_enum_1.LeadFieldTypeEnum.DATE:
|
|
73
118
|
return withHint(react_1.default.createElement(ui_1.CalendarInput, { block: true, label: label, lang: language, value: String(value || ''), error: error, onChange: (_e, payload) => onChange(payload.value) }));
|
|
74
119
|
case lead_field_type_enum_1.LeadFieldTypeEnum.TIME:
|
|
@@ -93,14 +138,14 @@ const LeadField = ({ field, value, error, language, profileCountry, questrooms,
|
|
|
93
138
|
case lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS: {
|
|
94
139
|
const min = (_a = field.min) !== null && _a !== void 0 ? _a : 1;
|
|
95
140
|
const max = (_b = field.max) !== null && _b !== void 0 ? _b : 30;
|
|
96
|
-
const current = typeof value === 'number' ? value :
|
|
141
|
+
const current = typeof value === 'number' ? value : undefined;
|
|
97
142
|
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 8 },
|
|
98
143
|
react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', weight: 'medium', color: 'primary' }, label),
|
|
99
144
|
react_1.default.createElement("div", { className: index_module_css_1.default.stepper },
|
|
100
|
-
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": t('lead.guestsMinus'), disabled: current <= min, onClick: () => onChange(Math.max(min, current - 1)) },
|
|
145
|
+
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": t('lead.guestsMinus'), disabled: current === undefined || current <= min, onClick: () => onChange(Math.max(min, (current !== null && current !== void 0 ? current : min + 1) - 1)) },
|
|
101
146
|
react_1.default.createElement(MinusM_1.default, null)),
|
|
102
|
-
react_1.default.createElement("
|
|
103
|
-
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": t('lead.guestsPlus'), disabled: current >= max, onClick: () => onChange(Math.min(max, current + 1)) },
|
|
147
|
+
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperValue, onClick: () => onChange(undefined) }, current !== null && current !== void 0 ? current : t('lead.guestsUnknown')),
|
|
148
|
+
react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.stepperButton, "aria-label": t('lead.guestsPlus'), disabled: current !== undefined && current >= max, onClick: () => onChange(Math.min(max, (current !== null && current !== void 0 ? current : min - 1) + 1)) },
|
|
104
149
|
react_1.default.createElement(PlusM_1.default, null))),
|
|
105
150
|
hint));
|
|
106
151
|
}
|
|
@@ -46,6 +46,10 @@ type Props = {
|
|
|
46
46
|
policyUrl?: string;
|
|
47
47
|
/** Документ согласия на обработку ПДн (галочка есть только в РФ). */
|
|
48
48
|
pdConsentUrl?: string;
|
|
49
|
+
/** Модалка управляет шагом, чтобы использовать свою кнопку «Назад». */
|
|
50
|
+
currentStep?: 1 | 2;
|
|
51
|
+
onStepChange?: (step: 1 | 2) => void;
|
|
52
|
+
showBackButton?: boolean;
|
|
49
53
|
};
|
|
50
54
|
export declare const LeadForm: React.FC<Props>;
|
|
51
55
|
export {};
|
|
@@ -48,6 +48,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
48
48
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
49
|
exports.LeadForm = void 0;
|
|
50
50
|
const ui_1 = require("@alphakits/ui");
|
|
51
|
+
const lead_field_type_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-type.enum");
|
|
51
52
|
const react_1 = __importStar(require("react"));
|
|
52
53
|
const personal_data_consent_1 = require("../personal-data-consent");
|
|
53
54
|
const index_module_css_1 = __importDefault(require("./index.module.css"));
|
|
@@ -56,11 +57,37 @@ const lib_1 = require("./lib");
|
|
|
56
57
|
const use_lead_t_1 = require("./use-lead-t");
|
|
57
58
|
const ArrowRight = () => (react_1.default.createElement("svg", { width: '18', height: '18', viewBox: '0 0 24 24', fill: 'none', "aria-hidden": 'true' },
|
|
58
59
|
react_1.default.createElement("path", { d: 'M5 12h14m-6-6 6 6-6 6', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' })));
|
|
59
|
-
const
|
|
60
|
+
const ArrowLeft = () => (react_1.default.createElement("svg", { width: '16', height: '16', viewBox: '0 0 24 24', fill: 'none', "aria-hidden": 'true' },
|
|
61
|
+
react_1.default.createElement("path", { d: 'M19 12H5m6 6-6-6 6-6', stroke: 'currentColor', strokeWidth: '2', strokeLinecap: 'round', strokeLinejoin: 'round' })));
|
|
62
|
+
const PLANNING_FIELD_TYPES = new Set([
|
|
63
|
+
lead_field_type_enum_1.LeadFieldTypeEnum.SELECT,
|
|
64
|
+
lead_field_type_enum_1.LeadFieldTypeEnum.RADIO,
|
|
65
|
+
lead_field_type_enum_1.LeadFieldTypeEnum.CHECKBOX,
|
|
66
|
+
lead_field_type_enum_1.LeadFieldTypeEnum.QUESTROOM,
|
|
67
|
+
lead_field_type_enum_1.LeadFieldTypeEnum.LOCATION,
|
|
68
|
+
]);
|
|
69
|
+
const PLANNING_FIELD_IDS = new Set(['f_children_age']);
|
|
70
|
+
const CONTACT_FIELD_ORDER = {
|
|
71
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.PHONE]: 0,
|
|
72
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.NAME]: 1,
|
|
73
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.DATE]: 2,
|
|
74
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.TIME]: 3,
|
|
75
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS]: 4,
|
|
76
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.TEXTAREA]: 5,
|
|
77
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.EMAIL]: 6,
|
|
78
|
+
[lead_field_type_enum_1.LeadFieldTypeEnum.CONSENT]: 7,
|
|
79
|
+
};
|
|
80
|
+
const LeadForm = ({ form, source, language, profileCountry, submit, prefill, contextNote, withHeader = true, onDone, doneLabel, onSuccess, policyUrl, pdConsentUrl, currentStep, onStepChange, showBackButton = true, }) => {
|
|
60
81
|
const t = (0, use_lead_t_1.useLeadT)();
|
|
61
82
|
const consentRequired = (0, personal_data_consent_1.requiresPersonalDataConsent)(profileCountry);
|
|
62
83
|
const [consent, setConsent] = (0, react_1.useState)(false);
|
|
63
84
|
const [showConsentError, setShowConsentError] = (0, react_1.useState)(false);
|
|
85
|
+
const [internalStep, setInternalStep] = (0, react_1.useState)(1);
|
|
86
|
+
const step = currentStep !== null && currentStep !== void 0 ? currentStep : internalStep;
|
|
87
|
+
const setStep = (nextStep) => {
|
|
88
|
+
setInternalStep(nextStep);
|
|
89
|
+
onStepChange === null || onStepChange === void 0 ? void 0 : onStepChange(nextStep);
|
|
90
|
+
};
|
|
64
91
|
const [values, setValues] = (0, react_1.useState)(() => (0, lib_1.buildInitialValues)(form, prefill));
|
|
65
92
|
const [errors, setErrors] = (0, react_1.useState)({});
|
|
66
93
|
const [formError, setFormError] = (0, react_1.useState)();
|
|
@@ -74,6 +101,67 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
|
|
|
74
101
|
setErrors((prev) => (prev[fieldId] ? Object.assign(Object.assign({}, prev), { [fieldId]: '' }) : prev));
|
|
75
102
|
setFormError(undefined);
|
|
76
103
|
};
|
|
104
|
+
const visibleFields = form.fields.filter((field) => (0, lib_1.isVisible)(field, values));
|
|
105
|
+
const planningFields = visibleFields.filter((field) => PLANNING_FIELD_IDS.has(field.id) || PLANNING_FIELD_TYPES.has(field.type));
|
|
106
|
+
const contactFields = visibleFields
|
|
107
|
+
.filter((field) => !PLANNING_FIELD_IDS.has(field.id) && !PLANNING_FIELD_TYPES.has(field.type))
|
|
108
|
+
.sort((left, right) => {
|
|
109
|
+
var _a, _b;
|
|
110
|
+
return ((_a = CONTACT_FIELD_ORDER[left.type]) !== null && _a !== void 0 ? _a : 10) - ((_b = CONTACT_FIELD_ORDER[right.type]) !== null && _b !== void 0 ? _b : 10) ||
|
|
111
|
+
left.order - right.order;
|
|
112
|
+
});
|
|
113
|
+
const hasTwoSteps = planningFields.length > 0 && contactFields.length > 0;
|
|
114
|
+
const activeFields = !hasTwoSteps ? visibleFields : step === 1 ? planningFields : contactFields;
|
|
115
|
+
const planningFieldIds = new Set(planningFields.map((field) => field.id));
|
|
116
|
+
const planningSummary = planningFields
|
|
117
|
+
.map((field) => {
|
|
118
|
+
var _a, _b, _c;
|
|
119
|
+
const value = values[field.id];
|
|
120
|
+
if (value === undefined || value === '' || value === false)
|
|
121
|
+
return '';
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
return value
|
|
124
|
+
.map((id) => {
|
|
125
|
+
var _a;
|
|
126
|
+
const selected = (_a = field.options) === null || _a === void 0 ? void 0 : _a.find((item) => item.id === id);
|
|
127
|
+
return selected ? (0, lib_1.optionLabel)(selected, language, id) : id;
|
|
128
|
+
})
|
|
129
|
+
.join(', ');
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === 'string' && ((_a = field.options) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
132
|
+
const selected = field.options.find((item) => item.id === value);
|
|
133
|
+
return selected ? (0, lib_1.optionLabel)(selected, language, value) : value;
|
|
134
|
+
}
|
|
135
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.QUESTROOM) {
|
|
136
|
+
return (((_b = form.questrooms.find((item) => String(item.id) === String(value))) === null || _b === void 0 ? void 0 : _b.title) || '');
|
|
137
|
+
}
|
|
138
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.LOCATION) {
|
|
139
|
+
return (((_c = form.locations.find((item) => String(item.id) === String(value))) === null || _c === void 0 ? void 0 : _c.title) || '');
|
|
140
|
+
}
|
|
141
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS ||
|
|
142
|
+
field.type === lead_field_type_enum_1.LeadFieldTypeEnum.NUMBER) {
|
|
143
|
+
return `${(0, lib_1.fieldTitle)(field, language)}: ${String(value)}`;
|
|
144
|
+
}
|
|
145
|
+
return String(value);
|
|
146
|
+
})
|
|
147
|
+
.filter(Boolean);
|
|
148
|
+
const handleNext = () => {
|
|
149
|
+
const result = (0, lib_1.validate)({
|
|
150
|
+
form,
|
|
151
|
+
values,
|
|
152
|
+
phoneValid,
|
|
153
|
+
t,
|
|
154
|
+
fieldIds: planningFieldIds,
|
|
155
|
+
requireContact: false,
|
|
156
|
+
});
|
|
157
|
+
if (Object.keys(result.errors).length) {
|
|
158
|
+
setErrors(result.errors);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
setErrors({});
|
|
162
|
+
setFormError(undefined);
|
|
163
|
+
setStep(2);
|
|
164
|
+
};
|
|
77
165
|
const handleSubmit = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
166
|
var _a;
|
|
79
167
|
if (sending)
|
|
@@ -84,6 +172,9 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
|
|
|
84
172
|
if (Object.keys(result.errors).length || result.formError || consentMissing) {
|
|
85
173
|
setErrors(result.errors);
|
|
86
174
|
setFormError(result.formError);
|
|
175
|
+
if (Object.keys(result.errors).some((fieldId) => planningFieldIds.has(fieldId))) {
|
|
176
|
+
setStep(1);
|
|
177
|
+
}
|
|
87
178
|
return;
|
|
88
179
|
}
|
|
89
180
|
setSending(true);
|
|
@@ -118,18 +209,34 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
|
|
|
118
209
|
}
|
|
119
210
|
const title = (0, lib_1.localized)(form.clientTitle, language) || t('lead.title');
|
|
120
211
|
const description = (0, lib_1.localized)(form.description, language);
|
|
212
|
+
const stepLabel = step === 1 ? t('lead.stepPlanning') : t('lead.stepContacts');
|
|
213
|
+
const headerTitle = hasTwoSteps && step === 2 ? t('lead.stepQuestMatchTitle') : title;
|
|
214
|
+
const headerDescription = hasTwoSteps && step === 2 ? t('lead.stepContactsHint') : description;
|
|
121
215
|
return (react_1.default.createElement("div", { className: index_module_css_1.default.form },
|
|
122
216
|
withHeader && (react_1.default.createElement("div", { className: index_module_css_1.default.header },
|
|
123
|
-
react_1.default.createElement(
|
|
124
|
-
|
|
125
|
-
|
|
217
|
+
hasTwoSteps ? (react_1.default.createElement(react_1.default.Fragment, null,
|
|
218
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.stepMeta },
|
|
219
|
+
step === 2 && showBackButton ? (react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.backButton, onClick: () => setStep(1) },
|
|
220
|
+
react_1.default.createElement(ArrowLeft, null),
|
|
221
|
+
t('lead.back'))) : (react_1.default.createElement("span", null)),
|
|
222
|
+
react_1.default.createElement("span", null, t('lead.stepCounter', {
|
|
223
|
+
current: step,
|
|
224
|
+
label: stepLabel,
|
|
225
|
+
}))),
|
|
226
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.progress, "aria-hidden": 'true' },
|
|
227
|
+
react_1.default.createElement("span", { "data-step": step })))) : (react_1.default.createElement("span", { className: index_module_css_1.default.headerAccent, "aria-hidden": 'true' })),
|
|
228
|
+
react_1.default.createElement(ui_1.Typography.Title, { tag: 'h1', view: 'small', weight: 'bold' }, headerTitle),
|
|
229
|
+
!!headerDescription && (react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', color: 'secondary' }, headerDescription)))),
|
|
126
230
|
!!formError && react_1.default.createElement(ui_1.ToastPlate, { view: 'negative' }, formError),
|
|
127
|
-
react_1.default.createElement("div", { className: index_module_css_1.default.
|
|
128
|
-
.
|
|
129
|
-
|
|
231
|
+
hasTwoSteps && step === 2 && !!planningSummary.length && (react_1.default.createElement("div", { className: index_module_css_1.default.selectionSummary },
|
|
232
|
+
react_1.default.createElement("div", null,
|
|
233
|
+
react_1.default.createElement("span", null, t('lead.selection')),
|
|
234
|
+
react_1.default.createElement("strong", null, planningSummary.join(' · '))),
|
|
235
|
+
react_1.default.createElement("button", { type: 'button', onClick: () => setStep(1) }, t('lead.edit')))),
|
|
236
|
+
react_1.default.createElement("div", { key: hasTwoSteps ? step : 'single', className: index_module_css_1.default.fields, "data-step": hasTwoSteps ? step : 1 }, activeFields.map((field) => (react_1.default.createElement("div", { key: field.id, className: index_module_css_1.default.fieldCard, "data-field-type": field.type },
|
|
130
237
|
react_1.default.createElement(lead_field_1.LeadField, { field: field, value: values[field.id], error: errors[field.id], language: language, profileCountry: profileCountry, questrooms: form.questrooms, locations: form.locations, onChange: (value) => setValue(field.id, value), onPhoneValidChange: setPhoneValid }))))),
|
|
131
238
|
react_1.default.createElement("input", { className: index_module_css_1.default.trap, tabIndex: -1, autoComplete: 'off', "aria-hidden": 'true', value: trap, onChange: (event) => setTrap(event.target.value) }),
|
|
132
|
-
react_1.default.createElement("div", { className: index_module_css_1.default.footer },
|
|
239
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.footer }, hasTwoSteps && step === 1 ? (react_1.default.createElement(ui_1.Button, { view: 'primary', size: 's', block: true, rightAddons: react_1.default.createElement(ArrowRight, null), onClick: handleNext }, t('lead.next'))) : (react_1.default.createElement(react_1.default.Fragment, null,
|
|
133
240
|
(consentRequired || !!policyUrl) && (react_1.default.createElement(personal_data_consent_1.PersonalDataConsent, { required: consentRequired, consentUrl: pdConsentUrl || '', policyUrl: policyUrl || '', checked: consent, showError: showConsentError, onChange: (checked) => {
|
|
134
241
|
setConsent(checked);
|
|
135
242
|
if (checked)
|
|
@@ -138,6 +245,6 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
|
|
|
138
245
|
react_1.default.createElement(ui_1.Button, { view: 'primary', size: 's', block: true, loading: sending, rightAddons: sending ? undefined : react_1.default.createElement(ArrowRight, null), onClick: handleSubmit }, sending
|
|
139
246
|
? t('lead.sending')
|
|
140
247
|
: (0, lib_1.localized)(form.submitLabel, language) || t('lead.submit')),
|
|
141
|
-
react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'secondary', className: index_module_css_1.default.footnote, tag: 'div' }, t('lead.footnote')))));
|
|
248
|
+
react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'secondary', className: index_module_css_1.default.footnote, tag: 'div' }, t('lead.footnote')))))));
|
|
142
249
|
};
|
|
143
250
|
exports.LeadForm = LeadForm;
|
package/dist/lead-form/lib.d.ts
CHANGED
|
@@ -37,13 +37,17 @@ type ValidateContext = {
|
|
|
37
37
|
/** `InputTel` сам знает, валиден ли номер для выбранной страны. */
|
|
38
38
|
phoneValid: boolean;
|
|
39
39
|
t: (key: string) => string;
|
|
40
|
+
/** Для перехода между шагами проверяем только поля текущего шага. */
|
|
41
|
+
fieldIds?: Set<string>;
|
|
42
|
+
/** На первом шаге контакт ещё не спрашивали — общая проверка не нужна. */
|
|
43
|
+
requireContact?: boolean;
|
|
40
44
|
};
|
|
41
45
|
/**
|
|
42
46
|
* Валидация на клиенте — это про удобство: сервер всё равно перепроверит
|
|
43
47
|
* обязательность и наличие канала связи. Возвращаем ошибки по id поля плюс
|
|
44
48
|
* общую ошибку формы.
|
|
45
49
|
*/
|
|
46
|
-
export declare const validate: ({ form, values, phoneValid, t, }: ValidateContext) => {
|
|
50
|
+
export declare const validate: ({ form, values, phoneValid, t, fieldIds, requireContact, }: ValidateContext) => {
|
|
47
51
|
errors: Record<string, string>;
|
|
48
52
|
formError?: string;
|
|
49
53
|
};
|
package/dist/lead-form/lib.js
CHANGED
|
@@ -46,20 +46,26 @@ exports.isMultiple = isMultiple;
|
|
|
46
46
|
* «уточните какой квест» блокировала бы отправку у всех остальных.
|
|
47
47
|
*/
|
|
48
48
|
const isVisible = (field, values) => {
|
|
49
|
+
var _a;
|
|
49
50
|
const condition = field.conditional;
|
|
50
51
|
if (!(condition === null || condition === void 0 ? void 0 : condition.fieldId))
|
|
51
52
|
return true;
|
|
52
53
|
const parentValue = values[condition.fieldId];
|
|
53
|
-
|
|
54
|
+
const optionIds = ((_a = condition.optionIds) === null || _a === void 0 ? void 0 : _a.length)
|
|
55
|
+
? condition.optionIds
|
|
56
|
+
: condition.optionId
|
|
57
|
+
? [condition.optionId]
|
|
58
|
+
: [];
|
|
59
|
+
if (!optionIds.length) {
|
|
54
60
|
return Array.isArray(parentValue) ? parentValue.length > 0 : !!parentValue;
|
|
55
61
|
}
|
|
56
|
-
if (Array.isArray(parentValue))
|
|
57
|
-
return parentValue.includes(
|
|
58
|
-
|
|
62
|
+
if (Array.isArray(parentValue)) {
|
|
63
|
+
return parentValue.some((value) => optionIds.includes(value));
|
|
64
|
+
}
|
|
65
|
+
return typeof parentValue === 'string' && optionIds.includes(parentValue);
|
|
59
66
|
};
|
|
60
67
|
exports.isVisible = isVisible;
|
|
61
68
|
const buildInitialValues = (form, prefill) => {
|
|
62
|
-
var _a;
|
|
63
69
|
const values = {};
|
|
64
70
|
for (const field of form.fields) {
|
|
65
71
|
const prefilled = field.mapsTo ? prefill === null || prefill === void 0 ? void 0 : prefill[field.mapsTo] : undefined;
|
|
@@ -75,10 +81,10 @@ const buildInitialValues = (form, prefill) => {
|
|
|
75
81
|
values[field.id] = [];
|
|
76
82
|
continue;
|
|
77
83
|
}
|
|
78
|
-
//
|
|
79
|
-
//
|
|
84
|
+
// Пустое значение у степпера означает «пока не знаю». Первый клик
|
|
85
|
+
// по плюсу начнёт счёт с минимального значения.
|
|
80
86
|
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS) {
|
|
81
|
-
values[field.id] =
|
|
87
|
+
values[field.id] = '';
|
|
82
88
|
continue;
|
|
83
89
|
}
|
|
84
90
|
values[field.id] = '';
|
|
@@ -101,11 +107,13 @@ const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
|
101
107
|
* обязательность и наличие канала связи. Возвращаем ошибки по id поля плюс
|
|
102
108
|
* общую ошибку формы.
|
|
103
109
|
*/
|
|
104
|
-
const validate = ({ form, values, phoneValid, t, }) => {
|
|
110
|
+
const validate = ({ form, values, phoneValid, t, fieldIds, requireContact = true, }) => {
|
|
105
111
|
const errors = {};
|
|
106
112
|
let hasPhone = false;
|
|
107
113
|
let hasEmail = false;
|
|
108
114
|
for (const field of form.fields) {
|
|
115
|
+
if (fieldIds && !fieldIds.has(field.id))
|
|
116
|
+
continue;
|
|
109
117
|
if (!(0, exports.isVisible)(field, values))
|
|
110
118
|
continue;
|
|
111
119
|
const value = values[field.id];
|
|
@@ -133,7 +141,9 @@ const validate = ({ form, values, phoneValid, t, }) => {
|
|
|
133
141
|
}
|
|
134
142
|
// Заявку без телефона и почты обработать нельзя — бэкенд её отклонит,
|
|
135
143
|
// так что честнее сказать это до отправки.
|
|
136
|
-
const formError = !hasPhone && !hasEmail && !Object.keys(errors).length
|
|
144
|
+
const formError = requireContact && !hasPhone && !hasEmail && !Object.keys(errors).length
|
|
145
|
+
? t('lead.needContact')
|
|
146
|
+
: undefined;
|
|
137
147
|
return { errors, formError };
|
|
138
148
|
};
|
|
139
149
|
exports.validate = validate;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const lead_field_type_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-type.enum");
|
|
4
|
+
const lib_1 = require("./lib");
|
|
5
|
+
const form = {
|
|
6
|
+
fields: [
|
|
7
|
+
{
|
|
8
|
+
id: 'occasion',
|
|
9
|
+
type: lead_field_type_enum_1.LeadFieldTypeEnum.RADIO,
|
|
10
|
+
required: true,
|
|
11
|
+
enabled: true,
|
|
12
|
+
order: 0,
|
|
13
|
+
options: [],
|
|
14
|
+
i18n: {},
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: 'phone',
|
|
18
|
+
type: lead_field_type_enum_1.LeadFieldTypeEnum.PHONE,
|
|
19
|
+
required: true,
|
|
20
|
+
enabled: true,
|
|
21
|
+
order: 1,
|
|
22
|
+
i18n: {},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
describe('lead form step validation', () => {
|
|
27
|
+
it('validates only planning fields before opening the contacts step', () => {
|
|
28
|
+
const result = (0, lib_1.validate)({
|
|
29
|
+
form,
|
|
30
|
+
values: { occasion: '', phone: '' },
|
|
31
|
+
phoneValid: false,
|
|
32
|
+
t: (key) => key,
|
|
33
|
+
fieldIds: new Set(['occasion']),
|
|
34
|
+
requireContact: false,
|
|
35
|
+
});
|
|
36
|
+
expect(result).toEqual({
|
|
37
|
+
errors: { occasion: 'lead.required' },
|
|
38
|
+
formError: undefined,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
it('shows children age for children and mixed groups', () => {
|
|
42
|
+
const ageField = {
|
|
43
|
+
id: 'age',
|
|
44
|
+
conditional: {
|
|
45
|
+
fieldId: 'audience',
|
|
46
|
+
optionIds: ['children', 'mixed'],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
expect((0, lib_1.isVisible)(ageField, { audience: 'children' })).toBe(true);
|
|
50
|
+
expect((0, lib_1.isVisible)(ageField, { audience: 'mixed' })).toBe(true);
|
|
51
|
+
expect((0, lib_1.isVisible)(ageField, { audience: 'adults' })).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('starts an optional guest stepper in the unknown state', () => {
|
|
54
|
+
const guestsForm = {
|
|
55
|
+
fields: [
|
|
56
|
+
{
|
|
57
|
+
id: 'guests',
|
|
58
|
+
type: lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS,
|
|
59
|
+
required: false,
|
|
60
|
+
enabled: true,
|
|
61
|
+
order: 0,
|
|
62
|
+
min: 1,
|
|
63
|
+
i18n: {},
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
expect((0, lib_1.buildInitialValues)(guestsForm)).toEqual({ guests: '' });
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -25,7 +25,19 @@ const DEFAULTS = {
|
|
|
25
25
|
'lead.unavailable': 'The request form is not available right now',
|
|
26
26
|
'lead.guestsMinus': 'One guest less',
|
|
27
27
|
'lead.guestsPlus': 'One guest more',
|
|
28
|
+
'lead.guestsUnknown': 'Not sure yet',
|
|
28
29
|
'lead.timeUndecided': 'Not sure about the time yet',
|
|
30
|
+
'lead.stepPlanning': 'Your preferences',
|
|
31
|
+
'lead.stepContacts': 'Contacts',
|
|
32
|
+
'lead.stepCounter': 'Step {{current}} of 2 · {{label}}',
|
|
33
|
+
'lead.stepContactsTitle': 'We will help you choose a quest',
|
|
34
|
+
'lead.stepQuestMatchTitle': 'We will help you choose a quest',
|
|
35
|
+
'lead.stepContactsHint': 'Leave your contacts and preferred date — we will confirm the details.',
|
|
36
|
+
'lead.next': 'Next — contacts',
|
|
37
|
+
'lead.back': 'Back',
|
|
38
|
+
'lead.selection': 'Your choices',
|
|
39
|
+
'lead.edit': 'Edit',
|
|
40
|
+
'lead.collapse': 'Collapse',
|
|
29
41
|
'lead.close': 'Close',
|
|
30
42
|
'lead.another': 'Send another request',
|
|
31
43
|
'lead.footnote': 'We will contact you and suggest options. A request is not a booking yet.',
|
|
@@ -41,6 +53,18 @@ const DEFAULTS = {
|
|
|
41
53
|
const RU_DEFAULTS = {
|
|
42
54
|
'lead.footnote': 'Менеджер свяжется с вами и предложит варианты. Заявка пока не бронирует игру.',
|
|
43
55
|
'lead.timeUndecided': 'Пока не определился со временем',
|
|
56
|
+
'lead.guestsUnknown': 'Не знаю',
|
|
57
|
+
'lead.stepPlanning': 'Ваши пожелания',
|
|
58
|
+
'lead.stepContacts': 'Контакты',
|
|
59
|
+
'lead.stepCounter': 'Шаг {{current}} из 2 · {{label}}',
|
|
60
|
+
'lead.stepContactsTitle': 'Поможем выбрать ваш квест',
|
|
61
|
+
'lead.stepQuestMatchTitle': 'Поможем выбрать ваш квест',
|
|
62
|
+
'lead.stepContactsHint': 'Оставьте контакты, желаемую дату и время — мы уточним детали.',
|
|
63
|
+
'lead.next': 'Далее — контакты',
|
|
64
|
+
'lead.back': 'Назад',
|
|
65
|
+
'lead.selection': 'Вы выбрали',
|
|
66
|
+
'lead.edit': 'Изменить',
|
|
67
|
+
'lead.collapse': 'Свернуть',
|
|
44
68
|
};
|
|
45
69
|
const useLeadT = () => {
|
|
46
70
|
var _a;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/hooks",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.25",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/services": "^2.1.
|
|
18
|
-
"@escapenavigator/types": "^2.1.
|
|
19
|
-
"@escapenavigator/utils": "^2.1.
|
|
17
|
+
"@escapenavigator/services": "^2.1.25",
|
|
18
|
+
"@escapenavigator/types": "^2.1.25",
|
|
19
|
+
"@escapenavigator/utils": "^2.1.25",
|
|
20
20
|
"libphonenumber-js": "^1.12.24",
|
|
21
21
|
"react": "19.2.6",
|
|
22
22
|
"react-phone-input-2": "^2.15.1"
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"ts-jest": "^29.1.1",
|
|
33
33
|
"typescript": "^5.6"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "561bdff886f82b428de45a54fc63ce6f71500015"
|
|
36
36
|
}
|