fix: AI chat authentication on iOS Safari by using global auth state

Problem:
- Floating chat had its own separate authentication check using localStorage
- iOS Safari has restrictions on localStorage access, especially with tracking prevention
- This caused logged-in users to still see login prompt in AI chat

Solution:
- Replace local auth state management with global useAuth hook from AuthProvider
- Remove redundant checkAuthStatus() function
- Update all authentication checks to use isAuthenticated from useAuth
- Update token retrieval to get directly from localStorage only when needed

Benefits:
- Single source of truth for authentication across the app
- More reliable authentication state management
- Better compatibility with iOS Safari's privacy features
- Automatic auth state synchronization when user logs in/out

Changes:
- Use useAuth hook instead of local isAuthenticated/authToken state
- Remove checkAuthStatus() function
- Update loadConversations to read token from localStorage directly
- Update loadConversation to read token from localStorage directly
- Update handleSendMessage to read token from localStorage directly
- Simplify handleAuthSuccess to rely on global auth state updates

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-13 07:00:54 +00:00
parent a0ed2b62ce
commit 65d868a7dd

View File

@@ -46,6 +46,7 @@ import { useState, useRef, useEffect, useCallback } from 'react'
import { useTranslations, useLocale } from 'next-intl'
import ReactMarkdown from 'react-markdown'
import { AuthModal } from '@/components/auth/auth-modal'
import { useAuth } from '@/hooks/use-auth'
// Random Bible-related loading messages
const LOADING_MESSAGES = [
@@ -77,6 +78,7 @@ export default function FloatingChat() {
const theme = useTheme()
const t = useTranslations('chat')
const locale = useLocale()
const { user, isAuthenticated } = useAuth() // Use global auth state
const [isOpen, setIsOpen] = useState(false)
const [isMinimized, setIsMinimized] = useState(false)
const [isFullscreen, setIsFullscreen] = useState(false)
@@ -98,8 +100,6 @@ export default function FloatingChat() {
// Conversation management state
const [conversations, setConversations] = useState<Conversation[]>([])
const [activeConversationId, setActiveConversationId] = useState<string | null>(null)
const [isAuthenticated, setIsAuthenticated] = useState(false)
const [authToken, setAuthToken] = useState<string | null>(null)
const [isLoadingConversations, setIsLoadingConversations] = useState(false)
const [menuAnchorEl, setMenuAnchorEl] = useState<HTMLElement | null>(null)
const [selectedConversationId, setSelectedConversationId] = useState<string | null>(null)
@@ -144,56 +144,22 @@ export default function FloatingChat() {
return () => window.removeEventListener('auth:sign-in-required', handler as EventListener)
}, [])
// Check authentication status
useEffect(() => {
checkAuthStatus()
}, [])
// Load conversations when authenticated
useEffect(() => {
if (isAuthenticated && authToken) {
if (isAuthenticated) {
loadConversations()
}
}, [isAuthenticated, authToken, locale])
const checkAuthStatus = useCallback(async () => {
try {
const token = localStorage.getItem('authToken')
if (token) {
// Verify token with the server
const response = await fetch('/api/auth/me', {
headers: {
'Authorization': `Bearer ${token}`
}
})
if (response.ok) {
setAuthToken(token)
setIsAuthenticated(true)
} else {
localStorage.removeItem('authToken')
setIsAuthenticated(false)
setAuthToken(null)
}
} else {
setIsAuthenticated(false)
setAuthToken(null)
}
} catch (error) {
console.error('Chat - Auth check failed:', error)
setIsAuthenticated(false)
setAuthToken(null)
}
}, [])
}, [isAuthenticated, locale])
const loadConversations = useCallback(async () => {
if (!authToken) return
const token = localStorage.getItem('authToken')
if (!token) return
setIsLoadingConversations(true)
try {
const response = await fetch(`/api/chat/conversations?language=${locale}&limit=20`, {
headers: {
'Authorization': `Bearer ${authToken}`
'Authorization': `Bearer ${token}`
}
})
@@ -208,15 +174,16 @@ export default function FloatingChat() {
} finally {
setIsLoadingConversations(false)
}
}, [authToken, locale])
}, [locale])
const loadConversation = useCallback(async (conversationId: string) => {
if (!authToken) return
const token = localStorage.getItem('authToken')
if (!token) return
try {
const response = await fetch(`/api/chat/conversations/${conversationId}`, {
headers: {
'Authorization': `Bearer ${authToken}`
'Authorization': `Bearer ${token}`
}
})
@@ -239,7 +206,7 @@ export default function FloatingChat() {
} catch (error) {
console.error('Error loading conversation:', error)
}
}, [authToken])
}, [])
const createNewConversation = useCallback(() => {
// Reset to a new conversation
@@ -369,12 +336,9 @@ export default function FloatingChat() {
}
// Add authentication if available
console.log('Chat - authToken value:', authToken ? 'present' : 'null')
if (authToken) {
headers['Authorization'] = `Bearer ${authToken}`
console.log('Chat - Authorization header added')
} else {
console.log('Chat - No authToken, skipping Authorization header')
const token = localStorage.getItem('authToken')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const response = await fetch('/api/chat', {
@@ -456,8 +420,8 @@ export default function FloatingChat() {
const handleAuthSuccess = () => {
setAuthModalOpen(false)
// Re-check auth status to update the UI
checkAuthStatus()
// Auth state will be updated automatically by the global useAuth hook
// Load conversations will trigger automatically via useEffect when isAuthenticated changes
}
return (