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

@@ -235,13 +235,13 @@
"connectDevice": "Connect Device",
"devicesConnected": "{{count}} connected",
"devicesOfMax": "{{used}} of {{max}} connected",
"devicesConnectedUnlimited": "{{used}} connected · unlimited",
"devicesShort": "dev.",
"trafficUsage": "{{used}} / {{limit}} GB",
"trafficUsageTitle": "Traffic Usage",
"usedTraffic": "used {{amount}}",
"usedSuffix": "used",
"unlimited": "Unlimited",
"unlimitedTraffic": "Unlimited traffic",
"tariff": "Tariff",
"validUntil": "until {{date}}",
"remaining": "Remaining",
@@ -1043,7 +1043,8 @@
"roleAssign": "Role Assignment",
"policies": "Access Policies",
"auditLog": "Audit Log",
"salesStats": "Sales Statistics"
"salesStats": "Sales Statistics",
"landings": "Landings"
},
"panel": {
"title": "Admin Panel",
@@ -1076,7 +1077,8 @@
"roleAssignDesc": "Assign and revoke user roles",
"policiesDesc": "Configure ABAC access policies",
"auditLogDesc": "Review system activity and access history",
"salesStatsDesc": "Sales analytics and trends"
"salesStatsDesc": "Sales analytics and trends",
"landingsDesc": "Quick purchase landing pages"
},
"salesStats": {
"title": "Sales Statistics",
@@ -3138,6 +3140,51 @@
"loadFailed": "Failed to load audit log",
"retry": "Try again"
}
},
"landings": {
"title": "Landing Pages",
"create": "Create Landing",
"edit": "Edit",
"slug": "URL Identifier",
"slugHint": "Lowercase, numbers and hyphens",
"pageTitle": "Title",
"subtitle": "Subtitle",
"footerText": "Footer (HTML)",
"features": "Features",
"addFeature": "Add",
"featureIcon": "Icon",
"featureTitle": "Title",
"featureDesc": "Description",
"tariffs": "Plans",
"selectTariffs": "Select plans",
"periods": "Periods",
"paymentMethods": "Payment Methods",
"addMethod": "Add method",
"methodId": "Method ID",
"methodName": "Name",
"methodDesc": "Description",
"methodIcon": "Icon URL",
"gifts": "Gifts",
"giftEnabled": "Gift purchases",
"customCss": "CSS",
"seo": "SEO",
"metaTitle": "Meta Title",
"metaDesc": "Meta Description",
"active": "Active",
"inactive": "Inactive",
"purchases": "purchases",
"copyUrl": "Copy URL",
"urlCopied": "URL copied",
"deleteConfirm": "Delete landing \"{{title}}\"?",
"created": "Landing created",
"updated": "Landing updated",
"deleted": "Landing deleted",
"saveOrder": "Save order",
"orderSaved": "Order saved",
"general": "General",
"content": "Content",
"save": "Save",
"back": "Back"
}
},
"adminUpdates": {
@@ -3865,5 +3912,44 @@
"error": "Failed to merge accounts. Please try again later.",
"expiresIn": "Expires in {{minutes}}",
"merging": "Merging..."
},
"landing": {
"notFound": "Page not found",
"forMe": "For me",
"asGift": "As a gift",
"contactLabel": "Your email or @username",
"yourContact": "Your email or @username",
"contactPlaceholder": "email@example.com or @username",
"contactHint": "Enter email or Telegram @username",
"recipientLabel": "Gift recipient",
"recipientPlaceholder": "Recipient email or @username",
"giftMessageLabel": "Greeting (optional)",
"giftMessagePlaceholder": "Write a greeting...",
"chooseTariff": "Choose a plan",
"devices": "devices",
"paymentMethod": "Payment method",
"processing": "Processing...",
"payButton": "Pay {{price}}",
"accessFor": "Access for {{period}}",
"purchaseError": "Failed to create payment",
"purchaseNotFound": "Purchase not found",
"awaitingPayment": "Awaiting payment",
"awaitingPaymentDesc": "Complete the payment in the opened window",
"purchaseSuccess": "Payment successful!",
"keySentTo": "Subscription key sent to {{contact}}",
"giftSentTo": "Gift sent to {{contact}}",
"purchaseFailed": "Payment failed",
"purchaseFailedDesc": "Payment was unsuccessful. Try again.",
"subscriptionLink": "Subscription link",
"daysAccess": "days of access",
"error": "Error",
"gb": "GB",
"selectedTariff": "Tariff",
"period": "Period",
"total": "Total",
"pay": "Pay",
"choosePeriod": "Choose period",
"copied": "Copied!",
"copyLink": "Copy link"
}
}

View File

@@ -866,7 +866,8 @@
"roleAssign": "تخصیص نقش",
"policies": "سیاست‌های دسترسی",
"auditLog": "گزارش بازرسی",
"salesStats": "آمار فروش"
"salesStats": "آمار فروش",
"landings": "صفحات فرود"
},
"panel": {
"title": "پنل مدیریت",
@@ -899,7 +900,8 @@
"roleAssignDesc": "تخصیص و لغو نقش‌های کاربران",
"policiesDesc": "تنظیم سیاست‌های کنترل دسترسی ABAC",
"auditLogDesc": "بررسی فعالیت سیستم و تاریخچه دسترسی",
"salesStatsDesc": "تحلیل فروش و روندها"
"salesStatsDesc": "تحلیل فروش و روندها",
"landingsDesc": "صفحات خرید سریع"
},
"salesStats": {
"title": "آمار فروش",
@@ -2864,6 +2866,51 @@
"loadFailed": "بارگذاری گزارش بازرسی ناموفق بود",
"retry": "تلاش مجدد"
}
},
"landings": {
"title": "صفحات فرود",
"create": "ایجاد صفحه فرود",
"edit": "ویرایش",
"slug": "شناسه URL",
"slugHint": "حروف کوچک، اعداد و خط تیره",
"pageTitle": "عنوان",
"subtitle": "زیرعنوان",
"footerText": "متن پاورقی (HTML)",
"features": "ویژگی‌ها",
"addFeature": "افزودن",
"featureIcon": "آیکون",
"featureTitle": "عنوان",
"featureDesc": "توضیحات",
"tariffs": "طرح‌ها",
"selectTariffs": "انتخاب طرح‌ها",
"periods": "دوره‌ها",
"paymentMethods": "روش‌های پرداخت",
"addMethod": "افزودن روش",
"methodId": "شناسه روش",
"methodName": "نام",
"methodDesc": "توضیحات",
"methodIcon": "URL آیکون",
"gifts": "هدایا",
"giftEnabled": "خرید هدیه",
"customCss": "CSS",
"seo": "SEO",
"metaTitle": "عنوان متا",
"metaDesc": "توضیحات متا",
"active": "فعال",
"inactive": "غیرفعال",
"purchases": "خرید",
"copyUrl": "کپی URL",
"urlCopied": "URL کپی شد",
"deleteConfirm": "حذف صفحه فرود «{{title}}»؟",
"created": "صفحه فرود ایجاد شد",
"updated": "صفحه فرود به‌روز شد",
"deleted": "صفحه فرود حذف شد",
"saveOrder": "ذخیره ترتیب",
"orderSaved": "ترتیب ذخیره شد",
"general": "عمومی",
"content": "محتوا",
"save": "ذخیره",
"back": "بازگشت"
}
},
"adminUpdates": {
@@ -3416,5 +3463,44 @@
"error": "ادغام ناموفق بود. لطفاً بعداً دوباره تلاش کنید.",
"expiresIn": "{{minutes}} دقیقه باقی مانده",
"merging": "در حال ادغام..."
},
"landing": {
"notFound": "صفحه یافت نشد",
"forMe": "برای خودم",
"asGift": "به عنوان هدیه",
"contactLabel": "ایمیل یا @نام کاربری شما",
"yourContact": "ایمیل یا @نام کاربری شما",
"contactPlaceholder": "email@example.com یا @username",
"contactHint": "ایمیل یا @نام کاربری تلگرام را وارد کنید",
"recipientLabel": "گیرنده هدیه",
"recipientPlaceholder": "ایمیل یا @نام کاربری گیرنده",
"giftMessageLabel": "پیام تبریک (اختیاری)",
"giftMessagePlaceholder": "یک پیام تبریک بنویسید...",
"chooseTariff": "یک طرح انتخاب کنید",
"devices": "دستگاه",
"paymentMethod": "روش پرداخت",
"processing": "در حال پردازش...",
"payButton": "پرداخت {{price}}",
"accessFor": "دسترسی برای {{period}}",
"purchaseError": "خطا در ایجاد پرداخت",
"purchaseNotFound": "خرید یافت نشد",
"awaitingPayment": "در انتظار پرداخت",
"awaitingPaymentDesc": "پرداخت را در پنجره باز شده تکمیل کنید",
"purchaseSuccess": "پرداخت موفق بود!",
"keySentTo": "کلید اشتراک به {{contact}} ارسال شد",
"giftSentTo": "هدیه به {{contact}} ارسال شد",
"purchaseFailed": "پرداخت ناموفق",
"purchaseFailedDesc": "پرداخت انجام نشد. دوباره تلاش کنید.",
"subscriptionLink": "لینک اشتراک",
"daysAccess": "روز دسترسی",
"error": "خطا",
"gb": "گیگابایت",
"selectedTariff": "تعرفه",
"period": "دوره",
"total": "مجموع",
"pay": "پرداخت",
"choosePeriod": "انتخاب دوره",
"copied": "کپی شد!",
"copyLink": "کپی لینک"
}
}

View File

@@ -247,13 +247,13 @@
"connectDevice": "Подключить устройство",
"devicesConnected": "{{count}} подключено",
"devicesOfMax": "{{used}} из {{max}} подключено",
"devicesConnectedUnlimited": "{{used}} подключено · без лимита",
"devicesShort": "устр.",
"trafficUsage": "{{used}} / {{limit}} ГБ",
"trafficUsageTitle": "Расход трафика",
"usedTraffic": "использовано {{amount}}",
"usedSuffix": "израсходовано",
"unlimited": "Безлимит",
"unlimitedTraffic": "Безлимитный трафик",
"tariff": "Тариф",
"validUntil": "до {{date}}",
"remaining": "Осталось",
@@ -1064,7 +1064,8 @@
"roleAssign": "Назначение ролей",
"policies": "Политики доступа",
"auditLog": "Журнал аудита",
"salesStats": "Статистика продаж"
"salesStats": "Статистика продаж",
"landings": "Лендинги"
},
"panel": {
"title": "Панель администратора",
@@ -1097,7 +1098,8 @@
"roleAssignDesc": "Назначение и отзыв ролей пользователей",
"policiesDesc": "Настройка политик контроля доступа ABAC",
"auditLogDesc": "Просмотр активности системы и истории доступа",
"salesStatsDesc": "Аналитика продаж и тренды"
"salesStatsDesc": "Аналитика продаж и тренды",
"landingsDesc": "Страницы быстрой покупки"
},
"salesStats": {
"title": "Статистика продаж",
@@ -3693,6 +3695,51 @@
"loadFailed": "Не удалось загрузить журнал аудита",
"retry": "Попробовать снова"
}
},
"landings": {
"title": "Лендинги",
"create": "Создать лендинг",
"edit": "Редактировать",
"slug": "URL-идентификатор",
"slugHint": "Латиница, цифры и дефис",
"pageTitle": "Заголовок",
"subtitle": "Подзаголовок",
"footerText": "Текст внизу (HTML)",
"features": "Преимущества",
"addFeature": "Добавить",
"featureIcon": "Иконка",
"featureTitle": "Заголовок",
"featureDesc": "Описание",
"tariffs": "Тарифы",
"selectTariffs": "Выберите тарифы",
"periods": "Периоды",
"paymentMethods": "Способы оплаты",
"addMethod": "Добавить метод",
"methodId": "ID метода",
"methodName": "Название",
"methodDesc": "Описание",
"methodIcon": "URL иконки",
"gifts": "Подарки",
"giftEnabled": "Покупка в подарок",
"customCss": "CSS",
"seo": "SEO",
"metaTitle": "Meta Title",
"metaDesc": "Meta Description",
"active": "Активен",
"inactive": "Неактивен",
"purchases": "покупок",
"copyUrl": "Скопировать URL",
"urlCopied": "URL скопирован",
"deleteConfirm": "Удалить лендинг «{{title}}»?",
"created": "Лендинг создан",
"updated": "Лендинг обновлён",
"deleted": "Лендинг удалён",
"saveOrder": "Сохранить порядок",
"orderSaved": "Порядок сохранён",
"general": "Основное",
"content": "Контент",
"save": "Сохранить",
"back": "Назад"
}
},
"adminUpdates": {
@@ -4428,5 +4475,44 @@
"error": "Ошибка при объединении. Попробуйте позже.",
"expiresIn": "Действует ещё {{minutes}}",
"merging": "Объединение..."
},
"landing": {
"notFound": "Страница не найдена",
"forMe": "Для себя",
"asGift": "В подарок",
"contactLabel": "Ваш email или @username Telegram",
"yourContact": "Ваш email или @username",
"contactPlaceholder": "email@example.com или @username",
"contactHint": "Введите email или Telegram @username",
"recipientLabel": "Кому подарить",
"recipientPlaceholder": "email или @username получателя",
"giftMessageLabel": "Поздравление (необязательно)",
"giftMessagePlaceholder": "Напишите поздравление...",
"chooseTariff": "Выберите тариф",
"devices": "устройств",
"paymentMethod": "Способ оплаты",
"processing": "Обработка...",
"payButton": "Оплатить {{price}}",
"accessFor": "Доступ на {{period}}",
"purchaseError": "Ошибка при создании оплаты",
"purchaseNotFound": "Покупка не найдена",
"awaitingPayment": "Ожидание оплаты",
"awaitingPaymentDesc": "Завершите оплату в открывшемся окне",
"purchaseSuccess": "Оплата прошла успешно!",
"keySentTo": "Ключ подписки отправлен на {{contact}}",
"giftSentTo": "Подарок отправлен на {{contact}}",
"purchaseFailed": "Ошибка оплаты",
"purchaseFailedDesc": "Оплата не прошла. Попробуйте снова.",
"subscriptionLink": "Ссылка подписки",
"daysAccess": "дней доступа",
"error": "Ошибка",
"gb": "ГБ",
"selectedTariff": "Тариф",
"period": "Период",
"total": "Итого",
"pay": "Оплатить",
"choosePeriod": "Выберите период",
"copied": "Скопировано!",
"copyLink": "Скопировать ссылку"
}
}

View File

@@ -866,7 +866,8 @@
"roleAssign": "角色分配",
"policies": "访问策略",
"auditLog": "审计日志",
"salesStats": "销售统计"
"salesStats": "销售统计",
"landings": "落地页"
},
"panel": {
"title": "管理面板",
@@ -899,7 +900,8 @@
"roleAssignDesc": "分配和撤销用户角色",
"policiesDesc": "配置ABAC访问控制策略",
"auditLogDesc": "查看系统活动和访问历史",
"salesStatsDesc": "销售分析与趋势"
"salesStatsDesc": "销售分析与趋势",
"landingsDesc": "快速购买落地页"
},
"salesStats": {
"title": "销售统计",
@@ -2863,6 +2865,51 @@
"loadFailed": "加载审计日志失败",
"retry": "重试"
}
},
"landings": {
"title": "落地页",
"create": "创建落地页",
"edit": "编辑",
"slug": "URL标识符",
"slugHint": "小写字母、数字和连字符",
"pageTitle": "标题",
"subtitle": "副标题",
"footerText": "底部文本 (HTML)",
"features": "功能特点",
"addFeature": "添加",
"featureIcon": "图标",
"featureTitle": "标题",
"featureDesc": "描述",
"tariffs": "套餐",
"selectTariffs": "选择套餐",
"periods": "周期",
"paymentMethods": "支付方式",
"addMethod": "添加方式",
"methodId": "方式ID",
"methodName": "名称",
"methodDesc": "描述",
"methodIcon": "图标URL",
"gifts": "礼物",
"giftEnabled": "礼物购买",
"customCss": "CSS",
"seo": "SEO",
"metaTitle": "Meta标题",
"metaDesc": "Meta描述",
"active": "已激活",
"inactive": "未激活",
"purchases": "次购买",
"copyUrl": "复制URL",
"urlCopied": "URL已复制",
"deleteConfirm": "删除落地页「{{title}}」?",
"created": "落地页已创建",
"updated": "落地页已更新",
"deleted": "落地页已删除",
"saveOrder": "保存排序",
"orderSaved": "排序已保存",
"general": "基本设置",
"content": "内容",
"save": "保存",
"back": "返回"
}
},
"adminUpdates": {
@@ -3415,5 +3462,44 @@
"error": "合并失败,请稍后重试。",
"expiresIn": "剩余 {{minutes}} 分钟",
"merging": "合并中..."
},
"landing": {
"notFound": "页面未找到",
"forMe": "为自己",
"asGift": "作为礼物",
"contactLabel": "您的邮箱或 @用户名",
"yourContact": "您的邮箱或 @用户名",
"contactPlaceholder": "email@example.com 或 @username",
"contactHint": "输入邮箱或 Telegram @用户名",
"recipientLabel": "赠送对象",
"recipientPlaceholder": "收件人邮箱或 @用户名",
"giftMessageLabel": "祝福语(可选)",
"giftMessagePlaceholder": "写一段祝福语...",
"chooseTariff": "选择套餐",
"devices": "设备",
"paymentMethod": "支付方式",
"processing": "处理中...",
"payButton": "支付 {{price}}",
"accessFor": "{{period}} 访问权限",
"purchaseError": "创建支付失败",
"purchaseNotFound": "未找到购买记录",
"awaitingPayment": "等待支付",
"awaitingPaymentDesc": "请在打开的窗口中完成支付",
"purchaseSuccess": "支付成功!",
"keySentTo": "订阅密钥已发送至 {{contact}}",
"giftSentTo": "礼物已发送至 {{contact}}",
"purchaseFailed": "支付失败",
"purchaseFailedDesc": "支付未成功,请重试。",
"subscriptionLink": "订阅链接",
"daysAccess": "天访问权限",
"error": "错误",
"gb": "GB",
"selectedTariff": "套餐",
"period": "时长",
"total": "总计",
"pay": "支付",
"choosePeriod": "选择时长",
"copied": "已复制!",
"copyLink": "复制链接"
}
}