1
Fork 0

Add frontend for playing latest video

This commit is contained in:
Hadeed 2025-04-09 05:46:19 +05:00
parent 428ee9eafe
commit a53f5a2ad2
4 changed files with 164 additions and 6 deletions

View file

@ -1,8 +1,14 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import utils
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"]
)
@app.get("/video")
def get_video(channel_name: str):

View file

@ -6,7 +6,8 @@
"": {
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-youtube": "^10.1.0"
},
"devDependencies": {
"autoprefixer": "^10.4.19",
@ -1171,6 +1172,15 @@
"node": ">=4"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@ -1257,6 +1267,12 @@
"node": ">=6"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
@ -1544,6 +1560,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/load-script": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/load-script/-/load-script-1.0.0.tgz",
"integrity": "sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==",
"license": "MIT"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -1613,6 +1635,12 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@ -1675,7 +1703,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -2075,6 +2102,17 @@
}
}
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
"react-is": "^16.13.1"
}
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -2121,6 +2159,29 @@
"react": "^18.3.1"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/react-youtube": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-youtube/-/react-youtube-10.1.0.tgz",
"integrity": "sha512-ZfGtcVpk0SSZtWCSTYOQKhfx5/1cfyEW1JN/mugGNfAxT3rmVJeMbGpA9+e78yG21ls5nc/5uZJETE3cm3knBg==",
"license": "MIT",
"dependencies": {
"fast-deep-equal": "3.1.3",
"prop-types": "15.8.1",
"youtube-player": "5.5.2"
},
"engines": {
"node": ">= 14.x"
},
"peerDependencies": {
"react": ">=0.14.1"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@ -2285,6 +2346,12 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sister": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/sister/-/sister-3.0.2.tgz",
"integrity": "sha512-p19rtTs+NksBRKW9qn0UhZ8/TUI9BPw9lmtHny+Y3TinWlOa9jWh9xB0AtPSdmOy49NJJJSSe0Ey4C7h0TrcYA==",
"license": "BSD-3-Clause"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -2752,6 +2819,17 @@
"engines": {
"node": ">= 14"
}
},
"node_modules/youtube-player": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/youtube-player/-/youtube-player-5.5.2.tgz",
"integrity": "sha512-ZGtsemSpXnDky2AUYWgxjaopgB+shFHgXVpiJFeNB5nWEugpW1KWYDaHKuLqh2b67r24GtP6HoSW5swvf0fFIQ==",
"license": "BSD-3-Clause",
"dependencies": {
"debug": "^2.6.6",
"load-script": "^1.0.0",
"sister": "^3.0.0"
}
}
}
}

View file

@ -2,7 +2,8 @@
"type": "module",
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-youtube": "^10.1.0"
},
"devDependencies": {
"autoprefixer": "^10.4.19",

View file

@ -1,3 +1,76 @@
export default () => (
<h1 className="text-4xl font-extrabold">Using React and Tailwind</h1>
);
import { useState } from "react";
import YouTube from "react-youtube";
export default function App() {
const [channelName, setChannelName] = useState("");
const [videoId, setVideoId] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState("");
async function fetchLatestVideo() {
if (!channelName) return;
setLoading(true);
setError("");
setVideoId(null);
try {
const res = await fetch(
`http://localhost:8000/video?channel_name=${channelName}`,
);
if (!res.ok) throw new Error("Channel not found or API error");
const data = await res.json();
setVideoId(data.id);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
const playerOptions = {
playerVars: {
autoplay: 0,
rel: 0,
},
};
return (
<div className="flex min-h-screen flex-col items-center justify-center bg-gray-100 p-6">
<h1 className="mb-6 text-4xl font-extrabold">
YouTube Latest Video Finder
</h1>
<form
onSubmit={(e) => {
e.preventDefault();
fetchLatestVideo();
}}
className="flex flex-col items-center gap-4 sm:flex-row"
>
<input
type="text"
value={channelName}
onChange={(e) => setChannelName(e.target.value)}
placeholder="Enter channel name"
className="w-72 rounded-md border border-gray-300 px-4 py-2"
/>
<button
type="submit"
disabled={loading}
className="rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700 disabled:opacity-50"
>
{loading ? "Loading..." : "Get Latest Video"}
</button>
</form>
{error && <p className="mt-4 text-sm text-red-600">{error}</p>}
{videoId && (
<div className="mt-8 flex w-full justify-center">
<YouTube videoId={videoId} opts={playerOptions} />
</div>
)}
</div>
);
}