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 000000000..785daa3b7 --- /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 000000000..44b6245f9 --- /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 000000000..ad4ab30ae --- /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 27944b117..7ea55266d 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 1d75c7d96..2a56a6ea9 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 ae119c75e..0bcc70bd8 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 000000000..81f3265a8 --- /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 000000000..4bfbfb866 --- /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 000000000..9441e9fd6 --- /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 000000000..3f7a0384c --- /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 000000000..f51b76559 --- /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 000000000..1aea3d501 --- /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 000000000..c967652d7 --- /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 000000000..0a10478ef --- /dev/null +++ b/packages/adena-extension/src/components/pages/web/wallet-creation-help-overlay/wallet-creation-help-overlay.tsx @@ -0,0 +1,113 @@ +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 { + hardwareWalletButtonRef?: React.RefObject; + airgapAccountButtonRef?: React.RefObject; + advancedOptionButtonRef?: React.RefObject; + onFinish: () => void; +} + +const WalletCreationHelpOverlay: React.FC = ({ + 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 (items.includes(null)) { + return []; + } + return items as OverlayItem[]; + }, [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; + }); + } + }, []); + + return ; +}; + +export default WalletCreationHelpOverlay; 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 ab755fee1..59d53249c 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) { 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 616afba6f..06028a130 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 0a46891d6..47494ee06 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 580f579aa..a52abafc6 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 5ea8676ac..aea26470d 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 } 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; @@ -26,6 +27,9 @@ const LandingScreen = (): ReactElement => { const { navigate } = useAppNavigate(); const { walletService } = useAdenaContext(); const theme = useTheme(); + const hardwareWalletButtonRef = useRef(null); + const airgapAccountButtonRef = useRef(null); + const advancedOptionButtonRef = useRef(null); const { data: existWallet, isLoading } = useQuery( ['existWallet', walletService], @@ -33,6 +37,18 @@ const LandingScreen = (): ReactElement => { {}, ); + const { data: visibleGuide, refetch: refetchVisibleGuide } = useQuery( + ['landingScreen/visibleGuide', existWallet], + async () => { + if (existWallet === undefined) { + return false; + } + const isSkip = await walletService.isSkipWalletGuide(existWallet); + return isSkip === false; + }, + {}, + ); + const animationMarginLeftSize = useMemo(() => { if (existWallet) { return -10; @@ -44,6 +60,13 @@ const LandingScreen = (): ReactElement => { navigate(RoutePath.WebSetupAirgap); }, []); + const confirmWalletGuide = useCallback(() => { + if (existWallet === undefined) { + return; + } + walletService.updateWalletGuideConfirmDate(existWallet).finally(refetchVisibleGuide); + }, [walletService, existWallet]); + if (isLoading) { return ; } @@ -53,12 +76,7 @@ const LandingScreen = (): ReactElement => { {existWallet ? ( - + {'Add Account'} @@ -89,6 +107,7 @@ const LandingScreen = (): ReactElement => { } text='Hardware Wallet' @@ -97,12 +116,14 @@ const LandingScreen = (): ReactElement => { }} /> } text='Airgap Account' onClick={moveSetupAirgapScreen} /> } text='Advanced Options' @@ -111,6 +132,15 @@ 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 2a6132c26..bb379b4fe 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 c7fbd5f1c..dc565ac92 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();