Починка импорта, плеер для аудио

This commit is contained in:
Халимов Рустам
2026-04-06 23:35:45 +03:00
parent d96e4ec7d4
commit 32c9bc43cf
10 changed files with 375 additions and 196 deletions

View File

@@ -212,7 +212,7 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
exit={{ opacity: 0, scale: 0.98, y: 15 }}
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
onClick={(e) => e.stopPropagation()}
className="relative w-full max-w-[560px] h-[85vh] bg-[#0a0a0a] rounded-[3rem] border border-white/10 shadow-2xl overflow-hidden flex flex-col backdrop-blur-3xl"
className="relative w-full max-w-[850px] h-[85vh] bg-[#0a0a0a] rounded-[3rem] border border-white/10 shadow-2xl overflow-hidden flex flex-col backdrop-blur-3xl"
>
<div className="flex items-center justify-between px-8 py-5 border-b border-white/5 bg-white/[0.01]">
<span className="text-sm font-black uppercase tracking-[0.3em] text-white/50">{t('userProfileUpper')}</span>
@@ -268,18 +268,18 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
{/* Tabs Nav */}
{availableTabs.length > 0 && (
<div className="bg-white/[0.03] p-1.5 rounded-2xl flex items-center mb-8 border border-white/5 shadow-inner">
<div className="bg-white/[0.03] p-2 rounded-2xl flex items-center mb-8 border border-white/5 shadow-inner gap-2">
{availableTabs.map((t) => (
<button
key={t.id}
onClick={() => setActiveTab(t.id)}
className={`relative flex-1 flex items-center justify-center gap-2.5 py-4 rounded-2xl transition-all duration-300 ${activeTab === t.id ? 'text-primary bg-white/[0.08]' : 'text-white/20 hover:text-white/60'}`}
className={`relative flex-1 flex items-center justify-center gap-3 py-4 rounded-2xl transition-all duration-300 ${activeTab === t.id ? 'text-primary bg-white/[0.08] shadow-[0_0_20px_rgba(168,85,247,0.15)]' : 'text-white/20 hover:text-white/60'}`}
>
<t.icon size={18} className="relative z-10" />
<div className="relative z-10 flex items-center gap-2">
<span className="text-[11px] font-black uppercase tracking-widest leading-none">{t.label}</span>
<t.icon size={20} className="relative z-10" />
<div className="relative z-10 flex items-center gap-2.5">
<span className="text-[12px] font-black uppercase tracking-widest leading-none">{t.label}</span>
{counts[t.id] > 0 && (
<span className="flex items-center justify-center min-w-[22px] h-[20px] px-1.5 rounded-lg bg-[#1e1e1e] border border-white/10 text-primary text-[10px] font-black shadow-md">
<span className="flex items-center justify-center min-w-[24px] h-[22px] px-2 rounded-lg bg-[#1e1e1e] border border-white/10 text-primary text-[11px] font-black shadow-md">
{counts[t.id]}
</span>
)}
@@ -301,14 +301,17 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
{(activeTab === 'media' || activeTab === 'gif') ? (
tabData.flatMap((item) => (item.media || []).map((m: any) => {
const mediaType = m.type?.toLowerCase() || 'image';
const isGif = (mediaType === 'image' && (m.url.toLowerCase().endsWith('.gif') || m.url.toLowerCase().endsWith('.mp4'))) || m.url.toLowerCase().indexOf('klipy') !== -1;
const mType = m.type?.toLowerCase() || 'image';
const mFilename = m.filename?.toLowerCase() || '';
const isGif = mType === 'gif' || (mType === 'image' && (mFilename.endsWith('.gif') || mFilename.endsWith('.mp4'))) || mFilename.includes('gif') || mFilename.includes('animation') || m.url?.toLowerCase().includes('klipy');
if (activeTab === 'media' && isGif) return null;
const isVideo = mediaType === 'video' || m.url.toLowerCase().endsWith('.mp4');
if (activeTab === 'gif' && !isGif) return null;
const isVideo = mType === 'video' || m.url?.toLowerCase().endsWith('.mp4');
return (
<div key={`${item.messageId}-${m.id}`} className="relative aspect-square group/item">
<div key={`${item.id}-${m.id}`} className="relative aspect-square group/item">
<button
onClick={() => {
const gIdx = galleryItems.findIndex(g => g.id === m.id);
@@ -319,7 +322,7 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
{isVideo ? (
<div className="w-full h-full relative">
<video src={resolveUrl(m.url)} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-700" />
{!isGif && (
{(activeTab === 'media') && (
<div className="absolute inset-0 bg-black/20 flex items-center justify-center">
<div className="w-10 h-10 rounded-full bg-black/50 backdrop-blur-sm border border-white/20 flex items-center justify-center text-white">
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
@@ -333,8 +336,7 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
{activeTab === 'gif' && <div className="absolute bottom-2 right-2 px-1.5 py-0.5 rounded-md bg-black/80 text-[8px] font-black text-white uppercase tracking-tighter">GIF</div>}
</button>
{/* Jump to Message Button */}
<div className="absolute top-3 right-3 opacity-0 group-hover/item:opacity-100 transition-opacity z-20 flex gap-1.5">
<div className="absolute top-3 right-3 opacity-0 group-hover/item:opacity-100 transition-opacity z-20">
<button
onClick={(e) => { e.stopPropagation(); onGoToMessage?.(item.id, item.createdAt); }}
className="p-2.5 rounded-2xl bg-primary/95 text-white shadow-xl backdrop-blur-md hover:scale-110 active:scale-95 transition-all"
@@ -350,7 +352,16 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
tabData.map((item, idx) => {
const mediaItem = item.media?.[0];
const linkUrl = item.links && item.links.length > 0 ? item.links[0] : (mediaItem?.url || '');
if (activeTab === 'files' && !mediaItem) return null;
const mType = mediaItem?.type?.toLowerCase() || 'file';
const mFilename = mediaItem?.filename?.toLowerCase() || '';
const isGif = mType === 'gif' || (mType === 'image' && (mFilename.endsWith('.mp4') || mFilename.endsWith('.gif'))) || mFilename.includes('gif') || mFilename.includes('animation') || (mediaItem?.url?.toLowerCase().includes('klipy'));
if (activeTab === 'files') {
if (!mediaItem) return null;
if (mType === 'image' || mType === 'video' || isGif) return null;
}
if (activeTab === 'links' && (!item.links || item.links.length === 0)) return null;
return (
@@ -359,8 +370,12 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
{activeTab === 'files' ? <FileText size={24} /> : <LinkIcon size={24} />}
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-black text-white truncate mb-1">{mediaItem?.filename || item.content}</div>
<div className="text-xs text-white/40 font-bold uppercase tracking-widest">{mediaItem?.size ? formatSize(mediaItem.size) : (linkUrl?.length > 40 ? linkUrl.slice(0, 40) + '...' : linkUrl)}</div>
<div className="text-sm font-black text-white truncate mb-1">
{activeTab === 'links' ? (item.content || linkUrl) : (mediaItem?.filename || item.content || 'File')}
</div>
<div className="text-xs text-white/40 font-bold uppercase tracking-widest">
{activeTab === 'files' ? formatSize(mediaItem?.size) : (linkUrl?.length > 50 ? linkUrl.slice(0, 50) + '...' : linkUrl)}
</div>
</div>
<div className="flex items-center gap-2">
@@ -376,14 +391,14 @@ export default function UserProfile({ userId, onClose, onMessage, isSelf: isSelf
<Download size={20} />
</button>
) : (
<button onClick={() => window.open(resolveUrl(linkUrl), '_blank')} className="p-3 rounded-2xl bg-white/5 hover:bg-primary/20 hover:text-primary transition-all">
<button onClick={() => window.open(linkUrl.startsWith('http') ? linkUrl : 'https://' + linkUrl, '_blank')} className="p-3 rounded-2xl bg-white/5 hover:bg-primary/20 hover:text-primary transition-all">
<ExternalLink size={20} />
</button>
)}
</div>
</div>
);
})
}).filter(Boolean)
)}
</motion.div>
)}