@escapenavigator/hooks 1.10.151 → 1.10.153

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.
@@ -0,0 +1,25 @@
1
+ type BuildConsentUrlsParams = {
2
+ /**
3
+ * База orders-сервиса. В виджете — `process.env.REACT_APP_ORDERS_DOMAIN`
4
+ * (регион уже зашит в env), в orders-SPA — `window.location.origin`.
5
+ */
6
+ baseUrl: string;
7
+ /** Slug компании оператора для ссылки на клиентское соглашение. */
8
+ slug?: string;
9
+ /**
10
+ * Код языка игры. Бэкенд `/documents/privacy_policy/:country` сам
11
+ * резолвит язык в основную страну, поэтому отдаём язык как есть.
12
+ */
13
+ language?: string;
14
+ };
15
+ export type ConsentUrls = {
16
+ agreementUrl: string;
17
+ policyUrl: string;
18
+ };
19
+ /**
20
+ * Строит публичные ссылки для блока согласий:
21
+ * - агримент: `/company/:slug/terms-of-services` (пусто, если нет slug);
22
+ * - политика: `/documents/privacy_policy/:language`.
23
+ */
24
+ export declare const buildConsentUrls: ({ baseUrl, slug, language }: BuildConsentUrlsParams) => ConsentUrls;
25
+ export {};
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildConsentUrls = void 0;
4
+ /**
5
+ * Строит публичные ссылки для блока согласий:
6
+ * - агримент: `/company/:slug/terms-of-services` (пусто, если нет slug);
7
+ * - политика: `/documents/privacy_policy/:language`.
8
+ */
9
+ const buildConsentUrls = ({ baseUrl, slug, language }) => {
10
+ const base = (baseUrl || '').replace(/\/+$/, '');
11
+ const lang = language || 'en';
12
+ return {
13
+ agreementUrl: slug ? `${base}/company/${slug}/terms-of-services` : '',
14
+ policyUrl: `${base}/documents/privacy_policy/${lang}`,
15
+ };
16
+ };
17
+ exports.buildConsentUrls = buildConsentUrls;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { buildConsentUrls, ConsentUrls } from './build-consent-urls';
3
+ export { buildConsentUrls };
4
+ export type { ConsentUrls };
5
+ export type ConsentField = 'agreeTerms' | 'agreePolicy' | 'sendAds';
6
+ export type ConsentValues = Record<ConsentField, boolean>;
7
+ export declare const INITIAL_CONSENT_VALUES: ConsentValues;
8
+ /** Обязательные согласия отмечены — можно подтверждать заказ/подпись. */
9
+ export declare const areRequiredConsentsAccepted: (values: ConsentValues) => boolean;
10
+ type Props = {
11
+ /** Ссылка на клиентское соглашение оператора. */
12
+ agreementUrl: string;
13
+ /** Ссылка на политику конфиденциальности. */
14
+ policyUrl: string;
15
+ values: ConsentValues;
16
+ onChange: (field: ConsentField, checked: boolean) => void;
17
+ /**
18
+ * Скрыть маркетинговый чекбокс (например, клиент уже согласился на рассылку
19
+ * ранее — повторно не спрашиваем).
20
+ */
21
+ hideMarketing?: boolean;
22
+ /** Подсветить невыбранные обязательные согласия + shake + текст ошибки. */
23
+ showRequiredError?: boolean;
24
+ };
25
+ export declare const ConsentCheckboxes: React.FC<Props>;
@@ -0,0 +1,43 @@
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.ConsentCheckboxes = exports.areRequiredConsentsAccepted = exports.INITIAL_CONSENT_VALUES = exports.buildConsentUrls = void 0;
8
+ const ui_1 = require("@alphakits/ui");
9
+ const react_1 = __importDefault(require("react"));
10
+ const react_i18next_1 = require("react-i18next");
11
+ const build_consent_urls_1 = require("./build-consent-urls");
12
+ Object.defineProperty(exports, "buildConsentUrls", { enumerable: true, get: function () { return build_consent_urls_1.buildConsentUrls; } });
13
+ const index_module_css_1 = __importDefault(require("./index.module.css"));
14
+ exports.INITIAL_CONSENT_VALUES = {
15
+ agreeTerms: false,
16
+ agreePolicy: false,
17
+ sendAds: false,
18
+ };
19
+ /** Обязательные согласия отмечены — можно подтверждать заказ/подпись. */
20
+ const areRequiredConsentsAccepted = (values) => values.agreeTerms && values.agreePolicy;
21
+ exports.areRequiredConsentsAccepted = areRequiredConsentsAccepted;
22
+ const labelColor = (invalid) => (invalid ? 'negative' : 'secondary');
23
+ const ConsentCheckboxes = ({ agreementUrl, policyUrl, values, onChange, hideMarketing, showRequiredError, }) => {
24
+ const { t } = (0, react_i18next_1.useTranslation)('common');
25
+ // Агримент + политику объединили в одну обязательную галочку: меньше
26
+ // «трения» на экране оплаты. Обе ссылки внутри одной переводимой строки.
27
+ const requiredAccepted = values.agreeTerms && values.agreePolicy;
28
+ const requiredInvalid = !!showRequiredError && !requiredAccepted;
29
+ const setRequired = (checked) => {
30
+ onChange('agreeTerms', checked);
31
+ onChange('agreePolicy', checked);
32
+ };
33
+ return (react_1.default.createElement("div", { className: [index_module_css_1.default.wrapper, showRequiredError ? index_module_css_1.default.shake : ''].join(' ').trim() },
34
+ react_1.default.createElement(ui_1.FlexColumns, { columns: 1, gr: 8 },
35
+ react_1.default.createElement(ui_1.Checkbox, { size: 's', align: 'start', block: true, checked: requiredAccepted, onChange: (_e, { checked }) => setRequired(checked), label: react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: labelColor(requiredInvalid) },
36
+ react_1.default.createElement(react_i18next_1.Trans, { t: t, ns: 'common', i18nKey: 'consent.agreementAndPolicy', defaults: 'I accept the <0>client agreement</0> and <1>privacy policy</1>', components: [
37
+ react_1.default.createElement(ui_1.Link, { key: 'agreement', size: 's', target: '_blank', rel: 'noreferrer', className: index_module_css_1.default.link, href: agreementUrl }),
38
+ react_1.default.createElement(ui_1.Link, { key: 'policy', size: 's', target: '_blank', rel: 'noreferrer', className: index_module_css_1.default.link, href: policyUrl }),
39
+ ] })) }),
40
+ !hideMarketing && (react_1.default.createElement(ui_1.Checkbox, { size: 's', align: 'start', block: true, checked: values.sendAds, onChange: (_e, { checked }) => onChange('sendAds', checked), label: react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('consent.marketing', "I'd like to receive special offers, promotions and news — no spam, unsubscribe anytime")) })),
41
+ showRequiredError && (react_1.default.createElement(ui_1.Typography.Text, { view: 'caps', color: 'negative' }, t('consent.required', 'Please accept the required agreements to continue'))))));
42
+ };
43
+ exports.ConsentCheckboxes = ConsentCheckboxes;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
+ export * from './consent-checkboxes';
1
2
  export * from './functional-popover';
2
3
  export * from './input-tel';
4
+ export * from './lazy-with-retry';
3
5
  export * from './players-and-variation-picker';
4
6
  export * from './use-debounced-value';
5
7
  export * from './use-localstore-state';
package/dist/index.js CHANGED
@@ -14,8 +14,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./consent-checkboxes"), exports);
17
18
  __exportStar(require("./functional-popover"), exports);
18
19
  __exportStar(require("./input-tel"), exports);
20
+ __exportStar(require("./lazy-with-retry"), exports);
19
21
  __exportStar(require("./players-and-variation-picker"), exports);
20
22
  __exportStar(require("./use-debounced-value"), exports);
21
23
  __exportStar(require("./use-localstore-state"), exports);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Обёртка над `React.lazy`, устойчивая к «исчезнувшим» чанкам после деплоя.
3
+ * Общая для всех фронтов (app / orders), завязана на React → живёт в hooks.
4
+ *
5
+ * Кейс: у пользователя открыта старая вкладка с index-OLD.js. После релиза
6
+ * этого чанка на CDN уже нет → `import()` бросает (формулировки зависят от
7
+ * браузера — см. `isStaleChunkError` в @escapenavigator/utils):
8
+ * - "Failed to fetch dynamically imported module" (Chrome/Safari)
9
+ * - "error loading dynamically imported module" (Firefox)
10
+ * - "Importing a module script failed"
11
+ * - "ChunkLoadError" / "Loading chunk N failed"
12
+ *
13
+ * Алгоритм:
14
+ * 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
15
+ * 2. Если опять упало и похоже на «протухший чанк» — один раз перезагружаем
16
+ * страницу с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage
17
+ * защищает от циклической перезагрузки.
18
+ */
19
+ import { ComponentType, lazy } from 'react';
20
+ /** Query-параметр cache-bust после reload; не должен попадать в `where` таблиц. */
21
+ export declare const CHUNK_RELOAD_QUERY_PARAM = "v";
22
+ /**
23
+ * Убирает служебный `?v=` из адресной строки (replaceState, без reload).
24
+ * Вызываем до первого рендера и после успешной загрузки lazy-чанка.
25
+ */
26
+ export declare const stripChunkReloadQueryParam: () => boolean;
27
+ export declare function lazyWithRetry<T extends ComponentType<any>>(factory: () => Promise<{
28
+ default: T;
29
+ }>): ReturnType<typeof lazy<T>>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Обёртка над `React.lazy`, устойчивая к «исчезнувшим» чанкам после деплоя.
4
+ * Общая для всех фронтов (app / orders), завязана на React → живёт в hooks.
5
+ *
6
+ * Кейс: у пользователя открыта старая вкладка с index-OLD.js. После релиза
7
+ * этого чанка на CDN уже нет → `import()` бросает (формулировки зависят от
8
+ * браузера — см. `isStaleChunkError` в @escapenavigator/utils):
9
+ * - "Failed to fetch dynamically imported module" (Chrome/Safari)
10
+ * - "error loading dynamically imported module" (Firefox)
11
+ * - "Importing a module script failed"
12
+ * - "ChunkLoadError" / "Loading chunk N failed"
13
+ *
14
+ * Алгоритм:
15
+ * 1. Один retry с небольшой паузой — на случай флапа сети/прогрева CDN.
16
+ * 2. Если опять упало и похоже на «протухший чанк» — один раз перезагружаем
17
+ * страницу с `?v=<ts>`, чтобы выкинуть HTTP-кэш. Флаг в sessionStorage
18
+ * защищает от циклической перезагрузки.
19
+ */
20
+ /* eslint-disable @typescript-eslint/no-explicit-any */
21
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ };
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.stripChunkReloadQueryParam = exports.CHUNK_RELOAD_QUERY_PARAM = void 0;
32
+ exports.lazyWithRetry = lazyWithRetry;
33
+ const is_stale_chunk_error_1 = require("@escapenavigator/utils/dist/is-stale-chunk-error");
34
+ const react_1 = require("react");
35
+ const RELOAD_FLAG = 'lazy_chunk_reload_attempted';
36
+ /** Query-параметр cache-bust после reload; не должен попадать в `where` таблиц. */
37
+ exports.CHUNK_RELOAD_QUERY_PARAM = 'v';
38
+ /**
39
+ * Убирает служебный `?v=` из адресной строки (replaceState, без reload).
40
+ * Вызываем до первого рендера и после успешной загрузки lazy-чанка.
41
+ */
42
+ const stripChunkReloadQueryParam = () => {
43
+ if (typeof window === 'undefined')
44
+ return false;
45
+ const url = new URL(window.location.href);
46
+ if (!url.searchParams.has(exports.CHUNK_RELOAD_QUERY_PARAM))
47
+ return false;
48
+ url.searchParams.delete(exports.CHUNK_RELOAD_QUERY_PARAM);
49
+ const next = url.pathname + (url.searchParams.toString() ? `?${url.searchParams}` : '') + url.hash;
50
+ window.history.replaceState(window.history.state, '', next);
51
+ return true;
52
+ };
53
+ exports.stripChunkReloadQueryParam = stripChunkReloadQueryParam;
54
+ const reloadOnce = () => {
55
+ try {
56
+ if (sessionStorage.getItem(RELOAD_FLAG))
57
+ return;
58
+ sessionStorage.setItem(RELOAD_FLAG, '1');
59
+ }
60
+ catch (_a) {
61
+ // sessionStorage может быть недоступен (Safari privacy / iframe);
62
+ // тогда просто перезагрузимся, лучше один лишний reload, чем белый экран.
63
+ }
64
+ const url = new URL(window.location.href);
65
+ url.searchParams.set(exports.CHUNK_RELOAD_QUERY_PARAM, Date.now().toString(36));
66
+ window.location.replace(url.toString());
67
+ };
68
+ function lazyWithRetry(factory) {
69
+ return (0, react_1.lazy)(() => __awaiter(this, void 0, void 0, function* () {
70
+ try {
71
+ const mod = yield factory();
72
+ try {
73
+ sessionStorage.removeItem(RELOAD_FLAG);
74
+ }
75
+ catch (_a) {
76
+ // см. reloadOnce: storage может быть недоступен
77
+ }
78
+ (0, exports.stripChunkReloadQueryParam)();
79
+ return mod;
80
+ }
81
+ catch (firstError) {
82
+ if (!(0, is_stale_chunk_error_1.isStaleChunkError)(firstError))
83
+ throw firstError;
84
+ try {
85
+ yield new Promise((r) => setTimeout(r, 350));
86
+ return yield factory();
87
+ }
88
+ catch (secondError) {
89
+ if ((0, is_stale_chunk_error_1.isStaleChunkError)(secondError)) {
90
+ reloadOnce();
91
+ }
92
+ throw secondError;
93
+ }
94
+ }
95
+ }));
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/hooks",
3
- "version": "1.10.151",
3
+ "version": "1.10.153",
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": "^1.10.159",
18
- "@escapenavigator/types": "^1.10.148",
19
- "@escapenavigator/utils": "^1.10.154",
17
+ "@escapenavigator/services": "^1.10.161",
18
+ "@escapenavigator/types": "^1.10.150",
19
+ "@escapenavigator/utils": "^1.10.156",
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": "8ac9c90c5d3971794408544fc837167b6495d246"
35
+ "gitHead": "a7a449f109ee8966f501a0b3fdabaca03439df53"
36
36
  }