feat: публичные лендинг-страницы для быстрой покупки VPN-подписок

- QuickPurchase: публичная страница покупки с выбором тарифа/периода/оплаты
- PurchaseSuccess: статус покупки с поллингом и копированием ссылки
- AdminLandings: управление лендингами (создание, сортировка, удаление)
- AdminLandingEditor: полнофункциональный редактор лендинга
- API-клиент landings.ts для всех эндпоинтов
- Роутинг /buy/:slug и /buy/success/:token
- Локализация ru/en/zh/fa для всех текстов лендинга
- Санитизация HTML/CSS, rate limit защита, DOMPurify
This commit is contained in:
Fringg
2026-03-06 07:02:21 +03:00
parent e278fec506
commit 8b5d777f0a
16 changed files with 3813 additions and 766 deletions

View File

@@ -236,15 +236,24 @@ export default function SubscriptionCardActive({
{t('dashboard.connectDevice')}
</div>
<div className="mt-0.5 text-[11px] text-dark-50/30">
{t('dashboard.devicesOfMax', {
used: connectedDevices,
max: subscription.device_limit,
})}
{subscription.device_limit === 0
? t('dashboard.devicesConnectedUnlimited', { used: connectedDevices })
: t('dashboard.devicesOfMax', {
used: connectedDevices,
max: subscription.device_limit,
})}
</div>
</div>
{/* Device indicator */}
{subscription.device_limit <= 10 ? (
{subscription.device_limit === 0 ? (
<div
className="flex flex-shrink-0 items-center text-lg text-dark-50/40"
aria-hidden="true"
>
</div>
) : subscription.device_limit <= 10 ? (
<div className="flex flex-shrink-0 gap-1.5" aria-hidden="true">
{Array.from({ length: subscription.device_limit }, (_, i) => (
<div

View File

@@ -175,7 +175,10 @@ export default function TrialOfferCard({
value: trialInfo.traffic_limit_gb === 0 ? '∞' : String(trialInfo.traffic_limit_gb),
label: t('common.units.gb'),
},
{ value: String(trialInfo.device_limit), label: t('subscription.trial.devices') },
{
value: trialInfo.device_limit === 0 ? '∞' : String(trialInfo.device_limit),
label: t('subscription.trial.devices'),
},
].map((stat, i) => (
<div key={i} className="text-center">
<div className="text-4xl font-extrabold leading-none tracking-tight text-dark-50">