diff --git a/frontend/.env b/frontend/.env index aaf30e6..94432cc 100644 --- a/frontend/.env +++ b/frontend/.env @@ -3,6 +3,6 @@ HTTPS=true SSL_CRT_FILE=/etc/letsencrypt/live/fergl.ie/cert.pem SSL_KEY_FILE=/etc/letsencrypt/live/fergl.ie/privkey.pem -_REACT_APP_API_URL=https://dev-streams.fergl.ie:8000 -REACT_APP_API_URL=https://api.streams.fergl.ie +REACT_APP_API_URL=https://dev-streams.fergl.ie:8000 +__REACT_APP_API_URL=https://api.streams.fergl.ie REACT_APP_SERVER_URL=http://localhost:9531 \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index 1efb8ec..3ea2076 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -23,3 +23,4 @@ yarn-debug.log* yarn-error.log* env.production .env.production +.vscode/ \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 8e7f8b6..1ab411b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,11 +6,13 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", + "@windmill/react-ui": "^0.6.0", "autoprefixer": "^10.4.4", "hls.js": "^1.1.5", "postcss": "^8.4.12", "react": "^17.0.2", "react-dom": "^17.0.2", + "react-icons": "^4.3.1", "react-router-dom": "6", "react-scripts": "5.0.0", "tailwindcss": "^3.0.23", diff --git a/frontend/public/index.html b/frontend/public/index.html index 1cf7b47..08121a0 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -21,6 +21,23 @@
+ + + + + \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index e29116c..96cf36c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,25 +1,14 @@ import React from "react"; -import { Sidebar } from "./components"; -import { Route, Routes } from "react-router-dom"; -import { HomePage, ChannelPage, PlayerPage } from "./pages"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import { Layout } from "./containers"; function App() { return ( -
-
- -
-
Header
-
- - } /> - } /> - } /> - -
-
-
-
+ + + } /> + + ); } diff --git a/frontend/src/components/epg.component.tsx b/frontend/src/components/epg.component.tsx index d73e61b..3249bb0 100644 --- a/frontend/src/components/epg.component.tsx +++ b/frontend/src/components/epg.component.tsx @@ -17,10 +17,82 @@ const EPGComponent = ({ channelId }: IEPGComponentProps) => { fetchPrograms().catch(console.error); }, [channelId]); return programs && programs.length ? ( -
Here be the epg for {channelId}
- ) : ( -

Loading...

- ); +
    +
  1. +
    +
    + + + +
    +
    +
    +
    + + Released on December 2, 2021 + +
    +
  2. + +
  3. +
    +
    + + + +
    +
    +
    +
    + + Released on December 23, 2021 + +
    +
  4. +
  5. +
    +
    + + + +
    +
    +
    +
    + + Released on January 5, 2022 + +
    +
  6. +
+ ) : null; }; export default EPGComponent; diff --git a/frontend/src/components/header.component.tsx b/frontend/src/components/header.component.tsx new file mode 100644 index 0000000..12e4887 --- /dev/null +++ b/frontend/src/components/header.component.tsx @@ -0,0 +1,157 @@ +import React from "react"; +import { + Avatar, + Badge, + Input, + Dropdown, + DropdownItem, + WindmillContext, +} from "@windmill/react-ui"; +import { SidebarContext } from "../context"; +import { + BellIcon, + MenuIcon, + MoonIcon, + OutlineCogIcon, + OutlineLogoutIcon, + OutlinePersonIcon, + SearchIcon, + SunIcon, +} from "../icons"; + +const Header = () => { + const { mode, toggleMode } = React.useContext(WindmillContext); + const { toggleSidebar } = React.useContext(SidebarContext); + + const [isNotificationsMenuOpen, setIsNotificationsMenuOpen] = + React.useState(false); + const [isProfileMenuOpen, setIsProfileMenuOpen] = React.useState(false); + + const handleNotificationsClick = () => { + setIsNotificationsMenuOpen(!isNotificationsMenuOpen); + }; + + const handleProfileClick = () => { + setIsProfileMenuOpen(!isProfileMenuOpen); + }; + return ( +
+
+ {/* */} + + {/* */} +
+
+
+
+ +
+
+ +
+
+ ); +}; + +export default Header; diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts index c2c4cca..056bd5a 100644 --- a/frontend/src/components/index.ts +++ b/frontend/src/components/index.ts @@ -1,6 +1,7 @@ import HLSPlayer from "./hls-player.component"; import Navbar from "./navbar.component"; -import Sidebar from "./sidebar.component"; +import Sidebar from "./sidebar/sidebar-content.component"; import EPGComponent from "./epg.component"; +import ThemedSuspence from "./themed-suspence.component"; -export { Navbar, Sidebar, HLSPlayer, EPGComponent }; +export { Navbar, Sidebar, HLSPlayer, EPGComponent, ThemedSuspence }; diff --git a/frontend/src/components/sidebar.component.tsx b/frontend/src/components/sidebar.component.tsx deleted file mode 100644 index 0efa6b6..0000000 --- a/frontend/src/components/sidebar.component.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React from "react"; -import { Link, NavLink } from "react-router-dom"; -import { Channel } from "../models/channel"; - -const Sidebar = () => { - const [channels, setChannels] = React.useState([]); - const [filteredChannels, setFilteredChannels] = React.useState([]); - React.useEffect(() => { - const fetchChannels = async () => { - const res = await fetch(`${process.env.REACT_APP_API_URL}/channels`); - const data = await res.json(); - setChannels(data); - setFilteredChannels(data); - }; - - fetchChannels().catch(console.error); - }, []); - const _searchChannels = ($event: React.ChangeEvent) => { - const searchString = $event.target.value; - if (searchString) { - const filteredChannels = channels.filter((c) => { - const result = c.category_name - .toLowerCase() - .includes(searchString.toLowerCase()); - console.log( - "sidebar.component", - `Category Name: ${c.category_name}`, - `Search String: ${searchString}` - ); - console.log("sidebar.component", "Result", result); - return result; - }); - setFilteredChannels(filteredChannels); - } else { - setFilteredChannels(channels); - } - }; - return ( - - ); -}; - -export default Sidebar; diff --git a/frontend/src/components/sidebar/desktop-sidebar.component.tsx b/frontend/src/components/sidebar/desktop-sidebar.component.tsx new file mode 100644 index 0000000..2e8218a --- /dev/null +++ b/frontend/src/components/sidebar/desktop-sidebar.component.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import SidebarContent from "./sidebar-content.component"; + +const DesktopSidebar = () => { + return ( + + ); +}; + +export default DesktopSidebar; diff --git a/frontend/src/components/sidebar/index.tsx b/frontend/src/components/sidebar/index.tsx new file mode 100644 index 0000000..5daf8cd --- /dev/null +++ b/frontend/src/components/sidebar/index.tsx @@ -0,0 +1,13 @@ +import DesktopSidebar from "./desktop-sidebar.component"; +import MobileSidebar from "./mobile-sidebar.component"; + +const Sidebar = () => { + return ( + <> + + + + ); +}; + +export default Sidebar; diff --git a/frontend/src/components/sidebar/mobile-sidebar.component.tsx b/frontend/src/components/sidebar/mobile-sidebar.component.tsx new file mode 100644 index 0000000..0638b8d --- /dev/null +++ b/frontend/src/components/sidebar/mobile-sidebar.component.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import { Transition, Backdrop } from "@windmill/react-ui"; +import { SidebarContext } from "../../context"; +import SidebarContent from "./sidebar-content.component"; + +const MobileSidebar = () => { + const { isSidebarOpen, closeSidebar } = React.useContext(SidebarContext); + return ( + + <> + + + + + + + + + + ); +}; + +export default MobileSidebar; diff --git a/frontend/src/components/sidebar/sidebar-content.component.tsx b/frontend/src/components/sidebar/sidebar-content.component.tsx new file mode 100644 index 0000000..d6c3cd8 --- /dev/null +++ b/frontend/src/components/sidebar/sidebar-content.component.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import { NavLink, Route } from "react-router-dom"; +import { Channel } from "../../models/channel"; + +const SidebarContent = () => { + const [channels, setChannels] = React.useState([]); + const [filteredChannels, setFilteredChannels] = React.useState([]); + React.useEffect(() => { + const fetchChannels = async () => { + const res = await fetch(`${process.env.REACT_APP_API_URL}/channels`); + const data = await res.json(); + setChannels(data); + setFilteredChannels(data); + }; + + fetchChannels().catch(console.error); + }, []); + const _searchChannels = ($event: React.ChangeEvent) => { + const searchString = $event.target.value; + if (searchString) { + const filteredChannels = channels.filter((c) => { + const result = c.category_name + .toLowerCase() + .includes(searchString.toLowerCase()); + console.log( + "sidebar.component", + `Category Name: ${c.category_name}`, + `Search String: ${searchString}` + ); + console.log("sidebar.component", "Result", result); + return result; + }); + setFilteredChannels(filteredChannels); + } else { + setFilteredChannels(channels); + } + }; + return ( +
+ + Xtreamium + +
    + {filteredChannels.map((channel: Channel) => ( +
  • + + `inline-flex items-center w-full text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200 ${ + isActive && "text-gray-800 dark:text-gray-100" + }` + } + children={({ isActive }) => { + return ( + <> + {isActive && ( + + )} + {/*
  • + ))} +
+
+ ); +}; + +export default SidebarContent; diff --git a/frontend/src/components/themed-suspence.component.tsx b/frontend/src/components/themed-suspence.component.tsx new file mode 100644 index 0000000..17b2018 --- /dev/null +++ b/frontend/src/components/themed-suspence.component.tsx @@ -0,0 +1,11 @@ +import React from "react"; + +const ThemedSuspence = () => { + return ( +
+ Loading... +
+ ); +}; + +export default ThemedSuspence; diff --git a/frontend/src/containers/index.ts b/frontend/src/containers/index.ts new file mode 100644 index 0000000..d206622 --- /dev/null +++ b/frontend/src/containers/index.ts @@ -0,0 +1,4 @@ +import Layout from "./layout.container"; +import Main from "./main.container"; + +export { Main, Layout }; diff --git a/frontend/src/containers/layout.container.tsx b/frontend/src/containers/layout.container.tsx new file mode 100644 index 0000000..1b80e31 --- /dev/null +++ b/frontend/src/containers/layout.container.tsx @@ -0,0 +1,40 @@ +import React, { Suspense } from "react"; +import { useLocation, Routes, Route } from "react-router-dom"; +import Header from "../components/header.component"; +import Main from "./main.container"; +import { ChannelPage, PlayerPage } from "../pages"; +import ThemedSuspence from "../components/themed-suspence.component"; +import { SidebarContext } from "../context"; +import Sidebar from "../components/sidebar"; + +const Layout = () => { + const { isSidebarOpen, closeSidebar } = React.useContext(SidebarContext); + let location = useLocation(); + + React.useEffect(() => { + closeSidebar(); + }, [location]); + return ( +
+ + +
+
+
+ }> + + } /> + } /> + + +
+
+
+ ); +}; + +export default Layout; diff --git a/frontend/src/containers/main.container.tsx b/frontend/src/containers/main.container.tsx new file mode 100644 index 0000000..2e35f2d --- /dev/null +++ b/frontend/src/containers/main.container.tsx @@ -0,0 +1,14 @@ +import React from "react"; + +interface IMainProps { + children: JSX.Element; +} +const Main = ({ children }: IMainProps) => { + return ( +
+
{children}
+
+ ); +}; + +export default Main; diff --git a/frontend/src/context/index.ts b/frontend/src/context/index.ts new file mode 100644 index 0000000..46777dd --- /dev/null +++ b/frontend/src/context/index.ts @@ -0,0 +1,4 @@ +import { SidebarContext } from "./sidebar.context"; +import { ThemeContext } from "./theme.context"; + +export { SidebarContext, ThemeContext }; diff --git a/frontend/src/context/sidebar.context.tsx b/frontend/src/context/sidebar.context.tsx new file mode 100644 index 0000000..a4cc7a8 --- /dev/null +++ b/frontend/src/context/sidebar.context.tsx @@ -0,0 +1,39 @@ +import React from "react"; + +interface ISidebarProvider { + children: React.ReactChild; +} +interface ISidebarProviderContext { + isSidebarOpen: boolean; + toggleSidebar: () => void; + closeSidebar: () => void; +} +export const SidebarContext = React.createContext({ + isSidebarOpen: false, + toggleSidebar: () => {}, + closeSidebar: () => {}, +}); + +export const SidebarProvider = ({ children }: ISidebarProvider) => { + const [isSidebarOpen, setIsSidebarOpen] = React.useState(false); + + const toggleSidebar = () => { + setIsSidebarOpen(!isSidebarOpen); + }; + const closeSidebar = () => { + setIsSidebarOpen(false); + }; + + const value = React.useMemo( + () => ({ + isSidebarOpen, + toggleSidebar, + closeSidebar, + }), + [isSidebarOpen] + ); + + return ( + {children} + ); +}; diff --git a/frontend/src/context/theme.context.tsx b/frontend/src/context/theme.context.tsx new file mode 100644 index 0000000..e325fc7 --- /dev/null +++ b/frontend/src/context/theme.context.tsx @@ -0,0 +1,81 @@ +import React, { + useState, + useEffect, + useRef, + useLayoutEffect, + useMemo, +} from "react"; + +/** + * Saves the old theme for future use + * @param {string} theme - Name of curent theme + * @return {string} previousTheme + */ +function usePrevious(theme: any) { + const ref = useRef(); + useEffect(() => { + ref.current = theme; + }); + return ref.current; +} + +/** + * Gets user preferences from local storage + * @param {string} key - localStorage key + * @return {array} getter and setter for user preferred theme + */ +function useStorageTheme( + key: any +): [string, React.Dispatch>] { + const userPreference = + !!window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + ? "1" + : "0"; + + const [theme, setTheme] = useState( + // use stored theme; fallback to user preference + localStorage.getItem(key) || userPreference + ); + + // update stored theme + useEffect(() => { + localStorage.setItem(key, theme); + }, [theme, key]); + + return [theme, setTheme]; +} + +// create context +export const ThemeContext = React.createContext({}); +interface IThemeProvider { + children: React.ReactChild; +} +// create context provider +export const ThemeProvider = ({ children }: IThemeProvider) => { + const [theme, setTheme] = useStorageTheme("theme"); + + // update root element class on theme change + const oldTheme = usePrevious(theme); + useLayoutEffect(() => { + document.documentElement.classList.remove(`theme-${oldTheme}`); + document.documentElement.classList.add(`theme-${theme}`); + }, [theme, oldTheme]); + + function toggleTheme() { + if (theme === "light") setTheme("dark"); + else setTheme("light"); + } + + const value = useMemo( + () => ({ + theme, + toggleTheme, + }), + [theme] + ); + + return ( + {children} + ); +}; diff --git a/frontend/src/icons/bell.svg b/frontend/src/icons/bell.svg new file mode 100644 index 0000000..00f6356 --- /dev/null +++ b/frontend/src/icons/bell.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/buttons.svg b/frontend/src/icons/buttons.svg new file mode 100644 index 0000000..f3dabf8 --- /dev/null +++ b/frontend/src/icons/buttons.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/cards.svg b/frontend/src/icons/cards.svg new file mode 100644 index 0000000..c21276b --- /dev/null +++ b/frontend/src/icons/cards.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/cart.svg b/frontend/src/icons/cart.svg new file mode 100644 index 0000000..432cf89 --- /dev/null +++ b/frontend/src/icons/cart.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/charts.svg b/frontend/src/icons/charts.svg new file mode 100644 index 0000000..df919e5 --- /dev/null +++ b/frontend/src/icons/charts.svg @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/frontend/src/icons/chat.svg b/frontend/src/icons/chat.svg new file mode 100644 index 0000000..c623259 --- /dev/null +++ b/frontend/src/icons/chat.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/dropdown.svg b/frontend/src/icons/dropdown.svg new file mode 100644 index 0000000..0981381 --- /dev/null +++ b/frontend/src/icons/dropdown.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/edit.svg b/frontend/src/icons/edit.svg new file mode 100644 index 0000000..8bfa8a3 --- /dev/null +++ b/frontend/src/icons/edit.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/forbidden.svg b/frontend/src/icons/forbidden.svg new file mode 100644 index 0000000..0edfe61 --- /dev/null +++ b/frontend/src/icons/forbidden.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/forms.svg b/frontend/src/icons/forms.svg new file mode 100644 index 0000000..1aabe76 --- /dev/null +++ b/frontend/src/icons/forms.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/github.svg b/frontend/src/icons/github.svg new file mode 100644 index 0000000..824ef14 --- /dev/null +++ b/frontend/src/icons/github.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/icons/heart.svg b/frontend/src/icons/heart.svg new file mode 100644 index 0000000..e3cd1a4 --- /dev/null +++ b/frontend/src/icons/heart.svg @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/frontend/src/icons/home.svg b/frontend/src/icons/home.svg new file mode 100644 index 0000000..e024614 --- /dev/null +++ b/frontend/src/icons/home.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/index.ts b/frontend/src/icons/index.ts new file mode 100644 index 0000000..13a6450 --- /dev/null +++ b/frontend/src/icons/index.ts @@ -0,0 +1,59 @@ +import { ReactComponent as ButtonsIcon } from './buttons.svg' +import { ReactComponent as CardsIcon } from './cards.svg' +import { ReactComponent as ChartsIcon } from './charts.svg' +import { ReactComponent as FormsIcon } from './forms.svg' +import { ReactComponent as HomeIcon } from './home.svg' +import { ReactComponent as ModalsIcon } from './modals.svg' +import { ReactComponent as PagesIcon } from './pages.svg' +import { ReactComponent as TablesIcon } from './tables.svg' +import { ReactComponent as HeartIcon } from './heart.svg' +import { ReactComponent as EditIcon } from './edit.svg' +import { ReactComponent as TrashIcon } from './trash.svg' +import { ReactComponent as ForbiddenIcon } from './forbidden.svg' +import { ReactComponent as GithubIcon } from './github.svg' +import { ReactComponent as TwitterIcon } from './twitter.svg' +import { ReactComponent as MailIcon } from './mail.svg' +import { ReactComponent as CartIcon } from './cart.svg' +import { ReactComponent as ChatIcon } from './chat.svg' +import { ReactComponent as MoneyIcon } from './money.svg' +import { ReactComponent as PeopleIcon } from './people.svg' +import { ReactComponent as SearchIcon } from './search.svg' +import { ReactComponent as MoonIcon } from './moon.svg' +import { ReactComponent as SunIcon } from './sun.svg' +import { ReactComponent as BellIcon } from './bell.svg' +import { ReactComponent as MenuIcon } from './menu.svg' +import { ReactComponent as DropdownIcon } from './dropdown.svg' +import { ReactComponent as OutlinePersonIcon } from './outlinePerson.svg' +import { ReactComponent as OutlineCogIcon } from './outlineCog.svg' +import { ReactComponent as OutlineLogoutIcon } from './outlineLogout.svg' + +export { + ButtonsIcon, + CardsIcon, + ChartsIcon, + FormsIcon, + HomeIcon, + ModalsIcon, + PagesIcon, + TablesIcon, + HeartIcon, + EditIcon, + TrashIcon, + ForbiddenIcon, + GithubIcon, + TwitterIcon, + MailIcon, + CartIcon, + ChatIcon, + MoneyIcon, + PeopleIcon, + SearchIcon, + MoonIcon, + SunIcon, + BellIcon, + MenuIcon, + DropdownIcon, + OutlinePersonIcon, + OutlineCogIcon, + OutlineLogoutIcon, +} diff --git a/frontend/src/icons/mail.svg b/frontend/src/icons/mail.svg new file mode 100644 index 0000000..3f2cd46 --- /dev/null +++ b/frontend/src/icons/mail.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/menu.svg b/frontend/src/icons/menu.svg new file mode 100644 index 0000000..468e70a --- /dev/null +++ b/frontend/src/icons/menu.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/modals.svg b/frontend/src/icons/modals.svg new file mode 100644 index 0000000..bbb0a8c --- /dev/null +++ b/frontend/src/icons/modals.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/money.svg b/frontend/src/icons/money.svg new file mode 100644 index 0000000..ca4f5de --- /dev/null +++ b/frontend/src/icons/money.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/moon.svg b/frontend/src/icons/moon.svg new file mode 100644 index 0000000..e461af1 --- /dev/null +++ b/frontend/src/icons/moon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/outlineCog.svg b/frontend/src/icons/outlineCog.svg new file mode 100644 index 0000000..8264cfa --- /dev/null +++ b/frontend/src/icons/outlineCog.svg @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/frontend/src/icons/outlineLogout.svg b/frontend/src/icons/outlineLogout.svg new file mode 100644 index 0000000..4e106ea --- /dev/null +++ b/frontend/src/icons/outlineLogout.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/outlinePerson.svg b/frontend/src/icons/outlinePerson.svg new file mode 100644 index 0000000..deaa5bc --- /dev/null +++ b/frontend/src/icons/outlinePerson.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/pages.svg b/frontend/src/icons/pages.svg new file mode 100644 index 0000000..9575c65 --- /dev/null +++ b/frontend/src/icons/pages.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/people.svg b/frontend/src/icons/people.svg new file mode 100644 index 0000000..5bca8a3 --- /dev/null +++ b/frontend/src/icons/people.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/search.svg b/frontend/src/icons/search.svg new file mode 100644 index 0000000..cc1f047 --- /dev/null +++ b/frontend/src/icons/search.svg @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/frontend/src/icons/sun.svg b/frontend/src/icons/sun.svg new file mode 100644 index 0000000..51a940f --- /dev/null +++ b/frontend/src/icons/sun.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/tables.svg b/frontend/src/icons/tables.svg new file mode 100644 index 0000000..9b52991 --- /dev/null +++ b/frontend/src/icons/tables.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/trash.svg b/frontend/src/icons/trash.svg new file mode 100644 index 0000000..2bcc7bc --- /dev/null +++ b/frontend/src/icons/trash.svg @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/frontend/src/icons/twitter.svg b/frontend/src/icons/twitter.svg new file mode 100644 index 0000000..c9509ba --- /dev/null +++ b/frontend/src/icons/twitter.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index a927631..ad9cbbb 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -3,13 +3,10 @@ import ReactDOM from "react-dom"; import "./index.css"; import App from "./App"; import reportWebVitals from "./reportWebVitals"; -import { BrowserRouter } from "react-router-dom"; ReactDOM.render( - - - + , document.getElementById("root") ); diff --git a/frontend/src/models/stream.ts b/frontend/src/models/stream.ts index 3f39a44..c24a644 100644 --- a/frontend/src/models/stream.ts +++ b/frontend/src/models/stream.ts @@ -4,7 +4,7 @@ export interface Stream { stream_type: string; stream_id: number; stream_icon: string; - epg_channel_id: null; + epg_channel_id: string; added: number; category_id: number; custom_sid: string; diff --git a/frontend/src/pages/channel.page.tsx b/frontend/src/pages/channel.page.tsx index ce83bc5..d904ae6 100644 --- a/frontend/src/pages/channel.page.tsx +++ b/frontend/src/pages/channel.page.tsx @@ -1,7 +1,21 @@ +import { + Table, + TableHeader, + TableCell, + TableBody, + TableRow, + TableContainer, + Badge, + Avatar, + Button, +} from "@windmill/react-ui"; import React, { Suspense } from "react"; import { useParams } from "react-router-dom"; +import { AiOutlinePlayCircle } from "react-icons/ai"; +import { FaChromecast } from "react-icons/fa"; import { Stream } from "../models/stream"; -const EPGComponent = React.lazy(() => import("../components/epg.component")); // Lazy-loaded +import { convertEpochToSpecificTimezone } from "../utils/date-utils"; +import { EPGComponent } from "../components"; const ChannelPage = () => { let params = useParams(); @@ -49,74 +63,89 @@ const ChannelPage = () => { } }; return ( -
- - - - - - + +
- Channel name - Type
+ + + Channel + Type + - - - {streams.map((stream: Stream) => ( - <> - - - - - + {false && ( + - - {stream.epg_channel_id && ( - - - )} - - ))} - -
-
-
- stream -
-
-

- {stream.name} -

-
+ + + {streams.map((stream: Stream) => [ + + +
+ +
+

{stream.name}

+

+ Added: {convertEpochToSpecificTimezone(stream.added)} +

-
- - + + + {stream.stream_type} + + +
+
- + + + + + , +
+ Loading epg}> + +
- Loading epg}> - - -
-
+ , + ])} + + + ); }; diff --git a/frontend/src/utils/date-utils.ts b/frontend/src/utils/date-utils.ts new file mode 100644 index 0000000..b2d578f --- /dev/null +++ b/frontend/src/utils/date-utils.ts @@ -0,0 +1,9 @@ +export const convertEpochToSpecificTimezone = ( + timeEpoch: number, + offset: number = 0 +) => { + var d = new Date(timeEpoch * 1000); + var utc = d.getTime() + d.getTimezoneOffset() * 60000; //This converts to UTC 00:00 + var nd = new Date(utc + 3600000 * offset); + return nd.toLocaleDateString(); +}; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 6b63080..666944f 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,3 +1,6 @@ +const defaultTheme = require("tailwindcss/defaultTheme"); +const windmill = require("@windmill/react-ui/config"); + module.exports = { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { @@ -5,6 +8,10 @@ module.exports = { fontFamily: { Rampart: ["Raleway", "sans-serif"], }, + boxShadow: { + bottom: + "0 5px 6px -7px rgba(0, 0, 0, 0.6), 0 2px 4px -5px rgba(0, 0, 0, 0.06)", + }, }, }, plugins: [], diff --git a/frontend/yarn.lock b/frontend/yarn.lock index c72f85a..ff4d3cf 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1024,7 +1024,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.17.8" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== @@ -1570,6 +1570,13 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@tailwindcss/forms@^0.3.2": + version "0.3.4" + resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.3.4.tgz#e4939dc16450eccf4fd2029770096f38cbb556d4" + integrity sha512-vlAoBifNJUkagB+PAdW4aHMe4pKmSLroH398UPgIogBFc91D2VlHUxe4pjxQhiJl0Nfw53sHSJSQBSTQBZP3vA== + dependencies: + mini-svg-data-uri "^1.2.3" + "@testing-library/dom@^8.0.0": version "8.12.0" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.12.0.tgz#fef5e545533fb084175dda6509ee71d7d2f72e23" @@ -2153,6 +2160,18 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" +"@windmill/react-ui@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@windmill/react-ui/-/react-ui-0.6.0.tgz#259ee5d8b08088d4b0258fdc7595760dda6b09fe" + integrity sha512-VjvRC0YI8V/uUMWU70XL0jHzBYmRGPMlvauLjdHJ0h60cSFm2ZrcvwIVktPi9eWw9au2cXov13rkwvVmPM/Yww== + dependencies: + "@tailwindcss/forms" "^0.3.2" + classnames "2.2.6" + deepmerge "4.2.2" + postcss "^8.2.15" + react-focus-lock "2.4.1" + react-transition-group "4.4.1" + "@xtuc/ieee754@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" @@ -2872,6 +2891,11 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== +classnames@2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" + integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== + clean-css@^5.2.2: version "5.2.4" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.2.4.tgz#982b058f8581adb2ae062520808fb2429bd487a4" @@ -3377,7 +3401,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: +deepmerge@4.2.2, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -3440,6 +3464,11 @@ detect-newline@^3.0.0: resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + detect-node@^2.0.4: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" @@ -3530,6 +3559,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -4237,6 +4274,11 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== +focus-lock@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.7.0.tgz#b2bfb0ca7beacc8710a1ff74275fe0dc60a1d88a" + integrity sha512-LI7v2mH02R55SekHYdv9pRHR9RajVNyIJ2N5IEkWbg7FT5ZmJ9Hw4mWxHeEUcd+dJo0QmzztHvDvWcc7prVFsw== + follow-redirects@^1.0.0: version "1.14.9" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" @@ -5841,6 +5883,11 @@ mini-css-extract-plugin@^2.4.5: dependencies: schema-utils "^4.0.0" +mini-svg-data-uri@^1.2.3: + version "1.4.4" + resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" + integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== + minimalistic-assert@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -6852,7 +6899,7 @@ postcss@^7.0.35: picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6, postcss@^8.4.7: +postcss@^8.2.15, postcss@^8.3.5, postcss@^8.4.12, postcss@^8.4.4, postcss@^8.4.6, postcss@^8.4.7: version "8.4.12" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== @@ -6913,7 +6960,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.8.1: +prop-types@^15.6.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7001,6 +7048,13 @@ react-app-polyfill@^3.0.0: regenerator-runtime "^0.13.9" whatwg-fetch "^3.6.2" +react-clientside-effect@^1.2.2: + version "1.2.5" + resolved "https://registry.yarnpkg.com/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz#e2c4dc3c9ee109f642fac4f5b6e9bf5bcd2219a3" + integrity sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA== + dependencies: + "@babel/runtime" "^7.12.13" + react-dev-utils@^12.0.0: version "12.0.0" resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.0.tgz#4eab12cdb95692a077616770b5988f0adf806526" @@ -7045,6 +7099,23 @@ react-error-overlay@^6.0.10: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6" integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA== +react-focus-lock@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/react-focus-lock/-/react-focus-lock-2.4.1.tgz#e842cc93da736b5c5d331799012544295cbcee4f" + integrity sha512-c5ZP56KSpj9EAxzScTqQO7bQQNPltf/W1ZEBDqNDOV1XOIwvAyHX0O7db9ekiAtxyKgnqZjQlLppVg94fUeL9w== + dependencies: + "@babel/runtime" "^7.0.0" + focus-lock "^0.7.0" + prop-types "^15.6.2" + react-clientside-effect "^1.2.2" + use-callback-ref "^1.2.1" + use-sidecar "^1.0.1" + +react-icons@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.3.1.tgz#2fa92aebbbc71f43d2db2ed1aed07361124e91ca" + integrity sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" @@ -7130,6 +7201,16 @@ react-scripts@5.0.0: optionalDependencies: fsevents "^2.3.2" +react-transition-group@4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -8100,7 +8181,7 @@ tsconfig-paths@^3.12.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^1.8.1: +tslib@^1.8.1, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -8243,6 +8324,19 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-callback-ref@^1.2.1: + version "1.2.5" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5" + integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg== + +use-sidecar@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b" + integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA== + dependencies: + detect-node-es "^1.1.0" + tslib "^1.9.3" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"