From 8068f847247307aa3adae1f6965987882be8a785 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Wed, 4 Feb 2026 16:53:58 +0300 Subject: [PATCH] fix: inline Stars confirmation and unified payment type display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace confirm dialog with inline UI panel to avoid browser popup blockers — pay button click is a direct user gesture - Pre-open browser window synchronously on pay click for fallback - Unify payment type display: show same styled panel whether one or both methods are enabled (single method = full-width button) - Remove getCostSubtitle text, add payStars translation key (ru, en, zh, fa) --- src/locales/en.json | 4 +- src/locales/fa.json | 4 +- src/locales/ru.json | 4 +- src/locales/zh.json | 4 +- src/pages/Wheel.tsx | 151 ++++++++++++++++++++++++-------------------- 5 files changed, 92 insertions(+), 75 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 0b5bf51..b4eabe4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -714,8 +714,8 @@ "starsPaymentFailed": "Payment failed. Please try again.", "starsPaymentRedirected": "Telegram will open for payment. Refresh the page after payment.", "starsNotAvailable": "Star payments are currently unavailable. Contact support.", - "confirmStarsTitle": "Star payment", - "confirmStarsPayment": "You will be redirected to Telegram to pay for one wheel spin. Continue?", + "confirmStarsPayment": "You will be redirected to Telegram to pay for one wheel spin.", + "payStars": "Pay {{count}} ⭐", "youWon": "You won", "noPrize": "No luck this time...", "noHistory": "No spin history yet", diff --git a/src/locales/fa.json b/src/locales/fa.json index e5d0b8d..4eb2d39 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -600,8 +600,8 @@ "starsPaymentFailed": "پرداخت ناموفق. دوباره تلاش کنید.", "starsPaymentRedirected": "تلگرام برای پرداخت باز می‌شود. پس از پرداخت صفحه را بازخوانی کنید.", "starsNotAvailable": "پرداخت با ستاره در حال حاضر در دسترس نیست. با پشتیبانی تماس بگیرید.", - "confirmStarsTitle": "پرداخت با ستاره", - "confirmStarsPayment": "شما به تلگرام برای پرداخت یک چرخش منتقل خواهید شد. ادامه می‌دهید؟", + "confirmStarsPayment": "شما به تلگرام برای پرداخت یک چرخش منتقل خواهید شد.", + "payStars": "پرداخت {{count}} ⭐", "noHistory": "هنوز تاریخچه‌ای نیست", "banner": { "title": "شانس خود را امتحان کنید!", diff --git a/src/locales/ru.json b/src/locales/ru.json index c279001..4a48ae4 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -742,8 +742,8 @@ "starsPaymentFailed": "Ошибка оплаты. Попробуйте еще раз.", "starsPaymentRedirected": "Откроется Telegram для оплаты. После оплаты обновите страницу.", "starsNotAvailable": "Оплата звёздами сейчас недоступна. Обратитесь в поддержку.", - "confirmStarsTitle": "Оплата звёздами", - "confirmStarsPayment": "Вы будете перенаправлены в Telegram для оплаты одного вращения колеса. Продолжить?", + "confirmStarsPayment": "Вы будете перенаправлены в Telegram для оплаты одного вращения колеса.", + "payStars": "Оплатить {{count}} ⭐", "youWon": "Вы выиграли", "noPrize": "В этот раз не повезло...", "noHistory": "История вращений пуста", diff --git a/src/locales/zh.json b/src/locales/zh.json index 896a0cc..adbae2a 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -604,8 +604,8 @@ "starsPaymentFailed": "支付失败。请重试。", "starsPaymentRedirected": "将打开Telegram进行支付。支付后请刷新页面。", "starsNotAvailable": "星星支付暂不可用。请联系客服。", - "confirmStarsTitle": "星星支付", - "confirmStarsPayment": "您将被重定向到Telegram进行一次转盘支付。继续吗?", + "confirmStarsPayment": "您将被重定向到Telegram进行一次转盘支付。", + "payStars": "支付 {{count}} ⭐", "noHistory": "暂无抽奖记录", "banner": { "title": "试试运气!", diff --git a/src/pages/Wheel.tsx b/src/pages/Wheel.tsx index 4f269ee..6a2cca0 100644 --- a/src/pages/Wheel.tsx +++ b/src/pages/Wheel.tsx @@ -7,7 +7,6 @@ import WheelLegend from '../components/wheel/WheelLegend'; import InsufficientBalancePrompt from '../components/InsufficientBalancePrompt'; import { usePlatform, useHaptic } from '@/platform'; import { useNotify } from '@/platform/hooks/useNotify'; -import { useNativeDialog } from '@/platform/hooks/useNativeDialog'; import { Card } from '@/components/data-display/Card/Card'; import { Button } from '@/components/primitives/Button/Button'; import { motion, AnimatePresence } from 'framer-motion'; @@ -64,7 +63,6 @@ export default function Wheel() { const { openInvoice, capabilities } = usePlatform(); const haptic = useHaptic(); const notify = useNotify(); - const dialog = useNativeDialog(); const [isSpinning, setIsSpinning] = useState(false); const [targetRotation, setTargetRotation] = useState(null); @@ -74,6 +72,7 @@ export default function Wheel() { ); const [isPayingStars, setIsPayingStars] = useState(false); const [historyExpanded, setHistoryExpanded] = useState(false); + const [showStarsConfirm, setShowStarsConfirm] = useState(false); const paymentTypeInitialized = useRef(false); const { @@ -171,6 +170,7 @@ export default function Wheel() { const pendingStarsResultRef = useRef(null); const isStarsSpinRef = useRef(false); const pollingAbortRef = useRef(null); + const preOpenedWindowRef = useRef(null); // Cleanup polling on unmount useEffect(() => { @@ -272,9 +272,12 @@ export default function Wheel() { setIsPayingStars(false); } } else { - // Fallback: open invoice URL in browser (unsupported platform) + // Fallback: redirect pre-opened window to invoice URL setIsPayingStars(false); - window.open(data.invoice_url, '_blank', 'noopener,noreferrer'); + if (preOpenedWindowRef.current) { + preOpenedWindowRef.current.location.href = data.invoice_url; + preOpenedWindowRef.current = null; + } setSpinResult({ success: true, prize_id: null, @@ -292,6 +295,10 @@ export default function Wheel() { }, onError: () => { setIsPayingStars(false); + if (preOpenedWindowRef.current) { + preOpenedWindowRef.current.close(); + preOpenedWindowRef.current = null; + } setSpinResult({ success: false, prize_id: null, @@ -308,13 +315,13 @@ export default function Wheel() { }, }); - const handleDirectStarsPay = async () => { - const confirmed = await dialog.confirm( - t('wheel.confirmStarsPayment'), - t('wheel.confirmStarsTitle'), - ); - if (!confirmed) return; + const handleDirectStarsPay = () => { + setShowStarsConfirm(false); setIsPayingStars(true); + // In browser: pre-open window synchronously (direct user gesture) to avoid popup blocker + if (!capabilities.hasInvoice) { + preOpenedWindowRef.current = window.open('about:blank', '_blank') || null; + } starsInvoiceMutation.mutate(); }; @@ -359,7 +366,7 @@ export default function Wheel() { notify.warning(t('wheel.starsNotAvailable')); return; } - handleDirectStarsPay(); + setShowStarsConfirm(true); } else { handleSpin(); } @@ -460,20 +467,6 @@ export default function Wheel() { isPayingStars || (config.daily_limit > 0 && config.user_spins_today >= config.daily_limit); - // Build cost subtitle for the spin button - const getCostSubtitle = () => { - if (bothMethodsAvailable) return null; // toggle handles it - if (paymentType === 'telegram_stars' && starsEnabled) { - return `${config.spin_cost_stars} ⭐`; - } - if (paymentType === 'subscription_days' && daysEnabled) { - return t('wheel.days', { count: config.spin_cost_days ?? 0 }); - } - return null; - }; - - const costSubtitle = getCostSubtitle(); - return (
{/* Simple Header */} @@ -504,53 +497,77 @@ export default function Wheel() { {/* Spin Controls */}
- {/* Payment type toggle - only if both methods available */} - {bothMethodsAvailable && ( + {/* Payment type selector */} + {(starsEnabled || daysEnabled) && (
-
- - +
+ {starsEnabled && ( + + )} + {daysEnabled && ( + + )}
)} - {/* Single Spin Button */} - - - {/* Cost subtitle when no toggle */} - {costSubtitle && !bothMethodsAvailable && ( -

{costSubtitle}

+ {/* Stars confirmation panel */} + {showStarsConfirm && !isSpinning && !isPayingStars ? ( +
+

+ {t('wheel.confirmStarsPayment')} +

+
+ + +
+
+ ) : ( + /* Single Spin Button */ + )} {/* Cannot spin hint */}