From 4de01ccd1df913510aab2368552e6f993e7aa25f Mon Sep 17 00:00:00 2001 From: Fringg Date: Wed, 13 May 2026 09:02:10 +0300 Subject: [PATCH] fix(dashboard): show full name on welcome, fix gender mismatch on trial-expired card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User reported single-letter welcome "Добро пожаловать, О!" (short first_name "Олег"-style users) and a confused "Истекла" feminine label on the masculine "Пробный период истёк" trial-expired card. * Add `src/utils/displayName.ts` helper that composes `first_name + last_name` with `username` and `#telegram_id` fallbacks. Single source of truth for user-facing name rendering across the app. * Apply `displayName(user)` in Dashboard welcome, AppHeader mobile drawer, and DesktopSidebar profile chip. Now a user with `first_name="О"` and `last_name="Иванов"` sees "О Иванов" instead of just "О". * `SubscriptionCardExpired` — context-aware Russian label: for trial subscriptions render masculine "Истёк" (agrees with "пробный период"), for paid subscriptions keep feminine "Истекла" (agrees with "подписка"). Uses i18next `context: subscription.is_trial ? 'trial' : ''` — falls back to base key for EN/ZH/FA which are grammatically neutral. * Add `expiredDate_trial: "Истёк"` only to `ru.json` (no changes needed for other locales — i18next context falls back to `expiredDate`). --- .../dashboard/SubscriptionCardExpired.tsx | 6 ++++- src/components/layout/AppShell/AppHeader.tsx | 5 ++-- .../layout/AppShell/DesktopSidebar.tsx | 5 ++-- src/locales/ru.json | 1 + src/pages/Dashboard.tsx | 3 ++- src/utils/displayName.ts | 23 +++++++++++++++++++ 6 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 src/utils/displayName.ts diff --git a/src/components/dashboard/SubscriptionCardExpired.tsx b/src/components/dashboard/SubscriptionCardExpired.tsx index 361e727..4709ded 100644 --- a/src/components/dashboard/SubscriptionCardExpired.tsx +++ b/src/components/dashboard/SubscriptionCardExpired.tsx @@ -228,7 +228,11 @@ export default function SubscriptionCardExpired({ >
- {isLimited ? t('dashboard.expired.activeUntil') : t('dashboard.expired.expiredDate')} + {isLimited + ? t('dashboard.expired.activeUntil') + : t('dashboard.expired.expiredDate', { + context: subscription.is_trial ? 'trial' : '', + })}
{formattedDate} diff --git a/src/components/layout/AppShell/AppHeader.tsx b/src/components/layout/AppShell/AppHeader.tsx index 7d2c89f..3cac5df 100644 --- a/src/components/layout/AppShell/AppHeader.tsx +++ b/src/components/layout/AppShell/AppHeader.tsx @@ -5,6 +5,7 @@ import { useState, useEffect } from 'react'; import { initDataUser } from '@telegram-apps/sdk-react'; import { useAuthStore } from '@/store/auth'; +import { displayName } from '@/utils/displayName'; import { useShallow } from 'zustand/shallow'; import { useTheme } from '@/hooks/useTheme'; import { usePlatform } from '@/platform'; @@ -340,9 +341,7 @@ export function AppHeader({
-
- {user?.first_name || user?.username} -
+
{displayName(user)}
@{user?.username || `ID: ${user?.telegram_id}`}
diff --git a/src/components/layout/AppShell/DesktopSidebar.tsx b/src/components/layout/AppShell/DesktopSidebar.tsx index 7799435..99b9ad9 100644 --- a/src/components/layout/AppShell/DesktopSidebar.tsx +++ b/src/components/layout/AppShell/DesktopSidebar.tsx @@ -4,6 +4,7 @@ import { motion } from 'framer-motion'; import { useQuery } from '@tanstack/react-query'; import { useAuthStore } from '@/store/auth'; +import { displayName } from '@/utils/displayName'; import { brandingApi, getCachedBranding, @@ -188,9 +189,7 @@ export function DesktopSidebar({
-

- {user?.first_name || user?.username || `#${user?.telegram_id}`} -

+

{displayName(user)}

@{user?.username || `ID: ${user?.telegram_id}`}

diff --git a/src/locales/ru.json b/src/locales/ru.json index 22b67af..be768f7 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -285,6 +285,7 @@ "traffic": "Трафик", "devices": "Устройства", "expiredDate": "Истекла", + "expiredDate_trial": "Истёк", "activeUntil": "Активна до" }, "suspended": { diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index b04cde4..66ae194 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -3,6 +3,7 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Link, useNavigate } from 'react-router'; import { useTranslation } from 'react-i18next'; import { useAuthStore } from '../store/auth'; +import { displayName } from '../utils/displayName'; import { useBlockingStore } from '../store/blocking'; import { subscriptionApi } from '../api/subscription'; import { referralApi } from '../api/referral'; @@ -254,7 +255,7 @@ export default function Dashboard() { {/* Header */}

- {t('dashboard.welcome', { name: user?.first_name || user?.username || '' })} + {t('dashboard.welcome', { name: displayName(user) })}

{t('dashboard.yourSubscription')}

diff --git a/src/utils/displayName.ts b/src/utils/displayName.ts new file mode 100644 index 0000000..d79412f --- /dev/null +++ b/src/utils/displayName.ts @@ -0,0 +1,23 @@ +/** + * Composes a user-facing display name from first_name + last_name with sensible fallbacks. + * + * Why: single-letter first_name (e.g., "О") looked confusing alone ("Добро пожаловать, О!"). + * Combining with last_name makes truncated/short first names readable. + */ +export interface NameSource { + first_name?: string | null; + last_name?: string | null; + username?: string | null; + telegram_id?: number | null; +} + +export function displayName(user?: NameSource | null): string { + if (!user) return ''; + const fullName = [user.first_name, user.last_name] + .filter((part): part is string => Boolean(part && part.trim())) + .join(' '); + if (fullName) return fullName; + if (user.username) return user.username; + if (user.telegram_id) return `#${user.telegram_id}`; + return ''; +}