@escapenavigator/utils 2.0.0 → 2.0.3
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/format-promocode-discount/index.d.ts +7 -0
- package/dist/format-promocode-discount/index.js +20 -0
- package/dist/get-order-cancelation-state/index.d.ts +9 -1
- package/dist/get-order-cancelation-state/index.js +6 -4
- package/dist/get-order-cancelation-state/index.test.js +92 -0
- package/dist/mask-contacts.d.ts +15 -0
- package/dist/mask-contacts.js +46 -0
- package/dist/role-contact-permissions.d.ts +7 -0
- package/dist/role-contact-permissions.js +9 -0
- package/dist/sanitize-html.d.ts +11 -0
- package/dist/sanitize-html.js +44 -0
- package/package.json +4 -3
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { PromocodeTypeEnum } from '@escapenavigator/types/dist/promocode/emun/promocode-type.enum';
|
|
2
|
+
/**
|
|
3
|
+
* Format promocode discount for display.
|
|
4
|
+
* Fixed amounts are stored in minor units (cents); percent values are stored
|
|
5
|
+
* as hundredths of a percent (1500 → 15%).
|
|
6
|
+
*/
|
|
7
|
+
export declare const formatPromocodeDiscount: (type: PromocodeTypeEnum, discount: number, currency: string, clientDescription?: string | null) => string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatPromocodeDiscount = void 0;
|
|
4
|
+
const promocode_type_enum_1 = require("@escapenavigator/types/dist/promocode/emun/promocode-type.enum");
|
|
5
|
+
const format_amount_1 = require("../format-amount");
|
|
6
|
+
/**
|
|
7
|
+
* Format promocode discount for display.
|
|
8
|
+
* Fixed amounts are stored in minor units (cents); percent values are stored
|
|
9
|
+
* as hundredths of a percent (1500 → 15%).
|
|
10
|
+
*/
|
|
11
|
+
const formatPromocodeDiscount = (type, discount, currency, clientDescription) => {
|
|
12
|
+
if (type === promocode_type_enum_1.PromocodeTypeEnum.PROMOTIONAL) {
|
|
13
|
+
return clientDescription?.trim() || '—';
|
|
14
|
+
}
|
|
15
|
+
if (type === promocode_type_enum_1.PromocodeTypeEnum.FIXED) {
|
|
16
|
+
return (0, format_amount_1.formatAmount)(discount, currency);
|
|
17
|
+
}
|
|
18
|
+
return `${discount / 100}%`;
|
|
19
|
+
};
|
|
20
|
+
exports.formatPromocodeDiscount = formatPromocodeDiscount;
|
|
@@ -22,6 +22,14 @@ type Props = {
|
|
|
22
22
|
payed: number;
|
|
23
23
|
minimalPrice: number;
|
|
24
24
|
slotDiscount: number;
|
|
25
|
+
/**
|
|
26
|
+
* Суммарная стоимость невозвратных допуслуг заказа (напр. страховка от
|
|
27
|
+
* отмены). Удерживается венью независимо от штрафа и таймингов отмены,
|
|
28
|
+
* поэтому вычитается из суммы к возврату (с полом 0). Неоплаченная часть
|
|
29
|
+
* остаётся долгом клиента и учитывается в total заказа (см. updatePrice),
|
|
30
|
+
* поэтому здесь в chargeAmount не добавляется.
|
|
31
|
+
*/
|
|
32
|
+
nonRefundableAmount?: number;
|
|
25
33
|
};
|
|
26
34
|
export declare enum FineDescriptions {
|
|
27
35
|
FINE_TON_SETUP = "descriptionFineNotSetup",
|
|
@@ -35,7 +43,7 @@ export declare const isCancelationAllowed: ({ date, minHoursForFreeCanceling, }:
|
|
|
35
43
|
date: string;
|
|
36
44
|
minHoursForFreeCanceling: number;
|
|
37
45
|
}) => boolean;
|
|
38
|
-
export declare function getOrderCancelationState({ cancelationReason, cancelationRule, cancelationAmount, transactions, date, returnCertificates, forceFine, certificates, minHoursForFreeCanceling, minHoursForFullFine, playersPrice, slotDiscount, payed, minimalPrice, }: Props): {
|
|
46
|
+
export declare function getOrderCancelationState({ cancelationReason, cancelationRule, cancelationAmount, transactions, date, returnCertificates, forceFine, certificates, minHoursForFreeCanceling, minHoursForFullFine, playersPrice, slotDiscount, payed, minimalPrice, nonRefundableAmount, }: Props): {
|
|
39
47
|
fineAmount: number;
|
|
40
48
|
returnAmount: number;
|
|
41
49
|
allowedCancelation: boolean;
|
|
@@ -22,7 +22,9 @@ var FineDescriptions;
|
|
|
22
22
|
})(FineDescriptions || (exports.FineDescriptions = FineDescriptions = {}));
|
|
23
23
|
const isCancelationAllowed = ({ date, minHoursForFreeCanceling = 0, }) => (0, date_1.differenceInHours)(new Date(date), new Date()) >= minHoursForFreeCanceling;
|
|
24
24
|
exports.isCancelationAllowed = isCancelationAllowed;
|
|
25
|
-
function getOrderCancelationState({ cancelationReason, cancelationRule, cancelationAmount, transactions, date, returnCertificates, forceFine, certificates, minHoursForFreeCanceling, minHoursForFullFine, playersPrice, slotDiscount = 0, payed, minimalPrice, }) {
|
|
25
|
+
function getOrderCancelationState({ cancelationReason, cancelationRule, cancelationAmount, transactions, date, returnCertificates, forceFine, certificates, minHoursForFreeCanceling, minHoursForFullFine, playersPrice, slotDiscount = 0, payed, minimalPrice, nonRefundableAmount = 0, }) {
|
|
26
|
+
// Удерживаем стоимость невозвратных услуг из суммы к возврату (пол 0).
|
|
27
|
+
const withholdNonRefundable = (amount) => Math.max(0, +(amount - nonRefundableAmount).toFixed(0));
|
|
26
28
|
const halfFineCancelation = cancelationRule !== questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.NO &&
|
|
27
29
|
cancelationReason !== order_cancel_reson_enum_1.OrderCancelReasonEnum.QUESTROOM &&
|
|
28
30
|
!(0, exports.isCancelationAllowed)({
|
|
@@ -69,7 +71,7 @@ function getOrderCancelationState({ cancelationReason, cancelationRule, cancelat
|
|
|
69
71
|
return {
|
|
70
72
|
fineAmount: 0,
|
|
71
73
|
chargeAmount: 0,
|
|
72
|
-
returnAmount: receivedTransactionsAmount,
|
|
74
|
+
returnAmount: withholdNonRefundable(receivedTransactionsAmount),
|
|
73
75
|
allowedCancelation,
|
|
74
76
|
type: fineAmount ? FineDescriptions.NO_FINE : FineDescriptions.FINE_TON_SETUP,
|
|
75
77
|
};
|
|
@@ -78,7 +80,7 @@ function getOrderCancelationState({ cancelationReason, cancelationRule, cancelat
|
|
|
78
80
|
return {
|
|
79
81
|
fineAmount: 0,
|
|
80
82
|
chargeAmount: 0,
|
|
81
|
-
returnAmount: receivedTransactionsAmount,
|
|
83
|
+
returnAmount: withholdNonRefundable(receivedTransactionsAmount),
|
|
82
84
|
allowedCancelation,
|
|
83
85
|
type: autoReturtAvailable ? FineDescriptions.AUTO_RETURN : FineDescriptions.HAND_RETURN,
|
|
84
86
|
};
|
|
@@ -96,7 +98,7 @@ function getOrderCancelationState({ cancelationReason, cancelationRule, cancelat
|
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
if (chargeAmount < 0) {
|
|
99
|
-
const returnAmount = +(chargeAmount * -1).toFixed(0);
|
|
101
|
+
const returnAmount = withholdNonRefundable(+(chargeAmount * -1).toFixed(0));
|
|
100
102
|
const allowAutoReturn = !slotDiscount && autoReturtAvailable && receivedTransactionsAmount >= returnAmount;
|
|
101
103
|
return {
|
|
102
104
|
type: allowAutoReturn ? FineDescriptions.AUTO_RETURN : FineDescriptions.HAND_RETURN,
|
|
@@ -253,4 +253,96 @@ describe('getOrderCancelationState function', () => {
|
|
|
253
253
|
allowedCancelation: false,
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
|
+
test('should withhold non-refundable amount from return on in-time cancelation', () => {
|
|
257
|
+
const result = (0, _1.getOrderCancelationState)({
|
|
258
|
+
cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
|
|
259
|
+
slotDiscount: 0,
|
|
260
|
+
cancelationAmount: 0,
|
|
261
|
+
minHoursForFullFine: 0,
|
|
262
|
+
cancelationRule: questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.FULL_PRICE,
|
|
263
|
+
transactions: [
|
|
264
|
+
{
|
|
265
|
+
amount: 120,
|
|
266
|
+
type: transaction_type_enum_1.TransactionTypeEnum.INSIDE,
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
date: (0, date_1.addHours)(new Date(), 50).toISOString(),
|
|
270
|
+
returnCertificates: true,
|
|
271
|
+
certificates: [],
|
|
272
|
+
minHoursForFreeCanceling: 48,
|
|
273
|
+
playersPrice: 100,
|
|
274
|
+
payed: 120,
|
|
275
|
+
minimalPrice: 50,
|
|
276
|
+
nonRefundableAmount: 20,
|
|
277
|
+
});
|
|
278
|
+
expect(result).toEqual({
|
|
279
|
+
type: 'descriptionFineWithReturn',
|
|
280
|
+
fineAmount: 0,
|
|
281
|
+
returnAmount: 100,
|
|
282
|
+
chargeAmount: 0,
|
|
283
|
+
allowedCancelation: true,
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
test('should withhold non-refundable amount on late cancelation with overpayment', () => {
|
|
287
|
+
const result = (0, _1.getOrderCancelationState)({
|
|
288
|
+
cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
|
|
289
|
+
slotDiscount: 0,
|
|
290
|
+
minHoursForFullFine: 0,
|
|
291
|
+
cancelationAmount: 0,
|
|
292
|
+
cancelationRule: questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.HALF_PRICE,
|
|
293
|
+
transactions: [
|
|
294
|
+
{
|
|
295
|
+
amount: 120,
|
|
296
|
+
type: transaction_type_enum_1.TransactionTypeEnum.SUCCEEDED,
|
|
297
|
+
},
|
|
298
|
+
],
|
|
299
|
+
date: new Date().toISOString(),
|
|
300
|
+
returnCertificates: true,
|
|
301
|
+
certificates: [],
|
|
302
|
+
minHoursForFreeCanceling: 48,
|
|
303
|
+
playersPrice: 100,
|
|
304
|
+
payed: 120,
|
|
305
|
+
minimalPrice: 50,
|
|
306
|
+
nonRefundableAmount: 20,
|
|
307
|
+
});
|
|
308
|
+
// fine = 50 (half of quest), refund base = 120 - 50 = 70, minus
|
|
309
|
+
// non-refundable 20 → 50.
|
|
310
|
+
expect(result).toEqual({
|
|
311
|
+
type: 'descriptionFineWithAutoReturn',
|
|
312
|
+
fineAmount: 50,
|
|
313
|
+
returnAmount: 50,
|
|
314
|
+
chargeAmount: 0,
|
|
315
|
+
allowedCancelation: false,
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
test('should floor return at 0 when non-refundable amount exceeds refund', () => {
|
|
319
|
+
const result = (0, _1.getOrderCancelationState)({
|
|
320
|
+
cancelationReason: order_cancel_reson_enum_1.OrderCancelReasonEnum.CLIENT,
|
|
321
|
+
slotDiscount: 0,
|
|
322
|
+
minHoursForFullFine: 0,
|
|
323
|
+
cancelationAmount: 0,
|
|
324
|
+
cancelationRule: questroom_cancelation_type_enum_1.QuestroomCancelationTypeEnum.FULL_PRICE,
|
|
325
|
+
transactions: [
|
|
326
|
+
{
|
|
327
|
+
amount: 120,
|
|
328
|
+
type: transaction_type_enum_1.TransactionTypeEnum.INSIDE,
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
date: (0, date_1.addHours)(new Date(), 50).toISOString(),
|
|
332
|
+
returnCertificates: true,
|
|
333
|
+
certificates: [],
|
|
334
|
+
minHoursForFreeCanceling: 48,
|
|
335
|
+
playersPrice: 100,
|
|
336
|
+
payed: 120,
|
|
337
|
+
minimalPrice: 50,
|
|
338
|
+
nonRefundableAmount: 200,
|
|
339
|
+
});
|
|
340
|
+
expect(result).toEqual({
|
|
341
|
+
type: 'descriptionFineWithReturn',
|
|
342
|
+
fineAmount: 0,
|
|
343
|
+
returnAmount: 0,
|
|
344
|
+
chargeAmount: 0,
|
|
345
|
+
allowedCancelation: true,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
256
348
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Символ-заполнитель для маскированных контактов клиента. */
|
|
2
|
+
export declare const MASK_CHAR = "\u2022";
|
|
3
|
+
/**
|
|
4
|
+
* Маскирует email, оставляя первый символ локальной части и домен:
|
|
5
|
+
* `john.doe@mail.com` → `j•••@mail.com`. Пустые/невалидные значения
|
|
6
|
+
* возвращаются как есть (нечего маскировать).
|
|
7
|
+
*/
|
|
8
|
+
export declare const maskEmail: (email?: string | null) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Маскирует телефон, оставляя последние 2 цифры:
|
|
11
|
+
* `+4915112345678` → `+49•••••••78`-подобный вид. Сохраняет ведущий `+`.
|
|
12
|
+
*/
|
|
13
|
+
export declare const maskPhone: (phone?: string | null) => string;
|
|
14
|
+
/** Похоже ли значение на уже замаскированное (содержит символ-заполнитель). */
|
|
15
|
+
export declare const isMaskedContact: (value?: string | null) => boolean;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isMaskedContact = exports.maskPhone = exports.maskEmail = exports.MASK_CHAR = void 0;
|
|
4
|
+
/** Символ-заполнитель для маскированных контактов клиента. */
|
|
5
|
+
exports.MASK_CHAR = '•';
|
|
6
|
+
/**
|
|
7
|
+
* Маскирует email, оставляя первый символ локальной части и домен:
|
|
8
|
+
* `john.doe@mail.com` → `j•••@mail.com`. Пустые/невалидные значения
|
|
9
|
+
* возвращаются как есть (нечего маскировать).
|
|
10
|
+
*/
|
|
11
|
+
const maskEmail = (email) => {
|
|
12
|
+
if (!email)
|
|
13
|
+
return email ?? '';
|
|
14
|
+
const trimmed = String(email).trim();
|
|
15
|
+
const at = trimmed.indexOf('@');
|
|
16
|
+
if (at <= 0) {
|
|
17
|
+
// Нет валидной локальной части — маскируем целиком.
|
|
18
|
+
return exports.MASK_CHAR.repeat(Math.max(trimmed.length, 3));
|
|
19
|
+
}
|
|
20
|
+
const local = trimmed.slice(0, at);
|
|
21
|
+
const domain = trimmed.slice(at);
|
|
22
|
+
const firstChar = local[0];
|
|
23
|
+
return `${firstChar}${exports.MASK_CHAR.repeat(3)}${domain}`;
|
|
24
|
+
};
|
|
25
|
+
exports.maskEmail = maskEmail;
|
|
26
|
+
/**
|
|
27
|
+
* Маскирует телефон, оставляя последние 2 цифры:
|
|
28
|
+
* `+4915112345678` → `+49•••••••78`-подобный вид. Сохраняет ведущий `+`.
|
|
29
|
+
*/
|
|
30
|
+
const maskPhone = (phone) => {
|
|
31
|
+
if (!phone)
|
|
32
|
+
return phone ?? '';
|
|
33
|
+
const trimmed = String(phone).trim();
|
|
34
|
+
const hasPlus = trimmed.startsWith('+');
|
|
35
|
+
const digits = trimmed.replace(/\D/g, '');
|
|
36
|
+
if (digits.length <= 2) {
|
|
37
|
+
return `${hasPlus ? '+' : ''}${exports.MASK_CHAR.repeat(Math.max(digits.length, 3))}`;
|
|
38
|
+
}
|
|
39
|
+
const visible = digits.slice(-2);
|
|
40
|
+
const maskedCount = digits.length - 2;
|
|
41
|
+
return `${hasPlus ? '+' : ''}${exports.MASK_CHAR.repeat(maskedCount)}${visible}`;
|
|
42
|
+
};
|
|
43
|
+
exports.maskPhone = maskPhone;
|
|
44
|
+
/** Похоже ли значение на уже замаскированное (содержит символ-заполнитель). */
|
|
45
|
+
const isMaskedContact = (value) => !!value && value.includes(exports.MASK_CHAR);
|
|
46
|
+
exports.isMaskedContact = isMaskedContact;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RoleRO } from '@escapenavigator/types/dist/role/role.ro';
|
|
2
|
+
export type RoleContactPermissions = Pick<RoleRO, 'totalAccess' | 'canRevealClientContacts'>;
|
|
3
|
+
/**
|
|
4
|
+
* Право видеть полные контакты клиента (телефон/email) без маскировки.
|
|
5
|
+
* Владелец (`totalAccess`) видит всегда; остальным нужен явный флаг.
|
|
6
|
+
*/
|
|
7
|
+
export declare const canRevealClientContacts: (role?: RoleContactPermissions | null) => boolean;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canRevealClientContacts = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Право видеть полные контакты клиента (телефон/email) без маскировки.
|
|
6
|
+
* Владелец (`totalAccess`) видит всегда; остальным нужен явный флаг.
|
|
7
|
+
*/
|
|
8
|
+
const canRevealClientContacts = (role) => !!role?.totalAccess || !!role?.canRevealClientContacts;
|
|
9
|
+
exports.canRevealClientContacts = canRevealClientContacts;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Единственная точка XSS-санитизации HTML перед `dangerouslySetInnerHTML`.
|
|
3
|
+
*
|
|
4
|
+
* `sanitizeQuillHtml` исторически только снимала цвета/пробелы и НЕ была защитой
|
|
5
|
+
* от XSS (сохраняла `<script>`/`<style>`, не резала `onerror=`/`javascript:`).
|
|
6
|
+
* Здесь через DOMPurify вырезаем скрипты, inline-обработчики событий и опасные
|
|
7
|
+
* URL-схемы, оставляя обычное форматирование (Quill-HTML договоров, писем,
|
|
8
|
+
* контента квестов). `isomorphic-dompurify` работает и в браузере, и в Node
|
|
9
|
+
* (SSR/тесты), поэтому утилита безопасна в общем пакете.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeHtml(html: string | null | undefined): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.sanitizeHtml = sanitizeHtml;
|
|
7
|
+
const isomorphic_dompurify_1 = __importDefault(require("isomorphic-dompurify"));
|
|
8
|
+
/**
|
|
9
|
+
* Единственная точка XSS-санитизации HTML перед `dangerouslySetInnerHTML`.
|
|
10
|
+
*
|
|
11
|
+
* `sanitizeQuillHtml` исторически только снимала цвета/пробелы и НЕ была защитой
|
|
12
|
+
* от XSS (сохраняла `<script>`/`<style>`, не резала `onerror=`/`javascript:`).
|
|
13
|
+
* Здесь через DOMPurify вырезаем скрипты, inline-обработчики событий и опасные
|
|
14
|
+
* URL-схемы, оставляя обычное форматирование (Quill-HTML договоров, писем,
|
|
15
|
+
* контента квестов). `isomorphic-dompurify` работает и в браузере, и в Node
|
|
16
|
+
* (SSR/тесты), поэтому утилита безопасна в общем пакете.
|
|
17
|
+
*/
|
|
18
|
+
function sanitizeHtml(html) {
|
|
19
|
+
if (!html)
|
|
20
|
+
return '';
|
|
21
|
+
return isomorphic_dompurify_1.default.sanitize(html, {
|
|
22
|
+
// Ссылки в контенте открываем в новой вкладке — сохраняем `target`,
|
|
23
|
+
// а `rel="noopener"` навешиваем хуком ниже.
|
|
24
|
+
ADD_ATTR: ['target'],
|
|
25
|
+
// `<style>` НЕ запрещаем: DOMPurify санитизирует CSS (убирает @import,
|
|
26
|
+
// expression и т.п.), а email-превью на нём держат вёрстку. Режем именно
|
|
27
|
+
// XSS-векторы: скрипты, встраивания и формы (фишинг).
|
|
28
|
+
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
|
|
29
|
+
FORBID_ATTR: ['srcdoc'],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Любой `<a target="_blank">` получает `rel="noopener noreferrer"` — защита от
|
|
33
|
+
// tabnabbing и утечки referrer из пользовательского HTML. Не используем
|
|
34
|
+
// `instanceof Element`: в Node (jsdom через isomorphic-dompurify) глобального
|
|
35
|
+
// `Element` нет, поэтому проверяем `nodeName` и наличие методов.
|
|
36
|
+
isomorphic_dompurify_1.default.addHook('afterSanitizeAttributes', (node) => {
|
|
37
|
+
const el = node;
|
|
38
|
+
if (el.nodeName === 'A' &&
|
|
39
|
+
typeof el.getAttribute === 'function' &&
|
|
40
|
+
typeof el.setAttribute === 'function' &&
|
|
41
|
+
el.getAttribute('target') === '_blank') {
|
|
42
|
+
el.setAttribute('rel', 'noopener noreferrer');
|
|
43
|
+
}
|
|
44
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@escapenavigator/utils",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
"test": "jest"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@escapenavigator/types": "^2.0.
|
|
17
|
+
"@escapenavigator/types": "^2.0.3",
|
|
18
18
|
"axios": "^0.21.4",
|
|
19
19
|
"class-transformer": "^0.5.1",
|
|
20
20
|
"class-validator": "^0.13.2",
|
|
21
21
|
"i18next": "^21.6.4",
|
|
22
|
+
"isomorphic-dompurify": "^3.18.0",
|
|
22
23
|
"libphonenumber-js": "^1.12.24",
|
|
23
24
|
"quill-delta": "^5.1.0"
|
|
24
25
|
},
|
|
@@ -28,5 +29,5 @@
|
|
|
28
29
|
"ts-jest": "^29.1.1",
|
|
29
30
|
"typescript": "^5.6"
|
|
30
31
|
},
|
|
31
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "13a7f625f7f30f2ae4fb84b7344ef2c51112e8b5"
|
|
32
33
|
}
|