fix(media): aplica estado 'não disponível' a imagem, vídeo, documento e sticker
continuous-integration/webhook Deploy concluido com sucesso (VPS 4)

Estende o tratamento de erro 400/404 do redownload para todos os tipos
de mídia. Quando mediaUnavailable=true (payload ausente no backend),
cada tipo exibe sua mensagem específica e desabilita o clique:
- Imagem: 'Imagem não disponível'
- Vídeo: 'Vídeo não disponível'
- Documento: 'Arquivo não disponível' + cursor-default
- Sticker: 'Figurinha não disponível'
DocumentBubble recebe nova prop mediaUnavailable para controle interno.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Deploy Agent
2026-05-12 03:31:59 +02:00
parent a3c78aed8c
commit 4efa6fb2f2
@@ -163,6 +163,7 @@ interface DocumentBubbleProps {
TimestampRow: React.ReactNode
onRedownload?: () => void
isRedownloading?: boolean
mediaUnavailable?: boolean
}
const DOC_TYPES: Record<string, { bg: string; label: string }> = {
@@ -177,7 +178,7 @@ const DOC_TYPES: Record<string, { bg: string; label: string }> = {
csv: { bg: '#276749', label: 'CSV' },
}
const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, TimestampRow, onRedownload, isRedownloading }) => {
const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, TimestampRow, onRedownload, isRedownloading, mediaUnavailable }) => {
const [previewOpen, setPreviewOpen] = useState(false)
// Derive extension from media_type or media_url
@@ -203,6 +204,7 @@ const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, Time
const isPdf = ext === 'pdf'
const handleClick = () => {
if (mediaUnavailable) return
if (!fullMediaUrl) {
onRedownload?.()
return
@@ -219,7 +221,7 @@ const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, Time
<div>
<div
onClick={handleClick}
className="flex items-center gap-3 bg-[#f0f2f5] p-3 rounded-xl border border-black/5 cursor-pointer hover:bg-[#e8eaed] transition-all active:scale-[0.98] min-w-[220px] max-w-[280px]"
className={`flex items-center gap-3 bg-[#f0f2f5] p-3 rounded-xl border border-black/5 transition-all min-w-[220px] max-w-[280px] ${mediaUnavailable ? 'cursor-default opacity-60' : 'cursor-pointer hover:bg-[#e8eaed] active:scale-[0.98]'}`}
>
{/* Type icon */}
<div
@@ -237,7 +239,7 @@ const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, Time
{filename}
</span>
<span className="text-[11px] text-[#667781] mt-[2px]">
{isRedownloading ? 'Baixando...' : `${ext.toUpperCase()}${sizeStr ? ` · ${sizeStr}` : ''}`}
{isRedownloading ? 'Baixando...' : mediaUnavailable ? 'Arquivo não disponível' : `${ext.toUpperCase()}${sizeStr ? ` · ${sizeStr}` : ''}`}
</span>
</div>
<Download className="w-4 h-4 text-[#8696a0] shrink-0" />
@@ -618,8 +620,8 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
return (
<div className="rounded-lg overflow-hidden w-[268px]">
<div
className="relative group/media w-[268px] h-[200px] bg-black/5 cursor-pointer overflow-hidden"
onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : handleRedownload()}
className={`relative group/media w-[268px] h-[200px] bg-black/5 overflow-hidden ${fullMediaUrl || mediaUnavailable ? '' : 'cursor-pointer'}`}
onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : (mediaUnavailable ? undefined : handleRedownload())}
>
{fullMediaUrl ? (
<img
@@ -628,10 +630,10 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
className="w-full h-full object-cover transition-transform group-hover/media:scale-[1.03] block"
/>
) : (
<div className="flex flex-col items-center justify-center w-full h-full gap-2">
<div className={`flex flex-col items-center justify-center w-full h-full gap-2 ${mediaUnavailable ? 'opacity-50' : ''}`}>
{isRedownloading
? <div className="w-8 h-8 border-2 border-slate-400 border-t-transparent rounded-full animate-spin" />
: <><ImageIcon className="w-8 h-8 text-slate-400" /><span className="text-[11px] text-slate-400">Toque para baixar</span></>
: <><ImageIcon className="w-8 h-8 text-slate-400" /><span className="text-[11px] text-slate-400">{mediaUnavailable ? 'Imagem não disponível' : 'Toque para baixar'}</span></>
}
</div>
)}
@@ -658,8 +660,8 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
return (
<div className="rounded-lg overflow-hidden w-[268px]">
<div
className="relative w-[268px] h-[200px] bg-black cursor-pointer group/vid overflow-hidden"
onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : handleRedownload()}
className={`relative w-[268px] h-[200px] bg-black overflow-hidden group/vid ${fullMediaUrl || mediaUnavailable ? '' : 'cursor-pointer'}`}
onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : (mediaUnavailable ? undefined : handleRedownload())}
>
{fullMediaUrl ? (
<div className="absolute inset-0 flex items-center justify-center bg-black/20 hover:bg-black/30 transition-colors">
@@ -668,10 +670,10 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
</div>
</div>
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center bg-black/40 gap-2">
<div className={`absolute inset-0 flex flex-col items-center justify-center bg-black/40 gap-2 ${mediaUnavailable ? 'opacity-50' : ''}`}>
{isRedownloading
? <div className="w-8 h-8 border-2 border-white/60 border-t-transparent rounded-full animate-spin" />
: <><Video className="w-8 h-8 text-white/60" /><span className="text-[11px] text-white/60">Toque para baixar</span></>
: <><Video className="w-8 h-8 text-white/60" /><span className="text-[11px] text-white/60">{mediaUnavailable ? 'Vídeo não disponível' : 'Toque para baixar'}</span></>
}
</div>
)}
@@ -717,7 +719,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
}
return <AudioBubble msg={msg} fullMediaUrl={fullMediaUrl} isOut={isOut} TimestampRow={TimestampRow} />;
case 'document':
return <DocumentBubble msg={msg} fullMediaUrl={fullMediaUrl} TimestampRow={TimestampRow} onRedownload={!fullMediaUrl ? handleRedownload : undefined} isRedownloading={isRedownloading} />;
return <DocumentBubble msg={msg} fullMediaUrl={fullMediaUrl} TimestampRow={TimestampRow} onRedownload={!fullMediaUrl ? handleRedownload : undefined} isRedownloading={isRedownloading} mediaUnavailable={mediaUnavailable} />;
case 'sticker':
return (
<div>
@@ -725,14 +727,14 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
? <img src={fullMediaUrl} alt="Sticker" className="max-w-[180px] max-h-[180px]" />
: (
<div
onClick={handleRedownload}
className="flex items-center gap-2 text-[13px] text-[#667781] italic cursor-pointer hover:text-[#111b21] transition-colors"
onClick={mediaUnavailable ? undefined : handleRedownload}
className={`flex items-center gap-2 text-[13px] text-[#667781] italic transition-colors ${mediaUnavailable ? 'cursor-default opacity-60' : 'cursor-pointer hover:text-[#111b21]'}`}
>
{isRedownloading
? <div className="w-4 h-4 border-2 border-slate-400 border-t-transparent rounded-full animate-spin" />
: <Star className="w-4 h-4" />
}
{isRedownloading ? 'Baixando...' : 'Toque para baixar figurinha'}
{isRedownloading ? 'Baixando...' : mediaUnavailable ? 'Figurinha não disponível' : 'Toque para baixar figurinha'}
</div>
)
}