Fix auth for all file thumbnails, add upload error logging
All image thumbnails in ticket photos and note attachments now use AuthImage component that fetches via Bearer token. Fixed 401 errors on Photos & Docs tab. Added error logging for note photo uploads to surface upload failures.
This commit is contained in:
@@ -116,11 +116,16 @@ export function TicketNotes({ ticketId }: TicketNotesProps) {
|
||||
formData.append('entityType', 'repair_note')
|
||||
formData.append('entityId', note.id)
|
||||
formData.append('category', 'attachment')
|
||||
await fetch('/v1/files', {
|
||||
const uploadRes = await fetch('/v1/files', {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
body: formData,
|
||||
})
|
||||
if (!uploadRes.ok) {
|
||||
const err = await uploadRes.json().catch(() => ({}))
|
||||
console.error('Photo upload failed:', err)
|
||||
toast.error(`Photo upload failed: ${(err as any).error?.message ?? 'Unknown error'}`)
|
||||
}
|
||||
}
|
||||
|
||||
queryClient.invalidateQueries({ queryKey: repairNoteKeys.all(ticketId) })
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef } from 'react'
|
||||
import { useRef, useState, useEffect } from 'react'
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import { queryOptions } from '@tanstack/react-query'
|
||||
import { api } from '@/lib/api-client'
|
||||
@@ -7,6 +7,30 @@ import { Button } from '@/components/ui/button'
|
||||
import { FileText, ImageIcon, Plus, Trash2 } from 'lucide-react'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
function AuthImage({ path, alt, className, onClick }: { path: string; alt: string; className?: string; onClick?: () => void }) {
|
||||
const token = useAuthStore((s) => s.token)
|
||||
const [src, setSrc] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
async function load() {
|
||||
try {
|
||||
const res = await fetch(`/v1/files/serve/${path}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
})
|
||||
if (!res.ok || cancelled) return
|
||||
const blob = await res.blob()
|
||||
if (!cancelled) setSrc(URL.createObjectURL(blob))
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
load()
|
||||
return () => { cancelled = true }
|
||||
}, [path, token])
|
||||
|
||||
if (!src) return <div className={`${className} bg-muted animate-pulse`} />
|
||||
return <img src={src} alt={alt} className={className} onClick={onClick} />
|
||||
}
|
||||
|
||||
async function openSignedFile(fileId: string) {
|
||||
try {
|
||||
const res = await api.get<{ url: string }>(`/v1/files/signed-url/${fileId}`)
|
||||
@@ -164,8 +188,8 @@ function PhotoSection({
|
||||
</button>
|
||||
) : (
|
||||
<button type="button" onClick={() => openSignedFile(photo.id)}>
|
||||
<img
|
||||
src={`/v1/files/serve/${photo.path}`}
|
||||
<AuthImage
|
||||
path={photo.path}
|
||||
alt={photo.filename}
|
||||
className="h-20 w-20 object-cover rounded-md border cursor-pointer hover:opacity-80 transition-opacity"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user