Добавлены новые зависимости для работы с эмодзи и улучшена структура базы данных. Реализована модель сообщений и обновлены компоненты для поддержки новых функций мессенджера. Обновлены запросы и мутации для работы с сообщениями и чатом.

This commit is contained in:
Bivekich
2025-07-16 22:07:38 +03:00
parent 823ef9a28c
commit 205c9eae98
33 changed files with 3288 additions and 229 deletions

View File

@ -0,0 +1,174 @@
"use client"
import { useState, useRef, useEffect } from 'react'
import { Button } from '@/components/ui/button'
import { Play, Pause, Volume2 } from 'lucide-react'
interface VoicePlayerProps {
audioUrl: string
duration?: number
isCurrentUser?: boolean
}
export function VoicePlayer({ audioUrl, duration = 0, isCurrentUser = false }: VoicePlayerProps) {
const [isPlaying, setIsPlaying] = useState(false)
const [currentTime, setCurrentTime] = useState(0)
const [audioDuration, setAudioDuration] = useState(duration > 0 ? duration : 0)
const [isLoading, setIsLoading] = useState(false)
const audioRef = useRef<HTMLAudioElement | null>(null)
// Обновляем длительность при изменении props
useEffect(() => {
if (duration > 0 && (!audioDuration || audioDuration === 0)) {
setAudioDuration(duration)
}
}, [duration, audioDuration])
useEffect(() => {
// Создаем аудио элемент
audioRef.current = new Audio(audioUrl)
const audio = audioRef.current
const handleLoadedMetadata = () => {
if (audio.duration && isFinite(audio.duration) && !isNaN(audio.duration)) {
setAudioDuration(audio.duration)
} else {
setAudioDuration(duration || 0)
}
setIsLoading(false)
}
const handleTimeUpdate = () => {
if (audio.currentTime && isFinite(audio.currentTime) && !isNaN(audio.currentTime)) {
setCurrentTime(audio.currentTime)
}
}
const handleEnded = () => {
setIsPlaying(false)
setCurrentTime(0)
}
const handleCanPlay = () => {
setIsLoading(false)
}
const handleLoadStart = () => {
setIsLoading(true)
}
const handleError = () => {
console.error('Audio loading error')
setIsLoading(false)
setIsPlaying(false)
}
audio.addEventListener('loadedmetadata', handleLoadedMetadata)
audio.addEventListener('timeupdate', handleTimeUpdate)
audio.addEventListener('ended', handleEnded)
audio.addEventListener('canplay', handleCanPlay)
audio.addEventListener('loadstart', handleLoadStart)
audio.addEventListener('error', handleError)
return () => {
if (audio) {
audio.removeEventListener('loadedmetadata', handleLoadedMetadata)
audio.removeEventListener('timeupdate', handleTimeUpdate)
audio.removeEventListener('ended', handleEnded)
audio.removeEventListener('canplay', handleCanPlay)
audio.removeEventListener('loadstart', handleLoadStart)
audio.removeEventListener('error', handleError)
audio.pause()
}
}
}, [audioUrl])
const togglePlayPause = () => {
const audio = audioRef.current
if (!audio) return
if (isPlaying) {
audio.pause()
setIsPlaying(false)
} else {
audio.play().then(() => {
setIsPlaying(true)
}).catch((error) => {
console.error('Error playing audio:', error)
setIsPlaying(false)
})
}
}
const formatTime = (time: number) => {
if (isNaN(time) || !isFinite(time) || time < 0) return '0:00'
const minutes = Math.floor(time / 60)
const seconds = Math.floor(time % 60)
return `${minutes}:${seconds.toString().padStart(2, '0')}`
}
const getProgress = () => {
if (!audioDuration || audioDuration === 0 || isNaN(audioDuration) || !isFinite(audioDuration)) return 0
if (!currentTime || isNaN(currentTime) || !isFinite(currentTime)) return 0
return Math.min((currentTime / audioDuration) * 100, 100)
}
return (
<div className={`flex items-center space-x-4 p-3 rounded-lg min-w-[200px] max-w-sm ${
isCurrentUser
? 'bg-blue-500/20 border border-blue-500/30'
: 'bg-white/10 border border-white/20'
}`}>
{/* Кнопка воспроизведения */}
<Button
onClick={togglePlayPause}
disabled={isLoading}
variant="ghost"
size="sm"
className={`p-2 rounded-full ${
isCurrentUser
? 'text-blue-300 hover:text-blue-200 hover:bg-blue-500/30'
: 'text-white hover:text-white/80 hover:bg-white/20'
}`}
>
{isLoading ? (
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full animate-spin" />
) : isPlaying ? (
<Pause className="h-4 w-4" />
) : (
<Play className="h-4 w-4" />
)}
</Button>
{/* Визуализация волны / прогресс бар */}
<div className="flex-1 space-y-2">
<div className="flex items-center space-x-3">
<Volume2 className={`h-3 w-3 ${
isCurrentUser ? 'text-blue-300' : 'text-white/60'
}`} />
<div className="flex-1 h-1 bg-white/20 rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-300 ${
isCurrentUser ? 'bg-blue-400' : 'bg-white/60'
}`}
style={{ width: `${getProgress()}%` }}
/>
</div>
</div>
{/* Время */}
<div className="flex justify-between items-center text-xs">
<span className={`${isCurrentUser ? 'text-blue-300/70' : 'text-white/50'} min-w-[2rem]`}>
{formatTime(currentTime)}
</span>
<span className={`${isCurrentUser ? 'text-blue-300/70' : 'text-white/50'} min-w-[2rem] text-right`}>
{formatTime(audioDuration)}
</span>
</div>
</div>
</div>
)
}