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:
@@ -348,19 +348,26 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
|
|||||||
|
|
||||||
// Convert S3 keys to presigned download URLs for message attachments
|
// Convert S3 keys to presigned download URLs for message attachments
|
||||||
useEffect(() => {
|
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 () => {
|
const convertFileUrls = async () => {
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
if ((msg.messageType === 'IMAGE' || msg.messageType === 'FILE') && msg.fileUrl) {
|
if (msg.messageType !== 'IMAGE' && msg.messageType !== 'FILE') continue;
|
||||||
// Skip if already converted
|
|
||||||
if (messageFileUrls[msg.id]) continue;
|
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://')) {
|
// Determine the source: fileUrl or content (some messages store S3 key in content)
|
||||||
setMessageFileUrls(prev => ({ ...prev, [msg.id]: msg.fileUrl! }));
|
const source = msg.fileUrl || msg.content;
|
||||||
|
if (!source) continue;
|
||||||
|
|
||||||
|
if (isUrl(source)) {
|
||||||
|
setMessageFileUrls(prev => ({ ...prev, [msg.id]: source }));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Convert S3 key to presigned download URL
|
|
||||||
|
if (isS3Key(source)) {
|
||||||
try {
|
try {
|
||||||
const presignedUrl = await uploadService.getPresignedDownloadUrl(msg.fileUrl);
|
const presignedUrl = await uploadService.getPresignedDownloadUrl(source);
|
||||||
setMessageFileUrls(prev => ({ ...prev, [msg.id]: presignedUrl }));
|
setMessageFileUrls(prev => ({ ...prev, [msg.id]: presignedUrl }));
|
||||||
} catch {
|
} catch {
|
||||||
// Keep original if conversion fails
|
// Keep original if conversion fails
|
||||||
@@ -886,13 +893,31 @@ export function MessagingPage({ initialConversationId }: { initialConversationId
|
|||||||
>
|
>
|
||||||
{message.messageType === 'IMAGE' ? (
|
{message.messageType === 'IMAGE' ? (
|
||||||
messageFileUrls[message.id] ? (
|
messageFileUrls[message.id] ? (
|
||||||
|
<>
|
||||||
<img
|
<img
|
||||||
src={messageFileUrls[message.id]}
|
src={messageFileUrls[message.id]}
|
||||||
alt={message.fileName || 'Image'}
|
alt={message.fileName || 'Image'}
|
||||||
className="rounded-[12px] max-w-full cursor-pointer"
|
className="rounded-[12px] max-w-full cursor-pointer"
|
||||||
style={{ maxHeight: '250px' }}
|
style={{ maxHeight: '250px' }}
|
||||||
onClick={() => window.open(messageFileUrls[message.id], '_blank')}
|
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-[200px] h-[150px] rounded-[12px] overflow-hidden">
|
||||||
<div className="w-full h-full shimmer-loading rounded-[12px]" />
|
<div className="w-full h-full shimmer-loading rounded-[12px]" />
|
||||||
|
|||||||
Reference in New Issue
Block a user