@escapenavigator/hooks 2.0.7 → 2.0.9

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.
@@ -99,6 +99,19 @@ export type PlayersAndVariationPickerProps = {
99
99
  * сводке рядом с «Итого»). По умолчанию `false` — поведение не меняется.
100
100
  */
101
101
  hidePriceHints?: boolean;
102
+ /**
103
+ * Опция «Определимся на месте» (questroom.playersSelectMode=OPTIONAL).
104
+ * Когда передана, первым тегом в ряду игроков рендерится отложенный
105
+ * выбор состава. Пока он выбран: числовые теги не подсвечены, секция
106
+ * детей скрыта (и дети сброшены в 0), ценовые подсказки пикера не
107
+ * показываются — хост рендерит «базовую стоимость от» сам. Клик по
108
+ * числу автоматически снимает опцию (onSelect(false)).
109
+ */
110
+ flexPlayersOption?: {
111
+ selected: boolean;
112
+ label: string;
113
+ onSelect: (flex: boolean) => void;
114
+ };
102
115
  };
103
116
  /**
104
117
  * Single shared component that renders the "Variation + Players" picker used
@@ -52,6 +52,7 @@ const format_amount_1 = require("@escapenavigator/utils/dist/format-amount");
52
52
  const get_players_price_1 = require("@escapenavigator/utils/dist/get-players-price");
53
53
  const get_prices_1 = require("@escapenavigator/utils/dist/get-prices");
54
54
  const react_1 = __importStar(require("react"));
55
+ const react_i18next_1 = require("react-i18next");
55
56
  const use_players_and_variation_picker_1 = require("../use-players-and-variation-picker");
56
57
  /**
57
58
  * Single shared component that renders the "Variation + Players" picker used
@@ -62,8 +63,26 @@ const use_players_and_variation_picker_1 = require("../use-players-and-variation
62
63
  * buttons, kids row, price hint, save button) was reimplemented in three
63
64
  * places with subtle drift. This component is the single source of truth.
64
65
  */
65
- const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loadSlotDetails, onVariationChange, onVariationResolved, onDetailsLoaded, players = 0, child = 0, tariff: tariffOverride, currency, minPlayers, maxPlayersLimit, slotDiscount, error, t, onChange, onSave, orderId, onLoadingChange, hidePriceHints = false, }) => {
66
+ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loadSlotDetails, onVariationChange, onVariationResolved, onDetailsLoaded, players = 0, child = 0, tariff: tariffOverride, currency, minPlayers, maxPlayersLimit, slotDiscount, error, t, onChange, onSave, orderId, onLoadingChange, hidePriceHints = false, flexPlayersOption, }) => {
66
67
  var _a, _b;
68
+ /**
69
+ * Страховка от «голых ключей» перевода. Пикер рендерит `prices:*` /
70
+ * `common:*` через переданный хостом `t`, но сам хост часто берёт
71
+ * `t` из `useTranslation()` без этих namespace'ов. В CRM `prices`
72
+ * грузится лениво — если юзер открыл карточку игры раньше, чем
73
+ * какой-либо экран задекларировал этот ns, бандл никогда не
74
+ * запрашивался и вместо переводов светились ключи/фолбэки.
75
+ *
76
+ * Декларируем namespace'ы прямо здесь: react-i18next сам догрузит
77
+ * недостающие бандлы и перерендерит компонент, когда они приедут.
78
+ * `useSuspense: false` — чтобы не требовать Suspense-границу у всех
79
+ * хостов; вместо этого до готовности показываем скелетон ниже.
80
+ * При ошибке загрузки i18next помечает ns как «loaded (failed)»,
81
+ * так что вечного скелетона не будет.
82
+ */
83
+ const { ready: translationsReady } = (0, react_i18next_1.useTranslation)(['prices', 'common'], {
84
+ useSuspense: false,
85
+ });
67
86
  const { loading: pickerLoading, options: pickerOptions, selectedOptionId, selectedVariation, buildVariationChange, selectVariation, details, } = (0, use_players_and_variation_picker_1.usePlayersAndVariationPicker)({
68
87
  slot,
69
88
  currentTariffId,
@@ -118,14 +137,26 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
118
137
  (0, react_1.useEffect)(() => {
119
138
  setChildren(child);
120
139
  }, [child]);
140
+ const flexSelected = !!(flexPlayersOption === null || flexPlayersOption === void 0 ? void 0 : flexPlayersOption.selected);
121
141
  const handleChangePlayers = (value) => {
122
142
  setPlayers(value);
123
143
  onChange === null || onChange === void 0 ? void 0 : onChange('players', value);
144
+ // Явный выбор числа снимает «Определимся на месте».
145
+ if (flexSelected)
146
+ flexPlayersOption === null || flexPlayersOption === void 0 ? void 0 : flexPlayersOption.onSelect(false);
124
147
  };
125
148
  const handleChangeChildren = (value) => {
126
149
  setChildren(value);
127
150
  onChange === null || onChange === void 0 ? void 0 : onChange('children', value);
128
151
  };
152
+ const handleSelectFlex = () => {
153
+ if (flexSelected)
154
+ return;
155
+ // Состав не определён — детей тоже сбрасываем (их выбор скрывается).
156
+ if (_children)
157
+ handleChangeChildren(0);
158
+ flexPlayersOption === null || flexPlayersOption === void 0 ? void 0 : flexPlayersOption.onSelect(true);
159
+ };
129
160
  // Цена 0 за число игроков = «это кол-во недоступно для выбора». Такие
130
161
  // ключи не рендерим кнопками и не учитываем в availablePlayerKeys/clamp.
131
162
  const playersOptions = (0, react_1.useMemo)(() => Object.entries(tariff)
@@ -216,7 +247,9 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
216
247
  // not yet available (initial render before fetch starts, in-flight fetch,
217
248
  // or backend returned no details). This prevents the brief flash of an
218
249
  // empty picker UI between "slot prop arrived" and "loading=true".
219
- if (slot && !details) {
250
+ // Also wait for the `prices`/`common` translation bundles so the picker
251
+ // never flashes raw i18n keys.
252
+ if (!translationsReady || (slot && !details)) {
220
253
  return (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 16 },
221
254
  react_1.default.createElement(skeleton_1.Skeleton, { visible: true, animate: true },
222
255
  react_1.default.createElement("div", { style: { height: 80 } })),
@@ -225,7 +258,11 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
225
258
  }
226
259
  return (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 16 },
227
260
  showVariations && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
228
- react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, t('prices:variation')),
261
+ react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, (details === null || details === void 0 ? void 0 : details.isFlex) === false
262
+ ? t('prices:chooseTariff', {
263
+ defaultValue: 'Выберите подходящий тариф',
264
+ })
265
+ : t('prices:variation')),
229
266
  react_1.default.createElement(flex_1.Flex, { gap: 'xs', wrap: true, justify: 'start' }, pickerOptions.map((option) => (react_1.default.createElement("div", { key: option.id, style: option.isFree
230
267
  ? undefined
231
268
  : { opacity: 0.4, pointerEvents: 'none' } },
@@ -236,14 +273,18 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
236
273
  react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
237
274
  react_1.default.createElement(flex_1.Flex, { gap: 'sm', align: 'center', justify: 'between' },
238
275
  react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, tariff.child ? t('prices:adults') : t('prices:choosePlayersAmount')),
239
- !!_children && !!_players && tariff[_players] != null && (react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, (0, format_amount_1.formatAmount)(tariff[_players], currency)))),
240
- react_1.default.createElement(flex_1.Flex, { gap: 'xs', wrap: true, justify: 'start' }, playersOptions
241
- .filter((p) => {
242
- const total = p.key + (_children || 0);
243
- return total >= effectiveMin && total <= effectiveMax;
244
- })
245
- .map((p) => (react_1.default.createElement(tag_1.Tag, { key: `adult-${p.key}`, text: `${p.key}`, onClick: () => handleChangePlayers(p.key), view: _players === p.key ? 'primary-inverted' : 'primary' }))))),
246
- !!childrenOptions.length && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
276
+ !flexSelected && !!_children && !!_players && tariff[_players] != null && (react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, (0, format_amount_1.formatAmount)(tariff[_players], currency)))),
277
+ react_1.default.createElement(flex_1.Flex, { gap: 'xs', wrap: true, justify: 'start' },
278
+ !!flexPlayersOption && (react_1.default.createElement(tag_1.Tag, { key: 'flex-players', text: flexPlayersOption.label, onClick: handleSelectFlex, view: flexSelected ? 'primary-inverted' : 'primary' })),
279
+ playersOptions
280
+ .filter((p) => {
281
+ const total = p.key + (_children || 0);
282
+ return total >= effectiveMin && total <= effectiveMax;
283
+ })
284
+ .map((p) => (react_1.default.createElement(tag_1.Tag, { key: `adult-${p.key}`, text: `${p.key}`, onClick: () => handleChangePlayers(p.key), view: !flexSelected && _players === p.key
285
+ ? 'primary-inverted'
286
+ : 'primary' }))))),
287
+ !flexSelected && !!childrenOptions.length && (react_1.default.createElement(flex_columns_1.FlexColumns, { columns: 1, gr: 8 },
247
288
  react_1.default.createElement(flex_1.Flex, { gap: 'sm', align: 'center', justify: 'between' },
248
289
  react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, t('prices:kids')),
249
290
  !!_children && tariff.child != null && (react_1.default.createElement(typography_1.Typography.Text, { view: 'title', color: 'secondary' }, (0, format_amount_1.formatAmount)(_children * tariff.child, currency)))),
@@ -255,13 +296,13 @@ const PlayersAndVariationPicker = ({ slot, currentTariffId, currentDuration, loa
255
296
  const isActive = _children === count;
256
297
  return (react_1.default.createElement(tag_1.Tag, { key: `child-${count}`, text: count === 0 ? t('prices:noKids') : `${count}`, onClick: () => handleChangeChildren(isActive ? 0 : count), view: isActive ? 'primary-inverted' : 'primary' }));
257
298
  })))),
258
- !hidePriceHints && !!totalHeads && (react_1.default.createElement(flex_1.Flex, { justify: 'start', gap: 'xs' },
299
+ !hidePriceHints && !flexSelected && !!totalHeads && (react_1.default.createElement(flex_1.Flex, { justify: 'start', gap: 'xs' },
259
300
  react_1.default.createElement(InfoMarkS_1.default, { style: { fill: 'var(--color-text-secondary)' } }),
260
301
  react_1.default.createElement(typography_1.Typography.Text, { view: 'caps', color: 'secondary' }, t('prices:totalPriceDescription', {
261
302
  teamPrice: (0, format_amount_1.formatAmount)(teamPrice, currency),
262
303
  personPrice: (0, format_amount_1.formatAmount)(personPrice, currency),
263
304
  })))),
264
- hidePriceHints && (!!totalHeads || !!childDescription) && (react_1.default.createElement("div", { style: {
305
+ hidePriceHints && !flexSelected && (!!totalHeads || !!childDescription) && (react_1.default.createElement("div", { style: {
265
306
  display: 'flex',
266
307
  flexDirection: 'column',
267
308
  gap: 6,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escapenavigator/hooks",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
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.7",
18
- "@escapenavigator/types": "^2.0.7",
19
- "@escapenavigator/utils": "^2.0.7",
17
+ "@escapenavigator/services": "^2.0.9",
18
+ "@escapenavigator/types": "^2.0.9",
19
+ "@escapenavigator/utils": "^2.0.9",
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": "1205192b8db237aeb156697754bced60b39197f8"
35
+ "gitHead": "eb29688633048e8011bc07e280dcaa84a2c61244"
36
36
  }