From d936871a490c6a7e28cbecab013d0ffa848a98d2 Mon Sep 17 00:00:00 2001 From: Ryan Moon Date: Sun, 29 Mar 2026 11:10:05 -0500 Subject: [PATCH] Fix note photos not displaying by using authenticated image fetching Note photo thumbnails were failing because img src pointed at an authenticated endpoint without auth headers. Added AuthImage component that fetches images via Bearer token and renders as blob URLs. Photos now display inline in note entries. Clicking still opens via signed URL. --- .../src/components/repairs/ticket-notes.tsx | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/packages/admin/src/components/repairs/ticket-notes.tsx b/packages/admin/src/components/repairs/ticket-notes.tsx index c3314c0..3358e1f 100644 --- a/packages/admin/src/components/repairs/ticket-notes.tsx +++ b/packages/admin/src/components/repairs/ticket-notes.tsx @@ -9,6 +9,7 @@ import { Textarea } from '@/components/ui/textarea' import { Badge } from '@/components/ui/badge' import { Send, Trash2, Eye, Lock, ImageIcon, X } from 'lucide-react' import { toast } from 'sonner' +import { useEffect } from 'react' import type { RepairNote } from '@/types/repair' const STATUS_LABELS: Record = { @@ -48,6 +49,31 @@ async function openSignedFile(fileId: string) { } } +/** Image component that fetches via authenticated request and displays as blob */ +function AuthImage({ path, alt, className, onClick }: { path: string; alt: string; className?: string; onClick?: () => void }) { + const token = useAuthStore((s) => s.token) + const [src, setSrc] = useState(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
+ return {alt} +} + interface TicketNotesProps { ticketId: string } @@ -263,8 +289,8 @@ function NoteEntry({ note, formatDate, canDelete, onDelete }: {
{photos.map((photo) => (