diff --git a/apps/admin/src/components/content/ResponseAnswer.tsx b/apps/admin/src/components/content/ResponseAnswer.tsx index 7ceb005..6f877ab 100644 --- a/apps/admin/src/components/content/ResponseAnswer.tsx +++ b/apps/admin/src/components/content/ResponseAnswer.tsx @@ -1,15 +1,30 @@ +import { useEffect, useState } from 'react'; import type { AnswerType, QuestionType } from '@sauci/shared'; import { Badge } from '@/components/ui/badge'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Image as ImageIcon, Music2 } from 'lucide-react'; +import { useAuth } from '@/contexts/AuthContext'; +import { supabaseConfig } from '@/config'; import { formatAdminResponse, QUESTION_TYPE_LABELS, type AdminResponseData } from '@/lib/questionResponses'; interface ResponseAnswerProps { answer: AnswerType; + responseId?: string; questionType?: QuestionType | null; responseData?: AdminResponseData; + responderId?: string; + responderName?: string | null; } -export function ResponseAnswer({ answer, questionType, responseData = null }: ResponseAnswerProps) { - const formatted = formatAdminResponse(questionType, answer, responseData); +export function ResponseAnswer({ + answer, + responseId, + questionType, + responseData = null, + responderId, + responderName, +}: ResponseAnswerProps) { + const formatted = formatAdminResponse(questionType, answer, responseData, { responderId, responderName }); const resolvedType = questionType ?? 'swipe'; return ( @@ -27,6 +42,111 @@ export function ResponseAnswer({ answer, questionType, responseData = null }: Re {formatted.detail}

)} + {(resolvedType === 'photo' || resolvedType === 'audio') && responseId && ( + + )} ); } + +function ProtectedResponseMedia({ + responseId, + mediaType, + label, +}: { + responseId: string; + mediaType: 'photo' | 'audio'; + label: string; +}) { + const { session } = useAuth(); + const [url, setUrl] = useState(null); + const [error, setError] = useState(null); + + useEffect(() => { + if (!session?.access_token) { + setError('Sign in to load media'); + return; + } + + let cancelled = false; + let objectUrl: string | null = null; + + const load = async () => { + try { + setError(null); + const response = await fetch(`${supabaseConfig.url}/functions/v1/admin-response-media`, { + method: 'POST', + headers: { + apikey: supabaseConfig.anonKey, + Authorization: `Bearer ${session.access_token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ responseId }), + }); + + if (!response.ok) { + const body = await response.json().catch(() => ({})); + throw new Error(body?.error || `Failed to load media (${response.status})`); + } + + objectUrl = URL.createObjectURL(await response.blob()); + if (!cancelled) setUrl(objectUrl); + } catch (loadError) { + if (!cancelled) { + setError(loadError instanceof Error ? loadError.message : 'Failed to load media'); + } + } + }; + + load(); + + return () => { + cancelled = true; + if (objectUrl) URL.revokeObjectURL(objectUrl); + }; + }, [responseId, session?.access_token]); + + if (error) { + return ( +

+ {mediaType === 'photo' ? : } + {error} +

+ ); + } + + if (!url) { + return ; + } + + if (mediaType === 'photo') { + return ( + + ); + } + + return ( +