From 887389f13906e9ed198fcb0bef67fbb23543153a Mon Sep 17 00:00:00 2001 From: Ostap Piatkovskyi <44294945+ost-ptk@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:19:45 +0300 Subject: [PATCH] feature: add CSPR name promotion (#1058) * add csprName support, update the home page and header, remove unused code * added cspr name support to deploys list * fix e2e tests * fix onboarding e2e tests * add promotion banner for cspr name and cspr name item to the navigation menu --- src/apps/popup/pages/home/index.tsx | 4 + .../popup/pages/navigation-menu/index.tsx | 34 +- src/assets/icons/cspr-name.svg | 5 + src/assets/illustrations/cspr-name-banner.svg | 889 ++++++++++++++++++ src/background/index.ts | 2 + src/background/redux/get-main-store.ts | 21 +- src/background/redux/promotion/actions.ts | 5 + src/background/redux/promotion/reducer.ts | 19 + src/background/redux/promotion/selectors.ts | 4 + src/background/redux/promotion/types.ts | 3 + src/background/redux/redux-action.ts | 4 +- src/background/redux/root-reducer.ts | 4 +- src/background/redux/root-selector.ts | 1 + src/background/redux/types.d.ts | 2 + src/fixtures/initial-state-for-popup-tests.ts | 5 +- .../cspr-name-banner/cspr-name-banner.tsx | 65 ++ src/libs/ui/theme-config.ts | 3 +- 17 files changed, 1055 insertions(+), 15 deletions(-) create mode 100644 src/assets/icons/cspr-name.svg create mode 100644 src/assets/illustrations/cspr-name-banner.svg create mode 100644 src/background/redux/promotion/actions.ts create mode 100644 src/background/redux/promotion/reducer.ts create mode 100644 src/background/redux/promotion/selectors.ts create mode 100644 src/background/redux/promotion/types.ts create mode 100644 src/libs/ui/components/cspr-name-banner/cspr-name-banner.tsx diff --git a/src/apps/popup/pages/home/index.tsx b/src/apps/popup/pages/home/index.tsx index 86ebfff5b..dbd8fea49 100644 --- a/src/apps/popup/pages/home/index.tsx +++ b/src/apps/popup/pages/home/index.tsx @@ -9,6 +9,7 @@ import { RouterPath, useTypedLocation, useTypedNavigate } from '@popup/router'; import { selectActiveNetworkSetting, + selectShowCSPRNamePromotion, selectVaultActiveAccount } from '@background/redux/root-selector'; @@ -28,6 +29,7 @@ import { Tile, Typography } from '@libs/ui/components'; +import { CsprNameBanner } from '@libs/ui/components/cspr-name-banner/cspr-name-banner'; import { AccountBalance } from './components/account-balance'; import { DeploysList } from './components/deploys-list'; @@ -58,6 +60,7 @@ export function HomePageContent() { const network = useSelector(selectActiveNetworkSetting); const activeAccount = useSelector(selectVaultActiveAccount); + const showCSPRNamePromotion = useSelector(selectShowCSPRNamePromotion); useEffect(() => { if (!state?.activeTabId) { @@ -69,6 +72,7 @@ export function HomePageContent() { return ( + {showCSPRNamePromotion && } {activeAccount && ( diff --git a/src/apps/popup/pages/navigation-menu/index.tsx b/src/apps/popup/pages/navigation-menu/index.tsx index 7d981c3ad..ac068c39a 100644 --- a/src/apps/popup/pages/navigation-menu/index.tsx +++ b/src/apps/popup/pages/navigation-menu/index.tsx @@ -68,6 +68,12 @@ const LogoContainer = styled.div` margin: 16px 0; `; +const CsprNameContainer = styled.div` + padding: 1px 8px; + background-color: ${props => props.theme.color.contentPositive}; + border-radius: ${props => props.theme.borderRadius.twoHundred}px; +`; + interface MenuItem { id: number; title: string; @@ -80,6 +86,7 @@ interface MenuItem { hide?: boolean; toggleButton?: boolean; isModalWindow?: boolean; + isCsprName?: boolean; } interface MenuGroup { @@ -184,9 +191,20 @@ export function NavigationMenuPageContent() { } } ] - : []) + : []), + { + id: 6, + title: t('CSPR.name'), + description: t('Get names for your accounts'), + iconPath: 'assets/icons/cspr-name.svg', + // TODO: add url to CSPR.name + href: '', + currentValue: t('New'), + disabled: false, + isCsprName: true + } // { - // id: 6, + // id: 7, // title: t('Add watch account'), // iconPath: 'assets/icons/plus.svg', // disabled: false, @@ -194,7 +212,7 @@ export function NavigationMenuPageContent() { // closeNavigationMenu(); // navigate(RouterPath.AddWatchAccount); // } - // } + // }, ] }, { @@ -367,11 +385,17 @@ export function NavigationMenuPageContent() { ) : ( {groupItem.title} )} - {groupItem.currentValue != null && ( + {groupItem.currentValue != null && !groupItem.isCsprName ? ( {groupItem.currentValue} - )} + ) : groupItem.currentValue != null && groupItem.isCsprName ? ( + + + {groupItem.currentValue} + + + ) : null} ); diff --git a/src/assets/icons/cspr-name.svg b/src/assets/icons/cspr-name.svg new file mode 100644 index 000000000..d8ebc875c --- /dev/null +++ b/src/assets/icons/cspr-name.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/illustrations/cspr-name-banner.svg b/src/assets/illustrations/cspr-name-banner.svg new file mode 100644 index 000000000..219c41bb3 --- /dev/null +++ b/src/assets/illustrations/cspr-name-banner.svg @@ -0,0 +1,889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/background/index.ts b/src/background/index.ts index 467440d22..e924f940b 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -62,6 +62,7 @@ import { ledgerRecipientToSaveOnSuccessChanged, ledgerStateCleared } from '@background/redux/ledger/actions'; +import { setShowCSPRNamePromotion } from '@background/redux/promotion/actions'; import { askForReviewAfterChanged, ratedInStoreChanged @@ -632,6 +633,7 @@ runtime.onMessage.addListener( case getType(ledgerDeployChanged): case getType(ledgerRecipientToSaveOnSuccessChanged): case getType(addWatchingAccount): + case getType(setShowCSPRNamePromotion): store.dispatch(action); return sendResponse(undefined); diff --git a/src/background/redux/get-main-store.ts b/src/background/redux/get-main-store.ts index 0bed04d0f..f79f7c562 100644 --- a/src/background/redux/get-main-store.ts +++ b/src/background/redux/get-main-store.ts @@ -7,6 +7,7 @@ import { createStore } from '@background/redux/index'; import { KeysState } from '@background/redux/keys/types'; import { LoginRetryCountState } from '@background/redux/login-retry-count/reducer'; import { LoginRetryLockoutTimeState } from '@background/redux/login-retry-lockout-time/types'; +import { PromotionState } from '@background/redux/promotion/types'; import { RateAppState } from '@background/redux/rate-app/types'; import { RecentRecipientPublicKeysState } from '@background/redux/recent-recipient-public-keys/types'; import { startBackground } from '@background/redux/sagas/actions'; @@ -22,6 +23,7 @@ export const VAULT_SETTINGS = 'Nmxd8BZh93MHua'; export const RECENT_RECIPIENT_PUBLIC_KEYS = '7c2WyRuGhEtaDX'; export const CONTACTS_KEY = 'teuwe6zH3A72gc'; export const RATE_APP = 'p4cGYubbwnd9ke'; +export const PROMOTION = 'k4uL4myuACMoxB'; type StorageState = { [VAULT_CIPHER_KEY]: string; @@ -33,6 +35,7 @@ type StorageState = { [RECENT_RECIPIENT_PUBLIC_KEYS]: RecentRecipientPublicKeysState; [CONTACTS_KEY]: ContactsState; [RATE_APP]: RateAppState; + [PROMOTION]: PromotionState; }; // this needs to be private let storeSingleton: ReturnType; @@ -56,7 +59,8 @@ export const selectPopupState = (state: RootState): PopupState => { contacts: state.contacts, rateApp: state.rateApp, accountBalances: state.accountBalances, - ledger: state.ledger + ledger: state.ledger, + promotion: state.promotion }; }; @@ -75,7 +79,8 @@ export async function getExistingMainStoreSingletonOrInit() { [VAULT_SETTINGS]: settings, [RECENT_RECIPIENT_PUBLIC_KEYS]: recentRecipientPublicKeys, [CONTACTS_KEY]: contacts, - [RATE_APP]: rateApp + [RATE_APP]: rateApp, + [PROMOTION]: promotion } = (await storage.local.get([ VAULT_CIPHER_KEY, KEYS_KEY, @@ -85,7 +90,8 @@ export async function getExistingMainStoreSingletonOrInit() { VAULT_SETTINGS, RECENT_RECIPIENT_PUBLIC_KEYS, CONTACTS_KEY, - RATE_APP + RATE_APP, + PROMOTION ])) as StorageState; if (storeSingleton == null) { @@ -104,7 +110,8 @@ export async function getExistingMainStoreSingletonOrInit() { settings, recentRecipientPublicKeys, contacts, - rateApp + rateApp, + promotion }); } // send start action @@ -127,7 +134,8 @@ export async function getExistingMainStoreSingletonOrInit() { settings, recentRecipientPublicKeys, contacts, - rateApp + rateApp, + promotion } = state; storage.local .set({ @@ -139,7 +147,8 @@ export async function getExistingMainStoreSingletonOrInit() { [VAULT_SETTINGS]: settings, [RECENT_RECIPIENT_PUBLIC_KEYS]: recentRecipientPublicKeys, [CONTACTS_KEY]: contacts, - [RATE_APP]: rateApp + [RATE_APP]: rateApp, + [PROMOTION]: promotion }) .catch(e => { console.error('Persist encrypted vault failed: ', e); diff --git a/src/background/redux/promotion/actions.ts b/src/background/redux/promotion/actions.ts new file mode 100644 index 000000000..96afe133f --- /dev/null +++ b/src/background/redux/promotion/actions.ts @@ -0,0 +1,5 @@ +import { createAction } from 'typesafe-actions'; + +export const setShowCSPRNamePromotion = createAction( + 'SET_SHOW_CSPR_NAME_PROMOTION' +)(); diff --git a/src/background/redux/promotion/reducer.ts b/src/background/redux/promotion/reducer.ts new file mode 100644 index 000000000..58d0dc49e --- /dev/null +++ b/src/background/redux/promotion/reducer.ts @@ -0,0 +1,19 @@ +import { createReducer } from 'typesafe-actions'; + +import { setShowCSPRNamePromotion } from '@background/redux/promotion/actions'; +import { PromotionState } from '@background/redux/promotion/types'; + +const initialState: PromotionState = { + showCSPRNamePromotion: true +}; + +export const reducer = createReducer(initialState).handleAction( + setShowCSPRNamePromotion, + ( + state: PromotionState, + action: ReturnType + ) => ({ + ...state, + showCSPRNamePromotion: action.payload + }) +); diff --git a/src/background/redux/promotion/selectors.ts b/src/background/redux/promotion/selectors.ts new file mode 100644 index 000000000..e9945edec --- /dev/null +++ b/src/background/redux/promotion/selectors.ts @@ -0,0 +1,4 @@ +import { RootState } from 'typesafe-actions'; + +export const selectShowCSPRNamePromotion = (state: RootState) => + state.promotion.showCSPRNamePromotion; diff --git a/src/background/redux/promotion/types.ts b/src/background/redux/promotion/types.ts new file mode 100644 index 000000000..0bbd16abf --- /dev/null +++ b/src/background/redux/promotion/types.ts @@ -0,0 +1,3 @@ +export interface PromotionState { + showCSPRNamePromotion: boolean; +} diff --git a/src/background/redux/redux-action.ts b/src/background/redux/redux-action.ts index c9a854f30..7ddc20f6f 100644 --- a/src/background/redux/redux-action.ts +++ b/src/background/redux/redux-action.ts @@ -9,6 +9,7 @@ import * as lastActivityTime from './last-activity-time/actions'; import * as ledger from './ledger/actions'; import * as loginRetryCount from './login-retry-count/actions'; import * as loginRetryLockoutTime from './login-retry-lockout-time/actions'; +import * as promotion from './promotion/actions'; import * as rateApp from './rate-app/actions'; import * as recentRecipientPublicKeys from './recent-recipient-public-keys/actions'; import * as sagas from './sagas/actions'; @@ -35,7 +36,8 @@ const reduxAction = { contacts, rateApp, accountBalances, - ledger + ledger, + promotion }; export type ReduxAction = ActionType; diff --git a/src/background/redux/root-reducer.ts b/src/background/redux/root-reducer.ts index a0bfdce60..d3d6a3aa6 100644 --- a/src/background/redux/root-reducer.ts +++ b/src/background/redux/root-reducer.ts @@ -9,6 +9,7 @@ import { reducer as lastActivityTime } from './last-activity-time/reducer'; import { reducer as ledger } from './ledger/reducer'; import { reducer as loginRetryCount } from './login-retry-count/reducer'; import { reducer as loginRetryLockoutTime } from './login-retry-lockout-time/reducer'; +import { reducer as promotion } from './promotion/reducer'; import { reducer as rateApp } from './rate-app/reducer'; import { reducer as recentRecipientPublicKeys } from './recent-recipient-public-keys/reducer'; import { reducer as session } from './session/reducer'; @@ -33,7 +34,8 @@ const rootReducer = combineReducers({ contacts, rateApp, accountBalances, - ledger + ledger, + promotion }); export default rootReducer; diff --git a/src/background/redux/root-selector.ts b/src/background/redux/root-selector.ts index 6ba5c8ada..ddcd68aa4 100644 --- a/src/background/redux/root-selector.ts +++ b/src/background/redux/root-selector.ts @@ -11,3 +11,4 @@ export * from './settings/selectors'; export * from './recent-recipient-public-keys/selectors'; export * from './rate-app/selectors'; export * from './ledger/selectors'; +export * from './promotion/selectors'; diff --git a/src/background/redux/types.d.ts b/src/background/redux/types.d.ts index 70b4636d5..efc28c05e 100644 --- a/src/background/redux/types.d.ts +++ b/src/background/redux/types.d.ts @@ -9,6 +9,7 @@ import { LastActivityTimeState } from '@background/redux/last-activity-time/redu import { LedgerState } from '@background/redux/ledger/types'; import { LoginRetryCountState } from '@background/redux/login-retry-count/reducer'; import { LoginRetryLockoutTimeState } from '@background/redux/login-retry-lockout-time/types'; +import { PromotionState } from '@background/redux/promotion/types'; import { RateAppState } from '@background/redux/rate-app/types'; import { RecentRecipientPublicKeysState } from '@background/redux/recent-recipient-public-keys/types'; import { SessionState } from '@background/redux/session/types'; @@ -51,4 +52,5 @@ export type PopupState = { rateApp: RateAppState; accountBalances: AccountBalancesState; ledger: LedgerState; + promotion: PromotionState; }; diff --git a/src/fixtures/initial-state-for-popup-tests.ts b/src/fixtures/initial-state-for-popup-tests.ts index 6daab5309..35b1094ad 100644 --- a/src/fixtures/initial-state-for-popup-tests.ts +++ b/src/fixtures/initial-state-for-popup-tests.ts @@ -113,5 +113,8 @@ export const initialStateForPopupTests: RootState = { ratedInStore: false, askForReviewAfter: null }, - accountBalances: [] + accountBalances: [], + promotion: { + showCSPRNamePromotion: true + } }; diff --git a/src/libs/ui/components/cspr-name-banner/cspr-name-banner.tsx b/src/libs/ui/components/cspr-name-banner/cspr-name-banner.tsx new file mode 100644 index 000000000..3ec083153 --- /dev/null +++ b/src/libs/ui/components/cspr-name-banner/cspr-name-banner.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import styled from 'styled-components'; + +import { setShowCSPRNamePromotion } from '@background/redux/promotion/actions'; +import { dispatchToMainStore } from '@background/redux/utils'; + +import { AlignedFlexRow, SpacingSize } from '@libs/layout'; + +const Container = styled.div` + margin-top: 24px; + height: 108px; + background-image: url('../../../../assets/illustrations/cspr-name-banner.svg'); +`; + +const ButtonsContainer = styled(AlignedFlexRow)` + padding-top: 68px; + padding-left: 16px; +`; + +const WhiteButton = styled.a` + padding: 0 10px; + + background-color: ${props => props.theme.color.contentOnFill}; + border-radius: ${props => props.theme.borderRadius.hundred}px; + color: ${props => props.theme.color.contentAction}; + + font-size: 12px; + font-weight: 600; + line-height: 24px; + + text-decoration: none; +`; + +const DismissButton = styled.div` + font-size: 12px; + font-weight: 400; + line-height: 24px; + + color: ${props => props.theme.color.contentOnFill}; + + cursor: pointer; +`; + +export const CsprNameBanner = () => { + const { t } = useTranslation(); + + const dismissPromotion = () => { + dispatchToMainStore(setShowCSPRNamePromotion(false)); + }; + + return ( + + + {/* TODO: add url to CSPR.name */} + + Get it now + + + Dismiss + + + + ); +}; diff --git a/src/libs/ui/theme-config.ts b/src/libs/ui/theme-config.ts index c57844a17..23b175e02 100644 --- a/src/libs/ui/theme-config.ts +++ b/src/libs/ui/theme-config.ts @@ -32,7 +32,8 @@ export const themeConfig = { sixteen: 16, twenty: 20, eighty: 80, - hundred: 100 + hundred: 100, + twoHundred: 200 }, padding: { 1.2: '1.2rem',