diff --git a/web/src/components/AppShell.tsx b/web/src/components/AppShell.tsx index 87a5b64..a2c74fd 100644 --- a/web/src/components/AppShell.tsx +++ b/web/src/components/AppShell.tsx @@ -1,7 +1,8 @@ import { useState, useEffect, type ReactNode } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import Icon from './Icon'; -import { getAuth, refreshAccessToken, redirectToLogin, type AuthUser } from '../lib/auth'; +import { getAuth, refreshAccessToken, redirectToLogin, backendLanguageToLocale, type AuthUser } from '../lib/auth'; +import { getUserProfile, type UserProfile } from '../lib/api'; interface NavItem { id: string; @@ -49,6 +50,7 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const [expandedNav, setExpandedNav] = useState('divination'); const [authUser, setAuthUser] = useState(null); + const [userProfile, setUserProfile] = useState(null); const [checkingAuth, setCheckingAuth] = useState(true); const activeNav = getActiveNav(navItems, locale, location.pathname); @@ -63,6 +65,20 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm refreshAccessToken() .then((data) => { if (alive) setAuthUser(data.user); + return getUserProfile(); + }) + .then((profile) => { + if (!alive) return; + setUserProfile(profile); + + // Check if URL locale matches user's language preference + const userLocale = backendLanguageToLocale(profile.settings?.preferences?.language || 'zh-CN'); + if (locale !== userLocale) { + // Redirect to the correct locale + const currentPath = window.location.pathname; + const newPath = currentPath.replace(`/${locale}`, `/${userLocale}`); + window.location.replace(newPath); + } }) .catch(() => { redirectToLogin(); @@ -74,7 +90,7 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm return () => { alive = false; }; - }, []); + }, [locale]); useEffect(() => { if (activeNav === 'manual' || activeNav === 'auto') setExpandedNav('divination'); @@ -93,8 +109,9 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm ); } - const shellUserName = userName || authUser.email.split('@')[0]; - const shellUserEmail = userEmail || authUser.email; + const shellUserName = userName || userProfile?.display_name || authUser?.email?.split('@')[0] || ''; + const shellUserEmail = userEmail || userProfile?.email || authUser?.email || ''; + const shellAvatarUrl = userProfile?.avatar_url; return (
@@ -171,12 +188,16 @@ export default function AppShell({ locale, brandName, navItems, userName, userEm { event.preventDefault(); navigate(`/${locale}/profile`); }} className={`flex items-center gap-3 p-3 rounded-[10px] hover:bg-slate-50 transition-colors ${sidebarCollapsed ? 'md:justify-center' : ''}`} title={sidebarCollapsed ? shellUserName : undefined}> -
- {shellUserName[0].toUpperCase()} -
+ {shellAvatarUrl ? ( + {shellUserName} + ) : ( +
+ {shellUserName ? shellUserName[0].toUpperCase() : '?'} +
+ )}
-

{shellUserName}

-

{shellUserEmail}

+

{shellUserName || '-'}

+

{shellUserEmail || '-'}

diff --git a/web/src/components/FeedbackPage.tsx b/web/src/components/FeedbackPage.tsx index 0634947..7e0c7d5 100644 --- a/web/src/components/FeedbackPage.tsx +++ b/web/src/components/FeedbackPage.tsx @@ -97,7 +97,7 @@ export default function FeedbackPage({ locale, feedback: f }: Props) { } setToast({ type: 'success', message: f.success }); - setTimeout(() => navigate(-1), 1500); + setTimeout(() => navigate(`/${locale}/settings`), 1500); } catch { setToast({ type: 'error', message: f.error }); } finally { @@ -110,7 +110,7 @@ export default function FeedbackPage({ locale, feedback: f }: Props) { {/* Page Header */}
+ {/* Toast */} + {toast && ( +
+ {toast.message} +
+ )} + {/* Language Section */}

{g.languageLabel}

diff --git a/web/src/components/ProfileDetailPage.tsx b/web/src/components/ProfileDetailPage.tsx index d13d181..de35f6d 100644 --- a/web/src/components/ProfileDetailPage.tsx +++ b/web/src/components/ProfileDetailPage.tsx @@ -154,7 +154,7 @@ export default function ProfileDetailPage({ locale, profile: p }: Props) { ); } - const email = getAuth()?.user?.email || 'user@example.com'; + const email = getAuth()?.user?.email || ''; return (
@@ -165,7 +165,7 @@ export default function ProfileDetailPage({ locale, profile: p }: Props) { {displayName} ) : (
- {(displayName || 'U')[0].toUpperCase()} + {displayName ? displayName[0].toUpperCase() : '?'}
)}

{p.avatarTitle}

@@ -205,7 +205,7 @@ export default function ProfileDetailPage({ locale, profile: p }: Props) { email

{p.emailLabel}

-

{email}

+

{email || '-'}

diff --git a/web/src/components/SettingsPage.tsx b/web/src/components/SettingsPage.tsx index 2458bfd..0a2a3e5 100644 --- a/web/src/components/SettingsPage.tsx +++ b/web/src/components/SettingsPage.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { logout } from '../lib/auth'; +import { logout, getAuth } from '../lib/auth'; import { getUserProfile, getPointsBalance, type UserProfile, type PointsBalance } from '../lib/api'; interface Props { @@ -37,8 +37,9 @@ export default function SettingsPage({ locale, settings: s }: Props) { } }; - const displayName = profile?.display_name || profile?.email?.split('@')[0] || 'User'; - const email = profile?.email || 'user@example.com'; + const authEmail = getAuth()?.user?.email; + const displayName = loading ? '' : (profile?.display_name || profile?.email?.split('@')[0] || authEmail?.split('@')[0] || ''); + const email = loading ? '' : (profile?.email || authEmail || ''); const bio = profile?.bio || ''; return ( @@ -71,13 +72,13 @@ export default function SettingsPage({ locale, settings: s }: Props) { {displayName} ) : (
- {displayName[0].toUpperCase()} + {displayName ? displayName[0].toUpperCase() : '?'}
)} {/* Name & Email */}
-

{displayName}

-

{email}

+

{loading ? '...' : (displayName || '-')}

+

{loading ? '...' : (email || '-')}

{/* Edit Profile Button */}