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
46 changes: 37 additions & 9 deletions app/protected/messages/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,24 @@ export default function MessagesPage() {
<div className="max-w-7xl mx-auto flex items-center justify-between">
<div className="flex items-center gap-3">
<MessageSquare className="h-6 w-6 text-primary" />
<h1 className="text-2xl font-bold">Messages</h1>
<h1 className="text-xl md:text-2xl font-bold">Messages</h1>
</div>
<Button onClick={() => setShowNewMessage(true)} className="gap-2">
<Plus className="h-4 w-4" />
New Message
<span className="hidden sm:inline">New Message</span>
<span className="sm:hidden">New</span>
</Button>
</div>
</div>

{/* Main Content */}
<div className="flex-1 overflow-hidden">
<div className="max-w-7xl mx-auto h-full flex">
{/* Sidebar - Conversation List */}
<div className="w-80 border-r flex flex-col bg-background">
{/* Sidebar - Conversation List (Hidden on mobile when conversation is selected) */}
<div className={`
w-full md:w-80 md:border-r flex flex-col bg-background
${selectedConversationId ? 'hidden md:flex' : 'flex'}
`}>
{/* Search */}
<div className="p-3 border-b">
<div className="relative">
Expand All @@ -97,12 +101,36 @@ export default function MessagesPage() {
</div>
</div>

{/* Main Area - Conversation View */}
<div className="flex-1 bg-background flex flex-col">
{/* Main Area - Conversation View (Hidden on mobile when no conversation selected) */}
<div className={`
flex-1 bg-background flex flex-col
${selectedConversationId ? 'flex' : 'hidden md:flex'}
`}>
{selectedConversationId && selectedConversation && (
<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>
<div className="border-b p-3 md:p-4 bg-muted/50 flex-shrink-0">
<div className="flex items-center gap-2 md:gap-3">
{/* Back button for mobile */}
<Button
variant="ghost"
size="icon"
className="md:hidden"
onClick={() => setSelectedConversationId(null)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<path d="m15 18-6-6 6-6"/>
</svg>
</Button>
<h2 className="font-semibold text-sm md:text-base truncate">{conversationName}</h2>
{!selectedConversation.is_group && selectedConversation.other_user && selectedConversation.other_user.id ? (
<UserStatusIndicator
userId={selectedConversation.other_user.id}
Expand Down
8 changes: 4 additions & 4 deletions components/messages/ConversationList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,24 +111,24 @@ export function ConversationList({ conversations, selectedId, onSelect, loading

<div className="flex-1 min-w-0">
<div className="flex items-center justify-between mb-1">
<span className={cn('font-semibold truncate', conversation.unread_count > 0 && 'text-primary')}>
<span className={cn('font-semibold truncate text-sm md:text-base', conversation.unread_count > 0 && 'text-primary')}>
{name}
</span>
{conversation.last_message_at && (
<span className="text-xs text-muted-foreground flex-shrink-0 ml-2">
<span className="text-[10px] md:text-xs text-muted-foreground flex-shrink-0 ml-2">
{formatDistanceToNow(new Date(conversation.last_message_at), { addSuffix: true })}
</span>
)}
</div>
<div className="flex items-center justify-between">
<p className={cn(
'text-sm truncate',
'text-xs md:text-sm truncate',
conversation.unread_count > 0 ? 'font-medium text-foreground' : 'text-muted-foreground'
)}>
{conversation.last_message_content || 'No messages yet'}
</p>
{conversation.unread_count > 0 && (
<Badge variant="default" className="ml-2 flex-shrink-0">
<Badge variant="default" className="ml-2 flex-shrink-0 text-xs">
{conversation.unread_count}
</Badge>
)}
Expand Down
8 changes: 4 additions & 4 deletions components/messages/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export function MessageInput({ onSend, disabled, placeholder = 'Type a message..
}, [content, isTyping, onTyping])

return (
<form onSubmit={handleSubmit} className="border-t bg-background p-3">
<form onSubmit={handleSubmit} className="border-t bg-background p-2 md:p-3">
<div className="flex gap-2 items-end max-w-full">
<Textarea
ref={textareaRef}
Expand All @@ -99,14 +99,14 @@ export function MessageInput({ onSend, disabled, placeholder = 'Type a message..
onKeyDown={handleKeyDown}
placeholder={placeholder}
disabled={disabled || sending}
className="min-h-[44px] max-h-[120px] resize-none flex-1"
className="min-h-[40px] md:min-h-[44px] max-h-[120px] resize-none flex-1 text-sm md:text-base"
rows={1}
/>
<Button
type="submit"
size="icon"
disabled={!content.trim() || sending || disabled}
className="flex-shrink-0 h-11 w-11"
className="flex-shrink-0 h-10 w-10 md:h-11 md:w-11"
>
{sending ? (
<Loader2 className="h-4 w-4 animate-spin" />
Expand All @@ -115,7 +115,7 @@ export function MessageInput({ onSend, disabled, placeholder = 'Type a message..
)}
</Button>
</div>
<p className="text-xs text-muted-foreground mt-1.5">
<p className="text-[10px] md:text-xs text-muted-foreground mt-1 md:mt-1.5 hidden sm:block">
Press Enter to send, Shift+Enter for new line
</p>
</form>
Expand Down
12 changes: 1 addition & 11 deletions components/messages/TypingIndicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,14 @@ interface TypingIndicatorProps {
export function TypingIndicator({ usernames }: TypingIndicatorProps) {
if (usernames.length === 0) return null

const getTypingText = () => {
if (usernames.length === 1) {
return `${usernames[0]} is typing...`
} else if (usernames.length === 2) {
return `${usernames[0]} and ${usernames[1]} are typing...`
} else {
return `${usernames[0]} and ${usernames.length - 1} others are typing...`
}
}

return (
<div className="flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground">
<div className="flex gap-1">
<span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" style={{ animationDelay: '0ms' }} />
<span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" style={{ animationDelay: '150ms' }} />
<span className="w-2 h-2 bg-muted-foreground rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
<span>{getTypingText()}</span>
<span>Typing...</span>
</div>
)
}
19 changes: 17 additions & 2 deletions hooks/useMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export function useMessages(conversationId: string | null) {

const supabase = createClient()

console.log('📡 Setting up message subscription for conversation:', conversationId)

const subscription = supabase
.channel(`messages:${conversationId}`)
.on(
Expand All @@ -53,6 +55,8 @@ export function useMessages(conversationId: string | null) {
filter: `conversation_id=eq.${conversationId}`
},
async (payload) => {
console.log('🔔 New message received via realtime:', payload)

// Fetch the complete message with sender details
const { data } = await supabase
.from('messages')
Expand All @@ -70,6 +74,8 @@ export function useMessages(conversationId: string | null) {
.single()

if (data) {
console.log('📨 Fetched complete message data:', data)

// Decrypt the message content
const decryptResponse = await fetch('/api/messages/decrypt', {
method: 'POST',
Expand All @@ -79,21 +85,30 @@ export function useMessages(conversationId: string | null) {

const { decrypted } = await decryptResponse.json()
const decryptedMessage = { ...data, content: decrypted }

console.log('🔓 Decrypted message:', decrypted)

setMessages(prev => {
// Avoid duplicates
const exists = prev.some(msg => msg.id === data.id)
if (exists) return prev
if (exists) {
console.log('⚠️ Message already exists, skipping')
return prev
}
console.log('✅ Adding new message to state')
return [...prev, decryptedMessage as Message]
})
// Mark as read
await messageService.markAsRead(conversationId)
}
}
)
.subscribe()
.subscribe((status) => {
console.log('Message subscription status:', status)
})

return () => {
console.log('🔌 Unsubscribing from messages')
subscription.unsubscribe()
}
}, [conversationId])
Expand Down
Loading