diff --git a/web/src/components/auth/LoginPage.tsx b/web/src/components/auth/LoginPage.tsx index 2f911132..68d5c240 100644 --- a/web/src/components/auth/LoginPage.tsx +++ b/web/src/components/auth/LoginPage.tsx @@ -5,6 +5,7 @@ import { useAuth } from '../../contexts/AuthContext' import { useLanguage } from '../../contexts/LanguageContext' import { t } from '../../i18n/translations' import { DeepVoidBackground } from '../common/DeepVoidBackground' +import { LanguageSwitcher } from '../common/LanguageSwitcher' import { OnboardingModeSelector } from './OnboardingModeSelector' import type { UserMode } from '../../lib/onboarding' @@ -19,6 +20,14 @@ export function LoginPage() { const [expiredToastId, setExpiredToastId] = useState(null) const [mode, setMode] = useState('beginner') + // Clean up stale auth state once on mount + useEffect(() => { + localStorage.removeItem('auth_token') + localStorage.removeItem('auth_user') + localStorage.removeItem('user_id') + }, []) + + // Show session-expired toast (re-runs on language change to update text) useEffect(() => { if (sessionStorage.getItem('from401') === 'true') { const id = toast.warning(t('sessionExpired', language), { duration: Infinity }) @@ -44,6 +53,8 @@ export function LoginPage() { return ( + +
diff --git a/web/src/components/common/LanguageSwitcher.tsx b/web/src/components/common/LanguageSwitcher.tsx new file mode 100644 index 00000000..28dec7c0 --- /dev/null +++ b/web/src/components/common/LanguageSwitcher.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import { Globe } from 'lucide-react' +import { useLanguage } from '../../contexts/LanguageContext' +import type { Language } from '../../i18n/translations' + +const languages: { code: Language; label: string }[] = [ + { code: 'zh', label: '中文' }, + { code: 'en', label: 'EN' }, + { code: 'id', label: 'ID' }, +] + +export function LanguageSwitcher() { + const { language, setLanguage } = useLanguage() + + return ( +
+ + {languages.map(({ code, label }) => ( + + ))} +
+ ) +} diff --git a/web/src/components/modals/SetupPage.tsx b/web/src/components/modals/SetupPage.tsx index 19d9635f..45c22fb5 100644 --- a/web/src/components/modals/SetupPage.tsx +++ b/web/src/components/modals/SetupPage.tsx @@ -1,10 +1,11 @@ -import React, { useState } from 'react' -import { Eye, EyeOff, Globe } from 'lucide-react' +import React, { useState, useEffect } from 'react' +import { Eye, EyeOff } from 'lucide-react' import { useAuth } from '../../contexts/AuthContext' import { invalidateSystemConfig } from '../../lib/config' import { OnboardingModeSelector } from '../auth/OnboardingModeSelector' import type { UserMode } from '../../lib/onboarding' import { useLanguage } from '../../contexts/LanguageContext' +import { LanguageSwitcher } from '../common/LanguageSwitcher' import type { Language } from '../../i18n/translations' const labels = { @@ -49,14 +50,8 @@ const labels = { }, } as const -const langOptions: { value: Language; label: string }[] = [ - { value: 'en', label: 'English' }, - { value: 'zh', label: '中文' }, - { value: 'id', label: 'Bahasa' }, -] - export function SetupPage() { - const { language, setLanguage } = useLanguage() + const { language } = useLanguage() const { register } = useAuth() const [email, setEmail] = useState('') const [password, setPassword] = useState('') @@ -65,6 +60,15 @@ export function SetupPage() { const [loading, setLoading] = useState(false) const [mode, setMode] = useState('beginner') + // Clean up any stale auth/onboarding state on setup page load + useEffect(() => { + localStorage.removeItem('auth_token') + localStorage.removeItem('auth_user') + localStorage.removeItem('user_id') + localStorage.removeItem('nofx_beginner_onboarding_completed') + localStorage.removeItem('nofx_beginner_wallet_address') + }, []) + const l = labels[language as keyof typeof labels] || labels.en const handleSubmit = async (e: React.FormEvent) => { @@ -124,26 +128,7 @@ export function SetupPage() { {/* Blur overlay */}
- {/* Language switcher */} -
-
- - {langOptions.map((opt) => ( - - ))} -
-
+ {/* Modal card */}
diff --git a/web/src/contexts/AuthContext.tsx b/web/src/contexts/AuthContext.tsx index fe62c49f..af2bc2f3 100644 --- a/web/src/contexts/AuthContext.tsx +++ b/web/src/contexts/AuthContext.tsx @@ -1,6 +1,6 @@ import React, { createContext, useContext, useState, useEffect } from 'react' import { flushSync } from 'react-dom' -import { getSystemConfig } from '../lib/config' +import { getSystemConfig, invalidateSystemConfig } from '../lib/config' import { reset401Flag, httpClient } from '../lib/httpClient' import { getPostAuthPath, setUserMode, type UserMode } from '../lib/onboarding' import { useLanguage } from './LanguageContext' @@ -225,6 +225,10 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }>('/api/register', requestBody) if (result.success && result.data) { + // Clear stale onboarding state so new users always see the welcome flow + localStorage.removeItem('nofx_beginner_onboarding_completed') + localStorage.removeItem('nofx_beginner_wallet_address') + const userInfo = { id: result.data.user_id, email: result.data.email } handlePostAuthSuccess(result.data.token, userInfo, mode) @@ -290,6 +294,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { setToken(null) localStorage.removeItem('auth_token') localStorage.removeItem('auth_user') + invalidateSystemConfig() window.history.pushState({}, '', '/') window.dispatchEvent(new PopStateEvent('popstate')) }