Skip to content

Commit

Permalink
feat(LLD) Post Onboarding New Items
Browse files Browse the repository at this point in the history
  • Loading branch information
male-gal committed Nov 28, 2023
1 parent f27d82a commit 5d03bf5
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 50 deletions.
6 changes: 6 additions & 0 deletions .changeset/poor-rats-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@ledgerhq/types-live": patch
"ledger-live-desktop": patch
---

Adding new items to LLD post onboarding
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,14 +18,25 @@ const ActionRowWrapper = styled(Flex)<{ completed: boolean }>`
const PostOnboardingActionRow: React.FC<Props> = 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 (
<ActionRowWrapper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,29 @@ const customImage: PostOnboardingAction = {
buttonLabelForAnalyticsEvent: "Set lock screen picture",
};

const assetsTransfer: PostOnboardingAction = {
id: PostOnboardingActionId.assetsTransfer,
featureFlagId: "postOnboardingAssetsTransfer",
Icon: Icons.ArrowDown,
title: "postOnboarding.actions.assetsTransfer.title",
titleCompleted: "postOnboarding.actions.assetsTransfer.titleCompleted",
description: "postOnboarding.actions.assetsTransfer.description",
actionCompletedPopupLabel: "postOnboarding.actions.assetsTransfer.popupLabel",
buttonLabelForAnalyticsEvent: "Secure your assets on Ledger",
startAction: ({ openModalCallback }) => 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.
*/
Expand All @@ -64,6 +87,8 @@ const postOnboardingActions: { [id in PostOnboardingActionId]?: PostOnboardingAc
migrateAssetsMock,
personalizeMock,
customImage,
assetsTransfer,
buyCrypto,
};

/**
Expand Down Expand Up @@ -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 [];
}
Expand Down
117 changes: 76 additions & 41 deletions apps/ledger-live-desktop/src/renderer/modals/Receive/index.tsx
Original file line number Diff line number Diff line change
@@ -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<State>(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 (
<Modal
name="MODAL_RECEIVE"
centered
onHide={this.handleReset}
preventBackdropClick={isModalLocked}
render={({ data, onClose }) => (
<Body
onClose={onClose}
stepId={stepId}
isAddressVerified={isAddressVerified}
verifyAddressError={verifyAddressError}
onChangeAddressVerified={this.handleChangeAddressVerified}
onChangeStepId={this.handleStepChange}
params={data || {}}
/>
)}
/>
// 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 (
<Modal
name="MODAL_RECEIVE"
centered
onHide={handleReset}
preventBackdropClick={isModalLocked}
render={({ data, onClose }) => (
<Body
onClose={onClose}
stepId={stepId}
isAddressVerified={isAddressVerified}
verifyAddressError={verifyAddressError}
onChangeAddressVerified={handleChangeAddressVerified}
onChangeStepId={setStepId}
params={data || {}}
/>
)}
/>
);
};

export default ReceiveModal;
1 change: 1 addition & 0 deletions apps/ledger-live-desktop/src/renderer/modals/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ const LiveAppExchange = ({ appId }: { appId: string }) => {
grow
style={{
overflow: "hidden",
height: "100%",
}}
>
{manifest ? (
Expand All @@ -103,8 +104,8 @@ export type ExchangeComponentParams = {
};

const Exchange = ({ match }: RouteComponentProps<ExchangeComponentParams>) => {
const { params } = match;
const appId = match?.params?.appId;

return <LiveAppExchange appId={params.appId || DEFAULT_MULTIBUY_APP_ID} />;
return <LiveAppExchange appId={appId || DEFAULT_MULTIBUY_APP_ID} />;
};
export default Exchange;
12 changes: 12 additions & 0 deletions apps/ledger-live-desktop/static/i18n/en/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
7 changes: 6 additions & 1 deletion libs/ledgerjs/packages/types-live/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ Ledger Live main types.
* [PostOnboardingActionId](#postonboardingactionid)
* [navigationParams](#navigationparams)
* [startAction](#startaction)
* [](#)
* [PostOnboardingAction](#postonboardingaction)
* [disabled](#disabled)
* [featureFlagId](#featureflagid)
Expand Down Expand Up @@ -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

Expand Down
6 changes: 4 additions & 2 deletions libs/ledgerjs/packages/types-live/src/postOnboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 5d03bf5

Please sign in to comment.