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 TimestampRow: React.ReactNode
onRedownload?: () => void onRedownload?: () => void
isRedownloading?: boolean isRedownloading?: boolean
mediaUnavailable?: boolean
} }
const DOC_TYPES: Record<string, { bg: string; label: string }> = { 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' }, 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) const [previewOpen, setPreviewOpen] = useState(false)
// Derive extension from media_type or media_url // 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 isPdf = ext === 'pdf'
const handleClick = () => { const handleClick = () => {
if (mediaUnavailable) return
if (!fullMediaUrl) { if (!fullMediaUrl) {
onRedownload?.() onRedownload?.()
return return
@@ -219,7 +221,7 @@ const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, Time
<div> <div>
<div <div
onClick={handleClick} 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 */} {/* Type icon */}
<div <div
@@ -237,7 +239,7 @@ const DocumentBubble: React.FC<DocumentBubbleProps> = ({ msg, fullMediaUrl, Time
{filename} {filename}
</span> </span>
<span className="text-[11px] text-[#667781] mt-[2px]"> <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> </span>
</div> </div>
<Download className="w-4 h-4 text-[#8696a0] shrink-0" /> <Download className="w-4 h-4 text-[#8696a0] shrink-0" />
@@ -618,8 +620,8 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
return ( return (
<div className="rounded-lg overflow-hidden w-[268px]"> <div className="rounded-lg overflow-hidden w-[268px]">
<div <div
className="relative group/media w-[268px] h-[200px] bg-black/5 cursor-pointer overflow-hidden" 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')) : handleRedownload()} onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : (mediaUnavailable ? undefined : handleRedownload())}
> >
{fullMediaUrl ? ( {fullMediaUrl ? (
<img <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" 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 {isRedownloading
? <div className="w-8 h-8 border-2 border-slate-400 border-t-transparent rounded-full animate-spin" /> ? <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> </div>
)} )}
@@ -658,8 +660,8 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
return ( return (
<div className="rounded-lg overflow-hidden w-[268px]"> <div className="rounded-lg overflow-hidden w-[268px]">
<div <div
className="relative w-[268px] h-[200px] bg-black cursor-pointer group/vid overflow-hidden" 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')) : handleRedownload()} onClick={() => fullMediaUrl ? (onOpenMedia ? onOpenMedia(msg) : window.open(fullMediaUrl, '_blank')) : (mediaUnavailable ? undefined : handleRedownload())}
> >
{fullMediaUrl ? ( {fullMediaUrl ? (
<div className="absolute inset-0 flex items-center justify-center bg-black/20 hover:bg-black/30 transition-colors"> <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> </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 {isRedownloading
? <div className="w-8 h-8 border-2 border-white/60 border-t-transparent rounded-full animate-spin" /> ? <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> </div>
)} )}
@@ -717,7 +719,7 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
} }
return <AudioBubble msg={msg} fullMediaUrl={fullMediaUrl} isOut={isOut} TimestampRow={TimestampRow} />; return <AudioBubble msg={msg} fullMediaUrl={fullMediaUrl} isOut={isOut} TimestampRow={TimestampRow} />;
case 'document': 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': case 'sticker':
return ( return (
<div> <div>
@@ -725,14 +727,14 @@ const MessageBubble: React.FC<MessageBubbleProps> = ({
? <img src={fullMediaUrl} alt="Sticker" className="max-w-[180px] max-h-[180px]" /> ? <img src={fullMediaUrl} alt="Sticker" className="max-w-[180px] max-h-[180px]" />
: ( : (
<div <div
onClick={handleRedownload} onClick={mediaUnavailable ? undefined : handleRedownload}
className="flex items-center gap-2 text-[13px] text-[#667781] italic cursor-pointer hover:text-[#111b21] transition-colors" className={`flex items-center gap-2 text-[13px] text-[#667781] italic transition-colors ${mediaUnavailable ? 'cursor-default opacity-60' : 'cursor-pointer hover:text-[#111b21]'}`}
> >
{isRedownloading {isRedownloading
? <div className="w-4 h-4 border-2 border-slate-400 border-t-transparent rounded-full animate-spin" /> ? <div className="w-4 h-4 border-2 border-slate-400 border-t-transparent rounded-full animate-spin" />
: <Star className="w-4 h-4" /> : <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> </div>
) )
} }