Patch: Fix to StreamerRoute to correctly send user to UserPage/VideoPage;
Update: Display Category Thumbnails
@@ -6,7 +6,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Team Software Project</title>
|
<title>Team Software Project</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="h-screen">
|
<body class="min-h-screen h-full">
|
||||||
<div id="root" class="h-full bg-gradient-to-tr from-[#07001F] via-[#1D0085] to-[#CC00AF]"></div>
|
<div id="root" class="h-full bg-gradient-to-tr from-[#07001F] via-[#1D0085] to-[#CC00AF]"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 844 KiB |
|
Before Width: | Height: | Size: 3.3 MiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 194 KiB |
BIN
frontend/public/images/thumbnails/categories/art.webp
Normal file
|
After Width: | Height: | Size: 508 KiB |
BIN
frontend/public/images/thumbnails/categories/chatting.webp
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
frontend/public/images/thumbnails/categories/cooking.webp
Normal file
|
After Width: | Height: | Size: 71 KiB |
BIN
frontend/public/images/thumbnails/categories/gaming.webp
Normal file
|
After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
BIN
frontend/public/images/thumbnails/categories/sports.webp
Normal file
|
After Width: | Height: | Size: 95 KiB |
@@ -27,6 +27,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||||||
thumbnail,
|
thumbnail,
|
||||||
onItemClick,
|
onItemClick,
|
||||||
}) => {
|
}) => {
|
||||||
|
console.log(title, "thumbnail", thumbnail);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex flex-col bg-gray-800 rounded-lg overflow-hidden cursor-pointer hover:bg-gray-700 transition-colors"
|
className="flex flex-col bg-gray-800 rounded-lg overflow-hidden cursor-pointer hover:bg-gray-700 transition-colors"
|
||||||
@@ -35,7 +36,7 @@ const ListItem: React.FC<ListItemProps> = ({
|
|||||||
<div className="relative w-full pt-[56.25%]">
|
<div className="relative w-full pt-[56.25%]">
|
||||||
{thumbnail ? (
|
{thumbnail ? (
|
||||||
<img
|
<img
|
||||||
src={`images/` + thumbnail}
|
src={thumbnail}
|
||||||
alt={title}
|
alt={title}
|
||||||
className="absolute top-0 left-0 w-full h-full object-cover"
|
className="absolute top-0 left-0 w-full h-full object-cover"
|
||||||
/>
|
/>
|
||||||
@@ -74,6 +75,7 @@ const ListRow: React.FC<ListRowProps> = ({
|
|||||||
title={item.title}
|
title={item.title}
|
||||||
streamer={item.type === "stream" ? (item.streamer) : undefined}
|
streamer={item.type === "stream" ? (item.streamer) : undefined}
|
||||||
viewers={item.viewers}
|
viewers={item.viewers}
|
||||||
|
thumbnail={item.thumbnail}
|
||||||
onItemClick={() =>
|
onItemClick={() =>
|
||||||
onClick?.(item.id, item.streamer || item.title)
|
onClick?.(item.id, item.streamer || item.title)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ const StreamerRoute: React.FC = () => {
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/streamer/${streamerName}/status`);
|
const response = await fetch(`/api/streamer/${streamerName}/status`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log("Stream status:", data);
|
setIsLive(data.is_live);
|
||||||
setIsLive(data.status === "live");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking stream status:", error);
|
console.error("Error checking stream status:", error);
|
||||||
setIsLive(false);
|
setIsLive(false);
|
||||||
@@ -36,7 +35,7 @@ const StreamerRoute: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// streamId=0 is a special case for the streamer's latest stream
|
// streamId=0 is a special case for the streamer's latest stream
|
||||||
return isLive ? <VideoPage streamId={0} /> : <UserPage profile={streamerName} />;
|
return isLive ? <VideoPage streamId={0} /> : (streamerName ? <UserPage username={streamerName} /> : <div>Error: Streamer not found</div>);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default StreamerRoute;
|
export default StreamerRoute;
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
// Streams
|
// Streams
|
||||||
fetch(fetch_url[0])
|
fetch(fetch_url[0])
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data: StreamItem[]) => {
|
||||||
const extractedData: StreamItem[] = data.streams.map((stream: any) => ({
|
const extractedData: StreamItem[] = data.map((stream: any) => ({
|
||||||
type: "stream",
|
type: "stream",
|
||||||
id: stream.stream_id,
|
id: stream.stream_id,
|
||||||
title: stream.title,
|
title: stream.title,
|
||||||
streamer: stream.user_id,
|
streamer: stream.username,
|
||||||
viewers: stream.num_viewers,
|
viewers: stream.num_viewers,
|
||||||
thumbnail: stream.thumbnail,
|
thumbnail: stream.thumbnail,
|
||||||
}));
|
}));
|
||||||
@@ -56,15 +56,17 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
// Categories
|
// Categories
|
||||||
fetch(fetch_url[1])
|
fetch(fetch_url[1])
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data: CategoryItem[]) => {
|
||||||
const extractedData: CategoryItem[] = data.categories.map(
|
const extractedData: CategoryItem[] = data.map(
|
||||||
(category: any) => ({
|
(category: any) => ({
|
||||||
type: "category",
|
type: "category",
|
||||||
id: category.category_id,
|
id: category.category_id,
|
||||||
title: category.category_name,
|
title: category.category_name,
|
||||||
viewers: category.num_viewers,
|
viewers: category.num_viewers,
|
||||||
|
thumbnail: `/images/thumbnails/categories/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
console.log(extractedData);
|
||||||
setFeaturedCategories(extractedData);
|
setFeaturedCategories(extractedData);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
|||||||
console.error("Failed to load stream data:", res.statusText);
|
console.error("Failed to load stream data:", res.statusText);
|
||||||
}
|
}
|
||||||
res.json().then((data) => {
|
res.json().then((data) => {
|
||||||
if (!data.validStream) navigate(`/`);
|
// if (!data.validStream) navigate(`/`);
|
||||||
console.log(`Loading stream data for ${streamerName}`);
|
console.log(`Loading stream data for ${streamerName}`);
|
||||||
setStreamData({
|
setStreamData({
|
||||||
streamId: data.streamId,
|
streamId: data.streamId,
|
||||||
|
|||||||
165
package-lock.json
generated
@@ -1,165 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "cs3305-team11",
|
|
||||||
"lockfileVersion": 3,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {
|
|
||||||
"": {
|
|
||||||
"dependencies": {
|
|
||||||
"lucide-react": "^0.474.0",
|
|
||||||
"react-router-dom": "^7.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^19.0.8",
|
|
||||||
"@types/react-dom": "^19.0.3",
|
|
||||||
"typescript": "^5.7.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/cookie": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
|
|
||||||
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@types/react": {
|
|
||||||
"version": "19.0.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
|
|
||||||
"integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"csstype": "^3.0.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@types/react-dom": {
|
|
||||||
"version": "19.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
|
|
||||||
"integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"peerDependencies": {
|
|
||||||
"@types/react": "^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cookie": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/csstype": {
|
|
||||||
"version": "3.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/lucide-react": {
|
|
||||||
"version": "0.474.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.474.0.tgz",
|
|
||||||
"integrity": "sha512-CmghgHkh0OJNmxGKWc0qfPJCYHASPMVSyGY8fj3xgk4v84ItqDg64JNKFZn5hC6E0vHi6gxnbCgwhyVB09wQtA==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react": {
|
|
||||||
"version": "19.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
|
|
||||||
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-dom": {
|
|
||||||
"version": "19.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
|
|
||||||
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"scheduler": "^0.25.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-router": {
|
|
||||||
"version": "7.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.3.tgz",
|
|
||||||
"integrity": "sha512-EezYymLY6Guk/zLQ2vRA8WvdUhWFEj5fcE3RfWihhxXBW7+cd1LsIiA3lmx+KCmneAGQuyBv820o44L2+TtkSA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@types/cookie": "^0.6.0",
|
|
||||||
"cookie": "^1.0.1",
|
|
||||||
"set-cookie-parser": "^2.6.0",
|
|
||||||
"turbo-stream": "2.4.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18",
|
|
||||||
"react-dom": ">=18"
|
|
||||||
},
|
|
||||||
"peerDependenciesMeta": {
|
|
||||||
"react-dom": {
|
|
||||||
"optional": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/react-router-dom": {
|
|
||||||
"version": "7.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.3.tgz",
|
|
||||||
"integrity": "sha512-qQGTE+77hleBzv9SIUIkGRvuFBQGagW+TQKy53UTZAO/3+YFNBYvRsNIZ1GT17yHbc63FylMOdS+m3oUriF1GA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"react-router": "7.1.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=18",
|
|
||||||
"react-dom": ">=18"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/scheduler": {
|
|
||||||
"version": "0.25.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
|
|
||||||
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/set-cookie-parser": {
|
|
||||||
"version": "2.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
|
||||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/turbo-stream": {
|
|
||||||
"version": "2.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
|
|
||||||
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/typescript": {
|
|
||||||
"version": "5.7.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
|
|
||||||
"integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
|
||||||
"bin": {
|
|
||||||
"tsc": "bin/tsc",
|
|
||||||
"tsserver": "bin/tsserver"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=14.17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
11
package.json
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/react": "^19.0.8",
|
|
||||||
"@types/react-dom": "^19.0.3",
|
|
||||||
"typescript": "^5.7.3"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"lucide-react": "^0.474.0",
|
|
||||||
"react-router-dom": "^7.1.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -42,11 +42,11 @@ def get_recommended_streams() -> list[dict]:
|
|||||||
@stream_bp.route('/get_categories')
|
@stream_bp.route('/get_categories')
|
||||||
def get_categories() -> list[dict]:
|
def get_categories() -> list[dict]:
|
||||||
"""
|
"""
|
||||||
Returns a list of streams in the most popular category
|
Returns a list of most watched categories
|
||||||
"""
|
"""
|
||||||
|
|
||||||
category_data = most_popular_category()
|
category_data = most_popular_category()
|
||||||
streams = recommendations_based_on_category(category_data["category_id"])
|
streams = recommendations_based_on_category(category_data['category_id'])
|
||||||
return jsonify(streams)
|
return jsonify(streams)
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
|
|||||||
@@ -49,3 +49,11 @@ CREATE TABLE streams
|
|||||||
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE,
|
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
|
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
SELECT users.user_id, title, username, num_viewers, category_name
|
||||||
|
FROM streams
|
||||||
|
JOIN users ON users.user_id = streams.user_id
|
||||||
|
JOIN categories ON streams.category_id = categories.category_id
|
||||||
|
ORDER BY num_viewers DESC
|
||||||
|
LIMIT 25;
|
||||||
@@ -61,7 +61,7 @@ def default_recommendations():
|
|||||||
JOIN users ON users.user_id = streams.user_id
|
JOIN users ON users.user_id = streams.user_id
|
||||||
JOIN categories ON streams.category_id = categories.category_id
|
JOIN categories ON streams.category_id = categories.category_id
|
||||||
ORDER BY num_viewers DESC
|
ORDER BY num_viewers DESC
|
||||||
LIMIT 25
|
LIMIT 25;
|
||||||
""")
|
""")
|
||||||
db.close_connection()
|
db.close_connection()
|
||||||
return data
|
return data
|
||||||
|
|||||||