messages and reviews
This commit is contained in:
166
frontend/src/pages/Messages.tsx
Normal file
166
frontend/src/pages/Messages.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Message, User } from '../types';
|
||||
import { messageAPI } from '../services/api';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import ChatWindow from '../components/ChatWindow';
|
||||
|
||||
const Messages: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const [messages, setMessages] = useState<Message[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [selectedRecipient, setSelectedRecipient] = useState<User | null>(null);
|
||||
const [showChat, setShowChat] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMessages();
|
||||
}, []);
|
||||
|
||||
const fetchMessages = async () => {
|
||||
try {
|
||||
const response = await messageAPI.getMessages();
|
||||
setMessages(response.data);
|
||||
} catch (err: any) {
|
||||
setError(err.response?.data?.error || 'Failed to fetch messages');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
|
||||
|
||||
if (diffInHours < 24) {
|
||||
return date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' });
|
||||
} else if (diffInHours < 48) {
|
||||
return 'Yesterday';
|
||||
} else {
|
||||
return date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMessageClick = async (message: Message) => {
|
||||
// Mark as read if unread
|
||||
if (!message.isRead) {
|
||||
try {
|
||||
await messageAPI.markAsRead(message.id);
|
||||
// Update local state
|
||||
setMessages(messages.map(m =>
|
||||
m.id === message.id ? { ...m, isRead: true } : m
|
||||
));
|
||||
} catch (err) {
|
||||
console.error('Failed to mark message as read:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// Open chat with sender
|
||||
if (message.sender) {
|
||||
setSelectedRecipient(message.sender);
|
||||
setShowChat(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="container mt-5">
|
||||
<div className="text-center">
|
||||
<div className="spinner-border" role="status">
|
||||
<span className="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className="container mt-4">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-md-8">
|
||||
<h1 className="mb-4">Messages</h1>
|
||||
|
||||
{error && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{messages.length === 0 ? (
|
||||
<div className="text-center py-5">
|
||||
<i className="bi bi-envelope" style={{ fontSize: '3rem', color: '#ccc' }}></i>
|
||||
<p className="text-muted mt-2">No messages in your inbox</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="list-group">
|
||||
{messages.map((message) => (
|
||||
<div
|
||||
key={message.id}
|
||||
className={`list-group-item list-group-item-action ${!message.isRead ? 'border-start border-primary border-4' : ''}`}
|
||||
onClick={() => handleMessageClick(message)}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
backgroundColor: !message.isRead ? '#f0f7ff' : 'white'
|
||||
}}
|
||||
>
|
||||
<div className="d-flex w-100 justify-content-between">
|
||||
<div className="d-flex align-items-center">
|
||||
{message.sender?.profileImage ? (
|
||||
<img
|
||||
src={message.sender.profileImage}
|
||||
alt={`${message.sender.firstName} ${message.sender.lastName}`}
|
||||
className="rounded-circle me-3"
|
||||
style={{ width: '40px', height: '40px', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
className="rounded-circle bg-secondary d-flex align-items-center justify-content-center me-3"
|
||||
style={{ width: '40px', height: '40px' }}
|
||||
>
|
||||
<i className="bi bi-person-fill text-white"></i>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="d-flex align-items-center">
|
||||
<h6 className={`mb-1 ${!message.isRead ? 'fw-bold' : ''}`}>
|
||||
{message.sender?.firstName} {message.sender?.lastName}
|
||||
</h6>
|
||||
{!message.isRead && (
|
||||
<span className="badge bg-primary ms-2">New</span>
|
||||
)}
|
||||
</div>
|
||||
<p className={`mb-1 text-truncate ${!message.isRead ? 'fw-semibold' : ''}`} style={{ maxWidth: '400px' }}>
|
||||
{message.subject}
|
||||
</p>
|
||||
<small className="text-muted text-truncate d-block" style={{ maxWidth: '400px' }}>
|
||||
{message.content}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<small className="text-muted">{formatDate(message.createdAt)}</small>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{selectedRecipient && (
|
||||
<ChatWindow
|
||||
show={showChat}
|
||||
onClose={() => {
|
||||
setShowChat(false);
|
||||
setSelectedRecipient(null);
|
||||
}}
|
||||
recipient={selectedRecipient}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Messages;
|
||||
Reference in New Issue
Block a user