diff --git a/assets/img/home/offers/amc-cover.png b/assets/img/home/offers/amc-cover.png new file mode 100644 index 0000000000..01d16b4985 Binary files /dev/null and b/assets/img/home/offers/amc-cover.png differ diff --git a/locales/de/translation.json b/locales/de/translation.json index 5755de89b9..ad6d226bcb 100644 --- a/locales/de/translation.json +++ b/locales/de/translation.json @@ -312,7 +312,7 @@ "My Wallets": "Meine Wallets", "Other Keys": "Andere Tasten", "Connect to Coinbase": "Mit Coinbase verbinden", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Verwalten Sie Ihre Coinbase-Konten, prüfen Sie Kontostände, Einzahlungen und Abhebungen zwischen Wallets.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Verwalten Sie Ihre Coinbase-Konten, prüfen Sie Kontostände, Einzahlungen und Abhebungen zwischen Wallets.", "Connect": "Verbinden", "Sign Up for Coinbase": "Anmeldung bei Coinbase", "It's a ghost town in here": "Hier herrscht geisterhafte Stille", diff --git a/locales/en/translation.json b/locales/en/translation.json index f7e82e07b1..dbd2de1083 100644 --- a/locales/en/translation.json +++ b/locales/en/translation.json @@ -331,7 +331,7 @@ "My Wallets": "My Wallets", "Other Keys": "Other Keys", "Connect to Coinbase": "Connect to Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.", "Connect": "Connect", "Sign Up for Coinbase": "Sign Up for Coinbase", "It's a ghost town in here": "It's a ghost town in here", diff --git a/locales/es/translation.json b/locales/es/translation.json index b5909119b3..b55116552d 100644 --- a/locales/es/translation.json +++ b/locales/es/translation.json @@ -312,7 +312,7 @@ "My Wallets": "Mis billeteras", "Other Keys": "Otras claves", "Connect to Coinbase": "Conectar a Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Administre sus cuentas de Coinbase, compruebe los balances, deposite y retire fondos entre billeteras.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Administre sus cuentas de Coinbase, compruebe los balances, deposite y retire fondos entre billeteras.", "Connect": "Conectar", "Sign Up for Coinbase": "Registrarse en Coinbase", "It's a ghost town in here": "Esto es un pueblo fantasma", diff --git a/locales/fr/translation.json b/locales/fr/translation.json index 690e46bec2..ffda14328f 100644 --- a/locales/fr/translation.json +++ b/locales/fr/translation.json @@ -312,7 +312,7 @@ "My Wallets": "Mes portefeuilles", "Other Keys": "Autres clés", "Connect to Coinbase": "Connectez-vous à Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Gérez vos comptes Coinbase, vérifiez les soldes, les dépôts et les retraits de fonds entre les portefeuilles.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Gérez vos comptes Coinbase, vérifiez les soldes, les dépôts et les retraits de fonds entre les portefeuilles.", "Connect": "Connecter", "Sign Up for Coinbase": "S'inscrire à Coinbase", "It's a ghost town in here": "C'est une ville fantôme ici", diff --git a/locales/ja/translation.json b/locales/ja/translation.json index a09e3af946..15a9be583e 100644 --- a/locales/ja/translation.json +++ b/locales/ja/translation.json @@ -312,7 +312,7 @@ "My Wallets": "マイウォレット", "Other Keys": "その他の鍵", "Connect to Coinbase": "Coinbaseに接続", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Coinbaseアカウントを管理し、残高を確認し、ウォレット間で資産を移動。", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Coinbaseアカウントを管理し、残高を確認し、ウォレット間で資産を移動。", "Connect": "接続する", "Sign Up for Coinbase": "Coinbaseに口座開設", "It's a ghost town in here": "ここはゴーストタウンです", diff --git a/locales/nl/translation.json b/locales/nl/translation.json index a24e272fc4..29a2e97522 100644 --- a/locales/nl/translation.json +++ b/locales/nl/translation.json @@ -312,7 +312,7 @@ "My Wallets": "Mijn wallets", "Other Keys": "Andere keys", "Connect to Coinbase": "Verbinden met Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Beheer uw Coinbase-accounts, controleer saldi, stortingen en neem geld op tussen wallets.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Beheer uw Coinbase-accounts, controleer saldi, stortingen en neem geld op tussen wallets.", "Connect": "Verbinden", "Sign Up for Coinbase": "Aanmelden voor Coinbase", "It's a ghost town in here": "Het is hier een spookstad", diff --git a/locales/pt/translation.json b/locales/pt/translation.json index e152eb5e13..22a86754b6 100644 --- a/locales/pt/translation.json +++ b/locales/pt/translation.json @@ -312,7 +312,7 @@ "My Wallets": "As minhas carteiras", "Other Keys": "Outras chaves", "Connect to Coinbase": "Conectar à Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Gira as suas contas da Coinbase, consulte saldos, depósitos e levante fundos entre carteiras.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Gira as suas contas da Coinbase, consulte saldos, depósitos e levante fundos entre carteiras.", "Connect": "Conectar", "Sign Up for Coinbase": "Inscreva-se na Coinbase", "It's a ghost town in here": "Este é um problema fantasma", diff --git a/locales/ru/translation.json b/locales/ru/translation.json index 79a8a87ff8..1583448738 100644 --- a/locales/ru/translation.json +++ b/locales/ru/translation.json @@ -312,7 +312,7 @@ "My Wallets": "Мои кошельки", "Other Keys": "Другие ключи", "Connect to Coinbase": "Подключиться к Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "Управляйте своими аккаунтами Coinbase, проверяйте и пополняйте баланс, а также переводите средства между кошельками.", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "Управляйте своими аккаунтами Coinbase, проверяйте и пополняйте баланс, а также переводите средства между кошельками.", "Connect": "Подключиться", "Sign Up for Coinbase": "Зарегистрироваться в Coinbase", "It's a ghost town in here": "Здесь пусто", diff --git a/locales/zh/translation.json b/locales/zh/translation.json index ff134efd62..e2fe7dc657 100644 --- a/locales/zh/translation.json +++ b/locales/zh/translation.json @@ -312,7 +312,7 @@ "My Wallets": "我的钱包", "Other Keys": "其他密钥", "Connect to Coinbase": "连接到 Coinbase", - "Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.": "管理您的 Coinbase 账户,查看余额、存款,在不同钱包之间提款。", + "Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.": "管理您的 Coinbase 账户,查看余额、存款,在不同钱包之间提款。", "Connect": "连接", "Sign Up for Coinbase": "注册 Coinbase", "It's a ghost town in here": "这里什么都没有", diff --git a/src/components/avatar/ProfileIcon.tsx b/src/components/avatar/ProfileIcon.tsx index 3ecd02818a..56b696d677 100644 --- a/src/components/avatar/ProfileIcon.tsx +++ b/src/components/avatar/ProfileIcon.tsx @@ -1,7 +1,7 @@ import React from 'react'; import * as Svg from 'react-native-svg'; import {useTheme} from 'styled-components/native'; -import {Midnight, ProgressBlue} from '../../styles/colors'; +import {Action, LightBlue, Midnight} from '../../styles/colors'; interface ProfileIconProps { color?: Svg.Color; @@ -14,8 +14,8 @@ const ProfileIcon: React.FC = props => { const theme = useTheme(); size = size || 35; - color = color || (theme.dark ? '#4989FF' : '#9FAFF5'); - background = background || (theme.dark ? Midnight : ProgressBlue); + color = color || (theme.dark ? '#4989FF' : Action); + background = background || (theme.dark ? Midnight : LightBlue); return ( diff --git a/src/components/back/HeaderBackButton.tsx b/src/components/back/HeaderBackButton.tsx index 94a19ac093..bde075d221 100644 --- a/src/components/back/HeaderBackButton.tsx +++ b/src/components/back/HeaderBackButton.tsx @@ -1,7 +1,23 @@ import React from 'react'; import {TouchableOpacity} from 'react-native-gesture-handler'; +import Svg, {Path} from 'react-native-svg'; import {useNavigation} from '@react-navigation/native'; -import Back from './Back'; +import styled, {useTheme} from 'styled-components/native'; +import {ActiveOpacity} from '../base/TouchableOpacity'; +import {LightBlack, NeutralSlate, Slate, SlateDark} from '../../styles/colors'; + +const Circle = styled.View` + width: 40px; + height: 40px; + border-radius: 20px; + align-items: center; + justify-content: center; + background-color: ${({theme}) => (theme.dark ? LightBlack : NeutralSlate)}; +`; + +const BackTouchable = styled(TouchableOpacity)` + padding-right: 10px; +`; interface HeaderBackButtonProps { onPress?: () => void; @@ -9,6 +25,7 @@ interface HeaderBackButtonProps { const HeaderBackButton: React.FC = ({onPress}) => { const navigation = useNavigation(); + const theme = useTheme(); const handlePress = () => { if (onPress) { @@ -18,10 +35,18 @@ const HeaderBackButton: React.FC = ({onPress}) => { } }; + const arrowFill = theme.dark ? Slate : SlateDark; return ( - - - + + + + + + + ); }; diff --git a/src/components/card/Card.tsx b/src/components/card/Card.tsx index 5547c96961..f9d8e0de12 100644 --- a/src/components/card/Card.tsx +++ b/src/components/card/Card.tsx @@ -16,8 +16,12 @@ const CardBody = styled.View` `; const CardFooter = styled.View` + flex-direction: row; + align-items: center; + justify-content: flex-end; min-height: 30px; padding: ${CardGutter}; + width: 100%; `; const BackgroundImage = styled.View` diff --git a/src/components/chain-search/ChainSearch.tsx b/src/components/chain-search/ChainSearch.tsx index 224209f418..341c38d4cf 100644 --- a/src/components/chain-search/ChainSearch.tsx +++ b/src/components/chain-search/ChainSearch.tsx @@ -6,7 +6,7 @@ import { SearchRoundInput, } from '../../components/styled/Containers'; import _ from 'lodash'; -import {Action, Slate, White} from '../../styles/colors'; +import {Action, LightBlue, Slate, White} from '../../styles/colors'; import {BaseText} from '../../components/styled/Text'; import SearchSvg from '../../../assets/img/search.svg'; import ChevronDownSvgLight from '../../../assets/img/chevron-down-lightmode.svg'; @@ -44,7 +44,7 @@ export const SearchFilterContainer = styled(TouchableOpacity)` height: 32px; margin: auto 8px auto 15px; border: 1px solid ${({theme: {dark}}) => (dark ? Action : 'transparent')}; - background: ${({theme: {dark}}) => (dark ? '#2240C440' : '#ECEFFD')}; + background: ${({theme: {dark}}) => (dark ? '#2240C440' : LightBlue)}; `; export const RowFilterContainer = styled.View` diff --git a/src/components/home-card/HomeCard.tsx b/src/components/home-card/HomeCard.tsx index 7e85a968e2..38c168031b 100644 --- a/src/components/home-card/HomeCard.tsx +++ b/src/components/home-card/HomeCard.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import {ReactElement, ReactNode} from 'react'; import styled, {useTheme} from 'styled-components/native'; -import {Action, LightBlack, Slate, SlateDark, White} from '../../styles/colors'; +import {LightBlack, Slate30, SlateDark, White} from '../../styles/colors'; import Haptic from '../haptic-feedback/haptic'; import { ActiveOpacity, @@ -13,23 +13,10 @@ import Card from '../card/Card'; import Percentage from '../percentage/Percentage'; import {View} from 'react-native'; import {BaseText, H3} from '../styled/Text'; -import * as Svg from 'react-native-svg'; import {shouldScale} from '../../utils/helper-methods'; import {useTranslation} from 'react-i18next'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; - -const Arrow = ({isDark}: {isDark: boolean}) => { - return ( - - - - ); -}; +import ArrowRightSvg from '../../navigation/tabs/home/components/ArrowRightSvg'; interface BodyProps { title?: string; @@ -42,20 +29,16 @@ interface BodyProps { } interface HomeCardProps { - header?: ReactNode; body: BodyProps; + footer?: ReactNode; onCTAPress?: () => void; backgroundImg?: () => ReactElement; } -const CardHeader = styled.View` - min-height: 20px; -`; - const CardBodyHeader = styled(BaseText)` - font-size: 14px; - line-height: 21px; - color: ${({theme: {dark}}) => (dark ? Slate : SlateDark)}; + font-size: 12px; + line-height: 15px; + color: ${({theme: {dark}}) => (dark ? Slate30 : SlateDark)}; margin-top: ${CardGutter}; `; @@ -88,16 +71,6 @@ const CardPillText = styled(BaseText)` color: ${SlateDark}; `; -const FooterArrow = styled(TouchableOpacity)` - width: 35px; - height: 35px; - align-self: flex-end; - border-radius: 50px; - background-color: ${({theme}) => (theme.dark ? '#0C204E' : '#ECEFFD')}; - align-items: center; - justify-content: center; -`; - const CardContainer = styled(TouchableOpacity)` left: ${ScreenGutter}; `; @@ -107,14 +80,17 @@ export const NeedBackupText = styled(BaseText)` text-align: center; color: ${({theme: {dark}}) => (dark ? White : SlateDark)}; padding: 2px 4px; - border: 1px solid ${({theme: {dark}}) => (dark ? White : '#E1E4E7')}; + border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; border-radius: 3px; position: absolute; + margin-top: 4px; `; -const HomeCard: React.FC = ({body, onCTAPress, header}) => { +export const HOME_CARD_HEIGHT = 143; +export const HOME_CARD_WIDTH = 170; + +const HomeCard: React.FC = ({body, footer, onCTAPress}) => { const {t} = useTranslation(); - const HeaderComp = {header}; const theme = useTheme(); const { title, @@ -137,10 +113,7 @@ const HomeCard: React.FC = ({body, onCTAPress, header}) => { <> {value && {value}} {percentageDifference ? ( - + ) : null} {pillText && ( @@ -162,22 +135,21 @@ const HomeCard: React.FC = ({body, onCTAPress, header}) => { } }; - const FooterComp = ( - - - - ); + const DefaultFooter = ; + + const FooterComp = footer ?? DefaultFooter; return ( diff --git a/src/components/icons/trend-arrow/DecrementArrow.tsx b/src/components/icons/trend-arrow/DecrementArrow.tsx new file mode 100644 index 0000000000..7b57ba691b --- /dev/null +++ b/src/components/icons/trend-arrow/DecrementArrow.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {useTheme} from 'styled-components/native'; +import Svg, {Rect, Path} from 'react-native-svg'; +import type {SvgProps} from 'react-native-svg'; +import {White} from '../../../styles/colors'; + +export interface TrendArrowProps extends SvgProps { + backgroundColor?: string; + arrowColor?: string; +} + +const DecrementArrow: React.FC = ({ + backgroundColor, + arrowColor, + width = 20, + height = 20, + ...rest +}) => { + const theme = useTheme(); + const defaultBackground = + backgroundColor ?? (theme.dark ? '#DA3636' : '#FF647C'); + const defaultArrowColor = arrowColor ?? White; + + return ( + + + + + ); +}; + +export default DecrementArrow; diff --git a/src/components/icons/trend-arrow/IncrementArrow.tsx b/src/components/icons/trend-arrow/IncrementArrow.tsx new file mode 100644 index 0000000000..48ca59f522 --- /dev/null +++ b/src/components/icons/trend-arrow/IncrementArrow.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import {useTheme} from 'styled-components/native'; +import Svg, {Rect, Path} from 'react-native-svg'; +import type {SvgProps} from 'react-native-svg'; +import {Success50, White} from '../../../styles/colors'; + +export interface TrendArrowProps extends SvgProps { + backgroundColor?: string; + arrowColor?: string; +} + +const IncrementArrow: React.FC = ({ + backgroundColor, + arrowColor, + width = 20, + height = 20, + ...rest +}) => { + const theme = useTheme(); + const defaultBackground = + backgroundColor ?? (theme.dark ? '#00954F' : '#0B5135'); + const defaultArrowColor = arrowColor ?? (theme.dark ? White : White); + + return ( + + + + + ); +}; + +export default IncrementArrow; diff --git a/src/components/list/AccountRow.tsx b/src/components/list/AccountRow.tsx index 68dc2ca41e..333465416e 100644 --- a/src/components/list/AccountRow.tsx +++ b/src/components/list/AccountRow.tsx @@ -6,7 +6,7 @@ import {formatCryptoAddress} from '../../utils/helper-methods'; import {SendToPillContainer} from '../../navigation/wallet/screens/send/confirm/Shared'; import {PillText} from '../../navigation/wallet/components/SendToPill'; import {View} from 'react-native'; -import {Action} from '../../styles/colors'; +import {Action, LightBlue} from '../../styles/colors'; import {css} from 'styled-components/native'; import {CurrencyImage} from '../currency-image/CurrencyImage'; import {CurrencyListIcons} from '../../constants/SupportedCurrencyOptions'; @@ -27,7 +27,7 @@ const RowContainer = styled(TouchableOpacity)<{selected: boolean}>` ${({selected}) => selected && css` - background: ${({theme: {dark}}) => (dark ? '#2240C440' : '#ECEFFD')}; + background: ${({theme: {dark}}) => (dark ? '#2240C440' : LightBlue)}; border-color: ${({theme: {dark}}) => (dark ? Action : Action)}; border-width: 1px; border-radius: 12px; diff --git a/src/components/list/KeyWalletsRow.tsx b/src/components/list/KeyWalletsRow.tsx index 51167fd62d..d7da5dbd5f 100644 --- a/src/components/list/KeyWalletsRow.tsx +++ b/src/components/list/KeyWalletsRow.tsx @@ -3,7 +3,13 @@ import styled from 'styled-components/native'; import {View} from 'react-native'; import {Badge, BaseText, H5} from '../styled/Text'; import KeySvg from '../../../assets/img/key.svg'; -import {LightBlack, Slate30, SlateDark, White} from '../../styles/colors'; +import { + LightBlack, + LightBlue, + Slate30, + SlateDark, + White, +} from '../../styles/colors'; import {Wallet} from '../../store/wallet/wallet.models'; import {WalletRowProps} from './WalletRow'; import WalletRow from './WalletRow'; @@ -41,7 +47,7 @@ interface KeyWalletsRowContainerProps { const KeyWalletsRowContainer = styled.View` margin-bottom: 0px; border-bottom-width: ${({isLast}) => (isLast ? 0 : 1)}px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; border-bottom-width: 0; gap: 24px; `; @@ -57,7 +63,7 @@ interface KeyNameContainerProps { const KeyNameContainer = styled.View` flex-direction: row; align-items: center; - border-bottom-color: ${({theme: {dark}}) => (dark ? SlateDark : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? SlateDark : LightBlue)}; border-bottom-width: ${({noBorder}) => (noBorder ? 0 : 1)}px; margin-top: 20px; ${({noBorder}) => (noBorder ? 'margin-left: 10px;' : '')} @@ -99,7 +105,7 @@ const AccountChainsContainer = styled(TouchableOpacity)` flex-direction: row; align-items: center; margin: 0px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; gap: 11px; `; @@ -117,13 +123,13 @@ interface AccountContainerProps { const AccountContainer = styled.View` gap: 12px; - border-bottom-color: ${({theme: {dark}}) => (dark ? '#333333' : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? '#333333' : LightBlue)}; border-bottom-width: ${({isLast}) => (isLast ? 0 : 1)}px; padding-bottom: 12px; `; const UtxoAccountContainer = styled.View` - border-bottom-color: ${({theme: {dark}}) => (dark ? '#333333' : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? '#333333' : LightBlue)}; border-bottom-width: ${({isLast}) => (isLast ? 0 : 1)}px; padding-bottom: ${({isLast}) => (isLast ? 0 : 12)}px; margin-top: ${({isSameChain}) => (isSameChain ? -24 : 0)}px; diff --git a/src/components/modal/chain-selector/ChainSelector.tsx b/src/components/modal/chain-selector/ChainSelector.tsx index 780f2d3e41..414f84b246 100644 --- a/src/components/modal/chain-selector/ChainSelector.tsx +++ b/src/components/modal/chain-selector/ChainSelector.tsx @@ -9,7 +9,14 @@ import {useDispatch, useSelector} from 'react-redux'; import {BaseText, H4, TextAlign} from '../../styled/Text'; import {AppActions} from '../../../store/app'; import {RootState} from '../../../store'; -import {Black, Action, SlateDark, White, Slate} from '../../../styles/colors'; +import { + Black, + Action, + SlateDark, + White, + Slate, + LightBlue, +} from '../../../styles/colors'; import haptic from '../../haptic-feedback/haptic'; import { @@ -88,7 +95,7 @@ const NetworkChainContainer = styled(TouchableOpacity)<{selected?: boolean}>` ${({selected}) => selected && css` - background: ${({theme: {dark}}) => (dark ? '#2240C440' : '#ECEFFD')}; + background: ${({theme: {dark}}) => (dark ? '#2240C440' : LightBlue)}; border-color: ${({theme: {dark}}) => (dark ? Action : Action)}; border-width: 1px; border-radius: 12px; diff --git a/src/components/modal/transact-menu/TransactMenu.tsx b/src/components/modal/transact-menu/TransactMenu.tsx index b189245ba5..ca4dded51d 100644 --- a/src/components/modal/transact-menu/TransactMenu.tsx +++ b/src/components/modal/transact-menu/TransactMenu.tsx @@ -11,6 +11,8 @@ import { White, Disabled, DisabledDark, + LinkBlue, + LightBlue, } from '../../../styles/colors'; import {ActiveOpacity, HEIGHT, SheetContainer} from '../../styled/Containers'; import {BaseText, H6} from '../../styled/Text'; @@ -30,7 +32,7 @@ const TransactButton = styled.View` `; const ModalContainer = styled(SheetContainer)` - background: ${({theme}) => (theme.dark ? '#101010' : White)}; + background: ${({theme}) => (theme.dark ? Midnight : LightBlue)}; flex: 1; `; @@ -41,13 +43,18 @@ const TransactItemContainer = styled(TouchableOpacity)` `; const ItemIconContainer = styled.View<{disabled: boolean}>` + width: 40px; + height: 40px; background-color: ${({theme}) => (theme.dark ? Midnight : Action)}; ${({disabled}) => disabled && css` background: ${({theme}) => (theme.dark ? DisabledDark : Disabled)}; `}; - border-radius: 11px; + border-radius: 23px; + overflow: hidden; + align-items: center; + justify-content: center; `; const ItemTextContainer = styled.View<{disabled: boolean}>` @@ -71,7 +78,7 @@ const ItemDescriptionText = styled(BaseText)` `; const ScanButtonContainer = styled(TouchableOpacity)` - background-color: ${({theme}) => (theme.dark ? Midnight : Action)}; + border: 2px solid ${({theme}) => (theme.dark ? LinkBlue : Action)}; flex-direction: row; align-self: center; align-items: center; @@ -80,11 +87,13 @@ const ScanButtonContainer = styled(TouchableOpacity)` height: 60px; padding-left: 11px; padding-right: 26px; - margin-bottom: 20px; + margin-bottom: 30px; + width: 100%; `; const ScanButtonText = styled(BaseText)` - color: ${White}; + color: ${({theme}) => (theme.dark ? White : Action)}; + font-size: 16px; `; const CloseButtonContainer = styled(TouchableOpacity)` @@ -163,7 +172,7 @@ const TransactModal = () => { { id: 'exchange', img: ({disabled}) => , - title: t('Exchange'), + title: t('Swap'), description: t('Swap crypto for another'), onPress: () => { dispatch( @@ -253,7 +262,7 @@ const TransactModal = () => { ); }; - const maxModalHeight = 650; + const maxModalHeight = 630; const modalHeight = Math.min(maxModalHeight, HEIGHT - 100); const modalHeightPercentage = modalHeight / HEIGHT; @@ -265,7 +274,7 @@ const TransactModal = () => { { - const theme = useTheme(); +const getIconStateColor = (color: string, disabled: boolean) => + disabled ? changeOpacity(color, 0.5) : color; +const BuyCrypto = () => { return ( - - - - - + + + ); }; const SellCrypto = ({disabled = false}) => { - const theme = useTheme(); - const disabledCircleColor = theme.dark ? DisabledDark : Disabled; - const disabledIconColor = theme.dark ? '#757575' : '#BDBDBD'; - return ( - ); }; const BuyGiftCard = ({disabled = false}) => { - const theme = useTheme(); - const fillColor = disabled - ? theme.dark - ? DisabledDark - : Disabled - : theme.dark - ? Midnight - : Action; - const iconColor = disabled - ? theme.dark - ? '#7A7A7A' - : '#BDBDBD' - : theme.dark - ? '#4989FF' - : White; - return ( - - - - - + + + + ); }; @@ -97,15 +66,20 @@ const BuyGiftCard = ({disabled = false}) => { const Close = () => { return ( - + { }; const Exchange = ({disabled = false}) => { - const theme = useTheme(); - const circleColor = theme.dark ? Midnight : Action; - const iconColor = theme.dark ? '#4989FF' : White; - - const disabledCircleColor = theme.dark ? DisabledDark : Disabled; - const disabledIconColor = theme.dark ? '#757575' : '#BDBDBD'; - return ( - + - ); }; const Receive = () => { - const theme = useTheme(); - return ( - + ); @@ -167,38 +128,30 @@ const Scan = () => { const theme = useTheme() as BitPayTheme; return ( - - + + + + ); }; const Send = ({disabled = false}) => { - const theme = useTheme(); - const circleColor = theme.dark ? Midnight : Action; - const iconColor = theme.dark ? '#4989FF' : White; - - const disabledCircleColor = theme.dark ? DisabledDark : Disabled; - const disabledIconColor = theme.dark ? '#757575' : '#BDBDBD'; - return ( - + ); diff --git a/src/components/percentage/Percentage.tsx b/src/components/percentage/Percentage.tsx index 5ce8223281..36a06b0175 100644 --- a/src/components/percentage/Percentage.tsx +++ b/src/components/percentage/Percentage.tsx @@ -1,13 +1,15 @@ -import styled from 'styled-components/native'; +import styled, {useTheme} from 'styled-components/native'; import * as React from 'react'; -import IncrementArrow from '../../../assets/img/home/exchange-rates/increment-arrow.svg'; -import DecrementArrow from '../../../assets/img/home/exchange-rates/decrement-arrow.svg'; +import IncrementArrow from '../icons/trend-arrow/IncrementArrow'; +import DecrementArrow from '../icons/trend-arrow/DecrementArrow'; import {BaseText} from '../styled/Text'; -import {Black, LuckySevens} from '../../styles/colors'; +import {Slate30, SlateDark} from '../../styles/colors'; -const PercentageContainer = styled(BaseText)<{darkModeColor: string}>` - font-size: 12px; - color: ${({theme: {dark}, darkModeColor}) => (dark ? darkModeColor : Black)}; +const PercentageContainer = styled(BaseText)<{ + color?: string; +}>` + font-size: 13px; + color: ${({color}) => color}; `; const PercentageRow = styled.View` @@ -15,27 +17,65 @@ const PercentageRow = styled.View` align-items: center; `; +const RangeLabel = styled(BaseText)` + font-size: 13px; + color: ${({theme}) => (theme.dark ? Slate30 : SlateDark)}; + font-weight: 400; + margin-left: 5px; +`; + export interface PercentageProps { percentageDifference: number; - darkModeColor?: string; + hideArrow?: boolean; + hideSign?: boolean; + priceChange?: string | number; + rangeLabel?: string; } const Percentage = ({ percentageDifference, - darkModeColor = LuckySevens, + hideArrow = false, + hideSign = false, + priceChange, + rangeLabel, }: PercentageProps) => { + const theme = useTheme(); + const isDarkMode = theme.dark; + const percentageColor = + percentageDifference >= 0 + ? isDarkMode + ? '#00954F' + : '#004D27' + : '#DA3636'; + const formattedPriceChange = + priceChange === null || priceChange === undefined + ? undefined + : String(priceChange); + const shouldShowPriceChange = Boolean(formattedPriceChange?.length); + const signPrefix = hideSign ? '' : percentageDifference < 0 ? '- ' : '+ '; + const percentageValue = `${signPrefix}${Math.abs(percentageDifference)}%`; + const wrappedPercentageValue = shouldShowPriceChange + ? `(${percentageValue})` + : percentageValue; + return ( <> - {percentageDifference > 0 ? ( + {!hideArrow && percentageDifference > 0 ? ( ) : null} - {percentageDifference < 0 ? ( + {!hideArrow && percentageDifference < 0 ? ( ) : null} - - {Math.abs(percentageDifference)}% + {shouldShowPriceChange ? ( + + {formattedPriceChange} + + ) : null} + + {wrappedPercentageValue} + {rangeLabel ? {rangeLabel} : null} ); diff --git a/src/components/settings/Settings.tsx b/src/components/settings/Settings.tsx index 9581e9a6da..7839285428 100644 --- a/src/components/settings/Settings.tsx +++ b/src/components/settings/Settings.tsx @@ -1,22 +1,24 @@ import React from 'react'; -import {Color, Rect, Svg, Ellipse, Circle} from 'react-native-svg'; +import {Rect, Svg, Path} from 'react-native-svg'; import styled, {useTheme} from 'styled-components/native'; -import {LightBlack, NeutralSlate, SlateDark, White} from '../../styles/colors'; +import {LightBlack, NeutralSlate, Slate, SlateDark} from '../../styles/colors'; import {ActiveOpacity, HeaderRightContainer} from '../styled/Containers'; import {TouchableOpacity} from 'react-native-gesture-handler'; interface SettingsSvgProps { - color: Color | undefined; - background: Color | undefined; + color: string; + background: string; } const SettingsSvg: React.FC = ({color, background}) => { return ( - - - + ); }; @@ -25,7 +27,7 @@ const SettingsSvgContainer = styled(TouchableOpacity)``; const Settings = ({onPress}: {onPress: () => void}) => { const theme = useTheme(); - const color = theme.dark ? White : SlateDark; + const color = theme.dark ? Slate : SlateDark; const background = theme.dark ? LightBlack : NeutralSlate; return ( diff --git a/src/components/styled/Containers.tsx b/src/components/styled/Containers.tsx index b0c21689af..39294b4b34 100644 --- a/src/components/styled/Containers.tsx +++ b/src/components/styled/Containers.tsx @@ -5,6 +5,7 @@ import styled, {css} from 'styled-components/native'; import { Feather, LightBlack, + LightBlue, NeutralSlate, SlateDark, White, @@ -23,7 +24,7 @@ export const isNotMobile = HEIGHT / WIDTH < 1.6; export const isNarrowHeight = HEIGHT < 700; export const CTA_RESERVED = 104; -export const ScreenGutter = '15px'; +export const ScreenGutter = '12px'; // Nav export const HeaderRightContainer = styled.View` height: 40px; @@ -131,7 +132,7 @@ export const RowContainer = styled(TouchableOpacity)` align-items: center; padding: 10px 4px; margin: 0 6px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; border-bottom-width: ${({isLast, noBorder}) => isLast || noBorder ? 0 : 1}px; opacity: ${({isDisabled}) => (isDisabled ? 0.5 : 1)}; @@ -150,7 +151,7 @@ export const RowContainerWithoutFeedback = styled.View` align-items: center; padding: 10px 4px; margin: 0 10px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; border-bottom-width: ${({isLast}) => (isLast ? 0 : 1)}px; `; @@ -165,6 +166,7 @@ export const CurrencyImageContainer = styled.View` justify-content: center; align-self: center; border-radius: 8px; + margin-right: 3px; `; // Card @@ -172,7 +174,7 @@ export const CardGutter = '15px'; export const CardContainer = styled.View` background: ${({theme}) => (theme.dark ? LightBlack : White)}; - border-radius: 21px; + border-radius: 12px; overflow: hidden; `; @@ -377,7 +379,7 @@ export const SearchInput = styled.TextInput` flex: 1; padding: 0 10px; border-right-width: 1px; - border-right-color: ${({theme: {dark}}) => (dark ? '#45484E' : '#ECEFFD')}; + border-right-color: ${({theme: {dark}}) => (dark ? '#45484E' : LightBlue)}; height: 32px; color: ${({theme}) => theme.colors.text}; background-color: transparent; @@ -422,7 +424,7 @@ export const CopyToClipboardContainer = styled(TouchableOpacity)` `; export const CopyImgContainer = styled.View` - border-right-color: ${({theme: {dark}}) => (dark ? '#46494E' : '#ECEFFD')}; + border-right-color: ${({theme: {dark}}) => (dark ? '#46494E' : LightBlue)}; border-right-width: 1px; padding-right: 10px; height: 25px; diff --git a/src/navigation/coinbase/components/CoinbaseBalanceCard.tsx b/src/navigation/coinbase/components/CoinbaseBalanceCard.tsx index 993583b48e..d52686be64 100644 --- a/src/navigation/coinbase/components/CoinbaseBalanceCard.tsx +++ b/src/navigation/coinbase/components/CoinbaseBalanceCard.tsx @@ -6,71 +6,53 @@ import { } from '../../../utils/helper-methods'; import {useNavigation} from '@react-navigation/native'; import CoinbaseSvg from '../../../../assets/img/logos/coinbase.svg'; -import styled, {useTheme} from 'styled-components/native'; +import styled from 'styled-components/native'; import {COINBASE_ENV} from '../../../api/coinbase/coinbase.constants'; import {useAppSelector} from '../../../utils/hooks'; import {HomeCarouselLayoutType} from '../../../store/app/app.models'; -import {BoxShadow} from '../../tabs/home/components/Styled'; -import {H3} from '../../../components/styled/Text'; -import {LightBlack, White} from '../../../styles/colors'; import { ActiveOpacity, Column, Row, - ScreenGutter, } from '../../../components/styled/Containers'; import { BalanceCode, BalanceCodeContainer, + BalanceContainer, + ListCard, } from '../../tabs/home/components/Wallet'; -import {Balance, OptionName} from '../../wallet/components/DropdownOption'; -import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import {Balance} from '../../wallet/components/DropdownOption'; +import ArrowRightSvg from '../../tabs/home/components/ArrowRightSvg'; +import {BaseText} from '../../../components/styled/Text'; +import {Slate30, SlateDark} from '../../../styles/colors'; interface CoinbaseCardComponentProps { layout: HomeCarouselLayoutType; } -const ListCard = styled(TouchableOpacity)` - background-color: ${({theme: {dark}}) => (dark ? LightBlack : White)}; - border-radius: 12px; - margin: 10px ${ScreenGutter}; - flex-direction: row; - align-items: center; - justify-content: space-between; - padding: 15px; - height: 75px; -`; - const HeaderImg = styled.View` width: 22px; height: 22px; align-items: center; justify-content: center; + margin-right: 0px; `; -const HeaderImgList = styled.View` - width: 15px; - height: 22px; +const FooterContainer = styled(Row)` align-items: center; - justify-content: center; + justify-content: space-between; + width: 100%; `; -const HeaderComponent = ( - - - -); - -const HeaderComponentList = ( - - - -); +const CoinbaseLabel = styled(BaseText)` + color: ${({theme: {dark}}) => (dark ? Slate30 : SlateDark)}; + font-size: 12px; + font-weight: 400; +`; const CoinbaseBalanceCard: React.FC = ({ layout, }) => { - const theme = useTheme(); const navigation = useNavigation(); const onCTAPress = () => { navigation.navigate('CoinbaseRoot'); @@ -87,41 +69,79 @@ const CoinbaseBalanceCard: React.FC = ({ const body = { title: 'Coinbase', value: formatFiatAmount(balance, defaultAltCurrency.isoCode), - hideKeyBalance: false, // TODO: adds this function to Coinbase Settings + hideKeyBalance: hideAllBalances, }; + const ListRow = styled(Row)` + align-items: center; + justify-content: space-between; + width: 100%; + `; + + const ShrinkColumn = styled(Column)` + flex-shrink: 1; + `; + + const ListHeaderRow = styled(Row)` + align-items: center; + justify-content: flex-end; + `; + + const HeaderColumn = styled(Column)` + justify-content: center; + align-items: flex-end; + margin-right: 12px; + `; + + const FooterHeaderImg = styled(HeaderImg)` + margin-right: 12px; + `; + if (layout === 'listView') { return ( - - - - {HeaderComponentList} - Coinbase - - + + + + Coinbase {!hideAllBalances ? ( - - {amount} - {code ? ( - - {code} - - ) : null} - + + + {amount} + {code ? ( + + {code} + + ) : null} + + ) : ( -

****

+ )} -
-
+ + + + + + + + + +
); } + const FooterComponent = ( + + + + + + + ); + return ( - + ); }; diff --git a/src/navigation/coinbase/components/CoinbaseIntro.tsx b/src/navigation/coinbase/components/CoinbaseIntro.tsx index bdc5929ffe..0fe047edbf 100644 --- a/src/navigation/coinbase/components/CoinbaseIntro.tsx +++ b/src/navigation/coinbase/components/CoinbaseIntro.tsx @@ -88,7 +88,7 @@ const CoinbaseIntro = () => { {t('Connect to Coinbase')} {t( - 'Manage your Coinbase accounts, check balances, deposits and withdraw funds between wallets.', + 'Manage your Coinbase accounts, check balances, deposit and withdraw funds between wallets.', )} diff --git a/src/navigation/coinbase/components/CoinbaseSettingsOption.tsx b/src/navigation/coinbase/components/CoinbaseSettingsOption.tsx index f5b873692b..6cd71cd4f2 100644 --- a/src/navigation/coinbase/components/CoinbaseSettingsOption.tsx +++ b/src/navigation/coinbase/components/CoinbaseSettingsOption.tsx @@ -1,6 +1,11 @@ import React from 'react'; import styled from 'styled-components/native'; -import {LightBlack, White, Cloud} from '../../../styles/colors'; +import { + LightBlack, + NeutralSlate, + Slate, + SlateDark, +} from '../../../styles/colors'; import {ActiveOpacity} from '../../../components/styled/Containers'; import {Theme} from '@react-navigation/native'; import Svg, {Path} from 'react-native-svg'; @@ -9,12 +14,16 @@ import {TouchableOpacity} from 'react-native-gesture-handler'; const SettingsSvgContainer = styled(TouchableOpacity)` margin: 0; padding: 8px; + height: 40px; + width: 40px; border-radius: 30px; - background-color: ${({theme: {dark}}) => (dark ? LightBlack : Cloud)}; + align-items: center; + justify-content: center; + background-color: ${({theme: {dark}}) => (dark ? LightBlack : NeutralSlate)}; `; const CogSvg = ({theme}: {theme: Theme}) => { - const cogColor = theme.dark ? White : LightBlack; + const cogColor = theme.dark ? Slate : SlateDark; return ( = ({children}) => { () => styled.View` flex: 1; padding-top: ${!showArchaxBanner ? insets.top : 0}px; + background-color: ${({theme}) => theme.colors.background}; `, [insets.top, insets.bottom, showArchaxBanner], ); diff --git a/src/navigation/tabs/home/HomeRoot.tsx b/src/navigation/tabs/home/HomeRoot.tsx index 711f6845c4..d12564ca6f 100644 --- a/src/navigation/tabs/home/HomeRoot.tsx +++ b/src/navigation/tabs/home/HomeRoot.tsx @@ -19,6 +19,7 @@ import { import {requestBrazeContentRefresh} from '../../../store/app/app.effects'; import { selectBrazeDoMore, + selectBrazeMarketingCarousel, selectBrazeQuickLinks, selectBrazeShopWithCrypto, } from '../../../store/app/app.selectors'; @@ -33,7 +34,6 @@ import { } from '../../../utils/helper-methods'; import {useAppDispatch, useAppSelector} from '../../../utils/hooks'; import {BalanceUpdateError} from '../../wallet/components/ErrorMessages'; -import AdvertisementsList from './components/advertisements/AdvertisementsList'; import DefaultAdvertisements from './components/advertisements/DefaultAdvertisements'; import Crypto from './components/Crypto'; import ExchangeRatesList, { @@ -45,6 +45,7 @@ import HomeSection from './components/HomeSection'; import LinkingButtons from './components/LinkingButtons'; import MockOffers from './components/offers/MockOffers'; import OffersCarousel from './components/offers/OffersCarousel'; +import MarketingCarousel from './components/MarketingCarousel'; import PortfolioBalance from './components/PortfolioBalance'; import DefaultQuickLinks from './components/quick-links/DefaultQuickLinks'; import QuickLinksCarousel from './components/quick-links/QuickLinksCarousel'; @@ -69,6 +70,7 @@ import { } from '../../../constants/currencies'; import {Network} from '../../../constants'; import SecurePasskeyBanner from './components/SecurePasskeyBanner'; +import DefaultMarketingCards from './components/DefaultMarketingCards'; export type HomeScreenProps = NativeStackScreenProps< TabsStackParamList, @@ -82,8 +84,9 @@ const HomeRoot: React.FC = ({route, navigation}) => { const theme = useTheme(); const themeType = useThemeType(); const [refreshing, setRefreshing] = useState(false); - const brazeShopWithCrypto = useAppSelector(selectBrazeShopWithCrypto); const brazeDoMore = useAppSelector(selectBrazeDoMore); + const brazeMarketingCarousel = useAppSelector(selectBrazeMarketingCarousel); + const brazeShopWithCrypto = useAppSelector(selectBrazeShopWithCrypto); const brazeQuickLinks = useAppSelector(selectBrazeQuickLinks); const keys = useAppSelector(({WALLET}) => WALLET.keys); const wallets = Object.values(keys).flatMap(k => k.wallets); @@ -123,13 +126,25 @@ const HomeRoot: React.FC = ({route, navigation}) => { } }, [passkeyCredentials, user]); + const memoizedMarketingCards = useMemo(() => { + if (STATIC_CONTENT_CARDS_ENABLED && !brazeMarketingCarousel.length) { + return DefaultMarketingCards(); + } + + return brazeMarketingCarousel; + }, [brazeMarketingCarousel]); + // Shop with Crypto const memoizedShopWithCryptoCards = useMemo(() => { - if (STATIC_CONTENT_CARDS_ENABLED && !brazeShopWithCrypto.length) { + const cardsWithCoverImage = brazeShopWithCrypto.filter( + card => card.extras?.cover_image, + ); + + if (STATIC_CONTENT_CARDS_ENABLED && !cardsWithCoverImage.length) { return MockOffers(); } - return brazeShopWithCrypto; + return cardsWithCoverImage; }, [brazeShopWithCrypto]); // Do More @@ -310,13 +325,14 @@ const HomeRoot: React.FC = ({route, navigation}) => { {appIsLoading ? null : ( <> - + + + {pendingTxps.length ? ( {pendingTxps.length} ) : null} - = ({route, navigation}) => { }> {/* ////////////////////////////// PORTFOLIO BALANCE */} {showPortfolioValue ? ( - + ) : null} @@ -352,8 +368,15 @@ const HomeRoot: React.FC = ({route, navigation}) => { ) : null} + {/* ////////////////////////////// MARKETING */} + {memoizedMarketingCards.length ? ( + + + + ) : null} + {/* ////////////////////////////// CRYPTO */} - + @@ -367,8 +390,9 @@ const HomeRoot: React.FC = ({route, navigation}) => { {/* ////////////////////////////// SHOP WITH CRYPTO */} {memoizedShopWithCryptoCards.length ? ( { navigation.navigate('Tabs', {screen: 'Shop'}); dispatch( @@ -381,16 +405,9 @@ const HomeRoot: React.FC = ({route, navigation}) => { ) : null} - {/* ////////////////////////////// DO MORE */} - {memoizedDoMoreCards.length ? ( - - - - ) : null} - {/* ////////////////////////////// EXCHANGE RATES */} {!showArchaxBanner && memoizedExchangeRates.length ? ( - + = ({route, navigation}) => { ) : null} - {/* ////////////////////////////// QUICK LINKS - Leave feedback etc */} - {memoizedQuickLinks.length ? ( - - - - ) : null} {showArchaxBanner && } diff --git a/src/navigation/tabs/home/components/AddSvg.tsx b/src/navigation/tabs/home/components/AddSvg.tsx new file mode 100644 index 0000000000..20aa048d20 --- /dev/null +++ b/src/navigation/tabs/home/components/AddSvg.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Circle, Path, Svg} from 'react-native-svg'; +import {useTheme} from 'styled-components/native'; +import {Action, LightBlue, LinkBlue, Midnight} from '../../../../styles/colors'; + +const AddSvg: React.FC<{ + width: number; + height: number; +}> = ({width = 40, height = 40}) => { + const theme = useTheme(); + return ( + + + + + ); +}; + +export default AddSvg; diff --git a/src/navigation/tabs/home/components/ArrowRightSvg.tsx b/src/navigation/tabs/home/components/ArrowRightSvg.tsx new file mode 100644 index 0000000000..90487bbc13 --- /dev/null +++ b/src/navigation/tabs/home/components/ArrowRightSvg.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import {Ellipse, G, Path, Svg} from 'react-native-svg'; +import {useTheme} from 'styled-components/native'; +import {LinkBlue, Midnight, Action, LightBlue} from '../../../../styles/colors'; + +const ArrowRightSvg: React.FC<{ + width?: number; + height?: number; +}> = ({width = 30, height = 30}) => { + const theme = useTheme(); + const circleColor = theme.dark ? Midnight : LightBlue; + const arrowColor = theme.dark ? LinkBlue : Action; + + const baseArrowWidth = 11.25; + const targetArrowWidth = 15; + const arrowScale = targetArrowWidth / baseArrowWidth; + const centerX = 20; + const centerY = 20; + const arrowOriginX = 6; + const arrowOriginY = 6.5; + + return ( + + + + + + + ); +}; + +export default ArrowRightSvg; diff --git a/src/navigation/tabs/home/components/ChevronRightSvg.tsx b/src/navigation/tabs/home/components/ChevronRightSvg.tsx new file mode 100644 index 0000000000..50c97cadb4 --- /dev/null +++ b/src/navigation/tabs/home/components/ChevronRightSvg.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Path, Svg} from 'react-native-svg'; +import {useTheme} from 'styled-components/native'; +import {Action, LinkBlue} from '../../../../styles/colors'; + +const ChevronRightSvg: React.FC<{width?: number; height?: number}> = ({ + width = 6, + height = 10, +}) => { + const theme = useTheme(); + const fillColor = theme.dark ? LinkBlue : Action; + + return ( + + + + ); +}; + +export default ChevronRightSvg; diff --git a/src/navigation/tabs/home/components/Crypto.tsx b/src/navigation/tabs/home/components/Crypto.tsx index 3f04f60613..69d350329f 100644 --- a/src/navigation/tabs/home/components/Crypto.tsx +++ b/src/navigation/tabs/home/components/Crypto.tsx @@ -35,8 +35,6 @@ import { } from '../../../../store/app/app.models'; import { CarouselItemContainer, - HomeSectionSubtext, - HomeSectionSubTitle, HomeSectionTitle, SectionHeaderContainer, } from './Styled'; @@ -44,25 +42,28 @@ import {View} from 'react-native'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; import CustomizeSvg from './CustomizeSvg'; import haptic from '../../../../components/haptic-feedback/haptic'; -import {Feather} from '../../../../styles/colors'; import Button from '../../../../components/button/Button'; import CoinbaseBalanceCard from '../../../coinbase/components/CoinbaseBalanceCard'; +import { + HOME_CARD_HEIGHT, + HOME_CARD_WIDTH, +} from '../../../../components/home-card/HomeCard'; import {COINBASE_ENV} from '../../../../api/coinbase/coinbase.constants'; import {WrongPasswordError} from '../../../wallet/components/ErrorMessages'; import {useTranslation} from 'react-i18next'; import {t} from 'i18next'; import {Analytics} from '../../../../store/analytics/analytics.effects'; +import AddSvg from './AddSvg'; //import {ConnectLedgerNanoXCard} from './cards/ConnectLedgerNanoX'; -import {successImport} from '../../../../store/wallet/wallet.actions'; -import {checkEncryptPassword} from '../../../../store/wallet/utils/wallet'; const CryptoContainer = styled.View` - background: ${({theme}) => (theme.dark ? '#111111' : Feather)}; - padding: 10px 0 12px; + background: ${({theme}) => theme.colors.background}; + padding: 5px 0 0px; `; const CarouselContainer = styled.View` - margin-top: 10px; + margin-top: 28px; + margin-bottom: 15px; `; const Row = styled.View` @@ -71,11 +72,39 @@ const Row = styled.View` const ListViewContainer = styled.View` padding: 20px 0 12px 0; + margin-bottom: 15px; `; const ButtonContainer = styled.View` padding: 20px 0; - margin-top: 15px; +`; + +const NoKeysSectionHeaderContainer = styled(SectionHeaderContainer)` + margin-bottom: 0px; +`; + +const NoKeysButtonWrapper = styled.View` + margin-bottom: 15px; +`; + +const CryptoSectionHeaderContainer = styled(SectionHeaderContainer)` + margin-bottom: -15px; + margin-top: 0px; +`; + +const CryptoHeaderRow = styled(Row)` + align-items: center; + width: 100%; + margin-bottom: 0px; +`; + +const CryptoHeaderTitle = styled(HomeSectionTitle)` + flex-grow: 1; +`; + +const CryptoHeaderActions = styled.View` + flex-direction: row; + gap: 8px; `; const _renderItem = ({item}: {item: {id: string; component: JSX.Element}}) => { @@ -311,44 +340,39 @@ const Crypto = () => { if (!hasKeys && !linkedCoinbase) { return ( - + - {t('My Crypto')} - - - {t( - 'You don’t have any crypto. Create a wallet, import a wallet or connect your Coinbase account.', - )} - - + {t('Your Crypto')} - - + + + + + + {/* */} - + ); } return ( - - - {t('My Crypto')} - - - {t( - 'View your wallets, card balance, connect to Coinbase and more.', - )} - + + + {t('Your Crypto')} + + { + haptic('soft'); + navigation.navigate('CreationOptions'); + }}> + + { @@ -383,21 +410,21 @@ const Crypto = () => { initialRoute: 'Customize Home', } as any); }}> - + - - - + + + {/* ////////////////////////////// CAROUSEL/LISTVIEW */} {homeCarouselLayoutType === 'carousel' ? ( - + { })} )} - {/* ////////////////////////////// CREATE DEFAULTS */} - - - - {t('Expand your Portfolio')} - - - - ); }; diff --git a/src/navigation/tabs/home/components/CustomizeSvg.tsx b/src/navigation/tabs/home/components/CustomizeSvg.tsx index 6f4de2900c..ef92ec5558 100644 --- a/src/navigation/tabs/home/components/CustomizeSvg.tsx +++ b/src/navigation/tabs/home/components/CustomizeSvg.tsx @@ -1,36 +1,20 @@ import React from 'react'; import {Circle, Path, Svg} from 'react-native-svg'; import {useTheme} from 'styled-components/native'; -import {Action, LinkBlue} from '../../../../styles/colors'; +import {Action, LightBlue, LinkBlue, Midnight} from '../../../../styles/colors'; const CustomizeSvg: React.FC<{ width: number; height: number; -}> = ({width = 35, height = 35}) => { +}> = ({width = 40, height = 40}) => { const theme = useTheme(); return ( - - - - - + + ); diff --git a/src/navigation/tabs/home/components/DefaultMarketingCards.ts b/src/navigation/tabs/home/components/DefaultMarketingCards.ts new file mode 100644 index 0000000000..720857497e --- /dev/null +++ b/src/navigation/tabs/home/components/DefaultMarketingCards.ts @@ -0,0 +1,43 @@ +import {ClassicContentCard} from '@braze/react-native-sdk'; +import {APP_DEEPLINK_PREFIX} from '../../../../constants/config'; +import {DEFAULT_CLASSIC_CONTENT_CARD} from '../../../../utils/braze'; +import {t} from 'i18next'; + +const DefaultMarketingCards = (): ClassicContentCard[] => { + return [ + // { + // ...DEFAULT_CLASSIC_CONTENT_CARD, + // id: 'dev_marketing_slide_1', + // image: require('../../../../../assets/img/home/offers/amc.png'), + // title: t('Buy movie tickets with crypto'), + // cardDescription: t('Buy an AMC Theatres gift card with crypto.'), + // url: `${APP_DEEPLINK_PREFIX}giftcard?merchant=amc%20theatres`, + // openURLInWebView: false, + // extras: { + // feed_type: 'marketing_carousel', + // }, + // }, + // { + // ...DEFAULT_CLASSIC_CONTENT_CARD, + // id: 'dev_marketing_slide_2', + // title: t('Grow your crypto with BitPay'), + // cardDescription: t('Discover new ways to buy, swap and spend securely.'), + // openURLInWebView: false, + // extras: { + // feed_type: 'marketing_carousel', + // }, + // }, + // { + // ...DEFAULT_CLASSIC_CONTENT_CARD, + // id: 'dev_marketing_slide_3', + // title: t('Learn about crypto with BitPay'), + // cardDescription: t('Enroll in crypto 101.'), + // openURLInWebView: false, + // extras: { + // feed_type: 'marketing_carousel', + // }, + // }, + ]; +}; + +export default DefaultMarketingCards; diff --git a/src/navigation/tabs/home/components/HeaderProfileButton.tsx b/src/navigation/tabs/home/components/HeaderProfileButton.tsx index 4a86712b94..08186c015e 100644 --- a/src/navigation/tabs/home/components/HeaderProfileButton.tsx +++ b/src/navigation/tabs/home/components/HeaderProfileButton.tsx @@ -21,7 +21,7 @@ const ProfileButton: React.FC = () => { ? navigation.navigate('BitPayIdProfile') : navigation.navigate('Login'); }}> - + ); diff --git a/src/navigation/tabs/home/components/HeaderScanButton.tsx b/src/navigation/tabs/home/components/HeaderScanButton.tsx index 057fcc8724..70a0a9dac1 100644 --- a/src/navigation/tabs/home/components/HeaderScanButton.tsx +++ b/src/navigation/tabs/home/components/HeaderScanButton.tsx @@ -5,6 +5,7 @@ import * as Svg from 'react-native-svg'; import {HeaderButtonContainer} from './Styled'; import { Action, + LightBlue, LinkBlue, Midnight, NeutralSlate, @@ -15,90 +16,16 @@ import {Analytics} from '../../../../store/analytics/analytics.effects'; const ScanIcon = () => { const theme = useTheme(); - const background = theme.dark ? Midnight : NeutralSlate; + const background = theme.dark ? Midnight : LightBlue; const fill = theme.dark ? LinkBlue : Action; return ( - - - + + - - - - - - - - ); diff --git a/src/navigation/tabs/home/components/HomeSection.tsx b/src/navigation/tabs/home/components/HomeSection.tsx index 82dfb943d4..eff0b69500 100644 --- a/src/navigation/tabs/home/components/HomeSection.tsx +++ b/src/navigation/tabs/home/components/HomeSection.tsx @@ -12,89 +12,111 @@ import { } from '../../../../components/styled/Containers'; import {BaseText, Link} from '../../../../components/styled/Text'; import {HomeSectionTitle} from './Styled'; -import {LightBlack, LuckySevens, SlateDark} from '../../../../styles/colors'; +import { + Action, + LightBlue, + Midnight, + Slate30, + SlateDark, + White, +} from '../../../../styles/colors'; +import ChevronRightSvg from './ChevronRightSvg'; interface HomeRowProps { title?: string | undefined; action?: string | undefined; onActionPress?: TouchableWithoutFeedbackProps['onPress']; - slimHeader?: boolean; style?: StyleProp; - slimContainer?: boolean; label?: string; children: React.ReactNode; } -const HomeRowContainer = styled.View<{slim?: boolean}>` - margin-bottom: ${({slim}) => (slim ? 32 : 28)}px; +const HomeRowContainer = styled.View` + margin-bottom: 10px; `; -const Header = styled.View<{slim?: boolean}>` +const Header = styled.View` display: flex; flex-direction: row; - margin: 0 ${ScreenGutter} ${({slim}) => (slim ? 0 : 12)}px 16px; + margin: 0 ${ScreenGutter} 0 16px; justify-content: space-between; align-items: center; `; +const HeaderLeft = styled.View` + flex-direction: row; + align-items: center; + flex-shrink: 1; +`; + const HomeSectionTitleContainer = styled.View` - width: 80%; + margin-right: 8px; + flex-shrink: 1; `; const HeaderLinkContainer = styled.View` margin-left: auto; + background-color: ${({theme: {dark}}) => (dark ? Midnight : LightBlue)}; + padding: 4px 10px 4px 12px; + border-radius: 50px; `; const HeaderLink = styled(Link)` - font-weight: 500; - font-size: 14px; + color: ${({theme: {dark}}) => (dark ? White : Action)}; + font-weight: 400; + font-size: 12px; + text-transform: capitalize; `; const HeaderLabel = styled(BaseText)` - font-weight: 500; - font-size: 14px; - color: ${({theme}) => (theme.dark ? LuckySevens : SlateDark)}; + font-weight: 400; + font-size: 12px; + color: ${({theme}) => (theme.dark ? Slate30 : SlateDark)}; `; const HeaderLabelContainer = styled.View` - border: 1px solid ${({theme: {dark}}) => (dark ? LightBlack : '#E1E4E7')}; - padding: 0 8px; + border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; + padding: 2px 8px; border-radius: 50px; + margin-left: 0px; +`; + +const HeaderLinkContent = styled.View` + flex-direction: row; + align-items: center; + gap: 7px; `; const HomeSection: React.FC = props => { - const { - title, - action, - onActionPress, - slimHeader, - children, - style, - slimContainer, - label, - } = props; + const {title, action, onActionPress, children, style, label} = props; return ( - + {title ? ( -
- - {title} - +
+ + + {title} + + {label ? ( + + {label} + + ) : null} + {action ? ( - {action} + + {action} + + ) : null} - {label ? ( - - {label} - - ) : null}
) : null} {children} diff --git a/src/navigation/tabs/home/components/InfoSvg.tsx b/src/navigation/tabs/home/components/InfoSvg.tsx new file mode 100644 index 0000000000..89e417bf67 --- /dev/null +++ b/src/navigation/tabs/home/components/InfoSvg.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {Path, Svg} from 'react-native-svg'; +import {useTheme} from 'styled-components/native'; +import {Slate, SlateDark} from '../../../../styles/colors'; + +const InfoSvg: React.FC<{ + width?: number; + height?: number; +}> = ({width = 16, height = 16}) => { + const theme = useTheme(); + const fillColor = theme.dark ? Slate : SlateDark; + + return ( + + + + ); +}; + +export default InfoSvg; diff --git a/src/navigation/tabs/home/components/LinkingButtons.tsx b/src/navigation/tabs/home/components/LinkingButtons.tsx index 77e67497ea..ca98f5e4fa 100644 --- a/src/navigation/tabs/home/components/LinkingButtons.tsx +++ b/src/navigation/tabs/home/components/LinkingButtons.tsx @@ -1,13 +1,13 @@ import React, {ReactNode} from 'react'; -import styled, {useTheme} from 'styled-components/native'; +import styled from 'styled-components/native'; import {Action, Midnight, White} from '../../../../styles/colors'; import Haptic from '../../../../components/haptic-feedback/haptic'; import {BaseText} from '../../../../components/styled/Text'; import {titleCasing} from '../../../../utils/helper-methods'; import {useAppDispatch, useAppSelector} from '../../../../utils/hooks'; -import {ActiveOpacity} from '../../../../components/styled/Containers'; +import {ActiveOpacity, WIDTH} from '../../../../components/styled/Containers'; import {useNavigation} from '@react-navigation/native'; -import {Path, Rect, Svg} from 'react-native-svg'; +import {Path, Svg} from 'react-native-svg'; import {useRequireKeyAndWalletRedirect} from '../../../../utils/hooks/useRequireKeyAndWalletRedirect'; import {useTranslation} from 'react-i18next'; import {WalletScreens} from '../../../wallet/WalletGroup'; @@ -15,27 +15,29 @@ import {Analytics} from '../../../../store/analytics/analytics.effects'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; const ButtonsRow = styled.View` - justify-content: center; + justify-content: space-between; flex-direction: row; align-self: center; + width: ${WIDTH - 24}px; + max-width: 340px; `; const ButtonContainer = styled.View` align-items: center; - margin: 0 16px; + margin: 0; `; const ButtonText = styled(BaseText)` - font-size: 12px; + font-size: 13px; line-height: 18px; - color: ${({theme: {dark}}) => (dark ? White : Action)}; - margin-top: 5px; + color: ${({theme: {dark}}) => (dark ? White : Midnight)}; + margin-top: 3px; `; const LinkButton = styled(TouchableOpacity)` - height: 43px; - width: 43px; - border-radius: 11px; + height: 40px; + width: 40px; + border-radius: 30px; align-items: center; justify-content: center; background: ${({theme: {dark}, disabled}) => @@ -44,75 +46,49 @@ const LinkButton = styled(TouchableOpacity)` `; const BuySvg = () => { - const theme = useTheme(); return ( - - + + ); }; const SellSvg = () => { - const theme = useTheme(); return ( - - - - + + ); }; const SwapSvg = () => { - const theme = useTheme(); return ( - - + ); }; const ReceiveSvg = () => { - const theme = useTheme(); return ( - + ); }; const SendSvg = () => { - const theme = useTheme(); return ( - + ); diff --git a/src/navigation/tabs/home/components/MarketingCarousel.tsx b/src/navigation/tabs/home/components/MarketingCarousel.tsx new file mode 100644 index 0000000000..654428aaa8 --- /dev/null +++ b/src/navigation/tabs/home/components/MarketingCarousel.tsx @@ -0,0 +1,445 @@ +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import Carousel, {ICarouselInstance} from 'react-native-reanimated-carousel'; +import styled from 'styled-components/native'; +import {Linking} from 'react-native'; +import Braze, {ContentCard} from '@braze/react-native-sdk'; +import FastImage, {Source} from 'react-native-fast-image'; +import { + ActiveOpacity, + ScreenGutter, + WIDTH, +} from '../../../../components/styled/Containers'; +import {BaseText} from '../../../../components/styled/Text'; +import { + Action, + Black, + LightBlack, + LightBlue, + Midnight, + NeutralSlate, + Slate30, + SlateDark, + White, +} from '../../../../styles/colors'; +import {useFocusEffect, useTheme} from '@react-navigation/native'; +import MarketingCloseIcon from './MarketingCloseIcon'; +import {APP_DEEPLINK_PREFIX} from '../../../../constants/config'; +import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import haptic from '../../../../components/haptic-feedback/haptic'; +import {useAppDispatch, useUrlEventHandler} from '../../../../utils/hooks'; +import {AppActions, AppEffects} from '../../../../store/app'; +import {LogActions} from '../../../../store/log'; +import { + isCaptionedContentCard, + isClassicContentCard, +} from '../../../../utils/braze'; + +const horizontalPadding = Number(ScreenGutter.replace('px', '')); +const cardWidth = WIDTH - horizontalPadding; +const cardHeight = 72; +const carouselScrollDuration = 500; + +const CarouselItemContainer = styled.View` + padding-left: ${horizontalPadding}px; + padding-right: 0; +`; + +type Slide = { + card: ContentCard; + title: string; + subtitle: string; + url?: string; + openURLInWebView?: boolean; + heroImage?: Source | null; + backgroundColor?: string; +}; + +interface MarketingCarouselProps { + contentCards: ContentCard[]; +} + +const MarketingCarousel: React.FC = ({ + contentCards, +}) => { + const [activeIndex, setActiveIndex] = useState(0); + const [dismissedCardIds, setDismissedCardIds] = useState([]); + const skipNextCardPressRef = useRef(false); + const dismissTimeoutsRef = useRef>>( + new Map(), + ); + const theme = useTheme(); + const carouselRef = useRef(null); + const dispatch = useAppDispatch(); + const urlEventHandler = useUrlEventHandler(); + const loggedImpressionsRef = useRef(new Set()); + + const slides = useMemo(() => { + const dismissedSet = new Set(dismissedCardIds); + + return contentCards + .filter(card => { + const cardId = card.id; + return !cardId || !dismissedSet.has(cardId); + }) + .map(card => { + let title = ''; + let subtitle = ''; + + if (isCaptionedContentCard(card)) { + title = card.title || ''; + subtitle = card.cardDescription || ''; + } else if (isClassicContentCard(card)) { + title = card.title || ''; + subtitle = card.cardDescription || ''; + } + + if (!title) { + title = card.id; + } + + let heroImage: Source | null = null; + + if (card.image) { + heroImage = + typeof card.image === 'string' + ? {uri: card.image} + : (card.image as Source); + } + + return { + card, + title: title, + subtitle: subtitle, + url: card.url, + openURLInWebView: card.openURLInWebView, + heroImage, + }; + }) + .filter(slide => slide.title); + }, [contentCards, dismissedCardIds]); + + useEffect(() => { + setActiveIndex(prevIndex => { + if (!slides.length) { + return 0; + } + + return Math.min(prevIndex, slides.length - 1); + }); + }, [slides.length]); + + useEffect(() => { + return () => { + dismissTimeoutsRef.current.forEach(timeoutId => clearTimeout(timeoutId)); + dismissTimeoutsRef.current.clear(); + }; + }, []); + + const logImpression = useCallback((card: ContentCard) => { + if (!card.id || card.id.startsWith('dev_')) { + return; + } + + if (loggedImpressionsRef.current.has(card.id)) { + return; + } + + loggedImpressionsRef.current.add(card.id); + Braze.logContentCardImpression(card.id); + }, []); + + useEffect(() => { + const slide = slides[activeIndex]; + + if (slide) { + logImpression(slide.card); + } + }, [activeIndex, logImpression, slides]); + + useFocusEffect( + useCallback(() => { + const slide = slides[activeIndex]; + if (slide) { + logImpression(slide.card); + } + }, [activeIndex, slides, logImpression]), + ); + + const handleSlidePress = useCallback( + async (slide: Slide) => { + const {card, url, openURLInWebView} = slide; + + if (!url) { + return; + } + + if (!card.id.startsWith('dev_')) { + Braze.logContentCardClicked(card.id); + } + + haptic('impactLight'); + + try { + const handled = await urlEventHandler({url}); + + if (!handled) { + if (url.startsWith(APP_DEEPLINK_PREFIX)) { + Linking.openURL(url); + } else if (openURLInWebView) { + dispatch(AppEffects.openUrlWithInAppBrowser(url)); + } else { + Linking.openURL(url); + } + } + } catch (err) { + dispatch( + LogActions.debug('Failed to open marketing slide URL: ' + url), + ); + dispatch(LogActions.debug(JSON.stringify(err))); + Linking.openURL(url).catch(() => { + dispatch(AppEffects.openUrlWithInAppBrowser(url)); + }); + } + }, + [dispatch, urlEventHandler], + ); + + if (!slides.length) { + return null; + } + + return ( + + { + gestureChain.activeOffsetX([-10, 10]); + gestureChain.failOffsetY([-10, 10]); + }} + loop={false} + vertical={false} + width={cardWidth} + height={cardHeight} + autoPlay={false} + style={{width: WIDTH}} + data={slides} + pagingEnabled + snapEnabled + scrollAnimationDuration={carouselScrollDuration} + onProgressChange={(_, absoluteProgress) => { + const nextIndex = Math.min( + slides.length - 1, + Math.max(0, Math.round(absoluteProgress)), + ); + setActiveIndex(nextIndex); + }} + renderItem={({item, index}) => ( + + + { + if (skipNextCardPressRef.current) { + skipNextCardPressRef.current = false; + return; + } + + handleSlidePress(item); + }} + accessibilityState={{disabled: !item.url}}> + + {item.heroImage ? ( + + + + ) : null} + + {item.title} + {item.subtitle ? ( + + {item.subtitle} + + ) : null} + + + + { + skipNextCardPressRef.current = true; + }} + onPress={() => { + skipNextCardPressRef.current = true; + const cardId = item.card.id; + + if (!cardId) { + return; + } + + if (dismissTimeoutsRef.current.has(cardId)) { + return; + } + + const targetIndex = + slides.length > 1 && index === slides.length - 1 + ? Math.max(0, index - 1) + : index; + + setActiveIndex(targetIndex); + + if (targetIndex !== index) { + carouselRef.current?.scrollTo({ + index: targetIndex, + animated: true, + }); + } + + const shouldDelay = targetIndex !== index; + + const completeDismissal = () => { + if (!cardId.startsWith('dev_')) { + Braze.logContentCardDismissed(cardId); + } + + dispatch(AppActions.dismissMarketingContentCard(cardId)); + setDismissedCardIds(prev => + prev.includes(cardId) ? prev : [...prev, cardId], + ); + }; + + if (shouldDelay) { + const timeoutId = setTimeout(() => { + dismissTimeoutsRef.current.delete(cardId); + completeDismissal(); + }, carouselScrollDuration); + + dismissTimeoutsRef.current.set(cardId, timeoutId); + } else { + completeDismissal(); + } + }}> + + + + + )} + /> + {slides.length > 1 ? ( + + {slides.map((slide, index) => ( + { + if (index !== activeIndex) { + carouselRef.current?.scrollTo({index, animated: true}); + } + }} + /> + ))} + + ) : null} + + ); +}; + +const Container = styled.View` + padding-top: 10px; + padding-bottom: 10px; +`; + +const CardContainer = styled.View<{backgroundColor: string}>` + background-color: ${({backgroundColor}) => backgroundColor}; + border-radius: 12px; + height: ${cardHeight}px; + flex-direction: row; + align-items: center; + padding-right: 16px; +`; + +const CardTouchable = styled(TouchableOpacity)` + flex: 1; + padding: 16px; +`; + +const CardRow = styled.View` + flex-direction: row; + align-items: center; +`; + +const HeroContainer = styled.View` + width: 40px; + height: 40px; + border-radius: 40px; + margin-right: 8px; + overflow: hidden; +`; + +const HeroImage = styled(FastImage)` + width: 40px; + height: 40px; +`; + +const CopyContainer = styled.View` + flex: 1; + margin-right: 12px; + margin-left: 2px; +`; + +const CardTitle = styled(BaseText)` + color: ${({theme}) => (theme.dark ? White : Black)}; + font-size: 13px; + font-style: normal; + font-weight: 400; + line-height: 20px; +`; + +const CardSubtitle = styled(BaseText)` + color: ${({theme}) => (theme.dark ? Slate30 : SlateDark)}; + font-size: 12px; + font-style: normal; + font-weight: 400; + line-height: 15px; + margin-top: 4px; +`; + +const CloseButton = styled.TouchableOpacity.attrs({ + hitSlop: {top: 12, bottom: 12, left: 12, right: 12}, +})` + width: 24px; + height: 24px; + align-items: center; + justify-content: center; +`; + +const DotsContainer = styled.View` + flex-direction: row; + justify-content: center; + margin-top: 14px; +`; + +const Dot = styled.TouchableOpacity.attrs({ + activeOpacity: ActiveOpacity, + hitSlop: {top: 8, bottom: 8, left: 8, right: 8}, +})<{active: boolean}>` + height: 8px; + border-radius: 4px; + margin: 0 4px; + width: ${({active}) => (active ? 20 : 8)}px; + background-color: ${({active, theme}) => + active ? Action : theme.dark ? LightBlack : NeutralSlate}; +`; + +export default MarketingCarousel; diff --git a/src/navigation/tabs/home/components/MarketingCloseIcon.tsx b/src/navigation/tabs/home/components/MarketingCloseIcon.tsx new file mode 100644 index 0000000000..217862aad9 --- /dev/null +++ b/src/navigation/tabs/home/components/MarketingCloseIcon.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import Svg, {Path} from 'react-native-svg'; +import {useTheme} from '@react-navigation/native'; +import {Slate, SlateDark} from '../../../../styles/colors'; + +interface MarketingCloseIconProps { + color?: string; +} + +const MarketingCloseIcon: React.FC = ({color}) => { + const {dark} = useTheme(); + const fillColor = color ?? (dark ? Slate : SlateDark); + + return ( + + + + ); +}; + +export default MarketingCloseIcon; diff --git a/src/navigation/tabs/home/components/PortfolioBalance.tsx b/src/navigation/tabs/home/components/PortfolioBalance.tsx index cae87589a6..bfd75f6c8e 100644 --- a/src/navigation/tabs/home/components/PortfolioBalance.tsx +++ b/src/navigation/tabs/home/components/PortfolioBalance.tsx @@ -1,14 +1,14 @@ import React from 'react'; import styled from 'styled-components/native'; import {BaseText, H2} from '../../../../components/styled/Text'; -import {Black, LuckySevens, SlateDark, White} from '../../../../styles/colors'; +import {Slate30, SlateDark, White} from '../../../../styles/colors'; import {useSelector} from 'react-redux'; import {RootState} from '../../../../store'; import { calculatePercentageDifference, formatFiatAmount, } from '../../../../utils/helper-methods'; -import InfoSvg from '../../../../../assets/img/info.svg'; +import InfoSvg from './InfoSvg'; import {ActiveOpacity} from '../../../../components/styled/Containers'; import {useAppDispatch, useAppSelector} from '../../../../utils/hooks'; import { @@ -32,8 +32,8 @@ const PortfolioBalanceHeader = styled(TouchableOpacity)` `; const PortfolioBalanceTitle = styled(BaseText)` - margin-right: 5px; - font-size: 14px; + margin-right: 3px; + font-size: 13px; color: ${({theme: {dark}}) => (dark ? White : SlateDark)}; `; @@ -43,14 +43,8 @@ const PortfolioBalanceText = styled(BaseText)` color: ${({theme}) => theme.colors.text}; `; -const PercentageText = styled(BaseText)` - font-size: 12px; - color: ${({theme: {dark}}) => (dark ? LuckySevens : Black)}; -`; - -const PercentageContainer = styled.View` - flex-direction: row; - justify-content: center; +const PercentageWrapper = styled.View` + align-items: center; `; const PortfolioBalance = () => { @@ -98,7 +92,7 @@ const PortfolioBalance = () => { activeOpacity={ActiveOpacity} onPress={showPortfolioBalanceInfoModal}> {t('Portfolio Balance')} - + { @@ -112,10 +106,13 @@ const PortfolioBalance = () => { })} {percentageDifference ? ( - - - {t('Last Day')} - + + + ) : null} ) : ( diff --git a/src/navigation/tabs/home/components/SecurePasskeyBanner.tsx b/src/navigation/tabs/home/components/SecurePasskeyBanner.tsx index 196c5e1c71..e2bd3aac78 100644 --- a/src/navigation/tabs/home/components/SecurePasskeyBanner.tsx +++ b/src/navigation/tabs/home/components/SecurePasskeyBanner.tsx @@ -1,26 +1,26 @@ import React from 'react'; import {CommonActions, useNavigation} from '@react-navigation/native'; -import {useTheme} from 'styled-components/native'; import styled from 'styled-components/native'; import {ActiveOpacity} from '@components/base/TouchableOpacity'; -import {BoxShadow} from '../../../../navigation/tabs/home/components/Styled'; import SecurePasskeyIcon from '../../../../../assets/img/secure-passkey.svg'; -import HomeArrowRight from '../../../../../assets/img/home-arrow-right.svg'; +import ArrowRightSvg from './ArrowRightSvg'; import {TouchableOpacity} from '../../../../components/base/TouchableOpacity'; -import {LightBlack, White} from '../../../../styles/colors'; +import {LightBlack, Slate30, White} from '../../../../styles/colors'; import {BaseText} from '../../../../components/styled/Text'; import {RootStacks} from '../../../../Root'; import {TabsScreens} from '../../../../navigation/tabs/TabsStack'; import {SecurityScreens} from '../../../../navigation/tabs/settings/security/SecurityGroup'; const PasskeyBannerContainer = styled(TouchableOpacity)` - background-color: ${({theme: {dark}}) => (dark ? LightBlack : White)}; + background-color: ${({theme: {dark}}) => (dark ? '#111' : White)}; + border-color: ${({theme: {dark}}) => (dark ? LightBlack : Slate30)}; + border-width: 1px; border-radius: 12px; flex-direction: column; justify-content: center; min-height: 100px; padding: 16px 35px 16px 76px; - margin: 0 15px; + margin: 0px 11px 30px; position: relative; `; @@ -49,10 +49,8 @@ const IconArrowRight = styled.View` const SecurePasskeyBanner: React.FC = () => { const navigation = useNavigation(); - const theme = useTheme(); return ( { navigation.dispatch( @@ -74,12 +72,12 @@ const SecurePasskeyBanner: React.FC = () => { Secure your account - Setup a Passkey + Create a Passkey - + ); diff --git a/src/navigation/tabs/home/components/Styled.tsx b/src/navigation/tabs/home/components/Styled.tsx index e687615189..f56d0fa912 100644 --- a/src/navigation/tabs/home/components/Styled.tsx +++ b/src/navigation/tabs/home/components/Styled.tsx @@ -14,9 +14,7 @@ export const HeaderLeftContainer = styled.View` flex-grow: 1; `; -export const HeaderButtonContainer = styled.View` - margin-left: ${ScreenGutter}; -`; +export const HeaderButtonContainer = styled.View``; export const HomeSectionSubtext = styled(H7)` color: ${({theme: {dark}}) => (dark ? Slate : Black)}; @@ -24,6 +22,8 @@ export const HomeSectionSubtext = styled(H7)` export const HomeSectionTitle = styled(H5)` color: ${({theme: {dark}}) => (dark ? White : Black)}; + font-size: 20px; + font-weight: 700; `; export const HomeSectionSubTitle = styled(HomeSectionTitle)` @@ -32,13 +32,11 @@ export const HomeSectionSubTitle = styled(HomeSectionTitle)` export const SectionHeaderContainer = styled.View<{justifyContent?: string}>` flex-direction: row; - margin: 20px ${ScreenGutter} 10px; + margin: 20px ${ScreenGutter}; justify-content: ${({justifyContent}) => justifyContent || 'flex-start'}; `; -export const CarouselItemContainer = styled.View` - padding: 20px 0; -`; +export const CarouselItemContainer = styled.View``; export const BoxShadow = { shadowColor: '#000', diff --git a/src/navigation/tabs/home/components/Wallet.tsx b/src/navigation/tabs/home/components/Wallet.tsx index 331a434b25..b1c2801a83 100644 --- a/src/navigation/tabs/home/components/Wallet.tsx +++ b/src/navigation/tabs/home/components/Wallet.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import styled, {useTheme} from 'styled-components/native'; +import {View} from 'react-native'; +import styled from 'styled-components/native'; import HomeCard from '../../../../components/home-card/HomeCard'; import {BaseText, H3} from '../../../../components/styled/Text'; import {Wallet} from '../../../../store/wallet/wallet.models'; @@ -24,13 +25,12 @@ import { ScreenGutter, } from '../../../../components/styled/Containers'; import {HomeCarouselLayoutType} from '../../../../store/app/app.models'; -import {BoxShadow} from './Styled'; -import {View} from 'react-native'; import Percentage from '../../../../components/percentage/Percentage'; import {useAppSelector} from '../../../../utils/hooks'; import {useTranslation} from 'react-i18next'; import AngleRightSvg from '../../../../../assets/img/angle-right.svg'; -import {Balance, OptionName} from '../../../wallet/components/DropdownOption'; +import ArrowRightSvg from './ArrowRightSvg'; +import {Balance} from '../../../wallet/components/DropdownOption'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; interface WalletCardComponentProps { @@ -52,12 +52,15 @@ export const HeaderImg = styled.View` `; export const ListCard = styled(TouchableOpacity)<{outlineStyle?: boolean}>` - border: ${({outlineStyle, theme}) => - outlineStyle ? `1px solid ${theme.dark ? SlateDark : Slate30}` : 'none'}; - background-color: ${({theme: {dark}}) => (dark ? LightBlack : White)}; + border: ${({theme, outlineStyle}) => + `1px solid ${ + theme.dark ? (!outlineStyle ? LightBlack : SlateDark) : Slate30 + }`}; + background-color: ${({theme: {dark}, outlineStyle}) => + dark ? (!outlineStyle ? '#111' : 'none') : White}; border-radius: 12px; margin: ${({outlineStyle}) => - outlineStyle ? `0px 0px ${ScreenGutter} 0px` : `10px ${ScreenGutter}`}; + outlineStyle ? `0px 0px ${ScreenGutter} 0px` : `8px ${ScreenGutter}`}; flex-direction: row; align-items: center; justify-content: space-between; @@ -68,15 +71,18 @@ export const ListCard = styled(TouchableOpacity)<{outlineStyle?: boolean}>` export const Img = styled.View<{isFirst: boolean}>` min-height: 22px; margin-left: ${({isFirst}) => (isFirst ? 0 : '-5px')}; + justify-content: center; + align-items: center; `; export const RemainingAssetsLabel = styled(BaseText)` - font-size: 12px; + font-size: 13px; font-style: normal; - font-weight: 500; + font-weight: 400; line-height: 18px; letter-spacing: 0; color: ${Slate}; + color: ${({theme: {dark}}) => (dark ? Slate30 : SlateDark)}; margin-left: 5px; `; @@ -85,10 +91,15 @@ const NeedBackupText = styled(BaseText)` text-align: center; color: ${({theme: {dark}}) => (dark ? White : SlateDark)}; padding: 2px 4px; - border: 1px solid ${({theme: {dark}}) => (dark ? White : Slate30)}; + border: 1px solid ${({theme: {dark}}) => (dark ? SlateDark : Slate30)}; border-radius: 3px; `; +export const BalanceContainer = styled.View` + flex-direction: row; + gap: 6px; +`; + export const BalanceCode = styled(BaseText)` font-size: 12px; color: ${({theme: {dark}}) => (dark ? NeutralSlate : SlateDark)}; @@ -99,6 +110,54 @@ export const BalanceCodeContainer = styled.View` padding-left: 2px; `; +export const SupportedNetworkIconContainer = styled.View` + border: 1px solid ${({theme: {dark}}) => (dark ? LightBlack : Slate30)}; + border-radius: 20px; + height: 30px; + align-items: center; + padding-left: 4px; + padding-right: 7px; + justify-content: center; +`; + +const RemainingAssetsContainer = styled.View` + padding-bottom: 0px; +`; + +const ListRow = styled(Row)` + align-items: center; + justify-content: space-between; + width: 100%; +`; + +const HeaderColumn = styled(Column)` + justify-content: center; + align-items: flex-end; + margin-right: 12px; +`; + +const NeedBackupRow = styled(Row)` + align-items: center; +`; + +const FooterSupportedNetworkIconContainer = styled( + SupportedNetworkIconContainer, +)` + margin-right: 12px; +`; + +const FooterContainer = styled(Row)` + align-items: center; + justify-content: space-between; + width: 100%; +`; + +const KeyName = styled(BaseText)` + color: ${({theme: {dark}}) => (dark ? Slate30 : SlateDark)}; + font-size: 12px; + font-weight: 400; +`; + export const WALLET_DISPLAY_LIMIT = 3; export const ICON_SIZE = 20; @@ -114,29 +173,26 @@ const WalletCardComponent: React.FC = ({ context, }) => { const {t} = useTranslation(); - const theme = useTheme(); const defaultAltCurrency = useAppSelector(({APP}) => APP.defaultAltCurrency); const walletInfo = wallets.slice(0, WALLET_DISPLAY_LIMIT); const remainingWalletCount = getRemainingWalletCount(wallets); const isListView = layout === 'listView'; - const HeaderComponent = ( + const SupportedNetworkIcons = ( {walletInfo.map((wallet, index) => { const {id, img} = wallet; return ( wallet && (
- + ) ); })} {remainingWalletCount ? ( - - - + {remainingWalletCount} {t('more')}{' '} - - + + + {remainingWalletCount} + ) : null} ); @@ -151,45 +207,53 @@ const WalletCardComponent: React.FC = ({ - + - {HeaderComponent} - {keyName} + {keyName} + + + {!hideKeyBalance && percentageDifference ? ( + + ) : null} + - - {needsBackup ? ( - - {t('Needs Backup')} - {context === 'keySelector' ? ( - - ) : null} - - ) : context === 'keySelector' ? ( - - ) : !hideKeyBalance ? ( - <> - - {amount} - {code ? ( - - {code} - + + + {needsBackup ? ( + + {t('Needs Backup')} + {context === 'keySelector' ? ( + ) : null} - - {percentageDifference ? ( - - ) : null} - - ) : ( -

****

- )} -
-
+ + ) : context === 'keySelector' ? ( + + ) : ( + + {SupportedNetworkIcons} + + )} + + + +
); } @@ -197,9 +261,17 @@ const WalletCardComponent: React.FC = ({ // todo refactor to not use multiple layers for home card as it will no longer be used for anything other then keys /* ////////////////////////////// CAROUSEL */ + const CardFooter = ( + + + {SupportedNetworkIcons} + + + + ); + return ( = ({ needsBackup, hideKeyBalance, }} + footer={CardFooter} onCTAPress={onPress} /> ); diff --git a/src/navigation/tabs/home/components/exchange-rates/ExchangeRateItem.tsx b/src/navigation/tabs/home/components/exchange-rates/ExchangeRateItem.tsx index 830d3d97d0..01b49f4bad 100644 --- a/src/navigation/tabs/home/components/exchange-rates/ExchangeRateItem.tsx +++ b/src/navigation/tabs/home/components/exchange-rates/ExchangeRateItem.tsx @@ -13,18 +13,20 @@ import { formatFiatAmountObj, getRateByCurrencyName, } from '../../../../../utils/helper-methods'; -import GainArrow from '../../../../../../assets/img/home/exchange-rates/increment-arrow.svg'; -import LossArrow from '../../../../../../assets/img/home/exchange-rates/decrement-arrow.svg'; +import GainArrow from '../../../../../components/icons/trend-arrow/IncrementArrow'; +import LossArrow from '../../../../../components/icons/trend-arrow/DecrementArrow'; import NeutralArrow from '../../../../../../assets/img/home/exchange-rates/flat-arrow.svg'; import {ExchangeRateItemProps} from './ExchangeRatesList'; import { LuckySevens, NeutralSlate, + Slate30, SlateDark, } from '../../../../../styles/colors'; import {View} from 'react-native'; import {useAppSelector} from '../../../../../utils/hooks'; import {TouchableOpacity} from '@components/base/TouchableOpacity'; +import Percentage from '../../../../../components/percentage/Percentage'; const RowContainer = styled(TouchableOpacity)` flex-direction: row; @@ -42,7 +44,8 @@ const SubTextContainer = styled.View` `; const ExchangeRateText = styled(H7)` - font-weight: 500; + font-size: 16px; + font-weight: 400; `; const ExchangeRateCode = styled(BaseText)` @@ -54,7 +57,8 @@ const ExchangeRateCode = styled(BaseText)` const ExchangeRateSubText = styled(Smallest)` line-height: 20px; - color: ${({theme}) => (theme.dark ? LuckySevens : SlateDark)}; + font-size: 13px; + color: ${({theme}) => (theme.dark ? Slate30 : SlateDark)}; `; const showLossGainOrNeutralArrow = (average: number | undefined) => { @@ -143,8 +147,7 @@ const ExchangeRateItem = ({ ) : null} - {showLossGainOrNeutralArrow(average)} - {Math.abs(average || 0)}% + diff --git a/src/navigation/tabs/home/components/offers/MockOffers.tsx b/src/navigation/tabs/home/components/offers/MockOffers.tsx index 27d363c359..32648abef4 100644 --- a/src/navigation/tabs/home/components/offers/MockOffers.tsx +++ b/src/navigation/tabs/home/components/offers/MockOffers.tsx @@ -8,8 +8,14 @@ const MockOffers = (): ClassicContentCard[] => { { ...DEFAULT_CLASSIC_CONTENT_CARD, id: 'dev_1', - cardDescription: t('Buy movie tickets at AMC Theaters'), + title: t('Buy movie tickets with crypto'), + cardDescription: t( + 'Buy an AMC Theatres gift card with crypto in the BitPay app.', + ), image: require('../../../../../../assets/img/home/offers/amc.png'), + extras: { + cover_image: require('../../../../../../assets/img/home/offers/amc-cover.png'), + }, url: `${APP_DEEPLINK_PREFIX}giftcard?merchant=amc%20theatres`, openURLInWebView: false, }, diff --git a/src/navigation/tabs/home/components/offers/OfferCard.tsx b/src/navigation/tabs/home/components/offers/OfferCard.tsx index b7e18f2806..13cc425a0d 100644 --- a/src/navigation/tabs/home/components/offers/OfferCard.tsx +++ b/src/navigation/tabs/home/components/offers/OfferCard.tsx @@ -1,6 +1,7 @@ import {useFocusEffect} from '@react-navigation/native'; import React from 'react'; import {Linking} from 'react-native'; +import styled from 'styled-components/native'; import Braze, {ContentCard} from '@braze/react-native-sdk'; import FastImage, {Source} from 'react-native-fast-image'; import haptic from '../../../../../components/haptic-feedback/haptic'; @@ -10,41 +11,73 @@ import { } from '../../../../../utils/braze'; import {useAppDispatch, useUrlEventHandler} from '../../../../../utils/hooks'; import {AppEffects} from '../../../../../store/app'; -import LinkCard from '../cards/LinkCard'; +import {logManager} from '../../../../../managers/LogManager'; +import {BaseText} from '../../../../../components/styled/Text'; +import { + Black, + LightBlack, + Slate, + Slate30, + SlateDark, + White, +} from '../../../../../styles/colors'; import {getRouteParam} from '../../../../../store/app/app.effects'; import {Analytics} from '../../../../../store/analytics/analytics.effects'; -import {logManager} from '../../../../../managers/LogManager'; +import {TouchableOpacity} from '@components/base/TouchableOpacity'; interface OfferCardProps { contentCard: ContentCard; } -const OFFER_HEIGHT = 30; -const OFFER_WIDTH = 30; - const OfferCard: React.FC = props => { const {contentCard} = props; const {image, url, openURLInWebView} = contentCard; const dispatch = useAppDispatch(); const urlEventHandler = useUrlEventHandler(); + let title = ''; let description = ''; - let imageSource: Source | null = null; - - if ( - isCaptionedContentCard(contentCard) || - isClassicContentCard(contentCard) - ) { - description = contentCard.cardDescription; + let iconSource: Source | null = null; + let coverImageSource: Source | null = null; + let cardImageSource: Source | null = null; + + if (isCaptionedContentCard(contentCard)) { + title = contentCard.title || ''; + description = contentCard.cardDescription || ''; + } else if (isClassicContentCard(contentCard)) { + title = contentCard.title || ''; + description = contentCard.cardDescription || ''; } + const coverImage = contentCard.extras?.cover_image; + if (image) { - if (typeof image === 'string') { - imageSource = {uri: image}; - } else { - imageSource = image as any; + cardImageSource = + typeof image === 'string' ? {uri: image} : (image as Source); + } + + if (coverImage) { + coverImageSource = + typeof coverImage === 'string' + ? {uri: coverImage} + : (coverImage as Source); + } + + if (contentCard.extras?.icon_image) { + const icon = contentCard.extras.icon_image as string; + + if (icon) { + iconSource = {uri: icon}; } } + if (!iconSource) { + iconSource = cardImageSource; + } + + if (!title) { + title = contentCard.id; + } + const _onPress = async () => { if (!contentCard.id.startsWith('dev_')) { Braze.logContentCardClicked(contentCard.id); @@ -92,22 +125,107 @@ const OfferCard: React.FC = props => { }); return ( - - imageSource && ( - - ) - } - description={description} + + accessibilityRole="button"> + + {coverImageSource ? ( + + ) : ( + + )} + + + + {iconSource ? ( + + ) : ( + + )} + + + {title ? {title} : null} + {description ? ( + {description} + ) : null} + + + ); }; +const OfferWrapper = styled(TouchableOpacity)` + width: 250px; + border-radius: 12px; + border: 1px solid ${({theme: {dark}}) => (dark ? LightBlack : Slate30)}; + overflow: hidden; +`; + +const CoverImageContainer = styled.View` + width: 100%; + height: 100px; + overflow: hidden; +`; + +const CoverImage = styled(FastImage)` + width: 100%; + height: 120px; +`; + +const CoverImageFallback = styled.View` + flex: 1; +`; + +const OfferContent = styled.View` + padding: 18px 20px 16px; + background: ${({theme: {dark}}) => (dark ? '#111' : White)}; + border-top-width: 1px; + border-top-color: ${({theme: {dark}}) => (dark ? LightBlack : Slate30)}; +`; + +const IconWrapper = styled.View` + width: 40px; + height: 40px; + align-items: center; + justify-content: center; + margin-bottom: 8px; + margin-top: -39px; + overflow: hidden; + border: 1px solid ${({theme: {dark}}) => (dark ? LightBlack : Slate30)}; + border-radius: 40px; +`; + +const OfferIcon = styled(FastImage)` + width: 40px; + height: 40px; +`; + +const IconPlaceholder = styled.View` + width: 100%; + height: 100%; +`; + +const TextContainer = styled.View``; + +const OfferTitle = styled(BaseText)` + font-size: 13px; + font-weight: 600; + color: ${({theme: {dark}}) => (dark ? White : Black)}; + margin-bottom: 6px; +`; + +const OfferDescription = styled(BaseText)` + font-size: 12px; + font-weight: 400; + line-height: 15px; + color: ${({theme: {dark}}) => (dark ? Slate : SlateDark)}; +`; + export default OfferCard; diff --git a/src/navigation/tabs/home/components/offers/OffersCarousel.tsx b/src/navigation/tabs/home/components/offers/OffersCarousel.tsx index 4197cc7ae1..02db8cc868 100644 --- a/src/navigation/tabs/home/components/offers/OffersCarousel.tsx +++ b/src/navigation/tabs/home/components/offers/OffersCarousel.tsx @@ -1,17 +1,26 @@ import React, {memo} from 'react'; import {ContentCard} from '@braze/react-native-sdk'; import Carousel from 'react-native-reanimated-carousel'; -import {WIDTH} from '../../../../../components/styled/Containers'; +import {ScreenGutter, WIDTH} from '../../../../../components/styled/Containers'; import OfferCard from './OfferCard'; -import {CarouselItemContainer} from '../Styled'; +import styled from 'styled-components/native'; + +const horizontalPadding = Number(ScreenGutter.replace('px', '')); + +const CarouselItemContainer = styled.View` + padding-left: ${horizontalPadding}px; + padding-right: 0; + padding-top: 20px; +`; interface OfferSlidesProps { contentCards: ContentCard[]; } -const itemWidth = 230; +const itemWidth = 250; +const itemHeight = 280; const renderOffer = ({item}: {item: ContentCard}) => ( - + ); @@ -21,11 +30,15 @@ const OffersCarousel: React.FC = props => { return ( { + gestureChain.activeOffsetX([-10, 10]); + gestureChain.failOffsetY([-10, 10]); + }} loop={false} vertical={false} style={{width: WIDTH}} - width={itemWidth} - height={itemWidth / 2} + width={itemWidth + horizontalPadding * 2} + height={itemHeight} autoPlay={false} data={contentCards} scrollAnimationDuration={1000} diff --git a/src/navigation/tabs/settings/security/screens/Passkeys.tsx b/src/navigation/tabs/settings/security/screens/Passkeys.tsx index 6f4102edaf..e684ae377d 100644 --- a/src/navigation/tabs/settings/security/screens/Passkeys.tsx +++ b/src/navigation/tabs/settings/security/screens/Passkeys.tsx @@ -415,7 +415,7 @@ const PasskeyScreen: React.FC = () => { listPasskeyCredentials && listPasskeyCredentials.length === 0 && ( - {t('Setup a passkey')} + {t('Create a passkey')} diff --git a/src/navigation/tabs/settings/security/screens/SecurityHome.tsx b/src/navigation/tabs/settings/security/screens/SecurityHome.tsx index eeb8778d1f..88e75fac02 100644 --- a/src/navigation/tabs/settings/security/screens/SecurityHome.tsx +++ b/src/navigation/tabs/settings/security/screens/SecurityHome.tsx @@ -24,8 +24,13 @@ import FaceDarkModeImg from '../../../../../../assets/img/face-darkmode.svg'; import PinImg from '../../../../../../assets/img/pin.svg'; import PinDarkModeImg from '../../../../../../assets/img/pin-darkmode.svg'; import styled from 'styled-components/native'; -import {Midnight, SlateDark, White} from '../../../../../styles/colors'; -import {H4, Paragraph} from '@components/styled/Text'; +import { + LightBlue, + Midnight, + SlateDark, + White, +} from '../../../../../styles/colors'; +import {H4, Paragraph} from '../../../../../components/styled/Text'; import {useTranslation} from 'react-i18next'; import {sleep} from '../../../../../utils/helper-methods'; import {useLogger} from '../../../../../utils/hooks'; @@ -67,7 +72,7 @@ const ImgContainer = styled(TouchableOpacity)` justify-content: center; height: 65px; width: 65px; - background-color: ${({theme: {dark}}) => (dark ? Midnight : '#ECEFFD')}; + background-color: ${({theme: {dark}}) => (dark ? Midnight : LightBlue)}; border-radius: 50px; `; diff --git a/src/navigation/tabs/shop/bill/components/BillStatus.tsx b/src/navigation/tabs/shop/bill/components/BillStatus.tsx index e4aee3d477..02edc13dca 100644 --- a/src/navigation/tabs/shop/bill/components/BillStatus.tsx +++ b/src/navigation/tabs/shop/bill/components/BillStatus.tsx @@ -8,6 +8,7 @@ import { SlateDark, LightBlack, LinkBlue, + LightBlue, } from '../../../../../styles/colors'; import { BillPayAccount, @@ -92,7 +93,7 @@ const statusFields = { }, }, refunded: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, text: 'Refunded', darkTheme: { @@ -101,7 +102,7 @@ const statusFields = { }, }, refundCreated: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, text: 'Refunding', darkTheme: { @@ -110,7 +111,7 @@ const statusFields = { }, }, processing: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, text: 'Processing', darkTheme: { @@ -119,7 +120,7 @@ const statusFields = { }, }, pending: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, text: 'Processing', darkTheme: { @@ -128,7 +129,7 @@ const statusFields = { }, }, connecting: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, text: 'Connecting', darkTheme: { @@ -137,7 +138,7 @@ const statusFields = { }, }, default: { - backgroundColor: '#ECEFFD', + backgroundColor: LightBlue, color: Action, darkTheme: { backgroundColor: '#071A6A', diff --git a/src/navigation/tabs/shop/components/svg/ShopTabSvgs.tsx b/src/navigation/tabs/shop/components/svg/ShopTabSvgs.tsx index 6c0ff3241c..8f70af7421 100644 --- a/src/navigation/tabs/shop/components/svg/ShopTabSvgs.tsx +++ b/src/navigation/tabs/shop/components/svg/ShopTabSvgs.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {Theme} from '@react-navigation/native'; import Svg, {Path, Rect, Circle, G} from 'react-native-svg'; -import {Action, Midnight, White} from '../../../../../styles/colors'; +import {Action, LightBlue, Midnight, White} from '../../../../../styles/colors'; const getOptionIconFill = (theme: Theme) => { return theme.dark ? White : Action; @@ -84,7 +84,7 @@ export const CloseSvg = ({theme}: {theme: Theme}) => { }; export const AddSvg = ({theme}: {theme: Theme}) => { - const circleColor = theme.dark ? Midnight : '#ECEFFD'; + const circleColor = theme.dark ? Midnight : LightBlue; const plusColor = theme.dark ? White : Action; return ( diff --git a/src/navigation/wallet/components/DropdownOption.tsx b/src/navigation/wallet/components/DropdownOption.tsx index 09f9cf4761..ea7d36ba6e 100644 --- a/src/navigation/wallet/components/DropdownOption.tsx +++ b/src/navigation/wallet/components/DropdownOption.tsx @@ -8,7 +8,7 @@ import { Row, ScreenGutter, } from '../../../components/styled/Containers'; -import {H3, H5} from '../../../components/styled/Text'; +import {H5} from '../../../components/styled/Text'; import {CurrencyImage} from '../../../components/currency-image/CurrencyImage'; import { BalanceCode, @@ -51,8 +51,16 @@ const BaseText = styled(H5)` export const OptionName = styled(BaseText)``; -export const Balance = styled(BaseText)` - font-weight: 700; +export const Balance = styled(BaseText)<{hidden?: boolean}>` + font-size: ${({hidden}) => (hidden ? '20px' : '16px')}; + font-weight: 400; + ${({hidden}) => + hidden + ? ` + flex-shrink: 1; + margin-bottom: -9px; + ` + : ''} `; const DropdownOption = ({ @@ -118,7 +126,7 @@ const DropdownOption = ({ ) : ( -

****

+ )} diff --git a/src/navigation/wallet/components/RangeDateSelector.tsx b/src/navigation/wallet/components/RangeDateSelector.tsx index f51c90e411..408ab163f5 100644 --- a/src/navigation/wallet/components/RangeDateSelector.tsx +++ b/src/navigation/wallet/components/RangeDateSelector.tsx @@ -58,9 +58,9 @@ const LinkButton = styled(TouchableOpacity)<{isActive: string; label: string}>` const RangeDateSelector = ({onPress}: Props) => { const [activeOption, setActiveOption] = useState(DateRanges.Day); const updateOptions: Array<{label: string; dateRange: DateRanges}> = [ - {label: '1M', dateRange: DateRanges.Month}, - {label: '1W', dateRange: DateRanges.Week}, {label: '1D', dateRange: DateRanges.Day}, + {label: '1W', dateRange: DateRanges.Week}, + {label: '1M', dateRange: DateRanges.Month}, ]; const isActive = updateOptions.find( opt => opt.dateRange === activeOption, diff --git a/src/navigation/wallet/components/ReceiveAddress.tsx b/src/navigation/wallet/components/ReceiveAddress.tsx index bcdca8c487..3646ab0aa4 100644 --- a/src/navigation/wallet/components/ReceiveAddress.tsx +++ b/src/navigation/wallet/components/ReceiveAddress.tsx @@ -20,6 +20,7 @@ import { Action, Black, LightBlack, + LightBlue, NeutralSlate, White, } from '../../../styles/colors'; @@ -77,7 +78,7 @@ const AddressText = styled(BaseText)` `; const CopyImgContainer = styled.View` - border-right-color: ${({theme: {dark}}) => (dark ? '#46494E' : '#ECEFFD')}; + border-right-color: ${({theme: {dark}}) => (dark ? '#46494E' : LightBlue)}; border-right-width: 1px; padding-right: 10px; height: 25px; @@ -149,7 +150,7 @@ const WarningDescription = styled(BaseText)<{isToken?: boolean}>` padding-bottom: 20px; border-bottom-width: 1px; border-bottom-color: ${({theme: {dark}}) => - dark ? LightBlack : '#ECEFFD'}; + dark ? LightBlack : LightBlue}; `}; `; diff --git a/src/navigation/wallet/components/SendToPill.tsx b/src/navigation/wallet/components/SendToPill.tsx index fb8e6f20bb..189345b2b8 100644 --- a/src/navigation/wallet/components/SendToPill.tsx +++ b/src/navigation/wallet/components/SendToPill.tsx @@ -1,6 +1,11 @@ import React, {ReactElement, useState} from 'react'; import styled from 'styled-components/native'; -import {BitPay, LightBlack, NeutralSlate} from '../../../styles/colors'; +import { + BitPay, + LightBlack, + LightBlue, + NeutralSlate, +} from '../../../styles/colors'; import {H7} from '../../../components/styled/Text'; import ArrowDownSvg from '../../../../assets/img/chevron-down.svg'; import ArrowUpSvg from '../../../../assets/img/chevron-up.svg'; @@ -21,7 +26,7 @@ interface StyleProps { export const PillContainer = styled.Pressable` background-color: ${({theme: {dark}, accent}) => - dark ? LightBlack : accent === 'action' ? '#ECEFFD' : NeutralSlate}; + dark ? LightBlack : accent === 'action' ? LightBlue : NeutralSlate}; flex-direction: row; border-radius: 40px; align-items: center; diff --git a/src/navigation/wallet/components/SendingToERC20Warning.tsx b/src/navigation/wallet/components/SendingToERC20Warning.tsx index 875dd9d349..1a6b913aad 100644 --- a/src/navigation/wallet/components/SendingToERC20Warning.tsx +++ b/src/navigation/wallet/components/SendingToERC20Warning.tsx @@ -10,6 +10,7 @@ import { Action, Black, LightBlack, + LightBlue, SlateDark, White, } from '../../../styles/colors'; @@ -109,7 +110,7 @@ const SendingToDescription = styled(BaseText)` color: ${({theme: {dark}}) => (dark ? White : Black)}; margin: 10px 0px 28px 0px; border-bottom-width: 1px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; line-height: 24px; `; diff --git a/src/navigation/wallet/components/WalletIcons.tsx b/src/navigation/wallet/components/WalletIcons.tsx index 39f291516a..92fe817a17 100644 --- a/src/navigation/wallet/components/WalletIcons.tsx +++ b/src/navigation/wallet/components/WalletIcons.tsx @@ -5,6 +5,7 @@ import { Action, Black, LightBlack, + LightBlue, LinkBlue, Midnight, NeutralSlate, @@ -177,7 +178,7 @@ const HomeSettings = () => { rx="17.5" ry="17.5" transform="rotate(180 17.5 17.5)" - fill={theme.dark ? Midnight : '#ECEFFD'} + fill={theme.dark ? Midnight : LightBlue} /> = ({ ), cta: () => setShowMultisigOptions(true), }, + { + id: 'coinbase', + title: t('Coinbase Account'), + description: t('Connect your Coinbase account'), + cta: () => navigation.navigate('CoinbaseRoot'), + }, ]; const showErrorModal = (e: string) => { diff --git a/src/navigation/wallet/screens/GlobalSelect.tsx b/src/navigation/wallet/screens/GlobalSelect.tsx index 7b670d4a0a..8637626385 100644 --- a/src/navigation/wallet/screens/GlobalSelect.tsx +++ b/src/navigation/wallet/screens/GlobalSelect.tsx @@ -37,6 +37,7 @@ import { Action, Black, LightBlack, + LightBlue, LinkBlue, SlateDark, White, @@ -183,7 +184,7 @@ export const WalletSelectMenuHeaderContainer = styled.View (currency ? 14 : 0)}px; padding-left: 5px; justify-content: ${({currency}) => (currency ? 'flex-start' : 'center')}; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; border-bottom-width: ${({currency}) => (currency ? 1 : 0)}px; `; @@ -212,7 +213,7 @@ const SearchComponentContainer = styled.View` const TitleNameContainer = styled.View` flex-direction: row; align-items: center; - border-bottom-color: ${({theme: {dark}}) => (dark ? SlateDark : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? SlateDark : LightBlue)}; border-bottom-width: 1px; margin-top: 20px; padding-bottom: 10px; diff --git a/src/navigation/wallet/screens/KeyOverview.tsx b/src/navigation/wallet/screens/KeyOverview.tsx index 431c67ea1a..7016b73732 100644 --- a/src/navigation/wallet/screens/KeyOverview.tsx +++ b/src/navigation/wallet/screens/KeyOverview.tsx @@ -49,6 +49,7 @@ import {KeyMethods, Status, Wallet} from '../../../store/wallet/wallet.models'; import { LightBlack, NeutralSlate, + Slate, SlateDark, White, } from '../../../styles/colors'; @@ -59,6 +60,7 @@ import { sleep, fixWalletAddresses, getEvmGasWallets, + calculatePercentageDifference, } from '../../../utils/helper-methods'; import { BalanceUpdateError, @@ -113,6 +115,7 @@ import {BitpaySupportedTokenOptsByAddress} from '../../../constants/tokens'; import {BWCErrorMessage} from '../../../constants/BWCError'; import ArchaxFooter from '../../../components/archax/archax-footer'; import {useOngoingProcess, useTokenContext} from '../../../contexts'; +import Percentage from '../../../components/percentage/Percentage'; LogBox.ignoreLogs([ 'Non-serializable values were found in the navigation state', @@ -157,6 +160,10 @@ const BalanceContainer = styled.View` align-items: center; `; +const PercentageWrapper = styled.View` + align-self: center; +`; + const WalletListHeader = styled.View` padding: 10px; margin-top: 10px; @@ -337,8 +344,18 @@ const KeyOverview = () => { updateStatusForKey(false); }, []); - const {wallets = [], totalBalance} = - useAppSelector(({WALLET}) => WALLET.keys[id]) || {}; + const { + wallets = [], + totalBalance = 0, + totalBalanceLastDay, + } = useAppSelector(({WALLET}) => WALLET.keys[id]) || {}; + + const percentageDifference = useMemo(() => { + if (!totalBalanceLastDay) { + return null; + } + return calculatePercentageDifference(totalBalance, totalBalanceLastDay); + }, [totalBalance, totalBalanceLastDay]); const memorizedAccountList = useMemo(() => { return buildAccountList(key, defaultAltCurrency.isoCode, rates, dispatch, { @@ -730,11 +747,22 @@ const KeyOverview = () => { dispatch(toggleHideAllBalances()); }}> {!hideAllBalances ? ( - - {formatFiatAmount(totalBalance, defaultAltCurrency.isoCode, { - currencyDisplay: 'symbol', - })} - + <> + + {formatFiatAmount(totalBalance, defaultAltCurrency.isoCode, { + currencyDisplay: 'symbol', + })} + + {percentageDifference ? ( + + + + ) : null} + ) : (

****

)} diff --git a/src/navigation/wallet/screens/PriceCharts.tsx b/src/navigation/wallet/screens/PriceCharts.tsx index 6f086077d6..84801d2380 100644 --- a/src/navigation/wallet/screens/PriceCharts.tsx +++ b/src/navigation/wallet/screens/PriceCharts.tsx @@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useLayoutEffect, + useMemo, useRef, useState, } from 'react'; @@ -11,15 +12,10 @@ import SkeletonPlaceholder from 'react-native-skeleton-placeholder'; import styled, {useTheme} from 'styled-components/native'; import Button from '../../../components/button/Button'; import {CtaContainer, WIDTH} from '../../../components/styled/Containers'; -import { - Badge, - BaseText, - H2, - H5, - HeaderTitle, -} from '../../../components/styled/Text'; +import {BaseText, H2, H5, HeaderTitle} from '../../../components/styled/Text'; import { SlateDark, + Slate30, White, Black, LuckySevens, @@ -38,14 +34,14 @@ import {ExchangeRateItemProps} from '../../tabs/home/components/exchange-rates/E import {fetchHistoricalRates} from '../../../store/wallet/effects'; import {useAppDispatch, useAppSelector, useLogger} from '../../../utils/hooks'; import {showBottomNotificationModal} from '../../../store/app/app.actions'; +import Percentage from '../../../components/percentage/Percentage'; import {BottomNotificationConfig} from '../../../components/modal/bottom-notification/BottomNotification'; import {CustomErrorMessage} from '../components/ErrorMessages'; import {BWCErrorMessage} from '../../../constants/BWCError'; import {DateRanges, Rate} from '../../../store/rate/rate.models'; -import GainArrow from '../../../../assets/img/home/exchange-rates/increment-arrow.svg'; -import LossArrow from '../../../../assets/img/home/exchange-rates/decrement-arrow.svg'; +import GainArrow from '../../../components/icons/trend-arrow/IncrementArrow'; +import LossArrow from '../../../components/icons/trend-arrow/DecrementArrow'; import NeutralArrow from '../../../../assets/img/home/exchange-rates/flat-arrow.svg'; -import {CurrencyImage} from '../../../components/currency-image/CurrencyImage'; import {useRequireKeyAndWalletRedirect} from '../../../utils/hooks/useRequireKeyAndWalletRedirect'; import {useTranslation} from 'react-i18next'; import {Analytics} from '../../../store/analytics/analytics.effects'; @@ -108,13 +104,15 @@ const SafeAreaView = styled.SafeAreaView` const HeaderContainer = styled.View` flex: 0 0 auto; - margin-left: 16px; - margin-top: 40px; + margin-top: 15px; + align-items: center; + width: 100%; + padding: 0 16px; `; const PriceChartContainer = styled.View` flex: 1 1 auto; - margin-top: 0; + margin-top: 16px; `; const RangeDateSelectorContainer = styled.View` @@ -128,6 +126,16 @@ const CurrencyAverageText = styled(H5)` const RowContainer = styled.View` align-items: center; flex-direction: row; + justify-content: center; +`; + +const AbbreviationLabel = styled(H5)` + font-size: 13px; + color: ${({theme}: {theme: {dark: boolean}}) => + theme.dark ? Slate30 : SlateDark}; + text-transform: uppercase; + font-weight: 400; + margin-bottom: 2px; `; const LoadingContainer = styled.View` @@ -198,16 +206,8 @@ const getFormattedData = ( }; }; -const PriceChartHeader = ({currencyName, currencyAbbreviation, img}: any) => { - return ( - - - - {currencyName} - - {currencyAbbreviation.toUpperCase()} - - ); +const PriceChartHeader = ({currencyName}: {currencyName: string}) => { + return {currencyName}; }; export const AxisLabel = ({ @@ -308,15 +308,9 @@ const PriceCharts = () => { navigation.setOptions({ gestureEnabled: false, // eslint-disable-next-line react/no-unstable-nested-components - headerTitle: () => ( - - ), + headerTitle: () => , }); - }, [navigation, currencyName, currencyAbbreviation, img]); + }, [navigation, currencyName]); const [loading, setLoading] = useState(true); const [showRageDateSelector, setShowRageDateSelector] = useState(true); @@ -325,6 +319,9 @@ const PriceCharts = () => { const [cachedRates, setCachedRates] = useState(defaultCachedRates); const [chartRowHeight, setChartRowHeight] = useState(-1); const gestureStarted = useRef(false); + const [selectedDateRange, setSelectedDateRange] = useState( + DateRanges.Day, + ); const [selectedPoint, setSelectedPoint] = useState( undefined as | { @@ -335,6 +332,14 @@ const PriceCharts = () => { } | undefined, ); + const rangeLabels = useMemo( + () => ({ + [DateRanges.Day]: t('Last Day'), + [DateRanges.Week]: t('Past Week'), + [DateRanges.Month]: t('Past Month'), + }), + [t], + ); const showErrorMessage = useCallback( async (msg: BottomNotificationConfig) => { @@ -404,6 +409,7 @@ const PriceCharts = () => { }; const redrawChart = async (dateRange: DateRanges) => { + setSelectedDateRange(dateRange); if (cachedRates[dateRange]?.data?.length) { logger.debug('[PriceCharts] Loading cached rates'); setNewDisplayData(cachedRates[dateRange]); @@ -540,19 +546,30 @@ const PriceCharts = () => { - - - + + + ) : ( <> + {currencyAbbreviation ? ( + {currencyAbbreviation} + ) : null} {currentPrice ? (

{formatFiatAmount( @@ -565,35 +582,38 @@ const PriceCharts = () => { )}

) : null} - - {showLossGainOrNeutralArrow(getPercentChange())} - - {selectedPoint?.priceChange ?? displayData.priceChange - ? formatFiatAmount( - selectedPoint?.priceChange ?? - displayData.priceChange ?? - 0, - defaultAltCurrency.isoCode, - {customPrecision: 'minimal', currencyAbbreviation}, - ) - : ''} - <> - {percentChange ? ( - <> - {!loading ? ' (' : ''} - {Math.abs(getPercentChange())}%{!loading ? ')' : ''} - - ) : null} - - - - - - {selectedPoint - ? moment(selectedPoint.date).format('MMM DD, YYYY hh:mm a') - : ' '} - - + {(() => { + const percentChangeValue = getPercentChange(); + const hasPercentChange = + typeof percentChangeValue === 'number' && + !Number.isNaN(percentChangeValue); + const rawPriceChange = + selectedPoint?.priceChange ?? displayData.priceChange; + const hasPriceChange = Boolean(rawPriceChange); + const formattedPriceChange = hasPriceChange + ? formatFiatAmount( + rawPriceChange ?? 0, + defaultAltCurrency.isoCode, + {customPrecision: 'minimal', currencyAbbreviation}, + ) + : undefined; + + if (!hasPercentChange && !hasPriceChange) { + return null; + } + + return ( + + + + ); + })()} )} diff --git a/src/navigation/wallet/screens/send/SendTo.tsx b/src/navigation/wallet/screens/send/SendTo.tsx index 679edf07a0..ded35fe656 100644 --- a/src/navigation/wallet/screens/send/SendTo.tsx +++ b/src/navigation/wallet/screens/send/SendTo.tsx @@ -17,6 +17,7 @@ import SendLightSvg from '../../../../../assets/img/send-icon-light.svg'; import ContactsSvg from '../../../../../assets/img/tab-icons/contacts.svg'; import { LightBlack, + LightBlue, Midnight, NeutralSlate, SlateDark, @@ -118,7 +119,7 @@ export const ContactTitleContainer = styled.View` flex-direction: row; align-items: center; padding-bottom: 10px; - border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : '#ECEFFD')}; + border-bottom-color: ${({theme: {dark}}) => (dark ? LightBlack : LightBlue)}; border-bottom-width: 1px; margin-bottom: 10px; `; diff --git a/src/store/app/app.actions.ts b/src/store/app/app.actions.ts index 769e7a900e..e4f01ab112 100644 --- a/src/store/app/app.actions.ts +++ b/src/store/app/app.actions.ts @@ -401,3 +401,8 @@ export const showArchaxBanner = (payload: boolean): AppActionType => ({ type: AppActionTypes.SHOW_ARCHAX_BANNER, payload, }); + +export const dismissMarketingContentCard = (cardId: string): AppActionType => ({ + type: AppActionTypes.DISMISS_MARKETING_CONTENT_CARD, + payload: cardId, +}); diff --git a/src/store/app/app.reducer.ts b/src/store/app/app.reducer.ts index 7d4abc17d9..19a317f2b6 100644 --- a/src/store/app/app.reducer.ts +++ b/src/store/app/app.reducer.ts @@ -164,6 +164,7 @@ export interface AppState { inAppBrowserOpen: boolean; tokensDataLoaded: boolean; showArchaxBanner: boolean; + dismissedMarketingCardIds: string[]; } const initialState: AppState = { @@ -260,6 +261,7 @@ const initialState: AppState = { inAppBrowserOpen: false, tokensDataLoaded: false, showArchaxBanner: false, + dismissedMarketingCardIds: [], }; export const appReducer = ( @@ -773,6 +775,23 @@ export const appReducer = ( showArchaxBanner: action.payload, }; + case AppActionTypes.DISMISS_MARKETING_CONTENT_CARD: { + const cardId = action.payload; + + if (!cardId) { + return state; + } + + if (state.dismissedMarketingCardIds.includes(cardId)) { + return state; + } + + return { + ...state, + dismissedMarketingCardIds: [...state.dismissedMarketingCardIds, cardId], + }; + } + default: return state; } diff --git a/src/store/app/app.selectors.ts b/src/store/app/app.selectors.ts index 75ec067ddf..fd589bd888 100644 --- a/src/store/app/app.selectors.ts +++ b/src/store/app/app.selectors.ts @@ -4,6 +4,7 @@ import {AppSelector} from '..'; import { isCardOffer, isDoMore, + isMarketingCarousel, isQuickLink, isShopWithCrypto, } from '../../utils/braze'; @@ -11,6 +12,9 @@ import { export const selectBrazeContentCards: AppSelector = ({APP}) => APP.brazeContentCards; +export const selectDismissedMarketingCardIds: AppSelector = ({APP}) => + APP.dismissedMarketingCardIds; + export const selectBrazeShopWithCrypto = createSelector( [selectBrazeContentCards], contentCards => contentCards.filter(isShopWithCrypto), @@ -31,6 +35,16 @@ export const selectBrazeCardOffers = createSelector( contentCards => contentCards.filter(isCardOffer), ); +export const selectBrazeMarketingCarousel = createSelector( + [selectBrazeContentCards, selectDismissedMarketingCardIds], + (contentCards, dismissedIds) => + contentCards.filter( + card => + isMarketingCarousel(card) && + (!card.id || !dismissedIds.includes(card.id)), + ), +); + export const selectNotificationsAccepted: AppSelector = ({APP}) => APP.notificationsAccepted; diff --git a/src/store/app/app.types.ts b/src/store/app/app.types.ts index 7d6f75d29e..e9ac6113fb 100644 --- a/src/store/app/app.types.ts +++ b/src/store/app/app.types.ts @@ -94,6 +94,7 @@ export enum AppActionTypes { IMPORT_LEDGER_MODAL_TOGGLED = 'APP/IMPORT_LEDGER_MODAL_TOGGLED', IN_APP_BROWSER_OPEN = 'APP/IN_APP_BROWSER_OPEN', SHOW_ARCHAX_BANNER = 'APP/SHOW_ARCHAX_BANNER', + DISMISS_MARKETING_CONTENT_CARD = 'APP/DISMISS_MARKETING_CONTENT_CARD', } interface ImportLedgerModalToggled { @@ -426,6 +427,11 @@ interface ShowArchaxBanner { payload: boolean; } +interface DismissMarketingContentCard { + type: typeof AppActionTypes.DISMISS_MARKETING_CONTENT_CARD; + payload: string; +} + export type AppActionType = | NetworkChanged | SuccessAppInit @@ -498,4 +504,5 @@ export type AppActionType = | ClearChainSelectorModalOptions | ShowWalletConnectStartModal | DismissWalletConnectStartModal - | ShowArchaxBanner; + | ShowArchaxBanner + | DismissMarketingContentCard; diff --git a/src/styles/colors.ts b/src/styles/colors.ts index 00dab99372..8a4643abe5 100644 --- a/src/styles/colors.ts +++ b/src/styles/colors.ts @@ -46,3 +46,4 @@ export const DisabledTextDark = '#656565'; export const NotificationPrimary = '#1F3AB3'; export const ProgressBlue = '#4F6EF7'; export const LinkBlue = '#4989FF'; +export const LightBlue = '#ECEFFD'; diff --git a/src/themes/bitpay.ts b/src/themes/bitpay.ts index e41b2b53db..47d7bbf301 100644 --- a/src/themes/bitpay.ts +++ b/src/themes/bitpay.ts @@ -5,7 +5,6 @@ import { BitPay, Black, LinkBlue, - OledBlack, SlateDark, White, } from '../styles/colors'; @@ -41,7 +40,7 @@ export const BitPayDarkTheme: BitPayTheme = { ...DarkTheme.colors, primary: BitPay, - background: OledBlack, + background: Black, card: DarkTheme.colors.card, text: White, link: LinkBlue, diff --git a/src/utils/braze.ts b/src/utils/braze.ts index 18099b30d1..c9ce480860 100644 --- a/src/utils/braze.ts +++ b/src/utils/braze.ts @@ -60,3 +60,7 @@ export const isDoMore = (contentCard: ContentCard) => { export const isCardOffer = (contentCard: ContentCard) => { return contentCard.extras.feed_type === 'card_promotion'; }; + +export const isMarketingCarousel = (contentCard: ContentCard) => { + return contentCard.extras.feed_type === 'marketing_carousel'; +}; diff --git a/src/utils/helper-methods.ts b/src/utils/helper-methods.ts index c8ec66cbb1..b9b530fe69 100644 --- a/src/utils/helper-methods.ts +++ b/src/utils/helper-methods.ts @@ -71,6 +71,30 @@ export const sleep = (duration: number) => export const titleCasing = (str: string) => `${str.charAt(0).toUpperCase()}${str.slice(1)}`; +export const changeOpacity = (color: string, targetOpacity: number) => { + const hex = color.replace('#', ''); + + const normalizedHex = + hex.length === 3 + ? hex + .split('') + .map(char => `${char}${char}`) + .join('') + : hex; + + if (normalizedHex.length !== 6) { + return color; + } + + const r = parseInt(normalizedHex.substring(0, 2), 16); + const g = parseInt(normalizedHex.substring(2, 4), 16); + const b = parseInt(normalizedHex.substring(4, 6), 16); + + const opacity = Math.max(0, Math.min(targetOpacity, 1)); + + return `rgba(${r}, ${g}, ${b}, ${opacity})`; +}; + export const parsePath = (path: string) => { return { purpose: path.split('/')[1],