@enigma-lake/tower-play-controller-sdk 2.2.2 → 2.2.4
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 +2 -6
- package/dist/components/base/DifficultySelector/Selector.d.ts +1 -1
- package/dist/components/base/DifficultySelector/types.d.ts +2 -0
- package/dist/components/base/FreeRoundsIntroModal/FreeRoundsIntroModal.d.ts +3 -2
- package/dist/components/base/FreeRoundsIntroModal/freeRoundsCoin.d.ts +11 -0
- package/dist/components/base/FreeRoundsSummaryModal/FreeRoundsSummaryModal.d.ts +9 -0
- package/dist/components/base/FreeRoundsSummaryModal/index.d.ts +3 -0
- package/dist/components/hooks/usePlayController.d.ts +0 -2
- 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 +84 -18
- package/dist/index.mjs +493 -195
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +1 -1
- package/dist/types/playController.d.ts +16 -14
- package/package.json +1 -1
- package/dist/components/base/FreeRoundsAffordance/FreeRoundsAffordance.d.ts +0 -10
- package/dist/components/base/FreeRoundsAffordance/index.d.ts +0 -2
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React, { createContext, useContext, useRef, useEffect, useCallback, useState,
|
|
2
|
+
import React, { createContext, useContext, useMemo, useRef, useEffect, useCallback, useState, Fragment as Fragment$1 } from 'react';
|
|
3
3
|
import cx$1 from 'classnames';
|
|
4
|
-
import { Currency, sendSetUserCurrencyEvent, sendSetUserCurrencyEventV2, setToggleGameWidgetsVisibility, setToggleGameWidgetsExpansion, getUserInformationEvent, EVENTS, sendOpenHighPlayModalEvent } from '@enigma-lake/zoot-platform-sdk';
|
|
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';
|
|
5
6
|
import { Menu, MenuButton, MenuItems, MenuItem, Switch as Switch$1, Dialog, DialogPanel, DialogTitle } from '@headlessui/react';
|
|
6
7
|
|
|
7
8
|
const hexToRgb = (hex) => {
|
|
@@ -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,6 +257,7 @@ function isFullMode(config) {
|
|
|
141
257
|
return config.withoutRiskAndAutoplay !== true;
|
|
142
258
|
}
|
|
143
259
|
const usePlayController = () => {
|
|
260
|
+
const { t } = useTranslation();
|
|
144
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;
|
|
@@ -155,17 +272,19 @@ const usePlayController = () => {
|
|
|
155
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;
|
|
158
|
-
// Free rounds (opt-in
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
//
|
|
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.
|
|
163
284
|
const isManualMode = mode === GAME_MODE.MANUAL;
|
|
164
285
|
const freeRoundsOptions = config.freeRoundsOptions;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
freeRoundsOptions.roundsRemaining > 0);
|
|
168
|
-
const isFreeRoundsEngaged = Boolean(isManualMode &&
|
|
286
|
+
const supportsGrantFundedAutoplay = Boolean(freeRoundsOptions?.supportsGrantFundedAutoplay);
|
|
287
|
+
const isFreeRoundsEngaged = Boolean((isManualMode || supportsGrantFundedAutoplay) &&
|
|
169
288
|
freeRoundsOptions?.engaged &&
|
|
170
289
|
freeRoundsOptions.roundsRemaining > 0);
|
|
171
290
|
// playAmount is in display units everywhere in this SDK (e.g. 1.00), while the
|
|
@@ -182,6 +301,33 @@ const usePlayController = () => {
|
|
|
182
301
|
: rawSetPlayAmount;
|
|
183
302
|
const playIntervalRef = useRef(null);
|
|
184
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
|
+
});
|
|
185
331
|
const stopAutoplay = () => {
|
|
186
332
|
isAutoplayActiveRef.current = false;
|
|
187
333
|
if (playIntervalRef.current) {
|
|
@@ -201,6 +347,35 @@ const usePlayController = () => {
|
|
|
201
347
|
if (!isAutoplayActiveRef.current || !autoplayEnabled) {
|
|
202
348
|
return;
|
|
203
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
|
+
}
|
|
204
379
|
if (remainingPlays < 1 || numberOfPlays === 0) {
|
|
205
380
|
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
206
381
|
stopAutoplay();
|
|
@@ -208,6 +383,7 @@ const usePlayController = () => {
|
|
|
208
383
|
}
|
|
209
384
|
setPlayedRounds(currentPlayedRounds + 1);
|
|
210
385
|
setNumberOfPlays((prev) => Math.max(prev - 1, 0));
|
|
386
|
+
session.roundsSubmitted += 1;
|
|
211
387
|
// onAutoPlay exists only in full mode (guarded by autoplayEnabled)
|
|
212
388
|
config.onAutoPlay(selection, () => {
|
|
213
389
|
if (!isAutoplayActiveRef.current) {
|
|
@@ -225,13 +401,32 @@ const usePlayController = () => {
|
|
|
225
401
|
if (selection.length === 0) {
|
|
226
402
|
showAutoPlayToast?.({
|
|
227
403
|
type: "info",
|
|
228
|
-
message: "
|
|
404
|
+
message: t("selectTileToast"),
|
|
229
405
|
});
|
|
230
406
|
return;
|
|
231
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
|
+
};
|
|
232
427
|
isAutoplayActiveRef.current = true;
|
|
233
428
|
setState(AUTO_PLAY_STATE.PLAYING);
|
|
234
|
-
loopRounds(playedRounds,
|
|
429
|
+
loopRounds(playedRounds, effectiveNumberOfPlays);
|
|
235
430
|
};
|
|
236
431
|
const adjustPlayAmount = ({ direction }) => {
|
|
237
432
|
if (isDisabled()) {
|
|
@@ -289,16 +484,15 @@ const usePlayController = () => {
|
|
|
289
484
|
setDisplayPlayAmountView,
|
|
290
485
|
togglePlayAmountView,
|
|
291
486
|
},
|
|
292
|
-
// free rounds (manual-mode only
|
|
487
|
+
// free rounds (binary engagement via the intro modal; manual-mode only
|
|
488
|
+
// unless the game opts into grant-funded autoplay)
|
|
293
489
|
freeRounds: {
|
|
294
|
-
isAvailable: isFreeRoundsAvailable,
|
|
295
490
|
isEngaged: isFreeRoundsEngaged,
|
|
296
491
|
roundsRemaining: freeRoundsOptions?.roundsRemaining ?? 0,
|
|
297
492
|
totalRounds: freeRoundsOptions?.totalRounds ?? 0,
|
|
298
493
|
stakeCents: freeRoundsOptions?.stakeCents ?? 0,
|
|
299
494
|
coinType: freeRoundsOptions?.coinType,
|
|
300
495
|
currency: freeRoundsOptions?.currency,
|
|
301
|
-
onUseFreeRounds: freeRoundsOptions?.onUseFreeRounds,
|
|
302
496
|
},
|
|
303
497
|
};
|
|
304
498
|
};
|
|
@@ -392,42 +586,117 @@ const SelectMenu = ({ disabled = false }) => {
|
|
|
392
586
|
}) })] })) }));
|
|
393
587
|
};
|
|
394
588
|
|
|
395
|
-
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"};
|
|
396
590
|
|
|
397
591
|
const PlayValueInput = ({ disabled, children, className, value, onClick, isClassicGame = false, }) => {
|
|
592
|
+
const { t } = useTranslation();
|
|
398
593
|
const handleOnClick = () => {
|
|
399
594
|
if (disabled) {
|
|
400
595
|
return;
|
|
401
596
|
}
|
|
402
597
|
onClick();
|
|
403
598
|
};
|
|
404
|
-
return (jsxs("div", { className: cx$1(styles$
|
|
405
|
-
[styles$
|
|
406
|
-
[styles$
|
|
407
|
-
}), children: [children && jsx("div", { className: cx$1(styles$
|
|
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) => {
|
|
408
603
|
e.preventDefault();
|
|
409
604
|
handleOnClick();
|
|
410
|
-
}, children: [jsx("span", { className: styles$
|
|
411
|
-
[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,
|
|
412
607
|
}), children: value })] })] }));
|
|
413
608
|
};
|
|
414
609
|
|
|
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
|
+
|
|
415
668
|
const PlayAmountControl = ({ isDisabled, forceRefresh, lockCurrency = false, }) => {
|
|
416
669
|
const { config } = useAutoManualPlayState();
|
|
417
|
-
const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, } = usePlayController();
|
|
670
|
+
const { playAmount, adjustPlayAmount, playValues: { togglePlayAmountView }, autoPlay: { state }, freeRounds, } = usePlayController();
|
|
418
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;
|
|
419
688
|
const handleTogglePlayAmount = () => {
|
|
420
689
|
setToggleGameWidgetsVisibility();
|
|
421
690
|
togglePlayAmountView();
|
|
422
691
|
setToggleGameWidgetsExpansion();
|
|
423
692
|
};
|
|
424
|
-
return (jsxs(GroupRow, { isClassicGame: isClassicGame, 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() ||
|
|
425
694
|
config.playOptions.disableInput ||
|
|
426
695
|
config.playOptions.disabledController ||
|
|
427
696
|
state === AUTO_PLAY_STATE.PLAYING ||
|
|
428
|
-
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(), children: jsx("span", { className: styles_group.x2, children: "-" }) }), jsx(Button, { className: styles_group.refreshButton, onClick: () => {
|
|
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: () => {
|
|
429
698
|
forceRefresh();
|
|
430
|
-
}, "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: "+" }) })] }));
|
|
431
700
|
};
|
|
432
701
|
|
|
433
702
|
const getDefaultButtonClassName = (currencyCode, styles) => {
|
|
@@ -449,17 +718,11 @@ const getActiveButtonClassName = (currencyCode, styles) => {
|
|
|
449
718
|
return styles.buttonDefault__active;
|
|
450
719
|
};
|
|
451
720
|
|
|
452
|
-
var AUTOPLAY_LABEL;
|
|
453
|
-
(function (AUTOPLAY_LABEL) {
|
|
454
|
-
AUTOPLAY_LABEL["START"] = "START AUTOPLAY";
|
|
455
|
-
AUTOPLAY_LABEL["STOP"] = "STOP AUTOPLAY";
|
|
456
|
-
})(AUTOPLAY_LABEL || (AUTOPLAY_LABEL = {}));
|
|
457
|
-
|
|
458
721
|
// Space activates buttons/links natively. If the user has keyboard focus on a
|
|
459
|
-
// different interactive control (the
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
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.
|
|
463
726
|
const isOtherInteractiveControlFocused = (button) => {
|
|
464
727
|
const active = document.activeElement;
|
|
465
728
|
return Boolean(active &&
|
|
@@ -517,20 +780,29 @@ const useSpacebarButtonTrigger = ({ roleButton, activeClassName, canTrigger, sup
|
|
|
517
780
|
|
|
518
781
|
const AutoPlayController = () => {
|
|
519
782
|
const { config } = useAutoManualPlayState();
|
|
783
|
+
const { t } = useTranslation();
|
|
520
784
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
521
|
-
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();
|
|
522
786
|
const { current } = config.currencyOptions;
|
|
523
787
|
const roleButton = GAME_MODE.AUTOPLAY;
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
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
|
+
}
|
|
527
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.
|
|
528
800
|
const defaultClassName = useMemo(() => {
|
|
529
|
-
if (
|
|
801
|
+
if (isStopState) {
|
|
530
802
|
return styles_button.buttonCashout;
|
|
531
803
|
}
|
|
532
804
|
return getDefaultButtonClassName(current.code, styles_button);
|
|
533
|
-
}, [
|
|
805
|
+
}, [isStopState, current.code]);
|
|
534
806
|
useSpacebarButtonTrigger({
|
|
535
807
|
roleButton,
|
|
536
808
|
activeClassName,
|
|
@@ -539,6 +811,10 @@ const AutoPlayController = () => {
|
|
|
539
811
|
cashoutDisabled,
|
|
540
812
|
isDisabled: isDisabled(),
|
|
541
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),
|
|
542
818
|
});
|
|
543
819
|
const isButtonDisabled = isAutoplayButtonDisabled({
|
|
544
820
|
state,
|
|
@@ -563,7 +839,7 @@ const AutoPlayController = () => {
|
|
|
563
839
|
}, [togglePlayAmountView]);
|
|
564
840
|
const renderButton = useMemo(() => {
|
|
565
841
|
if (displayPlayAmountView) {
|
|
566
|
-
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") }));
|
|
567
843
|
}
|
|
568
844
|
return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: buttonAction, roleType: roleButton, children: buttonLabel }));
|
|
569
845
|
}, [
|
|
@@ -574,27 +850,11 @@ const AutoPlayController = () => {
|
|
|
574
850
|
handleTogglePlayAmount,
|
|
575
851
|
isButtonDisabled,
|
|
576
852
|
roleButton,
|
|
853
|
+
t,
|
|
577
854
|
]);
|
|
578
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] }));
|
|
579
856
|
};
|
|
580
857
|
|
|
581
|
-
var freeRoundsStyles = {"affordance":"FreeRoundsAffordance-module__affordance___wBGcv","row":"FreeRoundsAffordance-module__row___KwByQ","playSlot":"FreeRoundsAffordance-module__playSlot___hCXTv","freeSlot":"FreeRoundsAffordance-module__freeSlot___-mQ8T","chip":"FreeRoundsAffordance-module__chip___OajJb","chipDisabled":"FreeRoundsAffordance-module__chipDisabled___W9hSU"};
|
|
582
|
-
|
|
583
|
-
// The "Use Free Rounds (N)" chip.
|
|
584
|
-
// - available && !engaged → renders the chip (engages via onUseFreeRounds).
|
|
585
|
-
// It is laid out beside the Play Now button (35/65 row) by
|
|
586
|
-
// ManualPlayController; this component only renders the chip itself.
|
|
587
|
-
// - engaged → renders nothing. There is intentionally NO "exit free rounds"
|
|
588
|
-
// affordance: once engaged the player cannot leave free-round mode from the
|
|
589
|
-
// controller. The engaged play button label is owned by ManualPlayController.
|
|
590
|
-
// Purely renders + fires the callback; the game owns the engaged state.
|
|
591
|
-
const FreeRoundsAffordance = ({ isAvailable, isEngaged, roundsRemaining, onUseFreeRounds, disabled = false, }) => {
|
|
592
|
-
if (isEngaged || !isAvailable || !onUseFreeRounds) {
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
return (jsxs("button", { type: "button", disabled: disabled, className: cx$1(freeRoundsStyles.chip, { [freeRoundsStyles.chipDisabled]: disabled }), onClick: disabled ? undefined : onUseFreeRounds, children: ["USE FREE ROUNDS (", roundsRemaining, ")"] }));
|
|
596
|
-
};
|
|
597
|
-
|
|
598
858
|
const useIsRunningExternal = () => {
|
|
599
859
|
const [isRunningExternal, setIsRunningExternal] = useState(false);
|
|
600
860
|
useEffect(() => {
|
|
@@ -619,6 +879,7 @@ const useIsRunningExternal = () => {
|
|
|
619
879
|
|
|
620
880
|
const ManualPlayController = () => {
|
|
621
881
|
const { config, doubleOrNothing } = useAutoManualPlayState();
|
|
882
|
+
const { t } = useTranslation();
|
|
622
883
|
const { isRunningExternal } = useIsRunningExternal();
|
|
623
884
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
624
885
|
const { isValidPlayAmount, playValues: { displayPlayAmountView, togglePlayAmountView }, manualPlay: { isDisabled, onPlay, canCashout, cashoutDisabled }, freeRounds, } = usePlayController();
|
|
@@ -668,7 +929,7 @@ const ManualPlayController = () => {
|
|
|
668
929
|
}), [cashoutDisabled, config.onCashout]);
|
|
669
930
|
const renderButton = useMemo(() => {
|
|
670
931
|
if (displayPlayAmountView) {
|
|
671
|
-
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") }));
|
|
672
933
|
}
|
|
673
934
|
if (canCashout) {
|
|
674
935
|
const isCashoutDisabled = isManualCashoutDisabled({
|
|
@@ -682,21 +943,29 @@ const ManualPlayController = () => {
|
|
|
682
943
|
doubleOrNothing.state === DOUBLE_OR_NOTHING_STATE.PROGRESS;
|
|
683
944
|
return (jsxs("div", { className: styles_button.buttonRow, children: [jsxs(Button, { disabled: isCashoutDisabled, className: cx$1(styles_button.buttonCashout, {
|
|
684
945
|
[styles_button.buttonRow__cashout]: showDoubleOrNothing,
|
|
685
|
-
}), 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, {
|
|
686
947
|
[styles_button.buttonDON__disabled]: isDoubleOrNothingDisabled,
|
|
687
|
-
}), 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;
|
|
688
955
|
}
|
|
689
956
|
// Free rounds reuse the play button label while ENGAGED — it becomes
|
|
690
|
-
// "PLAY FREE ROUND (
|
|
957
|
+
// "PLAY FREE ROUND" (no count; the remaining count lives ONLY in the
|
|
958
|
+
// "{N} FREE ROUNDS LEFT" footer).
|
|
691
959
|
const playNowText = freeRounds.isEngaged
|
|
692
|
-
?
|
|
693
|
-
: "
|
|
960
|
+
? t("playFreeRound")
|
|
961
|
+
: t("playNow");
|
|
694
962
|
return (jsx(Button, { disabled: isButtonDisabled, className: defaultClassName, onClick: onPlay, roleType: roleButton, children: playNowText }));
|
|
695
963
|
}, [
|
|
696
964
|
canCashout,
|
|
697
965
|
cashoutDisabled,
|
|
698
966
|
config.doubleOrNothing?.disabled,
|
|
699
967
|
config.doubleOrNothing?.display,
|
|
968
|
+
config.freeRoundsOptions?.autoplayActive,
|
|
700
969
|
config.playOptions.disabledController,
|
|
701
970
|
config.playOptions.isPlaying,
|
|
702
971
|
current.abbr,
|
|
@@ -706,7 +975,6 @@ const ManualPlayController = () => {
|
|
|
706
975
|
displayPlayAmountView,
|
|
707
976
|
doubleOrNothing.state,
|
|
708
977
|
freeRounds.isEngaged,
|
|
709
|
-
freeRounds.roundsRemaining,
|
|
710
978
|
handleDoubleOrNothingClick,
|
|
711
979
|
handleCashoutClick,
|
|
712
980
|
handleTogglePlayAmount,
|
|
@@ -714,33 +982,20 @@ const ManualPlayController = () => {
|
|
|
714
982
|
isRunningExternal,
|
|
715
983
|
onPlay,
|
|
716
984
|
roleButton,
|
|
985
|
+
t,
|
|
717
986
|
]);
|
|
718
|
-
|
|
719
|
-
// (Play Now at 35%, "Use Free Rounds" chip at 65%) instead of a full-width
|
|
720
|
-
// play button. Only when not cashing out and not in the play-amount view.
|
|
721
|
-
const showFreeRoundsRow = !displayPlayAmountView &&
|
|
722
|
-
!canCashout &&
|
|
723
|
-
freeRounds.isAvailable &&
|
|
724
|
-
!freeRounds.isEngaged;
|
|
725
|
-
// Engaging free rounds must be impossible while the controller is disabled or
|
|
726
|
-
// a (non-cashout) round is in progress — same signals that disable the play
|
|
727
|
-
// button. We deliberately exclude `!isValidPlayAmount` (the chip engages a
|
|
728
|
-
// grant, not the paid stake) and rely on `isDisabled()` /
|
|
729
|
-
// `disabledController` / `isPlaying` already in scope.
|
|
730
|
-
const isFreeRoundsAffordanceDisabled = isDisabled() ||
|
|
731
|
-
config.playOptions.disabledController ||
|
|
732
|
-
config.playOptions.isPlaying;
|
|
733
|
-
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()}`), showFreeRoundsRow ? (jsxs("div", { className: freeRoundsStyles.row, children: [jsx("div", { className: freeRoundsStyles.playSlot, children: renderButton }), jsx("div", { className: freeRoundsStyles.freeSlot, children: jsx(FreeRoundsAffordance, { isAvailable: freeRounds.isAvailable, isEngaged: freeRounds.isEngaged, roundsRemaining: freeRounds.roundsRemaining, totalRounds: freeRounds.totalRounds, onUseFreeRounds: freeRounds.onUseFreeRounds, disabled: isFreeRoundsAffordanceDisabled }) })] })) : (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] }));
|
|
734
988
|
};
|
|
735
989
|
|
|
736
|
-
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","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"};
|
|
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"};
|
|
737
991
|
|
|
738
992
|
var style_difficulty = {"base":"DifficultySelector-module__base___lDaQ-"};
|
|
739
993
|
|
|
740
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"};
|
|
741
995
|
|
|
742
996
|
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
743
|
-
const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, }) => {
|
|
997
|
+
const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled, isClassicGame = false, formatDisplay, }) => {
|
|
998
|
+
const display = (value) => formatDisplay ? formatDisplay(value) : capitalize(String(value));
|
|
744
999
|
const handleClick = (e) => {
|
|
745
1000
|
if (disabled) {
|
|
746
1001
|
return;
|
|
@@ -755,30 +1010,26 @@ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled,
|
|
|
755
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, {
|
|
756
1011
|
[style_select.disabled]: disabled && !isClassicGame,
|
|
757
1012
|
[style_select.disabledClassic]: disabled && isClassicGame,
|
|
758
|
-
}), style: { color: riskColor }, disabled: disabled, "data-difficulty-selector-button": true, children: [
|
|
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, {
|
|
759
1014
|
[style_select.selected]: String(currentValue) === String(value),
|
|
760
|
-
}), children:
|
|
1015
|
+
}), children: display(value) }, `element-${value}`))) })] })) })] }));
|
|
761
1016
|
};
|
|
762
1017
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
(function (RiskTypes) {
|
|
769
|
-
RiskTypes["LOW"] = "LOW";
|
|
770
|
-
RiskTypes["MEDIUM"] = "MEDIUM";
|
|
771
|
-
RiskTypes["HIGH"] = "HIGH";
|
|
772
|
-
})(RiskTypes || (RiskTypes = {}));
|
|
773
|
-
|
|
1018
|
+
const RISK_LABEL_KEY = {
|
|
1019
|
+
[RiskTypes.LOW]: "riskLow",
|
|
1020
|
+
[RiskTypes.MEDIUM]: "riskMedium",
|
|
1021
|
+
[RiskTypes.HIGH]: "riskHigh",
|
|
1022
|
+
};
|
|
774
1023
|
const DifficultySelector = ({ playOptions, dropdownConfig, isClassicGame = false, }) => {
|
|
1024
|
+
const { t } = useTranslation();
|
|
775
1025
|
const { risks, disabledMenu, currentRisk, onRiskChange } = playOptions;
|
|
776
|
-
const
|
|
1026
|
+
const riskColors = { ...DEFAULT_RISK_COLORS, ...dropdownConfig.riskColorConfig };
|
|
1027
|
+
const riskColor = useRef(riskColors[currentRisk ?? RiskTypes.LOW]);
|
|
777
1028
|
function onRiskSelected(value) {
|
|
778
|
-
riskColor.current =
|
|
1029
|
+
riskColor.current = riskColors[value];
|
|
779
1030
|
onRiskChange(value);
|
|
780
1031
|
}
|
|
781
|
-
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]) }) }));
|
|
782
1033
|
};
|
|
783
1034
|
|
|
784
1035
|
const ALLOWED_SEPARATORS = [",", "."];
|
|
@@ -801,7 +1052,7 @@ function cleanInputNumber(value) {
|
|
|
801
1052
|
return `${integerPart}.${decimalPart}`;
|
|
802
1053
|
}
|
|
803
1054
|
|
|
804
|
-
var styles$
|
|
1055
|
+
var styles$5 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
|
|
805
1056
|
|
|
806
1057
|
const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...restProps }) => {
|
|
807
1058
|
const handleChange = React.useCallback((event) => {
|
|
@@ -816,31 +1067,32 @@ const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...r
|
|
|
816
1067
|
event.target.value = clamped;
|
|
817
1068
|
onChange?.(event);
|
|
818
1069
|
}, [disabled, max, onChange]);
|
|
819
|
-
return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$
|
|
820
|
-
[styles$
|
|
821
|
-
[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,
|
|
822
1073
|
}) }));
|
|
823
1074
|
};
|
|
824
1075
|
|
|
825
|
-
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"};
|
|
826
1077
|
|
|
827
1078
|
const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
828
1079
|
const { config } = useAutoManualPlayState();
|
|
1080
|
+
const { t } = useTranslation();
|
|
829
1081
|
const { current } = config.currencyOptions;
|
|
830
1082
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
831
1083
|
const defaultClassName = useMemo(() => {
|
|
832
1084
|
if (current.code === Currency.GOLD) {
|
|
833
|
-
return styles$
|
|
1085
|
+
return styles$4.gold;
|
|
834
1086
|
}
|
|
835
1087
|
if (current.code === Currency.SWEEPS) {
|
|
836
|
-
return styles$
|
|
1088
|
+
return styles$4.sweeps;
|
|
837
1089
|
}
|
|
838
|
-
return styles$
|
|
1090
|
+
return styles$4.default;
|
|
839
1091
|
}, [current.code]);
|
|
840
|
-
const switcherClassName = useMemo(() => cx$1(styles$
|
|
841
|
-
[styles$
|
|
842
|
-
[styles$
|
|
843
|
-
[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,
|
|
844
1096
|
}), [defaultClassName, enabled, disabled]);
|
|
845
1097
|
useEffect(() => {
|
|
846
1098
|
const handleGlobalKeyDown = (event) => {
|
|
@@ -861,18 +1113,18 @@ const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
|
861
1113
|
};
|
|
862
1114
|
}, []);
|
|
863
1115
|
return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className: cx$1({
|
|
864
|
-
[styles$
|
|
865
|
-
[styles$
|
|
866
|
-
}), children: [jsx("span", { className: styles$
|
|
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 }) }) })] }) }));
|
|
867
1119
|
};
|
|
868
1120
|
|
|
869
|
-
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"};
|
|
870
1122
|
|
|
871
1123
|
const InputWithSwitch = ({ children, switcherConfig, disabled, className, isClassicGame = false, ...restProps }) => {
|
|
872
|
-
return (jsxs("div", { className: cx$1(styles$
|
|
873
|
-
[styles$
|
|
874
|
-
[styles$
|
|
875
|
-
}), children: [jsx(Switch, { ...switcherConfig }), jsx(Input, { ...restProps, className: cx$1(styles$
|
|
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 })] }));
|
|
876
1128
|
};
|
|
877
1129
|
|
|
878
1130
|
function getDefaultExportFromCjs (x) {
|
|
@@ -976,14 +1228,18 @@ const WidgetContainer = ({ state, widgets, displayPlayAmountView, }) => {
|
|
|
976
1228
|
return (jsxs("div", { className: cx("controllerWidgets"), children: [renderZone(left, "left"), renderZone(center, "center"), renderZone(right, "right")] }));
|
|
977
1229
|
};
|
|
978
1230
|
|
|
979
|
-
var styles$
|
|
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"};
|
|
980
1232
|
|
|
981
1233
|
const PlayValueList = () => {
|
|
982
1234
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
983
1235
|
const { config } = useAutoManualPlayState();
|
|
1236
|
+
const { t } = useTranslation();
|
|
984
1237
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
985
|
-
const { onChangeAmount, playAmount, totalBalance, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
|
|
1238
|
+
const { onChangeAmount, playAmount, totalBalance, autoPlay: { state: autoplayState }, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
|
|
986
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;
|
|
987
1243
|
if (!displayPlayAmountView) {
|
|
988
1244
|
return null;
|
|
989
1245
|
}
|
|
@@ -1014,6 +1270,9 @@ const PlayValueList = () => {
|
|
|
1014
1270
|
setForceRefresh(!forceRefresh);
|
|
1015
1271
|
};
|
|
1016
1272
|
const handleClick = (e) => {
|
|
1273
|
+
if (isAutoplayPlaying) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1017
1276
|
const el = e.target?.closest("[data-value]");
|
|
1018
1277
|
if (!el) {
|
|
1019
1278
|
return;
|
|
@@ -1027,39 +1286,43 @@ const PlayValueList = () => {
|
|
|
1027
1286
|
applyValue(value);
|
|
1028
1287
|
}
|
|
1029
1288
|
};
|
|
1030
|
-
return (jsx("div", { className: cx$1(styles$
|
|
1031
|
-
|
|
1032
|
-
|
|
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,
|
|
1033
1294
|
}), "data-value": playValue, role: "option", tabIndex: 0, "aria-selected": false, children: format(playValue, config.currencyOptions.current.decimals) }, `${config.currencyOptions.current.code}-${playValue}`))) }) }));
|
|
1034
1295
|
};
|
|
1035
1296
|
|
|
1036
|
-
var styles = {"backdrop":"FreeRoundsIntroModal-module__backdrop___qIgNx","panel":"FreeRoundsIntroModal-module__panel___-zQpe","
|
|
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"};
|
|
1037
1298
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
}
|
|
1044
|
-
if (coinType === COIN_TYPE_SWEEPS) {
|
|
1045
|
-
return "SC";
|
|
1046
|
-
}
|
|
1047
|
-
if (coinType === COIN_TYPE_GOLD) {
|
|
1048
|
-
return "GC";
|
|
1049
|
-
}
|
|
1050
|
-
return "";
|
|
1051
|
-
};
|
|
1052
|
-
// Centered "Play Now / Play Later" intro modal. Renders + fires callbacks only;
|
|
1053
|
-
// the SDK does no fetching. `onPlayNow` should engage free-round mode + close,
|
|
1054
|
-
// `onPlayLater` should close and stay on paid play.
|
|
1055
|
-
const FreeRoundsIntroModal = ({ open, roundsRemaining, stakeCents, coinType, currency, decimals = 2, onPlayNow, onPlayLater, }) => {
|
|
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();
|
|
1056
1305
|
const handleClose = () => onPlayLater?.();
|
|
1057
|
-
const
|
|
1058
|
-
const
|
|
1059
|
-
|
|
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") })] })] }) }) }));
|
|
1060
1322
|
};
|
|
1061
1323
|
|
|
1062
1324
|
const AutoManualPlayProvider = ({ children, config, }) => {
|
|
1325
|
+
const t = useMemo(() => createTranslator(resolveLanguage(config.language)), [config.language]);
|
|
1063
1326
|
const [mode, setMode] = useState(GAME_MODE.MANUAL);
|
|
1064
1327
|
const [autoplayState, setAutoplayState] = useState(AUTO_PLAY_STATE.IDLE);
|
|
1065
1328
|
const [doubleOrNothingState, setDoubleOrNothingState] = useState(DOUBLE_OR_NOTHING_STATE.IDLE);
|
|
@@ -1071,17 +1334,31 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1071
1334
|
const isControllerDisabled = config.playOptions.disabledController;
|
|
1072
1335
|
const isGamePlaying = config.playOptions.isPlaying;
|
|
1073
1336
|
const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
|
|
1074
|
-
// Free rounds are MANUAL-only. While a grant is engaged the
|
|
1075
|
-
// be able to flip into autoplay (which would silently drop
|
|
1076
|
-
// engaged state and let PAID autoplay start). This is
|
|
1077
|
-
// so it stays true even if a switch attempt slipped
|
|
1078
|
-
|
|
1079
|
-
|
|
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;
|
|
1080
1347
|
const isSimplified = config.withoutRiskAndAutoplay === true;
|
|
1081
1348
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
1082
1349
|
const togglePlayAmountView = useCallback(() => {
|
|
1083
1350
|
setDisplayPlayAmountView((v) => !v);
|
|
1084
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.
|
|
1356
|
+
useEffect(() => {
|
|
1357
|
+
if (!supportsGrantFundedAutoplay || !isFreeRoundsEngaged) {
|
|
1358
|
+
return;
|
|
1359
|
+
}
|
|
1360
|
+
setNumberOfPlays((prev) => prev > freeRoundsRemaining ? freeRoundsRemaining : prev);
|
|
1361
|
+
}, [supportsGrantFundedAutoplay, isFreeRoundsEngaged, freeRoundsRemaining]);
|
|
1085
1362
|
const startAutoplay = useCallback((numPlays) => {
|
|
1086
1363
|
setMode(GAME_MODE.AUTOPLAY);
|
|
1087
1364
|
setAutoplayState(AUTO_PLAY_STATE.SELECTING);
|
|
@@ -1121,12 +1398,15 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1121
1398
|
setSelection([]);
|
|
1122
1399
|
}, []);
|
|
1123
1400
|
const toggleMode = useCallback(() => {
|
|
1124
|
-
// No-op while a free-round grant is engaged
|
|
1125
|
-
//
|
|
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).
|
|
1126
1406
|
if (isGamePlaying ||
|
|
1127
1407
|
isControllerDisabled ||
|
|
1128
1408
|
isAutoplayPlaying ||
|
|
1129
|
-
|
|
1409
|
+
isModeLockedByFreeRounds) {
|
|
1130
1410
|
return;
|
|
1131
1411
|
}
|
|
1132
1412
|
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
@@ -1135,7 +1415,7 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1135
1415
|
isAutoplayPlaying,
|
|
1136
1416
|
isControllerDisabled,
|
|
1137
1417
|
isGamePlaying,
|
|
1138
|
-
|
|
1418
|
+
isModeLockedByFreeRounds,
|
|
1139
1419
|
]);
|
|
1140
1420
|
const updateSelection = useCallback((values) => {
|
|
1141
1421
|
if (!isAutoPlaying) {
|
|
@@ -1195,42 +1475,60 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1195
1475
|
displayPlayAmountView,
|
|
1196
1476
|
togglePlayAmountView,
|
|
1197
1477
|
]);
|
|
1198
|
-
return (jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, mode === GAME_MODE.MANUAL &&
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
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") })] }) }));
|
|
1233
1531
|
};
|
|
1234
1532
|
|
|
1235
|
-
export { AUTO_PLAY_STATE, AutoManualPlayProvider, DOUBLE_OR_NOTHING_STATE, FreeRoundsIntroModal, 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 };
|
|
1236
1534
|
//# sourceMappingURL=index.mjs.map
|