## Summary - Fixed avatar showing anonymous for several seconds on page load - Eliminated hydration mismatch for authentication state - Header now shows correct user state immediately ## Root Cause AccountStateInitializer was running client-side in useEffect AFTER hydration, causing: 1. Server renders anonymous state 2. Client hydrates with anonymous state 3. useEffect runs and updates state (causing the flash) ## Solution - Read auth cookies server-side in layout.tsx - Pass initial auth data as props to AccountStateInitializer - Initialize Valtio state synchronously before first render - Client-side cookie reading only as fallback ## Changes - Added server-side cookie parsing in layout.tsx - Modified AccountStateInitializer to accept initial auth data props - Made Header component reactive with useSnapshot from Valtio - State initialization happens synchronously, preventing the flash ## Test plan - [x] Avatar renders correctly on first load - [x] No anonymous avatar flash when logged in - [x] Login/logout still works properly - [x] State updates are reactive in the header 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
106 lines
No EOL
2.8 KiB
TypeScript
106 lines
No EOL
2.8 KiB
TypeScript
'use client'
|
|
|
|
import { useEffect, useRef } from 'react'
|
|
import { getCookie } from 'cookies-next'
|
|
import { accountState } from '~utils/accountState'
|
|
import { setHeaders } from '~utils/userToken'
|
|
|
|
interface InitialAuthData {
|
|
account: {
|
|
token: string
|
|
userId: string
|
|
username: string
|
|
role: number
|
|
}
|
|
user: {
|
|
avatar: {
|
|
picture: string
|
|
element: string
|
|
}
|
|
gender: number
|
|
language: string
|
|
theme: string
|
|
bahamut?: boolean
|
|
}
|
|
}
|
|
|
|
interface AccountStateInitializerProps {
|
|
initialAuthData?: InitialAuthData | null
|
|
}
|
|
|
|
export default function AccountStateInitializer({ initialAuthData }: AccountStateInitializerProps) {
|
|
const initialized = useRef(false)
|
|
|
|
// Initialize synchronously on first render if we have server data
|
|
if (initialAuthData && !initialized.current) {
|
|
initialized.current = true
|
|
const { account: accountData, user: userData } = initialAuthData
|
|
|
|
console.log(`Logged in as user "${accountData.username}"`)
|
|
|
|
// Set headers for API calls
|
|
setHeaders()
|
|
|
|
// Update account state
|
|
accountState.account.authorized = true
|
|
accountState.account.user = {
|
|
id: accountData.userId,
|
|
username: accountData.username,
|
|
role: accountData.role,
|
|
granblueId: '',
|
|
avatar: {
|
|
picture: userData.avatar.picture,
|
|
element: userData.avatar.element,
|
|
},
|
|
gender: userData.gender,
|
|
language: userData.language,
|
|
theme: userData.theme,
|
|
bahamut: userData.bahamut || false,
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
// Only run client-side cookie reading if no server data
|
|
if (initialized.current) return
|
|
|
|
const accountCookie = getCookie('account')
|
|
const userCookie = getCookie('user')
|
|
|
|
if (accountCookie && userCookie) {
|
|
try {
|
|
const accountData = JSON.parse(accountCookie as string)
|
|
const userData = JSON.parse(userCookie as string)
|
|
|
|
if (accountData && accountData.token) {
|
|
console.log(`Logged in as user "${accountData.username}"`)
|
|
|
|
// Set headers for API calls
|
|
setHeaders()
|
|
|
|
// Update account state
|
|
accountState.account.authorized = true
|
|
accountState.account.user = {
|
|
id: accountData.userId,
|
|
username: accountData.username,
|
|
role: accountData.role,
|
|
granblueId: '',
|
|
avatar: {
|
|
picture: userData.avatar.picture,
|
|
element: userData.avatar.element,
|
|
},
|
|
gender: userData.gender,
|
|
language: userData.language,
|
|
theme: userData.theme,
|
|
bahamut: userData.bahamut || false,
|
|
}
|
|
|
|
initialized.current = true
|
|
}
|
|
} catch (error) {
|
|
console.error('Error parsing account cookies:', error)
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
return null
|
|
} |