This commit is contained in:
jackiettran
2025-12-11 20:05:18 -05:00
parent 11593606aa
commit b0268a2fb7
28 changed files with 2578 additions and 432 deletions

View File

@@ -5,7 +5,8 @@ import React, {
useRef,
useCallback,
} from "react";
import { messageAPI, getMessageImageUrl } from "../services/api";
import { messageAPI } from "../services/api";
import { getSignedUrl } from "../services/uploadService";
import { User, Message } from "../types";
import { useAuth } from "../contexts/AuthContext";
import { useSocket } from "../contexts/SocketContext";
@@ -46,6 +47,7 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
const [hasScrolledToUnread, setHasScrolledToUnread] = useState(false);
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const [imagePreview, setImagePreview] = useState<string | null>(null);
const [imageUrls, setImageUrls] = useState<Map<string, string>>(new Map());
const messagesEndRef = useRef<HTMLDivElement>(null);
const messagesContainerRef = useRef<HTMLDivElement>(null);
const messageRefs = useRef<Map<string, HTMLDivElement>>(new Map());
@@ -189,6 +191,29 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
}
}, [messages, isRecipientTyping, isAtBottom, hasScrolledToUnread]);
// Pre-fetch signed URLs for private message images
useEffect(() => {
const fetchImageUrls = async () => {
const messagesWithImages = messages.filter(
(m) => m.imageFilename && !imageUrls.has(m.imageFilename)
);
if (messagesWithImages.length === 0) return;
const newUrls = new Map(imageUrls);
await Promise.all(
messagesWithImages.map(async (m) => {
const url = await getSignedUrl(m.imageFilename!);
newUrls.set(m.imageFilename!, url);
})
);
setImageUrls(newUrls);
};
fetchImageUrls();
}, [messages]);
const fetchMessages = async () => {
try {
// Fetch all messages between current user and recipient
@@ -525,27 +550,28 @@ const ChatWindow: React.FC<ChatWindowProps> = ({
wordBreak: "break-word",
}}
>
{message.imageFilename && (
<div className="mb-2">
<img
src={getMessageImageUrl(message.imageFilename)}
alt="Shared image"
style={{
width: "100%",
borderRadius: "8px",
cursor: "pointer",
maxHeight: "300px",
objectFit: "cover",
}}
onClick={() =>
window.open(
getMessageImageUrl(message.imageFilename!),
"_blank"
)
}
/>
</div>
)}
{message.imageFilename &&
imageUrls.has(message.imageFilename) && (
<div className="mb-2">
<img
src={imageUrls.get(message.imageFilename)}
alt="Shared image"
style={{
width: "100%",
borderRadius: "8px",
cursor: "pointer",
maxHeight: "300px",
objectFit: "cover",
}}
onClick={() =>
window.open(
imageUrls.get(message.imageFilename!),
"_blank"
)
}
/>
</div>
)}
{message.content.trim() && (
<p className="mb-1" style={{ fontSize: "0.95rem" }}>
{message.content}

View File

@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { ForumComment } from "../types";
import CommentForm from "./CommentForm";
import { getForumImageUrl } from "../services/api";
import { getPublicImageUrl } from "../services/uploadService";
interface CommentThreadProps {
comment: ForumComment;
@@ -217,7 +217,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
{comment.imageFilenames.map((image, index) => (
<div key={index} className="col-4 col-md-3">
<img
src={getForumImageUrl(image)}
src={getPublicImageUrl(image)}
alt={`Comment image`}
className="img-fluid rounded"
style={{
@@ -227,7 +227,7 @@ const CommentThread: React.FC<CommentThreadProps> = ({
cursor: "pointer",
}}
onClick={() =>
window.open(getForumImageUrl(image), "_blank")
window.open(getPublicImageUrl(image), "_blank")
}
/>
</div>

View File

@@ -1,6 +1,7 @@
import React from 'react';
import { Link } from 'react-router-dom';
import { Item } from '../types';
import { getPublicImageUrl } from '../services/uploadService';
interface ItemCardProps {
item: Item;
@@ -49,12 +50,13 @@ const ItemCard: React.FC<ItemCardProps> = ({
<div className="card h-100" style={{ cursor: 'pointer' }}>
{item.imageFilenames && item.imageFilenames[0] ? (
<img
src={item.imageFilenames[0]}
src={getPublicImageUrl(item.imageFilenames[0])}
className="card-img-top"
alt={item.name}
style={{
height: isCompact ? '150px' : '200px',
objectFit: 'cover'
style={{
height: isCompact ? '150px' : '200px',
objectFit: 'contain',
backgroundColor: '#f8f9fa'
}}
/>
) : (

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { Item } from '../types';
import { getPublicImageUrl } from '../services/uploadService';
interface ItemMarkerInfoProps {
item: Item;
@@ -31,12 +32,13 @@ const ItemMarkerInfo: React.FC<ItemMarkerInfoProps> = ({ item, onViewDetails })
<div className="card border-0">
{item.imageFilenames && item.imageFilenames[0] ? (
<img
src={item.imageFilenames[0]}
src={getPublicImageUrl(item.imageFilenames[0])}
className="card-img-top"
alt={item.name}
style={{
height: '120px',
objectFit: 'cover',
style={{
height: '120px',
objectFit: 'contain',
backgroundColor: '#f8f9fa',
borderRadius: '8px 8px 0 0'
}}
/>