REFACTOR: HomePage and ResultsPage for improved data handling
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { AuthContext } from "./context/AuthContext";
|
import { AuthContext } from "./context/AuthContext";
|
||||||
import { StreamsProvider } from "./context/StreamsContext";
|
import { ContentProvider } from "./context/ContentContext";
|
||||||
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
|
||||||
import HomePage from "./pages/HomePage";
|
import HomePage from "./pages/HomePage";
|
||||||
import StreamerRoute from "./components/Stream/StreamerRoute";
|
import StreamerRoute from "./components/Stream/StreamerRoute";
|
||||||
@@ -8,8 +8,8 @@ import NotFoundPage from "./pages/NotFoundPage";
|
|||||||
import UserPage from "./pages/UserPage";
|
import UserPage from "./pages/UserPage";
|
||||||
import ResetPasswordPage from "./pages/ResetPasswordPage";
|
import ResetPasswordPage from "./pages/ResetPasswordPage";
|
||||||
import CategoryPage from "./pages/CategoryPage";
|
import CategoryPage from "./pages/CategoryPage";
|
||||||
import CategoriesPage from "./pages/CategoriesPage";
|
import CategoriesPage from "./pages/AllCategoriesPage";
|
||||||
import FoundPage from "./pages/FoundPage";
|
import ResultsPage from "./pages/ResultsPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
@@ -32,7 +32,7 @@ function App() {
|
|||||||
<AuthContext.Provider
|
<AuthContext.Provider
|
||||||
value={{ isLoggedIn, username, setIsLoggedIn, setUsername }}
|
value={{ isLoggedIn, username, setIsLoggedIn, setUsername }}
|
||||||
>
|
>
|
||||||
<StreamsProvider>
|
<ContentProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
@@ -44,15 +44,21 @@ function App() {
|
|||||||
|
|
||||||
<Route path="/:streamerName" element={<StreamerRoute />} />
|
<Route path="/:streamerName" element={<StreamerRoute />} />
|
||||||
<Route path="/user/:username" element={<UserPage />} />
|
<Route path="/user/:username" element={<UserPage />} />
|
||||||
<Route path="/reset_password/:token" element={<ResetPasswordPage />}></Route>
|
<Route
|
||||||
<Route path="/category/:category_name" element={<CategoryPage />}></Route>
|
path="/reset_password/:token"
|
||||||
|
element={<ResetPasswordPage />}
|
||||||
|
></Route>
|
||||||
|
<Route
|
||||||
|
path="/category/:category_name"
|
||||||
|
element={<CategoryPage />}
|
||||||
|
></Route>
|
||||||
<Route path="/category" element={<CategoriesPage />}></Route>
|
<Route path="/category" element={<CategoriesPage />}></Route>
|
||||||
<Route path="/results" element={<FoundPage />}></Route>
|
<Route path="/results" element={<ResultsPage />}></Route>
|
||||||
<Route path="/404" element={<NotFoundPage />} />
|
<Route path="/404" element={<NotFoundPage />} />
|
||||||
<Route path="*" element={<Navigate to="/404" replace />} />
|
<Route path="*" element={<Navigate to="/404" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</StreamsProvider>
|
</ContentProvider>
|
||||||
</AuthContext.Provider>
|
</AuthContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export default function GoogleLogin() {
|
export default function GoogleLogin() {
|
||||||
const handleLoginClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleLoginClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -15,7 +13,8 @@ export default function GoogleLogin() {
|
|||||||
//w-full basis-[90%] (% size of original container)
|
//w-full basis-[90%] (% size of original container)
|
||||||
className="flex w-full max-w-[19em] basis-[90%] flex-grow flex-shrink items-center justify-start bg-white text-gray-600
|
className="flex w-full max-w-[19em] basis-[90%] flex-grow flex-shrink items-center justify-start bg-white text-gray-600
|
||||||
font-semibold py-[0.15em] pl-[0.3em] pr-[0.6em] rounded shadow-md flex-grow flex-shrink
|
font-semibold py-[0.15em] pl-[0.3em] pr-[0.6em] rounded shadow-md flex-grow flex-shrink
|
||||||
hover:bg-gray-100 active:bg-gray-200 sm:max-w-[18em] mx-[1em]">
|
hover:bg-gray-100 active:bg-gray-200 sm:max-w-[18em] mx-[1em]"
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="../../../images/icons/google-icon.png"
|
src="../../../images/icons/google-icon.png"
|
||||||
alt="Google logo"
|
alt="Google logo"
|
||||||
|
|||||||
135
frontend/src/context/ContentContext.tsx
Normal file
135
frontend/src/context/ContentContext.tsx
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import { createContext, useContext, useState, useEffect } from "react";
|
||||||
|
import { useAuth } from "./AuthContext";
|
||||||
|
|
||||||
|
// Base interfaces
|
||||||
|
interface Item {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
viewers: number;
|
||||||
|
thumbnail?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface StreamItem extends Item {
|
||||||
|
type: "stream";
|
||||||
|
streamer: string;
|
||||||
|
streamCategory: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryItem extends Item {
|
||||||
|
type: "category";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UserItem extends Item {
|
||||||
|
type: "user";
|
||||||
|
username: string;
|
||||||
|
isLive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Context type
|
||||||
|
interface ContentContextType {
|
||||||
|
streams: StreamItem[];
|
||||||
|
categories: CategoryItem[];
|
||||||
|
users: UserItem[];
|
||||||
|
setStreams: (streams: StreamItem[]) => void;
|
||||||
|
setCategories: (categories: CategoryItem[]) => void;
|
||||||
|
setUsers: (users: UserItem[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ContentContext = createContext<ContentContextType | undefined>(undefined);
|
||||||
|
|
||||||
|
export function ContentProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [streams, setStreams] = useState<StreamItem[]>([]);
|
||||||
|
const [categories, setCategories] = useState<CategoryItem[]>([]);
|
||||||
|
const [users, setUsers] = useState<UserItem[]>([]);
|
||||||
|
const { isLoggedIn } = useAuth();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Fetch streams
|
||||||
|
const streamsUrl = isLoggedIn
|
||||||
|
? "/api/streams/recommended"
|
||||||
|
: "/api/streams/popular/4";
|
||||||
|
|
||||||
|
fetch(streamsUrl)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: any[]) => {
|
||||||
|
const processedStreams: StreamItem[] = data.map(stream => ({
|
||||||
|
type: "stream",
|
||||||
|
id: stream.user_id,
|
||||||
|
title: stream.title,
|
||||||
|
streamer: stream.username,
|
||||||
|
streamCategory: stream.category_name,
|
||||||
|
viewers: stream.num_viewers,
|
||||||
|
thumbnail: stream.thumbnail ||
|
||||||
|
`/images/thumbnails/categories/${stream.category_name.toLowerCase().replace(/ /g, "_")}.webp`
|
||||||
|
}));
|
||||||
|
setStreams(processedStreams);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fetch categories
|
||||||
|
const categoriesUrl = isLoggedIn
|
||||||
|
? "/api/categories/recommended"
|
||||||
|
: "/api/categories/popular/4";
|
||||||
|
|
||||||
|
fetch(categoriesUrl)
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data: any[]) => {
|
||||||
|
const processedCategories: CategoryItem[] = data.map(category => ({
|
||||||
|
type: "category",
|
||||||
|
id: category.category_id,
|
||||||
|
title: category.category_name,
|
||||||
|
viewers: category.num_viewers,
|
||||||
|
thumbnail: `/images/thumbnails/categories/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`,
|
||||||
|
}));
|
||||||
|
setCategories(processedCategories);
|
||||||
|
});
|
||||||
|
}, [isLoggedIn]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ContentContext.Provider
|
||||||
|
value={{
|
||||||
|
streams,
|
||||||
|
categories,
|
||||||
|
users,
|
||||||
|
setStreams,
|
||||||
|
setCategories,
|
||||||
|
setUsers,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ContentContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom hooks for specific content types
|
||||||
|
export function useStreams() {
|
||||||
|
const context = useContext(ContentContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useStreams must be used within a ContentProvider");
|
||||||
|
}
|
||||||
|
return { streams: context.streams, setStreams: context.setStreams };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCategories() {
|
||||||
|
const context = useContext(ContentContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useCategories must be used within a ContentProvider");
|
||||||
|
}
|
||||||
|
return { categories: context.categories, setCategories: context.setCategories };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUsers() {
|
||||||
|
const context = useContext(ContentContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useUsers must be used within a ContentProvider");
|
||||||
|
}
|
||||||
|
return { users: context.users, setUsers: context.setUsers };
|
||||||
|
}
|
||||||
|
|
||||||
|
// General hook for all content
|
||||||
|
export function useContent() {
|
||||||
|
const context = useContext(ContentContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useContent must be used within a ContentProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from "react";
|
|
||||||
import { useAuth } from "./AuthContext";
|
|
||||||
|
|
||||||
interface Item {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
viewers: number;
|
|
||||||
thumbnail?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StreamItem extends Item {
|
|
||||||
type: "stream";
|
|
||||||
streamer: string;
|
|
||||||
streamCategory: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CategoryItem extends Item {
|
|
||||||
type: "category";
|
|
||||||
}
|
|
||||||
|
|
||||||
interface StreamsContextType {
|
|
||||||
featuredStreams: StreamItem[];
|
|
||||||
featuredCategories: CategoryItem[];
|
|
||||||
setFeaturedStreams: (streams: StreamItem[]) => void;
|
|
||||||
setFeaturedCategories: (categories: CategoryItem[]) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const StreamsContext = createContext<StreamsContextType | undefined>(undefined);
|
|
||||||
|
|
||||||
export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
|
||||||
const [featuredStreams, setFeaturedStreams] = useState<StreamItem[]>([]);
|
|
||||||
const [featuredCategories, setFeaturedCategories] = useState<CategoryItem[]>(
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
const { isLoggedIn } = useAuth();
|
|
||||||
|
|
||||||
const fetch_url = isLoggedIn
|
|
||||||
? ["/api/streams/recommended", "/api/categories/recommended"]
|
|
||||||
: ["/api/streams/popular/4", "/api/categories/popular/4"];
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Streams
|
|
||||||
fetch(fetch_url[0])
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data: StreamItem[]) => {
|
|
||||||
const extractedData: StreamItem[] = data.map((stream: any) => ({
|
|
||||||
type: "stream",
|
|
||||||
id: stream.user_id,
|
|
||||||
title: stream.title,
|
|
||||||
streamer: stream.username,
|
|
||||||
streamCategory: stream.category_name,
|
|
||||||
viewers: stream.num_viewers,
|
|
||||||
thumbnail:
|
|
||||||
stream.thumbnail ||
|
|
||||||
`/images/thumbnails/categories/${stream.category_name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/ /g, "_")}.webp`
|
|
||||||
}));
|
|
||||||
|
|
||||||
setFeaturedStreams(extractedData);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Categories
|
|
||||||
fetch(fetch_url[1])
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data: CategoryItem[]) => {
|
|
||||||
const extractedData: CategoryItem[] = data.map((category: any) => ({
|
|
||||||
type: "category",
|
|
||||||
id: category.category_id,
|
|
||||||
title: category.category_name,
|
|
||||||
viewers: category.num_viewers,
|
|
||||||
thumbnail: `/images/thumbnails/categories/${category.category_name
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/ /g, "_")}.webp`,
|
|
||||||
}));
|
|
||||||
console.log(extractedData);
|
|
||||||
setFeaturedCategories(extractedData);
|
|
||||||
});
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StreamsContext.Provider
|
|
||||||
value={{
|
|
||||||
featuredStreams,
|
|
||||||
featuredCategories,
|
|
||||||
setFeaturedStreams,
|
|
||||||
setFeaturedCategories,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</StreamsContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStreams() {
|
|
||||||
const context = useContext(StreamsContext);
|
|
||||||
if (context === undefined) {
|
|
||||||
throw new Error("useStreams must be used within a StreamsProvider");
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
import { createContext, useContext, useState, useEffect, ReactNode } from "react";
|
||||||
|
|
||||||
// Defines the Theme (Colour Theme) that would be shared/used
|
// Defines the Theme (Colour Theme) that would be shared/used
|
||||||
interface ThemeContextType {
|
interface ThemeContextType {
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
const FoundPage: React.FC = ({}) => {
|
|
||||||
const location = useLocation();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { searchResults, query } = location.state || { searchResults: null, query: "" };
|
|
||||||
if (!searchResults) {
|
|
||||||
return (
|
|
||||||
<div className="p-4">
|
|
||||||
<h2 className="text-xl font-bold">No results found for "{query}"</h2>
|
|
||||||
<button onClick={() => navigate(-1)} className="mt-4 px-4 py-2 bg-purple-500 text-white rounded">
|
|
||||||
Go Back
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="p-4">
|
|
||||||
<h2 className="text-xl font-bold mb-4">Search Results for "{query}"</h2>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold">Categories</h3>
|
|
||||||
<ul>
|
|
||||||
{searchResults.categories.map((category: any, index: number) => (
|
|
||||||
<li key={index} className="border p-2 rounded my-2">{category.category_name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold">Users</h3>
|
|
||||||
<ul>
|
|
||||||
{searchResults.users.map((user: any, index: number) => (
|
|
||||||
<li key={index} className="border p-2 rounded my-2">{user.username} {user.is_live ? "🔴" : ""}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<h3 className="text-lg font-semibold">Streams</h3>
|
|
||||||
<ul>
|
|
||||||
{searchResults.streams.map((stream: any, index: number) => (
|
|
||||||
<li key={index} className="border p-2 rounded my-2">
|
|
||||||
{stream.title} - {stream.num_viewers} viewers
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button onClick={() => navigate(-1)} className="mt-4 px-4 py-2 bg-purple-500 text-white rounded">
|
|
||||||
Go Back
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export default FoundPage
|
|
||||||
@@ -2,14 +2,15 @@ import React from "react";
|
|||||||
import Navbar from "../components/Layout/Navbar";
|
import Navbar from "../components/Layout/Navbar";
|
||||||
import ListRow from "../components/Layout/ListRow";
|
import ListRow from "../components/Layout/ListRow";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useStreams } from "../context/StreamsContext";
|
import { useStreams, useCategories } from "../context/ContentContext";
|
||||||
|
|
||||||
interface HomePageProps {
|
interface HomePageProps {
|
||||||
variant?: "default" | "personalised";
|
variant?: "default" | "personalised";
|
||||||
}
|
}
|
||||||
|
|
||||||
const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||||
const { featuredStreams, featuredCategories } = useStreams();
|
const { streams } = useStreams();
|
||||||
|
const { categories } = useCategories();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleStreamClick = (streamerName: string) => {
|
const handleStreamClick = (streamerName: string) => {
|
||||||
@@ -32,14 +33,14 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
|||||||
<ListRow
|
<ListRow
|
||||||
type="stream"
|
type="stream"
|
||||||
title={
|
title={
|
||||||
"Live Now" + (variant === "personalised" ? " - Recommended" : "")
|
"Streams - Live Now" + (variant === "personalised" ? " - Recommended" : "")
|
||||||
}
|
}
|
||||||
description={
|
description={
|
||||||
variant === "personalised"
|
variant === "personalised"
|
||||||
? "We think you might like these streams - Streamers recommended for you"
|
? "We think you might like these streams - Streamers recommended for you"
|
||||||
: "Streamers that are currently live"
|
: "Streamers that are currently live"
|
||||||
}
|
}
|
||||||
items={featuredStreams}
|
items={streams}
|
||||||
onClick={handleStreamClick}
|
onClick={handleStreamClick}
|
||||||
extraClasses="bg-red-950/60"
|
extraClasses="bg-red-950/60"
|
||||||
/>
|
/>
|
||||||
@@ -57,10 +58,9 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
|||||||
? "Current streams from your followed categories"
|
? "Current streams from your followed categories"
|
||||||
: "Categories that have been 'popping off' lately"
|
: "Categories that have been 'popping off' lately"
|
||||||
}
|
}
|
||||||
items={featuredCategories}
|
items={categories}
|
||||||
onClick={handleCategoryClick}
|
onClick={handleCategoryClick}
|
||||||
extraClasses="bg-green-950/60"
|
extraClasses="bg-green-950/60"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
72
frontend/src/pages/ResultsPage.tsx
Normal file
72
frontend/src/pages/ResultsPage.tsx
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const ResultsPage: React.FC = ({}) => {
|
||||||
|
const location = useLocation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { searchResults, query } = location.state || {
|
||||||
|
searchResults: null,
|
||||||
|
query: "",
|
||||||
|
};
|
||||||
|
if (!searchResults) {
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-xl font-bold">No results found for "{query}"</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="mt-4 px-4 py-2 bg-purple-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="p-4">
|
||||||
|
<h2 className="text-xl font-bold mb-4">Search Results for "{query}"</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold">Categories</h3>
|
||||||
|
<ul>
|
||||||
|
{searchResults.categories.map((category: any, index: number) => (
|
||||||
|
<li key={index} className="border p-2 rounded my-2">
|
||||||
|
{category.category_name}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold">Users</h3>
|
||||||
|
<ul>
|
||||||
|
{searchResults.users.map((user: any, index: number) => (
|
||||||
|
<li key={index} className="border p-2 rounded my-2">
|
||||||
|
{user.username} {user.is_live ? "🔴" : ""}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold">Streams</h3>
|
||||||
|
<ul>
|
||||||
|
{searchResults.streams.map((stream: any, index: number) => (
|
||||||
|
<li key={index} className="border p-2 rounded my-2">
|
||||||
|
{stream.title} - {stream.num_viewers} viewers
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate(-1)}
|
||||||
|
className="mt-4 px-4 py-2 bg-purple-500 text-white rounded"
|
||||||
|
>
|
||||||
|
Go Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResultsPage;
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from os import getenv
|
||||||
from authlib.integrations.flask_client import OAuth, OAuthError
|
from authlib.integrations.flask_client import OAuth, OAuthError
|
||||||
from flask import Blueprint, jsonify, session, redirect, request
|
from flask import Blueprint, jsonify, session, redirect, request
|
||||||
from blueprints.user import get_session_info_email
|
from blueprints.user import get_session_info_email
|
||||||
@@ -9,7 +10,6 @@ from random import randint
|
|||||||
oauth_bp = Blueprint("oauth", __name__)
|
oauth_bp = Blueprint("oauth", __name__)
|
||||||
google = None
|
google = None
|
||||||
|
|
||||||
from os import getenv
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
url_api = getenv("VITE_API_URL")
|
url_api = getenv("VITE_API_URL")
|
||||||
url = getenv("HOMEPAGE_URL")
|
url = getenv("HOMEPAGE_URL")
|
||||||
@@ -28,9 +28,10 @@ def init_oauth(app):
|
|||||||
api_base_url='https://www.googleapis.com/oauth2/v1/',
|
api_base_url='https://www.googleapis.com/oauth2/v1/',
|
||||||
userinfo_endpoint='https://openidconnect.googleapis.com/v1/userinfo',
|
userinfo_endpoint='https://openidconnect.googleapis.com/v1/userinfo',
|
||||||
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
|
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
|
||||||
redirect_uri=f"{url_api}/google_auth"
|
redirect_uri=f"{url}/api/google_auth"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@oauth_bp.route('/login/google')
|
@oauth_bp.route('/login/google')
|
||||||
def login_google():
|
def login_google():
|
||||||
"""
|
"""
|
||||||
@@ -41,10 +42,11 @@ def login_google():
|
|||||||
session["origin"] = request.args.get("next")
|
session["origin"] = request.args.get("next")
|
||||||
|
|
||||||
return google.authorize_redirect(
|
return google.authorize_redirect(
|
||||||
f'{url_api}/google_auth',
|
f'{url}/api/google_auth',
|
||||||
nonce=session['nonce']
|
nonce=session['nonce']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@oauth_bp.route('/google_auth')
|
@oauth_bp.route('/google_auth')
|
||||||
def google_auth():
|
def google_auth():
|
||||||
"""
|
"""
|
||||||
@@ -70,12 +72,13 @@ def google_auth():
|
|||||||
with Database() as db:
|
with Database() as db:
|
||||||
# Generates a new username for the user
|
# Generates a new username for the user
|
||||||
for _ in range(1000000):
|
for _ in range(1000000):
|
||||||
username = user.get("given_name") + str(randint(1, 1000000))
|
username = user.get("given_name") + \
|
||||||
|
str(randint(1, 1000000))
|
||||||
taken = db.fetchone("""
|
taken = db.fetchone("""
|
||||||
SELECT * FROM users
|
SELECT * FROM users
|
||||||
WHERE username = ?
|
WHERE username = ?
|
||||||
""", (username,))
|
""", (username,))
|
||||||
|
|
||||||
if not taken:
|
if not taken:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -91,7 +94,7 @@ def google_auth():
|
|||||||
)
|
)
|
||||||
user_data = get_session_info_email(user_email)
|
user_data = get_session_info_email(user_email)
|
||||||
|
|
||||||
origin = session.pop("origin", f"{url}")
|
origin = session.pop("origin", f"{url.replace('/api', '')}")
|
||||||
session.clear()
|
session.clear()
|
||||||
session["username"] = user_data["username"]
|
session["username"] = user_data["username"]
|
||||||
session["user_id"] = user_data["user_id"]
|
session["user_id"] = user_data["user_id"]
|
||||||
|
|||||||
Reference in New Issue
Block a user