@escapenavigator/hooks 2.1.24 → 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.
@@ -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 = __importDefault(require("react"));
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
- return withHint(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) }));
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 : min;
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("span", { className: index_module_css_1.default.stepperValue }, current),
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 {};
@@ -65,15 +65,29 @@ const PLANNING_FIELD_TYPES = new Set([
65
65
  lead_field_type_enum_1.LeadFieldTypeEnum.CHECKBOX,
66
66
  lead_field_type_enum_1.LeadFieldTypeEnum.QUESTROOM,
67
67
  lead_field_type_enum_1.LeadFieldTypeEnum.LOCATION,
68
- lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS,
69
- lead_field_type_enum_1.LeadFieldTypeEnum.NUMBER,
70
68
  ]);
71
- const LeadForm = ({ form, source, language, profileCountry, submit, prefill, contextNote, withHeader = true, onDone, doneLabel, onSuccess, policyUrl, pdConsentUrl, }) => {
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, }) => {
72
81
  const t = (0, use_lead_t_1.useLeadT)();
73
82
  const consentRequired = (0, personal_data_consent_1.requiresPersonalDataConsent)(profileCountry);
74
83
  const [consent, setConsent] = (0, react_1.useState)(false);
75
84
  const [showConsentError, setShowConsentError] = (0, react_1.useState)(false);
76
- const [step, setStep] = (0, react_1.useState)(1);
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
+ };
77
91
  const [values, setValues] = (0, react_1.useState)(() => (0, lib_1.buildInitialValues)(form, prefill));
78
92
  const [errors, setErrors] = (0, react_1.useState)({});
79
93
  const [formError, setFormError] = (0, react_1.useState)();
@@ -88,8 +102,14 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
88
102
  setFormError(undefined);
89
103
  };
90
104
  const visibleFields = form.fields.filter((field) => (0, lib_1.isVisible)(field, values));
91
- const planningFields = visibleFields.filter((field) => PLANNING_FIELD_TYPES.has(field.type));
92
- const contactFields = visibleFields.filter((field) => !PLANNING_FIELD_TYPES.has(field.type));
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
+ });
93
113
  const hasTwoSteps = planningFields.length > 0 && contactFields.length > 0;
94
114
  const activeFields = !hasTwoSteps ? visibleFields : step === 1 ? planningFields : contactFields;
95
115
  const planningFieldIds = new Set(planningFields.map((field) => field.id));
@@ -190,13 +210,13 @@ const LeadForm = ({ form, source, language, profileCountry, submit, prefill, con
190
210
  const title = (0, lib_1.localized)(form.clientTitle, language) || t('lead.title');
191
211
  const description = (0, lib_1.localized)(form.description, language);
192
212
  const stepLabel = step === 1 ? t('lead.stepPlanning') : t('lead.stepContacts');
193
- const headerTitle = hasTwoSteps && step === 2 ? t('lead.stepContactsTitle') : title;
213
+ const headerTitle = hasTwoSteps && step === 2 ? t('lead.stepQuestMatchTitle') : title;
194
214
  const headerDescription = hasTwoSteps && step === 2 ? t('lead.stepContactsHint') : description;
195
215
  return (react_1.default.createElement("div", { className: index_module_css_1.default.form },
196
216
  withHeader && (react_1.default.createElement("div", { className: index_module_css_1.default.header },
197
217
  hasTwoSteps ? (react_1.default.createElement(react_1.default.Fragment, null,
198
218
  react_1.default.createElement("div", { className: index_module_css_1.default.stepMeta },
199
- step === 2 ? (react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.backButton, onClick: () => setStep(1) },
219
+ step === 2 && showBackButton ? (react_1.default.createElement("button", { type: 'button', className: index_module_css_1.default.backButton, onClick: () => setStep(1) },
200
220
  react_1.default.createElement(ArrowLeft, null),
201
221
  t('lead.back'))) : (react_1.default.createElement("span", null)),
202
222
  react_1.default.createElement("span", null, t('lead.stepCounter', {
@@ -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
- if (!condition.optionId) {
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(condition.optionId);
58
- return parentValue === condition.optionId;
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] = (_a = field.min) !== null && _a !== void 0 ? _a : 2;
87
+ values[field.id] = '';
82
88
  continue;
83
89
  }
84
90
  values[field.id] = '';
@@ -38,4 +38,32 @@ describe('lead form step validation', () => {
38
38
  formError: undefined,
39
39
  });
40
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
+ });
41
69
  });
@@ -25,16 +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',
29
30
  'lead.stepPlanning': 'Your preferences',
30
31
  'lead.stepContacts': 'Contacts',
31
32
  'lead.stepCounter': 'Step {{current}} of 2 · {{label}}',
32
- 'lead.stepContactsTitle': 'How can we contact you?',
33
+ 'lead.stepContactsTitle': 'We will help you choose a quest',
34
+ 'lead.stepQuestMatchTitle': 'We will help you choose a quest',
33
35
  'lead.stepContactsHint': 'Leave your contacts and preferred date — we will confirm the details.',
34
36
  'lead.next': 'Next — contacts',
35
37
  'lead.back': 'Back',
36
38
  'lead.selection': 'Your choices',
37
39
  'lead.edit': 'Edit',
40
+ 'lead.collapse': 'Collapse',
38
41
  'lead.close': 'Close',
39
42
  'lead.another': 'Send another request',
40
43
  'lead.footnote': 'We will contact you and suggest options. A request is not a booking yet.',
@@ -50,15 +53,18 @@ const DEFAULTS = {
50
53
  const RU_DEFAULTS = {
51
54
  'lead.footnote': 'Менеджер свяжется с вами и предложит варианты. Заявка пока не бронирует игру.',
52
55
  'lead.timeUndecided': 'Пока не определился со временем',
56
+ 'lead.guestsUnknown': 'Не знаю',
53
57
  'lead.stepPlanning': 'Ваши пожелания',
54
58
  'lead.stepContacts': 'Контакты',
55
59
  'lead.stepCounter': 'Шаг {{current}} из 2 · {{label}}',
56
- 'lead.stepContactsTitle': 'Как с вами связаться?',
60
+ 'lead.stepContactsTitle': 'Поможем выбрать ваш квест',
61
+ 'lead.stepQuestMatchTitle': 'Поможем выбрать ваш квест',
57
62
  'lead.stepContactsHint': 'Оставьте контакты, желаемую дату и время — мы уточним детали.',
58
63
  'lead.next': 'Далее — контакты',
59
64
  'lead.back': 'Назад',
60
65
  'lead.selection': 'Вы выбрали',
61
66
  'lead.edit': 'Изменить',
67
+ 'lead.collapse': 'Свернуть',
62
68
  };
63
69
  const useLeadT = () => {
64
70
  var _a;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/hooks",
3
- "version": "2.1.24",
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.24",
18
- "@escapenavigator/types": "^2.1.24",
19
- "@escapenavigator/utils": "^2.1.24",
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": "153992d51e958b46acefa16ab49c9a0c1f727150"
35
+ "gitHead": "561bdff886f82b428de45a54fc63ce6f71500015"
36
36
  }