import React, { useState } from 'react';
import { Search, Music, Play, ExternalLink } from 'lucide-react';
export default function MusicSearch() {
const [query, setQuery] = useState('');
const [results, setResults] = useState({ spotify: [], soundcloud: [] });
const [loading, setLoading] = useState(false);
const searchSpotify = async (searchQuery) => {
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1000,
messages: [
{
role: "user",
content: `Search for "${searchQuery}" on Spotify. Return ONLY a JSON array of up to 5 results with this exact structure, no other text:
[{"title": "song name", "artist": "artist name", "url": "spotify link", "image": "image url or empty string"}]`
}
],
tools: [{
type: "web_search_20250305",
name: "web_search"
}]
})
});
const data = await response.json();
const text = data.content.map(item => item.type === "text" ? item.text : "").join("");
const cleaned = text.replace(/```json|```/g, "").trim();
return JSON.parse(cleaned);
} catch (err) {
console.error('Spotify search error:', err);
return [];
}
};
const searchSoundCloud = async (searchQuery) => {
try {
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "claude-sonnet-4-20250514",
max_tokens: 1000,
messages: [
{
role: "user",
content: `Search for "${searchQuery}" on SoundCloud. Return ONLY a JSON array of up to 5 results with this exact structure, no other text:
[{"title": "track name", "artist": "artist name", "url": "soundcloud link", "image": "image url or empty string"}]`
}
],
tools: [{
type: "web_search_20250305",
name: "web_search"
}]
})
});
const data = await response.json();
const text = data.content.map(item => item.type === "text" ? item.text : "").join("");
const cleaned = text.replace(/```json|```/g, "").trim();
return JSON.parse(cleaned);
} catch (err) {
console.error('SoundCloud search error:', err);
return [];
}
};
const handleSearch = async () => {
if (!query.trim()) return;
setLoading(true);
setResults({ spotify: [], soundcloud: [] });
const [spotifyData, soundcloudData] = await Promise.all([
searchSpotify(query),
searchSoundCloud(query)
]);
setResults({
spotify: spotifyData,
soundcloud: soundcloudData
});
setLoading(false);
};
const handleKeyPress = (e) => {
if (e.key === 'Enter') {
handleSearch();
}
};
const ResultCard = ({ item, platform }) => (
<div className="bg-white rounded-lg shadow-md p-4 hover:shadow-lg transition-shadow">
<div className="flex gap-4">
{item.image && (
<img
src={item.image}
alt={item.title}
className="w-20 h-20 rounded object-cover"
/>
)}
<div className="flex-1 min-w-0">
<h3 className="font-semibold text-gray-900 truncate">{item.title}</h3>
<p className="text-sm text-gray-600 truncate">{item.artist}</p>
<a
href={item.url}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-sm text-blue-600 hover:text-blue-800 mt-2"
>
<ExternalLink size={14} />
{platform}에서 듣기
</a>
</div>
</div>
</div>
);
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50 p-6">
<div className="max-w-6xl mx-auto">
<div className="text-center mb-8">
<div className="flex items-center justify-center gap-2 mb-2">
<Music className="text-purple-600" size={32} />
<h1 className="text-3xl font-bold text-gray-900">음악 통합 검색</h1>
</div>
<p className="text-gray-600">Spotify와 SoundCloud를 동시에 검색하세요</p>
</div>
<div className="mb-8">
<div className="flex gap-2 max-w-2xl mx-auto">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={20} />
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
onKeyPress={handleKeyPress}
placeholder="노래 제목, 아티스트 이름 입력..."
className="w-full pl-10 pr-4 py-3 rounded-lg border border-gray-300 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
/>
</div>
<button
onClick={handleSearch}
disabled={loading}
className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:bg-gray-400 transition-colors font-medium"
>
{loading ? '검색 중...' : '검색'}
</button>
</div>
</div>
{loading && (
<div className="text-center py-12">
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-gray-300 border-t-purple-600"></div>
<p className="mt-4 text-gray-600">결과를 가져오는 중...</p>
</div>
)}
{!loading && (results.spotify.length > 0 || results.soundcloud.length > 0) && (
<div className="grid md:grid-cols-2 gap-8">
<div>
<div className="flex items-center gap-2 mb-4">
<div className="w-8 h-8 bg-green-500 rounded-full flex items-center justify-center">
<Play size={16} className="text-white" />
</div>
<h2 className="text-xl font-bold text-gray-900">Spotify</h2>
</div>
<div className="space-y-4">
{results.spotify.length > 0 ? (
results.spotify.map((item, idx) => (
<ResultCard key={idx} item={item} platform="Spotify" />
))
) : (
<p className="text-gray-500 text-center py-8">결과가 없습니다</p>
)}
</div>
</div>
<div>
<div className="flex items-center gap-2 mb-4">
<div className="w-8 h-8 bg-orange-500 rounded-full flex items-center justify-center">
<Play size={16} className="text-white" />
</div>
<h2 className="text-xl font-bold text-gray-900">SoundCloud</h2>
</div>
<div className="space-y-4">
{results.soundcloud.length > 0 ? (
results.soundcloud.map((item, idx) => (
<ResultCard key={idx} item={item} platform="SoundCloud" />
))
) : (
<p className="text-gray-500 text-center py-8">결과가 없습니다</p>
)}
</div>
</div>
</div>
)}
</div>
</div>
);
}