@escapenavigator/hooks 2.0.45 → 2.0.46
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/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lead-form/index.d.ts +6 -0
- package/dist/lead-form/index.js +15 -0
- package/dist/lead-form/lead-field.d.ts +18 -0
- package/dist/lead-form/lead-field.js +105 -0
- package/dist/lead-form/lead-form.d.ts +44 -0
- package/dist/lead-form/lead-form.js +125 -0
- package/dist/lead-form/lib.d.ts +56 -0
- package/dist/lead-form/lib.js +170 -0
- package/dist/lead-form/use-lead-t.d.ts +2 -0
- package/dist/lead-form/use-lead-t.js +42 -0
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export * from './consent-checkboxes';
|
|
|
2
2
|
export * from './functional-popover';
|
|
3
3
|
export * from './input-tel';
|
|
4
4
|
export * from './lazy-with-retry';
|
|
5
|
+
export * from './lead-form';
|
|
5
6
|
export * from './players-and-variation-picker';
|
|
6
7
|
export * from './use-debounced-value';
|
|
7
8
|
export * from './use-localstore-state';
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ __exportStar(require("./consent-checkboxes"), exports);
|
|
|
18
18
|
__exportStar(require("./functional-popover"), exports);
|
|
19
19
|
__exportStar(require("./input-tel"), exports);
|
|
20
20
|
__exportStar(require("./lazy-with-retry"), exports);
|
|
21
|
+
__exportStar(require("./lead-form"), exports);
|
|
21
22
|
__exportStar(require("./players-and-variation-picker"), exports);
|
|
22
23
|
__exportStar(require("./use-debounced-value"), exports);
|
|
23
24
|
__exportStar(require("./use-localstore-state"), exports);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { LeadField } from './lead-field';
|
|
2
|
+
export type { LeadFormSubmit, LeadFormSubmitPayload, LeadFormSubmitResult } from './lead-form';
|
|
3
|
+
export { LeadForm } from './lead-form';
|
|
4
|
+
export type { LeadFieldValue, LeadFormPrefill, LeadFormValues } from './lib';
|
|
5
|
+
export { buildAnswers, buildInitialValues, isVisible, localized, validate } from './lib';
|
|
6
|
+
export { useLeadT } from './use-lead-t';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLeadT = exports.validate = exports.localized = exports.isVisible = exports.buildInitialValues = exports.buildAnswers = exports.LeadForm = exports.LeadField = void 0;
|
|
4
|
+
var lead_field_1 = require("./lead-field");
|
|
5
|
+
Object.defineProperty(exports, "LeadField", { enumerable: true, get: function () { return lead_field_1.LeadField; } });
|
|
6
|
+
var lead_form_1 = require("./lead-form");
|
|
7
|
+
Object.defineProperty(exports, "LeadForm", { enumerable: true, get: function () { return lead_form_1.LeadForm; } });
|
|
8
|
+
var lib_1 = require("./lib");
|
|
9
|
+
Object.defineProperty(exports, "buildAnswers", { enumerable: true, get: function () { return lib_1.buildAnswers; } });
|
|
10
|
+
Object.defineProperty(exports, "buildInitialValues", { enumerable: true, get: function () { return lib_1.buildInitialValues; } });
|
|
11
|
+
Object.defineProperty(exports, "isVisible", { enumerable: true, get: function () { return lib_1.isVisible; } });
|
|
12
|
+
Object.defineProperty(exports, "localized", { enumerable: true, get: function () { return lib_1.localized; } });
|
|
13
|
+
Object.defineProperty(exports, "validate", { enumerable: true, get: function () { return lib_1.validate; } });
|
|
14
|
+
var use_lead_t_1 = require("./use-lead-t");
|
|
15
|
+
Object.defineProperty(exports, "useLeadT", { enumerable: true, get: function () { return use_lead_t_1.useLeadT; } });
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { LeadFormField } from '@escapenavigator/types/dist/lead/lead-form-field.type';
|
|
2
|
+
import { PublicLeadFormReferenceRO } from '@escapenavigator/types/dist/lead/public/public-lead-form.ro';
|
|
3
|
+
import { CountriesEnum } from '@escapenavigator/types/dist/shared/enum/countries.enum';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { LeadFieldValue } from './lib';
|
|
6
|
+
type Props = {
|
|
7
|
+
field: LeadFormField;
|
|
8
|
+
value: LeadFieldValue;
|
|
9
|
+
error?: string;
|
|
10
|
+
language: string;
|
|
11
|
+
profileCountry: CountriesEnum;
|
|
12
|
+
questrooms: PublicLeadFormReferenceRO[];
|
|
13
|
+
locations: PublicLeadFormReferenceRO[];
|
|
14
|
+
onChange: (value: LeadFieldValue) => void;
|
|
15
|
+
onPhoneValidChange: (valid: boolean) => void;
|
|
16
|
+
};
|
|
17
|
+
export declare const LeadField: React.FC<Props>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.LeadField = void 0;
|
|
8
|
+
const MinusM_1 = __importDefault(require("@alphakits/icons/dist/MinusM"));
|
|
9
|
+
const PlusM_1 = __importDefault(require("@alphakits/icons/dist/PlusM"));
|
|
10
|
+
const ui_1 = require("@alphakits/ui");
|
|
11
|
+
const lead_field_type_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-type.enum");
|
|
12
|
+
const react_1 = __importDefault(require("react"));
|
|
13
|
+
const input_tel_1 = require("../input-tel");
|
|
14
|
+
const index_module_css_1 = __importDefault(require("./index.module.css"));
|
|
15
|
+
const lib_1 = require("./lib");
|
|
16
|
+
const use_lead_t_1 = require("./use-lead-t");
|
|
17
|
+
const TIME_STEP_MINUTES = 30;
|
|
18
|
+
/** Слоты выбора времени: клиенту хватает получаса, а свободный ввод он ломает. */
|
|
19
|
+
const buildTimeOptions = () => {
|
|
20
|
+
const out = [];
|
|
21
|
+
for (let minutes = 0; minutes < 24 * 60; minutes += TIME_STEP_MINUTES) {
|
|
22
|
+
const value = `${String(Math.floor(minutes / 60)).padStart(2, '0')}:${String(minutes % 60).padStart(2, '0')}`;
|
|
23
|
+
out.push({ key: value, content: value });
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
27
|
+
const TIME_OPTIONS = buildTimeOptions();
|
|
28
|
+
const LeadField = ({ field, value, error, language, profileCountry, questrooms, locations, onChange, onPhoneValidChange, }) => {
|
|
29
|
+
var _a, _b;
|
|
30
|
+
const t = (0, use_lead_t_1.useLeadT)();
|
|
31
|
+
const title = (0, lib_1.fieldTitle)(field, language);
|
|
32
|
+
const label = field.required ? `${title} *` : title;
|
|
33
|
+
const placeholder = (0, lib_1.fieldText)(field, language, 'placeholder');
|
|
34
|
+
const description = (0, lib_1.fieldText)(field, language, 'description');
|
|
35
|
+
const hint = description ? (react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', color: 'secondary' }, description)) : null;
|
|
36
|
+
const withHint = (control) => {
|
|
37
|
+
if (!hint)
|
|
38
|
+
return control;
|
|
39
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 4 },
|
|
40
|
+
control,
|
|
41
|
+
hint));
|
|
42
|
+
};
|
|
43
|
+
const activeOptions = (field.options || []).filter((option) => option.active !== false);
|
|
44
|
+
const referenceOptions = (items) => items.map((item) => ({ key: String(item.id), content: item.title }));
|
|
45
|
+
switch (field.type) {
|
|
46
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.PHONE:
|
|
47
|
+
return withHint(react_1.default.createElement(input_tel_1.InputTel, { label: label, value: String(value || ''), profileCountry: profileCountry, locale: language, widgetMode: true, error: error, onChange: (payload) => {
|
|
48
|
+
onChange(payload.phone);
|
|
49
|
+
onPhoneValidChange(payload.isValid);
|
|
50
|
+
} }));
|
|
51
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.TEXTAREA:
|
|
52
|
+
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) }));
|
|
53
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.DATE:
|
|
54
|
+
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) }));
|
|
55
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.TIME:
|
|
56
|
+
return withHint(react_1.default.createElement(ui_1.Select, { block: true, optionsListWidth: 'field', label: label, options: TIME_OPTIONS, selected: String(value || ''), error: error, onChange: ({ selected }) => onChange(selected === null || selected === void 0 ? void 0 : selected.key) }));
|
|
57
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS: {
|
|
58
|
+
const min = (_a = field.min) !== null && _a !== void 0 ? _a : 1;
|
|
59
|
+
const max = (_b = field.max) !== null && _b !== void 0 ? _b : 30;
|
|
60
|
+
const current = typeof value === 'number' ? value : min;
|
|
61
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 8 },
|
|
62
|
+
react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', weight: 'medium' }, label),
|
|
63
|
+
react_1.default.createElement("div", { className: index_module_css_1.default.stepper },
|
|
64
|
+
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)) },
|
|
65
|
+
react_1.default.createElement(MinusM_1.default, null)),
|
|
66
|
+
react_1.default.createElement("span", { className: index_module_css_1.default.stepperValue }, current),
|
|
67
|
+
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)) },
|
|
68
|
+
react_1.default.createElement(PlusM_1.default, null))),
|
|
69
|
+
hint));
|
|
70
|
+
}
|
|
71
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.NUMBER:
|
|
72
|
+
return withHint(react_1.default.createElement(ui_1.Input, { block: true, label: label, placeholder: placeholder, inputMode: 'numeric', value: value === undefined ? '' : String(value), error: error, onChange: (_e, payload) => onChange(payload.value.replace(/\D/g, '') || undefined) }));
|
|
73
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.SELECT:
|
|
74
|
+
return withHint(react_1.default.createElement(ui_1.Select, { block: true, allowUnselect: !field.required, optionsListWidth: 'field', label: label, placeholder: placeholder, options: activeOptions.map((option) => ({
|
|
75
|
+
key: option.id,
|
|
76
|
+
content: (0, lib_1.optionLabel)(option, language, option.id),
|
|
77
|
+
})), selected: String(value || ''), error: error, onChange: ({ selected }) => onChange(selected === null || selected === void 0 ? void 0 : selected.key) }));
|
|
78
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.RADIO:
|
|
79
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 8 },
|
|
80
|
+
react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', weight: 'medium' }, label),
|
|
81
|
+
activeOptions.map((option) => (react_1.default.createElement(ui_1.Radio, { key: option.id, label: (0, lib_1.optionLabel)(option, language, option.id), checked: value === option.id, onChange: () => onChange(option.id) }))),
|
|
82
|
+
hint,
|
|
83
|
+
!!error && (react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'negative' }, error))));
|
|
84
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.CHECKBOX: {
|
|
85
|
+
const selected = Array.isArray(value) ? value : [];
|
|
86
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 8 },
|
|
87
|
+
react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', weight: 'medium' }, label),
|
|
88
|
+
activeOptions.map((option) => (react_1.default.createElement(ui_1.Checkbox, { key: option.id, label: (0, lib_1.optionLabel)(option, language, option.id), checked: selected.includes(option.id), onChange: () => onChange(selected.includes(option.id)
|
|
89
|
+
? selected.filter((item) => item !== option.id)
|
|
90
|
+
: [...selected, option.id]) }))),
|
|
91
|
+
hint,
|
|
92
|
+
!!error && (react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'negative' }, error))));
|
|
93
|
+
}
|
|
94
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.QUESTROOM:
|
|
95
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.LOCATION:
|
|
96
|
+
return withHint(react_1.default.createElement(ui_1.Select, { block: true, allowUnselect: !field.required, optionsListWidth: 'field', label: label, placeholder: placeholder, options: referenceOptions(field.type === lead_field_type_enum_1.LeadFieldTypeEnum.QUESTROOM ? questrooms : locations), selected: String(value || ''), error: error, onChange: ({ selected }) => onChange(selected === null || selected === void 0 ? void 0 : selected.key) }));
|
|
97
|
+
case lead_field_type_enum_1.LeadFieldTypeEnum.CONSENT:
|
|
98
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 4 },
|
|
99
|
+
react_1.default.createElement(ui_1.Checkbox, { size: 's', align: 'start', block: true, label: react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'secondary' }, title), hint: description, checked: value === true, onChange: (_e, payload) => onChange(!!(payload === null || payload === void 0 ? void 0 : payload.checked)) }),
|
|
100
|
+
!!error && (react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'negative' }, error))));
|
|
101
|
+
default:
|
|
102
|
+
return withHint(react_1.default.createElement(ui_1.Input, { block: true, label: label, placeholder: placeholder, spellCheck: false, type: field.type === lead_field_type_enum_1.LeadFieldTypeEnum.EMAIL ? 'email' : 'text', value: String(value || ''), error: error, onChange: (_e, payload) => onChange(payload.value) }));
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
exports.LeadField = LeadField;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { LeadSourceEnum } from '@escapenavigator/types/dist/lead/enums/lead-source.enum';
|
|
2
|
+
import { PublicLeadFormRO } from '@escapenavigator/types/dist/lead/public/public-lead-form.ro';
|
|
3
|
+
import { SubmitLeadAnswerDto } from '@escapenavigator/types/dist/lead/public/submit-lead.dto';
|
|
4
|
+
import { SubmitLeadRO } from '@escapenavigator/types/dist/lead/public/submit-lead.ro';
|
|
5
|
+
import { CountriesEnum } from '@escapenavigator/types/dist/shared/enum/countries.enum';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import { LeadFormPrefill } from './lib';
|
|
8
|
+
export type LeadFormSubmitPayload = {
|
|
9
|
+
formId: number;
|
|
10
|
+
answers: SubmitLeadAnswerDto[];
|
|
11
|
+
source: LeadSourceEnum;
|
|
12
|
+
trap?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Отправку делает вызывающая сторона: из виджета заявка уходит с
|
|
16
|
+
* widget-токеном, со standalone-страницы — по uuid формы. `null` или
|
|
17
|
+
* ответ без `data` — обработанная ошибка. `error.message` с бэка нужна,
|
|
18
|
+
* чтобы показать лимит запросов отдельно от общего сбоя.
|
|
19
|
+
*/
|
|
20
|
+
export type LeadFormSubmitResult = {
|
|
21
|
+
data?: SubmitLeadRO;
|
|
22
|
+
error?: {
|
|
23
|
+
message?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type LeadFormSubmit = (payload: LeadFormSubmitPayload) => Promise<LeadFormSubmitResult | null>;
|
|
27
|
+
type Props = {
|
|
28
|
+
form: PublicLeadFormRO;
|
|
29
|
+
source: LeadSourceEnum;
|
|
30
|
+
language: string;
|
|
31
|
+
profileCountry: CountriesEnum;
|
|
32
|
+
submit: LeadFormSubmit;
|
|
33
|
+
/** Что клиент уже выбрал до формы — подставляем по роли поля. */
|
|
34
|
+
prefill?: LeadFormPrefill;
|
|
35
|
+
/** Контекст выбора (пакет, дата, гости) — уходит в комментарий заявки. */
|
|
36
|
+
contextNote?: string;
|
|
37
|
+
/** Шапку рисует контейнер, если у него своя (модалка, страница). */
|
|
38
|
+
withHeader?: boolean;
|
|
39
|
+
/** Что делать после успеха: модалка закрывается, страница остаётся. */
|
|
40
|
+
onDone?: () => void;
|
|
41
|
+
doneLabel?: string;
|
|
42
|
+
};
|
|
43
|
+
export declare const LeadForm: React.FC<Props>;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
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
|
+
})();
|
|
36
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
37
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
38
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
39
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
40
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
41
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
42
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
46
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.LeadForm = void 0;
|
|
50
|
+
const CheckmarkCircleFillM_1 = __importDefault(require("@alphakits/icons/dist/CheckmarkCircleFillM"));
|
|
51
|
+
const ui_1 = require("@alphakits/ui");
|
|
52
|
+
const react_1 = __importStar(require("react"));
|
|
53
|
+
const index_module_css_1 = __importDefault(require("./index.module.css"));
|
|
54
|
+
const lead_field_1 = require("./lead-field");
|
|
55
|
+
const lib_1 = require("./lib");
|
|
56
|
+
const use_lead_t_1 = require("./use-lead-t");
|
|
57
|
+
const LeadForm = ({ form, source, language, profileCountry, submit, prefill, contextNote, withHeader = true, onDone, doneLabel, }) => {
|
|
58
|
+
const t = (0, use_lead_t_1.useLeadT)();
|
|
59
|
+
const [values, setValues] = (0, react_1.useState)(() => (0, lib_1.buildInitialValues)(form, prefill));
|
|
60
|
+
const [errors, setErrors] = (0, react_1.useState)({});
|
|
61
|
+
const [formError, setFormError] = (0, react_1.useState)();
|
|
62
|
+
const [phoneValid, setPhoneValid] = (0, react_1.useState)(false);
|
|
63
|
+
const [sending, setSending] = (0, react_1.useState)(false);
|
|
64
|
+
const [success, setSuccess] = (0, react_1.useState)();
|
|
65
|
+
// Honeypot: настоящий человек скрытое поле не увидит и не заполнит.
|
|
66
|
+
const [trap, setTrap] = (0, react_1.useState)('');
|
|
67
|
+
const setValue = (fieldId, value) => {
|
|
68
|
+
setValues((prev) => (Object.assign(Object.assign({}, prev), { [fieldId]: value })));
|
|
69
|
+
setErrors((prev) => (prev[fieldId] ? Object.assign(Object.assign({}, prev), { [fieldId]: '' }) : prev));
|
|
70
|
+
setFormError(undefined);
|
|
71
|
+
};
|
|
72
|
+
const handleSubmit = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
73
|
+
var _a;
|
|
74
|
+
if (sending)
|
|
75
|
+
return;
|
|
76
|
+
const result = (0, lib_1.validate)({ form, values, phoneValid, t });
|
|
77
|
+
if (Object.keys(result.errors).length || result.formError) {
|
|
78
|
+
setErrors(result.errors);
|
|
79
|
+
setFormError(result.formError);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setSending(true);
|
|
83
|
+
setFormError(undefined);
|
|
84
|
+
const response = yield submit({
|
|
85
|
+
formId: form.id,
|
|
86
|
+
answers: (0, lib_1.buildAnswers)(form, values, contextNote),
|
|
87
|
+
source,
|
|
88
|
+
trap: trap || undefined,
|
|
89
|
+
});
|
|
90
|
+
setSending(false);
|
|
91
|
+
if (!(response === null || response === void 0 ? void 0 : response.data)) {
|
|
92
|
+
setFormError(/too many/i.test(((_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) || '')
|
|
93
|
+
? t('lead.tooManyRequests')
|
|
94
|
+
: t('lead.failed'));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
setSuccess(response.data);
|
|
98
|
+
});
|
|
99
|
+
if (success) {
|
|
100
|
+
return (react_1.default.createElement("div", { className: index_module_css_1.default.success },
|
|
101
|
+
react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 16 },
|
|
102
|
+
react_1.default.createElement("span", { className: index_module_css_1.default.successIcon },
|
|
103
|
+
react_1.default.createElement(CheckmarkCircleFillM_1.default, null)),
|
|
104
|
+
react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 4 },
|
|
105
|
+
react_1.default.createElement(ui_1.Typography.Title, { tag: 'div', view: 'xsmall', weight: 'bold' }, (0, lib_1.localized)(success.successTitle, language) || t('lead.successTitle')),
|
|
106
|
+
react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', color: 'secondary' }, (0, lib_1.localized)(success.successDescription, language) ||
|
|
107
|
+
t('lead.successText'))),
|
|
108
|
+
!!onDone && (react_1.default.createElement(ui_1.Button, { view: 'primary', size: 's', block: true, onClick: onDone }, doneLabel || t('lead.close'))))));
|
|
109
|
+
}
|
|
110
|
+
const title = (0, lib_1.localized)(form.clientTitle, language) || t('lead.title');
|
|
111
|
+
const description = (0, lib_1.localized)(form.description, language);
|
|
112
|
+
return (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 16 },
|
|
113
|
+
withHeader && (react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 4 },
|
|
114
|
+
react_1.default.createElement(ui_1.Typography.Title, { tag: 'div', view: 'xsmall', weight: 'bold' }, title),
|
|
115
|
+
!!description && (react_1.default.createElement(ui_1.Typography.Text, { view: 'primary-small', color: 'secondary' }, description)))),
|
|
116
|
+
!!formError && react_1.default.createElement(ui_1.ToastPlate, { view: 'negative' }, formError),
|
|
117
|
+
form.fields
|
|
118
|
+
.filter((field) => (0, lib_1.isVisible)(field, values))
|
|
119
|
+
.map((field) => (react_1.default.createElement(lead_field_1.LeadField, { key: field.id, 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 }))),
|
|
120
|
+
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) }),
|
|
121
|
+
react_1.default.createElement(ui_1.Button, { view: 'primary', size: 's', block: true, loading: sending, onClick: handleSubmit }, sending
|
|
122
|
+
? t('lead.sending')
|
|
123
|
+
: (0, lib_1.localized)(form.submitLabel, language) || t('lead.submit'))));
|
|
124
|
+
};
|
|
125
|
+
exports.LeadForm = LeadForm;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { LeadFieldMappingEnum } from '@escapenavigator/types/dist/lead/enums/lead-field-mapping.enum';
|
|
2
|
+
import { LeadFieldTypeEnum } from '@escapenavigator/types/dist/lead/enums/lead-field-type.enum';
|
|
3
|
+
import { LeadFormField } from '@escapenavigator/types/dist/lead/lead-form-field.type';
|
|
4
|
+
import { PublicLeadFormRO } from '@escapenavigator/types/dist/lead/public/public-lead-form.ro';
|
|
5
|
+
import { SubmitLeadAnswerDto } from '@escapenavigator/types/dist/lead/public/submit-lead.dto';
|
|
6
|
+
import { LocalizedString } from '@escapenavigator/types/dist/shared/localized-string.type';
|
|
7
|
+
export type LeadFieldValue = string | number | boolean | string[] | undefined;
|
|
8
|
+
export type LeadFormValues = Record<string, LeadFieldValue>;
|
|
9
|
+
/**
|
|
10
|
+
* Текст на языке виджета. Площадка могла не перевести форму на все языки —
|
|
11
|
+
* откатываемся на английский, потом на любой заполненный: пустой заголовок
|
|
12
|
+
* хуже чужого языка.
|
|
13
|
+
*/
|
|
14
|
+
export declare const localized: (source: LocalizedString | undefined, language: string) => string;
|
|
15
|
+
export declare const fieldTitle: (field: LeadFormField, language: string) => string;
|
|
16
|
+
export declare const fieldText: (field: LeadFormField, language: string, key: "placeholder" | "description") => string;
|
|
17
|
+
export declare const optionLabel: (option: {
|
|
18
|
+
i18n: LocalizedString;
|
|
19
|
+
}, language: string, fallback: string) => string;
|
|
20
|
+
export declare const isMultiple: (type: LeadFieldTypeEnum) => boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Поле скрыто, пока в управляющем поле не выбран нужный вариант. Скрытое
|
|
23
|
+
* поле не валидируется и не попадает в ответы — иначе обязательная
|
|
24
|
+
* «уточните какой квест» блокировала бы отправку у всех остальных.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isVisible: (field: LeadFormField, values: LeadFormValues) => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* Предзаполнение по смысловой роли поля, а не по его id: форма настраивается
|
|
29
|
+
* площадкой, и id полей у каждой свои. Роль (`mapsTo`) стабильна — по ней
|
|
30
|
+
* заявка из пакетов подставляет уже выбранную дату и число гостей.
|
|
31
|
+
*/
|
|
32
|
+
export type LeadFormPrefill = Partial<Record<LeadFieldMappingEnum, LeadFieldValue>>;
|
|
33
|
+
export declare const buildInitialValues: (form: PublicLeadFormRO, prefill?: LeadFormPrefill) => LeadFormValues;
|
|
34
|
+
type ValidateContext = {
|
|
35
|
+
form: PublicLeadFormRO;
|
|
36
|
+
values: LeadFormValues;
|
|
37
|
+
/** `InputTel` сам знает, валиден ли номер для выбранной страны. */
|
|
38
|
+
phoneValid: boolean;
|
|
39
|
+
t: (key: string) => string;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Валидация на клиенте — это про удобство: сервер всё равно перепроверит
|
|
43
|
+
* обязательность и наличие канала связи. Возвращаем ошибки по id поля плюс
|
|
44
|
+
* общую ошибку формы.
|
|
45
|
+
*/
|
|
46
|
+
export declare const validate: ({ form, values, phoneValid, t, }: ValidateContext) => {
|
|
47
|
+
errors: Record<string, string>;
|
|
48
|
+
formError?: string;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* `contextNote` — то, что клиент уже выбрал до формы (пакет, дата, гости).
|
|
52
|
+
* Дописываем его в комментарий, а не в отдельное поле: у формы площадки
|
|
53
|
+
* такого поля может не быть, а менеджеру этот контекст нужен в карточке.
|
|
54
|
+
*/
|
|
55
|
+
export declare const buildAnswers: (form: PublicLeadFormRO, values: LeadFormValues, contextNote?: string) => SubmitLeadAnswerDto[];
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAnswers = exports.validate = exports.buildInitialValues = exports.isVisible = exports.isMultiple = exports.optionLabel = exports.fieldText = exports.fieldTitle = exports.localized = void 0;
|
|
4
|
+
const lead_field_mapping_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-mapping.enum");
|
|
5
|
+
const lead_field_type_enum_1 = require("@escapenavigator/types/dist/lead/enums/lead-field-type.enum");
|
|
6
|
+
const languages_enum_1 = require("@escapenavigator/types/dist/shared/enum/languages.enum");
|
|
7
|
+
/**
|
|
8
|
+
* Текст на языке виджета. Площадка могла не перевести форму на все языки —
|
|
9
|
+
* откатываемся на английский, потом на любой заполненный: пустой заголовок
|
|
10
|
+
* хуже чужого языка.
|
|
11
|
+
*/
|
|
12
|
+
const localized = (source, language) => {
|
|
13
|
+
if (!source)
|
|
14
|
+
return '';
|
|
15
|
+
const exact = source[language];
|
|
16
|
+
if (exact === null || exact === void 0 ? void 0 : exact.trim())
|
|
17
|
+
return exact;
|
|
18
|
+
const fallback = source[languages_enum_1.Languages.EN];
|
|
19
|
+
if (fallback === null || fallback === void 0 ? void 0 : fallback.trim())
|
|
20
|
+
return fallback;
|
|
21
|
+
return Object.values(source).find((value) => value === null || value === void 0 ? void 0 : value.trim()) || '';
|
|
22
|
+
};
|
|
23
|
+
exports.localized = localized;
|
|
24
|
+
const fieldTitle = (field, language) => {
|
|
25
|
+
var _a, _b, _c, _d;
|
|
26
|
+
const i18n = ((_a = field.i18n) === null || _a === void 0 ? void 0 : _a[language]) || ((_b = field.i18n) === null || _b === void 0 ? void 0 : _b[languages_enum_1.Languages.EN]);
|
|
27
|
+
if ((_c = i18n === null || i18n === void 0 ? void 0 : i18n.title) === null || _c === void 0 ? void 0 : _c.trim())
|
|
28
|
+
return i18n.title;
|
|
29
|
+
return ((_d = Object.values(field.i18n || {}).find((item) => { var _a; return (_a = item === null || item === void 0 ? void 0 : item.title) === null || _a === void 0 ? void 0 : _a.trim(); })) === null || _d === void 0 ? void 0 : _d.title) || '';
|
|
30
|
+
};
|
|
31
|
+
exports.fieldTitle = fieldTitle;
|
|
32
|
+
const fieldText = (field, language, key) => {
|
|
33
|
+
var _a, _b;
|
|
34
|
+
const i18n = ((_a = field.i18n) === null || _a === void 0 ? void 0 : _a[language]) || ((_b = field.i18n) === null || _b === void 0 ? void 0 : _b[languages_enum_1.Languages.EN]);
|
|
35
|
+
return (i18n === null || i18n === void 0 ? void 0 : i18n[key]) || '';
|
|
36
|
+
};
|
|
37
|
+
exports.fieldText = fieldText;
|
|
38
|
+
const optionLabel = (option, language, fallback) => (0, exports.localized)(option.i18n, language) || fallback;
|
|
39
|
+
exports.optionLabel = optionLabel;
|
|
40
|
+
const MULTI_TYPES = [lead_field_type_enum_1.LeadFieldTypeEnum.CHECKBOX];
|
|
41
|
+
const isMultiple = (type) => MULTI_TYPES.includes(type);
|
|
42
|
+
exports.isMultiple = isMultiple;
|
|
43
|
+
/**
|
|
44
|
+
* Поле скрыто, пока в управляющем поле не выбран нужный вариант. Скрытое
|
|
45
|
+
* поле не валидируется и не попадает в ответы — иначе обязательная
|
|
46
|
+
* «уточните какой квест» блокировала бы отправку у всех остальных.
|
|
47
|
+
*/
|
|
48
|
+
const isVisible = (field, values) => {
|
|
49
|
+
const condition = field.conditional;
|
|
50
|
+
if (!(condition === null || condition === void 0 ? void 0 : condition.fieldId))
|
|
51
|
+
return true;
|
|
52
|
+
const parentValue = values[condition.fieldId];
|
|
53
|
+
if (!condition.optionId) {
|
|
54
|
+
return Array.isArray(parentValue) ? parentValue.length > 0 : !!parentValue;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(parentValue))
|
|
57
|
+
return parentValue.includes(condition.optionId);
|
|
58
|
+
return parentValue === condition.optionId;
|
|
59
|
+
};
|
|
60
|
+
exports.isVisible = isVisible;
|
|
61
|
+
const buildInitialValues = (form, prefill) => {
|
|
62
|
+
var _a;
|
|
63
|
+
const values = {};
|
|
64
|
+
for (const field of form.fields) {
|
|
65
|
+
const prefilled = field.mapsTo ? prefill === null || prefill === void 0 ? void 0 : prefill[field.mapsTo] : undefined;
|
|
66
|
+
if (prefilled !== undefined && prefilled !== '') {
|
|
67
|
+
values[field.id] = prefilled;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.CONSENT) {
|
|
71
|
+
values[field.id] = false;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if ((0, exports.isMultiple)(field.type)) {
|
|
75
|
+
values[field.id] = [];
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
// У степпера гостей всегда есть значение: пустое поле «сколько вас»
|
|
79
|
+
// клиент чаще пропускает, чем заполняет.
|
|
80
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.GUESTS) {
|
|
81
|
+
values[field.id] = (_a = field.min) !== null && _a !== void 0 ? _a : 2;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
values[field.id] = '';
|
|
85
|
+
}
|
|
86
|
+
return values;
|
|
87
|
+
};
|
|
88
|
+
exports.buildInitialValues = buildInitialValues;
|
|
89
|
+
const isEmpty = (value) => {
|
|
90
|
+
if (Array.isArray(value))
|
|
91
|
+
return !value.length;
|
|
92
|
+
if (typeof value === 'boolean')
|
|
93
|
+
return !value;
|
|
94
|
+
if (typeof value === 'number')
|
|
95
|
+
return false;
|
|
96
|
+
return !(value === null || value === void 0 ? void 0 : value.trim());
|
|
97
|
+
};
|
|
98
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
|
|
99
|
+
/**
|
|
100
|
+
* Валидация на клиенте — это про удобство: сервер всё равно перепроверит
|
|
101
|
+
* обязательность и наличие канала связи. Возвращаем ошибки по id поля плюс
|
|
102
|
+
* общую ошибку формы.
|
|
103
|
+
*/
|
|
104
|
+
const validate = ({ form, values, phoneValid, t, }) => {
|
|
105
|
+
const errors = {};
|
|
106
|
+
let hasPhone = false;
|
|
107
|
+
let hasEmail = false;
|
|
108
|
+
for (const field of form.fields) {
|
|
109
|
+
if (!(0, exports.isVisible)(field, values))
|
|
110
|
+
continue;
|
|
111
|
+
const value = values[field.id];
|
|
112
|
+
const empty = isEmpty(value);
|
|
113
|
+
if (field.required && empty) {
|
|
114
|
+
errors[field.id] = t('lead.required');
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (empty)
|
|
118
|
+
continue;
|
|
119
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.PHONE) {
|
|
120
|
+
if (!phoneValid) {
|
|
121
|
+
errors[field.id] = t('lead.invalidPhone');
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
hasPhone = true;
|
|
125
|
+
}
|
|
126
|
+
if (field.type === lead_field_type_enum_1.LeadFieldTypeEnum.EMAIL) {
|
|
127
|
+
if (!EMAIL_RE.test(String(value).trim())) {
|
|
128
|
+
errors[field.id] = t('lead.invalidEmail');
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
hasEmail = true;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// Заявку без телефона и почты обработать нельзя — бэкенд её отклонит,
|
|
135
|
+
// так что честнее сказать это до отправки.
|
|
136
|
+
const formError = !hasPhone && !hasEmail && !Object.keys(errors).length ? t('lead.needContact') : undefined;
|
|
137
|
+
return { errors, formError };
|
|
138
|
+
};
|
|
139
|
+
exports.validate = validate;
|
|
140
|
+
/**
|
|
141
|
+
* `contextNote` — то, что клиент уже выбрал до формы (пакет, дата, гости).
|
|
142
|
+
* Дописываем его в комментарий, а не в отдельное поле: у формы площадки
|
|
143
|
+
* такого поля может не быть, а менеджеру этот контекст нужен в карточке.
|
|
144
|
+
*/
|
|
145
|
+
const buildAnswers = (form, values, contextNote) => {
|
|
146
|
+
const commentField = form.fields.find((field) => field.mapsTo === lead_field_mapping_enum_1.LeadFieldMappingEnum.COMMENT && (0, exports.isVisible)(field, values));
|
|
147
|
+
return form.fields
|
|
148
|
+
.filter((field) => (0, exports.isVisible)(field, values))
|
|
149
|
+
.map((field) => {
|
|
150
|
+
const value = normalizeValue(values[field.id]);
|
|
151
|
+
if (!contextNote || field.id !== (commentField === null || commentField === void 0 ? void 0 : commentField.id)) {
|
|
152
|
+
return { fieldId: field.id, value };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
fieldId: field.id,
|
|
156
|
+
value: value ? `${contextNote}\n${value}` : contextNote,
|
|
157
|
+
};
|
|
158
|
+
})
|
|
159
|
+
.filter((answer) => answer.value !== undefined);
|
|
160
|
+
};
|
|
161
|
+
exports.buildAnswers = buildAnswers;
|
|
162
|
+
const normalizeValue = (value) => {
|
|
163
|
+
if (value === undefined || value === null)
|
|
164
|
+
return undefined;
|
|
165
|
+
if (Array.isArray(value))
|
|
166
|
+
return value.length ? value : undefined;
|
|
167
|
+
if (typeof value === 'string')
|
|
168
|
+
return value.trim() || undefined;
|
|
169
|
+
return value;
|
|
170
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useLeadT = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_i18next_1 = require("react-i18next");
|
|
6
|
+
/**
|
|
7
|
+
* Тексты формы заявки: часть приходит из настроек площадки (заголовок,
|
|
8
|
+
* подписи полей), но служебные строки — наши. Пока ключи не заведены в
|
|
9
|
+
* translations-сервисе, держим английские дефолты рядом с UI — ровно как
|
|
10
|
+
* в витрине пакетов (`use-package-t`).
|
|
11
|
+
*/
|
|
12
|
+
const DEFAULTS = {
|
|
13
|
+
'lead.title': 'Leave a request',
|
|
14
|
+
'lead.hint': 'Tell us what you need and we will call you back.',
|
|
15
|
+
'lead.submit': 'Send',
|
|
16
|
+
'lead.sending': 'Sending…',
|
|
17
|
+
'lead.successTitle': 'Thank you!',
|
|
18
|
+
'lead.successText': 'We have got your request and will contact you shortly.',
|
|
19
|
+
'lead.required': 'Please fill in this field',
|
|
20
|
+
'lead.invalidPhone': 'Enter a valid phone number',
|
|
21
|
+
'lead.invalidEmail': 'Enter a valid email',
|
|
22
|
+
'lead.needContact': 'Leave a phone number or an email so we can reach you',
|
|
23
|
+
'lead.failed': 'Failed to send the request, please try again',
|
|
24
|
+
'lead.tooManyRequests': 'Too many requests. Please try again later.',
|
|
25
|
+
'lead.unavailable': 'The request form is not available right now',
|
|
26
|
+
'lead.guestsMinus': 'One guest less',
|
|
27
|
+
'lead.guestsPlus': 'One guest more',
|
|
28
|
+
'lead.close': 'Close',
|
|
29
|
+
'lead.another': 'Send another request',
|
|
30
|
+
// Кнопка «оставить заявку» рядом с расписанием.
|
|
31
|
+
'lead.ctaTitle': 'Cannot find a suitable time?',
|
|
32
|
+
'lead.ctaHint': 'Leave a request and we will arrange the booking for you',
|
|
33
|
+
'lead.ctaButton': 'Leave a request',
|
|
34
|
+
// Та же кнопка в витрине сертификатов: про время говорить нельзя.
|
|
35
|
+
'lead.certCtaTitle': 'Need help with a gift?',
|
|
36
|
+
'lead.certCtaHint': 'Leave a request and we will pick a certificate for you',
|
|
37
|
+
};
|
|
38
|
+
const useLeadT = () => {
|
|
39
|
+
const { t } = (0, react_i18next_1.useTranslation)();
|
|
40
|
+
return (0, react_1.useCallback)((key, options) => { var _a; return t(key, Object.assign({ defaultValue: (_a = DEFAULTS[key]) !== null && _a !== void 0 ? _a : key }, options)); }, [t]);
|
|
41
|
+
};
|
|
42
|
+
exports.useLeadT = useLeadT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/hooks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.46",
|
|
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.0.
|
|
18
|
-
"@escapenavigator/types": "^2.0.
|
|
19
|
-
"@escapenavigator/utils": "^2.0.
|
|
17
|
+
"@escapenavigator/services": "^2.0.46",
|
|
18
|
+
"@escapenavigator/types": "^2.0.44",
|
|
19
|
+
"@escapenavigator/utils": "^2.0.46",
|
|
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": "1a84aa81dd428759ad51821e8095fd0b80b7cc62"
|
|
36
36
|
}
|