|
1 | | -import React, { useState } from "react"; |
| 1 | +import React, { useState, useEffect } from "react"; |
2 | 2 | import axios from "axios"; |
3 | | -import { FaPlus } from "react-icons/fa"; |
| 3 | +import { FaPlus, FaSearch } from "react-icons/fa"; |
4 | 4 | import { API_URL } from "../components/Config"; |
5 | 5 |
|
6 | 6 | const AddVideoBar = ({ room, onVideoAdded }) => { |
7 | | - const [url, setUrl] = useState(""); |
| 7 | + const [query, setQuery] = useState(""); |
| 8 | + const [results, setResults] = useState([]); |
8 | 9 | const [loading, setLoading] = useState(false); |
9 | 10 |
|
10 | | - const handleAdd = async (e) => { |
11 | | - e.preventDefault(); |
12 | | - if (!url.trim()) return; |
13 | | - |
14 | | - setLoading(true); |
15 | | - try { |
16 | | - let cleanUrl = url.trim(); |
| 11 | + // Debounced search |
| 12 | + useEffect(() => { |
| 13 | + if (!query.trim() || query.includes("http")) { |
| 14 | + setResults([]); |
| 15 | + return; |
| 16 | + } |
| 17 | + const delayDebounceFn = setTimeout(async () => { |
17 | 18 | try { |
18 | | - const parsedUrl = new URL(cleanUrl); |
19 | | - if (parsedUrl.hostname.includes("youtube.com")) { |
20 | | - const v = parsedUrl.searchParams.get("v"); |
21 | | - if (v) cleanUrl = `https://www.youtube.com/watch?v=${v}`; |
22 | | - } else if (parsedUrl.hostname.includes("youtu.be")) { |
23 | | - const v = parsedUrl.pathname.slice(1); |
24 | | - if (v) cleanUrl = `https://www.youtube.com/watch?v=${v}`; |
25 | | - } |
| 19 | + // Free, open-source YouTube search API |
| 20 | + const res = await axios.get(`https://vid.puffyan.us/api/v1/search?q=${encodeURIComponent(query)}`); |
| 21 | + setResults(res.data.slice(0, 5)); // Top 5 results |
26 | 22 | } catch (err) { |
27 | | - console.warn("URL parsing failed, using raw input:", err); |
28 | | - cleanUrl = url.trim(); |
| 23 | + console.error("Search failed", err); |
29 | 24 | } |
| 25 | + }, 500); |
| 26 | + return () => clearTimeout(delayDebounceFn); |
| 27 | + }, [query]); |
30 | 28 |
|
31 | | - |
32 | | - await axios.post(`${API_URL}/api/videos/add`, { |
33 | | - url: cleanUrl, |
34 | | - room, |
35 | | - }); |
36 | | - |
37 | | - setUrl(""); |
| 29 | + const handleAdd = async (videoUrl) => { |
| 30 | + setLoading(true); |
| 31 | + try { |
| 32 | + await axios.post(`${API_URL}/api/videos/add`, { url: videoUrl, room }); |
| 33 | + setQuery(""); |
| 34 | + setResults([]); |
38 | 35 | if (onVideoAdded) onVideoAdded(); |
39 | 36 | } catch (err) { |
40 | | - console.error("Add Video Error:", err); |
41 | | - alert("Failed to add video. Please ensure the link is valid."); |
| 37 | + alert("Failed to add video."); |
42 | 38 | } finally { |
43 | 39 | setLoading(false); |
44 | 40 | } |
45 | 41 | }; |
46 | 42 |
|
47 | 43 | return ( |
48 | | - <form onSubmit={handleAdd} className="add-video-form"> |
49 | | - <input |
50 | | - type="text" |
51 | | - placeholder="Paste YouTube URL..." |
52 | | - value={url} |
53 | | - onChange={(e) => setUrl(e.target.value)} |
54 | | - className="add-video-input" |
55 | | - /> |
56 | | - <button type="submit" disabled={loading} className="add-video-btn"aria-label="Add Video"> |
57 | | - {loading ? "..." : <FaPlus />} |
58 | | - </button> |
59 | | - </form> |
| 44 | + <div className="add-video-container" style={{ position: "relative" }}> |
| 45 | + <form onSubmit={(e) => { e.preventDefault(); if(query.includes("http")) handleAdd(query); }} className="add-video-form"> |
| 46 | + <input |
| 47 | + type="text" |
| 48 | + placeholder="Search YouTube or paste URL..." |
| 49 | + value={query} |
| 50 | + onChange={(e) => setQuery(e.target.value)} |
| 51 | + className="add-video-input" |
| 52 | + /> |
| 53 | + <button type="submit" disabled={loading} className="add-video-btn"> |
| 54 | + {loading ? "..." : (query.includes("http") ? <FaPlus /> : <FaSearch />)} |
| 55 | + </button> |
| 56 | + </form> |
| 57 | + |
| 58 | + {/* Dropdown Results */} |
| 59 | + {results.length > 0 && ( |
| 60 | + <div className="search-results-dropdown" style={{ position: "absolute", top: "100%", width: "100%", background: "#1a1a1a", zIndex: 100, borderRadius: "4px" }}> |
| 61 | + {results.map((v) => ( |
| 62 | + <div |
| 63 | + key={v.videoId} |
| 64 | + onClick={() => handleAdd(`https://www.youtube.com/watch?v=${v.videoId}`)} |
| 65 | + style={{ padding: "10px", borderBottom: "1px solid #333", cursor: "pointer", display: "flex", gap: "10px" }} |
| 66 | + > |
| 67 | + <img src={v.videoThumbnails[0]?.url} alt="" width="50" style={{ borderRadius: "4px" }} /> |
| 68 | + <div style={{ fontSize: "0.85rem", color: "white" }}>{v.title}</div> |
| 69 | + </div> |
| 70 | + ))} |
| 71 | + </div> |
| 72 | + )} |
| 73 | + </div> |
60 | 74 | ); |
61 | 75 | }; |
62 | 76 |
|
|
0 commit comments