s3
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -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'
|
||||
}}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user