diff --git a/.changeset/eighty-pants-give.md b/.changeset/eighty-pants-give.md new file mode 100644 index 000000000000..8bcf6ed2a23d --- /dev/null +++ b/.changeset/eighty-pants-give.md @@ -0,0 +1,5 @@ +--- +"live-mobile": minor +--- + +Update old reborn banner diff --git a/.changeset/loud-mangos-enjoy.md b/.changeset/loud-mangos-enjoy.md new file mode 100644 index 000000000000..cfbb538b3ada --- /dev/null +++ b/.changeset/loud-mangos-enjoy.md @@ -0,0 +1,5 @@ +--- +"@ledgerhq/react-ui": minor +--- + +Add new portfolio content card component diff --git a/.changeset/new-ties-fly.md b/.changeset/new-ties-fly.md new file mode 100644 index 000000000000..43f0b266158d --- /dev/null +++ b/.changeset/new-ties-fly.md @@ -0,0 +1,6 @@ +--- +"ledger-live-desktop": minor +"live-mobile": minor +--- + +change label stake for UK to Yield, to Earn for others diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx index c8a0d0745c4a..96eb19fecd91 100644 --- a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.tsx @@ -35,6 +35,7 @@ import TopGradient from "./TopGradient"; import Hide from "./Hide"; import { track } from "~/renderer/analytics/segment"; import { useAccountPath } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Location = Parameters>[0]; @@ -238,6 +239,7 @@ const MainSideBar = () => { const location = useLocation(); const dispatch = useDispatch(); const { t } = useTranslation(); + const earnLabel = useGetStakeLabelLocaleBased(); const manifest = useRemoteLiveAppManifest(BAANX_APP_ID); const isCardDisabled = !manifest; @@ -456,7 +458,7 @@ const MainSideBar = () => { /> { - const { t } = useTranslation(); const dispatch = useDispatch(); const balance = account.balance; const unit = useAccountUnit(account); const minRewardsBalance = 10 ** unit.magnitude; + const label = useGetStakeLabelLocaleBased(); + const onClick = useCallback(() => { dispatch(openModal("MODAL_ALGORAND_EARN_REWARDS_INFO", { account })); }, [dispatch, account]); @@ -27,7 +28,7 @@ const AccountHeaderActions: AlgorandFamily["accountHeaderManageActions"] = ({ key: "algorand", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts index fe1939152ef4..4d19171600e3 100644 --- a/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/bitcoin/AccountHeaderManageActions.ts @@ -1,6 +1,5 @@ import { getMainAccount } from "@ledgerhq/live-common/account/index"; -import { useTranslation } from "react-i18next"; import { useHistory } from "react-router"; import { track } from "~/renderer/analytics/segment"; import { stakeDefaultTrack } from "~/renderer/screens/stake/constants"; @@ -8,6 +7,7 @@ import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; import { TokenAccount } from "@ledgerhq/types-live"; import IconCoins from "~/renderer/icons/Coins"; import useFeature from "@ledgerhq/live-common/featureFlags/useFeature"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { account: BitcoinAccount | TokenAccount; @@ -20,7 +20,7 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { const stakeProgramsEnabled = stakeProgramsFeatureFlag?.enabled ?? false; const availableOnStake = stakeProgramsEnabled && listFlag.includes("bitcoin"); const history = useHistory(); - const { t } = useTranslation(); + const label = useGetStakeLabelLocaleBased(); const mainAccount = getMainAccount(account, parentAccount); const { bitcoinResources, @@ -53,7 +53,7 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { { key: "Stake", icon: IconCoins, - label: t("accounts.contextMenu.yield"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/cardano/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/cardano/AccountHeaderManageActions.ts index 0fd04af2e491..679d8494a8c4 100644 --- a/apps/ledger-live-desktop/src/renderer/families/cardano/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/cardano/AccountHeaderManageActions.ts @@ -8,6 +8,7 @@ import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { account: AccountLike; @@ -18,6 +19,8 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { const { t } = useTranslation(); const dispatch = useDispatch(); const mainAccount = getMainAccount(account, parentAccount); + const label = useGetStakeLabelLocaleBased(); + const { cardanoResources } = mainAccount as CardanoAccount; invariant(cardanoResources, "cardano account expected"); @@ -47,7 +50,7 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { onClick: onClick, icon: IconCoins, disabled: disableStakeButton, - label: t("account.stake"), + label, tooltip: disabledLabel, accountActionsTestId: "stake-button", }, diff --git a/apps/ledger-live-desktop/src/renderer/families/celo/AccountHeaderManageActions/AccountHeaderManageActions.tsx b/apps/ledger-live-desktop/src/renderer/families/celo/AccountHeaderManageActions/AccountHeaderManageActions.tsx index 798b8d47df28..0b7a8de169f3 100644 --- a/apps/ledger-live-desktop/src/renderer/families/celo/AccountHeaderManageActions/AccountHeaderManageActions.tsx +++ b/apps/ledger-live-desktop/src/renderer/families/celo/AccountHeaderManageActions/AccountHeaderManageActions.tsx @@ -8,6 +8,7 @@ import { openModal } from "~/renderer/actions/modals"; import { IconType } from "../../types"; import { CeloFamily } from "../types"; import Icon from "./Icon"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const AccountHeaderManageActions: CeloFamily["accountHeaderManageActions"] = ({ account, @@ -16,6 +17,7 @@ const AccountHeaderManageActions: CeloFamily["accountHeaderManageActions"] = ({ }) => { const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); const isRegistrationPending = isAccountRegistrationPending(account as CeloAccount); const onClick = useCallback(() => { if (isAccountEmpty(account)) { @@ -45,7 +47,7 @@ const AccountHeaderManageActions: CeloFamily["accountHeaderManageActions"] = ({ onClick: onClick, icon: (props: IconType) => , disabled: isRegistrationPending, - label: t("account.stake"), + label, tooltip: disabledLabel, event: "button_clicked2", eventProperties: { diff --git a/apps/ledger-live-desktop/src/renderer/families/cosmos/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/cosmos/AccountHeaderManageActions.ts index 48b48cb31e3b..7030460cc6a7 100644 --- a/apps/ledger-live-desktop/src/renderer/families/cosmos/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/cosmos/AccountHeaderManageActions.ts @@ -6,6 +6,7 @@ import { useCallback } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; import IconCoins from "~/renderer/icons/Coins"; type Props = { @@ -17,6 +18,8 @@ type Props = { const AccountHeaderActions = ({ account, parentAccount, source }: Props) => { const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); + const mainAccount = getMainAccount(account, parentAccount); const { cosmosResources } = mainAccount; const earnRewardEnabled = canDelegate(mainAccount); @@ -52,7 +55,7 @@ const AccountHeaderActions = ({ account, parentAccount, source }: Props) => { key: "Stake", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, tooltip: disabledLabel, event: "button_clicked2", eventProperties: { diff --git a/apps/ledger-live-desktop/src/renderer/families/elrond/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/elrond/AccountHeaderManageActions.ts index 22260c6cfda6..bbddc9b1d086 100644 --- a/apps/ledger-live-desktop/src/renderer/families/elrond/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/elrond/AccountHeaderManageActions.ts @@ -1,5 +1,4 @@ import { useCallback, useMemo } from "react"; -import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { hasMinimumDelegableBalance } from "@ledgerhq/live-common/families/elrond/helpers"; import { useElrondRandomizedValidators } from "@ledgerhq/live-common/families/elrond/react"; @@ -7,6 +6,7 @@ import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; import { SubAccount } from "@ledgerhq/types-live"; import { ElrondAccount } from "@ledgerhq/live-common/families/elrond/types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const AccountHeaderManageActions = (props: { account: ElrondAccount | SubAccount; @@ -14,8 +14,8 @@ const AccountHeaderManageActions = (props: { source?: string; }) => { const { account, source } = props; - const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); const validators = useElrondRandomizedValidators(); const earnRewardEnabled = useMemo( @@ -57,7 +57,7 @@ const AccountHeaderManageActions = (props: { key: "Stake", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/evm/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/evm/AccountHeaderManageActions.ts index 9a33815c923b..79442f2ff454 100644 --- a/apps/ledger-live-desktop/src/renderer/families/evm/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/evm/AccountHeaderManageActions.ts @@ -1,10 +1,10 @@ import { Account, AccountLike } from "@ledgerhq/types-live"; import { useCallback } from "react"; import { useDispatch } from "react-redux"; -import { useTranslation } from "react-i18next"; import IconCoins from "~/renderer/icons/Coins"; import { openModal } from "~/renderer/actions/modals"; import { isAccountEmpty } from "@ledgerhq/live-common/account/index"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { account: AccountLike; @@ -12,9 +12,8 @@ type Props = { }; const AccountHeaderActions = ({ account, parentAccount }: Props) => { - const { t } = useTranslation(); const dispatch = useDispatch(); - + const label = useGetStakeLabelLocaleBased(); const isEthereumAccount = account.type === "Account" && account.currency.id === "ethereum"; const onClickStake = useCallback(() => { @@ -44,9 +43,7 @@ const AccountHeaderActions = ({ account, parentAccount }: Props) => { button: "stake", }, icon: IconCoins, - label: t("account.stake", { - currency: account?.currency?.name, - }), + label, accountActionsTestId: "stake-button", }, ]; diff --git a/apps/ledger-live-desktop/src/renderer/families/near/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/near/AccountHeaderManageActions.ts index 61d88c4298bd..0f3563f1be25 100644 --- a/apps/ledger-live-desktop/src/renderer/families/near/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/near/AccountHeaderManageActions.ts @@ -1,19 +1,19 @@ import { getMainAccount } from "@ledgerhq/live-common/account/helpers"; import { canStake } from "@ledgerhq/live-common/families/near/logic"; import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; import { NearFamily } from "./types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const AccountHeaderActions: NearFamily["accountHeaderManageActions"] = ({ account, parentAccount, source, }) => { - const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); const mainAccount = getMainAccount(account, parentAccount); const { nearResources } = mainAccount; const stakingEnabled = canStake(mainAccount); @@ -52,7 +52,7 @@ const AccountHeaderActions: NearFamily["accountHeaderManageActions"] = ({ key: "Stake", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/polkadot/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/polkadot/AccountHeaderManageActions.ts index a1b4985a4fb6..d24119c8f00d 100644 --- a/apps/ledger-live-desktop/src/renderer/families/polkadot/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/polkadot/AccountHeaderManageActions.ts @@ -11,6 +11,7 @@ import { isAccountEmpty, getMainAccount } from "@ledgerhq/live-common/account/in import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; import { PolkadotAccount } from "@ledgerhq/live-common/families/polkadot/types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { account: PolkadotAccount | SubAccount; @@ -20,6 +21,7 @@ type Props = { const AccountHeaderManageActions = ({ account, parentAccount, source = "Account Page" }: Props) => { const { t } = useTranslation(); + const label = useGetStakeLabelLocaleBased(); const dispatch = useDispatch(); const mainAccount = getMainAccount(account, parentAccount); const { polkadotResources } = mainAccount; @@ -73,7 +75,7 @@ const AccountHeaderManageActions = ({ account, parentAccount, source = "Account onClick: onClick, icon: IconCoins, disabled: !manageEnabled, - label: t("account.stake"), + label, tooltip: disabledLabel, event: "button_clicked2", eventProperties: { diff --git a/apps/ledger-live-desktop/src/renderer/families/solana/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/solana/AccountHeaderManageActions.ts index 62ad22b1939d..248fa4eb2483 100644 --- a/apps/ledger-live-desktop/src/renderer/families/solana/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/solana/AccountHeaderManageActions.ts @@ -1,14 +1,14 @@ import { getMainAccount, isAccountEmpty } from "@ledgerhq/live-common/account/index"; import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; import { SolanaFamily } from "./types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const AccountHeaderActions: SolanaFamily["accountHeaderManageActions"] = ({ account, source }) => { - const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); const mainAccount = getMainAccount(account); const { solanaResources } = mainAccount; @@ -39,7 +39,7 @@ const AccountHeaderActions: SolanaFamily["accountHeaderManageActions"] = ({ acco key: "Stake", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/tezos/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/tezos/AccountHeaderManageActions.ts index fd331764cde2..d7a9d32057fe 100644 --- a/apps/ledger-live-desktop/src/renderer/families/tezos/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/tezos/AccountHeaderManageActions.ts @@ -1,20 +1,21 @@ import { isAccountEmpty } from "@ledgerhq/live-common/account/index"; import { useDelegation } from "@ledgerhq/live-common/families/tezos/react"; import { useCallback } from "react"; -import { useTranslation } from "react-i18next"; import { useDispatch } from "react-redux"; import { openModal } from "~/renderer/actions/modals"; import IconCoins from "~/renderer/icons/Coins"; import { TezosFamily } from "./types"; import { StepId } from "./DelegateFlowModal/types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const AccountHeaderManageActions: TezosFamily["accountHeaderManageActions"] = ({ account, parentAccount, source, }) => { - const { t } = useTranslation(); const dispatch = useDispatch(); + const label = useGetStakeLabelLocaleBased(); + const delegation = useDelegation(account); const onClick = useCallback(() => { @@ -50,7 +51,7 @@ const AccountHeaderManageActions: TezosFamily["accountHeaderManageActions"] = ({ key: "Stake", onClick: onClick, icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/families/tron/AccountHeaderManageActions.ts b/apps/ledger-live-desktop/src/renderer/families/tron/AccountHeaderManageActions.ts index 0bd8fde28b3e..91b7f8ad17ac 100644 --- a/apps/ledger-live-desktop/src/renderer/families/tron/AccountHeaderManageActions.ts +++ b/apps/ledger-live-desktop/src/renderer/families/tron/AccountHeaderManageActions.ts @@ -1,12 +1,12 @@ import { getMainAccount } from "@ledgerhq/live-common/account/index"; import { TronAccount } from "@ledgerhq/live-common/families/tron/types"; import { SubAccount } from "@ledgerhq/types-live"; -import { useTranslation } from "react-i18next"; import IconCoins from "~/renderer/icons/Coins"; import { ManageAction } from "../types"; import { useHistory } from "react-router"; import { track } from "~/renderer/analytics/segment"; import { stakeDefaultTrack } from "~/renderer/screens/stake/constants"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { account: TronAccount | SubAccount; @@ -18,7 +18,7 @@ const AccountHeaderManageActions = ({ parentAccount, }: Props): ManageAction[] | null | undefined => { const history = useHistory(); - const { t } = useTranslation(); + const label = useGetStakeLabelLocaleBased(); const mainAccount = getMainAccount(account, parentAccount); const { tronResources } = mainAccount; if (!tronResources || parentAccount) return null; @@ -48,7 +48,7 @@ const AccountHeaderManageActions = ({ { key: "Stake", icon: IconCoins, - label: t("account.stake"), + label, event: "button_clicked2", eventProperties: { button: "stake", diff --git a/apps/ledger-live-desktop/src/renderer/hooks/useGetStakeLabelLocaleBased.ts b/apps/ledger-live-desktop/src/renderer/hooks/useGetStakeLabelLocaleBased.ts new file mode 100644 index 000000000000..6de0099fceb2 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/hooks/useGetStakeLabelLocaleBased.ts @@ -0,0 +1,8 @@ +import { useTranslation } from "react-i18next"; +import { getSystemLocale } from "~/helpers/systemLocale"; + +export const useGetStakeLabelLocaleBased = () => { + const locale = getSystemLocale(); + const { t } = useTranslation(); + return locale === "en-GB" ? t("accounts.contextMenu.yield") : t("accounts.contextMenu.earn"); +}; diff --git a/apps/ledger-live-desktop/src/renderer/screens/asset/AssetBalanceSummaryHeader.tsx b/apps/ledger-live-desktop/src/renderer/screens/asset/AssetBalanceSummaryHeader.tsx index 18f22b3032c7..e135745681fa 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/asset/AssetBalanceSummaryHeader.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/asset/AssetBalanceSummaryHeader.tsx @@ -23,6 +23,7 @@ import { stakeDefaultTrack } from "~/renderer/screens/stake/constants"; import { AccountLike, BalanceHistoryWithCountervalue, ValueChange } from "@ledgerhq/types-live"; import { useFetchCurrencyAll } from "@ledgerhq/live-common/exchange/swap/hooks/index"; import { flattenAccountsSelector } from "~/renderer/reducers/accounts"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; type Props = { isAvailable: boolean; cryptoChange: ValueChange; @@ -94,12 +95,7 @@ export default function AssetBalanceSummaryHeader({ const availableOnStake = stakeProgramsEnabled && currency && listFlag.includes(currency?.id); const availableOnSwap = currenciesAll.includes(currency.id); - const yieldStakeLabelCoin = - currency && - (("family" in currency && currency.family === "bitcoin") || - ("parentCurrency" in currency && currency.parentCurrency.family === "bitcoin")) - ? t("accounts.contextMenu.yield") - : t("accounts.contextMenu.stake"); + const earnStakeLabelCoin = useGetStakeLabelLocaleBased(); const onBuy = useCallback(() => { setTrackingSource("asset header actions"); @@ -214,7 +210,7 @@ export default function AssetBalanceSummaryHeader({ {availableOnStake && ( )} diff --git a/apps/ledger-live-desktop/src/renderer/screens/dashboard/FeaturedButtons.tsx b/apps/ledger-live-desktop/src/renderer/screens/dashboard/FeaturedButtons.tsx index eaff003a39b0..54427c8d6296 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/dashboard/FeaturedButtons.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/dashboard/FeaturedButtons.tsx @@ -7,6 +7,7 @@ import { useHistory } from "react-router-dom"; import useStakeFlow from "~/renderer/screens/stake"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; import { track } from "~/renderer/analytics/segment"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const ButtonGrid = styled(Grid).attrs(() => ({ columns: 3, @@ -19,7 +20,7 @@ const ButtonGrid = styled(Grid).attrs(() => ({ const FeaturedButtons = () => { const history = useHistory(); const { t } = useTranslation(); - + const stakeLabel = useGetStakeLabelLocaleBased(); const bannerFeatureFlag = useFeature("portfolioExchangeBanner"); const stakeProgramsFeatureFlag = useFeature("stakePrograms"); @@ -67,7 +68,7 @@ const FeaturedButtons = () => { } disabled={stakeDisabled} - title={t("dashboard.featuredButtons.earn.title")} + title={stakeLabel} body={t("dashboard.featuredButtons.earn.description")} onClick={handleClickStake} entryButtonTestId="stake-entry-button" diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx index 737eb618003d..00248ea00016 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketCoin/index.tsx @@ -11,6 +11,7 @@ import MarketCoinChart from "./components/MarketCoinChart"; import MarketInfo from "./components/MarketInfo"; import { useMarketCoin } from "~/renderer/screens/market/hooks/useMarketCoin"; import { KeysPriceChange } from "@ledgerhq/live-common/market/utils/types"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const CryptoCurrencyIconWrapper = styled.div` height: 56px; @@ -67,10 +68,7 @@ export default function MarketCoinScreen() { changeCounterCurrency, } = useMarketCoin(); - const yieldStakeLabelCoin = - currency && currency.id === "bitcoin" - ? t("accounts.contextMenu.yield") - : t("accounts.contextMenu.stake"); + const earnStakeLabelCoin = useGetStakeLabelLocaleBased(); const { name, ticker, image, internalCurrency, price } = currency || {}; @@ -135,7 +133,7 @@ export default function MarketCoinScreen() { )} {availableOnStake && ( )} diff --git a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx index 0fbd95c8279f..c82f80d466fe 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/market/MarketList/components/MarketRowItem.tsx @@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next"; import { TableRow, TableCell } from "../../components/Table"; import { Page, useMarketActions } from "../../hooks/useMarketActions"; import { formatPercentage, formatPrice } from "../../utils"; +import { useGetStakeLabelLocaleBased } from "~/renderer/hooks/useGetStakeLabelLocaleBased"; const CryptoCurrencyIconWrapper = styled.div` height: 32px; @@ -60,10 +61,7 @@ export const MarketRow = memo(function MarketRowItem({ const { onBuy, onStake, onSwap, availableOnBuy, availableOnSwap, availableOnStake } = useMarketActions({ currency, page: Page.Market, currenciesAll }); - const yieldStakeLabelCoin = - currency && currency.id === "bitcoin" - ? t("accounts.contextMenu.yield") - : t("accounts.contextMenu.stake"); + const earnStakeLabelCoin = useGetStakeLabelLocaleBased(); const onCurrencyClick = useCallback(() => { if (currency) { @@ -164,7 +162,7 @@ export const MarketRow = memo(function MarketRowItem({ variant="color" onClick={e => onStake(e)} > - {yieldStakeLabelCoin} + {earnStakeLabelCoin} )} diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index 86ad946374f3..2f4429528ffb 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -1139,6 +1139,7 @@ "send": "Send", "swap": "Swap", "stake": "Stake", + "earn": "Earn", "yield": "Yield", "buy": "Buy", "sell": "Sell", @@ -1485,7 +1486,7 @@ }, "earn": { "title": "Stake", - "description": "Stake your crypto with Ledger" + "description": "Get rewards on your crypto" } }, "recoverBanner": { diff --git a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts-snapshots/Tron-firstAccountPage-linux.png b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts-snapshots/Tron-firstAccountPage-linux.png index d867b5b3cd66..ba2e20ebcd5d 100644 Binary files a/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts-snapshots/Tron-firstAccountPage-linux.png and b/apps/ledger-live-desktop/tests/specs/accounts/account.spec.ts-snapshots/Tron-firstAccountPage-linux.png differ diff --git a/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts-snapshots/account-page-with-stake-button-and-banner-linux.png b/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts-snapshots/account-page-with-stake-button-and-banner-linux.png index 75c97d41a57d..8c19a7bf6fef 100644 Binary files a/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts-snapshots/account-page-with-stake-button-and-banner-linux.png and b/apps/ledger-live-desktop/tests/specs/services/ethereumStaking.spec.ts-snapshots/account-page-with-stake-button-and-banner-linux.png differ diff --git a/apps/ledger-live-mobile/src/components/BuyDeviceBanner.tsx b/apps/ledger-live-mobile/src/components/BuyDeviceBanner.tsx deleted file mode 100644 index 71c1a2e8591e..000000000000 --- a/apps/ledger-live-mobile/src/components/BuyDeviceBanner.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import React, { useCallback } from "react"; -import { Image, ImageStyle, StyleProp, ViewStyle } from "react-native"; -import { Flex, Text, IconsLegacy, Link } from "@ledgerhq/native-ui"; -import styled from "styled-components/native"; -import { useNavigation } from "@react-navigation/native"; -import { useTranslation } from "react-i18next"; -import Button, { WrappedButtonProps } from "./wrappedUi/Button"; -import { NavigatorName, ScreenName } from "~/const"; -import ForceTheme from "./theme/ForceTheme"; - -import buyImgSource from "~/images/illustration/Shared/_NanoXTop.png"; -import setupImgSource from "~/images/illustration/Shared/_NanoXBoxTop.png"; -import { track } from "~/analytics"; -import { RootNavigationComposite, StackNavigatorNavigation } from "./RootNavigator/types/helpers"; -import { BaseNavigatorStackParamList } from "./RootNavigator/types/BaseNavigator"; - -type Props = { - topLeft?: JSX.Element | null; - buttonLabel?: string; - buttonSize?: WrappedButtonProps["size"]; - event?: string; - eventProperties?: Record; - style?: StyleProp; - imageScale?: number; - imageStyle?: StyleProp; - variant?: "buy" | "setup"; - screen: string; -}; - -const Container = styled(Flex).attrs({ - backgroundColor: "primary.c80", - padding: 16, - flexDirection: "row", - alignItems: "flex-start", - borderRadius: 2, -})``; - -/** Preset props for a big nano image */ -export const IMAGE_PROPS_BIG_NANO = { - imageScale: 1.5, - imageStyle: { - height: 176, - right: -65, - }, -}; - -/** Preset props for a small nano image */ -export const IMAGE_PROPS_SMALL_NANO = { - imageScale: 1, - imageStyle: { - height: 140, - right: -70, - bottom: -30, - }, -}; - -/** Preset props for a small nano image */ -export const IMAGE_PROPS_SMALL_NANO_BOX = { - imageScale: 2.5, - imageStyle: { - height: 110, - right: -160, - }, -}; - -export default function BuyDeviceBanner({ - topLeft, - buttonLabel, - buttonSize = "medium", - event, - eventProperties, - style, - imageStyle, - variant, - screen, -}: Props) { - const { t } = useTranslation(); - const { navigate } = - useNavigation>>(); - - const handleOnPress = useCallback(() => { - navigate(NavigatorName.BuyDevice); - }, [navigate]); - - const handleSetupCtaOnPress = useCallback(() => { - navigate(NavigatorName.BaseOnboarding, { - screen: NavigatorName.Onboarding, - params: { - screen: ScreenName.OnboardingPostWelcomeSelection, - params: { - userHasDevice: true, - }, - }, - }); - }, [navigate]); - - const onPress = useCallback(() => { - if (variant === "setup") { - handleSetupCtaOnPress(); - } else { - handleOnPress(); - track("button_clicked", { - button: "Discover the Nano", - page: screen, - }); - } - }, [handleOnPress, handleSetupCtaOnPress, screen, variant]); - - const pressMessage = useCallback(() => { - track("message_clicked", { - message: "I already have a device, set it up", - page: screen, - currency: eventProperties?.currency, - }); - handleSetupCtaOnPress(); - }, [screen, eventProperties?.currency, handleSetupCtaOnPress]); - - return ( - <> - - - {topLeft || ( - - - {t("buyDevice.bannerTitle")} - - - {t("buyDevice.bannerSubtitle")} - - - )} - - - - - - - - - - {variant !== "setup" && ( - - - {t("buyDevice.setupCta")} - - - )} - - ); -} diff --git a/apps/ledger-live-mobile/src/components/FabActions/hooks/useAssetActions.tsx b/apps/ledger-live-mobile/src/components/FabActions/hooks/useAssetActions.tsx index 6a9caa1948f5..278b741daeb4 100644 --- a/apps/ledger-live-mobile/src/components/FabActions/hooks/useAssetActions.tsx +++ b/apps/ledger-live-mobile/src/components/FabActions/hooks/useAssetActions.tsx @@ -16,6 +16,7 @@ import { sharedSwapTracking } from "~/screens/Swap/utils"; import { useFetchCurrencyAll } from "@ledgerhq/live-common/exchange/swap/hooks/index"; import { flattenAccountsSelector } from "~/reducers/accounts"; import { PtxToast } from "../../Toast/PtxToast"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; type useAssetActionsProps = { currency?: CryptoCurrency | TokenCurrency; @@ -39,6 +40,8 @@ export default function useAssetActions({ currency, accounts }: useAssetActionsP const ptxServiceCtaScreens = useFeature("ptxServiceCtaScreens"); const { t } = useTranslation(); + const stakeLabel = getStakeLabelLocaleBased(); + const readOnlyModeEnabled = useSelector(readOnlyModeEnabledSelector); const hasAccounts = accounts?.length && accounts.length > 0; const areAccountsBalanceEmpty = useMemo( @@ -152,7 +155,7 @@ export default function useAssetActions({ currency, accounts }: useAssetActionsP ...(canBeStaken ? [ { - label: currency.id === "bitcoin" ? t("account.yield") : t("account.stake"), + label: t(stakeLabel), Icon: iconStake, event: "button_clicked", eventProperties: { @@ -248,6 +251,7 @@ export default function useAssetActions({ currency, accounts }: useAssetActionsP hasAccounts, parentAccount, readOnlyModeEnabled, + stakeLabel, t, route, ]); diff --git a/apps/ledger-live-mobile/src/components/GradientContainer.tsx b/apps/ledger-live-mobile/src/components/GradientContainer.tsx index b36c0eb69059..e7b6b0abe52d 100644 --- a/apps/ledger-live-mobile/src/components/GradientContainer.tsx +++ b/apps/ledger-live-mobile/src/components/GradientContainer.tsx @@ -4,6 +4,7 @@ import { useTheme } from "styled-components/native"; import { Flex } from "@ledgerhq/native-ui"; import { StyleProp, ViewStyle } from "react-native"; +type Direction = "top-to-bottom" | "left-to-right" | "bottom-to-top" | "right-to-left"; type Props = { color?: string; containerStyle?: StyleProp; @@ -11,8 +12,39 @@ type Props = { children?: React.ReactNode; startOpacity?: number; endOpacity?: number; + direction?: Direction; + borderRadius?: number; }; +function getPositions(direction: Direction) { + let x1 = "0%"; + let y1 = "0%"; + let x2 = "0%"; + let y2 = "100%"; + + switch (direction) { + case "left-to-right": + x2 = "100%"; + y2 = "0%"; + break; + case "bottom-to-top": + y1 = "100%"; + y2 = "0%"; + break; + case "right-to-left": + x1 = "100%"; + x2 = "0%"; + break; + } + + return { + x1, + x2, + y1, + y2, + }; +} + export default function GradientContainer({ color, containerStyle, @@ -20,26 +52,26 @@ export default function GradientContainer({ children, startOpacity = 1, endOpacity = 0, + direction = "top-to-bottom", + borderRadius, }: Props) { const { colors } = useTheme(); + const gradientPositions = getPositions(direction); + return ( - - + + - + - + {children} diff --git a/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx b/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx index 474842bdf4fd..108ebe49d417 100644 --- a/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx +++ b/apps/ledger-live-mobile/src/components/TabBar/TransferDrawer.tsx @@ -13,8 +13,10 @@ import { NavigatorName } from "~/const"; import { hasOrderedNanoSelector, readOnlyModeEnabledSelector } from "~/reducers/settings"; import { Props as ModalProps } from "../QueuedDrawer"; import TransferButton from "../TransferButton"; -import BuyDeviceBanner, { IMAGE_PROPS_SMALL_NANO } from "../BuyDeviceBanner"; -import SetupDeviceBanner from "../SetupDeviceBanner"; +import BuyDeviceBanner, { + IMAGE_PROPS_BUY_DEVICE_FLEX_BOX, +} from "LLM/features/Reborn/components/BuyDeviceBanner"; +import SetupDeviceBanner from "LLM/features/Reborn/components/SetupDeviceBanner"; import { track, useAnalytics } from "~/analytics"; import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; import useQuickActions from "~/hooks/useQuickActions"; @@ -22,6 +24,7 @@ import { PTX_SERVICES_TOAST_ID } from "~/utils/constants"; import { useQuickAccessURI } from "@ledgerhq/live-common/hooks/recoverFeatureFlag"; import { EntryOf } from "~/types/helpers"; import { BaseNavigatorStackParamList } from "../RootNavigator/types/BaseNavigator"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; type ButtonItem = { title: string; @@ -42,6 +45,7 @@ export default function TransferDrawer({ onClose }: Omit onNavigate(STAKE.route), @@ -239,32 +243,34 @@ export default function TransferDrawer({ onClose }: Omit - - {buttonsList.map((button, index) => ( - - - - ))} - + <> + + + {buttonsList.map((button, index) => ( + + + + ))} + + {readOnlyModeEnabled && !hasOrderedNano && ( + {t("buyDevice.bannerTitle2")} } - style={{ marginTop: 36, paddingTop: 13.5, paddingBottom: 13.5 }} buttonLabel={t("buyDevice.bannerButtonTitle2")} buttonSize="small" event="button_clicked" eventProperties={bannerEventProperties} screen={screen} - {...IMAGE_PROPS_SMALL_NANO} /> )} {readOnlyModeEnabled && hasOrderedNano ? ( @@ -272,6 +278,6 @@ export default function TransferDrawer({ onClose }: Omit ) : null} - + ); } diff --git a/apps/ledger-live-mobile/src/components/theme/ForceTheme.tsx b/apps/ledger-live-mobile/src/components/theme/ForceTheme.tsx index 8f41447fe501..9c7a36d6d0f8 100644 --- a/apps/ledger-live-mobile/src/components/theme/ForceTheme.tsx +++ b/apps/ledger-live-mobile/src/components/theme/ForceTheme.tsx @@ -1,12 +1,11 @@ import React from "react"; import StyleProvider from "../../StyleProvider"; -export default function ForceTheme({ - children, - selectedPalette, -}: { +export type Props = { children?: React.ReactNode; selectedPalette: "light" | "dark"; -}): React.ReactElement { +}; + +export default function ForceTheme({ children, selectedPalette }: Props): React.ReactElement { return {children}; } diff --git a/apps/ledger-live-mobile/src/families/bitcoin/accountActions.tsx b/apps/ledger-live-mobile/src/families/bitcoin/accountActions.tsx index 75bc85b789f1..2284d3239eb7 100644 --- a/apps/ledger-live-mobile/src/families/bitcoin/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/bitcoin/accountActions.tsx @@ -6,6 +6,7 @@ import { getMainAccount, isAccountEmpty } from "@ledgerhq/live-common/account/in import { TokenAccount } from "@ledgerhq/types-live"; import { BitcoinAccount } from "@ledgerhq/live-common/families/bitcoin/types"; import { IconsLegacy } from "@ledgerhq/native-ui"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -15,6 +16,8 @@ const getMainActions = ({ parentAccount: BitcoinAccount | null | undefined; }): ActionButtonEvent[] => { const mainAccount = getMainAccount(account, parentAccount); + const label = getStakeLabelLocaleBased(); + const navigationParams: NavigationParamsType = isAccountEmpty(mainAccount) ? [ NavigatorName.NoFundsFlow, @@ -41,7 +44,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, event: "button_clicked", eventProperties: { diff --git a/apps/ledger-live-mobile/src/families/cardano/accountActions.tsx b/apps/ledger-live-mobile/src/families/cardano/accountActions.tsx index 4686a039b849..cbb9c791be37 100644 --- a/apps/ledger-live-mobile/src/families/cardano/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/cardano/accountActions.tsx @@ -6,6 +6,7 @@ import { ParamListBase, RouteProp } from "@react-navigation/native"; import type { CardanoAccount } from "@ledgerhq/live-common/families/cardano/types"; import { NavigatorName, ScreenName } from "../../const"; import { NavigationParamsType } from "../../components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -17,6 +18,7 @@ const getMainActions = ({ parentRoute: RouteProp; }) => { const isAlreadyDelegated = !!account.cardanoResources?.delegation?.poolId; + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = [ NavigatorName.CardanoDelegationFlow, @@ -37,7 +39,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, event: "button_clicked", eventProperties: { diff --git a/apps/ledger-live-mobile/src/families/celo/accountActions.tsx b/apps/ledger-live-mobile/src/families/celo/accountActions.tsx index 3dc3c0c81cea..0122c627af90 100644 --- a/apps/ledger-live-mobile/src/families/celo/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/celo/accountActions.tsx @@ -7,6 +7,7 @@ import { isAccountEmpty } from "@ledgerhq/live-common/account/index"; import CeloIcon from "./components/CeloIcon"; import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; import { NavigatorName, ScreenName } from "~/const"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -17,6 +18,7 @@ const getMainActions = ({ }): ActionButtonEvent[] => { const { celoResources } = account; invariant(celoResources, "celo resources not parsed"); + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = isAccountEmpty(account) ? [ @@ -42,7 +44,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: () => , eventProperties: { currency: "CELO", diff --git a/apps/ledger-live-mobile/src/families/cosmos/accountActions.tsx b/apps/ledger-live-mobile/src/families/cosmos/accountActions.tsx index 9a13fb85fe10..2579378beee1 100644 --- a/apps/ledger-live-mobile/src/families/cosmos/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/cosmos/accountActions.tsx @@ -8,6 +8,7 @@ import { CosmosAccount } from "@ledgerhq/live-common/families/cosmos/types"; import { Account } from "@ledgerhq/types-live"; import { NavigatorName, ScreenName } from "~/const"; import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -19,6 +20,7 @@ const getMainActions = ({ parentRoute: RouteProp; }): ActionButtonEvent[] => { const delegationDisabled = !canDelegate(account); + const label = getStakeLabelLocaleBased(); const startWithValidator = account.cosmosResources && account.cosmosResources?.delegations.length > 0; const navigationParams: NavigationParamsType = delegationDisabled @@ -49,7 +51,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "COSMOS", diff --git a/apps/ledger-live-mobile/src/families/elrond/accountActions.tsx b/apps/ledger-live-mobile/src/families/elrond/accountActions.tsx index 0d47a823a134..50607e14cdf5 100644 --- a/apps/ledger-live-mobile/src/families/elrond/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/elrond/accountActions.tsx @@ -13,6 +13,7 @@ import type { ActionButtonEvent, NavigationParamsType } from "~/components/FabAc import { getCurrentElrondPreloadData } from "@ledgerhq/coin-elrond/preload"; import { ParamListBase, RouteProp } from "@react-navigation/native"; import { NavigatorName, ScreenName } from "~/const"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; /* * Declare the types for the properties and return payload. @@ -35,6 +36,7 @@ const getMainActions = ({ parentRoute, }: getActionsType): getActionsReturnType => { const delegationEnabled = hasMinimumDelegableBalance(account); + const label = getStakeLabelLocaleBased(); /* * Get a list of all the providers, randomize, and also the screen, conditionally, based on existing amount of delegations. @@ -85,7 +87,7 @@ const getMainActions = ({ return [ { id: "stake", - label: , + label: , Icon: IconsLegacy.CoinsMedium, navigationParams, eventProperties: { diff --git a/apps/ledger-live-mobile/src/families/evm/accountActions.tsx b/apps/ledger-live-mobile/src/families/evm/accountActions.tsx index e7ebf10f6dd7..3ef17472f37c 100644 --- a/apps/ledger-live-mobile/src/families/evm/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/evm/accountActions.tsx @@ -8,6 +8,7 @@ import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions import { NavigatorName, ScreenName } from "~/const"; import BigNumber from "bignumber.js"; import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const ethMagnitude = getCryptoCurrencyById("ethereum").units[0].magnitude ?? 18; @@ -64,6 +65,8 @@ function getNavigatorParams({ parentRoute, account, parentAccount }: Props): Nav const getMainActions = ({ account, parentAccount, parentRoute }: Props): ActionButtonEvent[] => { if (account.type === "Account" && account.currency.id === "ethereum") { + const label = getStakeLabelLocaleBased(); + const navigationParams = getNavigatorParams({ account, parentAccount, @@ -74,7 +77,7 @@ const getMainActions = ({ account, parentAccount, parentRoute }: Props): ActionB { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "ETH", diff --git a/apps/ledger-live-mobile/src/families/near/accountActions.tsx b/apps/ledger-live-mobile/src/families/near/accountActions.tsx index d3a9275c9843..7509ae90f741 100644 --- a/apps/ledger-live-mobile/src/families/near/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/near/accountActions.tsx @@ -8,6 +8,7 @@ import type { Account } from "@ledgerhq/types-live"; import { NavigatorName, ScreenName } from "~/const"; import { ParamListBase, RouteProp } from "@react-navigation/native"; import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -21,6 +22,7 @@ const getMainActions = ({ const stakingDisabled = !canStake(account); const startWithValidator = account.nearResources && account.nearResources?.stakingPositions.length > 0; + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = stakingDisabled ? [ @@ -49,7 +51,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "NEAR", diff --git a/apps/ledger-live-mobile/src/families/polkadot/accountActions.tsx b/apps/ledger-live-mobile/src/families/polkadot/accountActions.tsx index 41f041f2795a..24809e00825f 100644 --- a/apps/ledger-live-mobile/src/families/polkadot/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/polkadot/accountActions.tsx @@ -22,6 +22,7 @@ import NominateIcon from "~/icons/Vote"; import ChillIcon from "~/icons/VoteNay"; import { NavigatorName, ScreenName } from "~/const"; import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = (args: { account: PolkadotAccount; @@ -36,6 +37,7 @@ const getMainActions = (args: { const hasBondedBalance = lockedBalance && lockedBalance.gt(0); const hasPendingBondOperation = hasPendingOperationType(account, "BOND"); const nominationEnabled = !electionOpen && canNominate(account); + const label = getStakeLabelLocaleBased(); const earnRewardsEnabled = !electionOpen && !hasBondedBalance && !hasPendingBondOperation; @@ -85,7 +87,7 @@ const getMainActions = (args: { { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "DOT", diff --git a/apps/ledger-live-mobile/src/families/solana/accountActions.tsx b/apps/ledger-live-mobile/src/families/solana/accountActions.tsx index b06291f63258..05e4d9a8c6d1 100644 --- a/apps/ledger-live-mobile/src/families/solana/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/solana/accountActions.tsx @@ -6,6 +6,7 @@ import { ParamListBase, RouteProp } from "@react-navigation/native"; import { SolanaAccount } from "@ledgerhq/live-common/families/solana/types"; import { NavigatorName, ScreenName } from "~/const"; import type { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -17,6 +18,7 @@ const getMainActions = ({ parentRoute: RouteProp; }): ActionButtonEvent[] => { const delegationDisabled = account.solanaResources?.stakes.length > 1; + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = delegationDisabled ? [ @@ -48,7 +50,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "SOL", diff --git a/apps/ledger-live-mobile/src/families/tezos/accountActions.tsx b/apps/ledger-live-mobile/src/families/tezos/accountActions.tsx index a133d6754899..21568e17e284 100644 --- a/apps/ledger-live-mobile/src/families/tezos/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/tezos/accountActions.tsx @@ -9,6 +9,7 @@ import { ParamListBase, RouteProp } from "@react-navigation/native"; import { IconsLegacy } from "@ledgerhq/native-ui"; import { NavigatorName, ScreenName } from "~/const"; import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getExtraSendActionParams = ({ account }: { account: AccountLike }) => { const delegation = getAccountDelegationSync(account); @@ -50,6 +51,7 @@ const getMainActions = ({ parentRoute?: RouteProp; }): ActionButtonEvent[] => { const delegationDisabled = isAccountDelegating(account) || account.type !== "Account"; + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = delegationDisabled ? [ NavigatorName.NoFundsFlow, @@ -77,7 +79,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, eventProperties: { currency: "XTZ", diff --git a/apps/ledger-live-mobile/src/families/tron/accountActions.tsx b/apps/ledger-live-mobile/src/families/tron/accountActions.tsx index 025f292e7e5d..13ef1b9217f8 100644 --- a/apps/ledger-live-mobile/src/families/tron/accountActions.tsx +++ b/apps/ledger-live-mobile/src/families/tron/accountActions.tsx @@ -6,6 +6,7 @@ import { ActionButtonEvent, NavigationParamsType } from "~/components/FabActions import { getMainAccount, isAccountEmpty } from "@ledgerhq/live-common/account/index"; import { TokenAccount } from "@ledgerhq/types-live"; import { IconsLegacy } from "@ledgerhq/native-ui"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const getMainActions = ({ account, @@ -15,6 +16,7 @@ const getMainActions = ({ parentAccount: TronAccount | null | undefined; }): ActionButtonEvent[] => { const mainAccount = getMainAccount(account, parentAccount); + const label = getStakeLabelLocaleBased(); const navigationParams: NavigationParamsType = isAccountEmpty(mainAccount) ? [ NavigatorName.NoFundsFlow, @@ -42,7 +44,7 @@ const getMainActions = ({ { id: "stake", navigationParams, - label: , + label: , Icon: IconsLegacy.CoinsMedium, event: "button_clicked", eventProperties: { diff --git a/apps/ledger-live-mobile/src/helpers/getStakeLabelLocaleBased.ts b/apps/ledger-live-mobile/src/helpers/getStakeLabelLocaleBased.ts new file mode 100644 index 000000000000..adc20660a0e9 --- /dev/null +++ b/apps/ledger-live-mobile/src/helpers/getStakeLabelLocaleBased.ts @@ -0,0 +1,4 @@ +import RNLocalize from "react-native-localize"; + +export const getStakeLabelLocaleBased = () => + RNLocalize.getCountry() === "GB" ? "account.yield" : "account.earn"; diff --git a/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTop.png b/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTop.png new file mode 100644 index 000000000000..096287fa9029 Binary files /dev/null and b/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTop.png differ diff --git a/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTwoSides.png b/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTwoSides.png new file mode 100644 index 000000000000..2b8072afecea Binary files /dev/null and b/apps/ledger-live-mobile/src/images/illustration/Shared/_FlexTwoSides.png differ diff --git a/apps/ledger-live-mobile/src/images/illustration/Shared/_LedgerSetup.png b/apps/ledger-live-mobile/src/images/illustration/Shared/_LedgerSetup.png new file mode 100644 index 000000000000..efbc3bcd854d Binary files /dev/null and b/apps/ledger-live-mobile/src/images/illustration/Shared/_LedgerSetup.png differ diff --git a/apps/ledger-live-mobile/src/locales/en/common.json b/apps/ledger-live-mobile/src/locales/en/common.json index fa6d09fc50ba..688b7496af05 100644 --- a/apps/ledger-live-mobile/src/locales/en/common.json +++ b/apps/ledger-live-mobile/src/locales/en/common.json @@ -1287,20 +1287,20 @@ "title": "You need a Ledger", "desc": "For your security, Ledger Live only works with a device. You need a device in order to continue.", "cta": "Buy your Ledger now", - "footer": "I already have a device, set it up now", - "bannerTitle": "Keep your crypto and NFTs safe", - "bannerTitle2": "You'll need a Nano to trade", - "bannerSubtitle": "with a Ledger Device", - "bannerButtonTitle": "Discover the Nano", - "bannerButtonTitle2": "Buy a device", - "setupCta": "I already have a device, set it up" + "footer": "I already have a Ledger, set it up", + "bannerTitle": "Top-notch security for your crypto and NFTs", + "bannerTitle2": "You will need a Ledger to be able to trade", + "bannerSubtitle": "with a Ledger Flex", + "bannerButtonTitle": "Discover the Flex", + "bannerButtonTitle2": "Buy a Ledger", + "setupCta": "I already have a Ledger, set it up" }, "postBuyDeviceSetupNanoWall": { "title": "Have you received your device?", "desc": "Once you receive your device, you can start its setup through Ledger Live!", "cta": "Setup my device", - "bannerTitle": "Received your device?", - "bannerCta": "Begin the setup", + "bannerTitle": "Received your Ledger?", + "bannerCta": "Begin setup", "continue": "Continue" }, "purchaseDevice": { @@ -2360,8 +2360,7 @@ "swap": "Swap", "send": "Send", "deposit": "Receive", - "stake": "Stake", - "yield": "Yield" + "stake": "Stake" }, "recoverBanner": { "subscribeDone": { @@ -3272,7 +3271,7 @@ }, "stake": { "title": "Stake", - "description": "Stake your crypto holdings." + "description": "Get rewards on your crypto." }, "swap": { "title": "Swap", diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/index.tsx b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/index.tsx new file mode 100644 index 000000000000..78b79202fcdd --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/index.tsx @@ -0,0 +1,128 @@ +import React from "react"; +import useBuyDeviceBannerModel, { Props } from "./useBuyDeviceBannerModel"; +import { Flex, Text, Icons } from "@ledgerhq/native-ui"; +import GradientContainer from "~/components/GradientContainer"; +import { useTranslation } from "react-i18next"; +import ForceTheme from "~/components/theme/ForceTheme"; +import { Image } from "react-native"; +import Button from "~/components/wrappedUi/Button"; + +type ViewProps = ReturnType; + +export const IMAGE_PROPS_BUY_DEVICE_FLEX = { + imageScale: 1, + imageStyle: { + height: 180, + right: -70, + bottom: 10, + }, +}; + +export const IMAGE_PROPS_BUY_DEVICE_FLEX_BOX = { + imageStyle: { + height: 120, + right: -70, + bottom: -1, + }, +}; + +export const IMAGE_PROPS_POST_PURCHASHE = { + imageStyle: { + height: 110, + right: -130, + bottom: -1, + }, +}; + +function View({ + topLeft, + buttonLabel, + buttonSize, + event, + imageSource, + imageStyle, + revertTheme, + colors, + variant, + eventProperties, + onPress, + pressMessage, +}: ViewProps) { + const { t } = useTranslation(); + + const RightIcon = ; + + return ( + + + + + + {topLeft || ( + <> + + {t("buyDevice.bannerTitle")} + + + {t("buyDevice.bannerSubtitle")} + + + )} + + + + + + + + + + + {variant !== "setup" && ( + + + + )} + + ); +} + +const BuyDeviceBanner = (props: Props) => { + return ; +}; + +export default BuyDeviceBanner; diff --git a/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts new file mode 100644 index 000000000000..1399be55faf9 --- /dev/null +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/BuyDeviceBanner/useBuyDeviceBannerModel.ts @@ -0,0 +1,118 @@ +import { useCallback } from "react"; +import { useTheme } from "styled-components/native"; +import { useNavigation } from "@react-navigation/native"; +import { + RootNavigationComposite, + StackNavigatorNavigation, +} from "~/components/RootNavigator/types/helpers"; +import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; +import { ImageSourcePropType, ImageStyle, StyleProp } from "react-native"; +import { NavigatorName, ScreenName } from "~/const"; +import { track } from "~/analytics"; +import { WrappedButtonProps } from "~/components/wrappedUi/Button"; +import { Props as ThemeProps } from "~/components/theme/ForceTheme"; + +import buyFlexSource from "~/images/illustration/Shared/_FlexTop.png"; +import buyDoubleFlexSource from "~/images/illustration/Shared/_FlexTwoSides.png"; +import setupYourLedger from "~/images/illustration/Shared/_LedgerSetup.png"; + +type Variant = "buy" | "setup"; + +type Image = "buyFlex" | "buyDoubleFlex" | "setupYourLedger"; + +export type Props = { + image?: Image; + variant?: Variant; + screen: string; + eventProperties?: Record; + topLeft?: JSX.Element | null; + buttonLabel?: string; + buttonSize?: WrappedButtonProps["size"]; + event?: string; + imageScale?: number; + imageStyle?: StyleProp; +}; +const useBuyDeviceBannerModel = ({ + image, + variant, + screen, + eventProperties, + topLeft, + buttonLabel, + buttonSize = "medium", + event, + imageStyle, +}: Props) => { + const { colors, theme } = useTheme(); + const { navigate } = + useNavigation>>(); + + const revertTheme: ThemeProps["selectedPalette"] = theme === "light" ? "dark" : "light"; + + const imageSource: ImageSourcePropType = (() => { + switch (image) { + case "buyFlex": + return buyFlexSource; + case "buyDoubleFlex": + return buyDoubleFlexSource; + case "setupYourLedger": + return setupYourLedger; + default: + return buyDoubleFlexSource; + } + })(); + + const handleOnPress = useCallback(() => { + navigate(NavigatorName.BuyDevice); + }, [navigate]); + + const handleSetupCtaOnPress = useCallback(() => { + navigate(NavigatorName.BaseOnboarding, { + screen: NavigatorName.Onboarding, + params: { + screen: ScreenName.OnboardingPostWelcomeSelection, + params: { + userHasDevice: true, + }, + }, + }); + }, [navigate]); + + const onPress = useCallback(() => { + if (variant === "setup") { + handleSetupCtaOnPress(); + } else { + handleOnPress(); + track("button_clicked", { + button: "Discover the Nano", + page: screen, + }); + } + }, [handleOnPress, handleSetupCtaOnPress, screen, variant]); + + const pressMessage = useCallback(() => { + track("message_clicked", { + message: "I already have a device, set it up", + page: screen, + currency: eventProperties?.currency, + }); + handleSetupCtaOnPress(); + }, [screen, eventProperties?.currency, handleSetupCtaOnPress]); + + return { + revertTheme, + colors, + imageSource, + variant, + topLeft, + buttonLabel, + buttonSize, + event, + imageStyle, + eventProperties, + onPress, + pressMessage, + }; +}; + +export default useBuyDeviceBannerModel; diff --git a/apps/ledger-live-mobile/src/components/SetupDeviceBanner.tsx b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/SetupDeviceBanner/index.tsx similarity index 62% rename from apps/ledger-live-mobile/src/components/SetupDeviceBanner.tsx rename to apps/ledger-live-mobile/src/newArch/features/Reborn/components/SetupDeviceBanner/index.tsx index 9382f9786996..740db4f34c0c 100644 --- a/apps/ledger-live-mobile/src/components/SetupDeviceBanner.tsx +++ b/apps/ledger-live-mobile/src/newArch/features/Reborn/components/SetupDeviceBanner/index.tsx @@ -1,32 +1,31 @@ import React from "react"; import { Text } from "@ledgerhq/native-ui"; import { useTranslation } from "react-i18next"; - -import BuyDeviceBanner, { IMAGE_PROPS_SMALL_NANO_BOX } from "./BuyDeviceBanner"; +import BuyDeviceBanner, { IMAGE_PROPS_POST_PURCHASHE } from "../BuyDeviceBanner"; type Props = { screen: string; }; -function SetupDeviceBanner({ screen }: Props) { +const SetupDeviceBanner = ({ screen }: Props) => { const { t } = useTranslation(); return ( + {t("postBuyDeviceSetupNanoWall.bannerTitle")} } - style={{ paddingTop: 13.5, paddingBottom: 13.5 }} buttonLabel={t("postBuyDeviceSetupNanoWall.bannerCta")} buttonSize="small" event="button_clicked" screen={screen} - {...IMAGE_PROPS_SMALL_NANO_BOX} + image="setupYourLedger" /> ); -} +}; export default SetupDeviceBanner; diff --git a/apps/ledger-live-mobile/src/screens/Account/ReadOnly/ReadOnlyAccount.tsx b/apps/ledger-live-mobile/src/screens/Account/ReadOnly/ReadOnlyAccount.tsx index 8b4f9fd2e1c1..74afe07d5dd7 100644 --- a/apps/ledger-live-mobile/src/screens/Account/ReadOnly/ReadOnlyAccount.tsx +++ b/apps/ledger-live-mobile/src/screens/Account/ReadOnly/ReadOnlyAccount.tsx @@ -10,8 +10,10 @@ import { useFocusEffect } from "@react-navigation/native"; import ReadOnlyGraphCard from "~/components/ReadOnlyGraphCard"; import ReadOnlyFabActions from "~/components/FabActions/ReadOnlyFabActions"; import GradientContainer from "~/components/GradientContainer"; -import BuyDeviceBanner, { IMAGE_PROPS_BIG_NANO } from "~/components/BuyDeviceBanner"; -import SetupDeviceBanner from "~/components/SetupDeviceBanner"; +import BuyDeviceBanner, { + IMAGE_PROPS_BUY_DEVICE_FLEX, +} from "LLM/features/Reborn/components/BuyDeviceBanner"; +import SetupDeviceBanner from "LLM/features/Reborn/components/SetupDeviceBanner"; import CurrencyUnitValue from "~/components/CurrencyUnitValue"; import { TrackScreen } from "~/analytics"; @@ -86,16 +88,12 @@ function ReadOnlyAccount({ route }: Props) { , - + {hasOrderedNano ? ( ) : ( )} , diff --git a/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioQuickActionsBar.tsx b/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioQuickActionsBar.tsx index 1faf8339dd05..6c5132e63533 100644 --- a/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioQuickActionsBar.tsx +++ b/apps/ledger-live-mobile/src/screens/Portfolio/PortfolioQuickActionsBar.tsx @@ -9,6 +9,7 @@ import { track } from "~/analytics"; import { useRoute } from "@react-navigation/native"; import { BaseNavigatorStackParamList } from "~/components/RootNavigator/types/BaseNavigator"; import { EntryOf } from "~/types/helpers"; +import { getStakeLabelLocaleBased } from "~/helpers/getStakeLabelLocaleBased"; const SHARED_CONFIG = { variant: "small" as const, @@ -17,6 +18,7 @@ const SHARED_CONFIG = { function PortfolioQuickActionsBar() { const navigation = useNavigation>(); const router = useRoute(); + const stakeLabel = getStakeLabelLocaleBased(); const { t } = useTranslation(); const { quickActionsList: { SEND, RECEIVE, BUY, SWAP, STAKE }, @@ -62,7 +64,7 @@ function PortfolioQuickActionsBar() { STAKE && { ...SHARED_CONFIG, Icon: STAKE.icon, - children: t("portfolio.quickActions.stake"), + children: t(stakeLabel), onPress: () => onNavigate(STAKE.route, "quick_action_stake"), disabled: STAKE.disabled, }, diff --git a/apps/ledger-live-mobile/src/screens/Portfolio/ReadOnly/index.tsx b/apps/ledger-live-mobile/src/screens/Portfolio/ReadOnly/index.tsx index 621a85a99c73..2f514a0a4b5a 100644 --- a/apps/ledger-live-mobile/src/screens/Portfolio/ReadOnly/index.tsx +++ b/apps/ledger-live-mobile/src/screens/Portfolio/ReadOnly/index.tsx @@ -25,8 +25,10 @@ import { NavigatorName, ScreenName } from "~/const"; import CheckLanguageAvailability from "~/components/CheckLanguageAvailability"; import CheckTermOfUseUpdate from "~/components/CheckTermOfUseUpdate"; import { TAB_BAR_SAFE_HEIGHT } from "~/components/TabBar/TabBarSafeAreaView"; -import SetupDeviceBanner from "~/components/SetupDeviceBanner"; -import BuyDeviceBanner, { IMAGE_PROPS_BIG_NANO } from "~/components/BuyDeviceBanner"; +import SetupDeviceBanner from "LLM/features/Reborn/components/SetupDeviceBanner"; +import BuyDeviceBanner, { + IMAGE_PROPS_BUY_DEVICE_FLEX, +} from "LLM/features/Reborn/components/BuyDeviceBanner"; import Assets from "../Assets"; import { AnalyticsContext } from "~/analytics/AnalyticsContext"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; @@ -108,7 +110,7 @@ function ReadOnlyPortfolio({ navigation }: NavigationProps) { , ...(hasOrderedNano ? [ - + , ] @@ -121,23 +123,19 @@ function ReadOnlyPortfolio({ navigation }: NavigationProps) { , ...(!hasOrderedNano ? [ - , + + + , ] : []), ], diff --git a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/ReadOnly/index.tsx b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/ReadOnly/index.tsx index 259245c942a1..d83ba16d8bb0 100644 --- a/apps/ledger-live-mobile/src/screens/WalletCentricAsset/ReadOnly/index.tsx +++ b/apps/ledger-live-mobile/src/screens/WalletCentricAsset/ReadOnly/index.tsx @@ -17,8 +17,10 @@ import EmptyAccountCard from "../../Account/EmptyAccountCard"; import CurrencyBackgroundGradient from "~/components/CurrencyBackgroundGradient"; import Header from "../Header"; import { hasOrderedNanoSelector } from "~/reducers/settings"; -import BuyDeviceBanner, { IMAGE_PROPS_BIG_NANO } from "~/components/BuyDeviceBanner"; -import SetupDeviceBanner from "~/components/SetupDeviceBanner"; +import BuyDeviceBanner, { + IMAGE_PROPS_BUY_DEVICE_FLEX, +} from "LLM/features/Reborn/components/BuyDeviceBanner"; +import SetupDeviceBanner from "LLM/features/Reborn/components/SetupDeviceBanner"; import { FabAssetActions } from "~/components/FabActions/actionsList/asset"; import { TrackScreen } from "~/analytics"; import { BaseComposite, StackNavigatorProps } from "~/components/RootNavigator/types/helpers"; @@ -75,16 +77,13 @@ const ReadOnlyAssetScreen = ({ route }: NavigationProps) => { , , - + {hasOrderedNano ? ( ) : ( { currency: currency.name, }} screen="Account" - {...IMAGE_PROPS_BIG_NANO} /> )} , diff --git a/libs/coin-modules/coin-bitcoin/src/__tests__/unit/synchronisation.unit.test.ts b/libs/coin-modules/coin-bitcoin/src/__tests__/unit/synchronisation.unit.test.ts index 5da2d31e196b..33c1f9b83a48 100644 --- a/libs/coin-modules/coin-bitcoin/src/__tests__/unit/synchronisation.unit.test.ts +++ b/libs/coin-modules/coin-bitcoin/src/__tests__/unit/synchronisation.unit.test.ts @@ -3,6 +3,8 @@ import { makeGetAccountShape } from "../../synchronisation"; import { createFixtureAccount, mockSignerContext } from "../fixtures/common.fixtures"; +jest.setTimeout(10000); + describe("synchronisation", () => { it("should return a function", () => { const result = makeGetAccountShape(mockSignerContext); diff --git a/libs/ui/packages/react/src/components/cta/Button/index.tsx b/libs/ui/packages/react/src/components/cta/Button/index.tsx index 90362250c77e..52b5a9dccf3c 100644 --- a/libs/ui/packages/react/src/components/cta/Button/index.tsx +++ b/libs/ui/packages/react/src/components/cta/Button/index.tsx @@ -24,7 +24,7 @@ interface BaseProps extends BaseStyledProps, BordersProps { } export interface ButtonProps extends BaseProps, React.RefAttributes { - Icon?: React.ComponentType<{ size: number; color?: string }>; + Icon?: React.ReactElement | React.ComponentType<{ size: number; color?: string }>; children?: React.ReactNode; onClick?: (event: React.SyntheticEvent) => void; iconSize?: number; @@ -236,7 +236,11 @@ const Button = ( ref?: React.ForwardedRef, ): React.ReactElement => { const iconNodeSize = iconSize || fontSizes[props.fontSize ?? 4]; - const IconNode = useMemo(() => Icon && , [iconNodeSize, Icon]); + const IconNode = useMemo(() => { + if (!Icon) return null; + if (typeof Icon === "object") return Icon; + return ; + }, [iconNodeSize, Icon]); return ( diff --git a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx index f5a50a0a9bc4..9b214029fda3 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/Carousel.stories.tsx @@ -1,25 +1,15 @@ +import { action } from "@storybook/addon-actions"; import type { Meta, StoryObj } from "@storybook/react"; -import React from "react"; +import React, { FC, ReactElement, useContext } from "react"; + +import PortfolioContentCard from "../ContentCard/PortfolioContentCard"; import Carousel from "./"; import { Props } from "./types"; -const CarouselStory = (args: Omit & { children: number }) => { - const slides = Array.from({ length: args.children }, (_, index) => ( -
- Slide {index} -
- )); - - return ; -}; +type Args = Omit & { children: number }; +type Parameters = { Slide: FC<{ index: number }> }; +const SlideContext = React.createContext([]); export default { title: "Layout/Carousel", argTypes: { @@ -33,6 +23,9 @@ export default { defaultValue: "default", control: "inline-radio", }, + onChange: { + description: "Function called when a new slide is shown.", + }, }, args: { variant: "default", @@ -45,7 +38,55 @@ export default { }, }, }, - render: CarouselStory, -} satisfies Meta; + decorators: [ + (Story: FC, { args, parameters }: { args: Args; parameters: Parameters }) => ( + ( + + ))} + > + + + ), + ], + render: ({ children, ...props }: Args) => ( + {useContext(SlideContext)} + ), +} satisfies Meta; + +export const Default: StoryObj = { + parameters: { + Slide: (({ index }) => ( +
+ Slide {index} +
+ )) satisfies Parameters["Slide"], + }, +}; + +export const PortfolioContentCards: StoryObj = { + parameters: { + Slide: (({ index }) => ( + onSlideAction(`click on slide ${index}`)} + onClose={() => onSlideAction(`close slide ${index}`)} + /> + )) satisfies Parameters["Slide"], + }, +}; + +const onSlideAction = action("onSlideAction"); -export const Default: StoryObj = {}; +const IMAGE_SRC = + "data:image/svg+xml,"; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/index.tsx b/libs/ui/packages/react/src/components/layout/Carousel/index.tsx index 1523b4b59c2c..61df29282e8a 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/index.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/index.tsx @@ -14,8 +14,12 @@ const EmblaContainer = styled.div` `; const EmblaSlide = styled.div` + display: flex; flex: 0 0 100%; min-width: 0; + > * { + flex-basis: 100%; + } `; const CarouselContainer = styled.div>` @@ -30,7 +34,7 @@ const CarouselContainer = styled.div>` /** * This component uses the https://github.com/davidjerleke/embla-carousel library. */ -const Carousel = ({ children, variant = "default" }: Props) => { +const Carousel = ({ children, variant = "default", onChange }: Props) => { const [currentIndex, setCurrentIndex] = useState(0); const [emblaRef, emblaApi] = useEmblaCarousel({ loop: true }); @@ -40,7 +44,9 @@ const Carousel = ({ children, variant = "default" }: Props) => { const newIndex = emblaApi.selectedScrollSnap(); setCurrentIndex(newIndex); emblaApi.scrollTo(newIndex); - }, [emblaApi]); + + onChange?.(newIndex); + }, [emblaApi, onChange]); useEffect(() => { if (!emblaApi) return; diff --git a/libs/ui/packages/react/src/components/layout/Carousel/types.tsx b/libs/ui/packages/react/src/components/layout/Carousel/types.tsx index d746d306b03a..9096d9638f9c 100644 --- a/libs/ui/packages/react/src/components/layout/Carousel/types.tsx +++ b/libs/ui/packages/react/src/components/layout/Carousel/types.tsx @@ -6,12 +6,13 @@ export type Variant = "content-card" | "default"; export type Props = { children: ReactElement[]; variant?: Variant; + onChange?: (index: number) => void; }; /** * Carousel's sub props to be passed to any component used by the carousel.. */ -export type SubProps = Required & { +export type SubProps = Required> & { emblaApi: UseEmblaCarouselType[1]; currentIndex: number; }; diff --git a/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/PortfolioContentCard.stories.tsx b/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/PortfolioContentCard.stories.tsx new file mode 100644 index 000000000000..d08b10c9eb08 --- /dev/null +++ b/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/PortfolioContentCard.stories.tsx @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import PortfolioContentCard, { PortfolioContentCardProps } from "."; + +export default { + title: "Layout/ContentCard/PortfolioContentCard", + component: PortfolioContentCard, + argTypes: { + title: { + description: "Title of the card.", + }, + cta: { + description: "Call to action text.", + }, + description: { + description: "Description of the card.", + }, + tag: { + description: "Tag to be displayed on top of the card.", + }, + image: { + description: "Image to be displayed on the right of the card.", + }, + onClick: { + description: "Function to be called when the card is clicked.", + }, + onClose: { + description: "Function to be called when the close button is clicked.", + }, + }, + args: { + title: "Ledger Recover", + description: "Get peace of mind and start your free trial.", + cta: "Start my free trial", + image: + "data:image/svg+xml,", + }, +} satisfies Meta; + +export const WithCta: StoryObj = {}; + +export const WithoutCta: StoryObj = { + args: { cta: undefined }, +}; + +export const WithTag: StoryObj = { + args: { tag: "New" }, +}; diff --git a/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/index.tsx b/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/index.tsx new file mode 100644 index 000000000000..d0447785af1f --- /dev/null +++ b/libs/ui/packages/react/src/components/layout/ContentCard/PortfolioContentCard/index.tsx @@ -0,0 +1,106 @@ +import React, { type ReactEventHandler } from "react"; +import styled from "styled-components"; + +import { StyleProvider } from "../../../../styles"; +import { Icons } from "../../../../assets"; +import { Text } from "../../../asorted"; +import { Button } from "../../../cta"; +import Tag from "../../../Tag"; + +export type PortfolioContentCardProps = { + title: string; + cta?: string; + description?: string; + tag?: string; + image?: string; + + onClick: ReactEventHandler; + onClose: ReactEventHandler; +}; + +export default function PortfolioContentCard({ + title, + cta, + description, + tag, + image, + onClick, + onClose, +}: PortfolioContentCardProps) { + const handleClose: ReactEventHandler = event => { + event.stopPropagation(); + onClose(event); + }; + + return ( + + {tag && {tag}} + {title} + {description && {description}} + {cta && ( + + )} + + + + + ); +} + +const StyledTag = styled(Tag).attrs({ size: "medium", type: "plain", active: true })` + font-size: 11px; + background-color: ${p => p.theme.colors.primary.c80}; +`; + +const Title = styled(Text).attrs({ variant: "h4Inter" })` + font-family: Inter; + font-size: 24px; + font-weight: 600; +`; + +const Desc = styled(Text).attrs({ variant: "small", color: "neutral.c70" })` + font-family: Inter; + font-size: 13px; + font-style: normal; + font-weight: 500; + padding-bottom: 8px; +`; + +const Wrapper = styled.div>` + background-color: ${p => p.theme.colors.background.card}; + background-image: ${p => (p.image ? `url("${p.image}")` : "none")}; + background-position: right center; + background-repeat: no-repeat; + background-size: 50% auto; + + cursor: pointer; + padding: 16px; + padding-top: ${p => (p.tag ? "16px" : "24px")}; + padding-right: 50%; + + position: relative; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: flex-start; + gap: 4px; +`; + +const CloseButton = styled(Button).attrs({ + Icon: , + iconButton: true, + outline: true, +})` + background-color: ${p => p.theme.colors.neutral.c30}; + position: absolute; + top: 10px; + right: 10px; + width: 24px; + height: 24px; + svg { + width: 12px; + height: 12px; + } +`; diff --git a/libs/ui/packages/react/src/components/layout/index.ts b/libs/ui/packages/react/src/components/layout/index.ts index 5fbaf23d7bbd..ddb921fa497f 100644 --- a/libs/ui/packages/react/src/components/layout/index.ts +++ b/libs/ui/packages/react/src/components/layout/index.ts @@ -7,3 +7,4 @@ export { default as Drawer } from "./Drawer"; export { default as Carousel } from "./Carousel"; export { default as VerticalTimeline } from "./List/VerticalTimeline"; export { default as NumberedList } from "./List/NumberedList"; +export { default as PortfolioContentCard } from "./ContentCard/PortfolioContentCard"; diff --git a/tools/actions/composites/update-snapshots-desktop/action.yml b/tools/actions/composites/update-snapshots-desktop/action.yml index 04f0336cf26e..36cee6473d2e 100644 --- a/tools/actions/composites/update-snapshots-desktop/action.yml +++ b/tools/actions/composites/update-snapshots-desktop/action.yml @@ -9,33 +9,41 @@ runs: using: "composite" steps: - name: Update playwright snapshots [Linux => xvfb-run] - if: startsWith(inputs.os, 'ubuntu') - run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- pnpm desktop test:playwright:update-snapshots - shell: bash - name: Update playwright snapshots + if: ${{ startsWith(inputs.os, 'ubuntu') }} + run: | + xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- pnpm desktop test:playwright:update-snapshots + shell: bash + + - name: Update playwright snapshots if: ${{ !startsWith(inputs.os, 'ubuntu') }} - run: pnpm desktop test:playwright:update-snapshots + run: | + pnpm desktop test:playwright:update-snapshots shell: bash - - name: status (Linux | macOS) + + - name: Status (Linux | macOS) if: ${{ !startsWith(inputs.os, 'windows') }} id: status run: | echo "status=$(git status --porcelain | wc -l)" >> $GITHUB_OUTPUT shell: bash - - name: status (Windows) + + - name: Status (Windows) id: status-windows - if: startsWith(inputs.os, 'windows') + if: ${{ startsWith(inputs.os, 'windows') }} run: | $out = $(git status --porcelain | measure -l | Format-Wide | Out-String -Stream) $out = $out.Trim() echo "status=$out" >> $env:GITHUB_OUTPUT shell: pwsh + - id: changes run: | echo ${{ steps.status.outputs.status }} echo "changes=$(git status -s)" shell: bash + - name: Commit snapshots - if: steps.status.outputs.status != 0 || steps.status-windows.outputs.status != 0 + if: ${{ steps.status.outputs.status != 0 || steps.status-windows.outputs.status != 0 }} run: | git add ./apps/ledger-live-desktop/tests/specs && git commit -m "test(lld): update screenshots (${{ inputs.os }}) ${{ steps.changes.outputs.changes }} lld, test, screenshot" && @@ -44,6 +52,7 @@ runs: git push || echo "" shell: bash + - name: Upload playwright results [On Failure] uses: actions/upload-artifact@v4 if: failure() && !cancelled()