fix(cabinet): show trial offer in multi-tariff mode

Two cabinet routes never showed TrialOfferCard for users without any
subscription when MULTI_TARIFF was enabled:

1. Dashboard (/)
   hasNoSubscription was derived from subscriptionResponse, but the
   /cabinet/subscription query is disabled in multi-tariff mode
   (`enabled: !isMultiTariff`). subscriptionResponse stayed undefined
   forever, so `has_subscription === false` was never true and the
   trial card never rendered.

   Fix: branch on isMultiTariff — use multiSubData.subscriptions.length
   when in multi-tariff, keep the single-tariff path unchanged.

2. /subscriptions list
   When the array came back empty the page rendered EmptyState directly,
   bypassing trial eligibility entirely. Users who navigated to
   'Подписки' from the menu before opening the dashboard saw the empty
   placeholder and nothing about the trial.

   Fix: fetch trial-info on empty state, render TrialOfferCard if
   trialInfo.is_available, fall back to EmptyState otherwise. Also
   wired the activate mutation locally (mirrors Dashboard) so the
   button works without bouncing through the home screen first.

Single-tariff behavior is unchanged. After the fix both entry points
agree: if you have no subscriptions and trial is available, you see
the offer regardless of which page you opened first.
This commit is contained in:
Fringg
2026-05-13 05:31:22 +03:00
parent 6ffd0ae824
commit 7c10843d9c
2 changed files with 58 additions and 4 deletions

View File

@@ -195,7 +195,12 @@ export default function Dashboard() {
refreshTrafficMutation.mutate();
}, [subscription, refreshTrafficMutation]);
const hasNoSubscription = subscriptionResponse?.has_subscription === false && !subLoading;
// В multi-tariff /cabinet/subscription отключён, поэтому subscriptionResponse=undefined.
// Используем список из /cabinet/subscriptions/list — пустой массив означает «нет подписок»,
// и тогда показываем TrialOfferCard. Без этой ветки multi-tariff юзер никогда не видел триал.
const hasNoSubscription = isMultiTariff
? multiSubData !== undefined && (multiSubData.subscriptions?.length ?? 0) === 0
: subscriptionResponse?.has_subscription === false && !subLoading;
// Show onboarding for new users after data loads
useEffect(() => {

View File

@@ -1,10 +1,14 @@
import { useQuery } from '@tanstack/react-query';
import { useState } from 'react';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Navigate, useNavigate } from 'react-router';
import { useTranslation } from 'react-i18next';
import { subscriptionApi } from '../api/subscription';
import { balanceApi } from '../api/balance';
import { useTheme } from '../hooks/useTheme';
import { getGlassColors } from '../utils/glassTheme';
import { useAuthStore } from '../store/auth';
import SubscriptionListCard from '../components/subscription/SubscriptionListCard';
import TrialOfferCard from '../components/dashboard/TrialOfferCard';
function EmptyState({ onBuy }: { onBuy: () => void }) {
const { t } = useTranslation();
@@ -55,6 +59,9 @@ export default function Subscriptions() {
const navigate = useNavigate();
const { isDark } = useTheme();
const g = getGlassColors(isDark);
const queryClient = useQueryClient();
const refreshUser = useAuthStore((state) => state.refreshUser);
const [trialError, setTrialError] = useState<string | null>(null);
const { data, isLoading } = useQuery({
queryKey: ['subscriptions-list'],
@@ -65,6 +72,39 @@ export default function Subscriptions() {
const subscriptions = data?.subscriptions ?? [];
const isMultiTariff = data?.multi_tariff_enabled ?? false;
const hasNoSubscriptions = !isLoading && subscriptions.length === 0;
// Если у юзера нет подписок — проверяем доступность триала, иначе
// (в multi-tariff) ему вообще негде увидеть оффер.
const { data: trialInfo, isLoading: trialLoading } = useQuery({
queryKey: ['trial-info'],
queryFn: () => subscriptionApi.getTrialInfo(),
enabled: hasNoSubscriptions,
staleTime: 30_000,
});
const { data: balanceData } = useQuery({
queryKey: ['balance'],
queryFn: balanceApi.getBalance,
enabled: hasNoSubscriptions && !!trialInfo?.is_available,
staleTime: 30_000,
});
const activateTrialMutation = useMutation({
mutationFn: () => subscriptionApi.activateTrial(),
onSuccess: () => {
setTrialError(null);
queryClient.invalidateQueries({ queryKey: ['subscription'] });
queryClient.invalidateQueries({ queryKey: ['subscriptions-list'] });
queryClient.invalidateQueries({ queryKey: ['trial-info'] });
queryClient.invalidateQueries({ queryKey: ['balance'] });
queryClient.invalidateQueries({ queryKey: ['purchase-options'] });
refreshUser();
},
onError: (error: { response?: { data?: { detail?: string } } }) => {
setTrialError(error.response?.data?.detail || t('common.error'));
},
});
// Single-tariff mode with one subscription: skip list, go directly to detail
if (data && !isMultiTariff && subscriptions.length === 1) {
@@ -115,8 +155,17 @@ export default function Subscriptions() {
</div>
)}
{/* Empty state */}
{!isLoading && subscriptions.length === 0 && (
{/* Empty state: показываем триал, если доступен; иначе — обычный empty */}
{hasNoSubscriptions && !trialLoading && trialInfo?.is_available && (
<TrialOfferCard
trialInfo={trialInfo}
balanceKopeks={balanceData?.balance_kopeks ?? 0}
balanceRubles={balanceData?.balance_rubles ?? 0}
activateTrialMutation={activateTrialMutation}
trialError={trialError}
/>
)}
{hasNoSubscriptions && !trialLoading && !trialInfo?.is_available && (
<EmptyState onBuy={() => navigate('/subscription/purchase')} />
)}