fix: switch attachment download to secure axios blob fetch

This commit is contained in:
dyzulk
2026-01-05 22:53:12 +07:00
parent a230ee5890
commit 11585a5f1b
2 changed files with 68 additions and 15 deletions

View File

@@ -214,12 +214,36 @@ export default function AdminTicketDetailsClient() {
{reply.attachments && reply.attachments.length > 0 && (
<div className="mt-3 space-y-2">
{reply.attachments.map((att: any) => (
<a
key={att.id}
href={att.download_url || getAttachmentUrl(att.file_path)}
target="_blank"
rel="noopener noreferrer"
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors ${
<button
key={att.id}
onClick={async (e) => {
e.preventDefault();
if (att.download_url) {
try {
const response = await axios.get(att.download_url, {
responseType: 'blob'
});
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', att.file_name);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (err: any) {
const errorMessage = parseApiError(err, t("toast_download_failed") || "Download failed");
if (err.response?.status === 404) {
addToast("File not found on server.", "error");
} else {
addToast(errorMessage, "error");
}
}
} else {
window.open(getAttachmentUrl(att.file_path), '_blank');
}
}}
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors text-left ${
isMe
? 'bg-white/10 hover:bg-white/20 text-white'
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
@@ -227,7 +251,7 @@ export default function AdminTicketDetailsClient() {
>
<FileText size={14} />
<span className="truncate max-w-[150px]">{att.file_name}</span>
</a>
</button>
))}
</div>
)}

View File

@@ -224,16 +224,45 @@ export default function TicketDetailsClient() {
>
{reply.message}
{/* Attachments Display */}
{/* Attachments Display */}
{reply.attachments && reply.attachments.length > 0 && (
<div className="mt-3 space-y-2">
{reply.attachments.map((att: any) => (
<a
key={att.id}
href={att.download_url || getAttachmentUrl(att.file_path)}
target="_blank"
rel="noopener noreferrer"
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors ${
<button
key={att.id}
onClick={async (e) => {
e.preventDefault();
if (att.download_url) {
try {
// Use axios to fetch with auth headers
const response = await axios.get(att.download_url, {
responseType: 'blob'
});
// Create object URL and trigger download
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', att.file_name);
document.body.appendChild(link);
link.click();
link.remove();
window.URL.revokeObjectURL(url);
} catch (err: any) {
const errorMessage = parseApiError(err, t("toast_download_failed") || "Download failed");
// If 404, file might actually be missing
if (err.response?.status === 404) {
addToast("File not found on server.", "error");
} else {
addToast(errorMessage, "error");
}
}
} else {
// Fallback for legacy local files
window.open(getAttachmentUrl(att.file_path), '_blank');
}
}}
className={`flex items-center gap-2 p-2 rounded-lg text-xs transition-colors text-left ${
isMe
? 'bg-white/10 hover:bg-white/20 text-white'
: 'bg-white dark:bg-white/5 hover:bg-gray-50 dark:hover:bg-white/10 text-gray-700 dark:text-gray-300 border border-gray-200 dark:border-gray-700'
@@ -241,7 +270,7 @@ export default function TicketDetailsClient() {
>
<FileText size={14} />
<span className="truncate max-w-[150px]">{att.file_name}</span>
</a>
</button>
))}
</div>
)}