feat(14-02): add clickable attachment links with signed URL fetching
- Import getMusicNotationUrl REST helper and useState/useCallback hooks - Add handleAttachmentClick handler that fetches signed S3 URL and opens in new tab - Implement loading state (isLoadingUrl) to prevent rapid clicks - Make filename a clickable link with underline and blue color - Add error handling that logs to console without crashing - Show 'wait' cursor during URL fetch - Browser handles file display based on Content-Type (PDF viewer, image display, audio player)
This commit is contained in:
parent
38fea32f22
commit
91cac19a52
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { formatTimestamp } from '../../../utils/formatTimestamp';
|
||||
import { formatFileSize } from '../../../services/attachmentValidation';
|
||||
import { getMusicNotationUrl } from '../../../helpers/rest';
|
||||
|
||||
/**
|
||||
* Get initials for avatar from sender name
|
||||
|
|
@ -40,7 +41,35 @@ const isAttachmentMessage = message => {
|
|||
* - React.memo for performance optimization
|
||||
*/
|
||||
const JKChatMessage = ({ message }) => {
|
||||
const { senderName, message: text, createdAt, attachmentName, attachmentSize } = message;
|
||||
const { senderName, message: text, createdAt, attachmentId, attachmentName, attachmentSize } = message;
|
||||
const [isLoadingUrl, setIsLoadingUrl] = useState(false);
|
||||
|
||||
/**
|
||||
* Handle attachment click - fetch signed S3 URL and open in new tab
|
||||
* @param {string} attachmentId - ID of the attachment to open
|
||||
*/
|
||||
const handleAttachmentClick = useCallback(
|
||||
async attachmentId => {
|
||||
if (isLoadingUrl) return; // Prevent rapid clicks
|
||||
|
||||
try {
|
||||
setIsLoadingUrl(true);
|
||||
const response = await getMusicNotationUrl(attachmentId);
|
||||
// Response is { url: "https://s3.amazonaws.com/...?signature=..." }
|
||||
if (response && response.url) {
|
||||
window.open(response.url, '_blank');
|
||||
} else {
|
||||
console.error('No URL in response:', response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch attachment URL:', error);
|
||||
// Could show toast error here, but keep it simple for now
|
||||
} finally {
|
||||
setIsLoadingUrl(false);
|
||||
}
|
||||
},
|
||||
[isLoadingUrl]
|
||||
);
|
||||
|
||||
// Common avatar style
|
||||
const avatarStyle = {
|
||||
|
|
@ -85,16 +114,41 @@ const JKChatMessage = ({ message }) => {
|
|||
<span style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>{senderName || 'Unknown'}</span>
|
||||
<span style={{ fontSize: '12px', color: '#6c757d' }}>{formatTimestamp(createdAt)}</span>
|
||||
</div>
|
||||
<div style={{ fontSize: '14px', color: '#212529', display: 'flex', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
fontSize: '14px',
|
||||
color: '#212529',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
gap: '4px'
|
||||
}}
|
||||
>
|
||||
<span style={fileIconStyle} role="img" aria-label="attachment">
|
||||
📎
|
||||
</span>
|
||||
<span>
|
||||
attached <strong>{attachmentName}</strong>
|
||||
{attachmentSize && (
|
||||
<span style={{ color: '#6c757d', marginLeft: '4px' }}>({formatFileSize(attachmentSize)})</span>
|
||||
)}
|
||||
<span>attached</span>
|
||||
<span
|
||||
onClick={() => handleAttachmentClick(attachmentId)}
|
||||
style={{
|
||||
cursor: isLoadingUrl ? 'wait' : 'pointer',
|
||||
color: '#1976d2',
|
||||
textDecoration: 'underline',
|
||||
fontWeight: 600,
|
||||
maxWidth: '200px',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
display: 'inline-block',
|
||||
verticalAlign: 'bottom'
|
||||
}}
|
||||
title={attachmentName}
|
||||
>
|
||||
{attachmentName}
|
||||
</span>
|
||||
{attachmentSize && (
|
||||
<span style={{ color: '#6c757d', flexShrink: 0 }}>({formatFileSize(attachmentSize)})</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue