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