From 58467996a25994940f5ea3b8901862001666e861 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Wed, 23 Oct 2024 19:05:10 +0300 Subject: [PATCH 1/2] Added prompt window handling for old and new UI Added ConnectionRequest page Updated styles --- .../src/popup/pages/Settings/Settings.tsx | 14 +- .../src/popup/popupX/constants/routes.ts | 6 + .../ExternalRequestLayout.scss | 18 +++ .../ExternalRequestLayout.tsx | 38 +++++ .../ExternalRequestLayout/index.ts | 1 + .../FullscreenPromptLayout.tsx | 74 +++++++++ .../FullscreenPromptLayout/index.ts | 1 + .../MainLayout/Header/Header.scss | 16 +- .../Header/components/MenuButton.tsx | 11 +- .../Header/components/MenuTiles.tsx | 11 +- .../ConnectionRequest/ConnectionRequest.scss | 13 ++ .../ConnectionRequest/ConnectionRequest.tsx | 71 +++++++++ .../prompts/ConnectionRequest/i18n/en.ts | 10 ++ .../pages/prompts/ConnectionRequest/index.ts | 1 + .../popup/popupX/shared/Button/Button.scss | 12 ++ .../src/popup/popupX/shared/Button/Button.tsx | 4 +- .../src/popup/popupX/shared/Page/Page.scss | 1 + .../src/popup/popupX/shared/utils/helpers.ts | 14 ++ .../src/popup/popupX/shell/Routes.tsx | 17 +- .../src/popup/popupX/styles/_elements.scss | 2 + .../shared/utils/message-prompt-handlers.ts | 150 ++++++++++++++++++ .../browser-wallet/src/popup/shell/Root.tsx | 31 +++- .../browser-wallet/src/popup/shell/Routes.tsx | 121 +++----------- .../src/popup/shell/i18n/locales/en.ts | 2 + .../src/popup/store/settings.ts | 2 + .../browser-wallet/src/popup/store/utils.ts | 2 + .../src/shared/storage/access.ts | 2 + .../src/shared/storage/types.ts | 6 + 28 files changed, 520 insertions(+), 131 deletions(-) create mode 100644 packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.scss create mode 100644 packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/index.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.scss create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.tsx create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/i18n/en.ts create mode 100644 packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/index.ts create mode 100644 packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts diff --git a/packages/browser-wallet/src/popup/pages/Settings/Settings.tsx b/packages/browser-wallet/src/popup/pages/Settings/Settings.tsx index 3c3dcd3bb..d7c259450 100644 --- a/packages/browser-wallet/src/popup/pages/Settings/Settings.tsx +++ b/packages/browser-wallet/src/popup/pages/Settings/Settings.tsx @@ -4,8 +4,8 @@ import { useTranslation } from 'react-i18next'; import { absoluteRoutes } from '@popup/constants/routes'; import NavList from '@popup/shared/NavList'; import { useAtom } from 'jotai'; -import { themeAtom } from '@popup/store/settings'; -import { Theme } from '@shared/storage/types'; +import { themeAtom, uiStyleAtom } from '@popup/store/settings'; +import { Theme, UiStyle } from '@shared/storage/types'; import SunIcon from '@assets/svg/sun.svg'; import MoonIcon from '@assets/svg/moon.svg'; import { ToggleCheckbox } from '@popup/shared/Form/ToggleCheckbox'; @@ -34,6 +34,8 @@ function LightDarkModeToggle() { export default function Settings() { const { t } = useTranslation('settings'); + const [, setUiStyle] = useAtom(uiStyleAtom); + return (
@@ -63,7 +65,13 @@ export default function Settings() { {t('fullscreenWallet')} )} - + { + setUiStyle(UiStyle.WalletX); + }} + > Wallet X diff --git a/packages/browser-wallet/src/popup/popupX/constants/routes.ts b/packages/browser-wallet/src/popup/popupX/constants/routes.ts index bda42c69d..6f9efc832 100644 --- a/packages/browser-wallet/src/popup/popupX/constants/routes.ts +++ b/packages/browser-wallet/src/popup/popupX/constants/routes.ts @@ -180,6 +180,12 @@ export const relativeRoutes = { }, }, }, + prompt: { + path: 'prompt', + connectionRequest: { + path: 'connectionRequest', + }, + }, }; /** diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.scss b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.scss new file mode 100644 index 000000000..0665582e2 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.scss @@ -0,0 +1,18 @@ +.external-request-layout-x { + height: calc(100% - 2.4rem); + overflow: hidden; + max-width: rem(1328px); + margin: auto; + + &__main { + padding: rem(20px) rem(24px) 0 rem(24px); + margin: auto; + height: 100%; + max-width: rem(703px); + overflow: auto; + + &::-webkit-scrollbar { + display: none; + } + } +} diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx new file mode 100644 index 000000000..6e13c8cbb --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/ExternalRequestLayout.tsx @@ -0,0 +1,38 @@ +import React, { useMemo } from 'react'; +import { Outlet } from 'react-router-dom'; +import Toast from '@popup/shared/Toast/Toast'; +import clsx from 'clsx'; +import { Connection, Fullscreen } from '@popup/popupX/page-layouts/MainLayout/Header/components'; +import FullscreenPromptLayout from '@popup/popupX/page-layouts/FullscreenPromptLayout'; + +function Header({ isScrolling }: { isScrolling: boolean }) { + return ( +
+
+ + +
+
+ ); +} + +export default function ExternalRequestLayout() { + const [scroll, setScroll] = React.useState(0); + const isScrolling = useMemo(() => scroll > 0, [!!scroll]); + return ( + +
+
+
{ + setScroll(e.currentTarget.scrollTop); + }} + > + +
+ +
+ + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/index.ts b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/index.ts new file mode 100644 index 000000000..80b8f467e --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/ExternalRequestLayout/index.ts @@ -0,0 +1 @@ +export { default } from './ExternalRequestLayout'; diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx new file mode 100644 index 000000000..c1da8704b --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/FullscreenPromptLayout.tsx @@ -0,0 +1,74 @@ +import React, { createContext, useRef, useCallback, useMemo, useState, ReactNode } from 'react'; +import { useNavigate, To } from 'react-router-dom'; +import { noOp } from 'wallet-common-helpers'; + +import { isSpawnedWindow } from '@popup/shared/window-helpers'; + +type OnCloseHandler = () => void; +type Unsubscribe = () => void; + +type OnClose = (handler: OnCloseHandler) => Unsubscribe; +type WithClose = (action: (...args: A) => void) => (...args: A) => void; + +type FullscreenPromptContext = { + onClose: OnClose; + /** + * Generate an action, that also closes the prompt. + */ + withClose: WithClose; + /** + * + */ + setReturnLocation: (location: To) => void; +}; + +const defaultContext: FullscreenPromptContext = { + onClose: () => noOp, + withClose: () => noOp, + setReturnLocation: noOp, +}; + +export const fullscreenPromptContext = createContext(defaultContext); + +export default function FullscreenPromptLayout({ children }: { children: ReactNode }) { + const nav = useNavigate(); + const [returnLocation, setReturnLocation] = useState(); + + const closeHandler = useRef(); + const close = useCallback(() => { + closeHandler?.current?.(); + + if (isSpawnedWindow) { + window.close(); + } else if (returnLocation) { + nav(returnLocation); + } else { + // Go back + nav(-1); + } + }, [returnLocation]); + + const withClose: WithClose = useCallback( + (action) => + (...args) => { + action(...args); + close(); + }, + [close] + ); + + const onClose: OnClose = useCallback((handler) => { + closeHandler.current = handler; + + return () => { + closeHandler.current = undefined; + }; + }, []); + + const contextValue: FullscreenPromptContext = useMemo( + () => ({ onClose, withClose, setReturnLocation }), + [onClose, withClose] + ); + + return {children}; +} diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/index.ts b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/index.ts new file mode 100644 index 000000000..2b44a12db --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/FullscreenPromptLayout/index.ts @@ -0,0 +1 @@ +export { default, fullscreenPromptContext } from './FullscreenPromptLayout'; diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss index a4f60a14d..bff91f155 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/Header.scss @@ -13,10 +13,10 @@ .fade-menu-bg { position: absolute; top: 0; - margin: rem(80px) auto 0 auto; + margin: rem(90px) auto 0 auto; width: 100%; max-width: rem(1328px); - height: calc(100% - rem(80px)); + height: calc(100% - rem(90px)); z-index: 1; backdrop-filter: blur(10px); background: rgba($color-black, 0.9); @@ -107,17 +107,15 @@ visibility: hidden; } - &_button { - display: flex; - padding: unset; - border: unset; - background: unset; - cursor: pointer; + .button__icon.transparent { + &:hover:not(:disabled) { + opacity: 1; + } } } &__menu-tiles { - padding: rem(24px); + padding: rem(14px) rem(24px) rem(24px) rem(24px); .tablet & { margin-top: rem(72px); diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuButton.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuButton.tsx index d6e50f40d..4f7d563e4 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuButton.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuButton.tsx @@ -2,6 +2,7 @@ import React, { useEffect } from 'react'; import DotsNine from '@assets/svgX/dots-nine.svg'; import Close from '@assets/svgX/close.svg'; import clsx from 'clsx'; +import Button from '@popup/popupX/shared/Button'; type Props = { setMenuOpen: (open: boolean) => void; @@ -21,15 +22,13 @@ export default function MenuButton({ setMenuOpen, menuOpen, hideMenu }: Props) { }, [menuOpen]); return (
- + />
); } diff --git a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx index 14aee3bad..386503bd0 100644 --- a/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx +++ b/packages/browser-wallet/src/popup/popupX/page-layouts/MainLayout/Header/components/MenuTiles.tsx @@ -14,6 +14,9 @@ import Text from '@popup/popupX/shared/Text'; import { Link } from 'react-router-dom'; import { absoluteRoutes } from '@popup/popupX/constants/routes'; import { useTranslation } from 'react-i18next'; +import { useAtom } from 'jotai'; +import { uiStyleAtom } from '@popup/store/settings'; +import { UiStyle } from '@shared/storage/types'; type MenuTilesProps = { menuOpen: boolean; @@ -22,6 +25,7 @@ type MenuTilesProps = { export default function MenuTiles({ menuOpen, setMenuOpen }: MenuTilesProps) { const { t } = useTranslation('x', { keyPrefix: 'header.menu' }); + const [, setUiStyle] = useAtom(uiStyleAtom); if (!menuOpen) return null; return (
@@ -86,7 +90,12 @@ export default function MenuTiles({ menuOpen, setMenuOpen }: MenuTilesProps) { {t('restore')} - + { + setUiStyle(UiStyle.Old); + }} + > {t('oldUI')} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.scss b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.scss new file mode 100644 index 000000000..75860b362 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.scss @@ -0,0 +1,13 @@ +.connection-request-x { + .text__main { + color: $color-mineral-2; + } + + .white { + color: $color-white; + } + + .capture__main_small { + margin-top: rem(24px); + } +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.tsx b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.tsx new file mode 100644 index 000000000..9d4c78d68 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/ConnectionRequest.tsx @@ -0,0 +1,71 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { useAtom, useAtomValue } from 'jotai'; +import Page from '@popup/popupX/shared/Page'; +import Text from '@popup/popupX/shared/Text'; +import Button from '@popup/popupX/shared/Button'; +import { displayNameOrSplitAddress } from '@popup/shared/utils/account-helpers'; +import { selectedCredentialAtom, storedAllowlistAtom } from '@popup/store/account'; +import { fullscreenPromptContext } from '@popup/popupX/page-layouts/FullscreenPromptLayout'; +import { handleAllowlistEntryUpdate } from '@popup/pages/Allowlist/util'; +import { useUrlDisplay } from '@popup/popupX/shared/utils/helpers'; + +type Props = { + onAllow(): void; + onReject(): void; +}; + +export default function ConnectionRequest({ onAllow, onReject }: Props) { + const { t } = useTranslation('x', { keyPrefix: 'prompts.connectionRequestX' }); + const [urlDisplay, url] = useUrlDisplay(); + const { onClose, withClose } = useContext(fullscreenPromptContext); + const selectedAccount = useAtomValue(selectedCredentialAtom); + const [allowlistLoading, setAllowlist] = useAtom(storedAllowlistAtom); + const allowlist = allowlistLoading.value; + const [connectButtonDisabled, setConnectButtonDisabled] = useState(false); + + useEffect(() => onClose(onReject), [onClose, onReject]); + + if (!selectedAccount || allowlistLoading.loading) { + return null; + } + + async function connectAccount(account: string, urlString: string) { + await handleAllowlistEntryUpdate(urlString, allowlist, [account], setAllowlist); + } + + return ( + + + + + }} + values={{ dApp: urlDisplay, account: displayNameOrSplitAddress(selectedAccount) }} + /> + + + }} + values={{ dApp: urlDisplay }} + /> + + + + + { + setConnectButtonDisabled(true); + connectAccount(selectedAccount.address, new URL(url).origin).then(withClose(onAllow)); + }} + /> + + + ); +} diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/i18n/en.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/i18n/en.ts new file mode 100644 index 000000000..6b6922e85 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/i18n/en.ts @@ -0,0 +1,10 @@ +const t = { + newConnection: 'New connection', + connectTo: 'Connect {{ account }} to \n<1>{{dApp}} ?', + connectionDetails: + 'This will allow <1>{{dApp}} to see your account address, public balance, transaction history, and suggest transactions to sign.\n\nYou should only connect your account to websites and services you trust.', + connect: 'Connect', + cancel: 'Cancel', +}; + +export default t; diff --git a/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/index.ts b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/index.ts new file mode 100644 index 000000000..d344dd981 --- /dev/null +++ b/packages/browser-wallet/src/popup/popupX/pages/prompts/ConnectionRequest/index.ts @@ -0,0 +1 @@ +export { default } from './ConnectionRequest'; diff --git a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.scss b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.scss index d94d7a3a5..935484ac3 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.scss +++ b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.scss @@ -1,4 +1,11 @@ .button { + &__base { + &:focus-visible { + outline: 1px solid $color-white; + outline-offset: -1px; + } + } + &__main { display: flex; align-items: center; @@ -11,6 +18,11 @@ border-radius: rem(32px); background: $color-white; + &.secondary { + background: $color-grey-2; + color: $color-white; + } + .tablet & { max-width: rem(240px); margin: auto; diff --git a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx index e1a511ce3..9a2d16b13 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx +++ b/packages/browser-wallet/src/popup/popupX/shared/Button/Button.tsx @@ -22,12 +22,12 @@ export function ButtonBase({ }: PolymorphicProps) { const Component = as || 'button'; - return ; + return ; } function ButtonMain({ label, className, ...props }: { label: string } & ButtonProps) { return ( - + {label} ); diff --git a/packages/browser-wallet/src/popup/popupX/shared/Page/Page.scss b/packages/browser-wallet/src/popup/popupX/shared/Page/Page.scss index 52fccad8c..ece22a70e 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/Page/Page.scss +++ b/packages/browser-wallet/src/popup/popupX/shared/Page/Page.scss @@ -28,6 +28,7 @@ display: flex; flex-direction: column; margin-top: auto; + gap: rem(8px); } } } diff --git a/packages/browser-wallet/src/popup/popupX/shared/utils/helpers.ts b/packages/browser-wallet/src/popup/popupX/shared/utils/helpers.ts index c0984c585..b0ac61638 100644 --- a/packages/browser-wallet/src/popup/popupX/shared/utils/helpers.ts +++ b/packages/browser-wallet/src/popup/popupX/shared/utils/helpers.ts @@ -1,3 +1,6 @@ +import { useLocation } from 'react-router-dom'; +import { displayUrl } from '@popup/shared/utils/string-helpers'; + export async function copyToClipboard(text: string): Promise { try { await navigator.clipboard.writeText(text); @@ -5,3 +8,14 @@ export async function copyToClipboard(text: string): Promise { // TODO: logging. } } + +export function useUrlDisplay() { + const { state } = useLocation(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const url = (state as any)?.payload?.url; + if (!url) { + return ['', '']; + } + return [displayUrl(url), url]; +} diff --git a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx index df76d099f..63d724046 100644 --- a/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx +++ b/packages/browser-wallet/src/popup/popupX/shell/Routes.tsx @@ -27,10 +27,14 @@ import { BakerKeys } from '@popup/popupX/pages/EarningRewards/Baker/BakerKeys'; import DelegationType from '@popup/popupX/pages/EarningRewards/Delegator/Type/DelegationType'; import PrivateKey from '@popup/popupX/pages/PrivateKey'; import { RestoreIntro, RestoreResult } from '@popup/popupX/pages/Restore'; +import { MessagePromptHandlersType } from '@popup/shared/utils/message-prompt-handlers'; +import ConnectionRequest from '@popup/popupX/pages/prompts/ConnectionRequest'; +import ExternalRequestLayout from '@popup/popupX/page-layouts/ExternalRequestLayout'; import RegisterDelegator from '../pages/EarningRewards/Delegator/Register/RegisterDelegator'; import DelegationResult from '../pages/EarningRewards/Delegator/Result/DelegationResult'; -export default function Routes() { +export default function Routes({ messagePromptHandlers }: { messagePromptHandlers: MessagePromptHandlersType }) { + const { handleConnectionResponse } = messagePromptHandlers; return ( @@ -120,6 +124,17 @@ export default function Routes() { + } path={relativeRoutes.prompt.path}> + handleConnectionResponse(true)} + onReject={() => handleConnectionResponse(false)} + /> + } + /> + ); diff --git a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss index 7f014c025..3b5e22d07 100644 --- a/packages/browser-wallet/src/popup/popupX/styles/_elements.scss +++ b/packages/browser-wallet/src/popup/popupX/styles/_elements.scss @@ -25,7 +25,9 @@ @import '../pages/EarningRewards/Delegator/Type/DelegationType'; @import '../pages/EarningRewards/Delegator/Register/RegisterDelegator'; @import '../pages/EarningRewards/Delegator/Result/DelegationResult'; +@import '../pages/prompts/ConnectionRequest/ConnectionRequest'; @import '../page-layouts/MainLayout'; +@import '../page-layouts/ExternalRequestLayout/ExternalRequestLayout'; @import '../shared/Page/Page'; @import '../shared/Card/Card'; @import '../shared/Text/Text'; diff --git a/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts b/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts new file mode 100644 index 000000000..403d77af1 --- /dev/null +++ b/packages/browser-wallet/src/popup/shared/utils/message-prompt-handlers.ts @@ -0,0 +1,150 @@ +import { useEffect, useRef } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { noOp } from 'wallet-common-helpers'; +import { absoluteRoutes } from '@popup/constants/routes'; +import { absoluteRoutes as absoluteRoutesX } from '@popup/popupX/constants/routes'; +import { createMessageTypeFilter, InternalMessageType, MessageStatusWrapper, MessageType } from '@messaging'; +import { popupMessageHandler } from '@popup/shared/message-handler'; +import { AccountTransactionSignature, IdProofOutput } from '@concordium/web-sdk'; + +const html = document.getElementsByTagName('html').item(0); + +type PromptKey = + | keyof Omit + | keyof Omit; + +const getRouteAndReplace = (pathname: string, promptKey: PromptKey) => { + const isPopupX = html?.classList.contains('popup-x'); + + // ToDo update all routes of absoluteRoutesX.prompt[promptKey] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + if (isPopupX && absoluteRoutesX.prompt[promptKey]) { + const replace = pathname.startsWith(absoluteRoutesX.prompt.path); // replace existing prompts. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const route = absoluteRoutesX.prompt[promptKey]?.path; + return { route, replace }; + } + const replace = pathname.startsWith(absoluteRoutes.prompt.path); // replace existing prompts. + const route = absoluteRoutes.prompt[promptKey].path; + return { route, replace }; +}; + +function useMessagePrompt(type: InternalMessageType | MessageType, promptKey: PromptKey) { + const navigate = useNavigate(); + const { pathname } = useLocation(); + + const eventResponseRef = useRef<(response: R) => void>(); + const handleResponse = (response: R) => { + eventResponseRef.current?.(response); + }; + + useEffect( + () => + popupMessageHandler.handleMessage(createMessageTypeFilter(type), (msg, _sender, respond) => { + eventResponseRef.current = respond; + const { route, replace } = getRouteAndReplace(pathname, promptKey); + + navigate(route, { state: msg, replace }); + + return true; + }), + [pathname] + ); + + return handleResponse; +} + +/** + * Used for internal prompt, which does not return responses to the background script + */ +function usePrompt(type: InternalMessageType | MessageType, promptKey: PromptKey) { + const navigate = useNavigate(); + const { pathname } = useLocation(); + + useEffect( + () => + popupMessageHandler.handleMessage(createMessageTypeFilter(type), (msg) => { + const { route, replace } = getRouteAndReplace(pathname, promptKey); + navigate(route, { state: msg, replace }); + }), + [pathname] + ); +} + +export type MessagePromptHandlersType = { + handleConnectionResponse: (response: boolean) => void; + handleConnectAccountsResponse: (response: MessageStatusWrapper) => void; + handleAddWeb3IdCredentialResponse: (response: MessageStatusWrapper) => void; + handleSendTransactionResponse: (response: MessageStatusWrapper) => void; + handleSignMessageResponse: (response: MessageStatusWrapper) => void; + handleSignCIS3MessageResponse: (response: MessageStatusWrapper) => void; + handleAddTokensResponse: (response: MessageStatusWrapper) => void; + handleIdProofResponse: (response: MessageStatusWrapper) => void; + handleWeb3IdProofResponse: (response: MessageStatusWrapper) => void; + handleAgeProofResponse: (response: MessageStatusWrapper) => void; +}; + +export function useMessagePromptHandlers(): MessagePromptHandlersType { + const handleConnectionResponse = useMessagePrompt(InternalMessageType.Connect, 'connectionRequest'); + const handleConnectAccountsResponse = useMessagePrompt>( + InternalMessageType.ConnectAccounts, + 'connectAccountsRequest' + ); + const handleAddWeb3IdCredentialResponse = useMessagePrompt>( + InternalMessageType.AddWeb3IdCredential, + 'addWeb3IdCredential' + ); + const handleSendTransactionResponse = useMessagePrompt>( + InternalMessageType.SendTransaction, + 'sendTransaction' + ); + const handleSignMessageResponse = useMessagePrompt>( + InternalMessageType.SignMessage, + 'signMessage' + ); + const handleSignCIS3MessageResponse = useMessagePrompt>( + InternalMessageType.SignCIS3Message, + 'signCIS3Message' + ); + const handleAddTokensResponse = useMessagePrompt>( + InternalMessageType.AddTokens, + 'addTokens' + ); + const handleIdProofResponse = useMessagePrompt>( + InternalMessageType.IdProof, + 'idProof' + ); + + // We manually stringify the presentation + const handleWeb3IdProofResponse = useMessagePrompt>( + InternalMessageType.Web3IdProof, + 'web3IdProof' + ); + const handleAgeProofResponse = useMessagePrompt>( + InternalMessageType.AgeProof, + 'ageProof' + ); + + usePrompt(InternalMessageType.EndIdentityIssuance, 'endIdentityIssuance'); + usePrompt(InternalMessageType.RecoveryFinished, 'recovery'); + usePrompt(InternalMessageType.ImportWeb3IdBackup, 'importWeb3IdBackup'); + + useEffect(() => { + popupMessageHandler.sendInternalMessage(InternalMessageType.PopupReady).catch(noOp); + }, []); + + return { + handleConnectionResponse, + handleConnectAccountsResponse, + handleAddWeb3IdCredentialResponse, + handleSendTransactionResponse, + handleSignMessageResponse, + handleSignCIS3MessageResponse, + handleAddTokensResponse, + handleIdProofResponse, + handleWeb3IdProofResponse, + handleAgeProofResponse, + }; +} diff --git a/packages/browser-wallet/src/popup/shell/Root.tsx b/packages/browser-wallet/src/popup/shell/Root.tsx index 5297ac295..36e175402 100644 --- a/packages/browser-wallet/src/popup/shell/Root.tsx +++ b/packages/browser-wallet/src/popup/shell/Root.tsx @@ -1,4 +1,4 @@ -import { Provider, useAtomValue } from 'jotai'; +import { Provider, useAtom, useAtomValue } from 'jotai'; import React, { ReactElement, useEffect, useMemo } from 'react'; import { MemoryRouter, useLocation } from 'react-router-dom'; import { InternalMessageType } from '@messaging'; @@ -7,8 +7,8 @@ import { noOp } from 'wallet-common-helpers'; import { Dimensions, large, medium, small } from '@popup/constants/dimensions'; import { popupMessageHandler } from '@popup/shared/message-handler'; import { isFullscreenWindow, isSpawnedWeb3IdProofWindow, isSpawnedWindow } from '@popup/shared/window-helpers'; -import { networkConfigurationAtom, themeAtom } from '@popup/store/settings'; -import { Theme as ThemeType } from '@shared/storage/types'; +import { networkConfigurationAtom, themeAtom, uiStyleAtom } from '@popup/store/settings'; +import { Theme as ThemeType, UiStyle } from '@shared/storage/types'; import BlockChainParametersContext from '@popup/shared/BlockChainParametersProvider'; import AccountInfoListenerContext from '@popup/shared/AccountInfoListenerContext'; @@ -16,6 +16,7 @@ import './i18n'; import { mainnet } from '@shared/constants/networkConfiguration'; import { routePrefix } from '@popup/popupX/constants/routes'; +import { MessagePromptHandlersType, useMessagePromptHandlers } from '@popup/shared/utils/message-prompt-handlers'; import Routes from './Routes'; import RoutesX from '../popupX/shell/Routes'; @@ -120,17 +121,35 @@ function Theme({ children }: { children: ReactElement }) { return children; } +function MessagePromptHandlers({ + children, +}: { + children: (messagePromptHandlers: MessagePromptHandlersType) => ReactElement; +}) { + const messagePromptHandlers = useMessagePromptHandlers(); + return children(messagePromptHandlers); +} + export default function Root() { + const [uiStyle] = useAtom(uiStyleAtom); + if (uiStyle.loading) return null; + return ( - + - - + + {(messagePromptHandlers) => ( + <> + + + + )} + diff --git a/packages/browser-wallet/src/popup/shell/Routes.tsx b/packages/browser-wallet/src/popup/shell/Routes.tsx index 228df4ab6..d2293b84f 100644 --- a/packages/browser-wallet/src/popup/shell/Routes.tsx +++ b/packages/browser-wallet/src/popup/shell/Routes.tsx @@ -1,10 +1,7 @@ -import React, { useEffect, useRef } from 'react'; -import { Route, Routes as ReactRoutes, useLocation, useNavigate } from 'react-router-dom'; -import { InternalMessageType, MessageType, createMessageTypeFilter, MessageStatusWrapper } from '@messaging'; -import { AccountTransactionSignature, IdProofOutput } from '@concordium/web-sdk'; -import { noOp } from 'wallet-common-helpers'; +import React from 'react'; +import { Route, Routes as ReactRoutes } from 'react-router-dom'; -import { absoluteRoutes, relativeRoutes, relativePath } from '@popup/constants/routes'; +import { absoluteRoutes, relativePath, relativeRoutes } from '@popup/constants/routes'; import MainLayout from '@popup/page-layouts/MainLayout'; import FullscreenPromptLayout from '@popup/page-layouts/FullscreenPromptLayout'; import Account from '@popup/pages/Account'; @@ -14,7 +11,6 @@ import SignCIS3Message from '@popup/pages/SignCIS3Message'; import SendTransaction from '@popup/pages/SendTransaction'; import Setup from '@popup/pages/Setup'; import ConnectionRequest from '@popup/pages/ConnectionRequest'; -import { popupMessageHandler } from '@popup/shared/message-handler'; import Settings from '@popup/pages/Settings'; import NetworkSettings from '@popup/pages/NetworkSettings'; import AddAccount from '@popup/pages/AddAccount'; @@ -36,102 +32,21 @@ import AddWeb3IdCredential from '@popup/pages/AddWeb3IdCredential/AddWeb3IdCrede import VerifiableCredentialImport from '@popup/pages/VerifiableCredentialBackup/VerifiableCredentialImport'; import AgeProofRequest from '@popup/pages/AgeProofRequest'; import ViewSeedPhrase from '@popup/pages/ViewSeedPhrase'; - -type PromptKey = keyof Omit; - -function useMessagePrompt(type: InternalMessageType | MessageType, promptKey: PromptKey) { - const navigate = useNavigate(); - const { pathname } = useLocation(); - - const eventResponseRef = useRef<(response: R) => void>(); - const handleResponse = (response: R) => { - eventResponseRef.current?.(response); - }; - - useEffect( - () => - popupMessageHandler.handleMessage(createMessageTypeFilter(type), (msg, _sender, respond) => { - eventResponseRef.current = respond; - - const replace = pathname.startsWith(absoluteRoutes.prompt.path); // replace existing prompts. - const route = absoluteRoutes.prompt[promptKey].path; - - navigate(route, { state: msg, replace }); - return true; - }), - [pathname] - ); - - return handleResponse; -} - -/** - * Used for internal prompt, which does not return responses to the background script - */ -function usePrompt(type: InternalMessageType | MessageType, promptKey: PromptKey) { - const navigate = useNavigate(); - const { pathname } = useLocation(); - - useEffect( - () => - popupMessageHandler.handleMessage(createMessageTypeFilter(type), (msg) => { - const replace = pathname.startsWith(absoluteRoutes.prompt.path); // replace existing prompts. - const route = absoluteRoutes.prompt[promptKey].path; - - navigate(route, { state: msg, replace }); - }), - [pathname] - ); -} - -export default function Routes() { - const handleConnectionResponse = useMessagePrompt(InternalMessageType.Connect, 'connectionRequest'); - const handleConnectAccountsResponse = useMessagePrompt>( - InternalMessageType.ConnectAccounts, - 'connectAccountsRequest' - ); - const handleAddWeb3IdCredentialResponse = useMessagePrompt>( - InternalMessageType.AddWeb3IdCredential, - 'addWeb3IdCredential' - ); - const handleSendTransactionResponse = useMessagePrompt>( - InternalMessageType.SendTransaction, - 'sendTransaction' - ); - const handleSignMessageResponse = useMessagePrompt>( - InternalMessageType.SignMessage, - 'signMessage' - ); - const handleSignCIS3MessageResponse = useMessagePrompt>( - InternalMessageType.SignCIS3Message, - 'signCIS3Message' - ); - const handleAddTokensResponse = useMessagePrompt>( - InternalMessageType.AddTokens, - 'addTokens' - ); - const handleIdProofResponse = useMessagePrompt>( - InternalMessageType.IdProof, - 'idProof' - ); - - // We manually stringify the presentation - const handleWeb3IdProofResponse = useMessagePrompt>( - InternalMessageType.Web3IdProof, - 'web3IdProof' - ); - const handleAgeProofResponse = useMessagePrompt>( - InternalMessageType.AgeProof, - 'ageProof' - ); - - usePrompt(InternalMessageType.EndIdentityIssuance, 'endIdentityIssuance'); - usePrompt(InternalMessageType.RecoveryFinished, 'recovery'); - usePrompt(InternalMessageType.ImportWeb3IdBackup, 'importWeb3IdBackup'); - - useEffect(() => { - popupMessageHandler.sendInternalMessage(InternalMessageType.PopupReady).catch(noOp); - }, []); +import { MessagePromptHandlersType } from '@popup/shared/utils/message-prompt-handlers'; + +export default function Routes({ messagePromptHandlers }: { messagePromptHandlers: MessagePromptHandlersType }) { + const { + handleConnectionResponse, + handleConnectAccountsResponse, + handleAddWeb3IdCredentialResponse, + handleSendTransactionResponse, + handleSignMessageResponse, + handleSignCIS3MessageResponse, + handleAddTokensResponse, + handleIdProofResponse, + handleWeb3IdProofResponse, + handleAgeProofResponse, + } = messagePromptHandlers; return ( diff --git a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts index 87e9512c9..8b6dd2cef 100644 --- a/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts +++ b/packages/browser-wallet/src/popup/shell/i18n/locales/en.ts @@ -47,6 +47,7 @@ import aboutPage from '@popup/popupX/pages/About/i18n/en'; import header from '@popup/popupX/page-layouts/MainLayout/Header/i18n/en'; import web3Id from '@popup/popupX/pages/Web3Id/i18n/en'; import earn from '@popup/popupX/pages/EarningRewards/i18n/en'; +import connectionRequestX from '@popup/popupX/pages/prompts/ConnectionRequest/i18n/en'; const t = { shared, @@ -97,6 +98,7 @@ const t = { header, web3Id, earn, + prompts: { connectionRequestX }, }, }; diff --git a/packages/browser-wallet/src/popup/store/settings.ts b/packages/browser-wallet/src/popup/store/settings.ts index a7cf8c6e6..2b2fded7c 100644 --- a/packages/browser-wallet/src/popup/store/settings.ts +++ b/packages/browser-wallet/src/popup/store/settings.ts @@ -4,6 +4,7 @@ import { EncryptedData, NetworkConfiguration, Theme, + UiStyle, } from '@shared/storage/types'; import { atom } from 'jotai'; import { EventType } from '@concordium/browser-wallet-api-helpers'; @@ -21,6 +22,7 @@ export const encryptedSeedPhraseAtom = atomWithChromeStorage(ChromeStorageKey.Theme, Theme.Light); +export const uiStyleAtom = atomWithChromeStorage(ChromeStorageKey.UiStyle, UiStyle.Old, true); export const hasBeenOnBoardedAtom = atomWithChromeStorage(ChromeStorageKey.HasBeenOnboarded, false, true); const storedNetworkConfigurationAtom = atomWithChromeStorage( diff --git a/packages/browser-wallet/src/popup/store/utils.ts b/packages/browser-wallet/src/popup/store/utils.ts index 52ee9192f..1af23fded 100644 --- a/packages/browser-wallet/src/popup/store/utils.ts +++ b/packages/browser-wallet/src/popup/store/utils.ts @@ -34,6 +34,7 @@ import { sessionVerifiableCredentialMetadataUrls, storedLog, storedMigrations, + storedUiStyle, } from '@shared/storage/access'; import { ChromeStorageKey } from '@shared/storage/types'; import { atom, PrimitiveAtom, WritableAtom } from 'jotai'; @@ -48,6 +49,7 @@ const accessorMap: Record> = { [ChromeStorageKey.SeedPhrase]: storedEncryptedSeedPhrase, [ChromeStorageKey.NetworkConfiguration]: storedCurrentNetwork, [ChromeStorageKey.Theme]: storedTheme, + [ChromeStorageKey.UiStyle]: storedUiStyle, [ChromeStorageKey.IdentityProviders]: useIndexedStorage(storedIdentityProviders, getGenesisHash), [ChromeStorageKey.HasBeenOnboarded]: storedHasBeenOnboarded, [ChromeStorageKey.Passcode]: sessionPasscode, diff --git a/packages/browser-wallet/src/shared/storage/access.ts b/packages/browser-wallet/src/shared/storage/access.ts index 63fe15827..03e2aeb44 100644 --- a/packages/browser-wallet/src/shared/storage/access.ts +++ b/packages/browser-wallet/src/shared/storage/access.ts @@ -18,6 +18,7 @@ import { AcceptedTermsState, VerifiableCredential, VerifiableCredentialSchema, + UiStyle, } from './types'; export type StorageAccessor = { @@ -176,6 +177,7 @@ export const storedCredentials = makeIndexedStorageAccessor( export const storedSelectedAccount = makeStorageAccessor('local', ChromeStorageKey.SelectedAccount); export const storedEncryptedSeedPhrase = makeStorageAccessor('local', ChromeStorageKey.SeedPhrase); export const storedTheme = makeStorageAccessor('local', ChromeStorageKey.Theme); +export const storedUiStyle = makeStorageAccessor('local', ChromeStorageKey.UiStyle); export const storedIdentityProviders = makeIndexedStorageAccessor( 'local', ChromeStorageKey.IdentityProviders diff --git a/packages/browser-wallet/src/shared/storage/types.ts b/packages/browser-wallet/src/shared/storage/types.ts index 817ebaa10..045039df7 100644 --- a/packages/browser-wallet/src/shared/storage/types.ts +++ b/packages/browser-wallet/src/shared/storage/types.ts @@ -17,6 +17,7 @@ export enum ChromeStorageKey { SeedPhrase = 'seedPhrase', SelectedAccount = 'selectedAccount', Theme = 'theme', + UiStyle = 'uiStyle', PendingIdentity = 'pendingIdentity', Identities = 'identities', SelectedIdentity = 'selectedIdentity', @@ -49,6 +50,11 @@ export enum Theme { Dark = 'dark', } +export enum UiStyle { + Old = 'old', + WalletX = 'walletX', +} + /** * Used to describe the status of an identity or a credential */ From 7360ca81a25c6924ab64c0fa4732f8cc52bd1cc8 Mon Sep 17 00:00:00 2001 From: IvanMahda Date: Thu, 24 Oct 2024 03:26:14 +0300 Subject: [PATCH 2/2] Fix TS error --- packages/browser-wallet/src/popup/popupX/shell/Root.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/browser-wallet/src/popup/popupX/shell/Root.tsx b/packages/browser-wallet/src/popup/popupX/shell/Root.tsx index a516a9804..b22a1f080 100644 --- a/packages/browser-wallet/src/popup/popupX/shell/Root.tsx +++ b/packages/browser-wallet/src/popup/popupX/shell/Root.tsx @@ -4,11 +4,13 @@ import { MemoryRouter } from 'react-router-dom'; import { absoluteRoutes } from '@popup/popupX/constants/routes'; import BlockChainParametersContext from '@popup/shared/BlockChainParametersProvider'; import AccountInfoListenerContext from '@popup/shared/AccountInfoListenerContext'; +import { useMessagePromptHandlers } from '@popup/shared/utils/message-prompt-handlers'; import { Network, Theme, Scaling } from './Providers'; import Routes from './Routes'; export default function Root() { + const messagePromptHandlers = useMessagePromptHandlers(); return ( @@ -17,7 +19,7 @@ export default function Root() { - +