diff --git a/frontend/package.json b/frontend/package.json
index 8e7f8b6..5fac733 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,6 +6,7 @@
"@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",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index e29116c..5d43de7 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -1,25 +1,16 @@
import React from "react";
import { Sidebar } from "./components";
-import { Route, Routes } from "react-router-dom";
+import { BrowserRouter, Route, Routes } from "react-router-dom";
import { HomePage, ChannelPage, PlayerPage } from "./pages";
+import { Layout } from "./containers";
function App() {
return (
-
-
-
-
-
Header
-
-
- } />
- } />
- } />
-
-
-
-
-
+
+
+ } />
+
+
);
}
diff --git a/frontend/src/components/header.component.tsx b/frontend/src/components/header.component.tsx
new file mode 100644
index 0000000..f63c1e8
--- /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 (
+
+
+ {/* */}
+
+ {/* */}
+
+
+ {/* */}
+ -
+
+
+ {/* */}
+ -
+
+
+ setIsNotificationsMenuOpen(false)}
+ >
+
+ Messages
+ 13
+
+
+ Sales
+ 2
+
+ alert("Alerts!")}>
+ Alerts
+
+
+
+ {/* */}
+ -
+
+ setIsProfileMenuOpen(false)}
+ >
+
+
+ Profile
+
+
+
+ Settings
+
+ alert("Log out!")}>
+
+ Log out
+
+
+
+
+
+
+ );
+};
+
+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 && (
+
+ )}
+ {/* */}
+ {channel.category_name}
+ >
+ );
+ }}
+ />
+
+ ))}
+
+
+ );
+};
+
+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..98a2c54
--- /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 { Sidebar } from "../components";
+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";
+
+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..e468977
--- /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: true,
+ 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..2174c65 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -7,9 +7,7 @@ import { BrowserRouter } from "react-router-dom";
ReactDOM.render(
-
-
-
+
,
document.getElementById("root")
);
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index c72f85a..561287d 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,18 @@ 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-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@@ -7130,6 +7196,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 +8176,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 +8319,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"