@enigma-lake/tower-play-controller-sdk 2.2.1 → 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 +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 +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 +497 -198
- 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
|
-
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"};
|
|
741
995
|
|
|
742
996
|
const capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
|
|
743
|
-
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));
|
|
744
999
|
const handleClick = (e) => {
|
|
745
1000
|
if (disabled) {
|
|
746
1001
|
return;
|
|
@@ -753,31 +1008,28 @@ const Selector = ({ currentValue, label, riskColor, values, onSelect, disabled,
|
|
|
753
1008
|
onSelect(value);
|
|
754
1009
|
};
|
|
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
|
-
[style_select.disabled]: disabled,
|
|
757
|
-
|
|
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, {
|
|
758
1014
|
[style_select.selected]: String(currentValue) === String(value),
|
|
759
|
-
}), children:
|
|
1015
|
+
}), children: display(value) }, `element-${value}`))) })] })) })] }));
|
|
760
1016
|
};
|
|
761
1017
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
(
|
|
768
|
-
|
|
769
|
-
RiskTypes["MEDIUM"] = "MEDIUM";
|
|
770
|
-
RiskTypes["HIGH"] = "HIGH";
|
|
771
|
-
})(RiskTypes || (RiskTypes = {}));
|
|
772
|
-
|
|
773
|
-
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();
|
|
774
1025
|
const { risks, disabledMenu, currentRisk, onRiskChange } = playOptions;
|
|
775
|
-
const
|
|
1026
|
+
const riskColors = { ...DEFAULT_RISK_COLORS, ...dropdownConfig.riskColorConfig };
|
|
1027
|
+
const riskColor = useRef(riskColors[currentRisk ?? RiskTypes.LOW]);
|
|
776
1028
|
function onRiskSelected(value) {
|
|
777
|
-
riskColor.current =
|
|
1029
|
+
riskColor.current = riskColors[value];
|
|
778
1030
|
onRiskChange(value);
|
|
779
1031
|
}
|
|
780
|
-
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]) }) }));
|
|
781
1033
|
};
|
|
782
1034
|
|
|
783
1035
|
const ALLOWED_SEPARATORS = [",", "."];
|
|
@@ -800,7 +1052,7 @@ function cleanInputNumber(value) {
|
|
|
800
1052
|
return `${integerPart}.${decimalPart}`;
|
|
801
1053
|
}
|
|
802
1054
|
|
|
803
|
-
var styles$
|
|
1055
|
+
var styles$5 = {"base":"Input-module__base___IifiA","disabled":"Input-module__disabled___WqyKa","disabledClassic":"Input-module__disabledClassic___hzsCt"};
|
|
804
1056
|
|
|
805
1057
|
const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...restProps }) => {
|
|
806
1058
|
const handleChange = React.useCallback((event) => {
|
|
@@ -815,31 +1067,32 @@ const Input = ({ onChange, disabled, className, max, isClassicGame = false, ...r
|
|
|
815
1067
|
event.target.value = clamped;
|
|
816
1068
|
onChange?.(event);
|
|
817
1069
|
}, [disabled, max, onChange]);
|
|
818
|
-
return (jsx("input", { ...restProps, type: "text", max: max, inputMode: "numeric", pattern: "\\d*", maxLength: 2, disabled: disabled, onChange: handleChange, className: cx$1(styles$
|
|
819
|
-
[styles$
|
|
820
|
-
[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,
|
|
821
1073
|
}) }));
|
|
822
1074
|
};
|
|
823
1075
|
|
|
824
|
-
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"};
|
|
825
1077
|
|
|
826
1078
|
const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
827
1079
|
const { config } = useAutoManualPlayState();
|
|
1080
|
+
const { t } = useTranslation();
|
|
828
1081
|
const { current } = config.currencyOptions;
|
|
829
1082
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
830
1083
|
const defaultClassName = useMemo(() => {
|
|
831
1084
|
if (current.code === Currency.GOLD) {
|
|
832
|
-
return styles$
|
|
1085
|
+
return styles$4.gold;
|
|
833
1086
|
}
|
|
834
1087
|
if (current.code === Currency.SWEEPS) {
|
|
835
|
-
return styles$
|
|
1088
|
+
return styles$4.sweeps;
|
|
836
1089
|
}
|
|
837
|
-
return styles$
|
|
1090
|
+
return styles$4.default;
|
|
838
1091
|
}, [current.code]);
|
|
839
|
-
const switcherClassName = useMemo(() => cx$1(styles$
|
|
840
|
-
[styles$
|
|
841
|
-
[styles$
|
|
842
|
-
[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,
|
|
843
1096
|
}), [defaultClassName, enabled, disabled]);
|
|
844
1097
|
useEffect(() => {
|
|
845
1098
|
const handleGlobalKeyDown = (event) => {
|
|
@@ -860,18 +1113,18 @@ const Switch = ({ enabled, onSwitch, disabled, isPlaying, }) => {
|
|
|
860
1113
|
};
|
|
861
1114
|
}, []);
|
|
862
1115
|
return (jsx(Switch$1, { checked: enabled, onChange: onSwitch, as: Fragment$1, disabled: isPlaying, children: jsxs("div", { className: cx$1({
|
|
863
|
-
[styles$
|
|
864
|
-
[styles$
|
|
865
|
-
}), 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 }) }) })] }) }));
|
|
866
1119
|
};
|
|
867
1120
|
|
|
868
|
-
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"};
|
|
869
1122
|
|
|
870
1123
|
const InputWithSwitch = ({ children, switcherConfig, disabled, className, isClassicGame = false, ...restProps }) => {
|
|
871
|
-
return (jsxs("div", { className: cx$1(styles$
|
|
872
|
-
[styles$
|
|
873
|
-
[styles$
|
|
874
|
-
}), 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 })] }));
|
|
875
1128
|
};
|
|
876
1129
|
|
|
877
1130
|
function getDefaultExportFromCjs (x) {
|
|
@@ -975,14 +1228,18 @@ const WidgetContainer = ({ state, widgets, displayPlayAmountView, }) => {
|
|
|
975
1228
|
return (jsxs("div", { className: cx("controllerWidgets"), children: [renderZone(left, "left"), renderZone(center, "center"), renderZone(right, "right")] }));
|
|
976
1229
|
};
|
|
977
1230
|
|
|
978
|
-
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"};
|
|
979
1232
|
|
|
980
1233
|
const PlayValueList = () => {
|
|
981
1234
|
const [forceRefresh, setForceRefresh] = useState(false);
|
|
982
1235
|
const { config } = useAutoManualPlayState();
|
|
1236
|
+
const { t } = useTranslation();
|
|
983
1237
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
984
|
-
const { onChangeAmount, playAmount, totalBalance, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
|
|
1238
|
+
const { onChangeAmount, playAmount, totalBalance, autoPlay: { state: autoplayState }, playValues: { displayPlayAmountView, togglePlayAmountView }, } = usePlayController();
|
|
985
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;
|
|
986
1243
|
if (!displayPlayAmountView) {
|
|
987
1244
|
return null;
|
|
988
1245
|
}
|
|
@@ -1013,6 +1270,9 @@ const PlayValueList = () => {
|
|
|
1013
1270
|
setForceRefresh(!forceRefresh);
|
|
1014
1271
|
};
|
|
1015
1272
|
const handleClick = (e) => {
|
|
1273
|
+
if (isAutoplayPlaying) {
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1016
1276
|
const el = e.target?.closest("[data-value]");
|
|
1017
1277
|
if (!el) {
|
|
1018
1278
|
return;
|
|
@@ -1026,39 +1286,43 @@ const PlayValueList = () => {
|
|
|
1026
1286
|
applyValue(value);
|
|
1027
1287
|
}
|
|
1028
1288
|
};
|
|
1029
|
-
return (jsx("div", { className: cx$1(styles$
|
|
1030
|
-
|
|
1031
|
-
|
|
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,
|
|
1032
1294
|
}), "data-value": playValue, role: "option", tabIndex: 0, "aria-selected": false, children: format(playValue, config.currencyOptions.current.decimals) }, `${config.currencyOptions.current.code}-${playValue}`))) }) }));
|
|
1033
1295
|
};
|
|
1034
1296
|
|
|
1035
|
-
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"};
|
|
1036
1298
|
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
if (coinType === COIN_TYPE_SWEEPS) {
|
|
1044
|
-
return "SC";
|
|
1045
|
-
}
|
|
1046
|
-
if (coinType === COIN_TYPE_GOLD) {
|
|
1047
|
-
return "GC";
|
|
1048
|
-
}
|
|
1049
|
-
return "";
|
|
1050
|
-
};
|
|
1051
|
-
// Centered "Play Now / Play Later" intro modal. Renders + fires callbacks only;
|
|
1052
|
-
// the SDK does no fetching. `onPlayNow` should engage free-round mode + close,
|
|
1053
|
-
// `onPlayLater` should close and stay on paid play.
|
|
1054
|
-
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();
|
|
1055
1305
|
const handleClose = () => onPlayLater?.();
|
|
1056
|
-
const
|
|
1057
|
-
const
|
|
1058
|
-
|
|
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") })] })] }) }) }));
|
|
1059
1322
|
};
|
|
1060
1323
|
|
|
1061
1324
|
const AutoManualPlayProvider = ({ children, config, }) => {
|
|
1325
|
+
const t = useMemo(() => createTranslator(resolveLanguage(config.language)), [config.language]);
|
|
1062
1326
|
const [mode, setMode] = useState(GAME_MODE.MANUAL);
|
|
1063
1327
|
const [autoplayState, setAutoplayState] = useState(AUTO_PLAY_STATE.IDLE);
|
|
1064
1328
|
const [doubleOrNothingState, setDoubleOrNothingState] = useState(DOUBLE_OR_NOTHING_STATE.IDLE);
|
|
@@ -1070,17 +1334,31 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1070
1334
|
const isControllerDisabled = config.playOptions.disabledController;
|
|
1071
1335
|
const isGamePlaying = config.playOptions.isPlaying;
|
|
1072
1336
|
const isAutoplayPlaying = autoplayState === AUTO_PLAY_STATE.PLAYING;
|
|
1073
|
-
// Free rounds are MANUAL-only. While a grant is engaged the
|
|
1074
|
-
// be able to flip into autoplay (which would silently drop
|
|
1075
|
-
// engaged state and let PAID autoplay start). This is
|
|
1076
|
-
// so it stays true even if a switch attempt slipped
|
|
1077
|
-
|
|
1078
|
-
|
|
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;
|
|
1079
1347
|
const isSimplified = config.withoutRiskAndAutoplay === true;
|
|
1080
1348
|
const isClassicGame = Boolean(config?.isClassicGame || config?.classicGame?.forceLeftStyle);
|
|
1081
1349
|
const togglePlayAmountView = useCallback(() => {
|
|
1082
1350
|
setDisplayPlayAmountView((v) => !v);
|
|
1083
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]);
|
|
1084
1362
|
const startAutoplay = useCallback((numPlays) => {
|
|
1085
1363
|
setMode(GAME_MODE.AUTOPLAY);
|
|
1086
1364
|
setAutoplayState(AUTO_PLAY_STATE.SELECTING);
|
|
@@ -1120,12 +1398,15 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1120
1398
|
setSelection([]);
|
|
1121
1399
|
}, []);
|
|
1122
1400
|
const toggleMode = useCallback(() => {
|
|
1123
|
-
// No-op while a free-round grant is engaged
|
|
1124
|
-
//
|
|
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).
|
|
1125
1406
|
if (isGamePlaying ||
|
|
1126
1407
|
isControllerDisabled ||
|
|
1127
1408
|
isAutoplayPlaying ||
|
|
1128
|
-
|
|
1409
|
+
isModeLockedByFreeRounds) {
|
|
1129
1410
|
return;
|
|
1130
1411
|
}
|
|
1131
1412
|
setNumberOfPlays(AUTOPLAY_DEFAULT_PLAY_ROUNDS_COUNT);
|
|
@@ -1134,7 +1415,7 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1134
1415
|
isAutoplayPlaying,
|
|
1135
1416
|
isControllerDisabled,
|
|
1136
1417
|
isGamePlaying,
|
|
1137
|
-
|
|
1418
|
+
isModeLockedByFreeRounds,
|
|
1138
1419
|
]);
|
|
1139
1420
|
const updateSelection = useCallback((values) => {
|
|
1140
1421
|
if (!isAutoPlaying) {
|
|
@@ -1194,42 +1475,60 @@ const AutoManualPlayProvider = ({ children, config, }) => {
|
|
|
1194
1475
|
displayPlayAmountView,
|
|
1195
1476
|
togglePlayAmountView,
|
|
1196
1477
|
]);
|
|
1197
|
-
return (jsxs(AutoManualPlayStateContext.Provider, { value: contextValue, children: [typeof children === "function" ? children(contextValue) : children, mode === GAME_MODE.MANUAL &&
|
|
1198
|
-
|
|
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
|
-
|
|
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") })] }) }));
|
|
1232
1531
|
};
|
|
1233
1532
|
|
|
1234
|
-
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 };
|
|
1235
1534
|
//# sourceMappingURL=index.mjs.map
|