redisign tokens paytment

This commit is contained in:
smanylov
2026-03-11 12:21:56 +07:00
parent fa60a79241
commit e8f4f9939f
4 changed files with 151 additions and 36 deletions
+91
View File
@@ -298,6 +298,97 @@
} }
} }
} }
.token-package {
background: #f8fafc;
border: 2px solid #e2e8f0;
border-radius: 16px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
overflow: hidden;
&:hover {
transform: translateY(-4px) scale(1.02);
border-color: #6366f1;
box-shadow: 0 12px 30px rgba(99, 102, 241, 0.2);
&::before {
left: 100%;
}
}
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(99, 102, 241, 0.1), transparent);
transition: left 0.5s ease;
}
&.selected {
border-color: #10b981;
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
transform: scale(1.05);
}
.token-package-amount {
font-size: 1.8rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 6px;
}
.token-package-text {
font-size: 0.85rem;
color: #64748b;
margin-bottom: 8px;
}
.token-package-price {
font-size: 1.3rem;
font-weight: 600;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 8px;
}
.token-package-detail {
color: #64748b;
font-size: 0.8rem;
margin-top: 6px;
}
}
.plan-button {
width: 100%;
max-width: 280px;
padding: 12px 18px;
border: none;
border-radius: 12px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
display: block;
margin: 0 auto;
}
.selected-token-info {
margin-top: 8px;
color: #64748b;
font-size: 0.85rem;
text-align: center;
}
} }
.secure-payments-wrapper { .secure-payments-wrapper {
+54 -34
View File
@@ -1,7 +1,7 @@
'use client' 'use client'
import { fetchTariffs, fetchTokensBundle } from '@/app/actions/action'; import { fetchTariffs, fetchTokensBundle } from '@/app/actions/action';
import {useQuery, useQueryClient} from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { convertBytes } from '@/app/lib/convertBytes'; import { convertBytes } from '@/app/lib/convertBytes';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { yooKasaCreatePayment } from '@/app/actions/yooKassa'; import { yooKasaCreatePayment } from '@/app/actions/yooKassa';
@@ -54,6 +54,7 @@ export default function PaymentTubBuyTokens() {
const [showWidget, setShowWidget] = useState<boolean>(false); const [showWidget, setShowWidget] = useState<boolean>(false);
const [yooMoneyConfig, setYooMoneyConfig] = useState<YooMoneyCheckoutWidgetConfig | null>(null); const [yooMoneyConfig, setYooMoneyConfig] = useState<YooMoneyCheckoutWidgetConfig | null>(null);
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [selectedTokenPackage, setSelectedTokenPackage] = useState<Tariff | null>(null);
async function createPayment(value: number, tokensBundle: number) { async function createPayment(value: number, tokensBundle: number) {
if (isProcessing) return; if (isProcessing) return;
@@ -175,50 +176,69 @@ export default function PaymentTubBuyTokens() {
setShowWidget(false); setShowWidget(false);
} }
function selectHandler(item: Tariff) {
console.log(item);
setSelectedTokenPackage(item);
console.log(selectedTokenPackage);
}
return ( return (
<> <>
<ModalWindow state={openWindow} callBack={closeModal}> <ModalWindow state={openWindow} callBack={closeModal}>
<PaymentConfirmationModal /> <PaymentConfirmationModal />
</ModalWindow> </ModalWindow>
<div className="tab-content"> <div className="tab-content block-wrapper">
<div className="billing-toggle" style={{ display: 'none' }}>
<div>
<button className="billing-btn active">💳 Помесячно</button>
<button className="billing-btn">
🔥 Годовая
<span>ВЫГОДНО</span>
</button>
</div>
</div>
<div className="subscription-grid"> <div className="subscription-grid">
{tokensPaymentData?.map((item: { {tokensPaymentData?.map((item: Tariff) => (
id: number; <div
name: string; className={`token-package ${selectedTokenPackage?.id === item.id ? 'selected' : ''}`}
price: number; key={item.id}
tokens: number; onClick={(() => {
maxFilesCount: number; selectHandler(item);
diskSize: number; })}
}) => ( >
<div className="plan-card animate-card" key={item.id}> <div
<div className="plan-header"> className="token-package-amount"
<div className="plan-price">{item.price ?? 0} </div> >
<div className="plan-tokens header"> {item.tokens ?? 0}
{item.tokens ?? 0}
<br />
токенов
</div>
</div> </div>
<div className="flex justify-center"> <div
<button className="token-package-text"
className="btn btn-primary" >
onClick={() => openPaymentWindow(item.price, item.name, item.tokens, item.id)} {t('tokens')}
> </div>
{t('buy')} <div className="token-package-price">{item.price ?? 0} </div>
</button> <div className="token-package-detail">
~{Math.ceil((item.price / item.tokens) * 10) / 10 } за токен
</div> </div>
</div> </div>
))} ))}
</div> </div>
<div>
<button
className="plan-button btn-primary"
onClick={() => {
if (selectedTokenPackage) {
openPaymentWindow(selectedTokenPackage.price, selectedTokenPackage.name, selectedTokenPackage.tokens, selectedTokenPackage.id)
}
}}
disabled={selectedTokenPackage ? false : true}
>
{selectedTokenPackage ? `Купить за ${selectedTokenPackage.price}` : t('select-a-token-package')}
</button>
{selectedTokenPackage && (
<div
className="selected-token-info"
>
{t('package-selected-tokens-price', {
tokens: selectedTokenPackage.tokens,
price: selectedTokenPackage.price
})}
</div>
)}
</div>
</div> </div>
{/* Модальное окно с виджетом */} {/* Модальное окно с виджетом */}
+3 -1
View File
@@ -294,7 +294,9 @@
"there-are-not-enough-funds-to-protect-file": "There are not enough funds to protect file. {count} tokens required", "there-are-not-enough-funds-to-protect-file": "There are not enough funds to protect file. {count} tokens required",
"duplicate-files": "Duplicate files", "duplicate-files": "Duplicate files",
"monitoring-is-not-supported": "Monitoring is not supported", "monitoring-is-not-supported": "Monitoring is not supported",
"frequently-asked-questions": "Frequently Asked Questions" "frequently-asked-questions": "Frequently Asked Questions",
"select-a-token-package": "Select a token package",
"package-selected-tokens-price": "Package selected: {tokens} tokens for {price}₽"
}, },
"Login-register-form": { "Login-register-form": {
"and": "and", "and": "and",
+3 -1
View File
@@ -294,7 +294,9 @@
"there-are-not-enough-funds-to-protect-file": "Для защиты файла недостаточно средств. Необходимо {count} токен(ов/а)", "there-are-not-enough-funds-to-protect-file": "Для защиты файла недостаточно средств. Необходимо {count} токен(ов/а)",
"duplicate-files": "Дубликаты файлов", "duplicate-files": "Дубликаты файлов",
"monitoring-is-not-supported": "Мониторинг не поддерживается", "monitoring-is-not-supported": "Мониторинг не поддерживается",
"frequently-asked-questions": "Часто задаваемые вопросы" "frequently-asked-questions": "Часто задаваемые вопросы",
"select-a-token-package": "Выберите пакет токенов",
"package-selected-tokens-price": "Выбран пакет: {tokens} токенов за {price}₽"
}, },
"Login-register-form": { "Login-register-form": {
"and": "и", "and": "и",