From c9636fb68d306bde75fe6aca357a8e36292039d0 Mon Sep 17 00:00:00 2001 From: jinoosss Date: Mon, 18 Mar 2024 14:33:56 +0900 Subject: [PATCH 1/3] feat: [ADN-503] Add informative tooltips --- .../src/assets/web/box-arrow.svg | 12 ++ .../src/assets/web/convenience-rate.svg | 8 + .../src/assets/web/security-rate.svg | 6 + .../common/storage/chrome-local-storage.ts | 6 +- .../src/components/atoms/web-button/index.tsx | 173 +++++++++++------- .../atoms/web-main-button/index.tsx | 14 +- .../web-help-overlay.stories.tsx | 56 ++++++ .../web-help-overlay.styles.ts | 49 +++++ .../web-help-overlay/web-help-overlay.tsx | 58 ++++++ .../web-help-tooltip.stories.tsx | 21 +++ .../web-help-tooltip.styles.ts | 57 ++++++ .../web-help-tooltip/web-help-tooltip.tsx | 67 +++++++ .../wallet-creation-help-overlay.styles.ts | 13 ++ .../wallet-creation-help-overlay.tsx | 119 ++++++++++++ .../migrations/v005/storage-migration-v005.ts | 2 + .../migrations/v005/storage-model-v005.ts | 6 + .../web/advanced-option-screen/index.tsx | 8 +- .../src/pages/web/landing-screen/index.tsx | 46 ++++- .../src/repositories/wallet/wallet.ts | 31 +++- .../src/services/wallet/wallet.ts | 15 ++ 20 files changed, 684 insertions(+), 83 deletions(-) create mode 100644 packages/adena-extension/src/assets/web/box-arrow.svg create mode 100644 packages/adena-extension/src/assets/web/convenience-rate.svg create mode 100644 packages/adena-extension/src/assets/web/security-rate.svg create mode 100644 packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.stories.tsx create mode 100644 packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.styles.ts create mode 100644 packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.tsx create mode 100644 packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.stories.tsx create mode 100644 packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.styles.ts create mode 100644 packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.tsx create mode 100644 packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.styles.ts create mode 100644 packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx diff --git a/packages/adena-extension/src/assets/web/box-arrow.svg b/packages/adena-extension/src/assets/web/box-arrow.svg new file mode 100644 index 00000000..785daa3b --- /dev/null +++ b/packages/adena-extension/src/assets/web/box-arrow.svg @@ -0,0 +1,12 @@ + + + + diff --git a/packages/adena-extension/src/assets/web/convenience-rate.svg b/packages/adena-extension/src/assets/web/convenience-rate.svg new file mode 100644 index 00000000..44b6245f --- /dev/null +++ b/packages/adena-extension/src/assets/web/convenience-rate.svg @@ -0,0 +1,8 @@ + + + diff --git a/packages/adena-extension/src/assets/web/security-rate.svg b/packages/adena-extension/src/assets/web/security-rate.svg new file mode 100644 index 00000000..ad4ab30a --- /dev/null +++ b/packages/adena-extension/src/assets/web/security-rate.svg @@ -0,0 +1,6 @@ + + + diff --git a/packages/adena-extension/src/common/storage/chrome-local-storage.ts b/packages/adena-extension/src/common/storage/chrome-local-storage.ts index 27944b11..7ea55266 100644 --- a/packages/adena-extension/src/common/storage/chrome-local-storage.ts +++ b/packages/adena-extension/src/common/storage/chrome-local-storage.ts @@ -13,7 +13,9 @@ type StorageKeyTypes = | 'ESTABLISH_SITES' | 'ADDRESS_BOOK' | 'ACCOUNT_TOKEN_METAINFOS' - | 'QUESTIONNAIRE_EXPIRED_DATE'; + | 'QUESTIONNAIRE_EXPIRED_DATE' + | 'WALLET_CREATION_GUIDE_CONFIRM_DATE' + | 'ADD_ACCOUNT_GUIDE_CONFIRM_DATE'; const StorageKeys: StorageKeyTypes[] = [ 'NETWORKS', @@ -27,6 +29,8 @@ const StorageKeys: StorageKeyTypes[] = [ 'ADDRESS_BOOK', 'ACCOUNT_TOKEN_METAINFOS', 'QUESTIONNAIRE_EXPIRED_DATE', + 'WALLET_CREATION_GUIDE_CONFIRM_DATE', + 'ADD_ACCOUNT_GUIDE_CONFIRM_DATE', ]; function isStorageKey(key: string): key is StorageKeyTypes { diff --git a/packages/adena-extension/src/components/atoms/web-button/index.tsx b/packages/adena-extension/src/components/atoms/web-button/index.tsx index 1d75c7d9..2a56a6ea 100644 --- a/packages/adena-extension/src/components/atoms/web-button/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-button/index.tsx @@ -9,19 +9,20 @@ import { WebText } from '../web-text'; import { Row, View } from '../base'; type WebButtonProps = { + buttonRef?: React.RefObject; size: 'full' | 'large' | 'small'; textType?: WebFontType; figure: 'primary' | 'secondary' | 'tertiary' | 'quaternary'; fixed?: boolean; } & ( - | { text: string; rightIcon?: 'chevronRight' } - | { + | { text: string; rightIcon?: 'chevronRight' } + | { children: ReactNode; } - ) & +) & ButtonHTMLAttributes; -const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small'; fixed?: boolean; }>` +const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small'; fixed?: boolean }>` cursor: pointer; border: none; border-radius: ${({ size }): string => (size === 'small' ? '12px' : '14px')}; @@ -32,7 +33,12 @@ const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small'; fixed height: 44px; justify-content: center; align-items: center; - ${({ fixed }): FlattenSimpleInterpolation | string => fixed ? css`flex-shrink: 0;` : ''}; + ${({ fixed }): FlattenSimpleInterpolation | string => + fixed + ? css` + flex-shrink: 0; + ` + : ''}; :disabled { cursor: default; @@ -42,39 +48,59 @@ const StyledButtonBase = styled.button<{ size: 'full' | 'large' | 'small'; fixed const StyledButtonPrimary = styled(StyledButtonBase)` color: ${getTheme('webNeutral', '_100')}; - background: linear-gradient(180deg, #0059FF 0%, #004BD6 100%); + background: linear-gradient(180deg, #0059ff 0%, #004bd6 100%); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.4) inset; - ${({ disabled }): FlattenSimpleInterpolation | string => !disabled ? css` - :hover { - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4) inset, 0px 0px 24px 0px #0059FF52, 0px 1px 3px 0px #0000001A, 0px 1px 2px 0px #0000000F; - } - :active { - box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.4) inset, 0px 0px 24px 0px #0059FF52, 0px 0px 0px 3px #0059FF29, 0px 1px 3px 0px #0000001A, 0px 1px 2px 0px #0000000F; - } - `: ''} + ${({ disabled }): FlattenSimpleInterpolation | string => + !disabled + ? css` + :hover { + box-shadow: + 0 0 0 2px rgba(255, 255, 255, 0.4) inset, + 0px 0px 24px 0px #0059ff52, + 0px 1px 3px 0px #0000001a, + 0px 1px 2px 0px #0000000f; + } + :active { + box-shadow: + 0 0 0 2px rgba(255, 255, 255, 0.4) inset, + 0px 0px 24px 0px #0059ff52, + 0px 0px 0px 3px #0059ff29, + 0px 1px 3px 0px #0000001a, + 0px 1px 2px 0px #0000000f; + } + ` + : ''} `; const StyledButtonSecondary = styled(StyledButtonBase)` - color: #ADCAFF; + color: #adcaff; background: rgba(0, 89, 255, 0.16); box-shadow: 0 0 0 1px rgba(122, 169, 255, 0.24) inset; - ${({ disabled }): FlattenSimpleInterpolation | string => !disabled ? css` - :hover { - color: #ADCAFF; - background: linear-gradient(180deg, rgba(0, 89, 255, 0.20) 0%, rgba(0, 89, 255, 0.00) 100%); - box-shadow: 0 0 0 2px rgba(122, 169, 255, 0.24) inset, 0px 1px 3px 0px #0000001A, 0px 1px 2px 0px #0000000F; - } - - :active { - color: #7AA9FF; - background: 0 0 0 2px rgba(122, 169, 255, 0.24) inset, linear-gradient(180deg, rgba(0, 89, 255, 0.20) 0%, rgba(0, 89, 255, 0.00) 100%); - } - `: ''} + ${({ disabled }): FlattenSimpleInterpolation | string => + !disabled + ? css` + :hover { + color: #adcaff; + background: linear-gradient(180deg, rgba(0, 89, 255, 0.2) 0%, rgba(0, 89, 255, 0) 100%); + box-shadow: + 0 0 0 2px rgba(122, 169, 255, 0.24) inset, + 0px 1px 3px 0px #0000001a, + 0px 1px 2px 0px #0000000f; + } + + :active { + color: #7aa9ff; + background: + 0 0 0 2px rgba(122, 169, 255, 0.24) inset, + linear-gradient(180deg, rgba(0, 89, 255, 0.2) 0%, rgba(0, 89, 255, 0) 100%); + } + ` + : ''} :disabled { - color: #7AA9FF; + color: #7aa9ff; opacity: 0.4; background: rgba(0, 89, 255, 0.16); } @@ -89,22 +115,33 @@ const StyledButtonTertiary = styled(StyledButtonBase)` fill: ${getTheme('webNeutral', '_300')}; } - ${({ disabled, theme }): FlattenSimpleInterpolation | string => !disabled ? css` - :hover { - background: rgba(188, 197, 214, 0.06); - box-shadow: 0 0 0 2px rgba(188, 197, 214, 0.24) inset, 0px 1px 3px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); - svg * { - fill: ${theme.webNeutral._100}; - } - } - :active { - color: ${theme.webNeutral._100}; - box-shadow: 0 0 0 1px rgba(188, 197, 214, 0.24) inset, 0px 0px 16px 0px rgba(255, 255, 255, 0.04), 0px 0px 0px 4px rgba(255, 255, 255, 0.04), 0px 1px 3px 0px rgba(0, 0, 0, 0.10), 0px 1px 2px 0px rgba(0, 0, 0, 0.06); - svg * { - fill: ${theme.webNeutral._100}; - } - } - `: ''} + ${({ disabled, theme }): FlattenSimpleInterpolation | string => + !disabled + ? css` + :hover { + background: rgba(188, 197, 214, 0.06); + box-shadow: + 0 0 0 2px rgba(188, 197, 214, 0.24) inset, + 0px 1px 3px 0px rgba(0, 0, 0, 0.1), + 0px 1px 2px 0px rgba(0, 0, 0, 0.06); + svg * { + fill: ${theme.webNeutral._100}; + } + } + :active { + color: ${theme.webNeutral._100}; + box-shadow: + 0 0 0 1px rgba(188, 197, 214, 0.24) inset, + 0px 0px 16px 0px rgba(255, 255, 255, 0.04), + 0px 0px 0px 4px rgba(255, 255, 255, 0.04), + 0px 1px 3px 0px rgba(0, 0, 0, 0.1), + 0px 1px 2px 0px rgba(0, 0, 0, 0.06); + svg * { + fill: ${theme.webNeutral._100}; + } + } + ` + : ''} `; const StyledButtonTertiarySmall = styled(StyledButtonTertiary)` @@ -118,23 +155,26 @@ const StyledButtonTertiarySmall = styled(StyledButtonTertiary)` fill: ${getTheme('webNeutral', '_300')}; } - ${({ disabled, theme }): FlattenSimpleInterpolation | string => !disabled ? css` - :hover { - color: ${theme.webNeutral._100}; - box-shadow: 0 0 0 1px rgba(188, 197, 214, 0.16) inset; - background: rgba(188, 197, 214, 0.08); - svg * { - fill: ${theme.webNeutral._100}; - } - } - :active { - color: ${theme.webNeutral._100}; - box-shadow: 0 0 0 1px rgba(188, 197, 214, 0.16) inset; - svg * { - fill: ${theme.webNeutral._100}; - } - } - `: ''} + ${({ disabled, theme }): FlattenSimpleInterpolation | string => + !disabled + ? css` + :hover { + color: ${theme.webNeutral._100}; + box-shadow: 0 0 0 1px rgba(188, 197, 214, 0.16) inset; + background: rgba(188, 197, 214, 0.08); + svg * { + fill: ${theme.webNeutral._100}; + } + } + :active { + color: ${theme.webNeutral._100}; + box-shadow: 0 0 0 1px rgba(188, 197, 214, 0.16) inset; + svg * { + fill: ${theme.webNeutral._100}; + } + } + ` + : ''} `; const StyledButtonQuaternary = styled(StyledButtonBase)` @@ -154,13 +194,14 @@ const StyledButtonQuaternary = styled(StyledButtonBase)` } `; -export const WebButton = ({ +export const WebButton: React.FC = ({ + buttonRef, figure, textType = 'title4', children, fixed, ...rest -}: WebButtonProps): ReactElement => { +}): ReactElement => { let StyledComponent; switch (figure) { case 'secondary': @@ -187,12 +228,10 @@ export const WebButton = ({ const isFixed = fixed || rest.size === 'small'; return ( - + {'text' in rest ? ( - {isRightButton && ( - - )} + {isRightButton && } {rest.text} diff --git a/packages/adena-extension/src/components/atoms/web-main-button/index.tsx b/packages/adena-extension/src/components/atoms/web-main-button/index.tsx index ae119c75..0bcc70bd 100644 --- a/packages/adena-extension/src/components/atoms/web-main-button/index.tsx +++ b/packages/adena-extension/src/components/atoms/web-main-button/index.tsx @@ -3,8 +3,8 @@ import styled from 'styled-components'; import { View } from '../base'; import { WebButton } from '../web-button'; import { WebText } from '../web-text'; - export interface WebMainButtonProps { + buttonRef?: React.RefObject; figure: 'primary' | 'secondary' | 'tertiary'; text: string; width?: CSSProperties['width']; @@ -32,6 +32,7 @@ const StyledImageWrapper = styled(View)` `; const WebMainButton: React.FC = ({ + buttonRef, iconElement, text, figure, @@ -41,18 +42,19 @@ const WebMainButton: React.FC = ({ }) => { return ( - - {iconElement} - - {text} + {iconElement} + + {text} + ); }; -export default WebMainButton; \ No newline at end of file +export default WebMainButton; diff --git a/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.stories.tsx b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.stories.tsx new file mode 100644 index 00000000..81f3265a --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.stories.tsx @@ -0,0 +1,56 @@ +import React from 'react'; +import WebHelpOverlay, { type WebHelpOverlayProps } from './web-help-overlay'; +import { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; + +export default { + title: 'components/molecules/WebHelpOverlay', + component: WebHelpOverlay, +} as Meta; + +export const Default: StoryObj = { + args: { + items: [ + { + x: 5, + y: 5, + tooltipInfo: { + securityRate: 2, + convenienceRate: 2, + content: ( + <> + 1. Tooltip + + ), + }, + }, + { + x: 15, + y: 5, + tooltipInfo: { + securityRate: 2, + convenienceRate: 2, + content: ( + <> + 2. Tooltip + + ), + }, + }, + { + x: 25, + y: 5, + tooltipInfo: { + securityRate: 2, + convenienceRate: 2, + content: ( + <> + 3. Tooltip + + ), + }, + }, + ], + onFinish: action('finish'), + }, +}; diff --git a/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.styles.ts b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.styles.ts new file mode 100644 index 00000000..4bfbfb86 --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.styles.ts @@ -0,0 +1,49 @@ +import styled, { keyframes } from 'styled-components'; + +export const WebHelpOverlayWrapper = styled.div` + position: fixed; + display: flex; + flex-direction: column; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + z-index: 99; + background: #00000080; +`; + +const overlayItemStartAnimation = keyframes` + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +`; + +const overlayItemEndAnimation = keyframes` + 0% { + opacity: 1; + } + 100% { + opacity: 0; + } +`; + +export const WebHelpOverlayItemWrapper = styled.div<{ x: number; y: number }>` + position: absolute; + display: flex; + flex-direction: column; + top: ${({ y }): string => `${y}px`}; + left: ${({ x }): string => `${x}px`}; + opacity: 0; + animation-duration: 0.4s; + animation-timing-function: ease-in-out; + animation-direction: alternate; + animation-fill-mode: both; + animation-name: ${overlayItemEndAnimation}; + + &.visible { + animation-name: ${overlayItemStartAnimation}; + } +`; diff --git a/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.tsx b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.tsx new file mode 100644 index 00000000..9441e9fd --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-overlay/web-help-overlay.tsx @@ -0,0 +1,58 @@ +import React, { useCallback, useState } from 'react'; +import WebHelpTooltip from '../web-help-tooltip/web-help-tooltip'; +import { WebHelpOverlayItemWrapper, WebHelpOverlayWrapper } from './web-help-overlay.styles'; + +export interface OverlayItem { + x: number; + y: number; + tooltipInfo: { + securityRate: number; + convenienceRate: number; + content: React.ReactNode; + }; +} + +export interface WebHelpOverlayProps { + items: OverlayItem[]; + onFinish: () => void; +} + +const WebHelpOverlay: React.FC = ({ items, onFinish }) => { + const [currentItemIndex, setCurrentItemIndex] = useState(0); + + const nextItem = useCallback(() => { + const hasNext = currentItemIndex + 1 < items.length; + if (!hasNext) { + onFinish(); + return; + } + setCurrentItemIndex((prev) => prev + 1); + }, [currentItemIndex, items]); + + return ( + + {items.map((item, index) => + index <= currentItemIndex ? ( + + + {item.tooltipInfo.content} + + + ) : ( + + ), + )} + + ); +}; + +export default WebHelpOverlay; diff --git a/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.stories.tsx b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.stories.tsx new file mode 100644 index 00000000..3f7a0384 --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.stories.tsx @@ -0,0 +1,21 @@ +import React, { PropsWithChildren } from 'react'; +import { Meta, StoryObj } from '@storybook/react'; + +import WebHelpTooltip, { type WebHelpTooltipProps } from './web-help-tooltip'; + +export default { + title: 'components/molecules/WebHelpTooltip', + component: WebHelpTooltip, +} as Meta; + +export const Default: StoryObj> = { + args: { + securityRate: 2, + convenienceRate: 2, + children: ( + <> + This allows you to connect with accounts from hardware wallets. + + ), + }, +}; diff --git a/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.styles.ts b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.styles.ts new file mode 100644 index 00000000..f51b7655 --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.styles.ts @@ -0,0 +1,57 @@ +import { Row, View } from '@components/atoms'; +import styled from 'styled-components'; + +export const WebHelpTooltipWrapper = styled(View)` + position: relative; + width: fit-content; + height: auto; + align-items: center; + margin-left: -50%; +`; + +export const WebHelpTooltipBoxArrowWrapper = styled(View)` + position: relative; + z-index: 5; +`; + +export const WebHelpTooltipBoxWrapper = styled(View)` + position: relative; + width: 100%; + height: auto; + padding: 20px; + margin-top: -1.5px; + flex-direction: column; + align-items: flex-start; + gap: 25px; + border-radius: 24px; + border: 1px solid #3f4348; + background: #191b1e; + backdrop-filter: blur(20px); + z-index: 4; + + .evaluation-wrapper { + display: flex; + flex-direction: row; + width: 100%; + justify-content: space-between; + align-items: center; + } +`; + +export const WebHelpTooltipEvaluation = styled(Row)` + gap: 3px; + + .value { + display: flex; + flex-direction: row; + + .icon-wrapper { + display: flex; + width: 14px; + height: 14px; + justify-content: center; + align-items: center; + padding: 2px 0; + } + } +`; diff --git a/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.tsx b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.tsx new file mode 100644 index 00000000..1aea3d50 --- /dev/null +++ b/packages/adena-extension/src/components/molecules/web-help-tooltip/web-help-tooltip.tsx @@ -0,0 +1,67 @@ +import { WebButton, WebImg, WebText } from '@components/atoms'; +import React, { PropsWithChildren } from 'react'; +import { + WebHelpTooltipBoxArrowWrapper, + WebHelpTooltipBoxWrapper, + WebHelpTooltipEvaluation, + WebHelpTooltipWrapper, +} from './web-help-tooltip.styles'; +import IconBoxArrow from '@assets/web/box-arrow.svg'; +import IconSecurityRate from '@assets/web/security-rate.svg'; +import IconConvenienceRate from '@assets/web/convenience-rate.svg'; +import _ from 'lodash'; + +export interface WebHelpTooltipProps { + securityRate: number; + convenienceRate: number; + confirm: () => void; +} + +const WebHelpTooltip: React.FC> = ({ + securityRate, + convenienceRate, + confirm, + children, +}) => { + return ( + + + + + + +
{children}
+
+ + + Security + +
+ {_.map(new Array(securityRate)).map((_, index) => ( +
+ +
+ ))} +
+
+ + + + Convenience + +
+ {_.map(new Array(convenienceRate)).map((_, index) => ( +
+ +
+ ))} +
+
+
+ +
+
+ ); +}; + +export default WebHelpTooltip; diff --git a/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.styles.ts b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.styles.ts new file mode 100644 index 00000000..c967652d --- /dev/null +++ b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.styles.ts @@ -0,0 +1,13 @@ +import { webFonts } from '@styles/theme'; +import styled from 'styled-components'; + +export const WalletCreationHelpOverlayItem = styled.span` + color: ${({ theme }): string => theme.webNeutral._100}; + word-break: keep-all; + white-space: nowrap; + ${webFonts.body4} + + b { + font-weight: 600; + } +`; diff --git a/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx new file mode 100644 index 00000000..f2c4dda2 --- /dev/null +++ b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx @@ -0,0 +1,119 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import WebHelpOverlay, { + OverlayItem, +} from '@components/molecules/web-help-overlay/web-help-overlay'; +import { WalletCreationHelpOverlayItem } from './wallet-creation-help-overlay.styles'; + +export interface WalletCreationHelpOverlayProps { + init: boolean; + hardwareWalletButtonRef?: React.RefObject; + airgapAccountButtonRef?: React.RefObject; + advancedOptionButtonRef?: React.RefObject; + onFinish: () => void; +} + +const WalletCreationHelpOverlay: React.FC = ({ + init, + hardwareWalletButtonRef, + airgapAccountButtonRef, + advancedOptionButtonRef, + onFinish, +}) => { + const [windowSize, setWindowSize] = useState<{ width: number; height: number }>({ + width: 0, + height: 0, + }); + const hardwareWalletHelpItem: OverlayItem | null = useMemo(() => { + if (!hardwareWalletButtonRef?.current) return null; + const { x, y, width, height } = hardwareWalletButtonRef.current.getBoundingClientRect(); + return { + x: x + width / 2, + y: y + height + 10, + tooltipInfo: { + securityRate: 2, + convenienceRate: 2, + content: ( + + This allows you to connect with accounts +
+ from hardware wallets. +
+ ), + }, + }; + }, [hardwareWalletButtonRef?.current, windowSize]); + + const airgapAccountHelpItem: OverlayItem | null = useMemo(() => { + if (!airgapAccountButtonRef?.current) return null; + const { x, y, width, height } = airgapAccountButtonRef.current.getBoundingClientRect(); + return { + x: x + width / 2, + y: y + height + 10, + tooltipInfo: { + securityRate: 3, + convenienceRate: 1, + content: ( + + This allows you to connect with accounts +
+ from an air-gapped environment. +
+ ), + }, + }; + }, [airgapAccountButtonRef?.current, windowSize]); + + const advancedOptionHelpItem: OverlayItem | null = useMemo(() => { + if (!advancedOptionButtonRef?.current) return null; + const { x, y, width, height } = advancedOptionButtonRef.current.getBoundingClientRect(); + return { + x: x + width / 2, + y: y + height + 10, + tooltipInfo: { + securityRate: 1, + convenienceRate: 3, + content: ( + + This allows you to create or restore accounts +
with Seed Phrases, Private Key or Google Login. +
+ ), + }, + }; + }, [advancedOptionButtonRef?.current, windowSize]); + + const helpItems = useMemo(() => { + const items = [hardwareWalletHelpItem, airgapAccountHelpItem, advancedOptionHelpItem]; + if (!init || items.includes(null)) { + return []; + } + return items as OverlayItem[]; + }, [init, hardwareWalletHelpItem, airgapAccountHelpItem, advancedOptionHelpItem]); + + useEffect(() => { + if (typeof window !== 'undefined') { + const handleResize = (): void => { + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight, + }); + }; + window.addEventListener('resize', handleResize); + handleResize(); + return () => window.removeEventListener('resize', handleResize); + } else { + return () => + window.removeEventListener('resize', () => { + return null; + }); + } + }, []); + + if (!init) { + return <>; + } + + return ; +}; + +export default WalletCreationHelpOverlay; diff --git a/packages/adena-extension/src/migrates/migrations/v005/storage-migration-v005.ts b/packages/adena-extension/src/migrates/migrations/v005/storage-migration-v005.ts index 616afba6..06028a13 100644 --- a/packages/adena-extension/src/migrates/migrations/v005/storage-migration-v005.ts +++ b/packages/adena-extension/src/migrates/migrations/v005/storage-migration-v005.ts @@ -19,6 +19,8 @@ export class StorageMigration005 implements Migration { ...previous, QUESTIONNAIRE_EXPIRED_DATE: null, ADDRESS_BOOK: this.migrateAddressBook(), + WALLET_CREATION_GUIDE_CONFIRM_DATE: null, + ADD_ACCOUNT_GUIDE_CONFIRM_DATE: null, }, }; } diff --git a/packages/adena-extension/src/migrates/migrations/v005/storage-model-v005.ts b/packages/adena-extension/src/migrates/migrations/v005/storage-model-v005.ts index 0a46891d..47494ee0 100644 --- a/packages/adena-extension/src/migrates/migrations/v005/storage-model-v005.ts +++ b/packages/adena-extension/src/migrates/migrations/v005/storage-model-v005.ts @@ -15,6 +15,8 @@ export type StorageModelDataV005 = { ADDRESS_BOOK: AddressBookModelV005; ACCOUNT_TOKEN_METAINFOS: AccountTokenMetainfoModelV005; QUESTIONNAIRE_EXPIRED_DATE: QuestionnaireExpiredDateModelV005; + WALLET_CREATION_GUIDE_CONFIRM_DATE: WalletCreationGuideConfirmDateModelV005; + ADD_ACCOUNT_GUIDE_CONFIRM_DATE: AddAccountGuideConfirmDateModelV005; }; export type NetworksModelV005 = { @@ -41,6 +43,10 @@ export type SerializedModelV005 = string; export type QuestionnaireExpiredDateModelV005 = number | null; +export type WalletCreationGuideConfirmDateModelV005 = number | null; + +export type AddAccountGuideConfirmDateModelV005 = number | null; + export type WalletModelV005 = { accounts: AccountDataModelV005[]; keyrings: KeyringDataModelV005[]; diff --git a/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx b/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx index 580f579a..a52abafc 100644 --- a/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/advanced-option-screen/index.tsx @@ -1,4 +1,4 @@ -import { ReactElement, useCallback } from 'react'; +import { ReactElement, useCallback, useRef } from 'react'; import styled, { useTheme } from 'styled-components'; import IconThunder from '@assets/web/round-thunder.svg'; @@ -28,6 +28,9 @@ const AdvancedOptionScreen = (): ReactElement => { const { navigate } = useAppNavigate(); const theme = useTheme(); const { wallet } = useWalletContext(); + const createWalletButtonRef = useRef(null); + const importWalletButtonRef = useRef(null); + const signInWithGoogleButtonRef = useRef(null); const onClickNewWallet = useCallback(() => { if (wallet && wallet.hasHDWallet()) { @@ -71,18 +74,21 @@ const AdvancedOptionScreen = (): ReactElement => { } text='Create New Wallet' onClick={onClickNewWallet} /> } text='Import Existing Wallet' onClick={onClickImportExistingWallet} /> } text='Sign In With Google' diff --git a/packages/adena-extension/src/pages/web/landing-screen/index.tsx b/packages/adena-extension/src/pages/web/landing-screen/index.tsx index 5ea8676a..b504855e 100644 --- a/packages/adena-extension/src/pages/web/landing-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/landing-screen/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useCallback, useMemo } from 'react'; +import React, { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; import styled, { useTheme } from 'styled-components'; import { useQuery } from '@tanstack/react-query'; @@ -14,6 +14,7 @@ import IconHardwareWallet from '@assets/icon-hardware-wallet'; import IconAirgap from '@assets/icon-airgap'; import IconThunder from '@assets/icon-thunder'; import Lottie from '@components/atoms/lottie'; +import WalletCreationHelpOverlay from '@components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay'; const StyledAnimationWrapper = styled.div` display: block; @@ -25,7 +26,11 @@ const StyledAnimationWrapper = styled.div` const LandingScreen = (): ReactElement => { const { navigate } = useAppNavigate(); const { walletService } = useAdenaContext(); + const [finishedGuide, setFinishedGuide] = useState(false); const theme = useTheme(); + const hardwareWalletButtonRef = useRef(null); + const airgapAccountButtonRef = useRef(null); + const advancedOptionButtonRef = useRef(null); const { data: existWallet, isLoading } = useQuery( ['existWallet', walletService], @@ -33,6 +38,18 @@ const LandingScreen = (): ReactElement => { {}, ); + const { data: visibleGuide } = useQuery( + ['landingScreen/visibleGuide', existWallet, finishedGuide], + async () => { + if (finishedGuide || existWallet === undefined) { + return false; + } + const isSkip = await walletService.isSkipWalletGuide(existWallet); + return isSkip === false; + }, + {}, + ); + const animationMarginLeftSize = useMemo(() => { if (existWallet) { return -10; @@ -44,6 +61,13 @@ const LandingScreen = (): ReactElement => { navigate(RoutePath.WebSetupAirgap); }, []); + const confirmWalletGuide = useCallback(() => { + if (existWallet === undefined) { + return; + } + walletService.updateWalletGuideConfirmDate(existWallet).finally(() => setFinishedGuide(true)); + }, [walletService, existWallet]); + if (isLoading) { return ; } @@ -53,12 +77,7 @@ const LandingScreen = (): ReactElement => { {existWallet ? ( - + {'Add Account'} @@ -89,6 +108,7 @@ const LandingScreen = (): ReactElement => { } text='Hardware Wallet' @@ -97,12 +117,14 @@ const LandingScreen = (): ReactElement => { }} /> } text='Airgap Account' onClick={moveSetupAirgapScreen} /> } text='Advanced Options' @@ -111,6 +133,16 @@ const LandingScreen = (): ReactElement => { }} /> + + {visibleGuide && ( + + )} ); }; diff --git a/packages/adena-extension/src/repositories/wallet/wallet.ts b/packages/adena-extension/src/repositories/wallet/wallet.ts index 2a6132c2..bb379b4f 100644 --- a/packages/adena-extension/src/repositories/wallet/wallet.ts +++ b/packages/adena-extension/src/repositories/wallet/wallet.ts @@ -6,7 +6,12 @@ import { encryptSha256Password, } from '@common/utils/crypto-utils'; -type LocalValueType = 'SERIALIZED' | 'ENCRYPTED_STORED_PASSWORD' | 'QUESTIONNAIRE_EXPIRED_DATE'; +type LocalValueType = + | 'SERIALIZED' + | 'ENCRYPTED_STORED_PASSWORD' + | 'QUESTIONNAIRE_EXPIRED_DATE' + | 'WALLET_CREATION_GUIDE_CONFIRM_DATE' + | 'ADD_ACCOUNT_GUIDE_CONFIRM_DATE'; type SessionValueType = 'ENCRYPTED_KEY' | 'ENCRYPTED_PASSWORD'; export class WalletRepository { @@ -105,4 +110,28 @@ export class WalletRepository { public updatedQuestionnaireExpiredDate = async (expiredDateTime: number): Promise => { await this.localStorage.set('QUESTIONNAIRE_EXPIRED_DATE', expiredDateTime); }; + + public getWalletCreationGuideConfirmDate = async (): Promise => { + const confirmDate = await this.localStorage.get('WALLET_CREATION_GUIDE_CONFIRM_DATE'); + if (!confirmDate) { + return null; + } + return Number(confirmDate); + }; + + public updateWalletCreationGuideConfirmDate = async (confirmDate: number): Promise => { + await this.localStorage.set('WALLET_CREATION_GUIDE_CONFIRM_DATE', confirmDate); + }; + + public getAddAccountGuideConfirmDate = async (): Promise => { + const confirmDate = await this.localStorage.get('ADD_ACCOUNT_GUIDE_CONFIRM_DATE'); + if (!confirmDate) { + return null; + } + return Number(confirmDate); + }; + + public updateAddAccountGuideConfirmDate = async (confirmDate: number): Promise => { + await this.localStorage.set('ADD_ACCOUNT_GUIDE_CONFIRM_DATE', confirmDate); + }; } diff --git a/packages/adena-extension/src/services/wallet/wallet.ts b/packages/adena-extension/src/services/wallet/wallet.ts index c7fbd5f1..dc565ac9 100644 --- a/packages/adena-extension/src/services/wallet/wallet.ts +++ b/packages/adena-extension/src/services/wallet/wallet.ts @@ -208,6 +208,21 @@ export class WalletService { await this.walletRepository.updatedQuestionnaireExpiredDate(expiredDateTime); }; + public isSkipWalletGuide = async (hasWallet: boolean): Promise => { + const confirmDate = hasWallet + ? await this.walletRepository.getAddAccountGuideConfirmDate() + : await this.walletRepository.getWalletCreationGuideConfirmDate(); + return !!confirmDate; + }; + + public updateWalletGuideConfirmDate = async (hasWallet: boolean): Promise => { + const confirmDate = dayjs().unix(); + const updateGuideConfirmDate = hasWallet + ? this.walletRepository.updateAddAccountGuideConfirmDate.bind(this.walletRepository) + : this.walletRepository.updateWalletCreationGuideConfirmDate.bind(this.walletRepository); + await updateGuideConfirmDate(confirmDate); + }; + public clear = async (): Promise => { await this.walletRepository.deleteSerializedWallet(); await this.walletRepository.deleteWalletPassword(); From b98625fae86d0453ab654d5a349b1dcb71d2be3f Mon Sep 17 00:00:00 2001 From: jinoosss Date: Mon, 18 Mar 2024 14:34:47 +0900 Subject: [PATCH 2/3] refactor: Remove console log --- .../src/hooks/web/wallet-export/use-wallet-export-screen.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts b/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts index ab755fee..59d53249 100644 --- a/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts +++ b/packages/adena-extension/src/hooks/web/wallet-export/use-wallet-export-screen.ts @@ -92,7 +92,6 @@ const useWalletExportScreen = (): UseWalletExportReturn => { const sessionStorage = AdenaStorage.session(); const exportType = await sessionStorage.get(WALLET_EXPORT_TYPE_STORAGE_KEY); const exportAccountId = await sessionStorage.get(WALLET_EXPORT_ACCOUNT_ID); - console.log('???', exportType); await sessionStorage.set(WALLET_EXPORT_TYPE_STORAGE_KEY, ''); await sessionStorage.set(WALLET_EXPORT_ACCOUNT_ID, ''); switch (exportType) { From 8902c2b0f83cfdd7b165fad19e3a92075e0d1677 Mon Sep 17 00:00:00 2001 From: "Lim SangHyun(Blue)" Date: Wed, 20 Mar 2024 10:19:58 +0900 Subject: [PATCH 3/3] feat. refactor code (#453) --- .../wallet-creation-help-overlay.tsx | 10 ++-------- .../src/pages/web/landing-screen/index.tsx | 12 +++++------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx index f2c4dda2..0a10478e 100644 --- a/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx +++ b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx @@ -5,7 +5,6 @@ import WebHelpOverlay, { import { WalletCreationHelpOverlayItem } from './wallet-creation-help-overlay.styles'; export interface WalletCreationHelpOverlayProps { - init: boolean; hardwareWalletButtonRef?: React.RefObject; airgapAccountButtonRef?: React.RefObject; advancedOptionButtonRef?: React.RefObject; @@ -13,7 +12,6 @@ export interface WalletCreationHelpOverlayProps { } const WalletCreationHelpOverlay: React.FC = ({ - init, hardwareWalletButtonRef, airgapAccountButtonRef, advancedOptionButtonRef, @@ -84,11 +82,11 @@ const WalletCreationHelpOverlay: React.FC = ({ const helpItems = useMemo(() => { const items = [hardwareWalletHelpItem, airgapAccountHelpItem, advancedOptionHelpItem]; - if (!init || items.includes(null)) { + if (items.includes(null)) { return []; } return items as OverlayItem[]; - }, [init, hardwareWalletHelpItem, airgapAccountHelpItem, advancedOptionHelpItem]); + }, [hardwareWalletHelpItem, airgapAccountHelpItem, advancedOptionHelpItem]); useEffect(() => { if (typeof window !== 'undefined') { @@ -109,10 +107,6 @@ const WalletCreationHelpOverlay: React.FC = ({ } }, []); - if (!init) { - return <>; - } - return ; }; diff --git a/packages/adena-extension/src/pages/web/landing-screen/index.tsx b/packages/adena-extension/src/pages/web/landing-screen/index.tsx index b504855e..aea26470 100644 --- a/packages/adena-extension/src/pages/web/landing-screen/index.tsx +++ b/packages/adena-extension/src/pages/web/landing-screen/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useCallback, useMemo, useRef, useState } from 'react'; +import React, { ReactElement, useCallback, useMemo, useRef } from 'react'; import styled, { useTheme } from 'styled-components'; import { useQuery } from '@tanstack/react-query'; @@ -26,7 +26,6 @@ const StyledAnimationWrapper = styled.div` const LandingScreen = (): ReactElement => { const { navigate } = useAppNavigate(); const { walletService } = useAdenaContext(); - const [finishedGuide, setFinishedGuide] = useState(false); const theme = useTheme(); const hardwareWalletButtonRef = useRef(null); const airgapAccountButtonRef = useRef(null); @@ -38,10 +37,10 @@ const LandingScreen = (): ReactElement => { {}, ); - const { data: visibleGuide } = useQuery( - ['landingScreen/visibleGuide', existWallet, finishedGuide], + const { data: visibleGuide, refetch: refetchVisibleGuide } = useQuery( + ['landingScreen/visibleGuide', existWallet], async () => { - if (finishedGuide || existWallet === undefined) { + if (existWallet === undefined) { return false; } const isSkip = await walletService.isSkipWalletGuide(existWallet); @@ -65,7 +64,7 @@ const LandingScreen = (): ReactElement => { if (existWallet === undefined) { return; } - walletService.updateWalletGuideConfirmDate(existWallet).finally(() => setFinishedGuide(true)); + walletService.updateWalletGuideConfirmDate(existWallet).finally(refetchVisibleGuide); }, [walletService, existWallet]); if (isLoading) { @@ -136,7 +135,6 @@ const LandingScreen = (): ReactElement => { {visibleGuide && (