nakama/frontend/src/components/MusicPickerModal.jsx

180 lines
5.9 KiB
React
Raw Normal View History

2025-12-15 08:04:16 +00:00
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 ? (
2025-12-15 19:51:01 +00:00
<img
src={track.coverImage.startsWith('http')
? track.coverImage
2026-01-01 19:39:12 +00:00
: (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
2025-12-15 19:51:01 +00:00
}
alt={track.title}
onError={(e) => {
e.target.style.display = 'none'
e.target.nextElementSibling.style.display = 'flex'
}}
/>
) : null}
<div style={{ display: track.coverImage ? 'none' : 'flex' }}>
2025-12-15 08:04:16 +00:00
<Music size={20} />
2025-12-15 19:51:01 +00:00
</div>
2025-12-15 08:04:16 +00:00
</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
)
}
2025-12-15 19:51:01 +00:00