@enigma-lake/tower-play-controller-sdk 2.0.24 → 2.0.25
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/components/base/DifficultySelector/DifficultySelector.d.ts +4 -7
- package/dist/components/base/DifficultySelector/Selector.d.ts +1 -1
- package/dist/components/base/DifficultySelector/types.d.ts +3 -0
- package/dist/components/base/FreeRoundsIntroModal/FreeRoundsIntroModal.d.ts +14 -0
- package/dist/components/base/FreeRoundsIntroModal/freeRoundsCoin.d.ts +11 -0
- package/dist/components/base/FreeRoundsIntroModal/index.d.ts +3 -0
- package/dist/components/base/FreeRoundsSummaryModal/FreeRoundsSummaryModal.d.ts +9 -0
- package/dist/components/base/FreeRoundsSummaryModal/index.d.ts +3 -0
- package/dist/components/base/GroupRow/GroupRow.d.ts +2 -1
- package/dist/components/base/Input/Input.d.ts +3 -1
- package/dist/components/base/InputWithSwitch/InputWithSwitch.d.ts +2 -1
- package/dist/components/base/PlayController/PlayController.d.ts +2 -1
- package/dist/components/base/PlayValueInput/PlayValueInput.d.ts +2 -1
- package/dist/components/hooks/usePlayController.d.ts +8 -0
- package/dist/components/hooks/useSpacebarButtonTrigger.d.ts +2 -1
- package/dist/i18n/I18nContext.d.ts +13 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/locales/en.d.ts +2 -0
- package/dist/i18n/locales/zh-CN.d.ts +2 -0
- package/dist/i18n/translator.d.ts +5 -0
- package/dist/i18n/types.d.ts +41 -0
- package/dist/index.d.ts +132 -11
- package/dist/index.mjs +594 -115
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/index.d.ts +2 -2
- package/dist/types/playController.d.ts +52 -9
- package/package.json +9 -2
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React, { createContext, useContext,
|
|
3
|
-
import { Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent, setTogglePlayAmountView } from '@enigma-lake/zoot-platform-sdk';
|
|
2
|
+
import React, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, Fragment as Fragment$1 } from 'react';
|
|
4
3
|
import cx$1 from 'classnames';
|
|
5
|
-
import {
|
|
4
|
+
import { createTranslator as createTranslator$1, DEFAULT_LANGUAGE, resolveLanguage, Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent } from '@enigma-lake/zoot-platform-sdk';
|
|
5
|
+
export { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES, resolveLanguage } from '@enigma-lake/zoot-platform-sdk';
|
|
6
|
+
import { Menu, MenuButton, MenuItems, MenuItem, Switch as Switch$1, Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
|
|
6
7
|
|
|
7
8
|
const hexToRgb = (hex) => {
|
|
8
9
|
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
|
@@ -102,6 +103,23 @@ const createAutoplayButtonAction = ({ state, cashoutDisabled, onStopPlay, onPlay
|
|
|
102
103
|
};
|
|
103
104
|
};
|
|
104
105
|
|
|
106
|
+
var SELECTORS;
|
|
107
|
+
(function (SELECTORS) {
|
|
108
|
+
SELECTORS["RISK"] = "Risk";
|
|
109
|
+
})(SELECTORS || (SELECTORS = {}));
|
|
110
|
+
var RiskTypes;
|
|
111
|
+
(function (RiskTypes) {
|
|
112
|
+
RiskTypes["LOW"] = "LOW";
|
|
113
|
+
RiskTypes["MEDIUM"] = "MEDIUM";
|
|
114
|
+
RiskTypes["HIGH"] = "HIGH";
|
|
115
|
+
})(RiskTypes || (RiskTypes = {}));
|
|
116
|
+
// standard controller palette (matches multi/plinko play-controller SDKs)
|
|
117
|
+
const DEFAULT_RISK_COLORS = {
|
|
118
|
+
[RiskTypes.LOW]: "#1AE380",
|
|
119
|
+
[RiskTypes.MEDIUM]: "#FFC058",
|
|
120
|
+
[RiskTypes.HIGH]: "#FF5646",
|
|
121
|
+
};
|
|
122
|
+
|
|
105
123
|
const AutoManualPlayStateContext = createContext(undefined);
|
|
106
124
|
const useAutoManualPlayState = () => {
|
|
107
125
|
const context = useContext(AutoManualPlayStateContext);
|
|
@@ -111,6 +129,104 @@ const useAutoManualPlayState = () => {
|
|
|
111
129
|
return context;
|
|
112
130
|
};
|
|
113
131
|
|
|
132
|
+
const en = {
|
|
133
|
+
playNow: "PLAY NOW",
|
|
134
|
+
playFreeRound: "PLAY FREE ROUND",
|
|
135
|
+
selectPlayAmount: "SELECT PLAY AMOUNT",
|
|
136
|
+
cashout: "CASHOUT",
|
|
137
|
+
collect: "COLLECT",
|
|
138
|
+
doubleTitle: "DOUBLE",
|
|
139
|
+
doubleSubtitle: "OR NOTHING",
|
|
140
|
+
startAutoplay: "START AUTOPLAY",
|
|
141
|
+
stopAutoplay: "STOP AUTOPLAY",
|
|
142
|
+
numberOfPlays: "Number of Plays",
|
|
143
|
+
selectPlayAmountAria: "Select play amount",
|
|
144
|
+
playAmountLabel: "Play amount",
|
|
145
|
+
autoLabel: "Auto",
|
|
146
|
+
selectTileToast: "Please select at least one tile to start autoplay.",
|
|
147
|
+
riskLabel: "Risk",
|
|
148
|
+
riskLow: "Low",
|
|
149
|
+
riskMedium: "Medium",
|
|
150
|
+
riskHigh: "High",
|
|
151
|
+
freeRoundLeftOne: "FREE ROUND LEFT",
|
|
152
|
+
freeRoundLeftOther: "FREE ROUNDS LEFT",
|
|
153
|
+
freeRoundsModalTitle: "Free Game Rounds",
|
|
154
|
+
freeRoundsHave: "You have",
|
|
155
|
+
freeRoundsUnitOne: "FREE GAME ROUND",
|
|
156
|
+
freeRoundsUnitOther: "FREE GAME ROUNDS",
|
|
157
|
+
freeRoundsPerRound: "{amount} per round",
|
|
158
|
+
freeRoundsExpiresIn: "Expires in {days} {unit}",
|
|
159
|
+
dayUnitOne: "day",
|
|
160
|
+
dayUnitOther: "days",
|
|
161
|
+
freeRoundsModalPlayNow: "Play now",
|
|
162
|
+
freeRoundsModalPlayLater: "Maybe later",
|
|
163
|
+
freeRoundsSummaryWon: "You won",
|
|
164
|
+
freeRoundsSummaryIn: "in",
|
|
165
|
+
freeRoundsSummaryUnitOne: "free game round",
|
|
166
|
+
freeRoundsSummaryUnitOther: "free game rounds",
|
|
167
|
+
freeRoundsSummaryCredited: "Winnings have been credited to your bonus balance.",
|
|
168
|
+
freeRoundsSummaryClose: "Close",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const zhCN = {
|
|
172
|
+
playNow: "立即游戏",
|
|
173
|
+
playFreeRound: "免费游戏",
|
|
174
|
+
selectPlayAmount: "选择下注金额",
|
|
175
|
+
cashout: "提现",
|
|
176
|
+
collect: "收取",
|
|
177
|
+
doubleTitle: "双倍",
|
|
178
|
+
doubleSubtitle: "或全无",
|
|
179
|
+
startAutoplay: "开始自动游戏",
|
|
180
|
+
stopAutoplay: "停止自动游戏",
|
|
181
|
+
numberOfPlays: "游戏次数",
|
|
182
|
+
selectPlayAmountAria: "选择下注金额",
|
|
183
|
+
playAmountLabel: "下注金额",
|
|
184
|
+
autoLabel: "自动",
|
|
185
|
+
selectTileToast: "请至少选择一个方块以开始自动游戏。",
|
|
186
|
+
riskLabel: "风险",
|
|
187
|
+
riskLow: "低",
|
|
188
|
+
riskMedium: "中",
|
|
189
|
+
riskHigh: "高",
|
|
190
|
+
freeRoundLeftOne: "剩余免费回合",
|
|
191
|
+
freeRoundLeftOther: "剩余免费回合",
|
|
192
|
+
freeRoundsModalTitle: "免费游戏回合",
|
|
193
|
+
freeRoundsHave: "您有",
|
|
194
|
+
freeRoundsUnitOne: "个免费游戏回合",
|
|
195
|
+
freeRoundsUnitOther: "个免费游戏回合",
|
|
196
|
+
freeRoundsPerRound: "每回合 {amount}",
|
|
197
|
+
freeRoundsExpiresIn: "{days} {unit}后到期",
|
|
198
|
+
dayUnitOne: "天",
|
|
199
|
+
dayUnitOther: "天",
|
|
200
|
+
freeRoundsModalPlayNow: "立即游戏",
|
|
201
|
+
freeRoundsModalPlayLater: "稍后再玩",
|
|
202
|
+
freeRoundsSummaryWon: "您赢得了",
|
|
203
|
+
freeRoundsSummaryIn: ",共",
|
|
204
|
+
freeRoundsSummaryUnitOne: "个免费游戏回合",
|
|
205
|
+
freeRoundsSummaryUnitOther: "个免费游戏回合",
|
|
206
|
+
freeRoundsSummaryCredited: "奖金已存入您的奖金余额。",
|
|
207
|
+
freeRoundsSummaryClose: "关闭",
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const dictionaries = {
|
|
211
|
+
en,
|
|
212
|
+
"zh-CN": zhCN,
|
|
213
|
+
};
|
|
214
|
+
const createTranslator = (language) => createTranslator$1(dictionaries, language);
|
|
215
|
+
|
|
216
|
+
const I18nContext = createContext(undefined);
|
|
217
|
+
const I18nProvider = ({ language, children, }) => {
|
|
218
|
+
const value = useMemo(() => {
|
|
219
|
+
const resolved = resolveLanguage(language);
|
|
220
|
+
return { language: resolved, t: createTranslator(resolved) };
|
|
221
|
+
}, [language]);
|
|
222
|
+
return jsx(I18nContext.Provider, { value: value, children: children });
|
|
223
|
+
};
|
|
224
|
+
const DEFAULT_VALUE = {
|
|
225
|
+
language: DEFAULT_LANGUAGE,
|
|
226
|
+
t: createTranslator(DEFAULT_LANGUAGE),
|
|
227
|
+
};
|
|
228
|
+
const useTranslation = () => useContext(I18nContext) ?? DEFAULT_VALUE;
|
|
229
|
+
|
|
114
230
|
const getNextPlayAmount = ({ currentAmount, defaultBetOptions, direction, }) => {
|
|
115
231
|
if (!defaultBetOptions.length) {
|
|
116
232
|
return currentAmount;
|
|
@@ -141,7 +257,8 @@ function isFullMode(config) {
|
|
|
141
257
|
return config.withoutRiskAndAutoplay !== true;
|
|
142
258
|
}
|
|
143
259
|
const usePlayController = () => {
|
|
144
|
-
const {
|
|
260
|
+
const { t } = useTranslation();
|
|
261
|
+
const { config, mode, autoPlay: { setNumberOfPlays, numberOfPlays, setPlayedRounds, playedRounds, selection, setState, state, }, playValues: { displayPlayAmountView, setDisplayPlayAmountView, togglePlayAmountView, }, } = useAutoManualPlayState();
|
|
145
262
|
const { current } = config.currencyOptions;
|
|
146
263
|
const { isPlaying, canCashout, disabledCashout = false, disabledController, playHook, totalBalance, } = config.playOptions;
|
|
147
264
|
// --- Autoplay-related values only valid in full mode ---
|
|
@@ -152,11 +269,65 @@ const usePlayController = () => {
|
|
|
152
269
|
const autoPlayDelay = autoplayEnabled
|
|
153
270
|
? (config.playOptions.autoPlayDelay ?? 1500)
|
|
154
271
|
: 1500;
|
|
155
|
-
const { playAmount = 0, playLimits, setPlayAmount = () => { }, } = playHook?.() ?? {};
|
|
272
|
+
const { playAmount: rawPlayAmount = 0, playLimits, setPlayAmount: rawSetPlayAmount = () => { }, } = playHook?.() ?? {};
|
|
156
273
|
const minPlayAmount = playLimits?.[current.code]?.limits.min ?? 0;
|
|
157
274
|
const maxPlayAmount = playLimits?.[current.code]?.limits.max ?? 0;
|
|
275
|
+
// Free rounds (opt-in): only when the player has ENGAGED free-rounds mode
|
|
276
|
+
// (via the intro modal's PLAY NOW) are the per-round value/coin locked to
|
|
277
|
+
// the grant. When not engaged, the controller renders exactly as if the
|
|
278
|
+
// user had no free rounds. Engagement coexists with the cashout flow.
|
|
279
|
+
//
|
|
280
|
+
// By default engagement is MANUAL-mode only (free rounds are never engaged
|
|
281
|
+
// in auto-play mode). Games whose autoplay submission path funds rounds
|
|
282
|
+
// from engaged grants opt into engaged autoplay via
|
|
283
|
+
// `supportsGrantFundedAutoplay`, which drops the manual-mode requirement.
|
|
284
|
+
const isManualMode = mode === GAME_MODE.MANUAL;
|
|
285
|
+
const freeRoundsOptions = config.freeRoundsOptions;
|
|
286
|
+
const supportsGrantFundedAutoplay = Boolean(freeRoundsOptions?.supportsGrantFundedAutoplay);
|
|
287
|
+
const isFreeRoundsEngaged = Boolean((isManualMode || supportsGrantFundedAutoplay) &&
|
|
288
|
+
freeRoundsOptions?.engaged &&
|
|
289
|
+
freeRoundsOptions.roundsRemaining > 0);
|
|
290
|
+
// playAmount is in display units everywhere in this SDK (e.g. 1.00), while the
|
|
291
|
+
// grant stake is in cents, so convert. When engaged, setPlayAmount is a no-op
|
|
292
|
+
// (the stake is locked to the grant).
|
|
293
|
+
const playAmount = isFreeRoundsEngaged
|
|
294
|
+
? freeRoundsOptions.stakeCents /
|
|
295
|
+
100
|
|
296
|
+
: rawPlayAmount;
|
|
297
|
+
const setPlayAmount = isFreeRoundsEngaged
|
|
298
|
+
? () => {
|
|
299
|
+
// locked to the free-round grant stake (no-op setter)
|
|
300
|
+
}
|
|
301
|
+
: rawSetPlayAmount;
|
|
158
302
|
const playIntervalRef = useRef(null);
|
|
159
303
|
const isAutoplayActiveRef = useRef(false);
|
|
304
|
+
// Each setTimeout tick invokes the `loopRounds` closure from the render that
|
|
305
|
+
// scheduled it, so reading `config.freeRoundsOptions` there can see a stale,
|
|
306
|
+
// pre-engagement value. Keep the latest free-rounds state in a ref
|
|
307
|
+
// (refreshed every render) so the loop's hard-stop checks always see live
|
|
308
|
+
// values — a grant engaged mid-loop still hard-stops paid autoplay, and an
|
|
309
|
+
// engaged grant-funded loop sees the live remaining count.
|
|
310
|
+
const freeRoundsLiveRef = useRef({
|
|
311
|
+
engaged: false,
|
|
312
|
+
remaining: 0,
|
|
313
|
+
supportsGrantFundedAutoplay: false,
|
|
314
|
+
});
|
|
315
|
+
freeRoundsLiveRef.current = {
|
|
316
|
+
engaged: Boolean(config.freeRoundsOptions?.engaged),
|
|
317
|
+
remaining: config.freeRoundsOptions?.roundsRemaining ?? 0,
|
|
318
|
+
supportsGrantFundedAutoplay,
|
|
319
|
+
};
|
|
320
|
+
// Per-session accounting for engaged (grant-funded) autoplay.
|
|
321
|
+
// roundsRemaining is game-driven and only decrements via an async refetch
|
|
322
|
+
// AFTER each settlement, so the live ref can still read stale-high when the
|
|
323
|
+
// loop schedules the next round. We therefore also count submissions
|
|
324
|
+
// locally: a session that started engaged may never submit more rounds than
|
|
325
|
+
// the grants available when it started, regardless of refetch timing.
|
|
326
|
+
const autoplaySessionRef = useRef({
|
|
327
|
+
startedEngaged: false,
|
|
328
|
+
grantsAtStart: 0,
|
|
329
|
+
roundsSubmitted: 0,
|
|
330
|
+
});
|
|
160
331
|
const stopAutoplay = () => {
|
|
161
332
|
isAutoplayActiveRef.current = false;
|
|
162
333
|
if (playIntervalRef.current) {
|
|
@@ -176,6 +347,35 @@ const usePlayController = () => {
|
|
|
176
347
|
if (!isAutoplayActiveRef.current || !autoplayEnabled) {
|
|
177
348
|
return;
|
|
178
349
|
}
|
|
350
|
+
// Free-rounds gates — all live values come from refs (closures here are
|
|
351
|
+
// stale across rounds).
|
|
352
|
+
const live = freeRoundsLiveRef.current;
|
|
353
|
+
const session = autoplaySessionRef.current;
|
|
354
|
+
if (!live.supportsGrantFundedAutoplay) {
|
|
355
|
+
// Legacy (flag absent/false): tower free rounds are MANUAL-only — there
|
|
356
|
+
// is no free autoplay. If a grant is engaged (or becomes engaged) while
|
|
357
|
+
// an autoplay loop is live, hard-stop instead of letting PAID autoplay
|
|
358
|
+
// run alongside the grant.
|
|
359
|
+
if (live.engaged) {
|
|
360
|
+
stopAutoplay();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (session.startedEngaged) {
|
|
365
|
+
// Grant-funded autoplay: an autoplay session that started on free
|
|
366
|
+
// rounds ends the moment the grants are exhausted or engagement ends
|
|
367
|
+
// mid-session — it must never submit a paid round. Checked BEFORE each
|
|
368
|
+
// submission so the round that consumes the last grant completes
|
|
369
|
+
// normally, then the loop halts via the normal stop path. Uses the
|
|
370
|
+
// local per-session submission count as well because the game's
|
|
371
|
+
// roundsRemaining refetch can lag behind the next scheduled round.
|
|
372
|
+
const grantsExhausted = session.roundsSubmitted >= session.grantsAtStart || live.remaining <= 0;
|
|
373
|
+
if (grantsExhausted || !live.engaged) {
|
|
374
|
+
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
375
|
+
stopAutoplay();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
179
379
|
if (remainingPlays < 1 || numberOfPlays === 0) {
|
|
180
380
|
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
181
381
|
stopAutoplay();
|
|
@@ -183,6 +383,7 @@ const usePlayController = () => {
|
|
|
183
383
|
}
|
|
184
384
|
setPlayedRounds(currentPlayedRounds + 1);
|
|
185
385
|
setNumberOfPlays((prev) => Math.max(prev - 1, 0));
|
|
386
|
+
session.roundsSubmitted += 1;
|
|
186
387
|
// onAutoPlay exists only in full mode (guarded by autoplayEnabled)
|
|
187
388
|
config.onAutoPlay(selection, () => {
|
|
188
389
|
if (!isAutoplayActiveRef.current) {
|
|
@@ -200,13 +401,32 @@ const usePlayController = () => {
|
|
|
200
401
|
if (selection.length === 0) {
|
|
201
402
|
showAutoPlayToast?.({
|
|
202
403
|
type: "info",
|
|
203
|
-
message: "
|
|
404
|
+
message: t("selectTileToast"),
|
|
204
405
|
});
|
|
205
406
|
return;
|
|
206
407
|
}
|
|
408
|
+
// Grant-funded autoplay: the effective play count is clamped to the
|
|
409
|
+
// remaining grants so autoplay can never spill into paid rounds (and an
|
|
410
|
+
// infinite/over-cap count stops at roundsRemaining). The game client
|
|
411
|
+
// enforces actual per-round grant consumption; this is the UI cap.
|
|
412
|
+
const effectiveNumberOfPlays = supportsGrantFundedAutoplay && isFreeRoundsEngaged
|
|
413
|
+
? Math.min(numberOfPlays, freeRoundsOptions?.roundsRemaining ?? 0)
|
|
414
|
+
: numberOfPlays;
|
|
415
|
+
if (effectiveNumberOfPlays !== numberOfPlays) {
|
|
416
|
+
setNumberOfPlays(effectiveNumberOfPlays);
|
|
417
|
+
}
|
|
418
|
+
// Snapshot the free-rounds state for this session. Uses the RAW engaged
|
|
419
|
+
// flag (not isFreeRoundsEngaged, which also requires remaining > 0) so a
|
|
420
|
+
// session started while engaged with 0 grants fails closed on the first
|
|
421
|
+
// loop iteration instead of silently becoming a paid session.
|
|
422
|
+
autoplaySessionRef.current = {
|
|
423
|
+
startedEngaged: Boolean(freeRoundsOptions?.engaged),
|
|
424
|
+
grantsAtStart: freeRoundsOptions?.roundsRemaining ?? 0,
|
|
425
|
+
roundsSubmitted: 0,
|
|
426
|
+
};
|
|
207
427
|
isAutoplayActiveRef.current = true;
|
|
208
428
|
setState(AUTO_PLAY_STATE.PLAYING);
|
|
209
|
-
loopRounds(playedRounds,
|
|
429
|
+
loopRounds(playedRounds, effectiveNumberOfPlays);
|
|
210
430
|
};
|
|
211
431
|
const adjustPlayAmount = ({ direction }) => {
|
|
212
432
|
if (isDisabled()) {
|
|
@@ -229,7 +449,8 @@ const usePlayController = () => {
|
|
|
229
449
|
}
|
|
230
450
|
setPlayAmount(playValue);
|
|
231
451
|
};
|
|
232
|
-
const isValidPlayAmount =
|
|
452
|
+
const isValidPlayAmount = isFreeRoundsEngaged ||
|
|
453
|
+
(playAmount >= minPlayAmount && playAmount <= maxPlayAmount);
|
|
233
454
|
return {
|
|
234
455
|
// amounts
|
|
235
456
|
playAmount,
|
|
@@ -263,6 +484,16 @@ const usePlayController = () => {
|
|
|
263
484
|
setDisplayPlayAmountView,
|
|
264
485
|
togglePlayAmountView,
|
|
265
486
|
},
|
|
487
|
+
// free rounds (binary engagement via the intro modal; manual-mode only
|
|
488
|
+
// unless the game opts into grant-funded autoplay)
|
|
489
|
+
freeRounds: {
|
|
490
|
+
isEngaged: isFreeRoundsEngaged,
|
|
491
|
+
roundsRemaining: freeRoundsOptions?.roundsRemaining ?? 0,
|
|
492
|
+
totalRounds: freeRoundsOptions?.totalRounds ?? 0,
|
|
493
|
+
stakeCents: freeRoundsOptions?.stakeCents ?? 0,
|
|
494
|
+
coinType: freeRoundsOptions?.coinType,
|
|
495
|
+
currency: freeRoundsOptions?.currency,
|
|
496
|
+
},
|
|
266
497
|
};
|
|
267
498
|
};
|
|
268
499
|
|
|
@@ -280,10 +511,12 @@ const Button = ({ disabled, roleType, className = "", theme = "primary", ...prop
|
|
|
280
511
|
};
|
|
281
512
|
Button.themes = themes;
|
|
282
513
|
|
|
283
|
-
var styles_group = {"base":"GroupRow-module__base___b241-","label":"GroupRow-module__label___Du57P","group":"GroupRow-module__group___V7xa6","groupItem":"GroupRow-module__groupItem___yNSX8","refreshButton":"GroupRow-module__refreshButton___m61F8","x2":"GroupRow-module__x2___9bLae","last":"GroupRow-module__last___ArsSn"};
|
|
514
|
+
var styles_group = {"base":"GroupRow-module__base___b241-","label":"GroupRow-module__label___Du57P","group":"GroupRow-module__group___V7xa6","groupItem":"GroupRow-module__groupItem___yNSX8","refreshButton":"GroupRow-module__refreshButton___m61F8","x2":"GroupRow-module__x2___9bLae","last":"GroupRow-module__last___ArsSn","isClassicGame":"GroupRow-module__isClassicGame___m9r03"};
|
|
284
515
|
|
|
285
|
-
const GroupRow = ({ children, label, className, ...restProps }) => {
|
|
286
|
-
return (jsxs("div", { ...restProps, className: cx$1(styles_group.base, className
|
|
516
|
+
const GroupRow = ({ children, label, className, isClassicGame = false, ...restProps }) => {
|
|
517
|
+
return (jsxs("div", { ...restProps, className: cx$1(styles_group.base, className, {
|
|
518
|
+
[styles_group.isClassicGame]: isClassicGame,
|
|
519
|
+
}), children: [label && jsx("div", { className: styles_group.label, children: label }), jsx("div", { className: styles_group.group, children: children })] }));
|
|
287
520
|
};
|
|
288
521
|
|
|
289
522
|
var style_select$1 = {"button":"SelectMenu-module__button___N-HeB","menu":"SelectMenu-module__menu___c5jvZ","menuItems":"SelectMenu-module__menuItems___BX1YQ","menuItem":"SelectMenu-module__menuItem___eXbi3","selected":"SelectMenu-module__selected___XyADw","disabledSVG":"SelectMenu-module__disabledSVG___OdVFW","disabled":"SelectMenu-module__disabled___jiTPl"};
|
|
@@ -315,6 +548,15 @@ const getIcon = (option) => {
|
|
|
315
548
|
const SelectMenu = ({ disabled = false }) => {
|
|
316
549
|
const { config } = useAutoManualPlayState();
|
|
317
550
|
const { current, available } = config.currencyOptions;
|
|
551
|
+
useEffect(() => {
|
|
552
|
+
const hex = config?.classicGame?.dropdown?.bgColorHex ??
|
|
553
|
+
config?.dropdown?.bgColorHex ??
|
|
554
|
+
"#01243A";
|
|
555
|
+
const v = hexToRgb(hex);
|
|
556
|
+
if (v) {
|
|
557
|
+
document.documentElement.style.setProperty("--play-dropdown-bg", v);
|
|
558
|
+
}
|
|
559
|
+
}, [config?.classicGame?.dropdown?.bgColorHex, config?.dropdown?.bgColorHex]);
|
|
318
560
|
const handleClick = (e) => {
|
|
319
561
|
if (disabled) {
|
|
320
562
|
return;
|
|
@@ -344,39 +586,117 @@ const SelectMenu = ({ disabled = false }) => {
|
|
|
344
586
|
}) })] })) }));
|
|
345
587
|
};
|
|
346
588
|
|
|
347
|
-
var styles$
|
|
589
|
+
var styles$6 = {"base":"PlayValueInput-module__base___sTNW2","icon":"PlayValueInput-module__icon___jZj7O","playValueInput":"PlayValueInput-module__playValueInput___7pJtn","playValueInputWithLabel":"PlayValueInput-module__playValueInputWithLabel___S55Cr","label":"PlayValueInput-module__label___1ZSAV","inputContainer":"PlayValueInput-module__inputContainer___RKr1l","playValueWithLabel":"PlayValueInput-module__playValueWithLabel___K4-pr","isClassicGame":"PlayValueInput-module__isClassicGame___lQzVE"};
|
|
348
590
|
|
|
349
|
-
const PlayValueInput = ({ disabled, children, className, value, onClick, }) => {
|
|
591
|
+
const PlayValueInput = ({ disabled, children, className, value, onClick, isClassicGame = false, }) => {
|
|
592
|
+
const { t } = useTranslation();
|
|
350
593
|
const handleOnClick = () => {
|
|
351
594
|
if (disabled) {
|
|
352
595
|
return;
|
|
353
596
|
}
|
|
354
597
|
onClick();
|
|
355
598
|
};
|
|
356
|
-
return (jsxs("div", { className: cx$1(styles$
|
|
357
|
-
[styles$
|
|
358
|
-
|
|
599
|
+
return (jsxs("div", { className: cx$1(styles$6.base, className, {
|
|
600
|
+
[styles$6.disabled]: disabled,
|
|
601
|
+
[styles$6.isClassicGame]: isClassicGame,
|
|
602
|
+
}), children: [children && jsx("div", { className: cx$1(styles$6.icon), children: children }), jsxs("div", { className: cx$1(styles$6.inputContainer), onClick: (e) => {
|
|
359
603
|
e.preventDefault();
|
|
360
604
|
handleOnClick();
|
|
361
|
-
}, children: [jsx("span", { className: styles$
|
|
362
|
-
[styles$
|
|
605
|
+
}, children: [jsx("span", { className: styles$6.label, children: t("playAmountLabel") }), jsx("div", { className: cx$1(styles$6.playValueInput, {
|
|
606
|
+
[styles$6.playValueInputWithLabel]: true,
|
|
363
607
|
}), children: value })] })] }));
|
|
364
608
|
};
|
|
365
609
|
|
|
366
|
-
const
|
|
610
|
+
const COIN_TYPE_SWEEPS = 0;
|
|
611
|
+
const COIN_TYPE_GOLD = 1;
|
|
612
|
+
// B2C coin decimals (platform convention): Sweeps shows 2 dp, Gold 0 dp.
|
|
613
|
+
const COIN_DECIMALS = {
|
|
614
|
+
[COIN_TYPE_SWEEPS]: 2,
|
|
615
|
+
[COIN_TYPE_GOLD]: 0,
|
|
616
|
+
};
|
|
617
|
+
const resolveFreeRoundsCoinLabel = ({ coinType, currency, }) => {
|
|
618
|
+
if (currency) {
|
|
619
|
+
return currency;
|
|
620
|
+
}
|
|
621
|
+
if (coinType === COIN_TYPE_SWEEPS) {
|
|
622
|
+
return "SC";
|
|
623
|
+
}
|
|
624
|
+
if (coinType === COIN_TYPE_GOLD) {
|
|
625
|
+
return "GC";
|
|
626
|
+
}
|
|
627
|
+
return "";
|
|
628
|
+
};
|
|
629
|
+
// The per-round value belongs to the GRANT's coin, which can differ from the
|
|
630
|
+
// player's active currency (e.g. a Sweeps grant surfaced while the player is
|
|
631
|
+
// on Gold). Use the grant coin's decimals so a 0.20 SC value doesn't render as
|
|
632
|
+
// "0" under Gold's 0-dp formatting. B2B grants (operator `currency`) fall back
|
|
633
|
+
// to the operator currency decimals passed in via `fallback`.
|
|
634
|
+
const resolveFreeRoundsDecimals = ({ coinType, currency, fallback, }) => {
|
|
635
|
+
if (!currency && coinType !== undefined && coinType in COIN_DECIMALS) {
|
|
636
|
+
return COIN_DECIMALS[coinType];
|
|
637
|
+
}
|
|
638
|
+
return fallback;
|
|
639
|
+
};
|
|
640
|
+
// Resolves display decimals from a coin/currency LABEL (cross-SDK standard):
|
|
641
|
+
// SC/SWEEPS = 2, GC/GOLD = 0, B2B operator currency strings = 2.
|
|
642
|
+
const resolveCoinLabelDecimals = (coin) => {
|
|
643
|
+
const normalized = coin.trim().toUpperCase();
|
|
644
|
+
if (normalized === "GC" || normalized === "GOLD") {
|
|
645
|
+
return 0;
|
|
646
|
+
}
|
|
647
|
+
return 2;
|
|
648
|
+
};
|
|
649
|
+
const MS_PER_DAY = 86_400_000;
|
|
650
|
+
// Computes the whole-day countdown for "*Expires in {N} days". Rounds UP so a
|
|
651
|
+
// grant expiring in 36 hours reads "2 days" (it is still claimable on day 2).
|
|
652
|
+
// Returns null when the input is missing/invalid or already expired.
|
|
653
|
+
const computeExpiresInDays = (expiresAt, now = Date.now()) => {
|
|
654
|
+
if (expiresAt === null || expiresAt === undefined) {
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
const expiryMs = new Date(expiresAt).getTime();
|
|
658
|
+
if (Number.isNaN(expiryMs)) {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
const remainingMs = expiryMs - now;
|
|
662
|
+
if (remainingMs <= 0) {
|
|
663
|
+
return null;
|
|
664
|
+
}
|
|
665
|
+
return Math.ceil(remainingMs / MS_PER_DAY);
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
const PlayAmountControl = ({ isDisabled, forceRefresh, lockCurrency = false, }) => {
|
|
367
669
|
const { config } = useAutoManualPlayState();
|
|
368
|
-
const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, } = usePlayController();
|
|
670
|
+
const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, freeRounds, } = usePlayController();
|
|
671
|
+
const isClassicGame = Boolean(config.isClassicGame || config.classicGame?.forceLeftStyle);
|
|
672
|
+
// While a free-rounds grant is engaged, the input is locked to the GRANT's
|
|
673
|
+
// per-round value and coin (which can differ from the active currency), so
|
|
674
|
+
// the locked amount is always truthful.
|
|
675
|
+
const { current } = config.currencyOptions;
|
|
676
|
+
const grantCoinLabel = resolveFreeRoundsCoinLabel({
|
|
677
|
+
coinType: freeRounds.coinType,
|
|
678
|
+
currency: freeRounds.currency,
|
|
679
|
+
});
|
|
680
|
+
const valueDecimals = freeRounds.isEngaged
|
|
681
|
+
? resolveFreeRoundsDecimals({
|
|
682
|
+
coinType: freeRounds.coinType,
|
|
683
|
+
currency: freeRounds.currency,
|
|
684
|
+
fallback: current.decimals,
|
|
685
|
+
})
|
|
686
|
+
: current.decimals;
|
|
687
|
+
const coinLabel = freeRounds.isEngaged && grantCoinLabel ? grantCoinLabel : current.abbr;
|
|
369
688
|
const handleTogglePlayAmount = () => {
|
|
370
689
|
setToggleGameWidgetsVisibility();
|
|
371
690
|
togglePlayAmountView();
|
|
372
691
|
setToggleGameWidgetsExpansion();
|
|
373
692
|
};
|
|
374
|
-
return (jsxs(GroupRow, { children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount,
|
|
693
|
+
return (jsxs(GroupRow, { isClassicGame: isClassicGame, children: [jsx(PlayValueInput, { className: styles_group.groupItem, value: `${format(playAmount, valueDecimals)} ${coinLabel}`, disabled: isDisabled() ||
|
|
375
694
|
config.playOptions.disableInput ||
|
|
376
695
|
config.playOptions.disabledController ||
|
|
377
|
-
state === AUTO_PLAY_STATE.PLAYING
|
|
696
|
+
state === AUTO_PLAY_STATE.PLAYING ||
|
|
697
|
+
lockCurrency, onClick: handleTogglePlayAmount, isClassicGame: isClassicGame, children: jsx(SelectMenu, { disabled: isDisabled() || lockCurrency }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: -1 }), theme: "ghost", disabled: isDisabled() || lockCurrency, children: jsx("span", { className: styles_group.x2, children: "-" }) }), jsx(Button, { className: styles_group.refreshButton, onClick: () => {
|
|
378
698
|
forceRefresh();
|
|
379
|
-
}, "data-button-refresh": true, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.first) }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: 1 }), theme: "ghost", disabled: isDisabled(), children: jsx("span", { className: cx$1(styles_group.x2, styles_group.last), children: "+" }) })] }));
|
|
699
|
+
}, "data-button-refresh": true, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.first) }) }), jsx(Button, { className: styles_group.groupItem, onClick: () => adjustPlayAmount({ direction: 1 }), theme: "ghost", disabled: isDisabled() || lockCurrency, children: jsx("span", { className: cx$1(styles_group.x2, styles_group.last), children: "+" }) })] }));
|
|
380
700
|
};
|
|
381
701
|
|
|
382
702
|
const getDefaultButtonClassName = (currencyCode, styles) => {
|
|
@@ -398,38 +718,56 @@ const getActiveButtonClassName = (currencyCode, styles) => {
|
|
|
398
718
|
return styles.buttonDefault__active;
|
|
399
719
|
};
|
|
400
720
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
const
|
|
721
|
+
// Space activates buttons/links natively. If the user has keyboard focus on a
|
|
722
|
+
// different interactive control (the intro modal's PLAY NOW / PLAY LATER
|
|
723
|
+
// buttons, etc.), let Space activate THAT control instead of hijacking it to
|
|
724
|
+
// click the play button — otherwise a paid play can fire while the user is
|
|
725
|
+
// trying to engage free rounds.
|
|
726
|
+
const isOtherInteractiveControlFocused = (button) => {
|
|
727
|
+
const active = document.activeElement;
|
|
728
|
+
return Boolean(active &&
|
|
729
|
+
active !== button &&
|
|
730
|
+
typeof active.matches === "function" &&
|
|
731
|
+
active.matches("button, a, input, select, textarea"));
|
|
732
|
+
};
|
|
733
|
+
const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, suppressed = false, }) => {
|
|
408
734
|
const handleKeyPress = useCallback((event) => {
|
|
409
735
|
if (event.code !== "Space") {
|
|
410
736
|
return;
|
|
411
737
|
}
|
|
738
|
+
if (suppressed) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
412
741
|
const button = selectButton(roleButton);
|
|
413
742
|
if (!button || !canTrigger()) {
|
|
414
743
|
return;
|
|
415
744
|
}
|
|
745
|
+
if (isOtherInteractiveControlFocused(button)) {
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
416
748
|
event.preventDefault();
|
|
417
749
|
event.stopPropagation();
|
|
418
750
|
addPressedClass(roleButton, activeClassName);
|
|
419
751
|
button.click();
|
|
420
|
-
}, [activeClassName, canTrigger, roleButton]);
|
|
752
|
+
}, [activeClassName, canTrigger, roleButton, suppressed]);
|
|
421
753
|
const handleKeyUp = useCallback((event) => {
|
|
422
754
|
if (event.code !== "Space") {
|
|
423
755
|
return;
|
|
424
756
|
}
|
|
757
|
+
if (suppressed) {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
425
760
|
const button = selectButton(roleButton);
|
|
426
761
|
if (!button) {
|
|
427
762
|
return;
|
|
428
763
|
}
|
|
764
|
+
if (isOtherInteractiveControlFocused(button)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
429
767
|
event.preventDefault();
|
|
430
768
|
event.stopPropagation();
|
|
431
769
|
removePressedClass(roleButton, activeClassName);
|
|
432
|
-
}, [activeClassName, roleButton]);
|
|
770
|
+
}, [activeClassName, roleButton, suppressed]);
|
|
433
771
|
useEffect(() => {
|
|
434
772
|
window.addEventListener("keydown", handleKeyPress, true);
|
|
435
773
|
window.addEventListener("keyup", handleKeyUp, true);
|
|
@@ -442,20 +780,29 @@ const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, })
|
|
|
442
780
|
|
|
443
781
|
const AutoPlayController = () => {
|
|
444
782
|
const { config } = useAutoManualPlayState();
|
|
783
|
+
const { t } = useTranslation();
|
|
445
784
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
446
|
-
const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
|
|
785
|
+
const { isValidPlayAmount, freeRounds, playValues: { displayPlayAmountView, togglePlayAmountView }, autoPlay: { isDisabled, state, onPlay, onStopPlay, cashoutDisabled }, } = usePlayController();
|
|
447
786
|
const { current } = config.currencyOptions;
|
|
448
787
|
const roleButton = GAME_MODE.AUTOPLAY;
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
788
|
+
const isStopState = state === AUTO_PLAY_STATE.PLAYING;
|
|
789
|
+
// Free-rounds engaged (grant-funded autoplay): the STOP control shows the
|
|
790
|
+
// live remaining grant count so the player sees the rounds tick down.
|
|
791
|
+
let buttonLabel = isStopState
|
|
792
|
+
? t("stopAutoplay")
|
|
793
|
+
: t("startAutoplay");
|
|
794
|
+
if (isStopState && freeRounds.isEngaged) {
|
|
795
|
+
buttonLabel = `${t("stopAutoplay")} (${freeRounds.roundsRemaining})`;
|
|
796
|
+
}
|
|
452
797
|
const activeClassName = getActiveButtonClassName(current.code, styles_button);
|
|
798
|
+
// Cashout styling keys off the PLAYING state (not the label text) so the
|
|
799
|
+
// engaged "(N)" label suffix can never break the style comparison.
|
|
453
800
|
const defaultClassName = useMemo(() => {
|
|
454
|
-
if (
|
|
801
|
+
if (isStopState) {
|
|
455
802
|
return styles_button.buttonCashout;
|
|
456
803
|
}
|
|
457
804
|
return getDefaultButtonClassName(current.code, styles_button);
|
|
458
|
-
}, [
|
|
805
|
+
}, [isStopState, current.code]);
|
|
459
806
|
useSpacebarButtonTrigger({
|
|
460
807
|
roleButton,
|
|
461
808
|
activeClassName,
|
|
@@ -464,6 +811,10 @@ const AutoPlayController = () => {
|
|
|
464
811
|
cashoutDisabled,
|
|
465
812
|
isDisabled: isDisabled(),
|
|
466
813
|
}),
|
|
814
|
+
// Suppress the global Space shortcut while the free-rounds intro modal is
|
|
815
|
+
// open so Space belongs to the modal's PLAY NOW / PLAY LATER buttons
|
|
816
|
+
// instead of starting/stopping the autoplay mounted behind it.
|
|
817
|
+
suppressed: Boolean(config.freeRoundsOptions?.showIntroModal),
|
|
467
818
|
});
|
|
468
819
|
const isButtonDisabled = isAutoplayButtonDisabled({
|
|
469
820
|
state,
|
|
@@ -488,7 +839,7 @@ const AutoPlayController = () => {
|
|
|
488
839
|
}, [togglePlayAmountView]);
|
|
489
840
|
const renderButton = useMemo(() => {
|
|
490
841
|
if (displayPlayAmountView) {
|
|
491
|
-
return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "
|
|
842
|
+
return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
|
|
492
843
|
}
|
|
493
844
|
return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: buttonAction, roleType: roleButton, children: buttonLabel }));
|
|
494
845
|
}, [
|
|
@@ -499,6 +850,7 @@ const AutoPlayController = () => {
|
|
|
499
850
|
handleTogglePlayAmount,
|
|
500
851
|
isButtonDisabled,
|
|
501
852
|
roleButton,
|
|
853
|
+
t,
|
|
502
854
|
]);
|
|
503
855
|
return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
|
|
504
856
|
};
|
|
@@ -527,9 +879,10 @@ const useIsRunningExternal = () => {
|
|
|
527
879
|
|
|
528
880
|
const ManualPlayController = () => {
|
|
529
881
|
const { config, doubleOrNothing } = useAutoManualPlayState();
|
|
882
|
+
const { t } = useTranslation();
|
|
530
883
|
const { isRunningExternal } = useIsRunningExternal();
|
|
531
884
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
532
|
-
const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, } = usePlayController();
|
|
885
|
+
const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, freeRounds, } = usePlayController();
|
|
533
886
|
const { current } = config.currencyOptions;
|
|
534
887
|
const roleButton = GAME_MODE.MANUAL;
|
|
535
888
|
const activeClassName = getActiveButtonClassName(current.code, styles_button);
|
|
@@ -538,6 +891,10 @@ const ManualPlayController = () => {
|
|
|
538
891
|
roleButton,
|
|
539
892
|
activeClassName,
|
|
540
893
|
canTrigger: () => !isDisabled(),
|
|
894
|
+
// Suppress the global Space-to-play shortcut while the free-rounds intro
|
|
895
|
+
// modal is open so Space belongs to the modal's PLAY NOW / PLAY LATER
|
|
896
|
+
// buttons instead of firing a paid play behind it.
|
|
897
|
+
suppressed: Boolean(config.freeRoundsOptions?.showIntroModal),
|
|
541
898
|
});
|
|
542
899
|
const isButtonDisabled = isDisabled() || !isValidPlayAmount;
|
|
543
900
|
const handleTogglePlayAmount = useCallback(() => {
|
|
@@ -572,7 +929,7 @@ const ManualPlayController = () => {
|
|
|
572
929
|
}), [cashoutDisabled, config.onCashout]);
|
|
573
930
|
const renderButton = useMemo(() => {
|
|
574
931
|
if (displayPlayAmountView) {
|
|
575
|
-
return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: "
|
|
932
|
+
return (jsx(Button, { className: styles_button.buttonPlayAmount, onClick: handleTogglePlayAmount, roleType: roleButton, children: t("selectPlayAmount") }));
|
|
576
933
|
}
|
|
577
934
|
if (canCashout) {
|
|
578
935
|
const isCashoutDisabled = isManualCashoutDisabled({
|
|
@@ -586,16 +943,29 @@ const ManualPlayController = () => {
|
|
|
586
943
|
doubleOrNothing.state === DOUBLE_OR_NOTHING_STATE.PROGRESS;
|
|
587
944
|
return (jsxs("div", { className: styles_button.buttonRow, children: [jsxs(Button, { disabled: isCashoutDisabled, className: cx$1(styles_button.buttonCashout, {
|
|
588
945
|
[styles_button.buttonRow__cashout]: showDoubleOrNothing,
|
|
589
|
-
}), onClick: handleCashoutClick, roleType: roleButton, children: [isRunningExternal ? "
|
|
946
|
+
}), onClick: handleCashoutClick, roleType: roleButton, children: [isRunningExternal ? t("collect") : t("cashout"), " ", format(current.possibleWin ?? 0, current.decimals), " ", current.abbr] }), showDoubleOrNothing ? (jsx(Button, { disabled: isDoubleOrNothingDisabled, className: cx$1(styles_button.buttonDON, styles_button.buttonRow__don, {
|
|
590
947
|
[styles_button.buttonDON__disabled]: isDoubleOrNothingDisabled,
|
|
591
|
-
}), onClick: handleDoubleOrNothingClick, roleType: roleButton, children: jsxs("div", { className: styles_button.buttonDON__content, children: [jsx("p", { className: styles_button.buttonDON__title, children: "
|
|
948
|
+
}), onClick: handleDoubleOrNothingClick, roleType: roleButton, children: jsxs("div", { className: styles_button.buttonDON__content, children: [jsx("p", { className: styles_button.buttonDON__title, children: t("doubleTitle") }), jsx("p", { className: styles_button.buttonDON__subtitle, children: t("doubleSubtitle") })] }) })) : null] }));
|
|
949
|
+
}
|
|
950
|
+
// While a game-driven autoplay loop is running, the game's stop-autoplay
|
|
951
|
+
// control is the only action — don't stack the manual play CTA behind it.
|
|
952
|
+
// The play amount, widgets and free-rounds footer stay as-is.
|
|
953
|
+
if (config.freeRoundsOptions?.autoplayActive) {
|
|
954
|
+
return null;
|
|
592
955
|
}
|
|
593
|
-
|
|
956
|
+
// Free rounds reuse the play button label while ENGAGED — it becomes
|
|
957
|
+
// "PLAY FREE ROUND" (no count; the remaining count lives ONLY in the
|
|
958
|
+
// "{N} FREE ROUNDS LEFT" footer).
|
|
959
|
+
const playNowText = freeRounds.isEngaged
|
|
960
|
+
? t("playFreeRound")
|
|
961
|
+
: t("playNow");
|
|
962
|
+
return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: onPlay, roleType: roleButton, children: playNowText }));
|
|
594
963
|
}, [
|
|
595
964
|
canCashout,
|
|
596
965
|
cashoutDisabled,
|
|
597
966
|
config.doubleOrNothing?.disabled,
|
|
598
967
|
config.doubleOrNothing?.display,
|
|
968
|
+
config.freeRoundsOptions?.autoplayActive,
|
|
599
969
|
config.playOptions.disabledController,
|
|
600
970
|
config.playOptions.isPlaying,
|
|
601
971
|
current.abbr,
|
|
@@ -604,6 +974,7 @@ const ManualPlayController = () => {
|
|
|
604
974
|
defaultClassName,
|
|
605
975
|
displayPlayAmountView,
|
|
606
976
|
doubleOrNothing.state,
|
|
977
|
+
freeRounds.isEngaged,
|
|
607
978
|
handleDoubleOrNothingClick,
|
|
608
979
|
handleCashoutClick,
|
|
609
980
|
handleTogglePlayAmount,
|
|
@@ -611,18 +982,20 @@ const ManualPlayController = () => {
|
|
|
611
982
|
isRunningExternal,
|
|
612
983
|
onPlay,
|
|
613
984
|
roleButton,
|
|
985
|
+
t,
|
|
614
986
|
]);
|
|
615
|
-
return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled || displayPlayAmountView, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
|
|
987
|
+
return (jsxs(Fragment, { children: [jsx(PlayAmountControl, { isDisabled: isDisabled || displayPlayAmountView, lockCurrency: freeRounds.isEngaged, forceRefresh: () => setForceRefresh(!forceRefresh), "data-force-refresh": `mini-play-amount-control-data-${forceRefresh.toString()}` }, `mini-play-amount-control-key-${forceRefresh.toString()}`), renderButton] }));
|
|
616
988
|
};
|
|
617
989
|
|
|
618
|
-
var styles_ui = {"base":"UI-module__base___wThyQ","container":"UI-module__container___gMSiK","betForm":"UI-module__betForm___hQkYd","disabled":"UI-module__disabled___dnZJX","auto":"UI-module__auto___5peb8","controllerWidgets":"UI-module__controllerWidgets___x2lBy","sideWidget":"UI-module__sideWidget___WMGRy","centerWidget":"UI-module__centerWidget___sLLUi","left":"UI-module__left___1wtUp","center":"UI-module__center___E0ILH","right":"UI-module__right___yLHvk"};
|
|
990
|
+
var styles_ui = {"base":"UI-module__base___wThyQ","container":"UI-module__container___gMSiK","betForm":"UI-module__betForm___hQkYd","isClassicGame":"UI-module__isClassicGame___zV1we","baseForceLeft":"UI-module__baseForceLeft___xfc-h","containerSpread":"UI-module__containerSpread___Axb7e","disabled":"UI-module__disabled___dnZJX","auto":"UI-module__auto___5peb8","freeRoundsFooter":"UI-module__freeRoundsFooter___AOprS","freeRoundsFooterCount":"UI-module__freeRoundsFooterCount___eaTrs","controllerWidgets":"UI-module__controllerWidgets___x2lBy","sideWidget":"UI-module__sideWidget___WMGRy","centerWidget":"UI-module__centerWidget___sLLUi","left":"UI-module__left___1wtUp","center":"UI-module__center___E0ILH","right":"UI-module__right___yLHvk","controls":"UI-module__controls___Z6L-V","inActiveRound":"UI-module__inActiveRound___HYAx2"};
|
|
619
991
|
|
|
620
992
|
var style_difficulty = {"base":"DifficultySelector-module__base___lDaQ-"};
|
|
621
993
|
|
|
622
|
-
var style_select = {"base":"Selector-module__base___-q0yf","label":"Selector-module__label___JJdbd","button":"Selector-module__button___MeRoC","menu":"Selector-module__menu___P0ROg","menuItems":"Selector-module__menuItems___chFk2","menuItem":"Selector-module__menuItem___isa4c","selected":"Selector-module__selected___nUJtR","disabled":"Selector-module__disabled___f1qGJ"};
|
|
994
|
+
var style_select = {"base":"Selector-module__base___-q0yf","label":"Selector-module__label___JJdbd","button":"Selector-module__button___MeRoC","menu":"Selector-module__menu___P0ROg","menuItems":"Selector-module__menuItems___chFk2","menuItem":"Selector-module__menuItem___isa4c","selected":"Selector-module__selected___nUJtR","disabled":"Selector-module__disabled___f1qGJ","disabledClassic":"Selector-module__disabledClassic___baiGO"};
|
|
623
995
|
|
|
624
996
|
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
625
|
-
const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, }) => {
|
|
997
|
+
const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, formatDisplay, }) => {
|
|
998
|
+
const display = (value) => formatDisplay ? formatDisplay(value) : capitalize(String(value));
|
|
626
999
|
const handleClick = (e) => {
|
|
627
1000
|
if (disabled) {
|
|
628
1001
|
return;
|
|
@@ -635,31 +1008,28 @@ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled,
|
|
|
635
1008
|
onSelect(value);
|
|
636
1009
|
};
|
|
637
1010
|
return (jsxs("div", { className: cx$1(style_select.base), children: [label && jsx("span", { className: cx$1(style_select.label), children: label }), jsx(Menu, { as: "div", className: cx$1(style_select.menu), children: ({ open }) => (jsxs(Fragment, { children: [jsxs(MenuButton, { className: cx$1(style_select.button, {
|
|
638
|
-
[style_select.disabled]: disabled,
|
|
639
|
-
|
|
1011
|
+
[style_select.disabled]: disabled && !isClassicGame,
|
|
1012
|
+
[style_select.disabledClassic]: disabled && isClassicGame,
|
|
1013
|
+
}), style: { color: riskColor }, disabled: disabled, "data-difficulty-selector-button": true, children: [display(currentValue), jsx(ChevronIcon, { open: open, disabled: disabled })] }), jsx(MenuItems, { anchor: { to: "top start" }, className: cx$1(style_select.menuItems), children: values.map((value) => (jsx(MenuItem, { as: "div", "data-value": value, onClick: handleClick, className: cx$1(style_select.menuItem, {
|
|
640
1014
|
[style_select.selected]: String(currentValue) === String(value),
|
|
641
|
-
}), children:
|
|
1015
|
+
}), children: display(value) }, `element-${value}`))) })] })) })] }));
|
|
642
1016
|
};
|
|
643
1017
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
(
|
|
650
|
-
|
|
651
|
-
RiskTypes["MEDIUM"] = "MEDIUM";
|
|
652
|
-
RiskTypes["HIGH"] = "HIGH";
|
|
653
|
-
})(RiskTypes || (RiskTypes = {}));
|
|
654
|
-
|
|
655
|
-
const DifficultySelector = ({ playOptions, dropdownConfig, }) => {
|
|
1018
|
+
const RISK_LABEL_KEY = {
|
|
1019
|
+
[RiskTypes.LOW]: "riskLow",
|
|
1020
|
+
[RiskTypes.MEDIUM]: "riskMedium",
|
|
1021
|
+
[RiskTypes.HIGH]: "riskHigh",
|
|
1022
|
+
};
|
|
1023
|
+
const DifficultySelector = ({ playOptions, dropdownConfig, isClassicGame = false, }) => {
|
|
1024
|
+
const { t } = useTranslation();
|
|
656
1025
|
const { risks, disabledMenu, currentRisk, onRiskChange } = playOptions;
|
|
657
|
-
const
|
|
1026
|
+
const riskColors = { ...DEFAULT_RISK_COLORS, ...dropdownConfig.riskColorConfig };
|
|
1027
|
+
const riskColor = useRef(riskColors[currentRisk ?? RiskTypes.LOW]);
|
|
658
1028
|
function onRiskSelected(value) {
|
|
659
|
-
riskColor.current =
|
|
1029
|
+
riskColor.current = riskColors[value];
|
|
660
1030
|
onRiskChange(value);
|
|
661
1031
|
}
|
|
662
|
-
return (jsx("div", { className: style_difficulty.base, children: jsx(Selector, { currentValue: currentRisk, label:
|
|
1032
|
+
return (jsx("div", { className: style_difficulty.base, children: jsx(Selector, { currentValue: currentRisk, label: t("riskLabel"), values: risks, onSelect: onRiskSelected, disabled: disabledMenu, riskColor: riskColor.current, isClassicGame: isClassicGame, formatDisplay: (risk) => t(RISK_LABEL_KEY[risk]) }) }));
|
|
663
1033
|
};
|
|
664
1034
|
|
|
665
1035
|
const ALLOWED_SEPARATORS = [",", "."];
|
|
@@ -682,9 +1052,9 @@ function cleanInputNumber(value) {
|
|
|
682
1052
|
return `${integerPart}.${decimalPart}`;
|
|
683
1053
|
}
|
|
684
1054
|
|
|
685
|
-
var styles$
|
|
1055
|
+
var styles$5 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
|
|
686
1056
|
|
|
687
|
-
const Input = ({ onChange, disabled, className, max, ...restProps }) => {
|
|
1057
|
+
const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...restProps }) => {
|
|
688
1058
|
const handleChange = React.useCallback((event) => {
|
|
689
1059
|
if (disabled) {
|
|
690
1060
|
return;
|
|
@@ -697,27 +1067,32 @@ const Input = ({ onChange, disabled, className, max, ...restProps }) => {
|
|
|
697
1067
|
event.target.value = clamped;
|
|
698
1068
|
onChange?.(event);
|
|
699
1069
|
}, [disabled, max, onChange]);
|
|
700
|
-
return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$
|
|
1070
|
+
return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$5.base, className, {
|
|
1071
|
+
[styles$5.disabled]: disabled && !isClassicGame,
|
|
1072
|
+
[styles$5.disabledClassic]: disabled && isClassicGame,
|
|
1073
|
+
}) }));
|
|
701
1074
|
};
|
|
702
1075
|
|
|
703
|
-
var styles$
|
|
1076
|
+
var styles$4 = {"base":"Switch-module__base___gj2ey","isClassicGame":"Switch-module__isClassicGame___H2Ffo","switcher":"Switch-module__switcher___gHXIx","gold":"Switch-module__gold___oewnb","sweeps":"Switch-module__sweeps___yS-IY","default":"Switch-module__default___TmHoU","unchecked":"Switch-module__unchecked___ooSS2","disabled":"Switch-module__disabled___lMRv0","thumb":"Switch-module__thumb___1wJ9D","move-right":"Switch-module__move-right___ca-6D","label":"Switch-module__label___pG2uz"};
|
|
704
1077
|
|
|
705
1078
|
const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
706
1079
|
const { config } = useAutoManualPlayState();
|
|
1080
|
+
const { t } = useTranslation();
|
|
707
1081
|
const { current } = config.currencyOptions;
|
|
1082
|
+
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
708
1083
|
const defaultClassName = useMemo(() => {
|
|
709
1084
|
if (current.code === Currency.GOLD) {
|
|
710
|
-
return styles$
|
|
1085
|
+
return styles$4.gold;
|
|
711
1086
|
}
|
|
712
1087
|
if (current.code === Currency.SWEEPS) {
|
|
713
|
-
return styles$
|
|
1088
|
+
return styles$4.sweeps;
|
|
714
1089
|
}
|
|
715
|
-
return styles$
|
|
1090
|
+
return styles$4.default;
|
|
716
1091
|
}, [current.code]);
|
|
717
|
-
const switcherClassName = useMemo(() => cx$1(styles$
|
|
718
|
-
[styles$
|
|
719
|
-
[styles$
|
|
720
|
-
[styles$
|
|
1092
|
+
const switcherClassName = useMemo(() => cx$1(styles$4.switcher, defaultClassName, {
|
|
1093
|
+
[styles$4.checked]: enabled,
|
|
1094
|
+
[styles$4.unchecked]: !enabled,
|
|
1095
|
+
[styles$4.disabled]: disabled,
|
|
721
1096
|
}), [defaultClassName, enabled, disabled]);
|
|
722
1097
|
useEffect(() => {
|
|
723
1098
|
const handleGlobalKeyDown = (event) => {
|
|
@@ -737,15 +1112,19 @@ const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
|
737
1112
|
window.removeEventListener("keydown", handleGlobalKeyDown, true);
|
|
738
1113
|
};
|
|
739
1114
|
}, []);
|
|
740
|
-
return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className:
|
|
1115
|
+
return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className: cx$1({
|
|
1116
|
+
[styles$4.isClassicGame]: isClassicGame,
|
|
1117
|
+
[styles$4.base]: !isClassicGame,
|
|
1118
|
+
}), children: [jsx("span", { className: styles$4.label, children: t("autoLabel") }), jsx("div", { className: switcherClassName, children: jsx("span", { className: cx$1(styles$4.thumb, { [styles$4["move-right"]]: enabled }) }) })] }) }));
|
|
741
1119
|
};
|
|
742
1120
|
|
|
743
|
-
var styles$
|
|
1121
|
+
var styles$3 = {"base":"InputWithSwitch-module__base___vjLoh","icon":"InputWithSwitch-module__icon___upeAL","input":"InputWithSwitch-module__input___K6RCD","label":"InputWithSwitch-module__label___tEjpj","classic":"InputWithSwitch-module__classic___3AzII"};
|
|
744
1122
|
|
|
745
|
-
const InputWithSwitch = ({ children, switcherConfig, disabled, className, ...restProps }) => {
|
|
746
|
-
return (jsxs("div", { className: cx$1(styles$
|
|
747
|
-
[styles$
|
|
748
|
-
|
|
1123
|
+
const InputWithSwitch = ({ children, switcherConfig, disabled, className, isClassicGame = false, ...restProps }) => {
|
|
1124
|
+
return (jsxs("div", { className: cx$1(styles$3.base, className, {
|
|
1125
|
+
[styles$3.disabled]: switcherConfig.disabled,
|
|
1126
|
+
[styles$3.classic]: isClassicGame,
|
|
1127
|
+
}), children: [jsx(Switch, { ...switcherConfig }), jsx(Input, { ...restProps, className: cx$1(styles$3.input), disabled: disabled, isClassicGame: isClassicGame }), jsx("div", { className: cx$1(styles$3.icon), children: children })] }));
|
|
749
1128
|
};
|
|
750
1129
|
|
|
751
1130
|
function getDefaultExportFromCjs (x) {
|
|
@@ -849,13 +1228,18 @@ const WidgetContainer = ({ state, widgets, displayPlayAmountView, }) => {
|
|
|
849
1228
|
return (jsxs("div", { className: cx("controllerWidgets"), children: [renderZone(left, "left"), renderZone(center, "center"), renderZone(right, "right")] }));
|
|
850
1229
|
};
|
|
851
1230
|
|
|
852
|
-
var styles = {"base":"PlayValueList-module__base___5duc6","playValuesWrapper":"PlayValueList-module__playValuesWrapper___-zfj2","playValue":"PlayValueList-module__playValue___TJQhI","selectedPlayValue":"PlayValueList-module__selectedPlayValue___3DhDC","disabledPlayValue":"PlayValueList-module__disabledPlayValue___U9Bkj"};
|
|
1231
|
+
var styles$2 = {"base":"PlayValueList-module__base___5duc6","playValuesWrapper":"PlayValueList-module__playValuesWrapper___-zfj2","locked":"PlayValueList-module__locked___JNPHM","playValue":"PlayValueList-module__playValue___TJQhI","selectedPlayValue":"PlayValueList-module__selectedPlayValue___3DhDC","disabledPlayValue":"PlayValueList-module__disabledPlayValue___U9Bkj","isClassicGame":"PlayValueList-module__isClassicGame___AjnGE"};
|
|
853
1232
|
|
|
854
1233
|
const PlayValueList = () => {
|
|
855
1234
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
856
1235
|
const { config } = useAutoManualPlayState();
|
|
857
|
-
const {
|
|
1236
|
+
const { t } = useTranslation();
|
|
1237
|
+
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
1238
|
+
const { onChangeAmount, playAmount, totalBalance, autoPlay: { state: autoplayState }, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
|
|
858
1239
|
const { playLimits } = config.playOptions.playHook();
|
|
1240
|
+
// While autoplay is running the stake is locked for the whole session, so
|
|
1241
|
+
// the play-amount table must not accept changes until it stops.
|
|
1242
|
+
const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
|
|
859
1243
|
if (!displayPlayAmountView) {
|
|
860
1244
|
return null;
|
|
861
1245
|
}
|
|
@@ -886,6 +1270,9 @@ const PlayValueList = () => {
|
|
|
886
1270
|
setForceRefresh(!forceRefresh);
|
|
887
1271
|
};
|
|
888
1272
|
const handleClick = (e) => {
|
|
1273
|
+
if (isAutoplayPlaying) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
889
1276
|
const el = e.target?.closest("[data-value]");
|
|
890
1277
|
if (!el) {
|
|
891
1278
|
return;
|
|
@@ -899,13 +1286,43 @@ const PlayValueList = () => {
|
|
|
899
1286
|
applyValue(value);
|
|
900
1287
|
}
|
|
901
1288
|
};
|
|
902
|
-
return (jsx("div", { className: cx$1(styles.base
|
|
903
|
-
|
|
904
|
-
|
|
1289
|
+
return (jsx("div", { className: cx$1(styles$2.base, { [styles$2.isClassicGame]: isClassicGame }), "data-force-refresh": forceRefresh, children: jsx("div", { className: cx$1(styles$2.playValuesWrapper, {
|
|
1290
|
+
[styles$2.locked]: isAutoplayPlaying,
|
|
1291
|
+
}), onClick: handleClick, role: "listbox", "aria-label": t("selectPlayAmountAria"), "aria-disabled": isAutoplayPlaying, "data-testid": "play-value-list", children: playLimits?.[config.currencyOptions.current.code].defaultBetOptions.map((playValue) => (jsx("div", { className: cx$1(styles$2.playValue, {
|
|
1292
|
+
[styles$2.selectedPlayValue]: playValue === playAmount,
|
|
1293
|
+
[styles$2.disabledPlayValue]: playValue > totalBalance,
|
|
905
1294
|
}), "data-value": playValue, role: "option", tabIndex: 0, "aria-selected": false, children: format(playValue, config.currencyOptions.current.decimals) }, `${config.currencyOptions.current.code}-${playValue}`))) }) }));
|
|
906
1295
|
};
|
|
907
1296
|
|
|
1297
|
+
var styles$1 = {"backdrop":"FreeRoundsIntroModal-module__backdrop___qIgNx","column":"FreeRoundsIntroModal-module__column___YG71X","panel":"FreeRoundsIntroModal-module__panel___-zQpe","header":"FreeRoundsIntroModal-module__header___K5jmV","summary":"FreeRoundsIntroModal-module__summary___qpAbF","accent":"FreeRoundsIntroModal-module__accent___m-2s6","perRound":"FreeRoundsIntroModal-module__perRound___5Sx-v","expiry":"FreeRoundsIntroModal-module__expiry___n-fve","actions":"FreeRoundsIntroModal-module__actions___toCBq","button":"FreeRoundsIntroModal-module__button___bSTmX","playNow":"FreeRoundsIntroModal-module__playNow___iVhdt","playLater":"FreeRoundsIntroModal-module__playLater___l9031"};
|
|
1298
|
+
|
|
1299
|
+
// Centered START intro pop-up — binary engagement decision. Renders + fires
|
|
1300
|
+
// callbacks only; the SDK does no fetching. `onPlayNow` should engage
|
|
1301
|
+
// free-rounds mode + close, `onPlayLater` should close and render zero
|
|
1302
|
+
// free-rounds UI for the rest of the session.
|
|
1303
|
+
const FreeRoundsIntroModal = ({ open, roundsRemaining, stakeCents, coinType, currency, decimals = 2, expiresAt, onPlayNow, onPlayLater, }) => {
|
|
1304
|
+
const { t } = useTranslation();
|
|
1305
|
+
const handleClose = () => onPlayLater?.();
|
|
1306
|
+
const coinLabel = resolveFreeRoundsCoinLabel({ coinType, currency });
|
|
1307
|
+
const valueDecimals = resolveFreeRoundsDecimals({
|
|
1308
|
+
coinType,
|
|
1309
|
+
currency,
|
|
1310
|
+
fallback: decimals,
|
|
1311
|
+
});
|
|
1312
|
+
const perRoundDisplay = format(stakeCents / 100, valueDecimals);
|
|
1313
|
+
const expiresInDays = computeExpiresInDays(expiresAt);
|
|
1314
|
+
return (jsx(Dialog, { open: open, onClose: handleClose, className: styles$1.backdrop, children: jsx("div", { className: styles$1.column, children: jsxs(DialogPanel, { className: styles$1.panel, children: [jsx(DialogTitle, { className: styles$1.header, children: t("freeRoundsModalTitle") }), jsxs("div", { className: styles$1.summary, children: [t("freeRoundsHave"), " ", jsx("span", { className: styles$1.accent, children: roundsRemaining }), " ", roundsRemaining === 1
|
|
1315
|
+
? t("freeRoundsUnitOne")
|
|
1316
|
+
: t("freeRoundsUnitOther")] }), jsx("div", { className: styles$1.perRound, children: t("freeRoundsPerRound", {
|
|
1317
|
+
amount: `${perRoundDisplay}${coinLabel ? ` ${coinLabel}` : ""}`,
|
|
1318
|
+
}) }), expiresInDays !== null && (jsx("div", { className: styles$1.expiry, children: t("freeRoundsExpiresIn", {
|
|
1319
|
+
days: expiresInDays,
|
|
1320
|
+
unit: expiresInDays === 1 ? t("dayUnitOne") : t("dayUnitOther"),
|
|
1321
|
+
}) })), jsxs("div", { className: styles$1.actions, children: [jsx("button", { type: "button", className: `${styles$1.button} ${styles$1.playNow}`, onClick: onPlayNow, children: t("freeRoundsModalPlayNow") }), jsx("button", { type: "button", className: `${styles$1.button} ${styles$1.playLater}`, onClick: onPlayLater, children: t("freeRoundsModalPlayLater") })] })] }) }) }));
|
|
1322
|
+
};
|
|
1323
|
+
|
|
908
1324
|
const AutoManualPlayProvider = ({ children, config, }) => {
|
|
1325
|
+
const t = useMemo(() => createTranslator(resolveLanguage(config.language)), [config.language]);
|
|
909
1326
|
const [mode, setMode] = useState(GAME_MODE.MANUAL);
|
|
910
1327
|
const [autoplayState, setAutoplayState] = useState(AUTO_PLAY_STATE.IDLE);
|
|
911
1328
|
const [doubleOrNothingState, setDoubleOrNothingState] = useState(DOUBLE_OR_NOTHING_STATE.IDLE);
|
|
@@ -917,13 +1334,31 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
917
1334
|
const isControllerDisabled = config.playOptions.disabledController;
|
|
918
1335
|
const isGamePlaying = config.playOptions.isPlaying;
|
|
919
1336
|
const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
|
|
1337
|
+
// Free rounds are MANUAL-only by default. While a grant is engaged the
|
|
1338
|
+
// player must not be able to flip into autoplay (which would silently drop
|
|
1339
|
+
// the manual-gated engaged state and let PAID autoplay start). This is
|
|
1340
|
+
// independent of `mode` so it stays true even if a switch attempt slipped
|
|
1341
|
+
// through. Games whose autoplay submission path funds rounds from engaged
|
|
1342
|
+
// grants opt out of the lock via `supportsGrantFundedAutoplay`.
|
|
1343
|
+
const freeRoundsRemaining = config.freeRoundsOptions?.roundsRemaining ?? 0;
|
|
1344
|
+
const isFreeRoundsEngaged = Boolean(config.freeRoundsOptions?.engaged && freeRoundsRemaining > 0);
|
|
1345
|
+
const supportsGrantFundedAutoplay = Boolean(config.freeRoundsOptions?.supportsGrantFundedAutoplay);
|
|
1346
|
+
const isModeLockedByFreeRounds = isFreeRoundsEngaged && !supportsGrantFundedAutoplay;
|
|
920
1347
|
const isSimplified = config.withoutRiskAndAutoplay === true;
|
|
1348
|
+
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
921
1349
|
const togglePlayAmountView = useCallback(() => {
|
|
922
1350
|
setDisplayPlayAmountView((v) => !v);
|
|
923
1351
|
}, []);
|
|
1352
|
+
// Grant-funded autoplay: while free rounds are engaged the selectable
|
|
1353
|
+
// autoplay count is hard-capped to the remaining grants (covers the default
|
|
1354
|
+
// count and the ∞ option — Infinity clamps down to roundsRemaining, so ∞ is
|
|
1355
|
+
// effectively disabled while engaged). No paid spillover.
|
|
924
1356
|
useEffect(() => {
|
|
925
|
-
|
|
926
|
-
|
|
1357
|
+
if (!supportsGrantFundedAutoplay || !isFreeRoundsEngaged) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
setNumberOfPlays((prev) => prev > freeRoundsRemaining ? freeRoundsRemaining : prev);
|
|
1361
|
+
}, [supportsGrantFundedAutoplay, isFreeRoundsEngaged, freeRoundsRemaining]);
|
|
927
1362
|
const startAutoplay = useCallback((numPlays) => {
|
|
928
1363
|
setMode(GAME_MODE.AUTOPLAY);
|
|
929
1364
|
setAutoplayState(AUTO_PLAY_STATE.SELECTING);
|
|
@@ -963,12 +1398,25 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
963
1398
|
setSelection([]);
|
|
964
1399
|
}, []);
|
|
965
1400
|
const toggleMode = useCallback(() => {
|
|
966
|
-
|
|
1401
|
+
// No-op while a free-round grant is engaged WITHOUT the game's
|
|
1402
|
+
// grant-funded-autoplay opt-in: manual-only, no-exit. The player cannot
|
|
1403
|
+
// switch to autoplay until the grant is consumed/released. When the game
|
|
1404
|
+
// sets `supportsGrantFundedAutoplay`, engaged switching is allowed (still
|
|
1405
|
+
// blocked while a round is in flight via the gates above).
|
|
1406
|
+
if (isGamePlaying ||
|
|
1407
|
+
isControllerDisabled ||
|
|
1408
|
+
isAutoplayPlaying ||
|
|
1409
|
+
isModeLockedByFreeRounds) {
|
|
967
1410
|
return;
|
|
968
1411
|
}
|
|
969
1412
|
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
970
1413
|
setMode((prevMode) => prevMode === GAME_MODE.MANUAL ? GAME_MODE.AUTOPLAY : GAME_MODE.MANUAL);
|
|
971
|
-
}, [
|
|
1414
|
+
}, [
|
|
1415
|
+
isAutoplayPlaying,
|
|
1416
|
+
isControllerDisabled,
|
|
1417
|
+
isGamePlaying,
|
|
1418
|
+
isModeLockedByFreeRounds,
|
|
1419
|
+
]);
|
|
972
1420
|
const updateSelection = useCallback((values) => {
|
|
973
1421
|
if (!isAutoPlaying) {
|
|
974
1422
|
setSelection(values);
|
|
@@ -1027,29 +1475,60 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1027
1475
|
displayPlayAmountView,
|
|
1028
1476
|
togglePlayAmountView,
|
|
1029
1477
|
]);
|
|
1030
|
-
return (jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children,
|
|
1031
|
-
"
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1478
|
+
return (jsx(I18nProvider, { language: config.language, children: jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, mode === GAME_MODE.MANUAL &&
|
|
1479
|
+
config.freeRoundsOptions?.showIntroModal && (jsx("div", { style: {
|
|
1480
|
+
"--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#221062"),
|
|
1481
|
+
}, children: jsx(FreeRoundsIntroModal, { open: config.freeRoundsOptions.showIntroModal, roundsRemaining: config.freeRoundsOptions.roundsRemaining, totalRounds: config.freeRoundsOptions.totalRounds, stakeCents: config.freeRoundsOptions.stakeCents, coinType: config.freeRoundsOptions.coinType, currency: config.freeRoundsOptions.currency, decimals: config.currencyOptions.current.decimals, expiresAt: config.freeRoundsOptions.expiresAt, onPlayNow: config.freeRoundsOptions.onPlayNow, onPlayLater: config.freeRoundsOptions.onPlayLater }) })), config.playOptions.displayController && (jsx("div", { className: cx$1(styles_ui.base, styles_ui.betForm, {
|
|
1482
|
+
[styles_ui.baseForceLeft]: config.classicGame?.forceLeftStyle,
|
|
1483
|
+
[styles_ui.isClassicGame]: config.isClassicGame,
|
|
1484
|
+
[styles_ui.inActiveRound]: mode === GAME_MODE.MANUAL && isGamePlaying,
|
|
1485
|
+
}), style: {
|
|
1486
|
+
"--play-bottom": config.panel.bottom ?? 0,
|
|
1487
|
+
"--play-panel-bg": hexToRgb(config.panel.bgColorHex ?? "#221062"),
|
|
1488
|
+
"--play-dropdown-bg": hexToRgb(config.classicGame?.dropdown?.bgColorHex ??
|
|
1489
|
+
config.dropdown.bgColorHex ??
|
|
1490
|
+
"#150C47"),
|
|
1491
|
+
"--play-panel-bg-opacity": config.panel.bgColorOpacity ?? 0.5,
|
|
1492
|
+
}, children: jsxs("div", { className: cx$1(styles_ui.container, {
|
|
1493
|
+
[styles_ui.containerSpread]: config.classicGame?.forceLeftStyle,
|
|
1494
|
+
}), children: [jsxs("div", { className: cx$1(styles_ui.controls), children: [jsx(PlayValueList, {}), !isSimplified ? (jsxs("div", { className: cx$1(styles_ui.auto), children: [jsx(DifficultySelector, { playOptions: {
|
|
1495
|
+
...config.playOptions,
|
|
1496
|
+
disabledMenu: isControllerDisabled ||
|
|
1497
|
+
config.playOptions.disabledMenu,
|
|
1498
|
+
}, dropdownConfig: config.dropdown, isClassicGame: isClassicGame }), jsx(InputWithSwitch, { value: numberOfPlays === Infinity ? 0 : numberOfPlays, type: "number", onChange: (e) => setNumberOfPlays(Number(e.currentTarget.value)), placeholder: t("numberOfPlays"), min: 0, max: 99, disabled: isControllerDisabled || mode === GAME_MODE.MANUAL, isClassicGame: isClassicGame, switcherConfig: {
|
|
1499
|
+
onSwitch: toggleMode,
|
|
1500
|
+
isPlaying: isAutoPlaying || isGamePlaying,
|
|
1501
|
+
enabled: mode !== GAME_MODE.MANUAL,
|
|
1502
|
+
disabled: isControllerDisabled ||
|
|
1503
|
+
isAutoplayPlaying ||
|
|
1504
|
+
isModeLockedByFreeRounds,
|
|
1505
|
+
}, children: !isClassicGame && (jsx("span", { className: cx$1({
|
|
1506
|
+
[styles_ui.disabled]: mode !== GAME_MODE.AUTOPLAY ||
|
|
1507
|
+
numberOfPlays !== Infinity ||
|
|
1508
|
+
isAutoplayPlaying,
|
|
1509
|
+
}), children: "\u221E" })) })] })) : null, mode === GAME_MODE.MANUAL ? (jsx(ManualPlayController, {})) : (jsx(AutoPlayController, {}))] }), isFreeRoundsEngaged && (jsxs("div", { className: styles_ui.freeRoundsFooter, children: [jsx("span", { className: styles_ui.freeRoundsFooterCount, children: config.freeRoundsOptions?.roundsRemaining }), " ", config.freeRoundsOptions?.roundsRemaining === 1
|
|
1510
|
+
? t("freeRoundLeftOne")
|
|
1511
|
+
: t("freeRoundLeftOther")] })), jsx(WidgetContainer, { state: autoplayState, displayPlayAmountView: displayPlayAmountView, widgets: {
|
|
1512
|
+
left: config.leftWidgets,
|
|
1513
|
+
center: config.centerWidgets,
|
|
1514
|
+
right: config.rightWidgets,
|
|
1515
|
+
} })] }) }))] }) }));
|
|
1516
|
+
};
|
|
1517
|
+
|
|
1518
|
+
var styles = {"backdrop":"FreeRoundsSummaryModal-module__backdrop___tNi3I","panel":"FreeRoundsSummaryModal-module__panel___2MIEp","header":"FreeRoundsSummaryModal-module__header___-BkpP","summary":"FreeRoundsSummaryModal-module__summary___YExX9","accent":"FreeRoundsSummaryModal-module__accent___z9aBF","hint":"FreeRoundsSummaryModal-module__hint___CYWqA","close":"FreeRoundsSummaryModal-module__close___4-irR"};
|
|
1519
|
+
|
|
1520
|
+
// END pop-up — shown by the game after the last free round settles. The game
|
|
1521
|
+
// owns visibility: it mounts open when the play-through completes, unmount
|
|
1522
|
+
// onClose.
|
|
1523
|
+
const FreeRoundsSummaryModal = ({ totalWinAmount, coin, roundsPlayed, decimals, onClose, }) => {
|
|
1524
|
+
const { t } = useTranslation();
|
|
1525
|
+
// Shared monetary formatter — truncates (never rounds up) so the summary
|
|
1526
|
+
// can't display more winnings than were actually credited.
|
|
1527
|
+
const winDisplay = format(totalWinAmount, decimals ?? resolveCoinLabelDecimals(coin));
|
|
1528
|
+
return (jsx(Dialog, { open: true, onClose: onClose, className: styles.backdrop, children: jsxs(DialogPanel, { className: styles.panel, children: [jsx(DialogTitle, { className: styles.header, children: t("freeRoundsModalTitle") }), jsxs("div", { className: styles.summary, children: [t("freeRoundsSummaryWon"), " ", jsxs("span", { className: styles.accent, children: [winDisplay, " ", coin] }), " ", t("freeRoundsSummaryIn"), " ", roundsPlayed, " ", roundsPlayed === 1
|
|
1529
|
+
? t("freeRoundsSummaryUnitOne")
|
|
1530
|
+
: t("freeRoundsSummaryUnitOther")] }), jsx("div", { className: styles.hint, children: t("freeRoundsSummaryCredited") }), jsx("button", { type: "button", className: styles.close, onClick: onClose, children: t("freeRoundsSummaryClose") })] }) }));
|
|
1052
1531
|
};
|
|
1053
1532
|
|
|
1054
|
-
export { AUTO_PLAY_STATE, AutoManualPlayProvider, DOUBLE_OR_NOTHING_STATE, GAME_MODE, WIDGET, canAutoplaySpaceTrigger, createAutoplayButtonAction, createManualCashoutClickHandler, format, isAutoplayButtonDisabled, isManualCashoutDisabled };
|
|
1533
|
+
export { AUTO_PLAY_STATE, AutoManualPlayProvider, DEFAULT_RISK_COLORS, DOUBLE_OR_NOTHING_STATE, FreeRoundsIntroModal, FreeRoundsSummaryModal, GAME_MODE, I18nProvider, WIDGET, canAutoplaySpaceTrigger, createAutoplayButtonAction, createManualCashoutClickHandler, createTranslator, format, isAutoplayButtonDisabled, isManualCashoutDisabled, useTranslation };
|
|
1055
1534
|
//# sourceMappingURL=index.mjs.map
|