Add files via upload

This commit is contained in:
Egor
2026-01-19 00:53:18 +03:00
committed by GitHub
parent 2b57afbc4f
commit 981e33ecfa
2 changed files with 245 additions and 124 deletions

View File

@@ -145,55 +145,93 @@ function PaymentMethodModal({ paymentMethods, onSelect, onClose }: PaymentMethod
const { formatAmount, currencySymbol } = useCurrency()
return (
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center z-50 p-4">
<div className="card max-w-lg w-full animate-slide-up max-h-[80vh] overflow-y-auto">
<div className="flex justify-between items-center mb-6">
<h2 className="text-lg font-semibold text-dark-100">{t('balance.selectPaymentMethod')}</h2>
<button onClick={onClose} className="btn-icon">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<div className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-end sm:items-center justify-center">
{/* Backdrop click to close */}
<div className="absolute inset-0" onClick={onClose} />
{/* Modal content - bottom sheet on mobile, centered on desktop */}
<div className="relative w-full sm:max-w-lg sm:mx-4 bg-dark-900 sm:rounded-2xl rounded-t-2xl border border-dark-700/50 shadow-2xl animate-slide-up max-h-[85vh] flex flex-col">
{/* Header */}
<div className="flex-shrink-0 flex justify-between items-center p-4 sm:p-5 border-b border-dark-800">
<div>
<h2 className="text-lg font-semibold text-dark-100">{t('balance.selectPaymentMethod')}</h2>
<p className="text-sm text-dark-400 mt-0.5">{t('balance.topUpBalance')}</p>
</div>
<button
onClick={onClose}
className="w-10 h-10 rounded-xl bg-dark-800 hover:bg-dark-700 flex items-center justify-center transition-colors"
>
<svg className="w-5 h-5 text-dark-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{!paymentMethods ? (
<div className="flex items-center justify-center py-12">
<div className="w-8 h-8 border-2 border-accent-500 border-t-transparent rounded-full animate-spin" />
</div>
) : paymentMethods.length === 0 ? (
<div className="text-center py-8 text-dark-400">
{t('balance.noPaymentMethods')}
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{paymentMethods.map((method) => {
const methodKey = method.id.toLowerCase().replace(/-/g, '_')
const translatedName = t(`balance.paymentMethods.${methodKey}.name`, { defaultValue: '' })
const translatedDesc = t(`balance.paymentMethods.${methodKey}.description`, { defaultValue: '' })
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto overscroll-contain p-4 sm:p-5">
{!paymentMethods ? (
<div className="flex items-center justify-center py-12">
<div className="w-8 h-8 border-2 border-accent-500 border-t-transparent rounded-full animate-spin" />
</div>
) : paymentMethods.length === 0 ? (
<div className="text-center py-8 text-dark-400">
{t('balance.noPaymentMethods')}
</div>
) : (
<div className="space-y-2">
{paymentMethods.map((method) => {
const methodKey = method.id.toLowerCase().replace(/-/g, '_')
const translatedName = t(`balance.paymentMethods.${methodKey}.name`, { defaultValue: '' })
const translatedDesc = t(`balance.paymentMethods.${methodKey}.description`, { defaultValue: '' })
return (
<button
key={method.id}
disabled={!method.is_available}
onClick={() => method.is_available && onSelect(method)}
className={`p-4 rounded-xl border text-left transition-all ${
method.is_available
? 'border-dark-700/50 hover:border-accent-500/50 bg-dark-800/30 cursor-pointer hover:bg-dark-800/50'
: 'border-dark-800/30 bg-dark-900/30 opacity-50 cursor-not-allowed'
}`}
>
<div className="font-semibold text-dark-100">{translatedName || method.name}</div>
{(translatedDesc || method.description) && (
<div className="text-sm text-dark-500 mt-1">{translatedDesc || method.description}</div>
)}
<div className="text-xs text-dark-600 mt-2">
{formatAmount(method.min_amount_kopeks / 100, 0)} {formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
</div>
</button>
)
})}
</div>
)}
return (
<button
key={method.id}
disabled={!method.is_available}
onClick={() => method.is_available && onSelect(method)}
className={`w-full p-4 rounded-xl border text-left transition-all flex items-center gap-4 ${
method.is_available
? 'border-dark-700/50 hover:border-accent-500/50 bg-dark-800/30 cursor-pointer hover:bg-dark-800/50 active:bg-dark-700/50'
: 'border-dark-800/30 bg-dark-900/30 opacity-50 cursor-not-allowed'
}`}
>
{/* Icon */}
<div className="w-12 h-12 rounded-xl bg-dark-800 flex items-center justify-center flex-shrink-0">
<svg className="w-6 h-6 text-accent-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 002.25-2.25V6.75A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25v10.5A2.25 2.25 0 004.5 19.5z" />
</svg>
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<div className="font-semibold text-dark-100">{translatedName || method.name}</div>
{(translatedDesc || method.description) && (
<div className="text-sm text-dark-500 mt-0.5 truncate">{translatedDesc || method.description}</div>
)}
<div className="text-xs text-dark-600 mt-1">
{formatAmount(method.min_amount_kopeks / 100, 0)} {formatAmount(method.max_amount_kopeks / 100, 0)} {currencySymbol}
</div>
</div>
{/* Arrow */}
<svg className="w-5 h-5 text-dark-500 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
)
})}
</div>
)}
</div>
{/* Footer */}
<div className="flex-shrink-0 px-4 sm:px-5 pt-4 sm:pt-5 safe-area-inset-bottom border-t border-dark-800 bg-dark-900">
<button
type="button"
onClick={onClose}
className="btn-secondary w-full h-12 text-base"
>
{t('common.cancel')}
</button>
</div>
</div>
</div>
)