Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion app/protected/messages/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,14 @@ export default function MessagesPage() {
<div className="border-b p-4 bg-muted/50 flex-shrink-0">
<div className="flex items-center gap-3">
<h2 className="font-semibold">{conversationName}</h2>
{!selectedConversation.is_group && selectedConversation.other_user && (
{!selectedConversation.is_group && selectedConversation.other_user && selectedConversation.other_user.id ? (
<UserStatusIndicator
userId={selectedConversation.other_user.id}
showLastSeen={true}
size="sm"
/>
) : !selectedConversation.is_group && (
<span className="text-xs text-red-500">No user data</span>
)}
</div>
</div>
Expand Down
19 changes: 18 additions & 1 deletion components/messages/ConversationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ interface ConversationListProps {
}

export function ConversationList({ conversations, selectedId, onSelect, loading }: ConversationListProps) {
// Debug: Log conversations data (must be before any returns)
React.useEffect(() => {
if (!loading && conversations.length > 0) {
console.log('ConversationList conversations:', conversations.map(c => ({
id: c.id,
is_group: c.is_group,
other_user: c.other_user ? {
id: c.other_user.id,
username: c.other_user.username
} : null
})))
}
}, [conversations, loading])

if (loading) {
return (
<div className="space-y-2 p-2">
Expand Down Expand Up @@ -85,11 +99,14 @@ export function ConversationList({ conversations, selectedId, onSelect, loading
{initials}
</AvatarFallback>
</Avatar>
{!conversation.is_group && otherUser && (
{!conversation.is_group && otherUser && otherUser.id && (
<div className="absolute bottom-0 right-0">
<UserStatusIndicator userId={otherUser.id} size="sm" />
</div>
)}
{!conversation.is_group && !otherUser && (
<div className="absolute bottom-0 right-0 w-2 h-2 bg-red-500 rounded-full" title="No user data" />
)}
</div>

<div className="flex-1 min-w-0">
Expand Down
21 changes: 20 additions & 1 deletion components/messages/UserStatusIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,26 @@ export function UserStatusIndicator({
}: UserStatusIndicatorProps) {
const { presence, loading } = useUserPresence(userId)

if (loading || !presence) return null
// Debug logging
React.useEffect(() => {
if (presence) {
console.log('UserStatusIndicator for', userId, ':', {
isOnline: presence.isOnline,
lastSeen: presence.lastSeen,
loading
})
}
}, [presence, userId, loading])

if (loading) {
console.log('UserStatusIndicator loading for', userId)
return null
}

if (!presence) {
console.log('UserStatusIndicator no presence data for', userId)
return null
}

const sizeClasses = {
sm: 'w-2 h-2',
Expand Down
85 changes: 69 additions & 16 deletions hooks/useUserPresence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,35 @@ export function useUserPresence(userId: string | null) {
.from('user_presence')
.select('*')
.eq('user_id', userId)
.single()
.maybeSingle() // Use maybeSingle instead of single to handle no results

if (error) {
console.error('Error fetching presence:', error)
}

if (data) {
console.log('Fetched presence for user:', userId, data)
setPresence({
userId: data.user_id,
isOnline: data.is_online,
lastSeen: data.last_seen
})
} else {
// No presence record yet - user is offline
console.log('No presence record for user:', userId)
setPresence({
userId: userId,
isOnline: false,
lastSeen: null
})
}
setLoading(false)
}

fetchPresence()

// Refresh presence every 10 seconds to catch updates
const refreshInterval = setInterval(fetchPresence, 10000)

// Subscribe to presence changes
const subscription = supabase
Expand All @@ -52,9 +68,15 @@ export function useUserPresence(userId: string | null) {
filter: `user_id=eq.${userId}`
},
(payload) => {
console.log('🔔 Presence update received for', userId, payload)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newData = payload.new as any
if (newData) {
console.log('📡 Updating presence state:', {
userId: newData.user_id,
isOnline: newData.is_online,
lastSeen: newData.last_seen
})
setPresence({
userId: newData.user_id,
isOnline: newData.is_online,
Expand All @@ -63,10 +85,13 @@ export function useUserPresence(userId: string | null) {
}
}
)
.subscribe()
.subscribe((status) => {
console.log('Subscription status for', userId, ':', status)
})

return () => {
subscription.unsubscribe()
clearInterval(refreshInterval)
}
}, [userId])

Expand All @@ -85,37 +110,65 @@ export function useMyPresence() {

// Set user as online
const setOnline = async () => {
await supabase
const { error } = await supabase
.from('user_presence')
.upsert({
user_id: user.id,
is_online: true,
last_seen: new Date().toISOString()
})
.upsert(
{
user_id: user.id,
is_online: true,
last_seen: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
onConflict: 'user_id'
}
)

if (error) {
console.error('Error setting online:', error)
} else {
console.log('✅ Set user online:', user.id)
}
}

// Set user as offline
const setOffline = async () => {
await supabase
const { error } = await supabase
.from('user_presence')
.upsert({
user_id: user.id,
is_online: false,
last_seen: new Date().toISOString()
})
.upsert(
{
user_id: user.id,
is_online: false,
last_seen: new Date().toISOString(),
updated_at: new Date().toISOString()
},
{
onConflict: 'user_id'
}
)

if (error) {
console.error('Error setting offline:', error)
} else {
console.log('❌ Set user offline:', user.id)
}
}

// Update presence every 30 seconds
// More frequent updates ensure status is current
const updatePresence = async () => {
if (document.visibilityState === 'visible') {
await setOnline()
}
}

// Set online on mount
// Set online immediately on mount
setOnline()

// Also set online after a short delay to ensure it's registered
setTimeout(setOnline, 1000)

// Update presence periodically
// Update presence periodically (every 30 seconds)
const interval = setInterval(updatePresence, 30000)

// Handle visibility change
Expand Down
Loading