From 8cb49167f47f8a9260c45c0384842f2aa806ef1c Mon Sep 17 00:00:00 2001 From: Ace Date: Thu, 18 Jun 2026 12:25:28 +0200 Subject: [PATCH 1/2] added Voice, blueticks, fixed messages, fixed search conversation, added scroll functionality, fixed file uploads & download added in screenshot paste function too, changed back to the orignal emoji pack --- src/app/(dashboard)/chats/page.tsx | 617 ++++++++++++++++++++++++----- src/app/globals.css | 3 + src/components/ui/scroll-area.tsx | 8 +- 3 files changed, 525 insertions(+), 103 deletions(-) diff --git a/src/app/(dashboard)/chats/page.tsx b/src/app/(dashboard)/chats/page.tsx index d8eadf9..2095f61 100644 --- a/src/app/(dashboard)/chats/page.tsx +++ b/src/app/(dashboard)/chats/page.tsx @@ -17,7 +17,8 @@ import { import { conversations as conversationsData } from "@/data/chats" import { Search, Send, Phone, Video, MoreHorizontal, Paperclip, - Smile, Flag, Ban, Trash2, Image, File, X, + Smile, Flag, Ban, Trash2, Image, FileIcon, X, Mic, Square, Play, Pause, Check, CheckCheck, + CornerDownRight, Forward, Pencil, Download, } from "lucide-react" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, @@ -31,6 +32,65 @@ import { toast } from "sonner" import data from "@emoji-mart/data" import Picker from "@emoji-mart/react" +function VoiceMessagePlayer({ src, initialDuration }: { src: string; initialDuration: number }) { + const [playing, setPlaying] = useState(false) + const [current, setCurrent] = useState(0) + const [duration, setDuration] = useState(initialDuration) + const audioRef = useRef(null) + const animRef = useRef(0) + + useEffect(() => { + const audio = audioRef.current + if (!audio) return + const onLoaded = () => { + if (isFinite(audio.duration)) setDuration(audio.duration) + } + const onEnded = () => { setPlaying(false); setCurrent(0) } + audio.addEventListener("loadedmetadata", onLoaded) + audio.addEventListener("ended", onEnded) + return () => { + audio.removeEventListener("loadedmetadata", onLoaded) + audio.removeEventListener("ended", onEnded) + } + }, [src]) + + useEffect(() => { + if (!playing) { cancelAnimationFrame(animRef.current); return } + const tick = () => { + if (audioRef.current) setCurrent(audioRef.current.currentTime) + animRef.current = requestAnimationFrame(tick) + } + animRef.current = requestAnimationFrame(tick) + return () => cancelAnimationFrame(animRef.current) + }, [playing]) + + const toggle = () => { + if (!audioRef.current) return + if (playing) { audioRef.current.pause(); setPlaying(false) } + else { audioRef.current.play(); setPlaying(true) } + } + + const displayDuration = isFinite(duration) && duration > 0 ? duration : initialDuration + const pct = displayDuration > 0 ? (current / displayDuration) * 100 : 0 + const fmt = (s: number) => { + const secs = isFinite(s) ? Math.max(0, Math.floor(s)) : 0 + return `${Math.floor(secs / 60)}:${(secs % 60).toString().padStart(2, "0")}` + } + + return ( +
+
- {conversations.map((conv) => { + {filteredConversations.map((conv) => { const person = otherParticipant(conv) const isActive = conv.id === activeChat return ( @@ -180,6 +430,9 @@ export default function ChatsPage() { ) })} + {filteredConversations.length === 0 && searchQuery && ( +
No conversations found
+ )}
@@ -206,16 +459,10 @@ export default function ChatsPage() {
- - @@ -234,10 +481,7 @@ export default function ChatsPage() { Block - toast.info("Conversation deleted")} - > + toast.info("Conversation deleted")}> Delete @@ -246,12 +490,13 @@ export default function ChatsPage() {
{/* Messages */} - -
+
+
{conversation.messages.map((msg) => { const isMe = msg.senderId === "user1" + const voiceUrl = voiceMessages.get(msg.id) return ( -
+
{isMe ? : null} @@ -261,92 +506,208 @@ export default function ChatsPage() {
- {msg.content} + {!isMe && ( + + + + + + setReplyingTo(msg)}> + Reply + + { setForwardMessage(msg); setForwardDialogOpen(true) }}> + Forward + + + + )} + {isMe && ( + + + + + + { setEditingMessage(msg); setMessageInput(msg.content) }}> + Edit + + setReplyingTo(msg)}> + Reply + + { setForwardMessage(msg); setForwardDialogOpen(true) }}> + Forward + + + handleDeleteMessage(msg.id)}> + Delete + + + + )} + {(() => { + const replyInfo = replyMap.get(msg.id) + return ( + <> + {replyInfo && ( +
+

{replyInfo.repliedToSender}

+

{replyInfo.repliedToContent}

+
+ )} + {voiceUrl ? ( +
+ + {msg.content && ( +

{msg.content}

+ )} +
+ ) : ( + <> + {(() => { + const files = messageAttachments.get(msg.id) + return files && files.length > 0 ? ( +
+ {files.map((file, i) => + file.type.startsWith("image/") ? ( +
+ {file.name} window.open(file.url, "_blank")} /> + + + +
+ ) : ( + + + {file.name} + + + ) + )} +
+ ) : null + })()} + {isOnlyEmoji(msg.content) ? ( + {msg.content.trim()} + ) : ( + msg.content + )} + + )} + + ) + })()}
- {msg.timestamp} + + {msg.timestamp} + {isMe && ( + (() => { + const status = messageStatus.get(msg.id) + if (status === "read") return + if (status === "delivered") return + return + })() + )} +
) })} +
- +
{/* Input */}
{attachments.length > 0 && (
{attachments.map((file, i) => ( -
+
{isImageFile(file) ? ( ) : ( - + )} {file.name} {formatFileSize(file.size)} -
))}
)} + {/* Reply bar */} + {replyingTo && ( +
+
+

+ Replying to {replyingTo.senderId === "user1" ? "yourself" : otherParticipant(conversation).name} +

+

{replyingTo.content}

+
+ +
+ )} + {editingMessage && ( +
+ + Editing message + +
+ )}
- -
- setMessageInput(e.target.value)} - placeholder="Type a message..." - className="h-9 w-full pr-9" - /> -
- - {showEmojiPicker && ( -
- + {isRecording ? ( +
+ + {formatDuration(recordingDuration)} +
+ + + +
+ ) : ( + <> +