diff --git a/src/App.tsx b/src/App.tsx index 909a8c2..6df73fb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -36,6 +36,8 @@ const Contests = lazy(() => import('./pages/Contests')); const Polls = lazy(() => import('./pages/Polls')); const Info = lazy(() => import('./pages/Info')); const Wheel = lazy(() => import('./pages/Wheel')); +const GiftSubscription = lazy(() => import('./pages/GiftSubscription')); +const GiftResult = lazy(() => import('./pages/GiftResult')); const Connection = lazy(() => import('./pages/Connection')); const ConnectionQR = lazy(() => import('./pages/ConnectionQR')); const QuickPurchase = lazy(() => import('./pages/QuickPurchase')); @@ -420,6 +422,26 @@ function App() { } /> + + + + + + } + /> + + + + + + } + /> {t('nav.referral')} )} + {giftEnabled && ( + + + {t('nav.gift')} + + )} {isAdmin && ( <> {/* Separator before admin */} @@ -415,6 +430,7 @@ export function AppShell({ children }: AppShellProps) { referralEnabled={referralEnabled} hasContests={hasContests} hasPolls={hasPolls} + giftEnabled={giftEnabled} /> {/* Desktop spacer */} diff --git a/src/components/layout/AppShell/icons.tsx b/src/components/layout/AppShell/icons.tsx index d8678c9..01c09b2 100644 --- a/src/components/layout/AppShell/icons.tsx +++ b/src/components/layout/AppShell/icons.tsx @@ -16,6 +16,7 @@ export { InfoIcon, CogIcon, WheelIcon, + GiftIcon, SearchIcon, PlusIcon, ArrowRightIcon, diff --git a/src/locales/en.json b/src/locales/en.json index 094646a..c1554a6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -44,7 +44,8 @@ "polls": "Polls", "info": "Info", "wheel": "Fortune Wheel", - "menu": "Menu" + "menu": "Menu", + "gift": "Gift" }, "notifications": { "ticketNotifications": "Ticket Notifications", @@ -3293,7 +3294,7 @@ "status_failed": "Failed", "status_expired": "Expired", "noPurchases": "No purchases", - "showing": "Showing {{from}}\u2013{{to}} of {{total}}", + "showing": "Showing {{from}}–{{to}} of {{total}}", "page": "Page {{current}} of {{total}}", "prev": "Previous", "next": "Next" @@ -4114,5 +4115,50 @@ "nMonths_one": "{{count}} month", "nMonths_other": "{{count}} months" } + }, + "gift": { + "title": "Gift Subscription", + "subtitle": "Send a VPN subscription as a gift", + "choosePeriod": "Choose period", + "chooseTariff": "Choose tariff", + "recipient": "Recipient", + "recipientPlaceholder": "Email or @telegram", + "recipientHint": "Enter email or Telegram username", + "giftMessage": "Greeting", + "giftMessagePlaceholder": "Add a personal message (optional)", + "paymentMode": "Payment method", + "fromBalance": "From balance", + "viaGateway": "Via payment gateway", + "yourBalance": "Your balance", + "insufficientBalance": "Insufficient funds", + "topUpBalance": "Top up balance", + "total": "Total", + "giftButton": "Send Gift", + "sending": "Sending gift...", + "gb": "GB", + "devices": "devices", + "paymentMethod": "Payment method", + "processing": "Processing...", + "successTitle": "Gift sent!", + "successDesc": "Recipient {{contact}} will be notified", + "pendingTitle": "Awaiting payment", + "pendingDesc": "Complete the payment in the payment system", + "pendingActivationTitle": "Pending activation", + "pendingActivationDesc": "Recipient has an active subscription. Gift is pending activation.", + "failedTitle": "Error", + "failedDesc": "Failed to send gift. Please try again.", + "backToGift": "Go back", + "backToDashboard": "Back to dashboard", + "tryAgain": "Try again", + "pollTimeout": "Processing is taking longer than usual", + "pollTimeoutDesc": "Try checking the status later", + "retry": "Check again", + "noToken": "Invalid link", + "tariff": "Tariff", + "period": "Period", + "giftMessageLabel": "Message", + "recipientLabel": "Recipient", + "featureDisabled": "Gift feature is temporarily unavailable", + "redirecting": "Redirecting..." } } diff --git a/src/locales/fa.json b/src/locales/fa.json index 93f9501..de87e73 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -44,7 +44,8 @@ "polls": "نظرسنجی", "info": "اطلاعات", "wheel": "چرخ شانس", - "menu": "منو" + "menu": "منو", + "gift": "هدیه" }, "notifications": { "ticketNotifications": "اعلان‌های تیکت", @@ -2964,10 +2965,10 @@ "content": "محتوا", "save": "ذخیره", "back": "بازگشت", - "invalidSlug": "Slug \u0641\u0642\u0637 \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u062f \u0634\u0627\u0645\u0644 \u062d\u0631\u0648\u0641 \u06a9\u0648\u0686\u06a9\u060c \u0627\u0639\u062f\u0627\u062f \u0648 \u062e\u0637 \u062a\u06cc\u0631\u0647 \u0628\u0627\u0634\u062f", - "titleRequired": "\u0639\u0646\u0648\u0627\u0646 \u0627\u0644\u0632\u0627\u0645\u06cc \u0627\u0633\u062a", - "noTariffs": "\u062d\u062f\u0627\u0642\u0644 \u06cc\u06a9 \u062a\u0639\u0631\u0641\u0647 \u0627\u0646\u062a\u062e\u0627\u0628 \u06a9\u0646\u06cc\u062f", - "noPaymentMethods": "\u062d\u062f\u0627\u0642\u0644 \u06cc\u06a9 \u0631\u0648\u0634 \u067e\u0631\u062f\u0627\u062e\u062a \u0627\u0636\u0627\u0641\u0647 \u06a9\u0646\u06cc\u062f", + "invalidSlug": "Slug فقط می‌تواند شامل حروف کوچک، اعداد و خط تیره باشد", + "titleRequired": "عنوان الزامی است", + "noTariffs": "حداقل یک تعرفه انتخاب کنید", + "noPaymentMethods": "حداقل یک روش پرداخت اضافه کنید", "selectMethods": "انتخاب روش‌های پرداخت موجود", "noSystemMethods": "هیچ روش پرداختی در سیستم پیکربندی نشده است", "methodOrder": "برای تغییر ترتیب بکشید", @@ -3568,9 +3569,9 @@ "copy": "کپی", "copied": "کپی شد!", "copyLink": "کپی لینک", - "giftToggleLabel": "\u0646\u0648\u0639 \u062e\u0631\u06cc\u062f", - "pollTimedOut": "\u0632\u0645\u0627\u0646 \u0628\u06cc\u0634\u062a\u0631\u06cc \u0637\u0648\u0644 \u06a9\u0634\u06cc\u062f", - "pollTimedOutDesc": "\u067e\u0631\u062f\u0627\u0632\u0634 \u067e\u0631\u062f\u0627\u062e\u062a \u0628\u06cc\u0634\u062a\u0631 \u0627\u0632 \u062d\u062f \u0645\u0639\u0645\u0648\u0644 \u0637\u0648\u0644 \u06a9\u0634\u06cc\u062f\u0647 \u0627\u0633\u062a. \u0645\u06cc\u200c\u062a\u0648\u0627\u0646\u06cc\u062f \u062f\u0648\u0628\u0627\u0631\u0647 \u0628\u0631\u0631\u0633\u06cc \u06a9\u0646\u06cc\u062f.", + "giftToggleLabel": "نوع خرید", + "pollTimedOut": "زمان بیشتری طول کشید", + "pollTimedOutDesc": "پردازش پرداخت بیشتر از حد معمول طول کشیده است. می‌توانید دوباره بررسی کنید.", "pendingActivation": "اشتراک آماده فعال‌سازی", "pendingActivationDesc": "شما قبلاً اشتراک فعالی دارید. فعال‌سازی اشتراک جدید جایگزین اشتراک فعلی می‌شود.", "activateNow": "فعال‌سازی", @@ -3609,5 +3610,50 @@ "nDays": "{{count}} روز", "nMonths": "{{count}} ماه" } + }, + "gift": { + "title": "هدیه اشتراک", + "subtitle": "اشتراک VPN را به عنوان هدیه ارسال کنید", + "choosePeriod": "انتخاب مدت", + "chooseTariff": "انتخاب طرح", + "recipient": "گیرنده", + "recipientPlaceholder": "ایمیل یا @telegram", + "recipientHint": "ایمیل یا نام کاربری تلگرام را وارد کنید", + "giftMessage": "پیام تبریک", + "giftMessagePlaceholder": "پیام شخصی اضافه کنید (اختیاری)", + "paymentMode": "روش پرداخت", + "fromBalance": "از موجودی", + "viaGateway": "درگاه پرداخت", + "yourBalance": "موجودی شما", + "insufficientBalance": "موجودی ناکافی", + "topUpBalance": "شارژ موجودی", + "total": "جمع", + "giftButton": "ارسال هدیه", + "sending": "در حال ارسال هدیه...", + "gb": "گیگابایت", + "devices": "دستگاه", + "paymentMethod": "روش پرداخت", + "processing": "در حال پردازش...", + "successTitle": "هدیه ارسال شد!", + "successDesc": "گیرنده {{contact}} اطلاع‌رسانی خواهد شد", + "pendingTitle": "در انتظار پرداخت", + "pendingDesc": "پرداخت را در سیستم پرداخت تکمیل کنید", + "pendingActivationTitle": "در انتظار فعال‌سازی", + "pendingActivationDesc": "گیرنده اشتراک فعال دارد. هدیه منتظر فعال‌سازی است.", + "failedTitle": "خطا", + "failedDesc": "ارسال هدیه ناموفق بود. لطفاً دوباره تلاش کنید.", + "backToGift": "بازگشت", + "backToDashboard": "بازگشت به داشبورد", + "tryAgain": "تلاش مجدد", + "pollTimeout": "پردازش بیش از حد معمول طول کشیده", + "pollTimeoutDesc": "بعداً وضعیت را بررسی کنید", + "retry": "بررسی مجدد", + "noToken": "لینک نامعتبر", + "tariff": "طرح", + "period": "مدت", + "giftMessageLabel": "پیام", + "recipientLabel": "گیرنده", + "featureDisabled": "قابلیت هدیه موقتاً در دسترس نیست", + "redirecting": "در حال انتقال..." } } diff --git a/src/locales/ru.json b/src/locales/ru.json index 9fc892c..a755e3d 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -44,7 +44,8 @@ "polls": "Опросы", "info": "Информация", "wheel": "Колесо удачи", - "menu": "Меню" + "menu": "Меню", + "gift": "Подарить" }, "notifications": { "ticketNotifications": "Уведомления о тикетах", @@ -3845,7 +3846,7 @@ "status_failed": "Ошибка", "status_expired": "Истёк", "noPurchases": "Нет покупок", - "showing": "Показано {{from}}\u2013{{to}} из {{total}}", + "showing": "Показано {{from}}–{{to}} из {{total}}", "page": "Стр. {{current}} из {{total}}", "prev": "Назад", "next": "Далее" @@ -4676,5 +4677,50 @@ "nMonths_few": "{{count}} месяца", "nMonths_many": "{{count}} месяцев" } + }, + "gift": { + "title": "Подарить подписку", + "subtitle": "Отправьте VPN-подписку в подарок", + "choosePeriod": "Выберите период", + "chooseTariff": "Выберите тариф", + "recipient": "Получатель", + "recipientPlaceholder": "Email или @telegram", + "recipientHint": "Введите email или юзернейм в Telegram", + "giftMessage": "Поздравление", + "giftMessagePlaceholder": "Добавьте личное сообщение (необязательно)", + "paymentMode": "Способ оплаты", + "fromBalance": "С баланса", + "viaGateway": "Через платёжку", + "yourBalance": "Ваш баланс", + "insufficientBalance": "Недостаточно средств", + "topUpBalance": "Пополнить баланс", + "total": "Итого", + "giftButton": "Подарить", + "sending": "Отправляем подарок...", + "gb": "ГБ", + "devices": "устройств", + "paymentMethod": "Способ оплаты", + "processing": "Обработка...", + "successTitle": "Подарок отправлен!", + "successDesc": "Получатель {{contact}} получит уведомление", + "pendingTitle": "Ожидание оплаты", + "pendingDesc": "Завершите оплату в платёжной системе", + "pendingActivationTitle": "Ожидает активации", + "pendingActivationDesc": "У получателя есть активная подписка. Подарок ожидает активации.", + "failedTitle": "Ошибка", + "failedDesc": "Не удалось отправить подарок. Попробуйте снова.", + "backToGift": "Вернуться", + "backToDashboard": "На главную", + "tryAgain": "Попробовать снова", + "pollTimeout": "Обработка занимает больше времени, чем обычно", + "pollTimeoutDesc": "Попробуйте проверить статус позже", + "retry": "Проверить снова", + "noToken": "Ссылка недействительна", + "tariff": "Тариф", + "period": "Период", + "giftMessageLabel": "Сообщение", + "recipientLabel": "Получатель", + "featureDisabled": "Функция подарков временно недоступна", + "redirecting": "Перенаправляем..." } } diff --git a/src/locales/zh.json b/src/locales/zh.json index 008aabf..a53b03c 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -44,7 +44,8 @@ "polls": "问卷", "info": "信息", "wheel": "幸运转盘", - "menu": "菜单" + "menu": "菜单", + "gift": "赠送" }, "notifications": { "ticketNotifications": "工单通知", @@ -2963,10 +2964,10 @@ "content": "内容", "save": "保存", "back": "返回", - "invalidSlug": "Slug\u53ea\u80fd\u5305\u542b\u5c0f\u5199\u5b57\u6bcd\u3001\u6570\u5b57\u548c\u8fde\u5b57\u7b26", - "titleRequired": "\u6807\u9898\u4e3a\u5fc5\u586b\u9879", - "noTariffs": "\u8bf7\u81f3\u5c11\u9009\u62e9\u4e00\u4e2a\u5957\u9910", - "noPaymentMethods": "\u8bf7\u81f3\u5c11\u6dfb\u52a0\u4e00\u4e2a\u652f\u4ed8\u65b9\u5f0f", + "invalidSlug": "Slug只能包含小写字母、数字和连字符", + "titleRequired": "标题为必填项", + "noTariffs": "请至少选择一个套餐", + "noPaymentMethods": "请至少添加一个支付方式", "selectMethods": "选择可用的支付方式", "noSystemMethods": "系统中未配置任何支付方式", "methodOrder": "拖动以调整顺序", @@ -3567,9 +3568,9 @@ "copy": "复制", "copied": "已复制!", "copyLink": "复制链接", - "giftToggleLabel": "\u8d2d\u4e70\u7c7b\u578b", - "pollTimedOut": "\u5904\u7406\u65f6\u95f4\u8f83\u957f", - "pollTimedOutDesc": "\u4ed8\u6b3e\u5904\u7406\u65f6\u95f4\u6bd4\u5e73\u65f6\u957f\u3002\u60a8\u53ef\u4ee5\u5c1d\u8bd5\u518d\u6b21\u68c0\u67e5\u3002", + "giftToggleLabel": "购买类型", + "pollTimedOut": "处理时间较长", + "pollTimedOutDesc": "付款处理时间比平时长。您可以尝试再次检查。", "pendingActivation": "订阅准备激活", "pendingActivationDesc": "您已有活跃订阅。激活新订阅将替换当前订阅。", "activateNow": "立即激活", @@ -3608,5 +3609,50 @@ "nDays": "{{count}}天", "nMonths": "{{count}}个月" } + }, + "gift": { + "title": "赠送订阅", + "subtitle": "将VPN订阅作为礼物发送", + "choosePeriod": "选择时长", + "chooseTariff": "选择套餐", + "recipient": "收件人", + "recipientPlaceholder": "邮箱或 @telegram", + "recipientHint": "输入邮箱或 Telegram 用户名", + "giftMessage": "祝福语", + "giftMessagePlaceholder": "添加个人消息(可选)", + "paymentMode": "支付方式", + "fromBalance": "余额支付", + "viaGateway": "在线支付", + "yourBalance": "您的余额", + "insufficientBalance": "余额不足", + "topUpBalance": "充值", + "total": "合计", + "giftButton": "赠送", + "sending": "正在发送礼物...", + "gb": "GB", + "devices": "设备", + "paymentMethod": "支付方式", + "processing": "处理中...", + "successTitle": "礼物已发送!", + "successDesc": "收件人 {{contact}} 将收到通知", + "pendingTitle": "等待支付", + "pendingDesc": "请在支付系统中完成支付", + "pendingActivationTitle": "等待激活", + "pendingActivationDesc": "收件人有活跃订阅,礼物等待激活。", + "failedTitle": "错误", + "failedDesc": "礼物发送失败,请重试。", + "backToGift": "返回", + "backToDashboard": "返回首页", + "tryAgain": "重试", + "pollTimeout": "处理时间超出预期", + "pollTimeoutDesc": "请稍后再查看状态", + "retry": "再次检查", + "noToken": "无效链接", + "tariff": "套餐", + "period": "时长", + "giftMessageLabel": "消息", + "recipientLabel": "收件人", + "featureDisabled": "礼物功能暂时不可用", + "redirecting": "正在跳转..." } }