messages and reviews

This commit is contained in:
jackiettran
2025-07-17 00:16:01 -04:00
parent aa3adc58ca
commit 1dbe821e70
21 changed files with 1981 additions and 102 deletions

View 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;