From 5d03bf514fcf7aea91dc8beae0cd503ed6b4ab3c Mon Sep 17 00:00:00 2001 From: male-gal Date: Tue, 28 Nov 2023 16:19:47 +0100 Subject: [PATCH] feat(LLD) Post Onboarding New Items --- .changeset/poor-rats-repeat.md | 6 + .../PostOnboardingActionRow.tsx | 20 ++- .../PostOnboardingHub/logic/index.tsx | 27 +++- .../src/renderer/modals/Receive/index.tsx | 117 ++++++++++++------ .../src/renderer/modals/types.ts | 1 + .../src/renderer/screens/exchange/index.tsx | 5 +- .../static/i18n/en/app.json | 12 ++ libs/ledgerjs/packages/types-live/README.md | 7 +- .../packages/types-live/src/postOnboarding.ts | 6 +- 9 files changed, 151 insertions(+), 50 deletions(-) create mode 100644 .changeset/poor-rats-repeat.md diff --git a/.changeset/poor-rats-repeat.md b/.changeset/poor-rats-repeat.md new file mode 100644 index 000000000000..83175cb0bf75 --- /dev/null +++ b/.changeset/poor-rats-repeat.md @@ -0,0 +1,6 @@ +--- +"@ledgerhq/types-live": patch +"ledger-live-desktop": patch +--- + +Adding new items to LLD post onboarding diff --git a/apps/ledger-live-desktop/src/renderer/components/PostOnboardingHub/PostOnboardingActionRow.tsx b/apps/ledger-live-desktop/src/renderer/components/PostOnboardingHub/PostOnboardingActionRow.tsx index 39b05750040c..86b23768ec6b 100644 --- a/apps/ledger-live-desktop/src/renderer/components/PostOnboardingHub/PostOnboardingActionRow.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/PostOnboardingHub/PostOnboardingActionRow.tsx @@ -1,10 +1,13 @@ import React, { useCallback } from "react"; import { Flex, Icons, Tag, Text } from "@ledgerhq/react-ui"; import { useTranslation } from "react-i18next"; -// import { useHistory } from "react-router-dom"; import { PostOnboardingActionState, PostOnboardingAction } from "@ledgerhq/types-live"; import { track } from "~/renderer/analytics/segment"; import styled from "styled-components"; +import { useDispatch } from "react-redux"; +import { openModal } from "~/renderer/actions/modals"; +import { AllModalNames } from "~/renderer/modals/types"; +import { useHistory } from "react-router"; export type Props = PostOnboardingAction & PostOnboardingActionState; @@ -15,14 +18,25 @@ const ActionRowWrapper = styled(Flex)<{ completed: boolean }>` const PostOnboardingActionRow: React.FC = props => { const { id, Icon, title, description, tagLabel, buttonLabelForAnalyticsEvent, completed } = props; const { t } = useTranslation(); + const dispatch = useDispatch(); + const history = useHistory(); const handleStartAction = useCallback(() => { + const openModalCallback = (modalName: AllModalNames) => { + dispatch(openModal(modalName, undefined)); + }; + const navigationCallback = (route: string) => { + history.push({ + pathname: route, + }); + }; + if ("startAction" in props) { - props.startAction(); + props.startAction({ openModalCallback, navigationCallback }); buttonLabelForAnalyticsEvent && track("button_clicked", { button: buttonLabelForAnalyticsEvent, flow: "post-onboarding" }); } - }, [props, buttonLabelForAnalyticsEvent]); + }, [props, dispatch, history, buttonLabelForAnalyticsEvent]); return ( openModalCallback("MODAL_RECEIVE"), +}; + +const buyCrypto: PostOnboardingAction = { + id: PostOnboardingActionId.buyCrypto, + Icon: Icons.Plus, + title: "postOnboarding.actions.buyCrypto.title", + titleCompleted: "postOnboarding.actions.buyCrypto.titleCompleted", + description: "postOnboarding.actions.buyCrypto.description", + actionCompletedPopupLabel: "postOnboarding.actions.buyCrypto.popupLabel", + buttonLabelForAnalyticsEvent: "Buy Crypto", + startAction: ({ navigationCallback }) => navigationCallback("/exchange"), +}; + /** * All implemented post onboarding actions. */ @@ -64,6 +87,8 @@ const postOnboardingActions: { [id in PostOnboardingActionId]?: PostOnboardingAc migrateAssetsMock, personalizeMock, customImage, + assetsTransfer, + buyCrypto, }; /** @@ -106,7 +131,7 @@ export function getPostOnboardingActionsForDevice( * Set here the list of actions for the post onboarding of the * DeviceModelId.stax * */ - return [customImage]; + return [customImage, assetsTransfer, buyCrypto]; default: return []; } diff --git a/apps/ledger-live-desktop/src/renderer/modals/Receive/index.tsx b/apps/ledger-live-desktop/src/renderer/modals/Receive/index.tsx index 3476aaa67d62..eefc78ff03a4 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/Receive/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/modals/Receive/index.tsx @@ -1,61 +1,96 @@ -import React, { PureComponent } from "react"; +import React, { useState, useCallback, useEffect } from "react"; import logger from "~/renderer/logger"; import Modal from "~/renderer/components/Modal"; import Body, { StepId } from "./Body"; +import { useDispatch, useSelector } from "react-redux"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import { openModal, closeModal } from "~/renderer/actions/modals"; + type State = { stepId: StepId; isAddressVerified: boolean | undefined | null; verifyAddressError: Error | undefined | null; }; + const INITIAL_STATE = { stepId: "account" as StepId, isAddressVerified: null, verifyAddressError: null, }; -class ReceiveModal extends PureComponent<{}, State> { - state = INITIAL_STATE; - handleReset = () => - this.setState({ - ...INITIAL_STATE, - }); - - handleStepChange = (stepId: StepId) => - this.setState({ - stepId, - }); - - handleChangeAddressVerified = (isAddressVerified?: boolean | null, err?: Error | null) => { +const ReceiveModal = () => { + const [state, setState] = useState(INITIAL_STATE); + + const { stepId, isAddressVerified, verifyAddressError } = state; + + const setStepId = (newStepId: State["stepId"]) => { + setState(prevState => ({ ...prevState, stepId: newStepId })); + }; + + const setIsAddressVerified = (newIsAddressVerified: State["isAddressVerified"]) => { + setState(prevState => ({ ...prevState, isAddressVerified: newIsAddressVerified })); + }; + + const setVerifyAddressError = (newVerifyAddressError: State["verifyAddressError"]) => { + setState(prevState => ({ ...prevState, verifyAddressError: newVerifyAddressError })); + }; + + const handleReset = () => { + setStepId(INITIAL_STATE.stepId); + setIsAddressVerified(INITIAL_STATE.isAddressVerified); + setVerifyAddressError(INITIAL_STATE.verifyAddressError); + }; + + const handleChangeAddressVerified = (isAddressVerified?: boolean | null, err?: Error | null) => { if (err && err.name !== "UserRefusedAddress") { logger.critical(err); } - this.setState({ - isAddressVerified, - verifyAddressError: err, - }); + setIsAddressVerified(isAddressVerified); + setVerifyAddressError(err); }; - render() { - const { stepId, isAddressVerified, verifyAddressError } = this.state; - const isModalLocked = stepId === "receive" && isAddressVerified === null; - return ( - ( - - )} - /> + // Making sure at least one account exists, if not, redirecting to the add account modal + const accounts = useSelector(accountsSelector); + + const dispatch = useDispatch(); + const hasAccounts = !!accounts.length; + + const openAddAccounts = useCallback(() => { + dispatch(closeModal("MODAL_RECEIVE")); + dispatch( + openModal("MODAL_ADD_ACCOUNTS", { + currency: null, + }), ); - } -} + }, [dispatch]); + + useEffect(() => { + if (!hasAccounts) { + openAddAccounts(); + } + }, [hasAccounts, openAddAccounts]); + + if (!hasAccounts) return null; + + const isModalLocked = stepId === "receive" && isAddressVerified === null; + return ( + ( + + )} + /> + ); +}; + export default ReceiveModal; diff --git a/apps/ledger-live-desktop/src/renderer/modals/types.ts b/apps/ledger-live-desktop/src/renderer/modals/types.ts index d01f33b499eb..3fe6875482a2 100644 --- a/apps/ledger-live-desktop/src/renderer/modals/types.ts +++ b/apps/ledger-live-desktop/src/renderer/modals/types.ts @@ -91,3 +91,4 @@ export type GlobalModalData = { * finally, we make a union with the coin modals data and we obtain the complete modal data type. */ export type ModalData = GlobalModalData & CoinModalsData; +export type AllModalNames = keyof ModalData; diff --git a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx index a2cc6363fba3..e4f261bf8cb4 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/exchange/index.tsx @@ -81,6 +81,7 @@ const LiveAppExchange = ({ appId }: { appId: string }) => { grow style={{ overflow: "hidden", + height: "100%", }} > {manifest ? ( @@ -103,8 +104,8 @@ export type ExchangeComponentParams = { }; const Exchange = ({ match }: RouteComponentProps) => { - const { params } = match; + const appId = match?.params?.appId; - return ; + return ; }; export default Exchange; diff --git a/apps/ledger-live-desktop/static/i18n/en/app.json b/apps/ledger-live-desktop/static/i18n/en/app.json index d99ff6e91f69..b34f570a23a5 100644 --- a/apps/ledger-live-desktop/static/i18n/en/app.json +++ b/apps/ledger-live-desktop/static/i18n/en/app.json @@ -1300,6 +1300,18 @@ "title": "What's next for your Ledger Stax?", "description": "Claim the Infinity NFT, customize it's lock screen, or\ntransfer your assets from other exchanges.", "link": "See what's next" + }, + "actions": { + "buyCrypto": { + "title": "Buy Crypto", + "description": "Choose from 500+ coins and the best quotes from buy-providers, tailored to your preferences.", + "actionCompletedPopupLabel": "Crypto bought" + }, + "assetsTransfer": { + "title": "Transfer your assets to Ledger", + "description": "Easily transfer assets from Coinbase or any other exchange or wallet.", + "actionCompletedPopupLabel": "Assets transferred" + } } }, "hideNftCollection": { diff --git a/libs/ledgerjs/packages/types-live/README.md b/libs/ledgerjs/packages/types-live/README.md index 9688740c7bd2..65a765348974 100644 --- a/libs/ledgerjs/packages/types-live/README.md +++ b/libs/ledgerjs/packages/types-live/README.md @@ -146,6 +146,7 @@ Ledger Live main types. * [PostOnboardingActionId](#postonboardingactionid) * [navigationParams](#navigationparams) * [startAction](#startaction) +* [](#) * [PostOnboardingAction](#postonboardingaction) * [disabled](#disabled) * [featureFlagId](#featureflagid) @@ -1194,7 +1195,11 @@ Type: [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global The function to call when the user presses the button for this action -Type: function (): void +Type: function (openModalCallback: any, navigationCallback: any): void + +### + +Optional Redux dispatch function ### PostOnboardingAction diff --git a/libs/ledgerjs/packages/types-live/src/postOnboarding.ts b/libs/ledgerjs/packages/types-live/src/postOnboarding.ts index 83eb489721c4..95b024ffbb73 100644 --- a/libs/ledgerjs/packages/types-live/src/postOnboarding.ts +++ b/libs/ledgerjs/packages/types-live/src/postOnboarding.ts @@ -29,9 +29,11 @@ type WithStartActionFunction = { /** * The function to call when the user presses the button for this action */ - startAction: () => void; + startAction: (openModalCallback?: any, navigationCallback?: any) => void; + /** + * Optional Redux dispatch function + */ }; - /** * All necessary information for complete integration of a post onboarding * action.