From cb6e20a28791eb6fdf0201cbd83a9e6958bf2d0d Mon Sep 17 00:00:00 2001 From: Memas Deligeorgakis Date: Thu, 1 Sep 2022 16:17:21 +0200 Subject: [PATCH] Staking, Governance, Public Goods Funding - Refactoring menu (#46) * Add sub menu * Add possibility to use a separate theme that is depending on current navigation route * Set the theme to be set to light mode and hide color mode toggle on entering Staking TopLevelMenu * Add menu highlight to mobile menu * Move settings icon and color mode toggle to less prominent location * Refactor routing to be able to use Location in App.tsx * corrected a UI bug where alignment was incorrect in 2nd level menu * Fixed broken UI in initial account creation based on PR feedback * Fixed main container height based on PR feedback --- .../SeedPhraseConfirmation.components.ts | 3 - .../Steps/Start/Start.components.ts | 1 - .../AccountOverview.components.ts | 2 +- .../src/App/App.components.ts | 6 +- apps/namada-interface/src/App/App.tsx | 145 +++++---- apps/namada-interface/src/App/AppRoutes.tsx | 6 +- .../StakingAndGovernance.tsx | 50 ++- .../TopNavigation/topNavigation.components.ts | 37 ++- .../src/App/TopNavigation/topNavigation.tsx | 302 ++++++++++++++---- .../topNavigationLoggedIn.components.ts | 4 +- .../TopNavigation/topNavigationLoggedIn.tsx | 54 +--- apps/namada-interface/src/App/types.ts | 45 ++- apps/namada-interface/src/index.tsx | 8 +- .../namada-interface/src/utils/theme/theme.ts | 94 ++++-- 14 files changed, 543 insertions(+), 214 deletions(-) diff --git a/apps/namada-interface/src/App/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts b/apps/namada-interface/src/App/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts index bc88f3a8a00..975167a2de0 100644 --- a/apps/namada-interface/src/App/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts +++ b/apps/namada-interface/src/App/AccountCreation/Steps/SeedPhraseConfirmation/SeedPhraseConfirmation.components.ts @@ -30,16 +30,13 @@ export const AccountInformationViewUpperPartContainer = styled.div` export const AccountInformationForm = styled.div` display: flex; flex-direction: column; - justify-content: space-between; align-items: flex-start; - height: 100%; width: 100%; `; export const DescriptionAndInputContainer = styled.div` display: flex; flex-direction: column; - justify-content: space-between; align-items: flex-start; height: 100%; width: 100%; diff --git a/apps/namada-interface/src/App/AccountCreation/Steps/Start/Start.components.ts b/apps/namada-interface/src/App/AccountCreation/Steps/Start/Start.components.ts index 62574adcbc2..0dc09cac696 100644 --- a/apps/namada-interface/src/App/AccountCreation/Steps/Start/Start.components.ts +++ b/apps/namada-interface/src/App/AccountCreation/Steps/Start/Start.components.ts @@ -3,7 +3,6 @@ import styled from "styled-components/macro"; export const StartViewContainer = styled.div` display: flex; flex-direction: column; - justify-content: space-between; align-items: center; height: 100%; `; diff --git a/apps/namada-interface/src/App/AccountOverview/AccountOverview.components.ts b/apps/namada-interface/src/App/AccountOverview/AccountOverview.components.ts index db82a9ba202..6241ccaf56a 100644 --- a/apps/namada-interface/src/App/AccountOverview/AccountOverview.components.ts +++ b/apps/namada-interface/src/App/AccountOverview/AccountOverview.components.ts @@ -21,7 +21,6 @@ const getColor = ( } }; export const AccountOverviewContainer = styled.div` - display: flex; flex-direction: column; justify-content: start; align-items: center; @@ -67,6 +66,7 @@ export const AccountTab = styled.div` display: flex; justify-content: center; align-items: center; + height: 52px; color: ${(props) => props.theme.colors.utility2.main80}; background-color: ${(props) => props.theme.colors.utility1.main60}; font-size: 14px; diff --git a/apps/namada-interface/src/App/App.components.ts b/apps/namada-interface/src/App/App.components.ts index e2c863f339b..8bc5bc2b8e2 100644 --- a/apps/namada-interface/src/App/App.components.ts +++ b/apps/namada-interface/src/App/App.components.ts @@ -7,6 +7,8 @@ enum ComponentColor { BackgroundColor, } +const topSectionHeight = "164px"; + const getColor = ( color: ComponentColor, theme: DesignConfiguration @@ -61,7 +63,7 @@ export const TopSection = styled.section` display: flex; justify-content: center; align-items: flex-start; - height: 120px; + height: ${topSectionHeight}; width: 100%; `; @@ -69,7 +71,7 @@ export const BottomSection = styled.section` display: flex; justify-content: center; align-items: flex-start; - height: calc(100% - 120px); + height: calc(100% - ${topSectionHeight}); width: 100%; `; diff --git a/apps/namada-interface/src/App/App.tsx b/apps/namada-interface/src/App/App.tsx index 4185a612895..1f4a8109d59 100644 --- a/apps/namada-interface/src/App/App.tsx +++ b/apps/namada-interface/src/App/App.tsx @@ -1,18 +1,13 @@ /* eslint-disable max-len */ import { useState, useEffect } from "react"; -import { - unstable_HistoryRouter as HistoryRouter, - Routes, - Route, - Outlet, -} from "react-router-dom"; +import { Routes, Route, Outlet, useLocation, Location } from "react-router-dom"; import { createBrowserHistory } from "history"; import { AnimatePresence } from "framer-motion"; import { ThemeProvider } from "styled-components/macro"; // internal import { getTheme } from "utils/theme"; -import { TopLevelRoute } from "./types"; +import { TopLevelRoute, locationToTopLevelRoute } from "./types"; import { TopNavigation } from "./TopNavigation"; import { AccountCreation } from "./AccountCreation"; @@ -51,13 +46,22 @@ export const AnimatedTransition = (props: { ); }; +// based on location we decide whether to use placeholder theme +const getShouldUsePlaceholderTheme = (location: Location): boolean => { + const topLevelRoute = locationToTopLevelRoute(location); + const isStaking = topLevelRoute === TopLevelRoute.Staking; + return isStaking; +}; + function App(): JSX.Element { const [isLightMode, setIsLightMode] = useState(true); - + const location = useLocation(); const [password, setPassword] = useState(); const [store, setStore] = useState(); const [persistor, setPersistor] = useState(); - const theme = getTheme(isLightMode); + + const ShouldUsePlaceholderTheme = getShouldUsePlaceholderTheme(location); + const theme = getTheme(isLightMode, ShouldUsePlaceholderTheme); useEffect(() => { if (store) { @@ -67,21 +71,19 @@ function App(): JSX.Element { if (password && store && persistor) { return ( - - + + - - setPassword(undefined)} - /> - + setPassword(undefined)} + /> @@ -93,8 +95,8 @@ function App(): JSX.Element { - - + + ); } @@ -102,62 +104,59 @@ function App(): JSX.Element { * Unlock Wallet & Create Master Seed flow: */ return ( - - - - - - setPassword(undefined)} - /> - - - - + + + + + setPassword(undefined)} + /> + + + + + + + + } + > - - + setStore(makeStore(password))} + /> } - > - + + setStore(makeStore(password))} /> - } - /> - - setStore(makeStore(password))} - /> - - } - /> - } - /> - - - - - - - + + } + /> + } /> + + + + + + ); } diff --git a/apps/namada-interface/src/App/AppRoutes.tsx b/apps/namada-interface/src/App/AppRoutes.tsx index f9300bb702b..3810f0cab55 100644 --- a/apps/namada-interface/src/App/AppRoutes.tsx +++ b/apps/namada-interface/src/App/AppRoutes.tsx @@ -127,11 +127,9 @@ const AppRoutes = ({ store, persistor, password }: Props): JSX.Element => { } /> + } diff --git a/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx b/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx index 6fb939b33d6..e5014a0f865 100644 --- a/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx +++ b/apps/namada-interface/src/App/StakingAndGovernance/StakingAndGovernance.tsx @@ -1,13 +1,61 @@ +import { useEffect } from "react"; import { NavigationContainer } from "components/NavigationContainer"; import { Heading, HeadingLevel } from "components/Heading"; -import { StakingAndGovernanceContainer } from "./StakingAndGovernance.components"; +import { Routes, Route, useLocation, useNavigate } from "react-router-dom"; +import { StakingAndGovernanceContainer } from "./StakingAndGovernance.components"; +import { + TopLevelRoute, + StakingAndGovernanceSubRoute, + locationToStakingAndGovernanceSubRoute, +} from "App/types"; export const StakingAndGovernance = (): JSX.Element => { + const location = useLocation(); + const navigate = useNavigate(); + + // we need one of the sub routes, staking alone has nothing + const stakingAndGovernanceSubRoute = + locationToStakingAndGovernanceSubRoute(location); + useEffect(() => { + if (!!!stakingAndGovernanceSubRoute) { + navigate( + `${TopLevelRoute.Staking}${StakingAndGovernanceSubRoute.Staking}` + ); + } + }); + return ( Staking & Governance + + *} /> + + StakingAndGovernanceSubRoute.Staking + + } + /> + + StakingAndGovernanceSubRoute.Governance + + } + /> + + StakingAndGovernanceSubRoute.PublicGoodsFunding + + } + /> + ` + justify-content: ${(props) => + props.spaceBetween ? "space-between" : "flex-end"}; + min-height: 48px; + width: 100%; + max-width: 760px; + margin-top: 24px; +`; + export const TopNavigationLogoContainer = styled.div` display: flex; width: 100%; @@ -70,6 +81,10 @@ export const MiddleSection = styled(Section)` width: 60%; `; +export const SubMenuContainer = styled(Section)` + justify-content: center; +`; + export const RightSection = styled(Section)` justify-content: end; width: 25%; @@ -115,6 +130,20 @@ export const MenuItem = styled.button<{ isSelected?: boolean }>` props.isSelected ? isSelected(props.theme.colors.utility2.main) : ""} `; +export const MenuItemForSecondRow = styled(MenuItem)` + margin-right: 16px; + margin-left: 0px; +`; + +export const MenuItemSubComponent = styled(MenuItem)` + margin: 0 22px 0 48px; + @media screen and (max-width: 860px) { + padding: 16px 0; + height: 100%; + justify-content: flex-start; + } +`; + export const MenuItemTextContainer = styled.div` display: flex; justify-content: center; @@ -182,15 +211,17 @@ export const MenuCloseButton = styled.button` export const OnlyInSmall = styled.div` display: flex; flex-direction: column; - // @media only screen and (min-width: 1024px) { + @media only screen and (min-width: 860px) { display: none; } `; export const OnlyInMedium = styled.div` - display: block; - //@media only screen and (max-width: 1024px) { + display: flex; + flex-direction: column; + align-items: center; + @media only screen and (max-width: 860px) { display: none; } diff --git a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx index ab568e9d64c..736670fad29 100644 --- a/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx +++ b/apps/namada-interface/src/App/TopNavigation/topNavigation.tsx @@ -1,18 +1,31 @@ -import React, { useState, useContext } from "react"; +import React, { useState, useContext, useEffect } from "react"; import { ThemeContext } from "styled-components"; -import { useNavigate, useLocation, NavigateFunction } from "react-router-dom"; +import { + useNavigate, + useLocation, + NavigateFunction, + Location, +} from "react-router-dom"; import { Persistor } from "redux-persist"; -import { Provider } from "react-redux"; +import { useAppDispatch, useAppSelector } from "store"; -import { TopLevelRoute } from "App/types"; +import { + TopLevelRoute, + StakingAndGovernanceSubRoute, + locationToTopLevelRoute, + locationToStakingAndGovernanceSubRoute, +} from "App/types"; import { Image, ImageName } from "components/Image"; import { Toggle } from "components/Toggle"; +import { Select } from "components/Select"; import { TopNavigationContainer, LeftSection, MiddleSection, RightSection, MenuItem, + MenuItemForSecondRow, + MenuItemSubComponent, MenuItemTextContainer, ColorModeContainer, LogoContainer, @@ -20,6 +33,7 @@ import { OnlyInMedium, TopNavigationContainerRow, TopNavigationContainerSecondRow, + TopNavigationSecondRowInnerContainer, TopNavigationLogoContainer, MenuButton, MobileMenu, @@ -27,12 +41,15 @@ import { MobileMenuListItem, MobileMenuHeader, MenuCloseButton, + SubMenuContainer, } from "./topNavigation.components"; import { AppStore } from "store/store"; +import { setChainId, SettingsState } from "slices/settings"; import TopNavigationLoggedIn from "./topNavigationLoggedIn"; import { SettingsButton } from "./topNavigationLoggedIn.components"; import { Icon, IconName } from "components/Icon"; +import Config, { Chain } from "config"; /** * this is rendered in one of 2 places depending of the screen size @@ -42,7 +59,7 @@ const TopNavigationMenuItems = (props: { }): React.ReactElement => { const { navigate } = props; const location = useLocation(); - + const topLevelPath = `/${location.pathname.split("/")[1]}`; return ( <> {/* Wallet */} @@ -67,14 +84,20 @@ const TopNavigationMenuItems = (props: { {/* Staking */} { + navigate(`${TopLevelRoute.Staking}`); + }} + isSelected={topLevelPath === TopLevelRoute.Staking} > Staking {/* Governance */} { + navigate(`${TopLevelRoute.Governance}`); + }} + isSelected={topLevelPath === TopLevelRoute.Governance} > Governance @@ -82,6 +105,101 @@ const TopNavigationMenuItems = (props: { ); }; +type SecondMenuRowProps = { + location: Location; + navigate: NavigateFunction; + setIsLightMode: React.Dispatch>; +}; + +const SecondMenuRow = (props: SecondMenuRowProps): React.ReactElement => { + const dispatch = useAppDispatch(); + const { navigate, location, setIsLightMode } = props; + const topLevelRoute = locationToTopLevelRoute(location); + const stakingAndGovernanceSubRoute = + locationToStakingAndGovernanceSubRoute(location); + const isSubMenuContentVisible = topLevelRoute === TopLevelRoute.Staking; + const { chainId } = useAppSelector((state) => state.settings); + + useEffect(() => { + if (topLevelRoute === TopLevelRoute.Staking) { + setIsLightMode(false); + } + }); + + // callback func for select component + const handleNetworkSelect = ( + event: React.ChangeEvent + ): void => { + const { value } = event.target; + dispatch(setChainId(value)); + }; + + // transform for select component + const chains = Object.values(Config.chain); + const networks = Object.values(chains).map(({ id, alias }: Chain) => ({ + label: alias, + value: id, + })); + + return ( + + {isSubMenuContentVisible && ( + + { + navigate( + `${TopLevelRoute.Staking}${StakingAndGovernanceSubRoute.Staking}` + ); + }} + isSelected={ + stakingAndGovernanceSubRoute === + StakingAndGovernanceSubRoute.Staking + } + > + Staking + + { + navigate( + `${TopLevelRoute.Staking}${StakingAndGovernanceSubRoute.Governance}` + ); + }} + isSelected={ + stakingAndGovernanceSubRoute === + StakingAndGovernanceSubRoute.Governance + } + > + Governance + + { + navigate( + `${TopLevelRoute.Staking}${StakingAndGovernanceSubRoute.PublicGoodsFunding}` + ); + }} + isSelected={ + stakingAndGovernanceSubRoute === + StakingAndGovernanceSubRoute.PublicGoodsFunding + } + > + Public Goods Funding + + + )} + + + - - ); }; diff --git a/apps/namada-interface/src/App/types.ts b/apps/namada-interface/src/App/types.ts index ebd6ab9f603..95fce33d4c5 100644 --- a/apps/namada-interface/src/App/types.ts +++ b/apps/namada-interface/src/App/types.ts @@ -1,3 +1,5 @@ +import { Location } from "react-router-dom"; + export const TopLevelRouteGenerator = { // this creates a route for TopLevelRoute.Token createRouteForTokenByTokenId: (tokenId: string) => `/token/${tokenId}`, @@ -28,7 +30,8 @@ export enum TopLevelRoute { TokenIbcTransfer = "/token/:id/ibc-transfer", /* STAKING AND GOVERNANCE */ - StakingAndGovernance = "/staking-and-governance", + Staking = "/staking", + Governance = "/governance", /* SETTINGS */ Settings = "/settings", @@ -37,6 +40,46 @@ export enum TopLevelRoute { SettingsAccountSettings = "/settings/account-settings/:id", } +export enum StakingAndGovernanceSubRoute { + Staking = "/staking", + Governance = "/governance", + PublicGoodsFunding = "/public-goods-funding", +} + +// returns the root route from react router +// location: host.com/xxx/thisWillNotBeReturned +// returns: TopLevelRoute.Xxx +export const locationToTopLevelRoute = ( + location: Location +): TopLevelRoute | undefined => { + const firstPartOtPath = `/${location.pathname.split("/")[1]}`; + const values = Object.values(TopLevelRoute); + const secondPartOtPathAsStakingAndGovernanceSubRoute = + firstPartOtPath as unknown as TopLevelRoute; + + if (values.includes(secondPartOtPathAsStakingAndGovernanceSubRoute)) { + return secondPartOtPathAsStakingAndGovernanceSubRoute; + } + return undefined; +}; + +// returns the second level route from react router +// location: host.com/thisWillNotBeReturned/xxx +// returns: StakingAndGovernanceSubRoute.Xxx +export const locationToStakingAndGovernanceSubRoute = ( + location: Location +): StakingAndGovernanceSubRoute | undefined => { + const secondPartOtPath = `/${location.pathname.split("/")[2]}`; + const values = Object.values(StakingAndGovernanceSubRoute); + const secondPartOtPathAsStakingAndGovernanceSubRoute = + secondPartOtPath as unknown as StakingAndGovernanceSubRoute; + + if (values.includes(secondPartOtPathAsStakingAndGovernanceSubRoute)) { + return secondPartOtPathAsStakingAndGovernanceSubRoute; + } + return undefined; +}; + export enum LocalStorageKeys { MasterSeed = "com.anoma.network:seed", Session = "com.anoma.network:session", diff --git a/apps/namada-interface/src/index.tsx b/apps/namada-interface/src/index.tsx index 1e82657dbca..0199baef299 100644 --- a/apps/namada-interface/src/index.tsx +++ b/apps/namada-interface/src/index.tsx @@ -1,12 +1,18 @@ import React from "react"; import ReactDOM from "react-dom"; +import { unstable_HistoryRouter as HistoryRouter } from "react-router-dom"; +import { createBrowserHistory } from "history"; import { App } from "./App"; import reportWebVitals from "./reportWebVitals"; import "./index.css"; +const history = createBrowserHistory({ window }); + ReactDOM.render( - + + + , document.getElementById("root") ); diff --git a/apps/namada-interface/src/utils/theme/theme.ts b/apps/namada-interface/src/utils/theme/theme.ts index d22cf58a8d9..b85cc09c634 100644 --- a/apps/namada-interface/src/utils/theme/theme.ts +++ b/apps/namada-interface/src/utils/theme/theme.ts @@ -198,6 +198,55 @@ const namadaLightColors: Colors = { }, }; +const placeholderThemeColors: Colors = { + primary: { + main: "#FFFF00", + main80: "#CCCC00", + main60: "#999900", + main40: "#666600", + main20: "#333300", + }, + secondary: { + main: "#11DFDF", + main80: "#41E5E5", + main60: "#70ECEC", + main40: "#A0F2F2", + main20: "#CFF9F9", + }, + tertiary: { + main: "#11DFDF", + main80: "#41E5E5", + main60: "#70ECEC", + main40: "#A0F2F2", + main20: "#CFF9F9", + }, + utility1: { + main: "#FFFFFF", + main80: "#F8F8F8", + main75: "#F8F8F8", + main70: "#F8F8F8", + main60: "#F3F3F3", + main40: "#F0F0F0", + main20: "#D9D9D9", + }, + utility2: { + main: "#000000", + main80: "#333333", + main60: "#666666", + main40: "#999999", + main20: "#CCCCCC", + }, + utility3: { + success: "#61C454", + warning: "#F5BF50", + error: "#ED695D", + highAttention: "#FF0000", + lowAttention: "#FAFF00", + black: "#000000", + white: "#FFFFFF", + }, +}; + const namadaSpacers = { horizontal: { xs: "8px", @@ -257,34 +306,33 @@ const namadaTypography = { }, }; -enum Brand { - Namada, -} - -const getBrand = (): Brand => { - return Brand.Namada; -}; - export type ThemeConfigurations = { isLightMode: boolean; }; -export const getTheme = (isLightMode: boolean): DesignConfiguration => { - // branding mode - const brand = getBrand(); - - // return the correct theming configuration - switch (brand) { - case Brand.Namada: - const namadaTheme: DesignConfiguration = { - colors: isLightMode ? namadaDarkColors : namadaLightColors, - spacers: namadaSpacers, - borderRadius: namadaBorderRadius, - typography: namadaTypography, - themeConfigurations: { isLightMode }, - }; - return namadaTheme; +export const getTheme = ( + isLightMode: boolean, + shouldUsePlaceholderTheme?: boolean +): DesignConfiguration => { + if (shouldUsePlaceholderTheme) { + const placeholderTheme: DesignConfiguration = { + colors: placeholderThemeColors, + spacers: namadaSpacers, + borderRadius: namadaBorderRadius, + typography: namadaTypography, + themeConfigurations: { isLightMode }, + }; + return placeholderTheme; } + + const namadaTheme: DesignConfiguration = { + colors: isLightMode ? namadaDarkColors : namadaLightColors, + spacers: namadaSpacers, + borderRadius: namadaBorderRadius, + typography: namadaTypography, + themeConfigurations: { isLightMode }, + }; + return namadaTheme; }; export type Theme = {