diff --git a/src/components/TopUpModal.tsx b/src/components/TopUpModal.tsx index 7f72688..81c4360 100644 --- a/src/components/TopUpModal.tsx +++ b/src/components/TopUpModal.tsx @@ -41,7 +41,6 @@ interface TopUpModalProps { export default function TopUpModal({ method, onClose, initialAmountRubles }: TopUpModalProps) { const { t } = useTranslation() const { formatAmount, currencySymbol, convertAmount, convertToRub, targetCurrency } = useCurrency() - const containerRef = useRef(null) const inputRef = useRef(null) const getInitialAmount = (): string => { @@ -58,7 +57,7 @@ export default function TopUpModal({ method, onClose, initialAmountRubles }: Top const [selectedOption, setSelectedOption] = useState( method.options && method.options.length > 0 ? method.options[0].id : null ) - const [isKeyboardOpen, setIsKeyboardOpen] = useState(false) + const [isInputFocused, setIsInputFocused] = useState(false) const popupRef = useRef(null) const hasOptions = method.options && method.options.length > 0 @@ -69,31 +68,6 @@ export default function TopUpModal({ method, onClose, initialAmountRubles }: Top const methodName = t(`balance.paymentMethods.${methodKey}.name`, { defaultValue: '' }) || method.name const isTelegramMiniApp = typeof window !== 'undefined' && Boolean(window.Telegram?.WebApp?.initData) - // Detect keyboard open/close - useEffect(() => { - const handleFocus = () => setIsKeyboardOpen(true) - const handleBlur = () => setTimeout(() => setIsKeyboardOpen(false), 100) - - const input = inputRef.current - if (input) { - input.addEventListener('focus', handleFocus) - input.addEventListener('blur', handleBlur) - return () => { - input.removeEventListener('focus', handleFocus) - input.removeEventListener('blur', handleBlur) - } - } - }, []) - - // Scroll to input when keyboard opens - useEffect(() => { - if (isKeyboardOpen && containerRef.current && inputRef.current) { - setTimeout(() => { - inputRef.current?.scrollIntoView({ behavior: 'smooth', block: 'center' }) - }, 150) - } - }, [isKeyboardOpen]) - const starsPaymentMutation = useMutation({ mutationFn: (amountKopeks: number) => balanceApi.createStarsInvoice(amountKopeks), onSuccess: (data) => { @@ -172,25 +146,23 @@ export default function TopUpModal({ method, onClose, initialAmountRubles }: Top const inputStep = currencyDecimals === 0 ? 1 : 0.01 const isPending = topUpMutation.isPending || starsPaymentMutation.isPending - return ( -
-
+ // When input is focused in Telegram Mini App, position modal at top + const modalPosition = isInputFocused && isTelegramMiniApp ? 'items-start pt-2' : 'items-end sm:items-center' -
- {/* Compact Header */} -
+ return ( +
+
{ inputRef.current?.blur(); onClose() }} /> + +
+ {/* Header - hide when keyboard open on mobile */} +
-
-

{methodName}

-
+

{methodName}

- {/* Scrollable Content */} -
-
- {/* Payment options - compact grid */} - {hasOptions && method.options && ( -
- {method.options.map((option) => ( + {/* Content */} +
+ {/* Payment options - hide when keyboard open */} + {hasOptions && method.options && !isInputFocused && ( +
+ {method.options.map((option) => ( + + ))} +
+ )} + + {/* Amount input */} +
+ setAmount(e.target.value)} + onFocus={() => setIsInputFocused(true)} + onBlur={() => setTimeout(() => setIsInputFocused(false), 150)} + placeholder={`${formatAmount(minRubles, 0)} – ${formatAmount(maxRubles, 0)}`} + step={inputStep} + className="w-full h-14 px-4 pr-16 text-xl font-semibold bg-dark-800 border border-dark-700 rounded-xl text-dark-100 placeholder:text-dark-500 focus:outline-none focus:border-accent-500 focus:ring-1 focus:ring-accent-500" + autoComplete="off" + /> + + {currencySymbol} + +
+ + {/* Quick amounts - always show, they help dismiss keyboard */} + {quickAmounts.length > 0 && ( +
+ {quickAmounts.map((a) => { + const quickValue = getQuickAmountValue(a) + const isSelected = amount === quickValue + return ( - ))} -
- )} - - {/* Amount input with currency */} -
- setAmount(e.target.value)} - placeholder={`${formatAmount(minRubles, 0)} – ${formatAmount(maxRubles, 0)}`} - step={inputStep} - className="w-full h-14 px-4 pr-16 text-xl font-semibold bg-dark-800 border border-dark-700 rounded-xl text-dark-100 placeholder:text-dark-500 focus:outline-none focus:border-accent-500 focus:ring-1 focus:ring-accent-500" - autoComplete="off" - /> - - {currencySymbol} - + ) + })}
+ )} - {/* Quick amounts - compact */} - {quickAmounts.length > 0 && ( -
- {quickAmounts.map((a) => { - const quickValue = getQuickAmountValue(a) - const isSelected = amount === quickValue - return ( - - ) - })} -
- )} + {/* Error - hide when keyboard open */} + {error && !isInputFocused && ( +
+ {error} +
+ )} - {/* Error */} - {error && ( -
- {error} -
- )} -
-
- - {/* Fixed Footer with Submit */} -
+ {/* Submit button - always visible */} - + + {/* Cancel - hide when keyboard open */} + {!isInputFocused && ( + + )}
+ + {/* Safe area for iPhone */} +
)