diff --git a/src/locales/en.json b/src/locales/en.json index 79dd250..6221861 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -262,6 +262,7 @@ "dashboard": { "title": "Dashboard", "welcome": "Welcome, {{name}}!", + "welcomeNoName": "Welcome!", "yourSubscription": "Your Subscription", "quickActions": "Quick Actions", "viewSubscription": "Manage Subscription", diff --git a/src/locales/fa.json b/src/locales/fa.json index 5bc36d9..5142049 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -247,6 +247,7 @@ "dashboard": { "title": "داشبورد", "welcome": "خوش آمدید، {{name}}!", + "welcomeNoName": "خوش آمدید!", "yourSubscription": "اشتراک شما", "quickActions": "دسترسی سریع", "viewSubscription": "مدیریت اشتراک", diff --git a/src/locales/ru.json b/src/locales/ru.json index c8239a3..32fc373 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -265,6 +265,7 @@ "dashboard": { "title": "Личный кабинет", "welcome": "Добро пожаловать, {{name}}!", + "welcomeNoName": "Добро пожаловать!", "yourSubscription": "Ваша подписка", "quickActions": "Быстрые действия", "viewSubscription": "Управление подпиской", diff --git a/src/locales/zh.json b/src/locales/zh.json index 2d8ff03..356a5c3 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -247,6 +247,7 @@ "dashboard": { "title": "个人中心", "welcome": "欢迎,{{name}}!", + "welcomeNoName": "欢迎!", "yourSubscription": "您的订阅", "quickActions": "快捷操作", "viewSubscription": "管理订阅", diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 0d0e254..714ad7a 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -251,12 +251,14 @@ export default function Dashboard() { setShowOnboarding(false); }; + const userName = displayName(user); + return (
{/* Header */}

- {t('dashboard.welcome', { name: displayName(user) })} + {userName ? t('dashboard.welcome', { name: userName }) : t('dashboard.welcomeNoName')}

{t('dashboard.yourSubscription')}

@@ -328,40 +330,37 @@ export default function Dashboard() { )} {/* Subscription Status Card — hidden in multi-tariff (managed via /subscriptions) */} - {!isMultiTariff && ( - <> - {subLoading ? ( -
-
-
-
-
-
-
-
-
-
-
+ {!isMultiTariff && + (subLoading ? ( +
+
+
+
- ) : subscription?.is_expired || - subscription?.status === 'disabled' || - subscription?.is_limited ? ( - - ) : subscription ? ( - - ) : null} - - )} +
+
+
+
+
+
+
+ ) : subscription?.is_expired || + subscription?.status === 'disabled' || + subscription?.is_limited ? ( + + ) : subscription ? ( + + ) : null)} {/* Нет подписок: показываем триал (если доступен) и ВСЕГДА одну явную кнопку покупки. Триал не обязателен, чтобы попасть в витрину — раньше diff --git a/src/utils/displayName.test.ts b/src/utils/displayName.test.ts new file mode 100644 index 0000000..00ea739 --- /dev/null +++ b/src/utils/displayName.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from 'vitest'; +import { displayName } from './displayName'; + +describe('displayName', () => { + it('returns empty string for missing user', () => { + expect(displayName(undefined)).toBe(''); + expect(displayName(null)).toBe(''); + }); + + it('combines first_name and last_name', () => { + expect(displayName({ first_name: 'Иван', last_name: 'Петров' })).toBe('Иван Петров'); + expect(displayName({ first_name: 'Иван', last_name: null })).toBe('Иван'); + expect(displayName({ first_name: null, last_name: 'Петров' })).toBe('Петров'); + }); + + it('falls back to username when there is no name', () => { + expect(displayName({ first_name: null, last_name: null, username: 'ivan' })).toBe('ivan'); + }); + + it('falls back to telegram_id when there is no name and no username', () => { + expect(displayName({ first_name: null, username: null, telegram_id: 123456 })).toBe('#123456'); + }); + + it('falls back to the email local part for email-only users', () => { + expect( + displayName({ + first_name: null, + last_name: null, + username: null, + telegram_id: null, + email: 'vasya@gmail.com', + }), + ).toBe('vasya'); + }); + + it('prefers telegram_id over email', () => { + expect(displayName({ telegram_id: 123456, email: 'vasya@gmail.com' })).toBe('#123456'); + }); + + it('returns empty string when every source is empty', () => { + expect( + displayName({ + first_name: ' ', + last_name: null, + username: null, + telegram_id: null, + email: null, + }), + ).toBe(''); + }); +}); diff --git a/src/utils/displayName.ts b/src/utils/displayName.ts index d79412f..c7c62ab 100644 --- a/src/utils/displayName.ts +++ b/src/utils/displayName.ts @@ -3,21 +3,28 @@ * * Why: single-letter first_name (e.g., "О") looked confusing alone ("Добро пожаловать, О!"). * Combining with last_name makes truncated/short first names readable. + * + * Email/OAuth registration does not collect a name, so for such users every + * Telegram-derived field is null — fall back to the email local part + * ("vasya@gmail.com" → "vasya") to avoid an empty name in greetings. */ export interface NameSource { first_name?: string | null; last_name?: string | null; username?: string | null; telegram_id?: number | null; + email?: string | 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())) + .filter((part): part is string => Boolean(part?.trim())) .join(' '); if (fullName) return fullName; if (user.username) return user.username; if (user.telegram_id) return `#${user.telegram_id}`; + const emailLocalPart = user.email?.trim().split('@')[0]; + if (emailLocalPart) return emailLocalPart; return ''; }