Added Deezer sounce for searching

This commit is contained in:
2026-07-30 08:31:47 -04:00
parent e5643590ee
commit ed34321168
6 changed files with 129 additions and 16 deletions

View File

@@ -1,12 +1,12 @@
import type {
MusicResult, YouTubeResult, DownloadRequest,
MusicResult, MusicSource, YouTubeResult, DownloadRequest,
DownloadStatus, Artist,
} from './types';
const BASE = '/api';
export async function searchMusic(query: string): Promise<MusicResult[]> {
const res = await fetch(`${BASE}/search/music?q=${encodeURIComponent(query)}`);
export async function searchMusic(query: string, source: MusicSource = 'itunes'): Promise<MusicResult[]> {
const res = await fetch(`${BASE}/search/music?q=${encodeURIComponent(query)}&source=${source}`);
if (!res.ok) throw new Error(await res.text());
return res.json();
}

View File

@@ -1,3 +1,5 @@
export type MusicSource = 'itunes' | 'deezer';
export interface MusicResult {
trackId: number;
trackName: string;

View File

@@ -1,26 +1,31 @@
import { useState, useRef } from 'react';
import { Search, Loader2, Music } from 'lucide-react';
import { searchMusic } from '../api/client';
import type { MusicResult } from '../api/types';
import type { MusicResult, MusicSource } from '../api/types';
interface Props {
onSelect: (track: MusicResult) => void;
}
const SOURCES: { value: MusicSource; label: string }[] = [
{ value: 'itunes', label: 'iTunes' },
{ value: 'deezer', label: 'Deezer' },
];
export default function MusicSearch({ onSelect }: Props) {
const [query, setQuery] = useState('');
const [source, setSource] = useState<MusicSource>('itunes');
const [results, setResults] = useState<MusicResult[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
async function handleSearch(e: React.FormEvent) {
e.preventDefault();
if (!query.trim()) return;
async function runSearch(q: string, src: MusicSource) {
if (!q.trim()) return;
setLoading(true);
setError('');
try {
const data = await searchMusic(query.trim());
const data = await searchMusic(q.trim(), src);
setResults(data);
if (data.length === 0) setError('No results found. Try a different search.');
} catch {
@@ -30,9 +35,30 @@ export default function MusicSearch({ onSelect }: Props) {
}
}
function handleSearch(e: React.FormEvent) {
e.preventDefault();
runSearch(query, source);
}
function handleSourceChange(e: React.ChangeEvent<HTMLSelectElement>) {
const next = e.target.value as MusicSource;
setSource(next);
if (query.trim() && results.length > 0) runSearch(query, next);
}
return (
<div>
<form onSubmit={handleSearch} className="search-form">
<select
className="source-select"
value={source}
onChange={handleSourceChange}
aria-label="Metadata source"
>
{SOURCES.map(s => (
<option key={s.value} value={s.value}>{s.label}</option>
))}
</select>
<div className="search-input-wrap">
<Search size={18} className="search-icon" />
<input

View File

@@ -145,6 +145,18 @@ button { font-family: inherit; cursor: pointer; }
}
.search-input:focus { border-color: var(--accent); }
.source-select {
padding: 9px 10px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 13px;
outline: none;
transition: border-color 0.15s;
}
.source-select:focus { border-color: var(--accent); }
/* ── Buttons ──────────────────────────────────────────────────── */
.btn-primary {
display: inline-flex;