feat: Improve message attachment handling by checking both fileUrl and content for S3 keys, and add an image loading error fallback UI.

This commit is contained in:
pradeepkumar
2026-03-20 01:49:46 +05:30
parent 0caf7af45b
commit d8e75f4a06

View File

@@ -348,19 +348,26 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
// Convert S3 keys to presigned download URLs for message attachments
useEffect(() => {
const isS3Key = (val: string) => val && !val.startsWith('http://') && !val.startsWith('https://') && !val.startsWith('/') && !val.startsWith('data:');
const isUrl = (val: string) => val.startsWith('http://') || val.startsWith('https://');
const convertFileUrls = async () => {
for (const msg of messages) {
if ((msg.messageType === 'IMAGE' || msg.messageType === 'FILE') && msg.fileUrl) {
// Skip if already converted
if (messageFileUrls[msg.id]) continue;
// Skip if it's already an accessible URL (e.g., Giphy GIF URLs)
if (msg.fileUrl.startsWith('http://') || msg.fileUrl.startsWith('https://')) {
setMessageFileUrls(prev => ({ ...prev, [msg.id]: msg.fileUrl! }));
continue;
}
// Convert S3 key to presigned download URL
if (msg.messageType !== 'IMAGE' && msg.messageType !== 'FILE') continue;
if (messageFileUrls[msg.id]) continue;
// Determine the source: fileUrl or content (some messages store S3 key in content)
const source = msg.fileUrl || msg.content;
if (!source) continue;
if (isUrl(source)) {
setMessageFileUrls(prev => ({ ...prev, [msg.id]: source }));
continue;
}
if (isS3Key(source)) {
try {
const presignedUrl = await uploadService.getPresignedDownloadUrl(msg.fileUrl);
const presignedUrl = await uploadService.getPresignedDownloadUrl(source);
setMessageFileUrls(prev => ({ ...prev, [msg.id]: presignedUrl }));
} catch {
// Keep original if conversion fails
@@ -886,13 +893,31 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
>
{message.messageType === 'IMAGE' ? (
messageFileUrls[message.id] ? (
<img
src={messageFileUrls[message.id]}
alt={message.fileName || 'Image'}
className="rounded-[12px] max-w-full cursor-pointer"
style={{ maxHeight: '250px' }}
onClick={() => window.open(messageFileUrls[message.id], '_blank')}
/>
<>
<img
src={messageFileUrls[message.id]}
alt={message.fileName || 'Image'}
className="rounded-[12px] max-w-full cursor-pointer"
style={{ maxHeight: '250px' }}
onClick={() => window.open(messageFileUrls[message.id], '_blank')}
onError={(e) => {
const target = e.target as HTMLImageElement;
target.style.display = 'none';
const fallback = target.nextElementSibling;
if (fallback) (fallback as HTMLElement).style.display = 'flex';
}}
/>
<div className="hidden items-center gap-2 px-3 py-2">
<Image src="/assets/icons/attachment-icon.svg" alt="Image" width={16} height={9} />
<span className="font-serif text-[13px]">{message.fileName || 'Image'}</span>
<button
onClick={() => window.open(messageFileUrls[message.id], '_blank')}
className="font-serif text-[12px] text-[#e58625] underline ml-1"
>
Download
</button>
</div>
</>
) : (
<div className="w-[200px] h-[150px] rounded-[12px] overflow-hidden">
<div className="w-full h-full shimmer-loading rounded-[12px]" />