diff --git a/packages/frontend/PAGES.ts b/packages/frontend/PAGES.ts new file mode 100644 index 0000000..52828d6 --- /dev/null +++ b/packages/frontend/PAGES.ts @@ -0,0 +1,12 @@ +export const pages = [ + { label: "Dashboard", id: "dashboard", path: "/" }, + { label: "Automate", id: "automate", path: "/automate" }, + { label: "Swap Tokens", id: "swap-tokens", path: "/swap-tokens" }, + { + label: "Import Positions", + id: "import-positions", + path: "/import-positions", + }, + { label: "Exit Positions", id: "exit-positions", path: "/exit-positions" }, + { label: "Info", id: "info", path: "/info" }, +]; diff --git a/packages/frontend/features/layout/Appbar.tsx b/packages/frontend/features/layout/Appbar.tsx new file mode 100644 index 0000000..44be9d6 --- /dev/null +++ b/packages/frontend/features/layout/Appbar.tsx @@ -0,0 +1,120 @@ +import Router from "next/router"; +import styled from "styled-components"; +import { useState } from "react"; +import { pages } from "../../PAGES"; + +const Container = styled.div` + color: white; + background: var(--bg); + height: 72px; + box-shadow: 0 6px 6px 0px rgba(0, 0, 0, 0.3); + z-index: 1; + position: relative; +`; + +const LogoText = styled.div` + color: var(--highlight); + text-shadow: 0px 0px 12px rgba(254, 146, 31, 0.4); + + font-family: IBM Plex Sans, Roboto, Sans-Serif; + font-size: 32px; + font-weight: 200; + text-transform: uppercase; + letter-spacing: 4px; + + position: absolute; + left: 50%; + top: 50%; + transform: translate(calc(-50% + 4px), -50%); +`; + +const MenuButton = styled.div` + color: #aaa; + font-size: 24px; + padding: 12px; + position: absolute; + right: 0; + top: 50%; + transform: translate(-12px, -50%); + cursor: pointer; +`; + +const Menu = styled.div` + width: 90%; + height: 100vh; + background: var(--dark-bg); + position: fixed; + z-index: 1; + left: ${(p) => (p.isOpen ? `0` : `-100%`)}; + transition: left 200ms ease-in-out; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +`; + +const Overlay = styled.div` + pointer-events: ${(p) => (p.show ? `unset` : `none`)}; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.5); + position: fixed; + z-index: 1; + opacity: ${(p) => (p.show ? `1` : `0`)}; + transition: opacity 200ms ease-in-out; +`; + +const CloseButton = styled.div` + color: white; + font-size: 24px; + padding: 12px 16px; + position: absolute; + right: 12px; + top: 12px; + cursor: pointer; +`; + +const MenuItem = styled.div` + font-family: IBM Plex Sans; + text-transform: uppercase; + font-weight: 200; + letter-spacing: 4px; + color: ${(p) => (p.active ? `var(--highlight)` : `#999`)}; + text-align: center; + margin-bottom: 3rem; + font-size: 18px; + cursor: pointer; +`; + +const Appbar = ({ activePage }) => { + const [isOpen, setIsOpen] = useState(false); + const openMenu = () => setIsOpen(true); + const closeMenu = () => setIsOpen(false); + return ( + <> + + Dedge + + + + + + {pages.map((page) => ( + { + closeMenu() + Router.push(page.path) + }} + > + {page.label} + + ))} + + + ); +}; + +export default Appbar; diff --git a/packages/frontend/features/layout/AppbarLayout.tsx b/packages/frontend/features/layout/AppbarLayout.tsx new file mode 100644 index 0000000..4b8dd4d --- /dev/null +++ b/packages/frontend/features/layout/AppbarLayout.tsx @@ -0,0 +1,28 @@ +import styled from "styled-components"; +import Appbar from "./Appbar"; +import StatusBar from "../status-bar/StatusBar"; + +const Container = styled.div` + width: 100%; + height: 100vh; + background: var(--dark-bg); + display: flex; + flex-direction: column; +`; + +const Content = styled.div` + overflow: auto; + flex: 1; +`; + +const AppbarLayout = ({ children, activePage }) => { + return ( + + + + {children} + + ); +}; + +export default AppbarLayout; diff --git a/packages/frontend/features/layout/Layout.tsx b/packages/frontend/features/layout/Layout.tsx new file mode 100644 index 0000000..804420e --- /dev/null +++ b/packages/frontend/features/layout/Layout.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import useScreenSize from "./useScreenSize"; +import AppbarLayout from "./AppbarLayout"; +import SidebarLayout from "./SidebarLayout"; + +const Layout = ({ children, activePage }) => { + const { isMobile } = useScreenSize(); + if (isMobile) { + return {children}; + } + return {children}; +}; + +export default Layout; diff --git a/packages/frontend/features/layout/Sidebar.tsx b/packages/frontend/features/layout/Sidebar.tsx new file mode 100644 index 0000000..2c89ee5 --- /dev/null +++ b/packages/frontend/features/layout/Sidebar.tsx @@ -0,0 +1,61 @@ +import Router from "next/router"; +import styled from "styled-components"; +import useScreenSize from "./useScreenSize"; +import { pages } from "../../PAGES"; + +const Container = styled.div` + background: var(--bg); + width: ${(p) => p.width}; + height: 100%; + + text-align: right; + padding: 24px; + display: flex; + flex-direction: column; + justify-content: center; +`; + +const LogoText = styled.div` + font-family: IBM Plex Sans, sans-serif; + font-size: 24px; + font-weight: 300; + color: var(--highlight); + text-transform: uppercase; + letter-spacing: 4px; + margin-right: -6px; + text-shadow: 0px 0px 12px rgba(254, 146, 31, 0.4); + + margin-top: 1rem; + margin-bottom: 2rem; +`; + +const NavItem = styled.div` + cursor: pointer; + color: ${(p) => (p.active ? `white` : `#666`)}; + margin-bottom: 2rem; + + &:hover { + color: var(--highlight); + } +`; + +const Sidebar = ({ activePage }) => { + const { isTablet } = useScreenSize(); + return ( + + Dedge + + {pages.map((page) => ( + Router.push(page.path)} + > + {page.label} + + ))} + + ); +}; + +export default Sidebar; diff --git a/packages/frontend/features/layout/SidebarLayout.tsx b/packages/frontend/features/layout/SidebarLayout.tsx new file mode 100644 index 0000000..d42143c --- /dev/null +++ b/packages/frontend/features/layout/SidebarLayout.tsx @@ -0,0 +1,37 @@ +import styled from "styled-components"; +import Sidebar from "./Sidebar"; +import StatusBar from "../status-bar/StatusBar"; + +const Container = styled.div` + width: 100%; + height: 100vh; + background: var(--dark-bg); + display: flex; +`; + +const Main = styled.div` + height: 100%; + flex: 1; + overflow: auto; + display: flex; + flex-direction: column; +`; + +const Content = styled.div` + flex: 1; + overflow: auto; +`; + +const SidebarLayout = ({ children, activePage }) => { + return ( + + +
+ + {children} +
+
+ ); +}; + +export default SidebarLayout; diff --git a/packages/frontend/features/layout/useScreenSize.ts b/packages/frontend/features/layout/useScreenSize.ts new file mode 100644 index 0000000..c14db80 --- /dev/null +++ b/packages/frontend/features/layout/useScreenSize.ts @@ -0,0 +1,11 @@ +import { useMedia } from "use-media"; + +const useScreenSize = () => { + const isDesktop = useMedia({ minWidth: "1200px" }); + const isTablet = useMedia({ minWidth: "600px" }) && !isDesktop; + const isMobile = !isDesktop && !isTablet; + + return { isDesktop, isTablet, isMobile }; +}; + +export default useScreenSize; diff --git a/packages/frontend/features/status-bar/MobileStatusBar.tsx b/packages/frontend/features/status-bar/MobileStatusBar.tsx new file mode 100644 index 0000000..bc0cb15 --- /dev/null +++ b/packages/frontend/features/status-bar/MobileStatusBar.tsx @@ -0,0 +1,98 @@ +import styled from "styled-components"; +import { useState } from "react"; + +const Container = styled.div` + width: 100%; + height: 72px; + background: black; + position: relative; +`; + +const Content = styled.div` + background black; + display: grid; + grid-template-columns: 1fr 1fr; + height: ${(p) => (p.isOpen ? `216px` : `72px`)}; + overflow: hidden; + transition: height 200ms ease-in-out; + position: relative; +`; + +const StatusItem = styled.div` + height: 72px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +`; +const Value = styled.div` + color: white; +`; +const Label = styled.div` + color: var(--highlight); + font-size: 12px; +`; + +const TriangleIndicator = styled.div` + position: absolute; + bottom: 4px; + left: 50%; + color: rgba(255, 255, 255, 0.15); + font-size: 24px; + transform: ${(p) => + p.invert ? "rotate(-180deg) translateX(50%)" : "translateX(-50%)"}; +`; + +const Status = ({ label, value, percent = false }) => { + const valueStr = percent + ? `${parseFloat(value).toFixed(2)}%` + : `$${parseFloat(value).toFixed(2)}`; + return ( + + {valueStr} + + + ); +}; + +const LastUpdate = ({ lastUpdated }) => { + const valueStr = "23 seconds ago"; + return ( + + {valueStr} + + + ); +}; + +const MobileStatusBar = ({ statusData }) => { + const [isOpen, setIsOpen] = useState(false); + const toggleOpen = () => setIsOpen(!isOpen); + + const { + supplyBalance, + borrowBalance, + borrowPercent, + liquidationPrice, + ethPrice, + lastUpdated, + } = statusData; + return ( + + + + + + + + + + + + + + + ); +}; + +export default MobileStatusBar; diff --git a/packages/frontend/features/status-bar/StatusBar.tsx b/packages/frontend/features/status-bar/StatusBar.tsx new file mode 100644 index 0000000..d289076 --- /dev/null +++ b/packages/frontend/features/status-bar/StatusBar.tsx @@ -0,0 +1,23 @@ +import useScreenSize from "../layout/useScreenSize"; +import MobileStatusBar from "./MobileStatusBar"; +import WideStatusBar from "./WideStatusBar"; + +const StatusBar = () => { + const { isMobile, isTablet } = useScreenSize(); + + const statusData = { + supplyBalance: 14242.13, + borrowBalance: 5242.12, + borrowPercent: 0.52, // 52% + liquidationPrice: 123.32, + ethPrice: 152.08, + lastUpdated: new Date(1586928677168), + }; + + if (isMobile || isTablet) { + return ; + } + return ; +}; + +export default StatusBar; diff --git a/packages/frontend/features/status-bar/WideStatusBar.tsx b/packages/frontend/features/status-bar/WideStatusBar.tsx new file mode 100644 index 0000000..cfd75b1 --- /dev/null +++ b/packages/frontend/features/status-bar/WideStatusBar.tsx @@ -0,0 +1,85 @@ +import styled from "styled-components"; + +const Container = styled.div` + width: 100%; + height: 120px; + background: black; + position: relative; +`; + +const Content = styled.div` + width: 100%; + max-width: 1200px; + height: 100%; + margin: auto; + padding: 12px; + display: flex; + justify-content: space-around; + align-items: center; +`; + +const StatusItem = styled.div` + height: 72px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; + flex: 1; +`; +const Value = styled.div` + color: white; + font-size: 24px; +`; +const Label = styled.div` + color: var(--highlight); + font-size: 12px; +`; + +const Status = ({ label, value, percent = false }) => { + const valueStr = percent + ? `${parseFloat(value).toFixed(2)}%` + : `$${parseFloat(value).toFixed(2)}`; + return ( + + {valueStr} + + + ); +}; + +const LastUpdate = ({ lastUpdated }) => { + const valueStr = "23 seconds ago"; + return ( + + {valueStr} + + + ); +}; + +const WideStatusBar = ({ statusData }) => { + const { + supplyBalance, + borrowBalance, + borrowPercent, + liquidationPrice, + ethPrice, + lastUpdated, + } = statusData; + return ( + + + + + + + + + + + + + ); +}; + +export default WideStatusBar; diff --git a/packages/frontend/package-lock.json b/packages/frontend/package-lock.json index c20be90..f364706 100644 --- a/packages/frontend/package-lock.json +++ b/packages/frontend/package-lock.json @@ -8582,6 +8582,11 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, + "use-media": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-media/-/use-media-1.4.0.tgz", + "integrity": "sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA==" + }, "use-subscription": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/use-subscription/-/use-subscription-1.1.1.tgz", diff --git a/packages/frontend/package.json b/packages/frontend/package.json index fe55b98..a5e1750 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -22,7 +22,8 @@ "react-dom": "^16.13.1", "rimble-ui": "^0.13.1", "styled-components": "^5.0.1", - "unstated-next": "^1.1.0" + "unstated-next": "^1.1.0", + "use-media": "^1.4.0" }, "devDependencies": { "@types/node": "^13.9.2", diff --git a/packages/frontend/pages/_app.tsx b/packages/frontend/pages/_app.tsx index 421c470..4cea569 100644 --- a/packages/frontend/pages/_app.tsx +++ b/packages/frontend/pages/_app.tsx @@ -1,6 +1,7 @@ import { AppProps } from "next/app"; import { BaseStyles, theme, ToastMessage } from "rimble-ui"; import { ThemeProvider, withTheme } from "styled-components"; +import { useRouter } from "next/router"; import Connection from "../containers/Connection"; import Contracts from "../containers/Contracts"; @@ -10,6 +11,9 @@ import CoinsContainer from "../containers/Coins"; import VaultsContainer from "../containers/Vaults"; import Head from "next/head"; +import "../theme.css"; +import Layout from "../features/layout/Layout"; + const customTheme = { ...theme, space: [0, 4, 8, 16, 32, 64, 128, 256, 512], @@ -30,6 +34,8 @@ const WithProviders = ({ children }) => ( ); function MyApp({ Component, pageProps }: AppProps) { + const router = useRouter(); + console.log(router.pathname); return ( @@ -65,6 +71,15 @@ function MyApp({ Component, pageProps }: AppProps) { /> + + +