Skip to content
38 changes: 19 additions & 19 deletions src/components/MessageInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
const textareaRef = useRef<HTMLTextAreaElement>(null);
const fileInputRef = useRef<HTMLInputElement>(null);

const sendMessage = useSendMessage();
const { startTyping, stopTyping } = useTypingIndicator(conversationId);

Expand All @@ -32,17 +32,17 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
// Typing indicator logic
useEffect(() => {
let typingTimeout: NodeJS.Timeout;

if (message.trim()) {
startTyping();

typingTimeout = setTimeout(() => {
stopTyping();
}, 3000);
} else {
stopTyping();
}

return () => {
if (typingTimeout) clearTimeout(typingTimeout);
stopTyping();
Expand All @@ -51,17 +51,17 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr

const handleSend = async () => {
if (!message.trim() || sendMessage.isPending) return;

const messageContent = message.trim();
setMessage('');

try {
await sendMessage.mutateAsync({
conversation_id: conversationId,
content: messageContent,
reply_to_id: replyTo?.id,
});

if (onCancelReply) onCancelReply();
} catch (error) {
console.error('Failed to send message:', error);
Expand All @@ -78,25 +78,25 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr

const handleFileUpload = async (file: File) => {
if (!user) return;

setIsUploading(true);

try {
const fileExt = file.name.split('.').pop();
const fileName = `${user.id}/${Date.now()}.${fileExt}`;

const { error: uploadError } = await supabase.storage
.from('message-files')
.upload(fileName, file);

if (uploadError) throw uploadError;

const { data: { publicUrl } } = supabase.storage
.from('message-files')
.getPublicUrl(fileName);

const messageType = file.type.startsWith('image/') ? 'image' : 'file';

await sendMessage.mutateAsync({
conversation_id: conversationId,
content: messageType === 'image' ? 'πŸ“· Image' : `πŸ“Ž ${file.name}`,
Expand All @@ -105,7 +105,7 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
file_name: file.name,
reply_to_id: replyTo?.id,
});

if (onCancelReply) onCancelReply();
} catch (error) {
console.error('Failed to upload file:', error);
Expand All @@ -122,7 +122,7 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
};

const commonEmojis = [
'πŸ˜€', 'πŸ˜‚', '😍', 'πŸ€”', '😒', '😑', 'πŸ‘', 'πŸ‘Ž',
'πŸ˜€', 'πŸ˜‚', '😍', 'πŸ€”', '😒', '😑', 'πŸ‘', 'πŸ‘Ž',
'❀️', 'πŸŽ‰', 'πŸ”₯', 'πŸ’―', 'πŸ‘', 'πŸ™', 'πŸ’ͺ', '🎯'
];

Expand Down Expand Up @@ -179,7 +179,7 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
>
<Paperclip className="w-5 h-5" />
</button>

<button
onClick={() => {
if (fileInputRef.current) {
Expand All @@ -203,10 +203,10 @@ const MessageInput = ({ conversationId, replyTo, onCancelReply }: MessageInputPr
onChange={(e) => setMessage(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="Type a message..."
className="w-full px-4 py-3 pr-12 bg-slate-800/50 border border-slate-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-cyan-400/50 focus:ring-1 focus:ring-cyan-400/50 resize-none min-h-[48px] max-h-32"
className="input-slate pr-12 resize-none min-h-12 max-h-32"
rows={1}
/>

{/* Emoji Button */}
<button
onClick={() => setShowEmojiPicker(!showEmojiPicker)}
Expand Down
16 changes: 8 additions & 8 deletions src/components/MessagingInterface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,17 @@ const MessagingInterface = () => {
// Filter conversations based on search
const filteredConversations = conversations.filter(conv => {
if (!searchQuery) return true;

if (conv.name) {
return conv.name.toLowerCase().includes(searchQuery.toLowerCase());
}

// For direct messages, search by participant names
const participantNames = conv.participants
?.filter(p => p.user_id !== user?.id)
.map(p => p.user?.user_metadata?.full_name || p.user?.user_metadata?.user_name || p.user?.email)
.join(' ') || '';

return participantNames.toLowerCase().includes(searchQuery.toLowerCase());
});

Expand Down Expand Up @@ -75,7 +75,7 @@ const MessagingInterface = () => {
<Plus className="w-5 h-5" />
</button>
</div>

{/* Search */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-gray-400" />
Expand All @@ -84,7 +84,7 @@ const MessagingInterface = () => {
placeholder="Search conversations..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-10 pr-4 py-2 bg-slate-800/50 border border-slate-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-cyan-400/50 focus:ring-1 focus:ring-cyan-400/50"
className="input-slate pl-10"
/>
</div>
</div>
Expand Down Expand Up @@ -125,7 +125,7 @@ const MessagingInterface = () => {
) : (
<MessageList messages={messages} />
)}

{/* Typing Indicators */}
{typingUsers.length > 0 && (
<div className="flex items-center gap-2 text-gray-400 text-sm">
Expand All @@ -135,12 +135,12 @@ const MessagingInterface = () => {
<div className="w-2 h-2 bg-cyan-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }} />
</div>
<span>
{typingUsers.map(t => t.user?.user_metadata?.full_name || t.user?.user_metadata?.user_name).join(', ')}
{typingUsers.map(t => t.user?.user_metadata?.full_name || t.user?.user_metadata?.user_name).join(', ')}
{typingUsers.length === 1 ? ' is' : ' are'} typing...
</span>
</div>
)}

<div ref={messagesEndRef} />
</div>

Expand Down
24 changes: 12 additions & 12 deletions src/components/PostDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const fetchPost = async (postId: number): Promise<Post> => {
.select('*')
.eq('id', postId)
.single();

if (error) {
throw new Error("Error fetching post: " + error.message);
}
Expand All @@ -35,31 +35,31 @@ const fetchPost = async (postId: number): Promise<Post> => {
const PostDetail = ({ postId }: PostDetailProps) => {
const navigate = useNavigate();
const [likeCount, setLikeCount] = useState(0);

const { data: post, error, isLoading } = useQuery<Post, Error>({
queryKey: ["post", postId],
queryFn: () => fetchPost(postId)
});

if (isLoading) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="flex-center min-h-screen">
<div className="text-lg text-gray-600">Loading post...</div>
</div>
);
}

if (error) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="flex-center min-h-screen">
<div className="text-lg text-red-600">Error loading post: {error.message}</div>
</div>
);
}

if (!post) {
return (
<div className="flex justify-center items-center min-h-screen">
<div className="flex-center min-h-screen">
<div className="text-lg text-gray-600">Post not found</div>
</div>
);
Expand All @@ -86,7 +86,7 @@ const PostDetail = ({ postId }: PostDetailProps) => {
{/* Back Button */}
<button
onClick={() => navigate(-1)}
className="flex items-center gap-2 text-gray-700 hover:text-gray-900 mb-4 transition"
className="flex-gap-2 text-gray-700 hover:text-gray-900 mb-4 transition"
>
<ArrowLeft className="w-5 h-5" />
<span className="font-medium">Back</span>
Expand All @@ -95,10 +95,10 @@ const PostDetail = ({ postId }: PostDetailProps) => {
{/* Post Card */}
<div className="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden">
{/* Header: Avatar and Title */}
<div className="flex items-center p-4 border-b border-gray-200">
<div className="flex-gap-2 p-4 border-b border-gray-200">
{post.avatar_url ? (
<img
src={post.avatar_url}
<img
src={post.avatar_url}
alt="User avatar"
className="w-10 h-10 rounded-full mr-3 shrink-0 object-cover"
/>
Expand All @@ -114,9 +114,9 @@ const PostDetail = ({ postId }: PostDetailProps) => {
{/* Image */}
{post.image_url && (
<div className="w-full bg-gray-100">
<img
src={post.image_url}
alt={post.title}
<img
src={post.image_url}
alt={post.title}
className="w-full h-auto object-contain max-h-[600px]"
/>
</div>
Expand Down
Loading