180 lines
5.9 KiB
JavaScript
180 lines
5.9 KiB
JavaScript
import { useState, useEffect } from 'react'
|
|
import { createPortal } from 'react-dom'
|
|
import { X, Search, Music, Heart } from 'lucide-react'
|
|
import { searchMusic, getFavorites } from '../utils/musicApi'
|
|
import { hapticFeedback } from '../utils/telegram'
|
|
import './MusicPickerModal.css'
|
|
|
|
export default function MusicPickerModal({ onClose, onSelect }) {
|
|
const [activeTab, setActiveTab] = useState('favorites')
|
|
const [query, setQuery] = useState('')
|
|
const [searchResults, setSearchResults] = useState([])
|
|
const [favorites, setFavorites] = useState([])
|
|
const [loading, setLoading] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (activeTab === 'favorites') {
|
|
loadFavorites()
|
|
}
|
|
}, [activeTab])
|
|
|
|
const loadFavorites = async () => {
|
|
try {
|
|
setLoading(true)
|
|
const data = await getFavorites({ limit: 50 })
|
|
setFavorites(data.tracks || [])
|
|
} catch (error) {
|
|
console.error('Ошибка загрузки избранного:', error)
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSearch = async () => {
|
|
if (!query.trim()) return
|
|
|
|
try {
|
|
setLoading(true)
|
|
hapticFeedback('light')
|
|
const results = await searchMusic(query.trim(), 'tracks')
|
|
setSearchResults(results.tracks || [])
|
|
hapticFeedback('success')
|
|
} catch (error) {
|
|
console.error('Ошибка поиска:', error)
|
|
hapticFeedback('error')
|
|
} finally {
|
|
setLoading(false)
|
|
}
|
|
}
|
|
|
|
const handleSelectTrack = (track) => {
|
|
hapticFeedback('light')
|
|
onSelect(track)
|
|
onClose()
|
|
}
|
|
|
|
const renderTrack = (track) => (
|
|
<div
|
|
key={track._id}
|
|
className="music-picker-track"
|
|
onClick={() => handleSelectTrack(track)}
|
|
>
|
|
<div className="track-cover-small">
|
|
{track.coverImage ? (
|
|
<img
|
|
src={track.coverImage.startsWith('http')
|
|
? track.coverImage
|
|
: (import.meta.env.DEV
|
|
? (import.meta.env.VITE_API_URL || 'http://localhost:3000/api').replace('/api', '')
|
|
: (import.meta.env.VITE_API_URL || 'https://nkm.guru/api').replace('/api', '')) + track.coverImage
|
|
}
|
|
alt={track.title}
|
|
onError={(e) => {
|
|
e.target.style.display = 'none'
|
|
e.target.nextElementSibling.style.display = 'flex'
|
|
}}
|
|
/>
|
|
) : null}
|
|
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
|
|
<Music size={20} />
|
|
</div>
|
|
</div>
|
|
<div className="track-info-small">
|
|
<div className="track-title-small">{track.title}</div>
|
|
<div className="track-artist-small">{track.artist?.name || 'Unknown'}</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
|
|
return createPortal(
|
|
<div className="music-picker-overlay" onClick={onClose}>
|
|
<div className="music-picker-modal" onClick={(e) => e.stopPropagation()}>
|
|
<div className="music-picker-header">
|
|
<h2>Выбрать трек</h2>
|
|
<button className="close-btn" onClick={onClose}>
|
|
<X size={24} />
|
|
</button>
|
|
</div>
|
|
|
|
<div className="music-picker-tabs">
|
|
<button
|
|
className={`picker-tab ${activeTab === 'favorites' ? 'active' : ''}`}
|
|
onClick={() => setActiveTab('favorites')}
|
|
>
|
|
<Heart size={18} />
|
|
Избранное
|
|
</button>
|
|
<button
|
|
className={`picker-tab ${activeTab === 'search' ? 'active' : ''}`}
|
|
onClick={() => setActiveTab('search')}
|
|
>
|
|
<Search size={18} />
|
|
Поиск
|
|
</button>
|
|
</div>
|
|
|
|
<div className="music-picker-content">
|
|
{activeTab === 'search' && (
|
|
<div className="search-section">
|
|
<div className="search-input-wrapper">
|
|
<Search size={18} />
|
|
<input
|
|
type="text"
|
|
placeholder="Поиск треков..."
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
|
|
/>
|
|
<button onClick={handleSearch} disabled={!query.trim() || loading}>
|
|
Найти
|
|
</button>
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="picker-loading">
|
|
<div className="spinner" />
|
|
<p>Поиск...</p>
|
|
</div>
|
|
) : searchResults.length === 0 && query ? (
|
|
<div className="picker-empty">
|
|
<p>Ничего не найдено</p>
|
|
</div>
|
|
) : searchResults.length > 0 ? (
|
|
<div className="tracks-list-picker">
|
|
{searchResults.map(renderTrack)}
|
|
</div>
|
|
) : (
|
|
<div className="picker-empty">
|
|
<Music size={48} color="var(--text-secondary)" />
|
|
<p>Введите запрос для поиска</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{activeTab === 'favorites' && (
|
|
loading ? (
|
|
<div className="picker-loading">
|
|
<div className="spinner" />
|
|
<p>Загрузка...</p>
|
|
</div>
|
|
) : favorites.length === 0 ? (
|
|
<div className="picker-empty">
|
|
<Heart size={48} color="var(--text-secondary)" />
|
|
<p>Нет избранных треков</p>
|
|
</div>
|
|
) : (
|
|
<div className="tracks-list-picker">
|
|
{favorites.map(renderTrack)}
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
)
|
|
}
|
|
|
|
|