@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
|
-
|
|
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' },
|
|
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' },
|
|
241
|
-
.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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.
|
|
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.
|
|
18
|
-
"@escapenavigator/types": "^2.0.
|
|
19
|
-
"@escapenavigator/utils": "^2.0.
|
|
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": "
|
|
35
|
+
"gitHead": "eb29688633048e8011bc07e280dcaa84a2c61244"
|
|
36
36
|
}
|