From a2116081cad8ed020895f9f68e723e1a0b4218d8 Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Thu, 30 May 2024 01:14:30 +0200 Subject: [PATCH 1/6] feat(namadillo_settings): creating settings modal and handling modal navigation --- apps/namadillo/src/App/AppRoutes.tsx | 78 ++++++++++++------- .../src/App/Common/TopNavigation.tsx | 16 +++- apps/namadillo/src/App/Settings/Advanced.tsx | 35 +++++++++ .../src/App/Settings/CurrencySelector.tsx | 21 +++++ .../App/Settings/CurrencySelectorEntry.tsx | 35 +++++++++ .../src/App/Settings/SettingsMain.tsx | 34 ++++++++ .../src/App/Settings/SettingsPanel.tsx | 67 ++++++++++++++++ .../App/Settings/SettingsPanelMenuItem.tsx | 31 ++++++++ apps/namadillo/src/App/Settings/routes.ts | 11 +++ 9 files changed, 296 insertions(+), 32 deletions(-) create mode 100644 apps/namadillo/src/App/Settings/Advanced.tsx create mode 100644 apps/namadillo/src/App/Settings/CurrencySelector.tsx create mode 100644 apps/namadillo/src/App/Settings/CurrencySelectorEntry.tsx create mode 100644 apps/namadillo/src/App/Settings/SettingsMain.tsx create mode 100644 apps/namadillo/src/App/Settings/SettingsPanel.tsx create mode 100644 apps/namadillo/src/App/Settings/SettingsPanelMenuItem.tsx create mode 100644 apps/namadillo/src/App/Settings/routes.ts diff --git a/apps/namadillo/src/App/AppRoutes.tsx b/apps/namadillo/src/App/AppRoutes.tsx index 6293be1d5..3b3779659 100644 --- a/apps/namadillo/src/App/AppRoutes.tsx +++ b/apps/namadillo/src/App/AppRoutes.tsx @@ -1,48 +1,68 @@ import { Router } from "@remix-run/router"; import { Route, + Routes, createBrowserRouter, createRoutesFromElements, + useLocation, } from "react-router-dom"; import { AccountOverview } from "./AccountOverview"; import { App } from "./App"; import { AnimatedTransition } from "./Common/AnimatedTransition"; import { Governance } from "./Governance"; +import { SettingsPanel } from "./Settings/SettingsPanel"; import { Staking } from "./Staking"; import GovernanceRoutes from "./Governance/routes"; +import SettingsRoutes from "./Settings/routes"; import StakingRoutes from "./Staking/routes"; -export const getRouter = (): Router => { - return createBrowserRouter( - createRoutesFromElements( - }> - - - - } - /> - - - - } - /> +export const MainRoutes = (): JSX.Element => { + const location = useLocation(); + const state = location.state as { backgroundLocation?: Location }; + + return ( + <> + + }> + + + + } + /> + + + + } + /> + + + + } + /> + + + - - - } + path={`${SettingsRoutes.index()}/*`} + element={} /> - - ) + + + ); +}; + +export const getRouter = (): Router => { + return createBrowserRouter( + createRoutesFromElements(} />) ); }; diff --git a/apps/namadillo/src/App/Common/TopNavigation.tsx b/apps/namadillo/src/App/Common/TopNavigation.tsx index abc4481f7..efd3d8a65 100644 --- a/apps/namadillo/src/App/Common/TopNavigation.tsx +++ b/apps/namadillo/src/App/Common/TopNavigation.tsx @@ -2,10 +2,11 @@ import { ToggleButton } from "@namada/components"; import { Chain } from "@namada/types"; import { ActiveAccount } from "App/Common/ActiveAccount"; import { ConnectExtensionButton } from "App/Common/ConnectExtensionButton"; +import SettingsRoutes from "App/Settings/routes"; import { ConnectStatus, useExtensionConnect } from "hooks/useExtensionConnect"; import { useAtom } from "jotai"; import { IoSettingsOutline } from "react-icons/io5"; -import { Link } from "react-router-dom"; +import { useLocation, useNavigate } from "react-router-dom"; import { hideBalancesAtom } from "slices/settings"; type Props = { @@ -15,6 +16,8 @@ type Props = { export const TopNavigation = ({ chain }: Props): JSX.Element => { const { connectionStatus } = useExtensionConnect(chain); const [hideBalances, setHideBalances] = useAtom(hideBalancesAtom); + const location = useLocation(); + const navigate = useNavigate(); return ( <> @@ -32,9 +35,16 @@ export const TopNavigation = ({ chain }: Props): JSX.Element => { onChange={() => setHideBalances(!hideBalances)} containerProps={{ className: "hidden text-white md:flex" }} /> - + )} diff --git a/apps/namadillo/src/App/Settings/Advanced.tsx b/apps/namadillo/src/App/Settings/Advanced.tsx new file mode 100644 index 000000000..c12fa9b8e --- /dev/null +++ b/apps/namadillo/src/App/Settings/Advanced.tsx @@ -0,0 +1,35 @@ +import { ActionButton, Input, Stack } from "@namada/components"; +import { useAtom } from "jotai"; +import { useState } from "react"; +import { rpcUrlAtom } from "slices/settings"; + +export const Advanced = (): JSX.Element => { + const [currentRpc, setCurrentRpc] = useAtom(rpcUrlAtom); + const [rpc, setRpc] = useState(currentRpc); + + const onSubmit = (e: React.FormEvent): void => { + e.preventDefault(); + setCurrentRpc(rpc); + window.history.back(); + }; + + return ( +
+ + setRpc(e.currentTarget.value)} + required + /> + + + Confirm + +
+ ); +}; diff --git a/apps/namadillo/src/App/Settings/CurrencySelector.tsx b/apps/namadillo/src/App/Settings/CurrencySelector.tsx new file mode 100644 index 000000000..cbc173b8f --- /dev/null +++ b/apps/namadillo/src/App/Settings/CurrencySelector.tsx @@ -0,0 +1,21 @@ +import { Stack } from "@namada/components"; +import { FiatCurrencyList } from "@namada/utils"; +import { useAtom } from "jotai"; +import { selectedCurrencyAtom } from "slices/settings"; +import { CurrencySelectorEntry } from "./CurrencySelectorEntry"; + +export const CurrencySelector = (): JSX.Element => { + const [selectedCurrency, setSelectedCurrency] = useAtom(selectedCurrencyAtom); + return ( + + {FiatCurrencyList.map((currency) => ( + setSelectedCurrency(currency.id)} + /> + ))} + + ); +}; diff --git a/apps/namadillo/src/App/Settings/CurrencySelectorEntry.tsx b/apps/namadillo/src/App/Settings/CurrencySelectorEntry.tsx new file mode 100644 index 000000000..1fb463a44 --- /dev/null +++ b/apps/namadillo/src/App/Settings/CurrencySelectorEntry.tsx @@ -0,0 +1,35 @@ +import { CurrencyInfoListItem } from "@namada/utils"; +import clsx from "clsx"; +import { twMerge } from "tailwind-merge"; + +type CurrencySelectorEntryType = { + currency: CurrencyInfoListItem; + selected: boolean; + onClick: () => void; +}; + +export const CurrencySelectorEntry = ({ + currency, + selected, + onClick, +}: CurrencySelectorEntryType): JSX.Element => { + return ( +
  • + +
  • + ); +}; diff --git a/apps/namadillo/src/App/Settings/SettingsMain.tsx b/apps/namadillo/src/App/Settings/SettingsMain.tsx new file mode 100644 index 000000000..d8cb85e4a --- /dev/null +++ b/apps/namadillo/src/App/Settings/SettingsMain.tsx @@ -0,0 +1,34 @@ +import { Stack, ToggleButton } from "@namada/components"; +import { SettingsPanelMenuItem } from "./SettingsPanelMenuItem"; +import SettingsRoutes from "./routes"; + +export const SettingsMain = (): JSX.Element => { + return ( +
    +
      + + +
    + + +

    Sign Arbitrary Message

    +

    + Enabling this setting puts you at risk of phishing attacks. Please + check what you are signing very carefully when using this feature. We + recommend keeping this setting turned off unless absolutely necessary +

    + {}} + /> +
    +
    + ); +}; diff --git a/apps/namadillo/src/App/Settings/SettingsPanel.tsx b/apps/namadillo/src/App/Settings/SettingsPanel.tsx new file mode 100644 index 000000000..84f794dc2 --- /dev/null +++ b/apps/namadillo/src/App/Settings/SettingsPanel.tsx @@ -0,0 +1,67 @@ +import { Modal } from "@namada/components"; +import clsx from "clsx"; +import { FaChevronLeft } from "react-icons/fa6"; +import { IoClose } from "react-icons/io5"; +import { Route, Routes, useLocation, useNavigate } from "react-router-dom"; +import { Advanced } from "./Advanced"; +import { CurrencySelector } from "./CurrencySelector"; +import { SettingsMain } from "./SettingsMain"; +import SettingsRoutes from "./routes"; + +export const SettingsPanel = (): JSX.Element => { + const location = useLocation(); + const navigate = useNavigate(); + + const onClose = (): void => { + if (location.state?.backgroundLocation) { + navigate((location.state.backgroundLocation as Location).pathname); + } else { + navigate("/"); + } + }; + + const onClickBack = (): void => { + navigate(SettingsRoutes.index(), { state: location.state }); + }; + + return ( + +
    +
    +

    Settings

    + + {location.pathname !== SettingsRoutes.index() && ( + + )} +
    + + } /> + } + /> + } /> + +
    +
    + ); +}; diff --git a/apps/namadillo/src/App/Settings/SettingsPanelMenuItem.tsx b/apps/namadillo/src/App/Settings/SettingsPanelMenuItem.tsx new file mode 100644 index 000000000..909d057ac --- /dev/null +++ b/apps/namadillo/src/App/Settings/SettingsPanelMenuItem.tsx @@ -0,0 +1,31 @@ +import clsx from "clsx"; +import { FaChevronRight } from "react-icons/fa6"; +import { Link, useLocation } from "react-router-dom"; + +type SettingsPanelMenuItemProps = { + text: string; + url: string; +}; + +export const SettingsPanelMenuItem = ({ + url, + text, +}: SettingsPanelMenuItemProps): JSX.Element => { + const location = useLocation(); + return ( +
  • + + {text} + + +
  • + ); +}; diff --git a/apps/namadillo/src/App/Settings/routes.ts b/apps/namadillo/src/App/Settings/routes.ts new file mode 100644 index 000000000..003d2448b --- /dev/null +++ b/apps/namadillo/src/App/Settings/routes.ts @@ -0,0 +1,11 @@ +import { RouteOutput, createRouteOutput } from "utils/routes"; + +export const index = (): string => "/settings"; + +export const routeOutput = createRouteOutput(index); + +export const currencySelection = (): RouteOutput => routeOutput("/currency"); + +export const advanced = (): RouteOutput => routeOutput("/advanced"); + +export default { index, currencySelection, advanced }; From 10218cc1f0795f0e04d1694d80abcc15c0b970a8 Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Thu, 30 May 2024 01:43:11 +0200 Subject: [PATCH 2/6] feat(components): adding more options and variants to ToggleButton --- packages/components/src/ToggleButton.tsx | 48 ++++++++++++++++++------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/packages/components/src/ToggleButton.tsx b/packages/components/src/ToggleButton.tsx index 416fd1406..0fd61fd5f 100644 --- a/packages/components/src/ToggleButton.tsx +++ b/packages/components/src/ToggleButton.tsx @@ -1,13 +1,6 @@ import clsx from "clsx"; import { twMerge } from "tailwind-merge"; -import { tv } from "tailwind-variants"; - -type Props = { - checked: boolean; - onChange: () => void; - label: string; - containerProps?: React.ComponentPropsWithoutRef<"label">; -}; +import { VariantProps, tv } from "tailwind-variants"; const toggleButtonClassList = tv({ slots: { @@ -17,32 +10,63 @@ const toggleButtonClassList = tv({ ), checkbox: clsx("invisible absolute pointer-events-none"), toggleContainer: clsx( - "relative rounded-3xl bg-rblack p-1 h-5 w-10 cursor-pointer", + "relative rounded-3xl p-1 h-5 w-10 cursor-pointer", "transition-all duration-100 ease-out-quad", "[&~span]:top-px" ), toggleIndicator: clsx( - "absolute left-0.5 top-0.5 h-[calc(100%-4px)] bg-yellow", + "absolute left-0.5 top-0.5 h-[calc(100%-4px)]", "aspect-square rounded-full transition-all duration-200 ease-out-quad" ), }, variants: { + color: { + yellow: { + toggleContainer: "bg-rblack", + toggleIndicator: "bg-yellow", + }, + white: { + toggleContainer: "bg-neutral-700", + toggleIndicator: "bg-white", + }, + }, + activeColor: { + yellow: {}, + }, checked: { true: { toggleIndicator: "translate-x-5", }, }, }, + compoundVariants: [ + { + checked: true, + activeColor: "yellow", + class: { + toggleIndicator: "bg-rblack", + toggleContainer: "bg-yellow", + }, + }, + ], }); +type ToggleButtonProps = { + onChange: () => void; + label: string; + containerProps?: React.ComponentPropsWithoutRef<"label">; +} & VariantProps; + export const ToggleButton = ({ checked, onChange, label, + color = "yellow", + activeColor, containerProps = {}, -}: Props): JSX.Element => { +}: ToggleButtonProps): JSX.Element => { const { container, checkbox, toggleContainer, toggleIndicator } = - toggleButtonClassList({ checked }); + toggleButtonClassList({ checked, color, activeColor }); const { className: containerClassName, ...containerPropsRest } = containerProps; From 1a933c9469b535b5dc0a5538bb7e897948760a22 Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Thu, 30 May 2024 01:43:33 +0200 Subject: [PATCH 3/6] feat(namadillo_settings): sign arbitrary messages control --- apps/namadillo/src/App/Settings/SettingsMain.tsx | 12 ++++++++++-- apps/namadillo/src/slices/settings.ts | 7 +++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/apps/namadillo/src/App/Settings/SettingsMain.tsx b/apps/namadillo/src/App/Settings/SettingsMain.tsx index d8cb85e4a..79a64a9ec 100644 --- a/apps/namadillo/src/App/Settings/SettingsMain.tsx +++ b/apps/namadillo/src/App/Settings/SettingsMain.tsx @@ -1,8 +1,14 @@ import { Stack, ToggleButton } from "@namada/components"; +import { useAtom } from "jotai"; +import { signArbitraryEnabledAtom } from "slices/settings"; import { SettingsPanelMenuItem } from "./SettingsPanelMenuItem"; import SettingsRoutes from "./routes"; export const SettingsMain = (): JSX.Element => { + const [signArbitraryEnabled, setSignArbitraryEnabled] = useAtom( + signArbitraryEnabledAtom + ); + return (
      @@ -25,8 +31,10 @@ export const SettingsMain = (): JSX.Element => {

      {}} + color="white" + activeColor="yellow" + checked={signArbitraryEnabled} + onChange={() => setSignArbitraryEnabled(!signArbitraryEnabled)} />
    diff --git a/apps/namadillo/src/slices/settings.ts b/apps/namadillo/src/slices/settings.ts index a12f8e646..4bf514627 100644 --- a/apps/namadillo/src/slices/settings.ts +++ b/apps/namadillo/src/slices/settings.ts @@ -8,6 +8,7 @@ type SettingsStorage = { hideBalances: boolean; rpcUrl: string; chainId: string; + signArbitraryEnabled: boolean; }; export const namadaExtensionConnectedAtom = atom(false); @@ -19,6 +20,7 @@ export const namadilloSettingsAtom = atomWithStorage( hideBalances: false, rpcUrl: process.env.NAMADA_INTERFACE_NAMADA_URL || "", chainId: process.env.NAMADA_INTERFACE_NAMADA_CHAIN_ID || "", + signArbitraryEnabled: false, } ); @@ -49,6 +51,11 @@ export const chainIdAtom = atom( changeSettings("chainId") ); +export const signArbitraryEnabledAtom = atom( + (get) => get(namadilloSettingsAtom).signArbitraryEnabled, + changeSettings("signArbitraryEnabled") +); + export const connectedChainsAtom = atom([]); export const addConnectedChainAtom = atom(null, (get, set, chain: ChainKey) => { const connectedChains = get(connectedChainsAtom); From 9fd6898f8617e3b0960e5c890cc528ceecd1af3d Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Thu, 30 May 2024 02:00:46 +0200 Subject: [PATCH 4/6] feat(namadillo_settings): warning to review the arbitrary message signing --- .../src/App/Settings/SettingsMain.tsx | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/apps/namadillo/src/App/Settings/SettingsMain.tsx b/apps/namadillo/src/App/Settings/SettingsMain.tsx index 79a64a9ec..56719a8a3 100644 --- a/apps/namadillo/src/App/Settings/SettingsMain.tsx +++ b/apps/namadillo/src/App/Settings/SettingsMain.tsx @@ -1,5 +1,6 @@ -import { Stack, ToggleButton } from "@namada/components"; +import { Alert, Stack, ToggleButton } from "@namada/components"; import { useAtom } from "jotai"; +import { IoWarning } from "react-icons/io5"; import { signArbitraryEnabledAtom } from "slices/settings"; import { SettingsPanelMenuItem } from "./SettingsPanelMenuItem"; import SettingsRoutes from "./routes"; @@ -10,7 +11,7 @@ export const SettingsMain = (): JSX.Element => { ); return ( -
    +
      { />
    - +

    Sign Arbitrary Message

    Enabling this setting puts you at risk of phishing attacks. Please check what you are signing very carefully when using this feature. We recommend keeping this setting turned off unless absolutely necessary

    + {signArbitraryEnabled && ( + +
    + + + + You are at risk of phishing attacks. Please review carefully what + you sign +
    +
    + )} Date: Thu, 30 May 2024 10:52:13 +0200 Subject: [PATCH 5/6] feat(namadillo_settings): improving modal navigation --- apps/namadillo/src/App/Settings/SettingsPanel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/namadillo/src/App/Settings/SettingsPanel.tsx b/apps/namadillo/src/App/Settings/SettingsPanel.tsx index 84f794dc2..747c5c309 100644 --- a/apps/namadillo/src/App/Settings/SettingsPanel.tsx +++ b/apps/namadillo/src/App/Settings/SettingsPanel.tsx @@ -14,9 +14,11 @@ export const SettingsPanel = (): JSX.Element => { const onClose = (): void => { if (location.state?.backgroundLocation) { - navigate((location.state.backgroundLocation as Location).pathname); + navigate((location.state.backgroundLocation as Location).pathname, { + replace: true, + }); } else { - navigate("/"); + navigate("/", { replace: true }); } }; From b75d8f3d48a7c2b365110786d6388246a3e11547 Mon Sep 17 00:00:00 2001 From: Pedro Rezende Date: Thu, 30 May 2024 10:57:32 +0200 Subject: [PATCH 6/6] feat(namadillo_settings): avoid to leave application --- apps/namadillo/src/App/Settings/Advanced.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/namadillo/src/App/Settings/Advanced.tsx b/apps/namadillo/src/App/Settings/Advanced.tsx index c12fa9b8e..83795cd66 100644 --- a/apps/namadillo/src/App/Settings/Advanced.tsx +++ b/apps/namadillo/src/App/Settings/Advanced.tsx @@ -1,16 +1,20 @@ import { ActionButton, Input, Stack } from "@namada/components"; +import SettingsRoute from "App/Settings/routes"; import { useAtom } from "jotai"; import { useState } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; import { rpcUrlAtom } from "slices/settings"; export const Advanced = (): JSX.Element => { + const navigate = useNavigate(); + const location = useLocation(); const [currentRpc, setCurrentRpc] = useAtom(rpcUrlAtom); const [rpc, setRpc] = useState(currentRpc); const onSubmit = (e: React.FormEvent): void => { e.preventDefault(); setCurrentRpc(rpc); - window.history.back(); + navigate(SettingsRoute.index(), { replace: true, state: location.state }); }; return (