diff --git a/src/AppWithNavigator.tsx b/src/AppWithNavigator.tsx index 53ec831..6f6dfeb 100644 --- a/src/AppWithNavigator.tsx +++ b/src/AppWithNavigator.tsx @@ -20,6 +20,9 @@ import { isInTelegramWebApp } from './hooks/useTelegramSDK'; * Manages Telegram BackButton visibility based on navigation location. * Shows back button on non-root routes, hides on root. */ +/** Pages reachable from bottom nav — treat as top-level (no back button). */ +const BOTTOM_NAV_PATHS = ['/', '/subscription', '/balance', '/referral', '/support', '/wheel']; + function TelegramBackButton() { const location = useLocation(); const navigate = useNavigate(); @@ -27,9 +30,9 @@ function TelegramBackButton() { navigateRef.current = navigate; useEffect(() => { - const isRoot = location.pathname === '/' || location.pathname === ''; + const isTopLevel = location.pathname === '' || BOTTOM_NAV_PATHS.includes(location.pathname); try { - if (isRoot) { + if (isTopLevel) { hideBackButton(); } else { showBackButton(); diff --git a/src/api/tariffs.ts b/src/api/tariffs.ts index bd8ba11..5e4234f 100644 --- a/src/api/tariffs.ts +++ b/src/api/tariffs.ts @@ -208,7 +208,9 @@ export const tariffsApi = { }, // Delete tariff - deleteTariff: async (tariffId: number): Promise<{ message: string }> => { + deleteTariff: async ( + tariffId: number, + ): Promise<{ message: string; affected_subscriptions: number }> => { const response = await apiClient.delete(`/cabinet/admin/tariffs/${tariffId}`); return response.data; }, diff --git a/src/locales/en.json b/src/locales/en.json index df1bd61..deb3be8 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -355,7 +355,11 @@ "selectPlatform": "Select platform", "app": "app", "apps": "apps", - "tutorial": "Tutorial" + "tutorial": "Tutorial", + "notConfigured": "Connection methods are not configured yet", + "notConfiguredUser": "Connection setup is in progress. Please check back later.", + "notConfiguredAdmin": "No connection apps have been configured. Set them up in the Apps settings.", + "goToApps": "Go to Apps settings" }, "myDevices": "My Devices", "noDevices": "No connected devices", @@ -1366,6 +1370,7 @@ "confirmDeleteText": "This action cannot be undone. The tariff will be permanently deleted.", "confirmDeleteWithSubscriptions": "This tariff has {{count}} active subscriptions. After deletion, users will need to select a new tariff when renewing.", "deleteSuccess": "Tariff deleted successfully", + "deleteSuccessWithSubscriptions": "Tariff deleted. {{count}} subscriptions reset — users will choose a new tariff on renewal.", "saveOrder": "Save order", "orderSaved": "Order saved", "dragToReorder": "Drag to reorder", diff --git a/src/locales/fa.json b/src/locales/fa.json index c1be32b..de083f3 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -307,7 +307,11 @@ "selectPlatform": "انتخاب پلتفرم", "app": "برنامه", "apps": "برنامه", - "tutorial": "آموزش" + "tutorial": "آموزش", + "notConfigured": "روشهای اتصال هنوز پیکربندی نشدهاند", + "notConfiguredUser": "تنظیمات اتصال در حال انجام است. لطفاً بعداً مراجعه کنید.", + "notConfiguredAdmin": "هیچ برنامه اتصالی پیکربندی نشده است. آنها را در تنظیمات برنامهها تنظیم کنید.", + "goToApps": "رفتن به تنظیمات برنامهها" }, "additionalOptions": { "title": "گزینههای اضافی", @@ -1072,6 +1076,7 @@ "confirmDeleteText": "این عمل قابل بازگشت نیست. تعرفه برای همیشه حذف میشود.", "confirmDeleteWithSubscriptions": "این تعرفه {{count}} اشتراک فعال دارد. پس از حذف، کاربران هنگام تمدید باید تعرفه جدید انتخاب کنند.", "deleteSuccess": "تعرفه با موفقیت حذف شد", + "deleteSuccessWithSubscriptions": "تعرفه حذف شد. تعرفه {{count}} اشتراک بازنشانی شد — کاربران هنگام تمدید تعرفه جدید انتخاب میکنند.", "saveOrder": "ذخیره ترتیب", "orderSaved": "ترتیب ذخیره شد", "dragToReorder": "برای تغییر ترتیب بکشید", diff --git a/src/locales/ru.json b/src/locales/ru.json index bd2f537..8108aa2 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -377,7 +377,11 @@ "selectPlatform": "Выберите платформу", "app": "приложение", "apps": "приложений", - "tutorial": "Инструкция" + "tutorial": "Инструкция", + "notConfigured": "Способы подключения ещё не настроены", + "notConfiguredUser": "Настройка подключения в процессе. Пожалуйста, загляните позже.", + "notConfiguredAdmin": "Приложения для подключения не настроены. Настройте их в разделе Приложения.", + "goToApps": "Перейти к настройке приложений" }, "myDevices": "Мои устройства", "noDevices": "Нет подключенных устройств", @@ -1884,6 +1888,7 @@ "confirmDeleteText": "Это действие нельзя отменить. Тариф будет удалён навсегда.", "confirmDeleteWithSubscriptions": "У этого тарифа {{count}} активных подписок. При удалении пользователям потребуется выбрать новый тариф при продлении.", "deleteSuccess": "Тариф успешно удален", + "deleteSuccessWithSubscriptions": "Тариф удален. У {{count}} подписок сброшен тариф — при продлении пользователи выберут новый.", "saveOrder": "Сохранить порядок", "orderSaved": "Порядок сохранён", "dragToReorder": "Перетащите для изменения порядка", diff --git a/src/locales/zh.json b/src/locales/zh.json index 3fc8ca5..f99142b 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -307,7 +307,11 @@ "selectPlatform": "选择平台", "app": "个应用", "apps": "个应用", - "tutorial": "教程" + "tutorial": "教程", + "notConfigured": "连接方式尚未配置", + "notConfiguredUser": "连接设置正在进行中,请稍后再来查看。", + "notConfiguredAdmin": "尚未配置连接应用。请在应用设置中进行配置。", + "goToApps": "前往应用设置" }, "additionalOptions": { "title": "附加选项", @@ -1110,6 +1114,7 @@ "confirmDeleteText": "此操作不可撤销。套餐将被永久删除。", "confirmDeleteWithSubscriptions": "此套餐有 {{count}} 个活跃订阅。删除后,用户续订时需要选择新套餐。", "deleteSuccess": "套餐删除成功", + "deleteSuccessWithSubscriptions": "套餐已删除。{{count}} 个订阅已重置 — 用户续订时将选择新套餐。", "saveOrder": "保存排序", "orderSaved": "排序已保存", "dragToReorder": "拖动以重新排序", diff --git a/src/pages/AdminTariffs.tsx b/src/pages/AdminTariffs.tsx index ba93d31..c45831c 100644 --- a/src/pages/AdminTariffs.tsx +++ b/src/pages/AdminTariffs.tsx @@ -282,9 +282,15 @@ export default function AdminTariffs() { // Mutations const deleteMutation = useMutation({ mutationFn: tariffsApi.deleteTariff, - onSuccess: () => { + onSuccess: (data) => { queryClient.invalidateQueries({ queryKey: ['admin-tariffs'] }); - notify.success(t('admin.tariffs.deleteSuccess')); + if (data.affected_subscriptions > 0) { + notify.success( + t('admin.tariffs.deleteSuccessWithSubscriptions', { count: data.affected_subscriptions }), + ); + } else { + notify.success(t('admin.tariffs.deleteSuccess')); + } }, }); diff --git a/src/pages/Connection.tsx b/src/pages/Connection.tsx index a68d271..e9fe6c9 100644 --- a/src/pages/Connection.tsx +++ b/src/pages/Connection.tsx @@ -1,5 +1,5 @@ -import { useEffect, useCallback, useRef } from 'react'; -import { useNavigate } from 'react-router'; +import { useEffect, useCallback, useMemo, useRef } from 'react'; +import { Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useQuery } from '@tanstack/react-query'; import { openLink as sdkOpenLink } from '@telegram-apps/sdk-react'; @@ -8,13 +8,13 @@ import { useTelegramSDK } from '../hooks/useTelegramSDK'; import { useHaptic } from '@/platform'; import { resolveTemplate, hasTemplates } from '../utils/templateEngine'; import { useAuthStore } from '../store/auth'; -import type { AppConfig } from '../types'; +import type { AppConfig, RemnawavePlatformData } from '../types'; import InstallationGuide from '../components/connection/InstallationGuide'; export default function Connection() { const { t, i18n } = useTranslation(); const navigate = useNavigate(); - const { user } = useAuthStore(); + const { user, isAdmin } = useAuthStore(); const { isTelegramWebApp } = useTelegramSDK(); const { impact: hapticImpact } = useHaptic(); @@ -79,6 +79,14 @@ export default function Connection() { [isTelegramWebApp, i18n.language, resolveUrl], ); + // Check if any platform has configured apps + const hasApps = useMemo(() => { + if (!appConfig?.platforms) return false; + return Object.values(appConfig.platforms).some( + (p: RemnawavePlatformData) => p.apps && p.apps.length > 0, + ); + }, [appConfig?.platforms]); + // Loading if (isLoading) { return ( @@ -100,6 +108,60 @@ export default function Connection() { ); } + // No apps configured — check before subscription since empty config also has no subscription + if (!hasApps) { + return ( +
+ {isAdmin + ? t('subscription.connection.notConfiguredAdmin') + : t('subscription.connection.notConfiguredUser')} +
+ {isAdmin && ( + + + {t('subscription.connection.goToApps')} + + )} +