diff --git a/.changeset/wild-spies-cry.md b/.changeset/wild-spies-cry.md new file mode 100644 index 000000000000..cbe60a7500ea --- /dev/null +++ b/.changeset/wild-spies-cry.md @@ -0,0 +1,51 @@ +--- +"@ledgerhq/live-common": major +"@ledgerhq/devices": major +"@ledgerhq/hw-app-btc": major +"@ledgerhq/live-cli": patch +"ledger-live-desktop": patch +"live-mobile": patch +"@ledgerhq/hw-app-eth": patch +"@ledgerhq/hw-transport-node-ble": patch +"@ledgerhq/hw-transport-node-hid-noevents": patch +"@ledgerhq/hw-transport-web-ble": patch +"@ledgerhq/hw-transport-webhid": patch +"@ledgerhq/hw-transport-webusb": patch +"@ledgerhq/react-native-hw-transport-ble": patch +"@ledgerhq/icons-ui": patch +"@ledgerhq/react-ui": patch +"has-hash-commit-deps": patch +"@actions/submit-bot-report": patch +"@actions/upload-images": patch +"esbuild-utils": patch +"live-github-bot": patch +"native-modules-tools": patch +--- + +#### Replace [webpack](https://webpack.js.org/) with [vite.js](https://vitejs.dev/) to speed up the ledger live desktop development process. + +To fully embrace the "bundleless" vite.js approach, it is necessary to transpile our packages contained in the monorepository to the ESM format, and [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) have been added to silently map to commonjs or esm depending on the need. + +#### 🔥 BREAKING CHANGES for `@ledgerhq/live-common`, `@ledgerhq/devices` and `@ledgerhq/hw-app-btc` consumers. + +As highlighted [here](https://github.com/nodejs/node#39994), it is not possible to target folders directly when using subpath exports. + +The workaround is to suffix the call with `/index` (or `/`). + +For instance… + +```ts +import * as currencies from "@ledgerhq/live-common/currencies"; +``` + +…must be rewritten to… + +```ts +import * as currencies from "@ledgerhq/live-common/currencies/index;"; +``` + +…or: + +```ts +import * as currencies from "@ledgerhq/live-common/currencies/;"; +``` diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs index 95e657a107ff..c7c8561f077b 100644 --- a/.pnpmfile.cjs +++ b/.pnpmfile.cjs @@ -50,8 +50,8 @@ function readPackage(pkg, context) { addDevDependencies( /^@ledgerhq\/(hw-app.*|hw-transport.*|cryptoassets|devices|errors|logs|react-native-hid|react-native-hw-transport-ble|types-.*)$/, { - jest: "^27.4.7", - "ts-jest": "^27.1.2", + jest: "^28.1.1", + "ts-jest": "^28.0.5", "ts-node": "^10.4.0", "@types/node": "*", "@types/jest": "*", @@ -159,6 +159,9 @@ function readPackage(pkg, context) { addPeerDependencies("jest-worker", { metro: "*", }), + addPeerDependencies("react-lottie", { + "prop-types": "*", + }), // "dmg-builder" is required to build .dmg electron apps on macs, // but is not declared as such by app-builder-lib. // I'm not adding it as a dependency because if I did, diff --git a/apps/cli/src/cli.ts b/apps/cli/src/cli.ts index 77d3f9618478..f47750a7dc2d 100644 --- a/apps/cli/src/cli.ts +++ b/apps/cli/src/cli.ts @@ -6,7 +6,7 @@ import commandLineArgs from "command-line-args"; import { closeAllDevices } from "./live-common-setup"; import commandsMain from "./commands-index"; // TODO cli-transaction.js => cli.js -import perFamily from "@ledgerhq/live-common/lib/generated/cli-transaction"; +import perFamily from "@ledgerhq/live-common/generated/cli-transaction"; const commands = { ...Object.values(perFamily) diff --git a/apps/cli/src/commands/app.ts b/apps/cli/src/commands/app.ts index 3193682277d7..601745352fb6 100644 --- a/apps/cli/src/commands/app.ts +++ b/apps/cli/src/commands/app.ts @@ -1,13 +1,13 @@ import { from, concat } from "rxjs"; import { map, mergeMap, ignoreElements } from "rxjs/operators"; -import manager from "@ledgerhq/live-common/lib/manager"; -import type { DeviceInfo } from "@ledgerhq/live-common/lib/types/manager"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import openApp from "@ledgerhq/live-common/lib/hw/openApp"; -import quitApp from "@ledgerhq/live-common/lib/hw/quitApp"; -import installApp from "@ledgerhq/live-common/lib/hw/installApp"; -import uninstallApp from "@ledgerhq/live-common/lib/hw/uninstallApp"; +import manager from "@ledgerhq/live-common/manager/index"; +import type { DeviceInfo } from "@ledgerhq/live-common/types/manager"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import openApp from "@ledgerhq/live-common/hw/openApp"; +import quitApp from "@ledgerhq/live-common/hw/quitApp"; +import installApp from "@ledgerhq/live-common/hw/installApp"; +import uninstallApp from "@ledgerhq/live-common/hw/uninstallApp"; import { deviceOpt, inferManagerApp } from "../scan"; export default { description: "Manage Ledger device's apps", diff --git a/apps/cli/src/commands/appUninstallAll.ts b/apps/cli/src/commands/appUninstallAll.ts index e173c6352a45..5950bcd7baea 100644 --- a/apps/cli/src/commands/appUninstallAll.ts +++ b/apps/cli/src/commands/appUninstallAll.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { from } from "rxjs"; import { mergeMap, filter, map } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import { reducer, runAll } from "@ledgerhq/live-common/lib/apps"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import { reducer, runAll } from "@ledgerhq/live-common/apps/index"; +import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; import { deviceOpt } from "../scan"; export default { description: "uninstall all apps in the device", diff --git a/apps/cli/src/commands/appsCheckAllAppVersions.ts b/apps/cli/src/commands/appsCheckAllAppVersions.ts index 0c4abda07dda..d6c48ee16c70 100644 --- a/apps/cli/src/commands/appsCheckAllAppVersions.ts +++ b/apps/cli/src/commands/appsCheckAllAppVersions.ts @@ -9,22 +9,22 @@ import { filter, map, } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import ManagerAPI from "@ledgerhq/live-common/lib/api/Manager"; -import network from "@ledgerhq/live-common/lib/network"; -import installApp from "@ledgerhq/live-common/lib/hw/installApp"; -import uninstallApp from "@ledgerhq/live-common/lib/hw/uninstallApp"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import ManagerAPI from "@ledgerhq/live-common/api/Manager"; +import network from "@ledgerhq/live-common/network"; +import installApp from "@ledgerhq/live-common/hw/installApp"; +import uninstallApp from "@ledgerhq/live-common/hw/uninstallApp"; import type { DeviceInfo, ApplicationVersion, Application, -} from "@ledgerhq/live-common/lib/types/manager"; -import { initState, reducer, runAll } from "@ledgerhq/live-common/lib/apps"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; -import { delay } from "@ledgerhq/live-common/lib/promise"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { getDependencies } from "@ledgerhq/live-common/lib/apps/polyfill"; +} from "@ledgerhq/live-common/types/manager"; +import { initState, reducer, runAll } from "@ledgerhq/live-common/apps/index"; +import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import { delay } from "@ledgerhq/live-common/promise"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { getDependencies } from "@ledgerhq/live-common/apps/polyfill"; import { deviceOpt } from "../scan"; type ResultCommon = { versionId: number; diff --git a/apps/cli/src/commands/appsInstallAll.ts b/apps/cli/src/commands/appsInstallAll.ts index 7f0dbe073479..adbf35be9242 100644 --- a/apps/cli/src/commands/appsInstallAll.ts +++ b/apps/cli/src/commands/appsInstallAll.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import { from } from "rxjs"; import { mergeMap, filter, map } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import { initState, reducer, runAll } from "@ledgerhq/live-common/lib/apps"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import { initState, reducer, runAll } from "@ledgerhq/live-common/apps/index"; +import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; import { deviceOpt } from "../scan"; export default { description: "test script to install and uninstall all apps", diff --git a/apps/cli/src/commands/appsUpdateTestAll.ts b/apps/cli/src/commands/appsUpdateTestAll.ts index 02b8f6d4987a..6d716636904e 100644 --- a/apps/cli/src/commands/appsUpdateTestAll.ts +++ b/apps/cli/src/commands/appsUpdateTestAll.ts @@ -1,16 +1,16 @@ /* eslint-disable no-console */ import { from, of, Observable } from "rxjs"; import { mergeMap, ignoreElements, filter, map } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { initState, reducer, runAll, getActionPlan, -} from "@ledgerhq/live-common/lib/apps"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; -import type { AppOp } from "@ledgerhq/live-common/lib/apps/types"; +} from "@ledgerhq/live-common/apps/index"; +import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import type { AppOp } from "@ledgerhq/live-common/apps/types"; import { deviceOpt } from "../scan"; const prettyActionPlan = (ops: AppOp[]) => diff --git a/apps/cli/src/commands/balanceHistory.ts b/apps/cli/src/commands/balanceHistory.ts index 98d08cf25602..d7957a24fbf3 100644 --- a/apps/cli/src/commands/balanceHistory.ts +++ b/apps/cli/src/commands/balanceHistory.ts @@ -2,14 +2,14 @@ import { BigNumber } from "bignumber.js"; import asciichart from "asciichart"; import invariant from "invariant"; import { map } from "rxjs/operators"; -import { toBalanceHistoryRaw } from "@ledgerhq/live-common/lib/account"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/types"; +import { toBalanceHistoryRaw } from "@ledgerhq/live-common/account/index"; +import type { PortfolioRange } from "@ledgerhq/live-common/types/index"; import { getBalanceHistory, getPortfolioCount, -} from "@ledgerhq/live-common/lib/portfolio/v2"; -import { getRanges } from "@ledgerhq/live-common/lib/portfolio/v2/range"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/portfolio/v2/index"; +import { getRanges } from "@ledgerhq/live-common/portfolio/v2/range"; +import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; const histoFormatters = { diff --git a/apps/cli/src/commands/bot.ts b/apps/cli/src/commands/bot.ts index afc6268fde77..7e70afd821be 100644 --- a/apps/cli/src/commands/bot.ts +++ b/apps/cli/src/commands/bot.ts @@ -1,8 +1,8 @@ /* eslint-disable no-console */ import { generateMnemonic } from "bip39"; import { from } from "rxjs"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { bot } from "@ledgerhq/live-common/lib/bot"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { bot } from "@ledgerhq/live-common/bot/index"; import { currencyOpt } from "../scan"; export default { description: diff --git a/apps/cli/src/commands/botPortfolio.ts b/apps/cli/src/commands/botPortfolio.ts index 64d5456a9cdf..d8e8b5e9b36a 100644 --- a/apps/cli/src/commands/botPortfolio.ts +++ b/apps/cli/src/commands/botPortfolio.ts @@ -1,8 +1,8 @@ import { from, defer, throwError } from "rxjs"; import { catchError, filter, map, mergeAll, timeoutWith } from "rxjs/operators"; -import { listSupportedCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { getCurrencyBridge } from "@ledgerhq/live-common/lib/bridge"; -import { accountFormatters } from "@ledgerhq/live-common/lib/account"; +import { listSupportedCurrencies } from "@ledgerhq/live-common/currencies/index"; +import { getCurrencyBridge } from "@ledgerhq/live-common/bridge/index"; +import { accountFormatters } from "@ledgerhq/live-common/account/index"; const blacklist = ["decred", "tezos", "stellar", "ethereum_classic"]; export default { description: diff --git a/apps/cli/src/commands/botTransfer.ts b/apps/cli/src/commands/botTransfer.ts index 95897662b8df..78c8649fd06c 100644 --- a/apps/cli/src/commands/botTransfer.ts +++ b/apps/cli/src/commands/botTransfer.ts @@ -11,30 +11,30 @@ import { import { listSupportedCurrencies, getFiatCurrencyByTicker, -} from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/currencies/index"; import { getAccountBridge, getCurrencyBridge, -} from "@ledgerhq/live-common/lib/bridge"; -import { getEnv, setEnv } from "@ledgerhq/live-common/lib/env"; -import { promiseAllBatched } from "@ledgerhq/live-common/lib/promise"; -import { Account } from "@ledgerhq/live-common/lib/types"; -import { makeBridgeCacheSystem } from "@ledgerhq/live-common/lib/bridge/cache"; +} from "@ledgerhq/live-common/bridge/index"; +import { getEnv, setEnv } from "@ledgerhq/live-common/env"; +import { promiseAllBatched } from "@ledgerhq/live-common/promise"; +import { Account } from "@ledgerhq/live-common/types/index"; +import { makeBridgeCacheSystem } from "@ledgerhq/live-common/bridge/cache"; import { autoSignTransaction, getImplicitDeviceAction, -} from "@ledgerhq/live-common/lib/bot/engine"; +} from "@ledgerhq/live-common/bot/engine"; import { createImplicitSpeculos, releaseSpeculosDevice, -} from "@ledgerhq/live-common/lib/load/speculos"; -import { formatOperation } from "@ledgerhq/live-common/lib/account"; +} from "@ledgerhq/live-common/load/speculos"; +import { formatOperation } from "@ledgerhq/live-common/account/index"; import { calculate, inferTrackingPairForAccounts, initialState, loadCountervalues, -} from "@ledgerhq/live-common/lib/countervalues/logic"; +} from "@ledgerhq/live-common/countervalues/logic"; const CONCURRENT = 3; diff --git a/apps/cli/src/commands/broadcast.ts b/apps/cli/src/commands/broadcast.ts index 7eb1a7a52d03..f1ab782684d0 100644 --- a/apps/cli/src/commands/broadcast.ts +++ b/apps/cli/src/commands/broadcast.ts @@ -1,7 +1,7 @@ import { from } from "rxjs"; import { map, concatMap } from "rxjs/operators"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; -import { toOperationRaw } from "@ledgerhq/live-common/lib/account"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; +import { toOperationRaw } from "@ledgerhq/live-common/account/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import type { InferSignedOperationsOpts } from "../signedOperation"; diff --git a/apps/cli/src/commands/countervalues.ts b/apps/cli/src/commands/countervalues.ts index f4ad58c166b0..fd276bf0190a 100644 --- a/apps/cli/src/commands/countervalues.ts +++ b/apps/cli/src/commands/countervalues.ts @@ -6,26 +6,26 @@ import { BigNumber } from "bignumber.js"; import asciichart from "asciichart"; import invariant from "invariant"; import { Observable } from "rxjs"; -import { toBalanceHistoryRaw } from "@ledgerhq/live-common/lib/account"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/types"; -import { getPortfolioCountByDate } from "@ledgerhq/live-common/lib/portfolio/v2"; +import { toBalanceHistoryRaw } from "@ledgerhq/live-common/account/index"; +import type { PortfolioRange } from "@ledgerhq/live-common/types/index"; +import { getPortfolioCountByDate } from "@ledgerhq/live-common/portfolio/v2/index"; import { getRanges, getDates, -} from "@ledgerhq/live-common/lib/portfolio/v2/range"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/portfolio/v2/range"; +import type { Currency } from "@ledgerhq/live-common/types/index"; import { formatCurrencyUnit, findCurrencyByTicker, listFiatCurrencies, -} from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/currencies/index"; import { initialState, calculateMany, loadCountervalues, resolveTrackingPairs, -} from "@ledgerhq/live-common/lib/countervalues/logic"; -import CountervaluesAPI from "@ledgerhq/live-common/lib/countervalues/api"; +} from "@ledgerhq/live-common/countervalues/logic"; +import CountervaluesAPI from "@ledgerhq/live-common/countervalues/api/index"; const histoFormatters = { stats: (histo, currency, countervalue) => (currency.ticker + " to " + countervalue.ticker).padEnd(12) + diff --git a/apps/cli/src/commands/derivation.ts b/apps/cli/src/commands/derivation.ts index 7f9b528fde45..2d2e29538100 100644 --- a/apps/cli/src/commands/derivation.ts +++ b/apps/cli/src/commands/derivation.ts @@ -1,12 +1,12 @@ import { of } from "rxjs"; -import { listSupportedCurrencies } from "@ledgerhq/live-common/lib/currencies"; +import { listSupportedCurrencies } from "@ledgerhq/live-common/currencies/index"; import { getDerivationModesForCurrency, runDerivationScheme, getDerivationScheme, -} from "@ledgerhq/live-common/lib/derivation"; -import { setEnv, getEnv } from "@ledgerhq/live-common/lib/env"; -import { getAccountPlaceholderName } from "@ledgerhq/live-common/lib/account"; +} from "@ledgerhq/live-common/derivation"; +import { setEnv, getEnv } from "@ledgerhq/live-common/env"; +import { getAccountPlaceholderName } from "@ledgerhq/live-common/account/index"; export default { args: [], job: () => diff --git a/apps/cli/src/commands/devDeviceAppsScenario.ts b/apps/cli/src/commands/devDeviceAppsScenario.ts index e612d2e17f1d..494fede2f7df 100644 --- a/apps/cli/src/commands/devDeviceAppsScenario.ts +++ b/apps/cli/src/commands/devDeviceAppsScenario.ts @@ -1,18 +1,18 @@ import { from, concat, defer, Observable } from "rxjs"; import { mergeMap, filter, map, ignoreElements } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { initState, ListAppsResult, reducer, runAll, -} from "@ledgerhq/live-common/lib/apps"; -import ManagerAPI from "@ledgerhq/live-common/lib/api/Manager"; -import { listApps, execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; -import installApp from "@ledgerhq/live-common/lib/hw/installApp"; +} from "@ledgerhq/live-common/apps/index"; +import ManagerAPI from "@ledgerhq/live-common/api/Manager"; +import { listApps, execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import installApp from "@ledgerhq/live-common/hw/installApp"; import { deviceOpt } from "../scan"; -import { Application } from "@ledgerhq/live-common/lib/types/manager"; +import { Application } from "@ledgerhq/live-common/types/manager"; type Scenario = number[]; // how to add a scenario: // wget https://manager.api.live.ledger.com/api/applications @@ -22,8 +22,28 @@ type Scenario = number[]; const scenarios: Record = { "nanos160-outdated-apps": [1679, 222, 2783, 3295, 3305], "nanos160-outdated-bitcoin-apps": [ - 3295, 3305, 3319, 3325, 3302, 3324, 3298, 3297, 3318, 3309, 3322, 3304, - 3296, 3308, 3299, 3300, 3312, 3303, 3301, 3315, 3314, 3323, + 3295, + 3305, + 3319, + 3325, + 3302, + 3324, + 3298, + 3297, + 3318, + 3309, + 3322, + 3304, + 3296, + 3308, + 3299, + 3300, + 3312, + 3303, + 3301, + 3315, + 3314, + 3323, ], }; const scenariosValues = Object.keys(scenarios).join(" | "); diff --git a/apps/cli/src/commands/deviceAppVersion.ts b/apps/cli/src/commands/deviceAppVersion.ts index 91811f6091e4..c7cdd7e315c1 100644 --- a/apps/cli/src/commands/deviceAppVersion.ts +++ b/apps/cli/src/commands/deviceAppVersion.ts @@ -1,6 +1,6 @@ import { from } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getAppAndVersion from "@ledgerhq/live-common/lib/hw/getAppAndVersion"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getAppAndVersion from "@ledgerhq/live-common/hw/getAppAndVersion"; import { deviceOpt } from "../scan"; export default { args: [deviceOpt], diff --git a/apps/cli/src/commands/deviceInfo.ts b/apps/cli/src/commands/deviceInfo.ts index 7fd61a43a58a..64c95d5ef5e4 100644 --- a/apps/cli/src/commands/deviceInfo.ts +++ b/apps/cli/src/commands/deviceInfo.ts @@ -1,6 +1,6 @@ import { from } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; import { deviceOpt } from "../scan"; export default { args: [deviceOpt], diff --git a/apps/cli/src/commands/deviceVersion.ts b/apps/cli/src/commands/deviceVersion.ts index 7654c0230739..51efdedde4b3 100644 --- a/apps/cli/src/commands/deviceVersion.ts +++ b/apps/cli/src/commands/deviceVersion.ts @@ -1,6 +1,6 @@ import { from } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getVersion from "@ledgerhq/live-common/lib/hw/getVersion"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getVersion from "@ledgerhq/live-common/hw/getVersion"; import { deviceOpt } from "../scan"; export default { args: [deviceOpt], diff --git a/apps/cli/src/commands/discoverDevices.ts b/apps/cli/src/commands/discoverDevices.ts index 8e491b4f827c..8e84d27cdb9c 100644 --- a/apps/cli/src/commands/discoverDevices.ts +++ b/apps/cli/src/commands/discoverDevices.ts @@ -1,5 +1,5 @@ import { map, tap, scan as rxScan } from "rxjs/operators"; -import { discoverDevices } from "@ledgerhq/live-common/lib/hw"; +import { discoverDevices } from "@ledgerhq/live-common/hw/index"; export default { args: [ { @@ -12,7 +12,8 @@ export default { name: "interactive", alias: "i", type: Boolean, - desc: "interactive mode that accumulate the events instead of showing them", + desc: + "interactive mode that accumulate the events instead of showing them", }, ], job: ({ diff --git a/apps/cli/src/commands/envs.ts b/apps/cli/src/commands/envs.ts index 968fcd3f5b01..690b2232618b 100644 --- a/apps/cli/src/commands/envs.ts +++ b/apps/cli/src/commands/envs.ts @@ -1,10 +1,6 @@ import { from } from "rxjs"; import { map } from "rxjs/operators"; -import { - getEnvDesc, - getEnv, - getAllEnvNames, -} from "@ledgerhq/live-common/lib/env"; +import { getEnvDesc, getEnv, getAllEnvNames } from "@ledgerhq/live-common/env"; export default { description: "Print available environment variables", args: [], diff --git a/apps/cli/src/commands/estimateMaxSpendable.ts b/apps/cli/src/commands/estimateMaxSpendable.ts index fa4741e71a9a..2402626b1356 100644 --- a/apps/cli/src/commands/estimateMaxSpendable.ts +++ b/apps/cli/src/commands/estimateMaxSpendable.ts @@ -1,11 +1,11 @@ import { concat, from } from "rxjs"; import { concatMap } from "rxjs/operators"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; import { getAccountUnit, getAccountName, -} from "@ledgerhq/live-common/lib/account"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/account/index"; +import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; diff --git a/apps/cli/src/commands/exportAccounts.ts b/apps/cli/src/commands/exportAccounts.ts index 359434617f93..eef1f5781bb8 100644 --- a/apps/cli/src/commands/exportAccounts.ts +++ b/apps/cli/src/commands/exportAccounts.ts @@ -1,11 +1,11 @@ import { of, interval } from "rxjs"; import { reduce, mergeMap, shareReplay, tap } from "rxjs/operators"; import { dataToFrames } from "qrloop"; -import { encode } from "@ledgerhq/live-common/lib/cross"; +import { encode } from "@ledgerhq/live-common/cross"; import { asQR } from "../qr"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; -import { Account } from "@ledgerhq/live-common/lib/types"; +import { Account } from "@ledgerhq/live-common/types/index"; export default { description: "Export given accounts to Live QR or console for importing", args: [ diff --git a/apps/cli/src/commands/firmwareRepair.ts b/apps/cli/src/commands/firmwareRepair.ts index 1e1dbc1b6c82..571b21993ffc 100644 --- a/apps/cli/src/commands/firmwareRepair.ts +++ b/apps/cli/src/commands/firmwareRepair.ts @@ -1,4 +1,4 @@ -import repairFirmwareUpdate from "@ledgerhq/live-common/lib/hw/firmwareUpdate-repair"; +import repairFirmwareUpdate from "@ledgerhq/live-common/hw/firmwareUpdate-repair"; import { deviceOpt } from "../scan"; export default { description: "Repair a firmware update", diff --git a/apps/cli/src/commands/firmwareUpdate.ts b/apps/cli/src/commands/firmwareUpdate.ts index 9975f496148f..190bfdbcb7d7 100644 --- a/apps/cli/src/commands/firmwareUpdate.ts +++ b/apps/cli/src/commands/firmwareUpdate.ts @@ -4,17 +4,17 @@ import { mergeMap } from "rxjs/operators"; import type { DeviceInfo, FirmwareUpdateContext, -} from "@ledgerhq/live-common/lib/types/manager"; +} from "@ledgerhq/live-common/types/manager"; import { UnknownMCU } from "@ledgerhq/errors"; -import ManagerAPI from "@ledgerhq/live-common/lib/api/Manager"; -import network from "@ledgerhq/live-common/lib/network"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { getProviderId } from "@ledgerhq/live-common/lib/manager/provider"; -import manager from "@ledgerhq/live-common/lib/manager"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import prepareFirmwareUpdate from "@ledgerhq/live-common/lib/hw/firmwareUpdate-prepare"; -import mainFirmwareUpdate from "@ledgerhq/live-common/lib/hw/firmwareUpdate-main"; +import ManagerAPI from "@ledgerhq/live-common/api/Manager"; +import network from "@ledgerhq/live-common/network"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { getProviderId } from "@ledgerhq/live-common/manager/provider"; +import manager from "@ledgerhq/live-common/manager/index"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import prepareFirmwareUpdate from "@ledgerhq/live-common/hw/firmwareUpdate-prepare"; +import mainFirmwareUpdate from "@ledgerhq/live-common/hw/firmwareUpdate-main"; import { deviceOpt } from "../scan"; const listFirmwareOSU = async () => { diff --git a/apps/cli/src/commands/generateTestScanAccounts.ts b/apps/cli/src/commands/generateTestScanAccounts.ts index a1647bcd95ad..b2674ab2ac05 100644 --- a/apps/cli/src/commands/generateTestScanAccounts.ts +++ b/apps/cli/src/commands/generateTestScanAccounts.ts @@ -1,9 +1,9 @@ import { listen } from "@ledgerhq/logs"; import { map, reduce } from "rxjs/operators"; -import { accountFormatters } from "@ledgerhq/live-common/lib/account"; +import { accountFormatters } from "@ledgerhq/live-common/account/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; -import { Account } from "@ledgerhq/live-common/lib/types"; +import { Account } from "@ledgerhq/live-common/types/index"; export default { description: "Generate a test for scan accounts (live-common dataset)", args: [ diff --git a/apps/cli/src/commands/generateTestTransaction.ts b/apps/cli/src/commands/generateTestTransaction.ts index 2d9f3730946a..9d9b533ecf23 100644 --- a/apps/cli/src/commands/generateTestTransaction.ts +++ b/apps/cli/src/commands/generateTestTransaction.ts @@ -1,18 +1,18 @@ import { BigNumber } from "bignumber.js"; -import { toAccountRaw } from "@ledgerhq/live-common/lib/account"; +import { toAccountRaw } from "@ledgerhq/live-common/account/index"; import { toTransactionRaw, toSignedOperationRaw, -} from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/transaction/index"; import { listen } from "@ledgerhq/logs"; import { from, defer, concat, EMPTY, Observable } from "rxjs"; import { map, reduce, filter, switchMap, concatMap } from "rxjs/operators"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; import type { InferTransactionsOpts } from "../transaction"; import { inferTransactions, inferTransactionsOpts } from "../transaction"; -import { SignedOperation } from "@ledgerhq/live-common/lib/types"; +import { SignedOperation } from "@ledgerhq/live-common/types/index"; const toJS = (obj) => { if (typeof obj === "object" && obj) { diff --git a/apps/cli/src/commands/genuineCheck.ts b/apps/cli/src/commands/genuineCheck.ts index 799138aad557..871ff4716215 100644 --- a/apps/cli/src/commands/genuineCheck.ts +++ b/apps/cli/src/commands/genuineCheck.ts @@ -1,8 +1,8 @@ import { from } from "rxjs"; import { mergeMap } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import genuineCheck from "@ledgerhq/live-common/lib/hw/genuineCheck"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import genuineCheck from "@ledgerhq/live-common/hw/genuineCheck"; import { deviceOpt } from "../scan"; export default { description: "Perform a genuine check with Ledger's HSM", diff --git a/apps/cli/src/commands/getAddress.ts b/apps/cli/src/commands/getAddress.ts index ed83f132a313..c45e47c4f4ef 100644 --- a/apps/cli/src/commands/getAddress.ts +++ b/apps/cli/src/commands/getAddress.ts @@ -1,8 +1,8 @@ import { from } from "rxjs"; import { mergeMap } from "rxjs/operators"; -import { asDerivationMode } from "@ledgerhq/live-common/lib/derivation"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getAddress from "@ledgerhq/live-common/lib/hw/getAddress"; +import { asDerivationMode } from "@ledgerhq/live-common/derivation"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getAddress from "@ledgerhq/live-common/hw/getAddress/index"; import { currencyOpt, deviceOpt, inferCurrency } from "../scan"; export default { description: diff --git a/apps/cli/src/commands/getTransactionStatus.ts b/apps/cli/src/commands/getTransactionStatus.ts index 744dbea80584..85f38ba01d5d 100644 --- a/apps/cli/src/commands/getTransactionStatus.ts +++ b/apps/cli/src/commands/getTransactionStatus.ts @@ -5,7 +5,7 @@ import { toTransactionStatusRaw, formatTransactionStatus, formatTransaction, -} from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/transaction/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import type { InferTransactionsOpts } from "../transaction"; diff --git a/apps/cli/src/commands/liveData.ts b/apps/cli/src/commands/liveData.ts index 78fcc67ca814..fb8244fc57bb 100644 --- a/apps/cli/src/commands/liveData.ts +++ b/apps/cli/src/commands/liveData.ts @@ -3,8 +3,8 @@ import { reduce, mergeMap } from "rxjs/operators"; import fs from "fs"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; -import { toAccountRaw } from "@ledgerhq/live-common/lib/account/serialization"; -import { Account } from "@ledgerhq/live-common/lib/types"; +import { toAccountRaw } from "@ledgerhq/live-common/account/serialization"; +import { Account } from "@ledgerhq/live-common/types/index"; export default { description: "utility for Ledger Live app.json file", args: [ diff --git a/apps/cli/src/commands/makeCompoundSummary.ts b/apps/cli/src/commands/makeCompoundSummary.ts index 49c1bc155c27..cb296ecd8367 100644 --- a/apps/cli/src/commands/makeCompoundSummary.ts +++ b/apps/cli/src/commands/makeCompoundSummary.ts @@ -2,13 +2,13 @@ import { map } from "rxjs/operators"; import type { CompoundAccountSummary, LoansLikeArray, -} from "@ledgerhq/live-common/lib/compound/types"; +} from "@ledgerhq/live-common/compound/types"; import { formatCurrencyUnit, findCompoundToken, -} from "@ledgerhq/live-common/lib/currencies"; -import { makeCompoundSummaryForAccount } from "@ledgerhq/live-common/lib/compound/logic"; -import type { TokenAccount, Account } from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/currencies/index"; +import { makeCompoundSummaryForAccount } from "@ledgerhq/live-common/compound/logic"; +import type { TokenAccount, Account } from "@ledgerhq/live-common/types/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; diff --git a/apps/cli/src/commands/managerListApps.ts b/apps/cli/src/commands/managerListApps.ts index 0ecaa0240c7b..c202784a25f1 100644 --- a/apps/cli/src/commands/managerListApps.ts +++ b/apps/cli/src/commands/managerListApps.ts @@ -1,8 +1,8 @@ import { from } from "rxjs"; import { filter, map, mergeMap } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import { listApps } from "@ledgerhq/live-common/lib/apps/hw"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import { listApps } from "@ledgerhq/live-common/apps/hw"; import { deviceOpt } from "../scan"; export default { description: "List apps that can be installed on the device", diff --git a/apps/cli/src/commands/portfolio.ts b/apps/cli/src/commands/portfolio.ts index 16f9bebc1935..c2a0dc416127 100644 --- a/apps/cli/src/commands/portfolio.ts +++ b/apps/cli/src/commands/portfolio.ts @@ -7,24 +7,24 @@ import type { Account, Currency, PortfolioRange, -} from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/types/index"; import { flattenAccounts, getAccountName, -} from "@ledgerhq/live-common/lib/account"; -import { getPortfolio } from "@ledgerhq/live-common/lib/portfolio/v2"; -import { getRanges } from "@ledgerhq/live-common/lib/portfolio/v2/range"; +} from "@ledgerhq/live-common/account/index"; +import { getPortfolio } from "@ledgerhq/live-common/portfolio/v2/index"; +import { getRanges } from "@ledgerhq/live-common/portfolio/v2/range"; import { formatCurrencyUnit, findCurrencyByTicker, -} from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/currencies/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import { initialState, loadCountervalues, inferTrackingPairForAccounts, -} from "@ledgerhq/live-common/lib/countervalues/logic"; +} from "@ledgerhq/live-common/countervalues/logic"; function asPortfolioRange(period: string): PortfolioRange { const ranges = getRanges(); diff --git a/apps/cli/src/commands/proxy.ts b/apps/cli/src/commands/proxy.ts index bbb4df365c4b..51413e607a42 100644 --- a/apps/cli/src/commands/proxy.ts +++ b/apps/cli/src/commands/proxy.ts @@ -5,7 +5,7 @@ import { openTransportReplayer, } from "@ledgerhq/hw-transport-mocker"; import { log, listen } from "@ledgerhq/logs"; -import { open } from "@ledgerhq/live-common/lib/hw"; +import { open } from "@ledgerhq/live-common/hw/index"; import fs from "fs"; import http from "http"; import express from "express"; @@ -21,7 +21,8 @@ const args = [ name: "file", alias: "f", type: String, - desc: "in combination with --record, will save all the proxied APDUs to a provided file. If --record is not provided, proxy will start in replay mode of the provided file. If --file is not used at all, the proxy will just act as a proxy without saving the APDU.", + desc: + "in combination with --record, will save all the proxied APDUs to a provided file. If --record is not provided, proxy will start in replay mode of the provided file. If --file is not used at all, the proxy will just act as a proxy without saving the APDU.", }, { name: "verbose", @@ -154,9 +155,9 @@ const job = ({ res.sendStatus(200); process.exit(0); - } catch (e: any) { + } catch (e) { res.sendStatus(400); - console.error(e.message); + console.error((e as Error).message); process.exit(1); } }); @@ -192,8 +193,8 @@ const job = ({ } } } - } catch (e: any) { - error = e.toString(); + } catch (e) { + error = e as Error; } pending = false; @@ -279,11 +280,11 @@ const job = ({ type: "opened", }) ); - } catch (e: any) { + } catch (e) { log("proxy", `WS(${index}): open failed! ${e}`); ws.send( JSON.stringify({ - error: e.message, + error: (e as Error).message, }) ); ws.close(); @@ -314,18 +315,18 @@ const job = ({ data: res.toString("hex"), }) ); - } catch (e: any) { + } catch (e) { log("proxy", `WS(${index}): ${apduHex} =>`, e); if (destroyed) return; ws.send( JSON.stringify({ type: "error", - error: e.message, + error: (e as Error).message, }) ); - if (e.name === "RecordStoreWrongAPDU") { - console.error(e.message); + if ((e as Error).name === "RecordStoreWrongAPDU") { + console.error((e as Error).message); process.exit(1); } } diff --git a/apps/cli/src/commands/receive.ts b/apps/cli/src/commands/receive.ts index 3b89ec99b704..a9425cc71b0c 100644 --- a/apps/cli/src/commands/receive.ts +++ b/apps/cli/src/commands/receive.ts @@ -1,10 +1,10 @@ import { of, concat, EMPTY } from "rxjs"; import { ignoreElements, concatMap, map } from "rxjs/operators"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import { asQR } from "../qr"; -import { FreshAddressIndexInvalid } from "@ledgerhq/live-common/lib/errors"; +import { FreshAddressIndexInvalid } from "@ledgerhq/live-common/errors"; export default { description: "Receive crypto-assets (verify on device)", args: [ diff --git a/apps/cli/src/commands/repl.ts b/apps/cli/src/commands/repl.ts index f7b0b371230b..93fc47996054 100644 --- a/apps/cli/src/commands/repl.ts +++ b/apps/cli/src/commands/repl.ts @@ -1,5 +1,5 @@ import { map, concatMap } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import { deviceOpt } from "../scan"; import { apdusFromFile } from "../stream"; export default { diff --git a/apps/cli/src/commands/satstack.ts b/apps/cli/src/commands/satstack.ts index e78b4fe3c766..9ff68e92b2a7 100644 --- a/apps/cli/src/commands/satstack.ts +++ b/apps/cli/src/commands/satstack.ts @@ -2,19 +2,19 @@ import fs from "fs"; import invariant from "invariant"; import { from, of, forkJoin } from "rxjs"; import { map, reduce, first, catchError } from "rxjs/operators"; -import { setEnv } from "@ledgerhq/live-common/lib/env"; -import { getCryptoCurrencyById } from "@ledgerhq/live-common/lib/currencies"; +import { setEnv } from "@ledgerhq/live-common/env"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; import { AccountDescriptor, scanDescriptors, -} from "@ledgerhq/live-common/lib/families/bitcoin/descriptor"; +} from "@ledgerhq/live-common/families/bitcoin/descriptor"; import { parseSatStackConfig, stringifySatStackConfig, editSatStackConfig, checkRPCNodeConfig, validateRPCNodeConfig, -} from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; +} from "@ledgerhq/live-common/families/bitcoin/satstack"; import { deviceOpt } from "../scan"; import { jsonFromFile } from "../stream"; const bitcoin = getCryptoCurrencyById("bitcoin"); diff --git a/apps/cli/src/commands/satstackStatus.ts b/apps/cli/src/commands/satstackStatus.ts index e19f9f61609b..620b7a51ac53 100644 --- a/apps/cli/src/commands/satstackStatus.ts +++ b/apps/cli/src/commands/satstackStatus.ts @@ -1,6 +1,6 @@ import { first } from "rxjs/operators"; -import { statusObservable } from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; -import { setEnv } from "@ledgerhq/live-common/lib/env"; +import { statusObservable } from "@ledgerhq/live-common/families/bitcoin/satstack"; +import { setEnv } from "@ledgerhq/live-common/env"; export default { description: "Check StackSats status", args: [ diff --git a/apps/cli/src/commands/scanDescriptors.ts b/apps/cli/src/commands/scanDescriptors.ts index 9723475702ef..da2a8d0ac01c 100644 --- a/apps/cli/src/commands/scanDescriptors.ts +++ b/apps/cli/src/commands/scanDescriptors.ts @@ -1,6 +1,6 @@ import { deviceOpt, currencyOpt } from "../scan"; -import { findCryptoCurrencyByKeyword } from "@ledgerhq/live-common/lib/currencies"; -import { scanDescriptors } from "@ledgerhq/live-common/lib/families/bitcoin/descriptor"; +import { findCryptoCurrencyByKeyword } from "@ledgerhq/live-common/currencies/index"; +import { scanDescriptors } from "@ledgerhq/live-common/families/bitcoin/descriptor"; function requiredCurrency(c) { if (!c) throw new Error("could not find currency"); diff --git a/apps/cli/src/commands/send.ts b/apps/cli/src/commands/send.ts index 59853bdc7993..1c94db399534 100644 --- a/apps/cli/src/commands/send.ts +++ b/apps/cli/src/commands/send.ts @@ -7,18 +7,18 @@ import { catchError, tap, } from "rxjs/operators"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; import { formatOperation, formatAccount, fromOperationRaw, -} from "@ledgerhq/live-common/lib/account"; +} from "@ledgerhq/live-common/account/index"; import { toSignOperationEventRaw, formatTransaction, formatTransactionStatus, -} from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/transaction/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import type { InferTransactionsOpts } from "../transaction"; diff --git a/apps/cli/src/commands/signMessage.ts b/apps/cli/src/commands/signMessage.ts index a15b738ced6a..40bbaaca7d0d 100644 --- a/apps/cli/src/commands/signMessage.ts +++ b/apps/cli/src/commands/signMessage.ts @@ -1,8 +1,8 @@ import fs from "fs"; import { from } from "rxjs"; import { mergeMap } from "rxjs/operators"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import signMessage from "@ledgerhq/live-common/lib/hw/signMessage"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import signMessage from "@ledgerhq/live-common/hw/signMessage/index"; import { currencyOpt, inferCurrency } from "../scan"; export default { description: diff --git a/apps/cli/src/commands/speculosList.ts b/apps/cli/src/commands/speculosList.ts index 3c41274a9ffc..12bf65f748ed 100644 --- a/apps/cli/src/commands/speculosList.ts +++ b/apps/cli/src/commands/speculosList.ts @@ -1,7 +1,7 @@ import { from } from "rxjs"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { listAppCandidates } from "@ledgerhq/live-common/lib/load/speculos"; -import { formatAppCandidate } from "@ledgerhq/live-common/lib/bot/formatters"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { listAppCandidates } from "@ledgerhq/live-common/load/speculos"; +import { formatAppCandidate } from "@ledgerhq/live-common/bot/formatters"; import invariant from "invariant"; import { mergeAll } from "rxjs/operators"; diff --git a/apps/cli/src/commands/swap.ts b/apps/cli/src/commands/swap.ts index 95aa49fbdc0d..c95d2441d6d2 100644 --- a/apps/cli/src/commands/swap.ts +++ b/apps/cli/src/commands/swap.ts @@ -4,30 +4,29 @@ import { accountWithMandatoryTokens, getAccountCurrency, getMainAccount, -} from "@ledgerhq/live-common/lib/account"; +} from "@ledgerhq/live-common/account/index"; import { getAbandonSeedAddress, findTokenById } from "@ledgerhq/cryptoassets"; import { from } from "rxjs"; import { BigNumber } from "bignumber.js"; import commandLineArgs from "command-line-args"; -import { delay } from "@ledgerhq/live-common/lib/promise"; +import { delay } from "@ledgerhq/live-common/promise"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; import type { Exchange, ExchangeRate, InitSwapResult, -} from "@ledgerhq/live-common/lib/exchange/swap/types"; -import { initSwap } from "@ledgerhq/live-common/lib/exchange/swap"; -import { getExchangeRates } from "@ledgerhq/live-common/lib/exchange/swap"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; -import { getAccountUnit } from "@ledgerhq/live-common/lib/account"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/exchange/swap/types"; +import { initSwap, getExchangeRates } from "@ledgerhq/live-common/exchange/swap/index"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; +import { getAccountUnit } from "@ledgerhq/live-common/account/index"; +import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; import invariant from "invariant"; import { Account, SignedOperation, SubAccount, -} from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/types/index"; type SwapJobOpts = ScanCommonOpts & { amount: string; useAllAmount: boolean; diff --git a/apps/cli/src/commands/sync.ts b/apps/cli/src/commands/sync.ts index eb769313a4e1..a06115dec823 100644 --- a/apps/cli/src/commands/sync.ts +++ b/apps/cli/src/commands/sync.ts @@ -2,9 +2,9 @@ import { map, switchMap } from "rxjs/operators"; import { accountFormatters, decodeAccountId, -} from "@ledgerhq/live-common/lib/account"; -import { getCryptoCurrencyById } from "@ledgerhq/live-common/lib/currencies"; -import { getCurrencyBridge } from "@ledgerhq/live-common/lib/bridge"; +} from "@ledgerhq/live-common/account/index"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; +import { getCurrencyBridge } from "@ledgerhq/live-common/bridge/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; export default { diff --git a/apps/cli/src/commands/synchronousOnboarding.ts b/apps/cli/src/commands/synchronousOnboarding.ts index c4d9cec043de..9fd66b9f126a 100644 --- a/apps/cli/src/commands/synchronousOnboarding.ts +++ b/apps/cli/src/commands/synchronousOnboarding.ts @@ -1,7 +1,7 @@ import { getOnboardingStatePolling, OnboardingStatePollingResult, -} from "@ledgerhq/live-common/lib/hw/getOnboardingStatePolling"; +} from "@ledgerhq/live-common/hw/getOnboardingStatePolling"; import { Observable } from "rxjs"; import { deviceOpt } from "../scan"; diff --git a/apps/cli/src/commands/testDetectOpCollision.ts b/apps/cli/src/commands/testDetectOpCollision.ts index 439295ad85ee..c24e384bba2b 100644 --- a/apps/cli/src/commands/testDetectOpCollision.ts +++ b/apps/cli/src/commands/testDetectOpCollision.ts @@ -3,10 +3,10 @@ import flatMap from "lodash/flatMap"; import groupBy from "lodash/groupBy"; import { from } from "rxjs"; import { concatMap, reduce } from "rxjs/operators"; -import { flattenAccounts } from "@ledgerhq/live-common/lib/account"; +import { flattenAccounts } from "@ledgerhq/live-common/account/index"; import { scan, scanCommonOpts } from "../scan"; import type { ScanCommonOpts } from "../scan"; -import { Account } from "@ledgerhq/live-common/lib/types"; +import { Account } from "@ledgerhq/live-common/types/index"; export default { description: "Detect operation collisions", args: [...scanCommonOpts], diff --git a/apps/cli/src/commands/testGetTrustedInputFromTxHash.ts b/apps/cli/src/commands/testGetTrustedInputFromTxHash.ts index 067b32535421..97f2e1e32ca2 100644 --- a/apps/cli/src/commands/testGetTrustedInputFromTxHash.ts +++ b/apps/cli/src/commands/testGetTrustedInputFromTxHash.ts @@ -1,11 +1,11 @@ /* eslint-disable no-console */ -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; import { deviceOpt } from "../scan"; import { from } from "rxjs"; import invariant from "invariant"; import Btc from "@ledgerhq/hw-app-btc"; -import network from "@ledgerhq/live-common/lib/network"; -import { findCurrencyExplorer } from "@ledgerhq/live-common/lib/api/Ledger"; +import network from "@ledgerhq/live-common/network"; +import { findCurrencyExplorer } from "@ledgerhq/live-common/api/Ledger"; import { findCryptoCurrencyById } from "@ledgerhq/cryptoassets"; const command = async (transport, currencyId, hash) => { @@ -41,7 +41,9 @@ const command = async (transport, currencyId, hash) => { ); const outHash = await btc.getTrustedInput(0, tx, [currency.id]); const ouHash = outHash.substring(8, 72); - const finalOut = Buffer.from(ouHash, "hex").reverse().toString("hex"); + const finalOut = Buffer.from(ouHash, "hex") + .reverse() + .toString("hex"); return { inHash: hash, finalOut, diff --git a/apps/cli/src/commands/user.ts b/apps/cli/src/commands/user.ts index 0d2bebf5c4c7..528c6a6e3433 100644 --- a/apps/cli/src/commands/user.ts +++ b/apps/cli/src/commands/user.ts @@ -1,5 +1,5 @@ import { of, concat } from "rxjs"; -import { getUserHashes } from "@ledgerhq/live-common/lib/user"; +import { getUserHashes } from "@ledgerhq/live-common/user"; export default { args: [], job: () => concat(of(JSON.stringify(getUserHashes()))), diff --git a/apps/cli/src/commands/walletconnect.ts b/apps/cli/src/commands/walletconnect.ts index d25cfb78e532..15073eba831e 100644 --- a/apps/cli/src/commands/walletconnect.ts +++ b/apps/cli/src/commands/walletconnect.ts @@ -8,17 +8,17 @@ import { first, tap, map, take } from "rxjs/operators"; import { Observable } from "rxjs"; import { log, listen } from "@ledgerhq/logs"; import WalletConnect from "@walletconnect/client"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; -import { parseCallRequest } from "@ledgerhq/live-common/lib/walletconnect"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; +import { parseCallRequest } from "@ledgerhq/live-common/walletconnect/index"; import type { WCCallRequest, WCPayload, -} from "@ledgerhq/live-common/lib/walletconnect"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import signMessage from "@ledgerhq/live-common/lib/hw/signMessage"; -import { apiForCurrency } from "@ledgerhq/live-common/lib/api/Ethereum"; -import { MessageData } from "@ledgerhq/live-common/lib/hw/signMessage/types"; -import { Operation, SignedOperation } from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/walletconnect/index"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import signMessage from "@ledgerhq/live-common/hw/signMessage/index"; +import { apiForCurrency } from "@ledgerhq/live-common/api/Ethereum"; +import { MessageData } from "@ledgerhq/live-common/hw/signMessage/types"; +import { Operation, SignedOperation } from "@ledgerhq/live-common/types/index"; type Opts = ScanCommonOpts & Partial<{ walletConnectURI: string; diff --git a/apps/cli/src/live-common-setup-base.ts b/apps/cli/src/live-common-setup-base.ts index 692e70f26354..d392d1040a96 100644 --- a/apps/cli/src/live-common-setup-base.ts +++ b/apps/cli/src/live-common-setup-base.ts @@ -1,10 +1,10 @@ /* eslint-disable no-console */ import winston from "winston"; -import { EnvName, setEnvUnsafe } from "@ledgerhq/live-common/lib/env"; -import simple from "@ledgerhq/live-common/lib/logs/simple"; +import { EnvName, setEnvUnsafe } from "@ledgerhq/live-common/env"; +import simple from "@ledgerhq/live-common/logs/simple"; import { listen } from "@ledgerhq/logs"; -import { setSupportedCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { setPlatformVersion } from "@ledgerhq/live-common/lib/platform/version"; +import { setSupportedCurrencies } from "@ledgerhq/live-common/currencies/index"; +import { setPlatformVersion } from "@ledgerhq/live-common/platform/version"; setPlatformVersion("0.0.1"); diff --git a/apps/cli/src/live-common-setup.ts b/apps/cli/src/live-common-setup.ts index 8068a8e75e98..fad1288349d2 100644 --- a/apps/cli/src/live-common-setup.ts +++ b/apps/cli/src/live-common-setup.ts @@ -17,11 +17,11 @@ import SpeculosTransport, { import { registerTransportModule, disconnect, -} from "@ledgerhq/live-common/lib/hw"; -import { retry } from "@ledgerhq/live-common/lib/promise"; -import { checkLibs } from "@ledgerhq/live-common/lib/sanityChecks"; -import { closeAllSpeculosDevices } from "@ledgerhq/live-common/lib/load/speculos"; -import { disconnectAll } from "@ledgerhq/live-common/lib/api"; +} from "@ledgerhq/live-common/hw/index"; +import { retry } from "@ledgerhq/live-common/promise"; +import { checkLibs } from "@ledgerhq/live-common/sanityChecks"; +import { closeAllSpeculosDevices } from "@ledgerhq/live-common/load/speculos"; +import { disconnectAll } from "@ledgerhq/live-common/api/index"; checkLibs({ NotEnoughBalance, @@ -173,9 +173,10 @@ async function init() { disconnect: async (query) => query.startsWith("ble") ? cacheBle[query] - ? ( - (await getTransport().constructor) as typeof TransportNodeBle - ).disconnect(cacheBle[query].id) + ? ((await getTransport() + .constructor) as typeof TransportNodeBle).disconnect( + cacheBle[query].id + ) : Promise.resolve() : undefined, }); diff --git a/apps/cli/src/scan.ts b/apps/cli/src/scan.ts index a207f216e014..28e652d73665 100644 --- a/apps/cli/src/scan.ts +++ b/apps/cli/src/scan.ts @@ -13,33 +13,33 @@ import type { Account, CryptoCurrency, SyncConfig, -} from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/types/index"; import { fromAccountRaw, encodeAccountId, decodeAccountId, emptyHistoryCache, -} from "@ledgerhq/live-common/lib/account"; -import { asDerivationMode } from "@ledgerhq/live-common/lib/derivation"; +} from "@ledgerhq/live-common/account/index"; +import { asDerivationMode } from "@ledgerhq/live-common/derivation"; import { getAccountBridge, getCurrencyBridge, -} from "@ledgerhq/live-common/lib/bridge"; +} from "@ledgerhq/live-common/bridge/index"; import { findCryptoCurrencyByKeyword, findCryptoCurrencyById, getCryptoCurrencyById, -} from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/currencies/index"; import { runDerivationScheme, getDerivationScheme, -} from "@ledgerhq/live-common/lib/derivation"; -import { makeBridgeCacheSystem } from "@ledgerhq/live-common/lib/bridge/cache"; -import getAppAndVersion from "@ledgerhq/live-common/lib/hw/getAppAndVersion"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import { delay } from "@ledgerhq/live-common/lib/promise"; +} from "@ledgerhq/live-common/derivation"; +import { makeBridgeCacheSystem } from "@ledgerhq/live-common/bridge/cache"; +import getAppAndVersion from "@ledgerhq/live-common/hw/getAppAndVersion"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import { delay } from "@ledgerhq/live-common/promise"; import { jsonFromFile } from "./stream"; -import { shortAddressPreview } from "@ledgerhq/live-common/lib/account/helpers"; +import { shortAddressPreview } from "@ledgerhq/live-common/account/helpers"; import fs from "fs"; export const deviceOpt = { name: "device", diff --git a/apps/cli/src/signedOperation.ts b/apps/cli/src/signedOperation.ts index e3667c0838cd..0ffe2c836ba1 100644 --- a/apps/cli/src/signedOperation.ts +++ b/apps/cli/src/signedOperation.ts @@ -1,8 +1,8 @@ import type { Observable } from "rxjs"; import { map } from "rxjs/operators"; import invariant from "invariant"; -import type { SignedOperation, Account } from "@ledgerhq/live-common/lib/types"; -import { fromSignedOperationRaw } from "@ledgerhq/live-common/lib/transaction"; +import type { SignedOperation, Account } from "@ledgerhq/live-common/types/index"; +import { fromSignedOperationRaw } from "@ledgerhq/live-common/transaction/index"; import { jsonFromFile } from "./stream"; export type InferSignedOperationsOpts = Partial<{ "signed-operation": string; diff --git a/apps/cli/src/transaction.ts b/apps/cli/src/transaction.ts index 4d98249bf590..80b87f7e3ed4 100644 --- a/apps/cli/src/transaction.ts +++ b/apps/cli/src/transaction.ts @@ -10,11 +10,11 @@ import type { Transaction, AccountLike, Account, -} from "@ledgerhq/live-common/lib/types"; -import perFamily from "@ledgerhq/live-common/lib/generated/cli-transaction"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; -import { getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import { parseCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/types/index"; +import perFamily from "@ledgerhq/live-common/generated/cli-transaction"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; +import { getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import { parseCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; const inferAmount = (account: AccountLike, str: string): BigNumber => { const currency = getAccountCurrency(account); diff --git a/apps/ledger-live-desktop/.eslintrc.js b/apps/ledger-live-desktop/.eslintrc.js index 9325032a6032..e0b2816c7a46 100644 --- a/apps/ledger-live-desktop/.eslintrc.js +++ b/apps/ledger-live-desktop/.eslintrc.js @@ -43,6 +43,7 @@ module.exports = { "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks "react-hooks/exhaustive-deps": "warn", // Checks effect dependencies "jest/no-done-callback": 0, + "react/jsx-filename-extension": "error", }, overrides: [ { @@ -70,6 +71,14 @@ module.exports = { "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": ["error"], "flowtype/no-types-missing-file-annotation": 0, + "react/jsx-filename-extension": 0, + + // Ignore live-common for the moment because this rule does not work with subpath exports + // See: https://github.com/import-js/eslint-plugin-import/issues/1810 + // "import/no-unresolved": [ + // "error", + // { ignore: ["^@ledgerhq/live-common/.*", "^@ledgerhq/react-ui/.*"] }, + // ], }, }, ], diff --git a/apps/ledger-live-desktop/.flowconfig b/apps/ledger-live-desktop/.flowconfig index 2608da16b8d4..2e66d1adce28 100644 --- a/apps/ledger-live-desktop/.flowconfig +++ b/apps/ledger-live-desktop/.flowconfig @@ -22,7 +22,8 @@ module.system.node.resolve_dirname=node_modules module.name_mapper='^~' ->'/src' module.name_mapper='^@ledgerhq/react-ui/\(.*\)' ->'/../../libs/ui/packages/react/lib/\1' module.name_mapper='^@ledgerhq/react-ui' ->'/../../libs/ui/packages/react/lib' -module.name_mapper='^@ledgerhq/live-common/\(.*\)' ->'/../../libs/ledger-live-common/\1' +module.name_mapper='^@ledgerhq/live-common' ->'/../../libs/ledger-live-common/lib' +module.name_mapper='^@ledgerhq/live-common/\(.*\)' ->'/../../libs/ledger-live-common/lib/\1' munge_underscores=true esproposal.optional_chaining=enable @@ -31,4 +32,4 @@ esproposal.optional_chaining=enable [untyped] .*/node_modules/react-select .*/libs/ledger-live-common/.* -/node_modules/@ledgerhq/live-common/lib/* +/node_modules/@ledgerhq/live-common/.* diff --git a/apps/ledger-live-desktop/jest.config.js b/apps/ledger-live-desktop/jest.config.js index 38f4f5dfe5da..b37ff1d76f39 100644 --- a/apps/ledger-live-desktop/jest.config.js +++ b/apps/ledger-live-desktop/jest.config.js @@ -5,13 +5,4 @@ module.exports = { }, globalSetup: "/tests/setup.js", setupFiles: ["/tests/jestSetup.js"], - moduleNameMapper: { - "^@polkadot/([^/]+)/(.+)$": [ - "@polkadot/$1/index.cjs", - "@polkadot/$1/node.cjs", - "@polkadot/$1/$2.cjs", - "@polkadot/$1/cjs/$2", - "@polkadot/$1/$2", - ], - }, }; diff --git a/apps/ledger-live-desktop/package.json b/apps/ledger-live-desktop/package.json index ad3d8b0e9c01..194acc17e30e 100644 --- a/apps/ledger-live-desktop/package.json +++ b/apps/ledger-live-desktop/package.json @@ -130,6 +130,7 @@ "tree-kill": "^1.2.2", "uncontrollable": "^7.2.1", "uuid": "^8.3.2", + "vite": "^2.9.10", "winston": "^3.2.1", "winston-transport": "^4.3.0", "write-file-atomic": "^3.0.3", @@ -152,10 +153,10 @@ "@babel/preset-flow": "^7.13.13", "@babel/preset-react": "^7.13.13", "@babel/preset-typescript": "^7.15.0", + "@bunchtogether/vite-plugin-flow": "^1.0.2", "@mapbox/node-pre-gyp": "^1.0.8", "@octokit/rest": "^18.12.0", "@playwright/test": "1.22.2", - "@pmmmwh/react-refresh-webpack-plugin": "^0.5.7", "@sentry/cli": "^2.1.0", "@storybook/addon-actions": "^6.3.12", "@storybook/addon-essentials": "^6.3.12", @@ -164,8 +165,10 @@ "@types/react-router-dom": "^5.3.2", "@types/react-select": "^4", "@types/redux-actions": "^2.6.2", - "@typescript-eslint/eslint-plugin": "^5.12.1", - "@typescript-eslint/parser": "^5.12.1", + "@typescript-eslint/eslint-plugin": "^5.28.0", + "@typescript-eslint/parser": "5.28.0", + "@vitejs/plugin-react": "^1.3.2", + "babel-cli": "^6.26.0", "babel-eslint": "^10.1.0", "babel-jest": "^27.3.1", "babel-plugin-module-resolver": "^4.1.0", @@ -173,7 +176,6 @@ "codecov": "^3.8.3", "cross-env": "^7.0.3", "css-loader": "^3.5.3", - "dotenv-webpack": "^7.1.0", "electron": "^15", "electron-builder": "23.0.3", "electron-devtools-installer": "^3.2.0", @@ -183,6 +185,7 @@ "eslint": "^7.32.0", "eslint-config-prettier": "^7.2.0", "eslint-config-standard": "^16.0.3", + "eslint-import-resolver-typescript": "^2.7.1", "eslint-plugin-flowtype": "^5.9.0", "eslint-plugin-import": "^2.25.3", "eslint-plugin-jest": "^24.4.0", @@ -200,9 +203,8 @@ "flow-typed": "^2.6.2", "git-rev-sync": "^2.0.0", "hasha": "^5.2.2", - "html-webpack-plugin": "^5.5.0", "istanbul-instrumenter-loader": "^3.0.1", - "jest": "^26.6.3", + "jest": "^28.1.1", "listr": "^0.14.3", "listr-verbose-renderer": "^0.6.0", "native-modules-tools": "workspace:*", @@ -215,14 +217,9 @@ "serve": "^13.0.2", "style-loader": "^1.2.1", "typescript": "^4.4.4", - "unused-webpack-plugin": "^2.4.0", "url-loader": "^4.1.1", "v8-to-istanbul": "^8.1.0", - "webpack": "^5.73.0", - "webpack-cli": "^4.9.1", - "webpack-dev-middleware": "^5.3.3", - "webpack-hot-middleware": "^2.25.1", - "webpackbar": "^5.0.2", + "vite-plugin-electron": "^0.4.8", "yargs": "^15.3.1" } } diff --git a/apps/ledger-live-desktop/release-notes.json b/apps/ledger-live-desktop/release-notes.json index 77305cebdc30..0637a088a01e 100644 --- a/apps/ledger-live-desktop/release-notes.json +++ b/apps/ledger-live-desktop/release-notes.json @@ -1,6 +1 @@ -[ - { - "tag_name": "2.43.0", - "body": "\nWe’re constantly adding new integrations and working on performance improvements to make Ledger Live a world-class experience. Here is what’s new in this release.\n\n### 🚀 Features\nWe’re excited to announce that Ledger Live is launching support for Cardano, one of the biggest cryptocurrencies based on its total market value. Now you can send and receive Cardano’s native token, ADA. \n\n### 🐛 Fixes\nWe’ve done some bug fixes and made a few small but important changes behind the curtain.\n" - } -] \ No newline at end of file +[] \ No newline at end of file diff --git a/apps/ledger-live-desktop/scripts/sync-families-dispatch.sh b/apps/ledger-live-desktop/scripts/sync-families-dispatch.sh index f26dfc01cf84..2b22ba8b8af8 100755 --- a/apps/ledger-live-desktop/scripts/sync-families-dispatch.sh +++ b/apps/ledger-live-desktop/scripts/sync-families-dispatch.sh @@ -4,20 +4,20 @@ set -e cd $(dirname $0) targets="\ -operationDetails.js \ -accountActions.js \ -TransactionConfirmFields.js \ +operationDetails.jsx \ +accountActions.jsx \ +TransactionConfirmFields.jsx \ AccountBodyHeader.js \ -AccountSubHeader.js \ -SendAmountFields.js \ -SendRecipientFields.js \ +AccountSubHeader.jsx \ +SendAmountFields.jsx \ +SendRecipientFields.jsx \ SendWarning.js \ -ReceiveWarning.js \ -AccountBalanceSummaryFooter.js \ -TokenList.js \ +ReceiveWarning.jsx \ +AccountBalanceSummaryFooter.jsx \ +TokenList.jsx \ AccountHeaderManageActions.js \ -StepReceiveFunds.js \ -NoAssociatedAccounts.js +StepReceiveFunds.jsx \ +NoAssociatedAccounts.jsx " cd ../src/renderer @@ -62,8 +62,8 @@ done for t in $targets; do out=../generated/$t - if [[ "$out" != *.js ]]; then - out=$out.js - fi + # if [[ "$out" != *.js ]]; then + # out=$out.js + # fi genTarget $t > $out done diff --git a/apps/ledger-live-desktop/src/config/languages.js b/apps/ledger-live-desktop/src/config/languages.js index 50f4f4ade1f6..4bd29730c2ef 100644 --- a/apps/ledger-live-desktop/src/config/languages.js +++ b/apps/ledger-live-desktop/src/config/languages.js @@ -1,5 +1,5 @@ // @flow -import { getEnv } from "@ledgerhq/live-common/lib/env"; +import { getEnv } from "@ledgerhq/live-common/env"; export const allLanguages = [ "de", diff --git a/apps/ledger-live-desktop/src/generate-cryptoassets-md.test.js b/apps/ledger-live-desktop/src/generate-cryptoassets-md.test.js index 6aefd5af4b80..a30ff04e3ff7 100644 --- a/apps/ledger-live-desktop/src/generate-cryptoassets-md.test.js +++ b/apps/ledger-live-desktop/src/generate-cryptoassets-md.test.js @@ -8,7 +8,7 @@ import { listCryptoCurrencies, isCurrencySupported, listTokens, -} from "@ledgerhq/live-common/lib/currencies"; +} from "@ledgerhq/live-common/currencies/index"; const outputFile = "cryptoassets.md"; test("generate cryptoassets.md", () => { diff --git a/apps/ledger-live-desktop/src/helpers/accountModel.js b/apps/ledger-live-desktop/src/helpers/accountModel.js index 1d9c984dbf55..de6ad0571f6e 100644 --- a/apps/ledger-live-desktop/src/helpers/accountModel.js +++ b/apps/ledger-live-desktop/src/helpers/accountModel.js @@ -2,10 +2,10 @@ * @module models/account * @flow */ -import { createDataModel } from "@ledgerhq/live-common/lib/DataModel"; -import { fromAccountRaw, toAccountRaw } from "@ledgerhq/live-common/lib/account"; -import type { DataModel } from "@ledgerhq/live-common/lib/DataModel"; -import type { Account, AccountRaw, Operation } from "@ledgerhq/live-common/lib/types"; +import { createDataModel } from "@ledgerhq/live-common/DataModel"; +import { fromAccountRaw, toAccountRaw } from "@ledgerhq/live-common/account/index"; +import type { DataModel } from "@ledgerhq/live-common/DataModel"; +import type { Account, AccountRaw, Operation } from "@ledgerhq/live-common/types/index"; /** * @memberof models/account diff --git a/apps/ledger-live-desktop/src/helpers/env.js b/apps/ledger-live-desktop/src/helpers/env.js index 619dbe867e9b..7da0aeabc6c6 100644 --- a/apps/ledger-live-desktop/src/helpers/env.js +++ b/apps/ledger-live-desktop/src/helpers/env.js @@ -1,6 +1,6 @@ // @flow import { ipcRenderer } from "electron"; -import { setEnvUnsafe } from "@ledgerhq/live-common/lib/env"; +import { setEnvUnsafe } from "@ledgerhq/live-common/env"; // List of environment variables for which killing internal process is necessary // (I must admit having considered calling this `bloodThirstyEnvs`) diff --git a/apps/ledger-live-desktop/src/helpers/nft.js b/apps/ledger-live-desktop/src/helpers/nft.js index a16dc48c08b1..29d031f2e687 100644 --- a/apps/ledger-live-desktop/src/helpers/nft.js +++ b/apps/ledger-live-desktop/src/helpers/nft.js @@ -1,5 +1,5 @@ // @flow -import type { NFTMetadata, NFTMediaSizes } from "@ledgerhq/live-common/lib/types"; +import type { NFTMetadata, NFTMediaSizes } from "@ledgerhq/live-common/types/index"; const mimeTypesMap = { video: ["video/mp4", "video/webm", "video/ogg"], diff --git a/apps/ledger-live-desktop/src/internal/commands/appOpExec.js b/apps/ledger-live-desktop/src/internal/commands/appOpExec.js index 7f44b76cbe2f..c6dd5c9bab35 100644 --- a/apps/ledger-live-desktop/src/internal/commands/appOpExec.js +++ b/apps/ledger-live-desktop/src/internal/commands/appOpExec.js @@ -1,9 +1,9 @@ // @flow import type { Observable } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import type { App } from "@ledgerhq/live-common/lib/types/manager"; -import { execWithTransport } from "@ledgerhq/live-common/lib/apps/hw"; -import type { AppOp } from "@ledgerhq/live-common/lib/apps"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import type { App } from "@ledgerhq/live-common/types/manager"; +import { execWithTransport } from "@ledgerhq/live-common/apps/hw"; +import type { AppOp } from "@ledgerhq/live-common/apps/index"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/checkRPCNodeConfig.js b/apps/ledger-live-desktop/src/internal/commands/checkRPCNodeConfig.js index dbb16ba4d6cc..2c18aec4f6d1 100644 --- a/apps/ledger-live-desktop/src/internal/commands/checkRPCNodeConfig.js +++ b/apps/ledger-live-desktop/src/internal/commands/checkRPCNodeConfig.js @@ -1,8 +1,8 @@ // @flow import { from } from "rxjs"; import type { Observable } from "rxjs"; -import { checkRPCNodeConfig } from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; -import type { RPCNodeConfig } from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; +import { checkRPCNodeConfig } from "@ledgerhq/live-common/families/bitcoin/satstack"; +import type { RPCNodeConfig } from "@ledgerhq/live-common/families/bitcoin/satstack"; const cmd = (config: RPCNodeConfig): Observable => from(checkRPCNodeConfig(config)); diff --git a/apps/ledger-live-desktop/src/internal/commands/checkSignatureAndPrepare.js b/apps/ledger-live-desktop/src/internal/commands/checkSignatureAndPrepare.js index be58a607e450..c7014e45a6ed 100644 --- a/apps/ledger-live-desktop/src/internal/commands/checkSignatureAndPrepare.js +++ b/apps/ledger-live-desktop/src/internal/commands/checkSignatureAndPrepare.js @@ -2,21 +2,18 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import type { SellRequestEvent } from "@ledgerhq/live-common/lib/exchange/sell/types"; +import type { SellRequestEvent } from "@ledgerhq/live-common/exchange/sell/types"; import type { AccountRaw, AccountRawLike, TransactionStatusRaw, TransactionRaw, -} from "@ledgerhq/live-common/lib/types"; -import { fromTransactionRaw } from "@ledgerhq/live-common/lib/transaction"; -import checkSignatureAndPrepare from "@ledgerhq/live-common/lib/exchange/sell/checkSignatureAndPrepare"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import { - fromAccountRaw, - fromAccountLikeRaw, -} from "@ledgerhq/live-common/lib/account/serialization"; -import { fromTransactionStatusRaw } from "@ledgerhq/live-common/lib/transaction/status"; +} from "@ledgerhq/live-common/types/index"; +import { fromTransactionRaw } from "@ledgerhq/live-common/transaction/index"; +import checkSignatureAndPrepare from "@ledgerhq/live-common/exchange/sell/checkSignatureAndPrepare"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import { fromAccountRaw, fromAccountLikeRaw } from "@ledgerhq/live-common/account/serialization"; +import { fromTransactionStatusRaw } from "@ledgerhq/live-common/transaction/status"; type Input = { parentAccount: ?AccountRaw, account: AccountRawLike, diff --git a/apps/ledger-live-desktop/src/internal/commands/completeExchange.js b/apps/ledger-live-desktop/src/internal/commands/completeExchange.js index b345862ced30..3a9c05e727ef 100644 --- a/apps/ledger-live-desktop/src/internal/commands/completeExchange.js +++ b/apps/ledger-live-desktop/src/internal/commands/completeExchange.js @@ -2,11 +2,11 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import type { ExchangeRaw } from "@ledgerhq/live-common/lib/exchange/platform/types"; -import completeExchange from "@ledgerhq/live-common/lib/exchange/platform/completeExchange"; -import { fromExchangeRaw } from "@ledgerhq/live-common/lib/exchange/platform/serialization"; -import type { TransactionRaw } from "@ledgerhq/live-common/lib/types"; -import { fromTransactionRaw } from "@ledgerhq/live-common/lib/transaction"; +import type { ExchangeRaw } from "@ledgerhq/live-common/exchange/platform/types"; +import completeExchange from "@ledgerhq/live-common/exchange/platform/completeExchange"; +import { fromExchangeRaw } from "@ledgerhq/live-common/exchange/platform/serialization"; +import type { TransactionRaw } from "@ledgerhq/live-common/types/index"; +import { fromTransactionRaw } from "@ledgerhq/live-common/transaction/index"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/connectApp.js b/apps/ledger-live-desktop/src/internal/commands/connectApp.js index 16868e172e24..3bbb09b409cb 100644 --- a/apps/ledger-live-desktop/src/internal/commands/connectApp.js +++ b/apps/ledger-live-desktop/src/internal/commands/connectApp.js @@ -2,8 +2,8 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import connectApp from "@ledgerhq/live-common/lib/hw/connectApp"; -import type { Input, ConnectAppEvent } from "@ledgerhq/live-common/lib/hw/connectApp"; +import connectApp from "@ledgerhq/live-common/hw/connectApp"; +import type { Input, ConnectAppEvent } from "@ledgerhq/live-common/hw/connectApp"; const cmd = (input: Input): Observable => from(connectApp(input)); export default cmd; diff --git a/apps/ledger-live-desktop/src/internal/commands/connectManager.js b/apps/ledger-live-desktop/src/internal/commands/connectManager.js index 030f1f8fc754..dfe9f695f4fd 100644 --- a/apps/ledger-live-desktop/src/internal/commands/connectManager.js +++ b/apps/ledger-live-desktop/src/internal/commands/connectManager.js @@ -2,8 +2,8 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import connectManager from "@ledgerhq/live-common/lib/hw/connectManager"; -import type { Input, ConnectManagerEvent } from "@ledgerhq/live-common/lib/hw/connectManager"; +import connectManager from "@ledgerhq/live-common/hw/connectManager"; +import type { Input, ConnectManagerEvent } from "@ledgerhq/live-common/hw/connectManager"; const cmd = (input: Input): Observable => from(connectManager(input)); diff --git a/apps/ledger-live-desktop/src/internal/commands/firmwareMain.js b/apps/ledger-live-desktop/src/internal/commands/firmwareMain.js index 13a7718dd289..46a7805957da 100644 --- a/apps/ledger-live-desktop/src/internal/commands/firmwareMain.js +++ b/apps/ledger-live-desktop/src/internal/commands/firmwareMain.js @@ -1,8 +1,8 @@ // @flow import type { Observable } from "rxjs"; -import main from "@ledgerhq/live-common/lib/hw/firmwareUpdate-main"; -import type { FirmwareUpdateContext } from "@ledgerhq/live-common/lib/types/manager"; +import main from "@ledgerhq/live-common/hw/firmwareUpdate-main"; +import type { FirmwareUpdateContext } from "@ledgerhq/live-common/types/manager"; type Input = FirmwareUpdateContext; diff --git a/apps/ledger-live-desktop/src/internal/commands/firmwarePrepare.js b/apps/ledger-live-desktop/src/internal/commands/firmwarePrepare.js index 0ca9988ac2e4..40478ae29c22 100644 --- a/apps/ledger-live-desktop/src/internal/commands/firmwarePrepare.js +++ b/apps/ledger-live-desktop/src/internal/commands/firmwarePrepare.js @@ -1,8 +1,8 @@ // @flow import type { Observable } from "rxjs"; -import prepare from "@ledgerhq/live-common/lib/hw/firmwareUpdate-prepare"; -import type { FirmwareUpdateContext } from "@ledgerhq/live-common/lib/types/manager"; +import prepare from "@ledgerhq/live-common/hw/firmwareUpdate-prepare"; +import type { FirmwareUpdateContext } from "@ledgerhq/live-common/types/manager"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/firmwareRepair.js b/apps/ledger-live-desktop/src/internal/commands/firmwareRepair.js index c48eefd238bd..7a39fbf8e2c8 100644 --- a/apps/ledger-live-desktop/src/internal/commands/firmwareRepair.js +++ b/apps/ledger-live-desktop/src/internal/commands/firmwareRepair.js @@ -1,7 +1,7 @@ // @flow import type { Observable } from "rxjs"; -import repair from "@ledgerhq/live-common/lib/hw/firmwareUpdate-repair"; +import repair from "@ledgerhq/live-common/hw/firmwareUpdate-repair"; type Input = { version: ?string, diff --git a/apps/ledger-live-desktop/src/internal/commands/firmwareUpdating.js b/apps/ledger-live-desktop/src/internal/commands/firmwareUpdating.js index 9c4b2d8ce6e8..6ad3ba2b8b6e 100644 --- a/apps/ledger-live-desktop/src/internal/commands/firmwareUpdating.js +++ b/apps/ledger-live-desktop/src/internal/commands/firmwareUpdating.js @@ -2,9 +2,9 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import { withDevicePolling } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getDeviceInfo from "@ledgerhq/live-common/lib/hw/getDeviceInfo"; -import type { DeviceInfo } from "@ledgerhq/live-common/lib/types/manager"; +import { withDevicePolling } from "@ledgerhq/live-common/hw/deviceAccess"; +import getDeviceInfo from "@ledgerhq/live-common/hw/getDeviceInfo"; +import type { DeviceInfo } from "@ledgerhq/live-common/types/manager"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/flushDevice.js b/apps/ledger-live-desktop/src/internal/commands/flushDevice.js index 48a5a091128c..259b1830e168 100644 --- a/apps/ledger-live-desktop/src/internal/commands/flushDevice.js +++ b/apps/ledger-live-desktop/src/internal/commands/flushDevice.js @@ -1,7 +1,7 @@ // @flow import type { Observable } from "rxjs"; import { from } from "rxjs"; -import flush from "@ledgerhq/live-common/lib/hw/flush"; +import flush from "@ledgerhq/live-common/hw/flush"; type Input = string; type Result = void; diff --git a/apps/ledger-live-desktop/src/internal/commands/getAppAndVersion.js b/apps/ledger-live-desktop/src/internal/commands/getAppAndVersion.js index 71ca40a5fbe4..173b19e80984 100644 --- a/apps/ledger-live-desktop/src/internal/commands/getAppAndVersion.js +++ b/apps/ledger-live-desktop/src/internal/commands/getAppAndVersion.js @@ -1,7 +1,7 @@ // @flow import type { Observable } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import getAppAndVersion from "@ledgerhq/live-common/lib/hw/getAppAndVersion"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import getAppAndVersion from "@ledgerhq/live-common/hw/getAppAndVersion"; import { from } from "rxjs"; type Input = { diff --git a/apps/ledger-live-desktop/src/internal/commands/getLatestFirmwareForDevice.js b/apps/ledger-live-desktop/src/internal/commands/getLatestFirmwareForDevice.js index be31319ba346..16af9a23a1b5 100644 --- a/apps/ledger-live-desktop/src/internal/commands/getLatestFirmwareForDevice.js +++ b/apps/ledger-live-desktop/src/internal/commands/getLatestFirmwareForDevice.js @@ -2,8 +2,8 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import type { DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/live-common/lib/types/manager"; -import manager from "@ledgerhq/live-common/lib/manager"; +import type { DeviceInfo, FirmwareUpdateContext } from "@ledgerhq/live-common/types/manager"; +import manager from "@ledgerhq/live-common/manager/index"; type Result = ?FirmwareUpdateContext; diff --git a/apps/ledger-live-desktop/src/internal/commands/getSatStackStatus.js b/apps/ledger-live-desktop/src/internal/commands/getSatStackStatus.js index 17a85fca1d40..06c2de6df2c8 100644 --- a/apps/ledger-live-desktop/src/internal/commands/getSatStackStatus.js +++ b/apps/ledger-live-desktop/src/internal/commands/getSatStackStatus.js @@ -1,6 +1,6 @@ // @flow -import type { SatStackStatus } from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; -import { statusObservable } from "@ledgerhq/live-common/lib/families/bitcoin/satstack"; +import type { SatStackStatus } from "@ledgerhq/live-common/families/bitcoin/satstack"; +import { statusObservable } from "@ledgerhq/live-common/families/bitcoin/satstack"; import type { Observable } from "rxjs"; const cmd = (): Observable => statusObservable; diff --git a/apps/ledger-live-desktop/src/internal/commands/getTransactionId.js b/apps/ledger-live-desktop/src/internal/commands/getTransactionId.js index 5a63de4667e1..c349db9509c6 100644 --- a/apps/ledger-live-desktop/src/internal/commands/getTransactionId.js +++ b/apps/ledger-live-desktop/src/internal/commands/getTransactionId.js @@ -2,9 +2,9 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import getTransactionId from "@ledgerhq/live-common/lib/exchange/sell/getTransactionId"; -import type { SellRequestEvent } from "@ledgerhq/live-common/lib/exchange/sell/types"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; +import getTransactionId from "@ledgerhq/live-common/exchange/sell/getTransactionId"; +import type { SellRequestEvent } from "@ledgerhq/live-common/exchange/sell/types"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/initSwap.js b/apps/ledger-live-desktop/src/internal/commands/initSwap.js index 93ea1135d879..a7268f3b0e8d 100644 --- a/apps/ledger-live-desktop/src/internal/commands/initSwap.js +++ b/apps/ledger-live-desktop/src/internal/commands/initSwap.js @@ -6,14 +6,14 @@ import type { ExchangeRateRaw, ExchangeRaw, SwapRequestEvent, -} from "@ledgerhq/live-common/lib/exchange/swap/types"; -import type { TransactionRaw } from "@ledgerhq/live-common/lib/types"; -import { fromTransactionRaw } from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/exchange/swap/types"; +import type { TransactionRaw } from "@ledgerhq/live-common/types/index"; +import { fromTransactionRaw } from "@ledgerhq/live-common/transaction/index"; import { fromExchangeRaw, fromExchangeRateRaw, -} from "@ledgerhq/live-common/lib/exchange/swap/serialization"; -import initSwap from "@ledgerhq/live-common/lib/exchange/swap/initSwap"; +} from "@ledgerhq/live-common/exchange/swap/serialization"; +import initSwap from "@ledgerhq/live-common/exchange/swap/initSwap"; type Input = { exchange: ExchangeRaw, diff --git a/apps/ledger-live-desktop/src/internal/commands/listApps.js b/apps/ledger-live-desktop/src/internal/commands/listApps.js index e236323d7fec..ae96cab3b4a6 100644 --- a/apps/ledger-live-desktop/src/internal/commands/listApps.js +++ b/apps/ledger-live-desktop/src/internal/commands/listApps.js @@ -1,9 +1,9 @@ // @flow import type { Observable } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import type { DeviceInfo } from "@ledgerhq/live-common/lib/types/manager"; -import { listApps } from "@ledgerhq/live-common/lib/apps/hw"; -import type { ListAppsEvent } from "@ledgerhq/live-common/lib/apps"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; +import type { DeviceInfo } from "@ledgerhq/live-common/types/manager"; +import { listApps } from "@ledgerhq/live-common/apps/hw"; +import type { ListAppsEvent } from "@ledgerhq/live-common/apps/index"; type Input = { deviceInfo: DeviceInfo, diff --git a/apps/ledger-live-desktop/src/internal/commands/scanDescriptors.js b/apps/ledger-live-desktop/src/internal/commands/scanDescriptors.js index d66057d25660..7badcc8f36d7 100644 --- a/apps/ledger-live-desktop/src/internal/commands/scanDescriptors.js +++ b/apps/ledger-live-desktop/src/internal/commands/scanDescriptors.js @@ -1,9 +1,9 @@ // @flow import type { Observable } from "rxjs"; import { from } from "rxjs"; -import { scanDescriptors } from "@ledgerhq/live-common/lib/families/bitcoin/descriptor"; -import { getCryptoCurrencyById } from "@ledgerhq/live-common/lib/currencies"; -import type { AccountDescriptor } from "@ledgerhq/live-common/lib/families/bitcoin/descriptor"; +import { scanDescriptors } from "@ledgerhq/live-common/families/bitcoin/descriptor"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; +import type { AccountDescriptor } from "@ledgerhq/live-common/families/bitcoin/descriptor"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/signMessage.js b/apps/ledger-live-desktop/src/internal/commands/signMessage.js index 59806f1e5f7a..80b6f58c6dc4 100644 --- a/apps/ledger-live-desktop/src/internal/commands/signMessage.js +++ b/apps/ledger-live-desktop/src/internal/commands/signMessage.js @@ -1,9 +1,9 @@ // @flow import type { Observable } from "rxjs"; -import { signMessageExec } from "@ledgerhq/live-common/lib/hw/signMessage"; -import type { Input } from "@ledgerhq/live-common/lib/hw/signMessage"; -import type { Result } from "@ledgerhq/live-common/lib/hw/signMessage/types"; +import { signMessageExec } from "@ledgerhq/live-common/hw/signMessage/index"; +import type { Input } from "@ledgerhq/live-common/hw/signMessage/index"; +import type { Result } from "@ledgerhq/live-common/hw/signMessage/types"; const cmd = (input: Input): Observable => signMessageExec(input); diff --git a/apps/ledger-live-desktop/src/internal/commands/startExchange.js b/apps/ledger-live-desktop/src/internal/commands/startExchange.js index cc070abfbf6d..9d02d5f45573 100644 --- a/apps/ledger-live-desktop/src/internal/commands/startExchange.js +++ b/apps/ledger-live-desktop/src/internal/commands/startExchange.js @@ -2,7 +2,7 @@ import type { Observable } from "rxjs"; import { from } from "rxjs"; -import startExchange from "@ledgerhq/live-common/lib/exchange/platform/startExchange"; +import startExchange from "@ledgerhq/live-common/exchange/platform/startExchange"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/testApdu.js b/apps/ledger-live-desktop/src/internal/commands/testApdu.js index 4777103dee2a..077ce1fe9817 100644 --- a/apps/ledger-live-desktop/src/internal/commands/testApdu.js +++ b/apps/ledger-live-desktop/src/internal/commands/testApdu.js @@ -3,7 +3,7 @@ // This is a test example for dev testing purpose. import type { Observable } from "rxjs"; import { from } from "rxjs"; -import { withDevice } from "@ledgerhq/live-common/lib/hw/deviceAccess"; +import { withDevice } from "@ledgerhq/live-common/hw/deviceAccess"; type Input = { deviceId: string, diff --git a/apps/ledger-live-desktop/src/internal/commands/websocketBridge.js b/apps/ledger-live-desktop/src/internal/commands/websocketBridge.js index 860c78c4b1b9..d81e8728fe1d 100644 --- a/apps/ledger-live-desktop/src/internal/commands/websocketBridge.js +++ b/apps/ledger-live-desktop/src/internal/commands/websocketBridge.js @@ -1,7 +1,7 @@ // @flow import { Observable } from "rxjs"; import { log, listen } from "@ledgerhq/logs"; -import { open } from "@ledgerhq/live-common/lib/hw"; +import { open } from "@ledgerhq/live-common/hw/index"; import WebSocket from "ws"; type Input = { diff --git a/apps/ledger-live-desktop/src/internal/index.js b/apps/ledger-live-desktop/src/internal/index.js index a64aef91ab9d..007dacca4595 100644 --- a/apps/ledger-live-desktop/src/internal/index.js +++ b/apps/ledger-live-desktop/src/internal/index.js @@ -1,10 +1,10 @@ // @flow import * as Sentry from "@sentry/node"; import { unsubscribeSetup } from "./live-common-setup"; -import { setEnvUnsafe } from "@ledgerhq/live-common/lib/env"; +import { setEnvUnsafe } from "@ledgerhq/live-common/env"; import { serializeError } from "@ledgerhq/errors"; -import { getCurrencyBridge } from "@ledgerhq/live-common/lib/bridge"; -import { getCryptoCurrencyById } from "@ledgerhq/live-common/lib/currencies"; +import { getCurrencyBridge } from "@ledgerhq/live-common/bridge/index"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; import { log } from "@ledgerhq/logs"; import logger from "~/logger"; import LoggerTransport from "~/logger/logger-transport-internal"; diff --git a/apps/ledger-live-desktop/src/internal/live-common-setup.js b/apps/ledger-live-desktop/src/internal/live-common-setup.js index a992f11f1744..4581ac9b59f8 100644 --- a/apps/ledger-live-desktop/src/internal/live-common-setup.js +++ b/apps/ledger-live-desktop/src/internal/live-common-setup.js @@ -2,10 +2,10 @@ import "~/live-common-setup"; import { throwError } from "rxjs"; import throttle from "lodash/throttle"; -import { registerTransportModule } from "@ledgerhq/live-common/lib/hw"; -import { addAccessHook, setErrorRemapping } from "@ledgerhq/live-common/lib/hw/deviceAccess"; -import { setEnvUnsafe, getEnv } from "@ledgerhq/live-common/lib/env"; -import { retry } from "@ledgerhq/live-common/lib/promise"; +import { registerTransportModule } from "@ledgerhq/live-common/hw/index"; +import { addAccessHook, setErrorRemapping } from "@ledgerhq/live-common/hw/deviceAccess"; +import { setEnvUnsafe, getEnv } from "@ledgerhq/live-common/env"; +import { retry } from "@ledgerhq/live-common/promise"; import TransportNodeHidSingleton, { usbDetect } from "@ledgerhq/hw-transport-node-hid-singleton"; import TransportHttp from "@ledgerhq/hw-transport-http"; import { DisconnectedDevice } from "@ledgerhq/errors"; diff --git a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js index bd36c18a8154..67cb5dcebc50 100644 --- a/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js +++ b/apps/ledger-live-desktop/src/live-common-set-supported-currencies.js @@ -1,6 +1,6 @@ // @flow -import { setSupportedCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { setPlatformVersion } from "@ledgerhq/live-common/lib/platform/version"; +import { setSupportedCurrencies } from "@ledgerhq/live-common/currencies/index"; +import { setPlatformVersion } from "@ledgerhq/live-common/platform/version"; setPlatformVersion("0.0.1"); diff --git a/apps/ledger-live-desktop/src/logger/index.js b/apps/ledger-live-desktop/src/logger/index.js index d9294e25a0f8..9e80335185f5 100644 --- a/apps/ledger-live-desktop/src/logger/index.js +++ b/apps/ledger-live-desktop/src/logger/index.js @@ -1,3 +1 @@ -const logger = require("./logger"); - -module.exports = logger; +export { default, enableDebugLogger, add, summarize } from "./logger"; diff --git a/apps/ledger-live-desktop/src/logger/logger.js b/apps/ledger-live-desktop/src/logger/logger.js index 72851e7cc2e3..a0115d1eb6cd 100644 --- a/apps/ledger-live-desktop/src/logger/logger.js +++ b/apps/ledger-live-desktop/src/logger/logger.js @@ -74,7 +74,7 @@ const captureBreadcrumb = (breadcrumb: any) => { if (!process.env.STORYBOOK_ENV) { try { if (typeof window !== "undefined") { - require("~/sentry/renderer").captureBreadcrumb(breadcrumb); + import("~/sentry/renderer").then(sentry => sentry.captureBreadcrumb(breadcrumb)); } else if (process.title === "Ledger Live Internal") { require("~/sentry/internal").captureBreadcrumb(breadcrumb); } else { @@ -89,7 +89,7 @@ const captureBreadcrumb = (breadcrumb: any) => { const captureException = (error: Error) => { try { if (typeof window !== "undefined") { - require("~/sentry/renderer").captureException(error); + import("~/sentry/renderer").then(sentry => sentry.captureException(error)); } else if (process.title === "Ledger Live Internal") { require("~/sentry/internal").captureException(error); } else { diff --git a/apps/ledger-live-desktop/src/main/db/index.js b/apps/ledger-live-desktop/src/main/db/index.js index 00589fba0764..a5dceb65c46a 100644 --- a/apps/ledger-live-desktop/src/main/db/index.js +++ b/apps/ledger-live-desktop/src/main/db/index.js @@ -6,7 +6,7 @@ import cloneDeep from "lodash/cloneDeep"; import get from "lodash/get"; import set from "lodash/set"; import { NoDBPathGiven, DBWrongPassword } from "@ledgerhq/errors"; -import {} from "@ledgerhq/live-common/lib/promise"; +import {} from "@ledgerhq/live-common/promise"; import { encryptData, decryptData } from "~/main/db/crypto"; import { readFile, writeFile } from "~/main/db/fsHelper"; diff --git a/apps/ledger-live-desktop/src/main/internal-lifecycle.js b/apps/ledger-live-desktop/src/main/internal-lifecycle.js index c80b7b7e5748..f311b1443c37 100644 --- a/apps/ledger-live-desktop/src/main/internal-lifecycle.js +++ b/apps/ledger-live-desktop/src/main/internal-lifecycle.js @@ -1,7 +1,7 @@ // @flow import { app, ipcMain } from "electron"; import path from "path"; -import { setEnvUnsafe, getAllEnvs } from "@ledgerhq/live-common/lib/env"; +import { setEnvUnsafe, getAllEnvs } from "@ledgerhq/live-common/env"; import { isRestartNeeded } from "~/helpers/env"; import logger from "~/logger"; import { getMainWindow } from "./window-lifecycle"; diff --git a/apps/ledger-live-desktop/src/main/updater/createElectronAppUpdater.js b/apps/ledger-live-desktop/src/main/updater/createElectronAppUpdater.js index 4bffbbc500da..d71607b5abff 100644 --- a/apps/ledger-live-desktop/src/main/updater/createElectronAppUpdater.js +++ b/apps/ledger-live-desktop/src/main/updater/createElectronAppUpdater.js @@ -5,7 +5,7 @@ import path from "path"; import fs from "fs"; import { UpdateFetchFileFail } from "@ledgerhq/errors"; -import network from "@ledgerhq/live-common/lib/network"; +import network from "@ledgerhq/live-common/network"; import { fsReadFile } from "~/helpers/fs"; import createAppUpdater from "./createAppUpdater"; diff --git a/apps/ledger-live-desktop/src/main/window-lifecycle.js b/apps/ledger-live-desktop/src/main/window-lifecycle.js index 91eb8ee2149d..67617e39339d 100644 --- a/apps/ledger-live-desktop/src/main/window-lifecycle.js +++ b/apps/ledger-live-desktop/src/main/window-lifecycle.js @@ -2,7 +2,7 @@ import "./setup"; import { BrowserWindow, screen, shell, app } from "electron"; import path from "path"; -import { delay } from "@ledgerhq/live-common/lib/promise"; +import { delay } from "@ledgerhq/live-common/promise"; import { URL } from "url"; import * as remoteMain from "@electron/remote/main"; diff --git a/apps/ledger-live-desktop/src/renderer/App.js b/apps/ledger-live-desktop/src/renderer/App.js deleted file mode 100644 index 6df15372d32a..000000000000 --- a/apps/ledger-live-desktop/src/renderer/App.js +++ /dev/null @@ -1,115 +0,0 @@ -// @flow -import React, { useEffect, useState } from "react"; -import { Provider, useSelector } from "react-redux"; -import type { Store } from "redux"; -import { HashRouter as Router } from "react-router-dom"; -import { NftMetadataProvider } from "@ledgerhq/live-common/lib/nft/NftMetadataProvider"; - -import "./global.css"; -import "tippy.js/dist/tippy.css"; -import "tippy.js/animations/shift-away.css"; -import "tippy.js/animations/shift-toward.css"; -import "tippy.js/dist/svg-arrow.css"; - -import type { State } from "~/renderer/reducers"; - -import StyleProvider from "~/renderer/styles/StyleProvider"; - -import { UpdaterProvider } from "~/renderer/components/Updater/UpdaterContext"; -import ThrowBlock from "~/renderer/components/ThrowBlock"; -import LiveStyleSheetManager from "~/renderer/styles/LiveStyleSheetManager"; -import { RemoteConfigProvider } from "~/renderer/components/RemoteConfig"; -// $FlowFixMe -import { FirebaseRemoteConfigProvider } from "~/renderer/components/FirebaseRemoteConfig"; -// $FlowFixMe -import { FirebaseFeatureFlagsProvider } from "~/renderer/components/FirebaseFeatureFlags"; -import CountervaluesProvider from "~/renderer/components/CountervaluesProvider"; -import DrawerProvider from "~/renderer/drawers/Provider"; -import Default from "./Default"; -import WalletConnectProvider from "./screens/WalletConnect/Provider"; -import { AnnouncementProviderWrapper } from "~/renderer/components/AnnouncementProviderWrapper"; -import { PlatformAppProviderWrapper } from "~/renderer/components/PlatformAppProviderWrapper"; -import { ToastProvider } from "@ledgerhq/live-common/lib/notifications/ToastProvider"; -import { themeSelector } from "./actions/general"; -// $FlowFixMe -import MarketDataProvider from "~/renderer/screens/market/MarketDataProviderWrapper"; - -const reloadApp = event => { - if ((event.ctrlKey || event.metaKey) && event.key === "r") { - window.api.reloadRenderer(); - } -}; - -type Props = { - store: Store, - initialCountervalues: *, -}; - -const InnerApp = ({ initialCountervalues }: { initialCountervalues: * }) => { - const [reloadEnabled, setReloadEnabled] = useState(true); - - useEffect(() => { - const reload = e => { - if (reloadEnabled) { - reloadApp(e); - } - }; - - window.addEventListener("keydown", reload); - return () => window.removeEventListener("keydown", reload); - }, [reloadEnabled]); - - const selectedPalette = useSelector(themeSelector) || "light"; - - return ( - - { - if (!__DEV__) { - setReloadEnabled(false); - } - }} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -const App = ({ store, initialCountervalues }: Props) => { - return ( - - - - - - ); -}; - -export default App; diff --git a/apps/ledger-live-desktop/src/renderer/App.jsx b/apps/ledger-live-desktop/src/renderer/App.jsx new file mode 100644 index 000000000000..1e00aaad5721 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/App.jsx @@ -0,0 +1,115 @@ +// @flow +import React, { useEffect, useState } from "react"; +import { Provider, useSelector } from "react-redux"; +import type { Store } from "redux"; +import { HashRouter as Router } from "react-router-dom"; +import { NftMetadataProvider } from "@ledgerhq/live-common/nft/NftMetadataProvider/index"; + +import "./global.css"; +import "tippy.js/dist/tippy.css"; +import "tippy.js/animations/shift-away.css"; +import "tippy.js/animations/shift-toward.css"; +import "tippy.js/dist/svg-arrow.css"; + +import type { State } from "~/renderer/reducers"; + +import StyleProvider from "~/renderer/styles/StyleProvider"; + +import { UpdaterProvider } from "~/renderer/components/Updater/UpdaterContext"; +import ThrowBlock from "~/renderer/components/ThrowBlock"; +import LiveStyleSheetManager from "~/renderer/styles/LiveStyleSheetManager"; +import { RemoteConfigProvider } from "~/renderer/components/RemoteConfig"; +// $FlowFixMe +import { FirebaseRemoteConfigProvider } from "~/renderer/components/FirebaseRemoteConfig"; +// $FlowFixMe +import { FirebaseFeatureFlagsProvider } from "~/renderer/components/FirebaseFeatureFlags"; +import CountervaluesProvider from "~/renderer/components/CountervaluesProvider"; +import DrawerProvider from "~/renderer/drawers/Provider"; +import Default from "./Default"; +import WalletConnectProvider from "./screens/WalletConnect/Provider"; +import { AnnouncementProviderWrapper } from "~/renderer/components/AnnouncementProviderWrapper"; +import { PlatformAppProviderWrapper } from "~/renderer/components/PlatformAppProviderWrapper"; +import { ToastProvider } from "@ledgerhq/live-common/notifications/ToastProvider/index"; +import { themeSelector } from "./actions/general"; +// $FlowFixMe +import MarketDataProvider from "~/renderer/screens/market/MarketDataProviderWrapper"; + +const reloadApp = event => { + if ((event.ctrlKey || event.metaKey) && event.key === "r") { + window.api.reloadRenderer(); + } +}; + +type Props = { + store: Store, + initialCountervalues: *, +}; + +const InnerApp = ({ initialCountervalues }: { initialCountervalues: * }) => { + const [reloadEnabled, setReloadEnabled] = useState(true); + + useEffect(() => { + const reload = e => { + if (reloadEnabled) { + reloadApp(e); + } + }; + + window.addEventListener("keydown", reload); + return () => window.removeEventListener("keydown", reload); + }, [reloadEnabled]); + + const selectedPalette = useSelector(themeSelector) || "light"; + + return ( + + { + if (!__DEV__) { + setReloadEnabled(false); + } + }} + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +const App = ({ store, initialCountervalues }: Props) => { + return ( + + + + + + ); +}; + +export default App; diff --git a/apps/ledger-live-desktop/src/renderer/AppError.js b/apps/ledger-live-desktop/src/renderer/AppError.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/AppError.js rename to apps/ledger-live-desktop/src/renderer/AppError.jsx diff --git a/apps/ledger-live-desktop/src/renderer/Default.js b/apps/ledger-live-desktop/src/renderer/Default.js deleted file mode 100644 index d3d8abd3a3f9..000000000000 --- a/apps/ledger-live-desktop/src/renderer/Default.js +++ /dev/null @@ -1,284 +0,0 @@ -// @flow -import React, { useEffect, useRef } from "react"; -import styled from "styled-components"; -import { ipcRenderer } from "electron"; -import { Redirect, Route, Switch, useLocation } from "react-router-dom"; -import { FeatureToggle } from "@ledgerhq/live-common/lib/featureFlags"; -import TrackAppStart from "~/renderer/components/TrackAppStart"; -import { BridgeSyncProvider } from "~/renderer/bridge/BridgeSyncContext"; -import { SyncNewAccounts } from "~/renderer/bridge/SyncNewAccounts"; -import Dashboard from "~/renderer/screens/dashboard"; -import Settings from "~/renderer/screens/settings"; -import Accounts from "~/renderer/screens/accounts"; -import Card from "~/renderer/screens/card"; -import Manager from "~/renderer/screens/manager"; -import Exchange from "~/renderer/screens/exchange"; -import Swap2 from "~/renderer/screens/exchange/Swap2"; -import USBTroubleshooting from "~/renderer/screens/USBTroubleshooting"; -import Account from "~/renderer/screens/account"; -import WalletConnect from "~/renderer/screens/WalletConnect"; -import Asset from "~/renderer/screens/asset"; -import Lend from "~/renderer/screens/lend"; -import PlatformCatalog from "~/renderer/screens/platform"; -import PlatformApp from "~/renderer/screens/platform/App"; -import NFTGallery from "~/renderer/screens/nft/Gallery"; -import NFTCollection from "~/renderer/screens/nft/Gallery/Collection"; -import Box from "~/renderer/components/Box/Box"; -import ListenDevices from "~/renderer/components/ListenDevices"; -import ExportLogsButton from "~/renderer/components/ExportLogsButton"; -import Idler from "~/renderer/components/Idler"; -import IsUnlocked from "~/renderer/components/IsUnlocked"; -import OnboardingOrElse from "~/renderer/components/OnboardingOrElse"; -import AppRegionDrag from "~/renderer/components/AppRegionDrag"; -import IsNewVersion from "~/renderer/components/IsNewVersion"; -import IsSystemLanguageAvailable from "~/renderer/components/IsSystemLanguageAvailable"; -// $FlowFixMe -import IsTermOfUseUpdated from "./components/IsTermOfUseUpdated"; -import DeviceBusyIndicator from "~/renderer/components/DeviceBusyIndicator"; -import KeyboardContent from "~/renderer/components/KeyboardContent"; -import PerfIndicator from "~/renderer/components/PerfIndicator"; -import MainSideBar from "~/renderer/components/MainSideBar"; -import TriggerAppReady from "~/renderer/components/TriggerAppReady"; -import ContextMenuWrapper from "~/renderer/components/ContextMenu/ContextMenuWrapper"; -import DebugUpdater from "~/renderer/components/debug/DebugUpdater"; -import DebugTheme from "~/renderer/components/debug/DebugTheme"; -import DebugFirmwareUpdater from "~/renderer/components/debug/DebugFirmwareUpdater"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import Page from "~/renderer/components/Page"; -import AnalyticsConsole from "~/renderer/components/AnalyticsConsole"; -import DebugMock from "~/renderer/components/debug/DebugMock"; -import DebugSkeletons from "~/renderer/components/debug/DebugSkeletons"; -import { DisableTransactionBroadcastWarning } from "~/renderer/components/debug/DisableTransactionBroadcastWarning"; -import { DebugWrapper } from "~/renderer/components/debug/shared"; -import useDeeplink from "~/renderer/hooks/useDeeplinking"; -import useUSBTroubleshooting from "~/renderer/hooks/useUSBTroubleshooting"; -import ModalsLayer from "./ModalsLayer"; -import { ToastOverlay } from "~/renderer/components/ToastOverlay"; -import Drawer from "~/renderer/drawers/Drawer"; -import UpdateBanner from "~/renderer/components/Updater/Banner"; -import FirmwareUpdateBanner from "~/renderer/components/FirmwareUpdateBanner"; -// $FlowFixMe -import Market from "~/renderer/screens/market"; -// $FlowFixMe -import MarketCoinScreen from "~/renderer/screens/market/MarketCoinScreen"; -// $FlowFixMe -import Learn from "~/renderer/screens/learn"; - -import { useProviders } from "~/renderer/screens/exchange/Swap2/Form"; - -// in order to test sentry integration, we need the ability to test it out. -const LetThisCrashForCrashTest = () => { - throw new Error("CrashTestRendering"); -}; -const LetMainSendCrashTest = () => { - useEffect(() => { - ipcRenderer.send("mainCrashTest"); - }, []); - return null; -}; -const LetInternalSendCrashTest = () => { - useEffect(() => { - ipcRenderer.send("internalCrashTest"); - }, []); - return null; -}; - -export const TopBannerContainer: ThemedComponent<{}> = styled.div` - position: sticky; - top: 0; - z-index: 19; - & > *:not(:first-child) { - display: none; - } -`; - -const NightlyLayerR = () => { - const children = []; - const w = 200; - const h = 100; - for (let y = 0.5; y < 20; y++) { - for (let x = 0.5; x < 20; x++) { - children.push( -
- PRERELEASE -
- {__APP_VERSION__} -
, - ); - } - } - return ( -
- {children} -
- ); -}; - -const NightlyLayer = React.memo(NightlyLayerR); - -export default function Default() { - const location = useLocation(); - const ref: React$ElementRef = useRef(); - useDeeplink(); - useUSBTroubleshooting(); - - useProviders(); // prefetch data from swap providers here - - // every time location changes, scroll back up - useEffect(() => { - if (ref && ref.current) { - ref.current.scrollTo(0, 0); - } - }, [location]); - - return ( - <> - - - - - - {process.platform === "darwin" ? : null} - - - - - - - {process.env.DEBUG_THEME ? : null} - {process.env.MOCK ? : null} - {process.env.DEBUG_UPDATE ? : null} - {process.env.DEBUG_SKELETONS ? : null} - {process.env.DEBUG_FIRMWARE_UPDATE ? : null} - - {process.env.DISABLE_TRANSACTION_BROADCAST ? ( - - ) : null} - - - - - - - - - - - - - - - - - - - - } /> - } /> - } /> - } /> - - } /> - } - exact - /> - } - /> - } /> - } /> - } - /> - } - /> - } - /> - } /> - } - /> - } /> - } - /> - - } - /> - } /> - - } /> - - - - - - - - {__PRERELEASE__ && __CHANNEL__ !== "next" && !__CHANNEL__.includes("sha") ? ( - - ) : null} - - - - - - - - - - - - - - - - - - - - - - {process.env.ANALYTICS_CONSOLE ? : null} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/Default.jsx b/apps/ledger-live-desktop/src/renderer/Default.jsx new file mode 100644 index 000000000000..19eacd30e064 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/Default.jsx @@ -0,0 +1,284 @@ +// @flow +import React, { useEffect, useRef } from "react"; +import styled from "styled-components"; +import { ipcRenderer } from "electron"; +import { Redirect, Route, Switch, useLocation } from "react-router-dom"; +import { FeatureToggle } from "@ledgerhq/live-common/featureFlags/index"; +import TrackAppStart from "~/renderer/components/TrackAppStart"; +import { BridgeSyncProvider } from "~/renderer/bridge/BridgeSyncContext"; +import { SyncNewAccounts } from "~/renderer/bridge/SyncNewAccounts"; +import Dashboard from "~/renderer/screens/dashboard"; +import Settings from "~/renderer/screens/settings"; +import Accounts from "~/renderer/screens/accounts"; +import Card from "~/renderer/screens/card"; +import Manager from "~/renderer/screens/manager"; +import Exchange from "~/renderer/screens/exchange"; +import Swap2 from "~/renderer/screens/exchange/Swap2"; +import USBTroubleshooting from "~/renderer/screens/USBTroubleshooting"; +import Account from "~/renderer/screens/account"; +import WalletConnect from "~/renderer/screens/WalletConnect"; +import Asset from "~/renderer/screens/asset"; +import Lend from "~/renderer/screens/lend"; +import PlatformCatalog from "~/renderer/screens/platform"; +import PlatformApp from "~/renderer/screens/platform/App"; +import NFTGallery from "~/renderer/screens/nft/Gallery"; +import NFTCollection from "~/renderer/screens/nft/Gallery/Collection"; +import Box from "~/renderer/components/Box/Box"; +import ListenDevices from "~/renderer/components/ListenDevices"; +import ExportLogsButton from "~/renderer/components/ExportLogsButton"; +import Idler from "~/renderer/components/Idler"; +import IsUnlocked from "~/renderer/components/IsUnlocked"; +import OnboardingOrElse from "~/renderer/components/OnboardingOrElse"; +import AppRegionDrag from "~/renderer/components/AppRegionDrag"; +import IsNewVersion from "~/renderer/components/IsNewVersion"; +import IsSystemLanguageAvailable from "~/renderer/components/IsSystemLanguageAvailable"; +// $FlowFixMe +import IsTermOfUseUpdated from "./components/IsTermOfUseUpdated"; +import DeviceBusyIndicator from "~/renderer/components/DeviceBusyIndicator"; +import KeyboardContent from "~/renderer/components/KeyboardContent"; +import PerfIndicator from "~/renderer/components/PerfIndicator"; +import MainSideBar from "~/renderer/components/MainSideBar"; +import TriggerAppReady from "~/renderer/components/TriggerAppReady"; +import ContextMenuWrapper from "~/renderer/components/ContextMenu/ContextMenuWrapper"; +import DebugUpdater from "~/renderer/components/debug/DebugUpdater"; +import DebugTheme from "~/renderer/components/debug/DebugTheme"; +import DebugFirmwareUpdater from "~/renderer/components/debug/DebugFirmwareUpdater"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import Page from "~/renderer/components/Page"; +import AnalyticsConsole from "~/renderer/components/AnalyticsConsole"; +import DebugMock from "~/renderer/components/debug/DebugMock"; +import DebugSkeletons from "~/renderer/components/debug/DebugSkeletons"; +import { DisableTransactionBroadcastWarning } from "~/renderer/components/debug/DisableTransactionBroadcastWarning"; +import { DebugWrapper } from "~/renderer/components/debug/shared"; +import useDeeplink from "~/renderer/hooks/useDeeplinking"; +import useUSBTroubleshooting from "~/renderer/hooks/useUSBTroubleshooting"; +import ModalsLayer from "./ModalsLayer"; +import { ToastOverlay } from "~/renderer/components/ToastOverlay"; +import Drawer from "~/renderer/drawers/Drawer"; +import UpdateBanner from "~/renderer/components/Updater/Banner"; +import FirmwareUpdateBanner from "~/renderer/components/FirmwareUpdateBanner"; +// $FlowFixMe +import Market from "~/renderer/screens/market"; +// $FlowFixMe +import MarketCoinScreen from "~/renderer/screens/market/MarketCoinScreen"; +// $FlowFixMe +import Learn from "~/renderer/screens/learn"; + +import { useProviders } from "~/renderer/screens/exchange/Swap2/Form"; + +// in order to test sentry integration, we need the ability to test it out. +const LetThisCrashForCrashTest = () => { + throw new Error("CrashTestRendering"); +}; +const LetMainSendCrashTest = () => { + useEffect(() => { + ipcRenderer.send("mainCrashTest"); + }, []); + return null; +}; +const LetInternalSendCrashTest = () => { + useEffect(() => { + ipcRenderer.send("internalCrashTest"); + }, []); + return null; +}; + +export const TopBannerContainer: ThemedComponent<{}> = styled.div` + position: sticky; + top: 0; + z-index: 19; + & > *:not(:first-child) { + display: none; + } +`; + +const NightlyLayerR = () => { + const children = []; + const w = 200; + const h = 100; + for (let y = 0.5; y < 20; y++) { + for (let x = 0.5; x < 20; x++) { + children.push( +
+ PRERELEASE +
+ {__APP_VERSION__} +
, + ); + } + } + return ( +
+ {children} +
+ ); +}; + +const NightlyLayer = React.memo(NightlyLayerR); + +export default function Default() { + const location = useLocation(); + const ref: React$ElementRef = useRef(); + useDeeplink(); + useUSBTroubleshooting(); + + useProviders(); // prefetch data from swap providers here + + // every time location changes, scroll back up + useEffect(() => { + if (ref && ref.current) { + ref.current.scrollTo(0, 0); + } + }, [location]); + + return ( + <> + + + + + + {process.platform === "darwin" ? : null} + + + + + + + {process.env.DEBUG_THEME ? : null} + {process.env.MOCK ? : null} + {process.env.DEBUG_UPDATE ? : null} + {process.env.DEBUG_SKELETONS ? : null} + {process.env.DEBUG_FIRMWARE_UPDATE ? : null} + + {process.env.DISABLE_TRANSACTION_BROADCAST ? ( + + ) : null} + + + + + + + + + + + + + + + + + + + + } /> + } /> + } /> + } /> + + } /> + } + exact + /> + } + /> + } /> + } /> + } + /> + } + /> + } + /> + } /> + } + /> + } /> + } + /> + + } + /> + } /> + + } /> + + + + + + + + {__PRERELEASE__ && __CHANNEL__ !== "next" && !__CHANNEL__.includes("sha") ? ( + + ) : null} + + + + + + + + + + + + + + + + + + + + + + {process.env.ANALYTICS_CONSOLE ? : null} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/ModalsLayer.js b/apps/ledger-live-desktop/src/renderer/ModalsLayer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/ModalsLayer.js rename to apps/ledger-live-desktop/src/renderer/ModalsLayer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/ReactRoot.js b/apps/ledger-live-desktop/src/renderer/ReactRoot.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/ReactRoot.js rename to apps/ledger-live-desktop/src/renderer/ReactRoot.jsx diff --git a/apps/ledger-live-desktop/src/renderer/actions/accounts.js b/apps/ledger-live-desktop/src/renderer/actions/accounts.js index 149cea953a07..0fd11b5da5cd 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/accounts.js +++ b/apps/ledger-live-desktop/src/renderer/actions/accounts.js @@ -1,7 +1,7 @@ // @flow -import type { Account, SubAccount } from "@ledgerhq/live-common/lib/types"; -import { implicitMigration } from "@ledgerhq/live-common/lib/migrations/accounts"; +import type { Account, SubAccount } from "@ledgerhq/live-common/types/index"; +import { implicitMigration } from "@ledgerhq/live-common/migrations/accounts"; import { getKey } from "~/renderer/storage"; export const replaceAccounts = (payload: Account[]) => ({ diff --git a/apps/ledger-live-desktop/src/renderer/actions/devices.js b/apps/ledger-live-desktop/src/renderer/actions/devices.js index 33cb97422615..92a2162c5ac5 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/devices.js +++ b/apps/ledger-live-desktop/src/renderer/actions/devices.js @@ -1,6 +1,6 @@ // @flow -import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types"; +import type { Device } from "@ledgerhq/live-common/hw/actions/types"; export type SetCurrentDevice = (Device | null) => { type: string, payload: Device | null }; export const setCurrentDevice: SetCurrentDevice = payload => ({ diff --git a/apps/ledger-live-desktop/src/renderer/actions/general.js b/apps/ledger-live-desktop/src/renderer/actions/general.js index 9db254e6cce0..5e70057d4f17 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/general.js +++ b/apps/ledger-live-desktop/src/renderer/actions/general.js @@ -3,20 +3,20 @@ import { useMemo, useCallback, useEffect, useState } from "react"; import { useSelector, useDispatch } from "react-redux"; import type { OutputSelector } from "reselect"; import { createSelector } from "reselect"; -import type { Account } from "@ledgerhq/live-common/lib/types"; -import { isAccountDelegating } from "@ledgerhq/live-common/lib/families/tezos/bakers"; +import type { Account } from "@ledgerhq/live-common/types/index"; +import { isAccountDelegating } from "@ledgerhq/live-common/families/tezos/bakers"; import { flattenSortAccounts, sortAccountsComparatorFromOrder, -} from "@ledgerhq/live-common/lib/account"; +} from "@ledgerhq/live-common/account/index"; import { reorderAccounts } from "~/renderer/actions/accounts"; -import type { FlattenAccountsOptions } from "@ledgerhq/live-common/lib/account"; +import type { FlattenAccountsOptions } from "@ledgerhq/live-common/account/index"; import { useCalculateCountervalueCallback as useCalculateCountervalueCallbackCommon, useTrackingPairForAccounts, -} from "@ledgerhq/live-common/lib/countervalues/react"; -import type { TrackingPair } from "@ledgerhq/live-common/lib/countervalues/types"; -import { useDistribution as useDistributionRaw } from "@ledgerhq/live-common/lib/portfolio/v2/react"; +} from "@ledgerhq/live-common/countervalues/react"; +import type { TrackingPair } from "@ledgerhq/live-common/countervalues/types"; +import { useDistribution as useDistributionRaw } from "@ledgerhq/live-common/portfolio/v2/react"; import type { State } from "~/renderer/reducers"; import { accountsSelector, activeAccountsSelector } from "~/renderer/reducers/accounts"; import { osDarkModeSelector } from "~/renderer/reducers/application"; diff --git a/apps/ledger-live-desktop/src/renderer/actions/portfolio.js b/apps/ledger-live-desktop/src/renderer/actions/portfolio.js index 591ea5053033..594f4a7245d8 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/portfolio.js +++ b/apps/ledger-live-desktop/src/renderer/actions/portfolio.js @@ -1,12 +1,12 @@ // @flow import { useSelector } from "react-redux"; -import type { CryptoCurrency, TokenCurrency, AccountLike } from "@ledgerhq/live-common/lib/types"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/portfolio/v2/types"; +import type { CryptoCurrency, TokenCurrency, AccountLike } from "@ledgerhq/live-common/types/index"; +import type { PortfolioRange } from "@ledgerhq/live-common/portfolio/v2/types"; import { usePortfolio as usePortfolioRaw, useBalanceHistoryWithCountervalue as useBalanceHistoryWithCountervalueRaw, useCurrencyPortfolio as useCurrencyPortfolioRaw, -} from "@ledgerhq/live-common/lib/portfolio/v2/react"; +} from "@ledgerhq/live-common/portfolio/v2/react"; import { selectedTimeRangeSelector } from "~/renderer/reducers/settings"; import { counterValueCurrencySelector } from "./../reducers/settings"; import { accountsSelector } from "./../reducers/accounts"; diff --git a/apps/ledger-live-desktop/src/renderer/actions/settings.js b/apps/ledger-live-desktop/src/renderer/actions/settings.js index c8222122d8af..e70fbd4bbe96 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/settings.js +++ b/apps/ledger-live-desktop/src/renderer/actions/settings.js @@ -4,9 +4,9 @@ import type { Dispatch } from "redux"; import { useDispatch, useSelector } from "react-redux"; import { useTranslation } from "react-i18next"; import type { DeviceModelId } from "@ledgerhq/devices"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/portfolio/v2/types"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import type { DeviceModelInfo } from "@ledgerhq/live-common/lib/types/manager"; +import type { PortfolioRange } from "@ledgerhq/live-common/portfolio/v2/types"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import type { DeviceModelInfo } from "@ledgerhq/live-common/types/manager"; import { setEnvOnAllThreads } from "~/helpers/env"; import type { SettingsState as Settings } from "~/renderer/reducers/settings"; import { diff --git a/apps/ledger-live-desktop/src/renderer/actions/swap.js b/apps/ledger-live-desktop/src/renderer/actions/swap.js index 967902d3d25e..8e9f70686da8 100644 --- a/apps/ledger-live-desktop/src/renderer/actions/swap.js +++ b/apps/ledger-live-desktop/src/renderer/actions/swap.js @@ -1,11 +1,8 @@ // @flow -import { getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import { flattenAccounts } from "@ledgerhq/live-common/lib/account/helpers"; -import type { - Transaction, - UPDATE_PROVIDERS_TYPE, -} from "@ledgerhq/live-common/lib/exchange/swap/types"; -import type { Account, TokenAccount } from "@ledgerhq/live-common/lib/types"; +import { getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import { flattenAccounts } from "@ledgerhq/live-common/account/helpers"; +import type { Transaction, UPDATE_PROVIDERS_TYPE } from "@ledgerhq/live-common/exchange/swap/types"; +import type { Account, TokenAccount } from "@ledgerhq/live-common/types/index"; import memoize from "lodash/memoize"; import { createAction } from "redux-actions"; import type { OutputSelector } from "reselect"; diff --git a/apps/ledger-live-desktop/src/renderer/animations/index.js b/apps/ledger-live-desktop/src/renderer/animations/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/animations/index.js rename to apps/ledger-live-desktop/src/renderer/animations/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.js b/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.js deleted file mode 100644 index 862bfb19cada..000000000000 --- a/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.js +++ /dev/null @@ -1,65 +0,0 @@ -// @flow - -import React, { useCallback, useEffect, useRef } from "react"; -import { BridgeSync } from "@ledgerhq/live-common/lib/bridge/react"; -import { toAccountRaw } from "@ledgerhq/live-common/lib/account"; -import { useSelector, useDispatch } from "react-redux"; -import logger from "~/logger"; -import { updateAccountWithUpdater } from "~/renderer/actions/accounts"; -import { accountsSelector } from "~/renderer/reducers/accounts"; -import { recentlyChangedExperimental } from "~/renderer/experimental"; -import { recentlyKilledInternalProcess } from "~/renderer/reset"; -import { track } from "~/renderer/analytics/segment"; -import { prepareCurrency, hydrateCurrency } from "./cache"; -import { hasOngoingSync } from "./proxy"; -import { blacklistedTokenIdsSelector } from "~/renderer/reducers/settings"; -import { command } from "~/renderer/commands"; - -export const BridgeSyncProvider = ({ children }: { children: React$Node }) => { - const accounts = useSelector(accountsSelector); - const accountsRef = useRef(accounts); - const blacklistedTokenIds = useSelector(blacklistedTokenIdsSelector); - const dispatch = useDispatch(); - const updateAccount = useCallback( - (accountId, updater) => dispatch(updateAccountWithUpdater(accountId, updater)), - [dispatch], - ); - - // during ongoing sync, if an account is changed, we inform the internal process of its latest state to the sync can properly reconciliate - useEffect(() => { - accounts.forEach(account => { - const prev = accountsRef.current.find(a => a.id === account.id); - if (prev !== account && hasOngoingSync(account.id)) { - command("AccountSyncSet")({ account: toAccountRaw(account) }).subscribe(); - } - }); - accountsRef.current = accounts; - }, [accounts]); - - const recoverError = useCallback(error => { - const isInternalProcessError = error && error.message.includes("Internal process error"); - if ( - isInternalProcessError && - (recentlyKilledInternalProcess() || recentlyChangedExperimental()) - ) { - // This error is normal because the thread was recently killed. we silent it for the user. - return; - } - logger.critical(error); - return error; - }, []); - - return ( - - {children} - - ); -}; diff --git a/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.jsx b/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.jsx new file mode 100644 index 000000000000..7c6b34b65e2a --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/bridge/BridgeSyncContext.jsx @@ -0,0 +1,65 @@ +// @flow + +import React, { useCallback, useEffect, useRef } from "react"; +import { BridgeSync } from "@ledgerhq/live-common/bridge/react/index"; +import { toAccountRaw } from "@ledgerhq/live-common/account/index"; +import { useSelector, useDispatch } from "react-redux"; +import logger from "~/logger"; +import { updateAccountWithUpdater } from "~/renderer/actions/accounts"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import { recentlyChangedExperimental } from "~/renderer/experimental"; +import { recentlyKilledInternalProcess } from "~/renderer/reset"; +import { track } from "~/renderer/analytics/segment"; +import { prepareCurrency, hydrateCurrency } from "./cache"; +import { hasOngoingSync } from "./proxy"; +import { blacklistedTokenIdsSelector } from "~/renderer/reducers/settings"; +import { command } from "~/renderer/commands"; + +export const BridgeSyncProvider = ({ children }: { children: React$Node }) => { + const accounts = useSelector(accountsSelector); + const accountsRef = useRef(accounts); + const blacklistedTokenIds = useSelector(blacklistedTokenIdsSelector); + const dispatch = useDispatch(); + const updateAccount = useCallback( + (accountId, updater) => dispatch(updateAccountWithUpdater(accountId, updater)), + [dispatch], + ); + + // during ongoing sync, if an account is changed, we inform the internal process of its latest state to the sync can properly reconciliate + useEffect(() => { + accounts.forEach(account => { + const prev = accountsRef.current.find(a => a.id === account.id); + if (prev !== account && hasOngoingSync(account.id)) { + command("AccountSyncSet")({ account: toAccountRaw(account) }).subscribe(); + } + }); + accountsRef.current = accounts; + }, [accounts]); + + const recoverError = useCallback(error => { + const isInternalProcessError = error && error.message.includes("Internal process error"); + if ( + isInternalProcessError && + (recentlyKilledInternalProcess() || recentlyChangedExperimental()) + ) { + // This error is normal because the thread was recently killed. we silent it for the user. + return; + } + logger.critical(error); + return error; + }, []); + + return ( + + {children} + + ); +}; diff --git a/apps/ledger-live-desktop/src/renderer/bridge/SyncNewAccounts.js b/apps/ledger-live-desktop/src/renderer/bridge/SyncNewAccounts.js index 8ee4d8434551..e4fd8b02fabc 100644 --- a/apps/ledger-live-desktop/src/renderer/bridge/SyncNewAccounts.js +++ b/apps/ledger-live-desktop/src/renderer/bridge/SyncNewAccounts.js @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import { useSelector } from "react-redux"; -import { useBridgeSync } from "@ledgerhq/live-common/lib/bridge/react"; +import { useBridgeSync } from "@ledgerhq/live-common/bridge/react/index"; import { accountsSelector } from "../reducers/accounts"; export const SyncNewAccounts = ({ priority }: { priority: number }) => { diff --git a/apps/ledger-live-desktop/src/renderer/bridge/cache.js b/apps/ledger-live-desktop/src/renderer/bridge/cache.js index ce9bbac14f42..624c1b27df3c 100644 --- a/apps/ledger-live-desktop/src/renderer/bridge/cache.js +++ b/apps/ledger-live-desktop/src/renderer/bridge/cache.js @@ -1,9 +1,9 @@ // @flow import { ipcRenderer } from "electron"; -import { makeBridgeCacheSystem } from "@ledgerhq/live-common/lib/bridge/cache"; +import { makeBridgeCacheSystem } from "@ledgerhq/live-common/bridge/cache"; import { log } from "@ledgerhq/logs"; -import type { CryptoCurrency } from "@ledgerhq/live-common/lib/types"; -import { logger } from "~/logger"; +import type { CryptoCurrency } from "@ledgerhq/live-common/types/index"; +import logger from "~/logger"; export function clearBridgeCache() { Object.keys(global.localStorage) diff --git a/apps/ledger-live-desktop/src/renderer/bridge/proxy-commands.js b/apps/ledger-live-desktop/src/renderer/bridge/proxy-commands.js index 8ab38f793266..32726d70b4f6 100644 --- a/apps/ledger-live-desktop/src/renderer/bridge/proxy-commands.js +++ b/apps/ledger-live-desktop/src/renderer/bridge/proxy-commands.js @@ -16,7 +16,7 @@ import type { SignOperationEventRaw, SignedOperationRaw, OperationRaw, -} from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/types/index"; import { fromTransactionRaw, toTransactionRaw, @@ -24,7 +24,7 @@ import { fromSignedOperationRaw, toSignOperationEventRaw, formatTransaction, -} from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/transaction/index"; import { fromAccountRaw, fromAccountLikeRaw, @@ -32,10 +32,10 @@ import { toOperationRaw, formatOperation, formatAccount, -} from "@ledgerhq/live-common/lib/account"; -import { getCryptoCurrencyById } from "@ledgerhq/live-common/lib/currencies"; -import { toScanAccountEventRaw } from "@ledgerhq/live-common/lib/bridge"; -import * as bridgeImpl from "@ledgerhq/live-common/lib/bridge/impl"; +} from "@ledgerhq/live-common/account/index"; +import { getCryptoCurrencyById } from "@ledgerhq/live-common/currencies/index"; +import { toScanAccountEventRaw } from "@ledgerhq/live-common/bridge/index"; +import * as bridgeImpl from "@ledgerhq/live-common/bridge/impl"; const cmdCurrencyPreload = ({ currencyId }: { currencyId: string }): Observable => { const currency = getCryptoCurrencyById(currencyId); diff --git a/apps/ledger-live-desktop/src/renderer/bridge/proxy.js b/apps/ledger-live-desktop/src/renderer/bridge/proxy.js index f83af82be97f..8bb0b452f952 100644 --- a/apps/ledger-live-desktop/src/renderer/bridge/proxy.js +++ b/apps/ledger-live-desktop/src/renderer/bridge/proxy.js @@ -9,7 +9,7 @@ import type { AccountLike, CurrencyBridge, AccountBridge, -} from "@ledgerhq/live-common/lib/types"; +} from "@ledgerhq/live-common/types/index"; import isEqual from "lodash/isEqual"; import { fromTransactionRaw, @@ -17,15 +17,15 @@ import { toSignedOperationRaw, fromTransactionStatusRaw, fromSignOperationEventRaw, -} from "@ledgerhq/live-common/lib/transaction"; +} from "@ledgerhq/live-common/transaction/index"; import { toAccountLikeRaw, toAccountRaw, fromOperationRaw, -} from "@ledgerhq/live-common/lib/account"; -import { patchAccount } from "@ledgerhq/live-common/lib/reconciliation"; -import { fromScanAccountEventRaw } from "@ledgerhq/live-common/lib/bridge"; -import * as bridgeImpl from "@ledgerhq/live-common/lib/bridge/impl"; +} from "@ledgerhq/live-common/account/index"; +import { patchAccount } from "@ledgerhq/live-common/reconciliation"; +import { fromScanAccountEventRaw } from "@ledgerhq/live-common/bridge/index"; +import * as bridgeImpl from "@ledgerhq/live-common/bridge/impl"; import { command } from "~/renderer/commands"; const scanAccounts = ({ currency, deviceId, syncConfig }) => diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Header.js b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Header.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Header.js rename to apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Header.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.js b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.js deleted file mode 100644 index bdc377c1ac97..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.js +++ /dev/null @@ -1,190 +0,0 @@ -// @flow -import React, { useCallback } from "react"; -import { useHistory } from "react-router-dom"; -import { BigNumber } from "bignumber.js"; -import { useSelector } from "react-redux"; -import styled from "styled-components"; -import { getAccountName } from "@ledgerhq/live-common/lib/account"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types/account"; -import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies"; -import { useCurrencyColor } from "~/renderer/getCurrencyColor"; -import CounterValue from "~/renderer/components/CounterValue"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import Text from "~/renderer/components/Text"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; -import Box from "~/renderer/components/Box"; -import AccountContextMenu from "~/renderer/components/ContextMenu/AccountContextMenu"; -import { accountsSelector } from "~/renderer/reducers/accounts"; -import IconDots from "~/renderer/icons/Dots"; -import Bar from "~/renderer/components/AssetDistribution/Bar"; -import ToolTip from "~/renderer/components/Tooltip"; -import useTheme from "~/renderer/hooks/useTheme"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; - -export type AccountDistributionItem = { - account: AccountLike, - distribution: number, // % of the total (normalized in 0-1) - amount: BigNumber, - currency: CryptoCurrency | TokenCurrency, -}; - -type Props = { - item: AccountDistributionItem, - isVisible: boolean, -}; - -export default function Row({ - item: { currency, amount, distribution, account }, - isVisible, -}: Props) { - const accounts = useSelector(accountsSelector); - const theme = useTheme(); - const history = useHistory(); - const onAccountClick = useCallback( - account => { - setTrackingSource("account allocation"); - history.push({ - pathname: - account.type !== "Account" - ? `/account/${account.parentId}/${account.id}` - : `/account/${account.id}`, - }); - }, - [history], - ); - - const parentAccount = - account.type !== "Account" ? accounts.find(a => a.id === account.parentId) : null; - const color = useCurrencyColor(currency, theme.colors.palette.background.paper); - const displayName = getAccountName(account); - const percentage = (Math.floor(distribution * 10000) / 100).toFixed(2); - const icon = ; - return ( - - onAccountClick(account)}> - - {icon} - - {parentAccount ? ( - - {parentAccount.name} - - ) : null} - - - {displayName} - - - - - - {!!distribution && ( - <> - - {`${percentage}%`} - - - - )} - - - - - - - - - {distribution ? ( - - ) : ( - - {"-"} - - )} - - - - - - - - - - ); -} - -const Wrapper: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - padding: 16px 20px; - cursor: pointer; - - > * { - display: flex; - align-items: center; - flex-direction: row; - box-sizing: border-box; - } - - &:hover { - background: ${p => p.theme.colors.palette.background.default}; - } -`; - -const AccountWrapper: ThemedComponent<{}> = styled.div` - width: 25%; - > :first-child { - margin-right: 10px; - } - > :nth-child(2) { - flex: 1; - align-items: flex-start; - margin-right: 8px; - } -`; -const Distribution: ThemedComponent<{}> = styled.div` - width: 25%; - text-align: right; - > :first-child { - margin-right: 11px; - width: 40px; //max width for a 99.99% case - text-align: right; - } -`; -const Amount: ThemedComponent<{}> = styled.div` - width: 25%; - text-align: right; - justify-content: flex-end; -`; -const Value: ThemedComponent<{}> = styled.div` - width: 20%; - box-sizing: border-box; - padding-left: 8px; - text-align: right; - justify-content: flex-end; -`; -const Dots: ThemedComponent<{}> = styled.div` - width: 5%; - justify-content: flex-end; - cursor: pointer; - color: ${p => p.theme.colors.palette.divider}; - &:hover { - color: ${p => p.theme.colors.palette.text.shade60}; - } -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.jsx b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.jsx new file mode 100644 index 000000000000..cd9115fc48fe --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/Row.jsx @@ -0,0 +1,190 @@ +// @flow +import React, { useCallback } from "react"; +import { useHistory } from "react-router-dom"; +import { BigNumber } from "bignumber.js"; +import { useSelector } from "react-redux"; +import styled from "styled-components"; +import { getAccountName } from "@ledgerhq/live-common/account/index"; +import type { AccountLike } from "@ledgerhq/live-common/types/account"; +import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/types/currencies"; +import { useCurrencyColor } from "~/renderer/getCurrencyColor"; +import CounterValue from "~/renderer/components/CounterValue"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import Text from "~/renderer/components/Text"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; +import Box from "~/renderer/components/Box"; +import AccountContextMenu from "~/renderer/components/ContextMenu/AccountContextMenu"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import IconDots from "~/renderer/icons/Dots"; +import Bar from "~/renderer/components/AssetDistribution/Bar"; +import ToolTip from "~/renderer/components/Tooltip"; +import useTheme from "~/renderer/hooks/useTheme"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; + +export type AccountDistributionItem = { + account: AccountLike, + distribution: number, // % of the total (normalized in 0-1) + amount: BigNumber, + currency: CryptoCurrency | TokenCurrency, +}; + +type Props = { + item: AccountDistributionItem, + isVisible: boolean, +}; + +export default function Row({ + item: { currency, amount, distribution, account }, + isVisible, +}: Props) { + const accounts = useSelector(accountsSelector); + const theme = useTheme(); + const history = useHistory(); + const onAccountClick = useCallback( + account => { + setTrackingSource("account allocation"); + history.push({ + pathname: + account.type !== "Account" + ? `/account/${account.parentId}/${account.id}` + : `/account/${account.id}`, + }); + }, + [history], + ); + + const parentAccount = + account.type !== "Account" ? accounts.find(a => a.id === account.parentId) : null; + const color = useCurrencyColor(currency, theme.colors.palette.background.paper); + const displayName = getAccountName(account); + const percentage = (Math.floor(distribution * 10000) / 100).toFixed(2); + const icon = ; + return ( + + onAccountClick(account)}> + + {icon} + + {parentAccount ? ( + + {parentAccount.name} + + ) : null} + + + {displayName} + + + + + + {!!distribution && ( + <> + + {`${percentage}%`} + + + + )} + + + + + + + + + {distribution ? ( + + ) : ( + + {"-"} + + )} + + + + + + + + + + ); +} + +const Wrapper: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 16px 20px; + cursor: pointer; + + > * { + display: flex; + align-items: center; + flex-direction: row; + box-sizing: border-box; + } + + &:hover { + background: ${p => p.theme.colors.palette.background.default}; + } +`; + +const AccountWrapper: ThemedComponent<{}> = styled.div` + width: 25%; + > :first-child { + margin-right: 10px; + } + > :nth-child(2) { + flex: 1; + align-items: flex-start; + margin-right: 8px; + } +`; +const Distribution: ThemedComponent<{}> = styled.div` + width: 25%; + text-align: right; + > :first-child { + margin-right: 11px; + width: 40px; //max width for a 99.99% case + text-align: right; + } +`; +const Amount: ThemedComponent<{}> = styled.div` + width: 25%; + text-align: right; + justify-content: flex-end; +`; +const Value: ThemedComponent<{}> = styled.div` + width: 20%; + box-sizing: border-box; + padding-left: 8px; + text-align: right; + justify-content: flex-end; +`; +const Dots: ThemedComponent<{}> = styled.div` + width: 5%; + justify-content: flex-end; + cursor: pointer; + color: ${p => p.theme.colors.palette.divider}; + &:hover { + color: ${p => p.theme.colors.palette.text.shade60}; + } +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.js b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.js deleted file mode 100644 index ba5040520c37..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.js +++ /dev/null @@ -1,78 +0,0 @@ -// @flow -import React, { useLayoutEffect, useRef, useState, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { BigNumber } from "bignumber.js"; -import Text from "~/renderer/components/Text"; -import Card from "~/renderer/components/Box/Card"; -import { getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; -import Box from "~/renderer/components/Box"; -import Header from "./Header"; -import Row from "./Row"; - -type Props = { - accounts: AccountLike[], -}; - -export default function AccountDistribution({ accounts }: Props) { - const { t } = useTranslation(); - const total = accounts.reduce((total, a) => total.plus(a.balance), BigNumber(0)); - const accountDistribution = useMemo( - () => - accounts - .map(a => { - const from = getAccountCurrency(a); - return { - account: a, - currency: from, - distribution: a.balance.div(total).toNumber(), - amount: a.balance, - }; - }) - .sort((a, b) => b.distribution - a.distribution), - [accounts, total], - ); - - const cardRef = useRef(null); - const [isVisible, setVisible] = useState(!!process.env.PLAYWRIGHT_RUN || false); - - useLayoutEffect(() => { - const scrollArea = document.getElementById("scroll-area"); - if (!cardRef.current) { - return; - } - const callback = entries => { - if (entries[0] && entries[0].isIntersecting) { - setVisible(true); - } - }; - const observer = new IntersectionObserver(callback, { - threshold: 0.0, - root: scrollArea, - rootMargin: "-48px", - }); - observer.observe(cardRef.current); - return () => { - observer.disconnect(); - }; - }, []); - - return ( - <> - - - {t("accountDistribution.header", { count: accountDistribution.length })} - - - - -
-
- {accountDistribution.map(item => ( - - ))} -
- - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.jsx b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.jsx new file mode 100644 index 000000000000..70259f9558fe --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AccountDistribution/index.jsx @@ -0,0 +1,78 @@ +// @flow +import React, { useLayoutEffect, useRef, useState, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { BigNumber } from "bignumber.js"; +import Text from "~/renderer/components/Text"; +import Card from "~/renderer/components/Box/Card"; +import { getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; +import Box from "~/renderer/components/Box"; +import Header from "./Header"; +import Row from "./Row"; + +type Props = { + accounts: AccountLike[], +}; + +export default function AccountDistribution({ accounts }: Props) { + const { t } = useTranslation(); + const total = accounts.reduce((total, a) => total.plus(a.balance), BigNumber(0)); + const accountDistribution = useMemo( + () => + accounts + .map(a => { + const from = getAccountCurrency(a); + return { + account: a, + currency: from, + distribution: a.balance.div(total).toNumber(), + amount: a.balance, + }; + }) + .sort((a, b) => b.distribution - a.distribution), + [accounts, total], + ); + + const cardRef = useRef(null); + const [isVisible, setVisible] = useState(!!process.env.PLAYWRIGHT_RUN || false); + + useLayoutEffect(() => { + const scrollArea = document.getElementById("scroll-area"); + if (!cardRef.current) { + return; + } + const callback = entries => { + if (entries[0] && entries[0].isIntersecting) { + setVisible(true); + } + }; + const observer = new IntersectionObserver(callback, { + threshold: 0.0, + root: scrollArea, + rootMargin: "-48px", + }); + observer.observe(cardRef.current); + return () => { + observer.disconnect(); + }; + }, []); + + return ( + <> + + + {t("accountDistribution.header", { count: accountDistribution.length })} + + + + +
+
+ {accountDistribution.map(item => ( + + ))} +
+ + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.js b/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.js deleted file mode 100644 index 41cd06af910b..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.js +++ /dev/null @@ -1,43 +0,0 @@ -// @flow -import React from "react"; -import styled from "styled-components"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; -import { getTagDerivationMode } from "@ledgerhq/live-common/lib/derivation"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import Text from "~/renderer/components/Text"; - -const CurrencyLabel: ThemedComponent<*> = styled(Text).attrs(() => ({ - color: "palette.text.shade60", - ff: "Inter|SemiBold", - fontSize: "8px", -}))` - display: inline-block; - padding: 0 4px; - height: 14px; - line-height: 14px; - border-color: currentColor; - border-width: 1px; - border-style: solid; - border-radius: 4px; - text-align: center; - flex: 0 0 auto !important; - box-sizing: content-box; - text-transform: uppercase; - flex: unset; -`; - -type Props = { - account: AccountLike, - margin?: string, -}; - -export default function AccountTagDerivationMode({ account, margin }: Props) { - if (account.type !== "Account") return null; - - const tag = - account.derivationMode !== undefined && - account.derivationMode !== null && - getTagDerivationMode(account.currency, account.derivationMode); - - return tag ? {tag} : null; -} diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.jsx b/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.jsx new file mode 100644 index 000000000000..1b2e1b593853 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AccountTagDerivationMode.jsx @@ -0,0 +1,43 @@ +// @flow +import React from "react"; +import styled from "styled-components"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; +import { getTagDerivationMode } from "@ledgerhq/live-common/derivation"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import Text from "~/renderer/components/Text"; + +const CurrencyLabel: ThemedComponent<*> = styled(Text).attrs(() => ({ + color: "palette.text.shade60", + ff: "Inter|SemiBold", + fontSize: "8px", +}))` + display: inline-block; + padding: 0 4px; + height: 14px; + line-height: 14px; + border-color: currentColor; + border-width: 1px; + border-style: solid; + border-radius: 4px; + text-align: center; + flex: 0 0 auto !important; + box-sizing: content-box; + text-transform: uppercase; + flex: unset; +`; + +type Props = { + account: AccountLike, + margin?: string, +}; + +export default function AccountTagDerivationMode({ account, margin }: Props) { + if (account.type !== "Account") return null; + + const tag = + account.derivationMode !== undefined && + account.derivationMode !== null && + getTagDerivationMode(account.currency, account.derivationMode); + + return tag ? {tag} : null; +} diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.js b/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.js deleted file mode 100644 index 8055ececff41..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.js +++ /dev/null @@ -1,167 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import type { Account } from "@ledgerhq/live-common/lib/types"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { darken } from "~/renderer/styles/helpers"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import Box, { Tabbable } from "~/renderer/components/Box"; -import CheckBox from "~/renderer/components/CheckBox"; -import CryptoCurrencyIconWithCount from "~/renderer/components/CryptoCurrencyIconWithCount"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import Input from "~/renderer/components/Input"; -import AccountTagDerivationMode from "../AccountTagDerivationMode"; - -const InputWrapper = styled.div` - margin-left: 4px; - width: 100%; -`; -type Props = { - account: Account, - isChecked?: boolean, - isDisabled?: boolean, - isReadonly?: boolean, - autoFocusInput?: boolean, - accountName: string, - onToggleAccount?: (Account, boolean) => void, - onEditName?: (Account, string) => void, - hideAmount?: boolean, -}; - -export default class AccountRow extends PureComponent { - handlePreventSubmit = (e: SyntheticEvent<*>) => { - e.preventDefault(); - e.stopPropagation(); - }; - - handleKeyPress = (e: SyntheticEvent) => { - // this fixes a bug with the event propagating to the Tabbable - e.stopPropagation(); - }; - - onToggleAccount = () => { - const { onToggleAccount, account, isChecked } = this.props; - if (onToggleAccount) onToggleAccount(account, !isChecked); - }; - - handleChangeName = (name: string) => { - const { onEditName, account } = this.props; - if (onEditName) onEditName(account, name); - }; - - onClickInput = (e: SyntheticEvent<*>) => { - e.preventDefault(); - e.stopPropagation(); - }; - - onFocus = (e: *) => { - e.target.select(); - }; - - onBlur = (e: *) => { - const { onEditName, account } = this.props; - const { value } = e.target; - if (!value && onEditName) { - // don't leave an empty input on blur - onEditName(account, account.name); - } - }; - - _input = null; - overflowStyles = { textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }; - render() { - const { - account, - isChecked, - onEditName, - accountName, - isDisabled, - isReadonly, - autoFocusInput, - hideAmount, - } = this.props; - - const tokenCount = (account.subAccounts && account.subAccounts.length) || 0; - - const tag = ; - - return ( - - - - {onEditName ? ( - - - - ) : ( -
- {accountName} - {tag} -
- )} -
- {!hideAmount ? ( - - ) : null} - {!isDisabled && !isReadonly && } -
- ); - } -} - -const AccountRowContainer: ThemedComponent<{ - isDisabled?: boolean, -}> = styled(Tabbable).attrs(() => ({ - horizontal: true, - alignItems: "center", - bg: "palette.background.default", - px: 3, - flow: 1, -}))` - height: 48px; - border-radius: 4px; - - opacity: ${p => (p.isDisabled ? 0.5 : 1)}; - pointer-events: ${p => (p.isDisabled ? "none" : "auto")}; - - &:hover { - background-color: ${p => darken(p.theme.colors.palette.background.default, 0.015)}; - } - - &:active { - background-color: ${p => darken(p.theme.colors.palette.background.default, 0.03)}; - } -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.jsx b/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.jsx new file mode 100644 index 000000000000..9a8067977cfc --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AccountsList/AccountRow.jsx @@ -0,0 +1,167 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import type { Account } from "@ledgerhq/live-common/types/index"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { darken } from "~/renderer/styles/helpers"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import Box, { Tabbable } from "~/renderer/components/Box"; +import CheckBox from "~/renderer/components/CheckBox"; +import CryptoCurrencyIconWithCount from "~/renderer/components/CryptoCurrencyIconWithCount"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import Input from "~/renderer/components/Input"; +import AccountTagDerivationMode from "../AccountTagDerivationMode"; + +const InputWrapper = styled.div` + margin-left: 4px; + width: 100%; +`; +type Props = { + account: Account, + isChecked?: boolean, + isDisabled?: boolean, + isReadonly?: boolean, + autoFocusInput?: boolean, + accountName: string, + onToggleAccount?: (Account, boolean) => void, + onEditName?: (Account, string) => void, + hideAmount?: boolean, +}; + +export default class AccountRow extends PureComponent { + handlePreventSubmit = (e: SyntheticEvent<*>) => { + e.preventDefault(); + e.stopPropagation(); + }; + + handleKeyPress = (e: SyntheticEvent) => { + // this fixes a bug with the event propagating to the Tabbable + e.stopPropagation(); + }; + + onToggleAccount = () => { + const { onToggleAccount, account, isChecked } = this.props; + if (onToggleAccount) onToggleAccount(account, !isChecked); + }; + + handleChangeName = (name: string) => { + const { onEditName, account } = this.props; + if (onEditName) onEditName(account, name); + }; + + onClickInput = (e: SyntheticEvent<*>) => { + e.preventDefault(); + e.stopPropagation(); + }; + + onFocus = (e: *) => { + e.target.select(); + }; + + onBlur = (e: *) => { + const { onEditName, account } = this.props; + const { value } = e.target; + if (!value && onEditName) { + // don't leave an empty input on blur + onEditName(account, account.name); + } + }; + + _input = null; + overflowStyles = { textOverflow: "ellipsis", overflow: "hidden", whiteSpace: "nowrap" }; + render() { + const { + account, + isChecked, + onEditName, + accountName, + isDisabled, + isReadonly, + autoFocusInput, + hideAmount, + } = this.props; + + const tokenCount = (account.subAccounts && account.subAccounts.length) || 0; + + const tag = ; + + return ( + + + + {onEditName ? ( + + + + ) : ( +
+ {accountName} + {tag} +
+ )} +
+ {!hideAmount ? ( + + ) : null} + {!isDisabled && !isReadonly && } +
+ ); + } +} + +const AccountRowContainer: ThemedComponent<{ + isDisabled?: boolean, +}> = styled(Tabbable).attrs(() => ({ + horizontal: true, + alignItems: "center", + bg: "palette.background.default", + px: 3, + flow: 1, +}))` + height: 48px; + border-radius: 4px; + + opacity: ${p => (p.isDisabled ? 0.5 : 1)}; + pointer-events: ${p => (p.isDisabled ? "none" : "auto")}; + + &:hover { + background-color: ${p => darken(p.theme.colors.palette.background.default, 0.015)}; + } + + &:active { + background-color: ${p => darken(p.theme.colors.palette.background.default, 0.03)}; + } +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.js b/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.js deleted file mode 100644 index 56ae6fb2ccf5..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.js +++ /dev/null @@ -1,154 +0,0 @@ -// @flow - -import React, { Component } from "react"; -import { withTranslation } from "react-i18next"; -import type { TFunction } from "react-i18next"; -import type { Account, CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types"; -import Box from "~/renderer/components/Box"; -import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; -import FakeLink from "~/renderer/components/FakeLink"; -import { SpoilerIcon } from "~/renderer/components/Spoiler"; -import { openURL } from "~/renderer/linking"; -import AccountRow from "./AccountRow"; - -class AccountsList extends Component< - { - accounts: Account[], - currency?: CryptoCurrency | TokenCurrency, - checkedIds?: string[], - editedNames: { [accountId: string]: string }, - setAccountName?: (Account, string) => void, - onToggleAccount?: Account => void, - onSelectAll?: (Account[]) => void, - onUnselectAll?: (Account[]) => void, - title?: React$Node, - emptyText?: React$Node, - autoFocusFirstInput?: boolean, - collapsible?: boolean, - hideAmount?: boolean, - supportLink?: { id: *, url: string }, - t: TFunction, - ToggleAllComponent?: React$Node, - }, - { - collapsed: boolean, - }, -> { - static defaultProps = { - editedNames: {}, - }; - - state = { - collapsed: !!this.props.collapsible, - }; - - toggleCollapse = () => { - this.setState(({ collapsed }) => ({ collapsed: !collapsed })); - }; - - onSelectAll = () => { - const { accounts, onSelectAll } = this.props; - if (onSelectAll) onSelectAll(accounts); - }; - - onUnselectAll = () => { - const { accounts, onUnselectAll } = this.props; - if (onUnselectAll) onUnselectAll(accounts); - }; - - render() { - const { - accounts, - currency, - checkedIds, - onToggleAccount, - editedNames, - setAccountName, - onSelectAll, - onUnselectAll, - title, - emptyText, - autoFocusFirstInput, - collapsible, - hideAmount, - supportLink, - t, - ToggleAllComponent, - } = this.props; - const { collapsed } = this.state; - const withToggleAll = !!onSelectAll && !!onUnselectAll && accounts.length > 1; - const isAllSelected = - !checkedIds || accounts.every(acc => !!checkedIds.find(id => acc.id === id)); - return ( - - {(title || withToggleAll) && ( - - {title && ( - - {collapsible ? : null} - {title} - - )} - {supportLink ? ( - openURL(supportLink.url)} - label={t("addAccounts.supportLinks." + supportLink.id)} - /> - ) : null} - {ToggleAllComponent || - (withToggleAll && ( - - {isAllSelected - ? t("addAccounts.unselectAll", { count: accounts.length }) - : t("addAccounts.selectAll", { count: accounts.length })} - - ))} - - )} - {collapsed ? null : accounts.length ? ( - - {accounts.map((account, i) => ( - id === account.id) !== undefined} - onToggleAccount={onToggleAccount} - onEditName={setAccountName} - hideAmount={hideAmount} - accountName={ - typeof editedNames[account.id] === "string" - ? editedNames[account.id] - : account.name - } - /> - ))} - - ) : emptyText ? ( - - {emptyText} - - ) : null} - - ); - } -} - -export default withTranslation()(AccountsList); diff --git a/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.jsx b/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.jsx new file mode 100644 index 000000000000..eaf791ae7e20 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AccountsList/index.jsx @@ -0,0 +1,154 @@ +// @flow + +import React, { Component } from "react"; +import { withTranslation } from "react-i18next"; +import type { TFunction } from "react-i18next"; +import type { Account, CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/types/index"; +import Box from "~/renderer/components/Box"; +import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; +import FakeLink from "~/renderer/components/FakeLink"; +import { SpoilerIcon } from "~/renderer/components/Spoiler"; +import { openURL } from "~/renderer/linking"; +import AccountRow from "./AccountRow"; + +class AccountsList extends Component< + { + accounts: Account[], + currency?: CryptoCurrency | TokenCurrency, + checkedIds?: string[], + editedNames: { [accountId: string]: string }, + setAccountName?: (Account, string) => void, + onToggleAccount?: Account => void, + onSelectAll?: (Account[]) => void, + onUnselectAll?: (Account[]) => void, + title?: React$Node, + emptyText?: React$Node, + autoFocusFirstInput?: boolean, + collapsible?: boolean, + hideAmount?: boolean, + supportLink?: { id: *, url: string }, + t: TFunction, + ToggleAllComponent?: React$Node, + }, + { + collapsed: boolean, + }, +> { + static defaultProps = { + editedNames: {}, + }; + + state = { + collapsed: !!this.props.collapsible, + }; + + toggleCollapse = () => { + this.setState(({ collapsed }) => ({ collapsed: !collapsed })); + }; + + onSelectAll = () => { + const { accounts, onSelectAll } = this.props; + if (onSelectAll) onSelectAll(accounts); + }; + + onUnselectAll = () => { + const { accounts, onUnselectAll } = this.props; + if (onUnselectAll) onUnselectAll(accounts); + }; + + render() { + const { + accounts, + currency, + checkedIds, + onToggleAccount, + editedNames, + setAccountName, + onSelectAll, + onUnselectAll, + title, + emptyText, + autoFocusFirstInput, + collapsible, + hideAmount, + supportLink, + t, + ToggleAllComponent, + } = this.props; + const { collapsed } = this.state; + const withToggleAll = !!onSelectAll && !!onUnselectAll && accounts.length > 1; + const isAllSelected = + !checkedIds || accounts.every(acc => !!checkedIds.find(id => acc.id === id)); + return ( + + {(title || withToggleAll) && ( + + {title && ( + + {collapsible ? : null} + {title} + + )} + {supportLink ? ( + openURL(supportLink.url)} + label={t("addAccounts.supportLinks." + supportLink.id)} + /> + ) : null} + {ToggleAllComponent || + (withToggleAll && ( + + {isAllSelected + ? t("addAccounts.unselectAll", { count: accounts.length }) + : t("addAccounts.selectAll", { count: accounts.length })} + + ))} + + )} + {collapsed ? null : accounts.length ? ( + + {accounts.map((account, i) => ( + id === account.id) !== undefined} + onToggleAccount={onToggleAccount} + onEditName={setAccountName} + hideAmount={hideAmount} + accountName={ + typeof editedNames[account.id] === "string" + ? editedNames[account.id] + : account.name + } + /> + ))} + + ) : emptyText ? ( + + {emptyText} + + ) : null} + + ); + } +} + +export default withTranslation()(AccountsList); diff --git a/apps/ledger-live-desktop/src/renderer/components/Alert.js b/apps/ledger-live-desktop/src/renderer/components/Alert.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Alert.js rename to apps/ledger-live-desktop/src/renderer/components/Alert.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AnalyticsConsole.js b/apps/ledger-live-desktop/src/renderer/components/AnalyticsConsole.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AnalyticsConsole.js rename to apps/ledger-live-desktop/src/renderer/components/AnalyticsConsole.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AnimatedCountdown.js b/apps/ledger-live-desktop/src/renderer/components/AnimatedCountdown.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AnimatedCountdown.js rename to apps/ledger-live-desktop/src/renderer/components/AnimatedCountdown.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.js b/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.js deleted file mode 100644 index aff2881ba8c7..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.js +++ /dev/null @@ -1,161 +0,0 @@ -// @flow -import React, { useCallback, useMemo } from "react"; -import { AnnouncementProvider } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider"; -import type { Announcement } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider/types"; -import { getKey, setKey } from "~/renderer/storage"; -import { cryptoCurrenciesSelector } from "~/renderer/reducers/accounts"; -import { languageSelector, lastSeenDeviceSelector } from "~/renderer/reducers/settings"; -import { useSelector, useDispatch } from "react-redux"; -import { ServiceStatusProvider } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider"; -import { useToasts } from "@ledgerhq/live-common/lib/notifications/ToastProvider/index"; -import { openInformationCenter } from "~/renderer/actions/UI"; -import { track } from "~/renderer/analytics/segment"; -import fetchApi from "../../../tests/mocks/notificationsHelpers"; -import networkApi from "../../../tests/mocks/serviceStatusHelpers"; - -let notificationsApi; -let serviceStatusApi; - -if (process.env.MOCK || process.env.PLAYWRIGHT_RUN) { - notificationsApi = fetchApi; - serviceStatusApi = networkApi; -} - -type Props = { - children: React$Node, -}; - -async function saveAnnouncements({ - announcements, - seenIds, - lastUpdateTime, -}: { - announcements: Announcement[], - seenIds: string[], - lastUpdateTime: number, -}) { - setKey("app", "announcements", { - announcements, - seenIds, - lastUpdateTime, - }); -} - -async function loadAnnouncements(): Promise<{ - announcements: Announcement[], - seenIds: string[], - lastUpdateTime: number, -}> { - const data = await getKey("app", "announcements", { - announcements: [], - seenIds: [], - lastUpdateTime: new Date().getTime(), - }); - return data; -} - -const getOsPlatform = () => { - switch (process.platform) { - case "darwin": - return "mac"; - - case "win32": - case "win64": - return "windows"; - - case "linux": - return "linux"; - - default: - return undefined; - } -}; - -export function AnnouncementProviderWrapper({ children }: Props) { - const startDate = useMemo(() => new Date(), []); - const language = useSelector(languageSelector); - const currenciesRaw = useSelector(cryptoCurrenciesSelector); - const { currencies, tickers } = currenciesRaw.reduce( - ({ currencies, tickers }, { id, ticker }) => ({ - currencies: [...currencies, id], - tickers: [...tickers, ticker], - }), - { currencies: [], tickers: [] }, - ); - const lastSeenDevice = useSelector(lastSeenDeviceSelector); - const dispatch = useDispatch(); - const osPlatform = getOsPlatform(); - - // $FlowFixMe please help on fixing this. bad type on live-common? - const { pushToast, dismissToast } = useToasts(); - - const context = { - language, - currencies, - getDate: () => new Date(), - lastSeenDevice: lastSeenDevice || undefined, - platform: osPlatform, - appVersion: __APP_VERSION__, - }; - - const onNewAnnouncement = useCallback( - (announcement: Announcement) => { - // eslint-disable-next-line camelcase - const { uuid, content, icon, utm_campaign, published_at } = announcement; - - track("Announcement Received", { - uuid, - utm_campaign, - }); - - if (new Date(published_at) > startDate) { - pushToast({ - id: uuid, - type: "announcement", - title: content.title, - text: content.text, - icon, - callback: () => dispatch(openInformationCenter("announcement")), - }); - } - }, - [pushToast, dispatch, startDate], - ); - - const onAnnouncementRead = useCallback( - (announcement: Announcement) => { - // eslint-disable-next-line camelcase - const { uuid, utm_campaign } = announcement; - - track("Announcement Viewed", { - uuid, - utm_campaign, - }); - - dismissToast(uuid); - }, - [dismissToast], - ); - - const autoUpdateDelay = process.env.PLAYWRIGHT_RUN || process.env.MOCK ? 16 : 60000; - - return ( - - - {children} - - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.jsx b/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.jsx new file mode 100644 index 000000000000..3d9173e4d676 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AnnouncementProviderWrapper.jsx @@ -0,0 +1,161 @@ +// @flow +import React, { useCallback, useMemo } from "react"; +import { AnnouncementProvider } from "@ledgerhq/live-common/notifications/AnnouncementProvider/index"; +import type { Announcement } from "@ledgerhq/live-common/notifications/AnnouncementProvider/types"; +import { getKey, setKey } from "~/renderer/storage"; +import { cryptoCurrenciesSelector } from "~/renderer/reducers/accounts"; +import { languageSelector, lastSeenDeviceSelector } from "~/renderer/reducers/settings"; +import { useSelector, useDispatch } from "react-redux"; +import { ServiceStatusProvider } from "@ledgerhq/live-common/notifications/ServiceStatusProvider/index"; +import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; +import { openInformationCenter } from "~/renderer/actions/UI"; +import { track } from "~/renderer/analytics/segment"; +import fetchApi from "../../../tests/mocks/notificationsHelpers"; +import networkApi from "../../../tests/mocks/serviceStatusHelpers"; + +let notificationsApi; +let serviceStatusApi; + +if (process.env.MOCK || process.env.PLAYWRIGHT_RUN) { + notificationsApi = fetchApi; + serviceStatusApi = networkApi; +} + +type Props = { + children: React$Node, +}; + +async function saveAnnouncements({ + announcements, + seenIds, + lastUpdateTime, +}: { + announcements: Announcement[], + seenIds: string[], + lastUpdateTime: number, +}) { + setKey("app", "announcements", { + announcements, + seenIds, + lastUpdateTime, + }); +} + +async function loadAnnouncements(): Promise<{ + announcements: Announcement[], + seenIds: string[], + lastUpdateTime: number, +}> { + const data = await getKey("app", "announcements", { + announcements: [], + seenIds: [], + lastUpdateTime: new Date().getTime(), + }); + return data; +} + +const getOsPlatform = () => { + switch (process.platform) { + case "darwin": + return "mac"; + + case "win32": + case "win64": + return "windows"; + + case "linux": + return "linux"; + + default: + return undefined; + } +}; + +export function AnnouncementProviderWrapper({ children }: Props) { + const startDate = useMemo(() => new Date(), []); + const language = useSelector(languageSelector); + const currenciesRaw = useSelector(cryptoCurrenciesSelector); + const { currencies, tickers } = currenciesRaw.reduce( + ({ currencies, tickers }, { id, ticker }) => ({ + currencies: [...currencies, id], + tickers: [...tickers, ticker], + }), + { currencies: [], tickers: [] }, + ); + const lastSeenDevice = useSelector(lastSeenDeviceSelector); + const dispatch = useDispatch(); + const osPlatform = getOsPlatform(); + + // $FlowFixMe please help on fixing this. bad type on live-common? + const { pushToast, dismissToast } = useToasts(); + + const context = { + language, + currencies, + getDate: () => new Date(), + lastSeenDevice: lastSeenDevice || undefined, + platform: osPlatform, + appVersion: __APP_VERSION__, + }; + + const onNewAnnouncement = useCallback( + (announcement: Announcement) => { + // eslint-disable-next-line camelcase + const { uuid, content, icon, utm_campaign, published_at } = announcement; + + track("Announcement Received", { + uuid, + utm_campaign, + }); + + if (new Date(published_at) > startDate) { + pushToast({ + id: uuid, + type: "announcement", + title: content.title, + text: content.text, + icon, + callback: () => dispatch(openInformationCenter("announcement")), + }); + } + }, + [pushToast, dispatch, startDate], + ); + + const onAnnouncementRead = useCallback( + (announcement: Announcement) => { + // eslint-disable-next-line camelcase + const { uuid, utm_campaign } = announcement; + + track("Announcement Viewed", { + uuid, + utm_campaign, + }); + + dismissToast(uuid); + }, + [dismissToast], + ); + + const autoUpdateDelay = process.env.PLAYWRIGHT_RUN || process.env.MOCK ? 16 : 60000; + + return ( + + + {children} + + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/ArrowSeparator.js b/apps/ledger-live-desktop/src/renderer/components/ArrowSeparator.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ArrowSeparator.js rename to apps/ledger-live-desktop/src/renderer/components/ArrowSeparator.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Bar.js b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Bar.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Bar.js rename to apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Bar.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Header.js b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Header.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Header.js rename to apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Header.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.js b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.js deleted file mode 100644 index 1995e44a8bdb..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.js +++ /dev/null @@ -1,175 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import { useSelector } from "react-redux"; -import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies"; -import { useCurrencyColor } from "~/renderer/getCurrencyColor"; -import styled from "styled-components"; -import CounterValue, { NoCountervaluePlaceholder } from "~/renderer/components/CounterValue"; -import { useHistory } from "react-router-dom"; -import useTheme from "~/renderer/hooks/useTheme"; - -import FormattedVal from "~/renderer/components/FormattedVal"; -import Price from "~/renderer/components/Price"; -import Text from "~/renderer/components/Text"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import Tooltip from "~/renderer/components/Tooltip"; -import Bar from "./Bar"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import { localeSelector } from "~/renderer/reducers/settings"; - -export type DistributionItem = { - currency: CryptoCurrency | TokenCurrency, - distribution: number, // % of the total (normalized in 0-1) - amount: number, - countervalue: number, // countervalue of the amount that was calculated based of the rate provided -}; - -type Props = { - item: DistributionItem, - isVisible: boolean, -}; - -const Wrapper: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: row; - justify-content: space-between; - padding: 16px 20px; - > * { - display: flex; - align-items: center; - flex-direction: row; - box-sizing: border-box; - } - - cursor: pointer; - - &:hover { - background: ${p => p.theme.colors.palette.background.default}; - } -`; - -const Asset: ThemedComponent<{}> = styled.div` - flex: 1; - width: 20%; - > :first-child { - margin-right: 10px; - } - > :nth-child(2) { - margin-right: 8px; - } -`; -const PriceSection: ThemedComponent<{}> = styled.div` - width: 20%; - text-align: left; - > :first-child { - padding-right: 24px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - width: 100%; - display: block; - } -`; -const Distribution: ThemedComponent<{}> = styled.div` - width: 20%; - text-align: right; - > :first-child { - margin-right: 11px; - width: 40px; //max width for a 99.99% case - text-align: right; - } -`; -const Amount: ThemedComponent<{}> = styled.div` - width: 25%; - justify-content: flex-end; -`; -const Value: ThemedComponent<{}> = styled.div` - width: 15%; - box-sizing: border-box; - padding-left: 24px; - justify-content: flex-end; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - -const Row = ({ item: { currency, amount, distribution }, isVisible }: Props) => { - const theme = useTheme(); - const history = useHistory(); - const locale = useSelector(localeSelector); - const color = useCurrencyColor(currency, theme.colors.palette.background.paper); - const percentage = Math.floor(distribution * 10000) / 100; - const percentageWording = percentage.toLocaleString(locale, { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - }); - const icon = ; - const onClick = useCallback(() => { - setTrackingSource("asset allocation"); - history.push({ pathname: `/asset/${currency.id}` }); - }, [currency, history]); - - return ( - - - {icon} - - - {currency.name} - - - - - {distribution ? ( - - ) : ( - - )} - - - {!!distribution && ( - <> - - {`${percentageWording}%`} - - - - )} - - - - - - - - - {distribution ? ( - - ) : ( - - )} - - - - ); -}; - -export default Row; diff --git a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.jsx b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.jsx new file mode 100644 index 000000000000..dfc8813238f5 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/Row.jsx @@ -0,0 +1,175 @@ +// @flow + +import React, { useCallback } from "react"; +import { useSelector } from "react-redux"; +import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/types/currencies"; +import { useCurrencyColor } from "~/renderer/getCurrencyColor"; +import styled from "styled-components"; +import CounterValue, { NoCountervaluePlaceholder } from "~/renderer/components/CounterValue"; +import { useHistory } from "react-router-dom"; +import useTheme from "~/renderer/hooks/useTheme"; + +import FormattedVal from "~/renderer/components/FormattedVal"; +import Price from "~/renderer/components/Price"; +import Text from "~/renderer/components/Text"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; +import Tooltip from "~/renderer/components/Tooltip"; +import Bar from "./Bar"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import { localeSelector } from "~/renderer/reducers/settings"; + +export type DistributionItem = { + currency: CryptoCurrency | TokenCurrency, + distribution: number, // % of the total (normalized in 0-1) + amount: number, + countervalue: number, // countervalue of the amount that was calculated based of the rate provided +}; + +type Props = { + item: DistributionItem, + isVisible: boolean, +}; + +const Wrapper: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 16px 20px; + > * { + display: flex; + align-items: center; + flex-direction: row; + box-sizing: border-box; + } + + cursor: pointer; + + &:hover { + background: ${p => p.theme.colors.palette.background.default}; + } +`; + +const Asset: ThemedComponent<{}> = styled.div` + flex: 1; + width: 20%; + > :first-child { + margin-right: 10px; + } + > :nth-child(2) { + margin-right: 8px; + } +`; +const PriceSection: ThemedComponent<{}> = styled.div` + width: 20%; + text-align: left; + > :first-child { + padding-right: 24px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + display: block; + } +`; +const Distribution: ThemedComponent<{}> = styled.div` + width: 20%; + text-align: right; + > :first-child { + margin-right: 11px; + width: 40px; //max width for a 99.99% case + text-align: right; + } +`; +const Amount: ThemedComponent<{}> = styled.div` + width: 25%; + justify-content: flex-end; +`; +const Value: ThemedComponent<{}> = styled.div` + width: 15%; + box-sizing: border-box; + padding-left: 24px; + justify-content: flex-end; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + +const Row = ({ item: { currency, amount, distribution }, isVisible }: Props) => { + const theme = useTheme(); + const history = useHistory(); + const locale = useSelector(localeSelector); + const color = useCurrencyColor(currency, theme.colors.palette.background.paper); + const percentage = Math.floor(distribution * 10000) / 100; + const percentageWording = percentage.toLocaleString(locale, { + minimumFractionDigits: 0, + maximumFractionDigits: 2, + }); + const icon = ; + const onClick = useCallback(() => { + setTrackingSource("asset allocation"); + history.push({ pathname: `/asset/${currency.id}` }); + }, [currency, history]); + + return ( + + + {icon} + + + {currency.name} + + + + + {distribution ? ( + + ) : ( + + )} + + + {!!distribution && ( + <> + + {`${percentageWording}%`} + + + + )} + + + + + + + + + {distribution ? ( + + ) : ( + + )} + + + + ); +}; + +export default Row; diff --git a/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/index.js b/apps/ledger-live-desktop/src/renderer/components/AssetDistribution/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AssetDistribution/index.js rename to apps/ledger-live-desktop/src/renderer/components/AssetDistribution/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/AutoRepair.js b/apps/ledger-live-desktop/src/renderer/components/AutoRepair.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/AutoRepair.js rename to apps/ledger-live-desktop/src/renderer/components/AutoRepair.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BadgeLabel.js b/apps/ledger-live-desktop/src/renderer/components/BadgeLabel.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/BadgeLabel.js rename to apps/ledger-live-desktop/src/renderer/components/BadgeLabel.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.js b/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.js deleted file mode 100644 index d33adc65e0b6..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.js +++ /dev/null @@ -1,163 +0,0 @@ -// @flow -import React, { useCallback } from "react"; -import styled from "styled-components"; -import { useTranslation } from "react-i18next"; -import type { Unit, AccountLike } from "@ledgerhq/live-common/lib/types"; -import type { ValueChange } from "@ledgerhq/live-common/lib/portfolio/v2/types"; -import Box from "~/renderer/components/Box"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import PillsDaysCount from "~/renderer/components/PillsDaysCount"; -import TransactionsPendingConfirmationWarning from "~/renderer/components/TransactionsPendingConfirmationWarning"; -import { PlaceholderLine } from "./Placeholder"; - -// $FlowFixMe -import Button from "~/renderer/components/Button.ui.tsx"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import { useHistory } from "react-router-dom"; - -type BalanceSinceProps = { - valueChange: ValueChange, - totalBalance: number, - isAvailable: boolean, -}; - -type BalanceTotalProps = { - children?: any, - unit?: Unit, - isAvailable: boolean, - totalBalance: number, - showCryptoEvenIfNotAvailable?: boolean, - account?: AccountLike, - withTransactionsPendingConfirmationWarning?: boolean, -}; - -type Props = { - unit: Unit, -} & BalanceSinceProps; - -export function BalanceDiff({ totalBalance, valueChange, unit, isAvailable, ...boxProps }: Props) { - if (!isAvailable) return null; - - return ( - - - {typeof valueChange.percentage === "number" && ( - - )} - - - - ); -} - -export function BalanceTotal({ - unit, - totalBalance, - isAvailable, - showCryptoEvenIfNotAvailable, - children = null, - withTransactionsPendingConfirmationWarning, - account, - ...boxProps -}: BalanceTotalProps) { - return ( - - - - {!isAvailable && !showCryptoEvenIfNotAvailable ? ( - - ) : ( - - )} - {withTransactionsPendingConfirmationWarning ? ( - - ) : null} - - {(isAvailable || showCryptoEvenIfNotAvailable) && children} - - - ); -} - -export default function BalanceInfos({ totalBalance, valueChange, isAvailable, unit }: Props) { - const { t } = useTranslation(); - const history = useHistory(); - - const onBuy = useCallback(() => { - setTrackingSource("Page Portfolio"); - history.push({ - pathname: "/exchange", - }); - }, [history]); - - const onSwap = useCallback(() => { - setTrackingSource("Page Market"); - - history.push({ - pathname: "/swap", - }); - }, [history]); - - return ( - - - - {t("dashboard.totalBalance")} - - - - - - - - - - - ); -} - -const Sub = styled(Box).attrs(() => ({ - ff: "Inter", - fontSize: 4, -}))` - text-transform: lowercase; -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.jsx b/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.jsx new file mode 100644 index 000000000000..d6c2a81f4268 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/BalanceInfos/index.jsx @@ -0,0 +1,163 @@ +// @flow +import React, { useCallback } from "react"; +import styled from "styled-components"; +import { useTranslation } from "react-i18next"; +import type { Unit, AccountLike } from "@ledgerhq/live-common/types/index"; +import type { ValueChange } from "@ledgerhq/live-common/portfolio/v2/types"; +import Box from "~/renderer/components/Box"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import PillsDaysCount from "~/renderer/components/PillsDaysCount"; +import TransactionsPendingConfirmationWarning from "~/renderer/components/TransactionsPendingConfirmationWarning"; +import { PlaceholderLine } from "./Placeholder"; + +// $FlowFixMe +import Button from "~/renderer/components/Button.ui.tsx"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import { useHistory } from "react-router-dom"; + +type BalanceSinceProps = { + valueChange: ValueChange, + totalBalance: number, + isAvailable: boolean, +}; + +type BalanceTotalProps = { + children?: any, + unit?: Unit, + isAvailable: boolean, + totalBalance: number, + showCryptoEvenIfNotAvailable?: boolean, + account?: AccountLike, + withTransactionsPendingConfirmationWarning?: boolean, +}; + +type Props = { + unit: Unit, +} & BalanceSinceProps; + +export function BalanceDiff({ totalBalance, valueChange, unit, isAvailable, ...boxProps }: Props) { + if (!isAvailable) return null; + + return ( + + + {typeof valueChange.percentage === "number" && ( + + )} + + + + ); +} + +export function BalanceTotal({ + unit, + totalBalance, + isAvailable, + showCryptoEvenIfNotAvailable, + children = null, + withTransactionsPendingConfirmationWarning, + account, + ...boxProps +}: BalanceTotalProps) { + return ( + + + + {!isAvailable && !showCryptoEvenIfNotAvailable ? ( + + ) : ( + + )} + {withTransactionsPendingConfirmationWarning ? ( + + ) : null} + + {(isAvailable || showCryptoEvenIfNotAvailable) && children} + + + ); +} + +export default function BalanceInfos({ totalBalance, valueChange, isAvailable, unit }: Props) { + const { t } = useTranslation(); + const history = useHistory(); + + const onBuy = useCallback(() => { + setTrackingSource("Page Portfolio"); + history.push({ + pathname: "/exchange", + }); + }, [history]); + + const onSwap = useCallback(() => { + setTrackingSource("Page Market"); + + history.push({ + pathname: "/swap", + }); + }, [history]); + + return ( + + + + {t("dashboard.totalBalance")} + + + + + + + + + + + ); +} + +const Sub = styled(Box).attrs(() => ({ + ff: "Inter", + fontSize: 4, +}))` + text-transform: lowercase; +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/BigSpinner.js b/apps/ledger-live-desktop/src/renderer/components/BigSpinner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/BigSpinner.js rename to apps/ledger-live-desktop/src/renderer/components/BigSpinner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BoldToggle.js b/apps/ledger-live-desktop/src/renderer/components/BoldToggle.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/BoldToggle.js rename to apps/ledger-live-desktop/src/renderer/components/BoldToggle.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Box/Card.js b/apps/ledger-live-desktop/src/renderer/components/Box/Card.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Box/Card.js rename to apps/ledger-live-desktop/src/renderer/components/Box/Card.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Box/Tabbable.js b/apps/ledger-live-desktop/src/renderer/components/Box/Tabbable.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Box/Tabbable.js rename to apps/ledger-live-desktop/src/renderer/components/Box/Tabbable.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.js b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.js deleted file mode 100644 index b6b4a8c4e3eb..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.js +++ /dev/null @@ -1,176 +0,0 @@ -// @flow -import React, { useCallback, useMemo } from "react"; -import { useTranslation } from "react-i18next"; -import { useSelector } from "react-redux"; -import Box from "~/renderer/components/Box"; -import { useHistory, useParams } from "react-router-dom"; -import { - listSubAccounts, - getAccountCurrency, - findSubAccountById, - getAccountName, -} from "@ledgerhq/live-common/lib/account"; -import type { Account, AccountLike } from "@ledgerhq/live-common/lib/types"; -import { accountsSelector } from "~/renderer/reducers/accounts"; -import IconCheck from "~/renderer/icons/Check"; -import IconAngleDown from "~/renderer/icons/AngleDown"; -import IconAngleUp from "~/renderer/icons/AngleUp"; -import DropDownSelector from "~/renderer/components/DropDownSelector"; -import Button from "~/renderer/components/Button"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import { Separator, Item, TextLink, AngleDown, Check } from "./common"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; - -const AccountCrumb = () => { - const { t } = useTranslation(); - const accounts = useSelector(accountsSelector); - const history = useHistory(); - const { parentId, id } = useParams(); - - const account: ?Account = useMemo( - () => (parentId ? accounts.find(a => a.id === parentId) : accounts.find(a => a.id === id)), - [parentId, accounts, id], - ); - - const tokenAccount: ?AccountLike = useMemo( - () => (parentId && account && id ? findSubAccountById(account, id) : null), - [parentId, account, id], - ); - - const currency = useMemo( - () => - tokenAccount - ? getAccountCurrency(tokenAccount) - : account - ? getAccountCurrency(account) - : null, - [tokenAccount, account], - ); - - const items = useMemo(() => (parentId && account ? listSubAccounts(account) : accounts), [ - parentId, - account, - accounts, - ]); - - const renderItem = useCallback(({ item, isActive }) => { - const currency = getAccountCurrency(item.account); - - return ( - - - - {getAccountName(item.account)} - - {isActive && ( - - - - )} - - ); - }, []); - - const onAccountSelected = useCallback( - item => { - if (!item) { - return; - } - - setTrackingSource("account breadcrumb"); - if (parentId) { - history.push({ - pathname: `/account/${parentId}/${item.key}`, - }); - } else { - history.push({ - pathname: `/account/${item.key}`, - }); - } - }, - [parentId, history], - ); - - const openActiveAccount = useCallback( - (e: SyntheticEvent) => { - e.stopPropagation(); - setTrackingSource("account breadcrumb"); - if (parentId) { - if (id) { - history.push({ - pathname: `/account/${parentId}/${id}`, - }); - } - } else { - if (id) { - history.push({ - pathname: `/account/${id}`, - }); - } - } - }, - [history, parentId, id], - ); - - const processItemsForDropdown = useCallback( - (items: any[]) => - items.map(item => ({ key: item.id, label: getAccountName(item), account: item })), - [], - ); - - const processedItems = useMemo(() => processItemsForDropdown(items || []), [ - items, - processItemsForDropdown, - ]); - - if (!id) { - return ( - - - - ); - } - - return ( - <> - - a.key === id)} - > - {({ isOpen, value }) => - value ? ( - - - {currency && } - - - {isOpen ? : } - - - - ) : null - } - - - ); -}; - -export default AccountCrumb; diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.jsx b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.jsx new file mode 100644 index 000000000000..64235b741bdc --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AccountCrumb.jsx @@ -0,0 +1,176 @@ +// @flow +import React, { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import Box from "~/renderer/components/Box"; +import { useHistory, useParams } from "react-router-dom"; +import { + listSubAccounts, + getAccountCurrency, + findSubAccountById, + getAccountName, +} from "@ledgerhq/live-common/account/index"; +import type { Account, AccountLike } from "@ledgerhq/live-common/types/index"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import IconCheck from "~/renderer/icons/Check"; +import IconAngleDown from "~/renderer/icons/AngleDown"; +import IconAngleUp from "~/renderer/icons/AngleUp"; +import DropDownSelector from "~/renderer/components/DropDownSelector"; +import Button from "~/renderer/components/Button"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; +import { Separator, Item, TextLink, AngleDown, Check } from "./common"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; + +const AccountCrumb = () => { + const { t } = useTranslation(); + const accounts = useSelector(accountsSelector); + const history = useHistory(); + const { parentId, id } = useParams(); + + const account: ?Account = useMemo( + () => (parentId ? accounts.find(a => a.id === parentId) : accounts.find(a => a.id === id)), + [parentId, accounts, id], + ); + + const tokenAccount: ?AccountLike = useMemo( + () => (parentId && account && id ? findSubAccountById(account, id) : null), + [parentId, account, id], + ); + + const currency = useMemo( + () => + tokenAccount + ? getAccountCurrency(tokenAccount) + : account + ? getAccountCurrency(account) + : null, + [tokenAccount, account], + ); + + const items = useMemo(() => (parentId && account ? listSubAccounts(account) : accounts), [ + parentId, + account, + accounts, + ]); + + const renderItem = useCallback(({ item, isActive }) => { + const currency = getAccountCurrency(item.account); + + return ( + + + + {getAccountName(item.account)} + + {isActive && ( + + + + )} + + ); + }, []); + + const onAccountSelected = useCallback( + item => { + if (!item) { + return; + } + + setTrackingSource("account breadcrumb"); + if (parentId) { + history.push({ + pathname: `/account/${parentId}/${item.key}`, + }); + } else { + history.push({ + pathname: `/account/${item.key}`, + }); + } + }, + [parentId, history], + ); + + const openActiveAccount = useCallback( + (e: SyntheticEvent) => { + e.stopPropagation(); + setTrackingSource("account breadcrumb"); + if (parentId) { + if (id) { + history.push({ + pathname: `/account/${parentId}/${id}`, + }); + } + } else { + if (id) { + history.push({ + pathname: `/account/${id}`, + }); + } + } + }, + [history, parentId, id], + ); + + const processItemsForDropdown = useCallback( + (items: any[]) => + items.map(item => ({ key: item.id, label: getAccountName(item), account: item })), + [], + ); + + const processedItems = useMemo(() => processItemsForDropdown(items || []), [ + items, + processItemsForDropdown, + ]); + + if (!id) { + return ( + + + + ); + } + + return ( + <> + + a.key === id)} + > + {({ isOpen, value }) => + value ? ( + + + {currency && } + + + {isOpen ? : } + + + + ) : null + } + + + ); +}; + +export default AccountCrumb; diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AssetCrumb.js b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AssetCrumb.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AssetCrumb.js rename to apps/ledger-live-desktop/src/renderer/components/Breadcrumb/AssetCrumb.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.js b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.js deleted file mode 100644 index ab1b8fb9fe0d..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import React, { useCallback } from "react"; -import { useHistory, useParams } from "react-router-dom"; -import Button from "~/renderer/components/Button"; -import Text from "~/renderer/components/Text"; -import { Separator, TextLink } from "./common"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import { useTranslation } from "react-i18next"; -import { useSingleCoinMarketData } from "@ledgerhq/live-common/lib/market/MarketDataProvider"; - -export default function MarketCrumb() { - const { t } = useTranslation(); - const history = useHistory(); - const { currencyId } = useParams(); - const { selectedCoinData } = useSingleCoinMarketData(currencyId); - - const goBackToMarket = useCallback( - item => { - setTrackingSource("Page Market Coin - Breadcrumb"); - history.push({ pathname: `/market` }); - }, - [history], - ); - - return selectedCoinData ? ( - <> - - - - - {selectedCoinData.name} - - ) : null; -} diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.jsx b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.jsx new file mode 100644 index 000000000000..f0eb8102279d --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/MarketCrumb.jsx @@ -0,0 +1,34 @@ +// @flow +import React, { useCallback } from "react"; +import { useHistory, useParams } from "react-router-dom"; +import Button from "~/renderer/components/Button"; +import Text from "~/renderer/components/Text"; +import { Separator, TextLink } from "./common"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import { useTranslation } from "react-i18next"; +import { useSingleCoinMarketData } from "@ledgerhq/live-common/market/MarketDataProvider"; + +export default function MarketCrumb() { + const { t } = useTranslation(); + const history = useHistory(); + const { currencyId } = useParams(); + const { selectedCoinData } = useSingleCoinMarketData(currencyId); + + const goBackToMarket = useCallback( + item => { + setTrackingSource("Page Market Coin - Breadcrumb"); + history.push({ pathname: `/market` }); + }, + [history], + ); + + return selectedCoinData ? ( + <> + + + + + {selectedCoinData.name} + + ) : null; +} diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.js b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.js deleted file mode 100644 index 5bde604ed9df..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.js +++ /dev/null @@ -1,115 +0,0 @@ -// @flow -import React, { useCallback, useMemo, memo } from "react"; -import { useHistory, useParams } from "react-router-dom"; -import { useSelector } from "react-redux"; -import { nftsByCollections } from "@ledgerhq/live-common/lib/nft"; -import type { ProtoNFT } from "@ledgerhq/live-common/lib/nft"; -import { accountSelector } from "~/renderer/reducers/accounts"; -import DropDownSelector from "~/renderer/components/DropDownSelector"; -import type { DropDownItemType } from "~/renderer/components/DropDownSelector"; -import Button from "~/renderer/components/Button"; -import Text from "~/renderer/components/Text"; -import IconCheck from "~/renderer/icons/Check"; -import IconAngleDown from "~/renderer/icons/AngleDown"; -import IconAngleUp from "~/renderer/icons/AngleUp"; -import { Separator, Item, TextLink, AngleDown, Check } from "./common"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import CollectionName from "~/renderer/components/Nft/CollectionName"; - -const LabelWithMeta = ({ - item, - isActive, -}: { - isActive: boolean, - item: DropDownItemType, -}) => ( - - - - - {isActive && ( - - - - )} - -); - -const NFTCrumb = () => { - const history = useHistory(); - const { id, collectionAddress } = useParams(); - const account = useSelector(state => accountSelector(state, { accountId: id })); - const collections = useMemo(() => nftsByCollections(account.nfts), [account.nfts]); - - const items: DropDownItemType[] = useMemo( - () => - Object.entries(collections).map(([contract, nfts]: [string, any]) => ({ - key: contract, - label: contract, - content: nfts[0], - })), - [collections], - ); - - const activeItem: ?DropDownItemType = useMemo( - () => items.find(item => item.key === collectionAddress) || items[0], - [collectionAddress, items], - ); - - const onCollectionSelected = useCallback( - item => { - if (!item) return; - setTrackingSource("NFT breadcrumb"); - history.push({ pathname: `/account/${account.id}/nft-collection/${item.key}` }); - }, - [account.id, history], - ); - - const onSeeAll = useCallback( - item => { - setTrackingSource("NFT breadcrumb"); - history.push({ pathname: `/account/${account.id}/nft-collection` }); - }, - [account.id, history], - ); - - return ( - <> - - - - - {collectionAddress ? ( - <> - - - {({ isOpen }) => ( - - - - {isOpen ? : } - - - )} - - - ) : null} - - ); -}; - -export default memo<{}>(NFTCrumb); diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.jsx b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.jsx new file mode 100644 index 000000000000..dd0bf8153472 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/NFTCrumb.jsx @@ -0,0 +1,115 @@ +// @flow +import React, { useCallback, useMemo, memo } from "react"; +import { useHistory, useParams } from "react-router-dom"; +import { useSelector } from "react-redux"; +import { nftsByCollections } from "@ledgerhq/live-common/nft/index"; +import type { ProtoNFT } from "@ledgerhq/live-common/nft/index"; +import { accountSelector } from "~/renderer/reducers/accounts"; +import DropDownSelector from "~/renderer/components/DropDownSelector"; +import type { DropDownItemType } from "~/renderer/components/DropDownSelector"; +import Button from "~/renderer/components/Button"; +import Text from "~/renderer/components/Text"; +import IconCheck from "~/renderer/icons/Check"; +import IconAngleDown from "~/renderer/icons/AngleDown"; +import IconAngleUp from "~/renderer/icons/AngleUp"; +import { Separator, Item, TextLink, AngleDown, Check } from "./common"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import CollectionName from "~/renderer/components/Nft/CollectionName"; + +const LabelWithMeta = ({ + item, + isActive, +}: { + isActive: boolean, + item: DropDownItemType, +}) => ( + + + + + {isActive && ( + + + + )} + +); + +const NFTCrumb = () => { + const history = useHistory(); + const { id, collectionAddress } = useParams(); + const account = useSelector(state => accountSelector(state, { accountId: id })); + const collections = useMemo(() => nftsByCollections(account.nfts), [account.nfts]); + + const items: DropDownItemType[] = useMemo( + () => + Object.entries(collections).map(([contract, nfts]: [string, any]) => ({ + key: contract, + label: contract, + content: nfts[0], + })), + [collections], + ); + + const activeItem: ?DropDownItemType = useMemo( + () => items.find(item => item.key === collectionAddress) || items[0], + [collectionAddress, items], + ); + + const onCollectionSelected = useCallback( + item => { + if (!item) return; + setTrackingSource("NFT breadcrumb"); + history.push({ pathname: `/account/${account.id}/nft-collection/${item.key}` }); + }, + [account.id, history], + ); + + const onSeeAll = useCallback( + item => { + setTrackingSource("NFT breadcrumb"); + history.push({ pathname: `/account/${account.id}/nft-collection` }); + }, + [account.id, history], + ); + + return ( + <> + + + + + {collectionAddress ? ( + <> + + + {({ isOpen }) => ( + + + + {isOpen ? : } + + + )} + + + ) : null} + + ); +}; + +export default memo<{}>(NFTCrumb); diff --git a/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/index.js b/apps/ledger-live-desktop/src/renderer/components/Breadcrumb/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Breadcrumb/index.js rename to apps/ledger-live-desktop/src/renderer/components/Breadcrumb/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BroadcastErrorDisclaimer.js b/apps/ledger-live-desktop/src/renderer/components/BroadcastErrorDisclaimer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/BroadcastErrorDisclaimer.js rename to apps/ledger-live-desktop/src/renderer/components/BroadcastErrorDisclaimer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BulletRow.js b/apps/ledger-live-desktop/src/renderer/components/BulletRow.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/BulletRow.js rename to apps/ledger-live-desktop/src/renderer/components/BulletRow.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Button.js b/apps/ledger-live-desktop/src/renderer/components/Button.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Button.js rename to apps/ledger-live-desktop/src/renderer/components/Button.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/BuyButton.js b/apps/ledger-live-desktop/src/renderer/components/BuyButton.js deleted file mode 100644 index 9ab557ee5992..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/BuyButton.js +++ /dev/null @@ -1,42 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import { Trans } from "react-i18next"; -import Button from "~/renderer/components/Button"; -import { useHistory } from "react-router-dom"; -import { closeAllModal } from "~/renderer/actions/modals"; -import { useDispatch } from "react-redux"; -import type { Account, CryptoCurrency } from "@ledgerhq/live-common/lib/types"; - -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import { isCurrencySupported } from "~/renderer/screens/exchange/config"; - -const BuyButton = ({ currency, account }: { currency: CryptoCurrency, account: Account }) => { - const history = useHistory(); - const dispatch = useDispatch(); - - const onClick = useCallback(() => { - dispatch(closeAllModal()); - setTrackingSource("send flow"); - history.push({ - pathname: "/exchange", - state: { - tab: 0, - defaultCurrency: currency, - defaultAccount: account, - }, - }); - }, [account, currency, dispatch, history]); - - if (!isCurrencySupported("BUY", currency)) { - return null; - } - - return ( - - ); -}; - -export default BuyButton; diff --git a/apps/ledger-live-desktop/src/renderer/components/BuyButton.jsx b/apps/ledger-live-desktop/src/renderer/components/BuyButton.jsx new file mode 100644 index 000000000000..e619e0e4f15b --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/BuyButton.jsx @@ -0,0 +1,42 @@ +// @flow + +import React, { useCallback } from "react"; +import { Trans } from "react-i18next"; +import Button from "~/renderer/components/Button"; +import { useHistory } from "react-router-dom"; +import { closeAllModal } from "~/renderer/actions/modals"; +import { useDispatch } from "react-redux"; +import type { Account, CryptoCurrency } from "@ledgerhq/live-common/types/index"; + +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import { isCurrencySupported } from "~/renderer/screens/exchange/config"; + +const BuyButton = ({ currency, account }: { currency: CryptoCurrency, account: Account }) => { + const history = useHistory(); + const dispatch = useDispatch(); + + const onClick = useCallback(() => { + dispatch(closeAllModal()); + setTrackingSource("send flow"); + history.push({ + pathname: "/exchange", + state: { + tab: 0, + defaultCurrency: currency, + defaultAccount: account, + }, + }); + }, [account, currency, dispatch, history]); + + if (!isCurrencySupported("BUY", currency)) { + return null; + } + + return ( + + ); +}; + +export default BuyButton; diff --git a/apps/ledger-live-desktop/src/renderer/components/ByteSize.js b/apps/ledger-live-desktop/src/renderer/components/ByteSize.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ByteSize.js rename to apps/ledger-live-desktop/src/renderer/components/ByteSize.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/Slide.js b/apps/ledger-live-desktop/src/renderer/components/Carousel/Slide.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Carousel/Slide.js rename to apps/ledger-live-desktop/src/renderer/components/Carousel/Slide.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.js b/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.js deleted file mode 100644 index dd7f862acaa1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.js +++ /dev/null @@ -1,88 +0,0 @@ -// @flow -import React, { useCallback, useMemo, useState, useEffect } from "react"; -import Animated from "animated/lib/targets/react-dom"; -import Easing from "animated/lib/Easing"; -import { delay } from "@ledgerhq/live-common/lib/promise"; -const easing = Easing.linear(); - -type Props = { - duration: number, - onComplete: () => void, - paused?: boolean, -}; - -const TimeBasedProgressBar = ({ duration, onComplete, paused }: Props) => { - const [nonce, setNonce] = useState(1); - const [outOfFocusPaused, setOutOfFocusPaused] = useState(false); - const progress = useMemo(() => { - if (nonce || paused) { - // NB we need this check. - return new Animated.Value(0); - } - }, [nonce, paused]); - - const onWindowFocus = useCallback(() => { - setOutOfFocusPaused(false); - setNonce(nonce + 1); - }, [nonce]); - - const onWindowBlur = useCallback(() => { - setOutOfFocusPaused(true); - setNonce(nonce + 1); - }, [nonce]); - - useEffect(() => { - window.addEventListener("focus", onWindowFocus); - window.addEventListener("blur", onWindowBlur); - return () => { - window.removeEventListener("focus", onWindowFocus); - window.removeEventListener("blur", onWindowBlur); - }; - }); - - useEffect(() => { - let cancelled = false; - if (paused) return; - async function scheduleDelayedAnimation() { - if (!paused && !cancelled) { - if (cancelled) return; - Animated.timing(progress, { - toValue: 1, - duration, - easing, - }).start(); - await delay(duration); - - if (cancelled || outOfFocusPaused) return; - onComplete(); - setNonce(nonce + 1); - } - } - if (!cancelled) { - scheduleDelayedAnimation(); - } - - return () => { - cancelled = true; - }; - }, [duration, nonce, onComplete, outOfFocusPaused, paused, progress]); - - return ( -
- {paused || outOfFocusPaused ? null : ( - - )} -
- ); -}; - -export default TimeBasedProgressBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.jsx b/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.jsx new file mode 100644 index 000000000000..223fdf832931 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Carousel/TimeBasedProgressBar.jsx @@ -0,0 +1,88 @@ +// @flow +import React, { useCallback, useMemo, useState, useEffect } from "react"; +import Animated from "animated/lib/targets/react-dom"; +import Easing from "animated/lib/Easing"; +import { delay } from "@ledgerhq/live-common/promise"; +const easing = Easing.linear(); + +type Props = { + duration: number, + onComplete: () => void, + paused?: boolean, +}; + +const TimeBasedProgressBar = ({ duration, onComplete, paused }: Props) => { + const [nonce, setNonce] = useState(1); + const [outOfFocusPaused, setOutOfFocusPaused] = useState(false); + const progress = useMemo(() => { + if (nonce || paused) { + // NB we need this check. + return new Animated.Value(0); + } + }, [nonce, paused]); + + const onWindowFocus = useCallback(() => { + setOutOfFocusPaused(false); + setNonce(nonce + 1); + }, [nonce]); + + const onWindowBlur = useCallback(() => { + setOutOfFocusPaused(true); + setNonce(nonce + 1); + }, [nonce]); + + useEffect(() => { + window.addEventListener("focus", onWindowFocus); + window.addEventListener("blur", onWindowBlur); + return () => { + window.removeEventListener("focus", onWindowFocus); + window.removeEventListener("blur", onWindowBlur); + }; + }); + + useEffect(() => { + let cancelled = false; + if (paused) return; + async function scheduleDelayedAnimation() { + if (!paused && !cancelled) { + if (cancelled) return; + Animated.timing(progress, { + toValue: 1, + duration, + easing, + }).start(); + await delay(duration); + + if (cancelled || outOfFocusPaused) return; + onComplete(); + setNonce(nonce + 1); + } + } + if (!cancelled) { + scheduleDelayedAnimation(); + } + + return () => { + cancelled = true; + }; + }, [duration, nonce, onComplete, outOfFocusPaused, paused, progress]); + + return ( +
+ {paused || outOfFocusPaused ? null : ( + + )} +
+ ); +}; + +export default TimeBasedProgressBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/banners/Swap/index.js b/apps/ledger-live-desktop/src/renderer/components/Carousel/banners/Swap/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Carousel/banners/Swap/index.js rename to apps/ledger-live-desktop/src/renderer/components/Carousel/banners/Swap/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.js b/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.js deleted file mode 100644 index 67aff490c5c1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.js +++ /dev/null @@ -1,282 +0,0 @@ -// @flow -import React, { useMemo } from "react"; -import map from "lodash/map"; -import { Trans } from "react-i18next"; -import Slide from "./Slide"; -import { urls } from "~/config/urls"; - -export const getTransitions = (transition: "slide" | "flip", reverse: boolean = false) => { - const mult = reverse ? -1 : 1; - return { - flip: { - from: { - opacity: 1, - transform: `rotateX(${180 * mult}deg)`, - }, - enter: { - opacity: 1, - transform: "rotateX(0deg)", - }, - leave: { - opacity: 1, - transform: `rotateX(${-180 * mult}deg)`, - }, - config: { mass: 20, tension: 200, friction: 100 }, - }, - slide: { - from: { - position: "absolute", - opacity: 1, - transform: `translate3d(${100 * mult}%,0,0)`, - }, - enter: { - opacity: 1, - transform: "translate3d(0%,0,0)", - }, - leave: { - opacity: 1, - transform: `translate3d((${-100 * mult}%,0,0)`, - }, - initial: null, - }, - }[transition]; -}; - -const SLIDES = [ - { - url: urls.banners.ledgerAcademy, - name: "ledgerAcademy", - title: , - description: , - imgs: [ - { - // $FlowFixMe - source: require("./banners/LedgerAcademy/images/bg.png"), - transform: [0, 60, 5, 60], - size: { - width: 160, - height: 160, - }, - }, - { - // $FlowFixMe - source: require("./banners/LedgerAcademy/images/card.png"), - transform: [65, 50, 20, 50], - size: { - width: 109, - height: 109, - }, - }, - { - // $FlowFixMe - source: require("./banners/LedgerAcademy/images/coin.png"), - transform: [-15, 20, 25, 20], - size: { - width: 28, - height: 67, - }, - }, - { - // $FlowFixMe - source: require("./banners/LedgerAcademy/images/hat.png"), - transform: [10, 30, 0, 30], - size: { - width: 110, - height: 112, - }, - }, - { - // $FlowFixMe - source: require("./banners/LedgerAcademy/images/nano.png"), - transform: [75, 25, 8, 25], - size: { - width: 50, - height: 27, - }, - }, - ], - }, - { - path: "/exchange", - name: "buyCrypto", - title: , - description: , - imgs: [ - { - // $FlowFixMe - source: require("./banners/BuyCrypto/images/bg.png"), - transform: [-10, 60, -8, 60], - size: { - width: 180, - height: 180, - }, - }, - { - // $FlowFixMe - source: require("./banners/BuyCrypto/images/cart.png"), - transform: [20, 40, 7, 40], - size: { - width: 131, - height: 130, - }, - }, - { - // $FlowFixMe - source: require("./banners/BuyCrypto/images/coin.png"), - transform: [53, 30, 53, 30], - size: { - width: 151, - height: 21, - }, - }, - { - // $FlowFixMe - source: require("./banners/BuyCrypto/images/coin2.png"), - transform: [58, 25, 20, 25], - size: { - width: 151, - height: 17, - }, - }, - { - // $FlowFixMe - source: require("./banners/BuyCrypto/images/coin3.png"), - transform: [29, 20, 33, 20], - size: { - width: 151, - height: 24, - }, - }, - ], - }, - { - path: "/swap", - name: "swap", - title: , - description: , - imgs: [ - { - // $FlowFixMe - source: require("./banners/Swap/images/bg.png"), - transform: [0, 60, 5, 60], - size: { - width: 180, - height: 180, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/coin1.png"), - transform: [37, 25, 24, 25], - size: { - width: 48, - height: 55, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/coin2.png"), - transform: [115, 25, 28, 25], - size: { - width: 50, - height: 53, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/loop.png"), - transform: [20, 35, 5, 35], - size: { - width: 160, - height: 99, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/smallcoin1.png"), - transform: [115, 15, 35, 15], - size: { - width: 18, - height: 14, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/smallcoin2.png"), - transform: [88, 20, 65, 20], - size: { - width: 4, - height: 5, - }, - }, - { - // $FlowFixMe - source: require("./banners/Swap/images/smallcoin3.png"), - transform: [78, 17, 32, 17], - size: { - width: 10, - height: 13, - }, - }, - ], - }, - { - url: urls.banners.familyPack, - name: "familyPack", - title: , - description: , - imgs: [ - { - // $FlowFixMe - source: require("./banners/BackupPack/images/bg.png"), - transform: [20, 60, 5, 60], - size: { - width: 150, - height: 150, - }, - }, - { - // $FlowFixMe - source: require("./banners/BackupPack/images/nanos.png"), - transform: [-55, 13, 5, 15], - size: { - width: 162, - height: 167, - }, - }, - { - // $FlowFixMe - source: require("./banners/BackupPack/images/nanos.png"), - transform: [0, 15, 5, 15], - size: { - width: 162, - height: 167, - }, - }, - { - // $FlowFixMe - source: require("./banners/BackupPack/images/nanos.png"), - transform: [55, 17, 5, 15], - size: { - width: 162, - height: 167, - }, - }, - ], - }, -]; - -export const useDefaultSlides = () => { - return useMemo( - () => - // $FlowFixMe - map(process.env.PLAYWRIGHT_RUN ? [SLIDES[2], SLIDES[1]] : SLIDES, (slide: Props) => ({ - id: slide.name, - // eslint-disable-next-line react/display-name - Component: () => , - start: slide.start, - end: slide.end, - })), - [], - ); -}; diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.jsx b/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.jsx new file mode 100644 index 000000000000..db3bb02fcfe3 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Carousel/helpers.jsx @@ -0,0 +1,281 @@ +// @flow +import React, { useMemo } from "react"; +import map from "lodash/map"; +import { Trans } from "react-i18next"; +import Slide from "./Slide"; +import { urls } from "~/config/urls"; + +import LedgerAcademyBgImage from "./banners/LedgerAcademy/images/bg.png"; +import LedgerAcademyCardImage from "./banners/LedgerAcademy/images/card.png"; +import LedgerAcademyCoinImage from "./banners/LedgerAcademy/images/coin.png"; +import LedgerAcademyHatImage from "./banners/LedgerAcademy/images/hat.png"; +import LedgerAcademyNanoImage from "./banners/LedgerAcademy/images/nano.png"; +import BuyCryptoBgImage from "./banners/BuyCrypto/images/bg.png"; +import BuyCryptoCartImage from "./banners/BuyCrypto/images/cart.png"; +import BuyCryptoCoinImage from "./banners/BuyCrypto/images/coin.png"; +import BuyCryptoCoin2Image from "./banners/BuyCrypto/images/coin2.png"; +import BuyCryptoCoin3Image from "./banners/BuyCrypto/images/coin3.png"; +import SwapBgImage from "./banners/Swap/images/bg.png"; +import SwapCoin1Image from "./banners/Swap/images/coin1.png"; +import SwapCoin2Image from "./banners/Swap/images/coin2.png"; +import SwapLoopImage from "./banners/Swap/images/loop.png"; +import SwapSmallCoin1Image from "./banners/Swap/images/smallcoin1.png"; +import SwapSmallCoin2Image from "./banners/Swap/images/smallcoin2.png"; +import SwapSmallCoin3Image from "./banners/Swap/images/smallcoin3.png"; +import BackupPackBgImage from "./banners/BackupPack/images/bg.png"; +import BackupPackNanosImage from "./banners/BackupPack/images/nanos.png"; + +export const getTransitions = (transition: "slide" | "flip", reverse: boolean = false) => { + const mult = reverse ? -1 : 1; + return { + flip: { + from: { + opacity: 1, + transform: `rotateX(${180 * mult}deg)`, + }, + enter: { + opacity: 1, + transform: "rotateX(0deg)", + }, + leave: { + opacity: 1, + transform: `rotateX(${-180 * mult}deg)`, + }, + config: { mass: 20, tension: 200, friction: 100 }, + }, + slide: { + from: { + position: "absolute", + opacity: 1, + transform: `translate3d(${100 * mult}%,0,0)`, + }, + enter: { + opacity: 1, + transform: "translate3d(0%,0,0)", + }, + leave: { + opacity: 1, + transform: `translate3d((${-100 * mult}%,0,0)`, + }, + initial: null, + }, + }[transition]; +}; + +const SLIDES = [ + { + url: urls.banners.ledgerAcademy, + name: "ledgerAcademy", + title: , + description: , + imgs: [ + { + source: LedgerAcademyBgImage, + transform: [0, 60, 5, 60], + size: { + width: 160, + height: 160, + }, + }, + { + source: LedgerAcademyCardImage, + transform: [65, 50, 20, 50], + size: { + width: 109, + height: 109, + }, + }, + { + source: LedgerAcademyCoinImage, + transform: [-15, 20, 25, 20], + size: { + width: 28, + height: 67, + }, + }, + { + source: LedgerAcademyHatImage, + transform: [10, 30, 0, 30], + size: { + width: 110, + height: 112, + }, + }, + { + source: LedgerAcademyNanoImage, + transform: [75, 25, 8, 25], + size: { + width: 50, + height: 27, + }, + }, + ], + }, + { + path: "/exchange", + name: "buyCrypto", + title: , + description: , + imgs: [ + { + source: BuyCryptoBgImage, + transform: [-10, 60, -8, 60], + size: { + width: 180, + height: 180, + }, + }, + { + source: BuyCryptoCartImage, + transform: [20, 40, 7, 40], + size: { + width: 131, + height: 130, + }, + }, + { + source: BuyCryptoCoinImage, + transform: [53, 30, 53, 30], + size: { + width: 151, + height: 21, + }, + }, + { + source: BuyCryptoCoin2Image, + transform: [58, 25, 20, 25], + size: { + width: 151, + height: 17, + }, + }, + { + source: BuyCryptoCoin3Image, + transform: [29, 20, 33, 20], + size: { + width: 151, + height: 24, + }, + }, + ], + }, + { + path: "/swap", + name: "swap", + title: , + description: , + imgs: [ + { + source: SwapBgImage, + transform: [0, 60, 5, 60], + size: { + width: 180, + height: 180, + }, + }, + { + source: SwapCoin1Image, + transform: [37, 25, 24, 25], + size: { + width: 48, + height: 55, + }, + }, + { + source: SwapCoin2Image, + transform: [115, 25, 28, 25], + size: { + width: 50, + height: 53, + }, + }, + { + source: SwapLoopImage, + transform: [20, 35, 5, 35], + size: { + width: 160, + height: 99, + }, + }, + { + source: SwapSmallCoin1Image, + transform: [115, 15, 35, 15], + size: { + width: 18, + height: 14, + }, + }, + { + source: SwapSmallCoin2Image, + transform: [88, 20, 65, 20], + size: { + width: 4, + height: 5, + }, + }, + { + source: SwapSmallCoin3Image, + transform: [78, 17, 32, 17], + size: { + width: 10, + height: 13, + }, + }, + ], + }, + { + url: urls.banners.familyPack, + name: "familyPack", + title: , + description: , + imgs: [ + { + source: BackupPackBgImage, + transform: [20, 60, 5, 60], + size: { + width: 150, + height: 150, + }, + }, + { + source: BackupPackNanosImage, + transform: [-55, 13, 5, 15], + size: { + width: 162, + height: 167, + }, + }, + { + source: BackupPackNanosImage, + transform: [0, 15, 5, 15], + size: { + width: 162, + height: 167, + }, + }, + { + source: BackupPackNanosImage, + transform: [55, 17, 5, 15], + size: { + width: 162, + height: 167, + }, + }, + ], + }, +]; + +export const useDefaultSlides = () => { + return useMemo( + () => + // $FlowFixMe + map(process.env.PLAYWRIGHT_RUN ? [SLIDES[2], SLIDES[1]] : SLIDES, (slide: Props) => ({ + id: slide.name, + // eslint-disable-next-line react/display-name + Component: () => , + start: slide.start, + end: slide.end, + })), + [], + ); +}; diff --git a/apps/ledger-live-desktop/src/renderer/components/Carousel/index.js b/apps/ledger-live-desktop/src/renderer/components/Carousel/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Carousel/index.js rename to apps/ledger-live-desktop/src/renderer/components/Carousel/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Chart/Tooltip.js b/apps/ledger-live-desktop/src/renderer/components/Chart/Tooltip.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Chart/Tooltip.js rename to apps/ledger-live-desktop/src/renderer/components/Chart/Tooltip.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Chart/index.js b/apps/ledger-live-desktop/src/renderer/components/Chart/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Chart/index.js rename to apps/ledger-live-desktop/src/renderer/components/Chart/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ChartPreview/index.js b/apps/ledger-live-desktop/src/renderer/components/ChartPreview/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ChartPreview/index.js rename to apps/ledger-live-desktop/src/renderer/components/ChartPreview/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/CheckBox.js b/apps/ledger-live-desktop/src/renderer/components/CheckBox.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/CheckBox.js rename to apps/ledger-live-desktop/src/renderer/components/CheckBox.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ClearCacheBanner.js b/apps/ledger-live-desktop/src/renderer/components/ClearCacheBanner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ClearCacheBanner.js rename to apps/ledger-live-desktop/src/renderer/components/ClearCacheBanner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/CollapsibleCard.js b/apps/ledger-live-desktop/src/renderer/components/CollapsibleCard.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/CollapsibleCard.js rename to apps/ledger-live-desktop/src/renderer/components/CollapsibleCard.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshooting.js b/apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshooting.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshooting.js rename to apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshooting.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshootingHelpButton.js b/apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshootingHelpButton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshootingHelpButton.js rename to apps/ledger-live-desktop/src/renderer/components/ConnectTroubleshootingHelpButton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.js b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.js deleted file mode 100644 index e3f4cd5d9a2e..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.js +++ /dev/null @@ -1,168 +0,0 @@ -// @flow -import React, { useMemo } from "react"; -import { useDispatch, useSelector } from "react-redux"; -import { useHistory } from "react-router-dom"; -import type { Account, AccountLike } from "@ledgerhq/live-common/lib/types/account"; -import { getAccountCurrency, getMainAccount } from "@ledgerhq/live-common/lib/account/helpers"; -import { openModal } from "~/renderer/actions/modals"; -import IconReceive from "~/renderer/icons/Receive"; -import IconSend from "~/renderer/icons/Send"; -import IconStar from "~/renderer/icons/Star"; -import IconBuy from "~/renderer/icons/Exchange"; -import IconSwap from "~/renderer/icons/Swap"; -import IconBan from "~/renderer/icons/Ban"; -import IconAccountSettings from "~/renderer/icons/AccountSettings"; -import ContextMenuItem from "./ContextMenuItem"; -import { toggleStarAction } from "~/renderer/actions/accounts"; -import { useRefreshAccountsOrdering } from "~/renderer/actions/general"; -import { swapSelectableCurrenciesSelector } from "~/renderer/reducers/settings"; -import { isCurrencySupported } from "~/renderer/screens/exchange/config"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; - -type Props = { - account: AccountLike, - parentAccount?: ?Account, - leftClick?: boolean, - children: any, - withStar?: boolean, -}; - -export default function AccountContextMenu({ - leftClick, - children, - account, - parentAccount, - withStar, -}: Props) { - const history = useHistory(); - const dispatch = useDispatch(); - const refreshAccountsOrdering = useRefreshAccountsOrdering(); - const swapSelectableCurrencies = useSelector(swapSelectableCurrenciesSelector); - - const menuItems = useMemo(() => { - const currency = getAccountCurrency(account); - const mainAccount = getMainAccount(account, parentAccount); - - const items = [ - { - label: "accounts.contextMenu.send", - Icon: IconSend, - callback: () => dispatch(openModal("MODAL_SEND", { account, parentAccount })), - }, - { - label: "accounts.contextMenu.receive", - Icon: IconReceive, - callback: () => dispatch(openModal("MODAL_RECEIVE", { account, parentAccount })), - }, - ]; - - const availableOnBuy = isCurrencySupported("BUY", currency); - if (availableOnBuy) { - items.push({ - label: "accounts.contextMenu.buy", - Icon: IconBuy, - callback: () => { - setTrackingSource("account context menu"); - history.push({ - pathname: "/exchange", - state: { - defaultCurrency: currency, - defaultAccount: mainAccount, - }, - }); - }, - }); - } - - const availableOnSell = isCurrencySupported("SELL", currency); - if (availableOnSell) { - items.push({ - label: "accounts.contextMenu.sell", - Icon: IconBuy, - callback: () => { - setTrackingSource("account context menu"); - history.push({ - pathname: "/exchange", - state: { - tab: 1, - defaultCurrency: currency, - defaultAccount: mainAccount, - }, - }); - }, - }); - } - - const availableOnSwap = swapSelectableCurrencies.includes(currency.id); - if (availableOnSwap) { - items.push({ - label: "accounts.contextMenu.swap", - Icon: IconSwap, - callback: () => { - setTrackingSource("account context menu"); - history.push({ - pathname: "/swap", - state: { - defaultCurrency: currency, - defaultAccount: account, - defaultParentAccount: parentAccount, - }, - }); - }, - }); - } - - if (withStar) { - items.push({ - label: "accounts.contextMenu.star", - Icon: IconStar, - callback: () => { - dispatch( - toggleStarAction(account.id, account.type !== "Account" ? account.parentId : undefined), - ); - refreshAccountsOrdering(); - }, - }); - } - - if (account.type === "Account") { - items.push({ - label: "accounts.contextMenu.edit", - Icon: IconAccountSettings, - callback: () => dispatch(openModal("MODAL_SETTINGS_ACCOUNT", { account })), - }); - } - - if (account.type === "TokenAccount") { - items.push({ - label: "accounts.contextMenu.hideToken", - Icon: IconBan, - id: "token-menu-hide", - callback: () => dispatch(openModal("MODAL_BLACKLIST_TOKEN", { token: account.token })), - }); - } - - return items; - }, [ - account, - history, - parentAccount, - withStar, - dispatch, - refreshAccountsOrdering, - swapSelectableCurrencies, - ]); - - const currency = getAccountCurrency(account); - - return ( - - {children} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.jsx b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.jsx new file mode 100644 index 000000000000..05347e8f13a6 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/AccountContextMenu.jsx @@ -0,0 +1,168 @@ +// @flow +import React, { useMemo } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { useHistory } from "react-router-dom"; +import type { Account, AccountLike } from "@ledgerhq/live-common/types/account"; +import { getAccountCurrency, getMainAccount } from "@ledgerhq/live-common/account/helpers"; +import { openModal } from "~/renderer/actions/modals"; +import IconReceive from "~/renderer/icons/Receive"; +import IconSend from "~/renderer/icons/Send"; +import IconStar from "~/renderer/icons/Star"; +import IconBuy from "~/renderer/icons/Exchange"; +import IconSwap from "~/renderer/icons/Swap"; +import IconBan from "~/renderer/icons/Ban"; +import IconAccountSettings from "~/renderer/icons/AccountSettings"; +import ContextMenuItem from "./ContextMenuItem"; +import { toggleStarAction } from "~/renderer/actions/accounts"; +import { useRefreshAccountsOrdering } from "~/renderer/actions/general"; +import { swapSelectableCurrenciesSelector } from "~/renderer/reducers/settings"; +import { isCurrencySupported } from "~/renderer/screens/exchange/config"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; + +type Props = { + account: AccountLike, + parentAccount?: ?Account, + leftClick?: boolean, + children: any, + withStar?: boolean, +}; + +export default function AccountContextMenu({ + leftClick, + children, + account, + parentAccount, + withStar, +}: Props) { + const history = useHistory(); + const dispatch = useDispatch(); + const refreshAccountsOrdering = useRefreshAccountsOrdering(); + const swapSelectableCurrencies = useSelector(swapSelectableCurrenciesSelector); + + const menuItems = useMemo(() => { + const currency = getAccountCurrency(account); + const mainAccount = getMainAccount(account, parentAccount); + + const items = [ + { + label: "accounts.contextMenu.send", + Icon: IconSend, + callback: () => dispatch(openModal("MODAL_SEND", { account, parentAccount })), + }, + { + label: "accounts.contextMenu.receive", + Icon: IconReceive, + callback: () => dispatch(openModal("MODAL_RECEIVE", { account, parentAccount })), + }, + ]; + + const availableOnBuy = isCurrencySupported("BUY", currency); + if (availableOnBuy) { + items.push({ + label: "accounts.contextMenu.buy", + Icon: IconBuy, + callback: () => { + setTrackingSource("account context menu"); + history.push({ + pathname: "/exchange", + state: { + defaultCurrency: currency, + defaultAccount: mainAccount, + }, + }); + }, + }); + } + + const availableOnSell = isCurrencySupported("SELL", currency); + if (availableOnSell) { + items.push({ + label: "accounts.contextMenu.sell", + Icon: IconBuy, + callback: () => { + setTrackingSource("account context menu"); + history.push({ + pathname: "/exchange", + state: { + tab: 1, + defaultCurrency: currency, + defaultAccount: mainAccount, + }, + }); + }, + }); + } + + const availableOnSwap = swapSelectableCurrencies.includes(currency.id); + if (availableOnSwap) { + items.push({ + label: "accounts.contextMenu.swap", + Icon: IconSwap, + callback: () => { + setTrackingSource("account context menu"); + history.push({ + pathname: "/swap", + state: { + defaultCurrency: currency, + defaultAccount: account, + defaultParentAccount: parentAccount, + }, + }); + }, + }); + } + + if (withStar) { + items.push({ + label: "accounts.contextMenu.star", + Icon: IconStar, + callback: () => { + dispatch( + toggleStarAction(account.id, account.type !== "Account" ? account.parentId : undefined), + ); + refreshAccountsOrdering(); + }, + }); + } + + if (account.type === "Account") { + items.push({ + label: "accounts.contextMenu.edit", + Icon: IconAccountSettings, + callback: () => dispatch(openModal("MODAL_SETTINGS_ACCOUNT", { account })), + }); + } + + if (account.type === "TokenAccount") { + items.push({ + label: "accounts.contextMenu.hideToken", + Icon: IconBan, + id: "token-menu-hide", + callback: () => dispatch(openModal("MODAL_BLACKLIST_TOKEN", { token: account.token })), + }); + } + + return items; + }, [ + account, + history, + parentAccount, + withStar, + dispatch, + refreshAccountsOrdering, + swapSelectableCurrencies, + ]); + + const currency = getAccountCurrency(account); + + return ( + + {children} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuItem.js b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuItem.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuItem.js rename to apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuItem.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuWrapper.js b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuWrapper.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuWrapper.js rename to apps/ledger-live-desktop/src/renderer/components/ContextMenu/ContextMenuWrapper.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.js b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.js deleted file mode 100644 index 077b96329793..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow -import React from "react"; -import { useDispatch } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { useHistory } from "react-router-dom"; -import IconBan from "~/renderer/icons/Ban"; -import { openModal } from "~/renderer/actions/modals"; -import ContextMenuItem from "./ContextMenuItem"; -import { setDrawer } from "~/renderer/drawers/Provider"; - -import type { Account } from "@ledgerhq/live-common/lib/types"; - -type Props = { - account: Account, - collectionAddress: string, - collectionName?: string, - children: any, - leftClick?: boolean, - goBackToAccount?: boolean, -}; - -export default function NFTCollectionContextMenu({ - children, - account, - collectionAddress, - collectionName, - leftClick, - goBackToAccount = false, -}: Props) { - const { t } = useTranslation(); - const dispatch = useDispatch(); - const history = useHistory(); - - const menuItems = [ - { - key: "hide", - label: t("hideNftCollection.hideCTA"), - Icon: IconBan, - callback: () => - dispatch( - openModal("MODAL_HIDE_NFT_COLLECTION", { - collectionName: collectionName ?? collectionAddress, - collectionId: `${account.id}|${collectionAddress}`, - onClose: () => { - if (goBackToAccount) { - setDrawer(); - history.replace(`account/${account.id}`); - } - }, - }), - ), - }, - ]; - - return ( - - {children} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.jsx b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.jsx new file mode 100644 index 000000000000..71d0ba17e344 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTCollectionContextMenu.jsx @@ -0,0 +1,60 @@ +// @flow +import React from "react"; +import { useDispatch } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { useHistory } from "react-router-dom"; +import IconBan from "~/renderer/icons/Ban"; +import { openModal } from "~/renderer/actions/modals"; +import ContextMenuItem from "./ContextMenuItem"; +import { setDrawer } from "~/renderer/drawers/Provider"; + +import type { Account } from "@ledgerhq/live-common/types/index"; + +type Props = { + account: Account, + collectionAddress: string, + collectionName?: string, + children: any, + leftClick?: boolean, + goBackToAccount?: boolean, +}; + +export default function NFTCollectionContextMenu({ + children, + account, + collectionAddress, + collectionName, + leftClick, + goBackToAccount = false, +}: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const history = useHistory(); + + const menuItems = [ + { + key: "hide", + label: t("hideNftCollection.hideCTA"), + Icon: IconBan, + callback: () => + dispatch( + openModal("MODAL_HIDE_NFT_COLLECTION", { + collectionName: collectionName ?? collectionAddress, + collectionId: `${account.id}|${collectionAddress}`, + onClose: () => { + if (goBackToAccount) { + setDrawer(); + history.replace(`account/${account.id}`); + } + }, + }), + ), + }, + ]; + + return ( + + {children} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.js b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.js deleted file mode 100644 index 4a53c7c02420..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.js +++ /dev/null @@ -1,33 +0,0 @@ -// @flow -import React, { memo } from "react"; -import ContextMenuItem from "./ContextMenuItem"; -import type { Account, ProtoNFT, NFTMetadata } from "@ledgerhq/live-common/lib/types"; -import useNftLinks from "~/renderer/hooks/useNftLinks"; - -type Props = { - account: Account, - nft: ProtoNFT, - metadata: NFTMetadata, - leftClick?: boolean, - children: any, - onHideCollection?: () => void, -}; - -const NFTContextMenu = ({ - leftClick, - children, - account, - nft, - metadata, - onHideCollection, -}: Props) => { - const links = useNftLinks(account, nft, metadata, onHideCollection); - - return ( - - {children} - - ); -}; - -export default memo(NFTContextMenu); diff --git a/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.jsx b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.jsx new file mode 100644 index 000000000000..333fcd4edd0c --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ContextMenu/NFTContextMenu.jsx @@ -0,0 +1,33 @@ +// @flow +import React, { memo } from "react"; +import ContextMenuItem from "./ContextMenuItem"; +import type { Account, ProtoNFT, NFTMetadata } from "@ledgerhq/live-common/types/index"; +import useNftLinks from "~/renderer/hooks/useNftLinks"; + +type Props = { + account: Account, + nft: ProtoNFT, + metadata: NFTMetadata, + leftClick?: boolean, + children: any, + onHideCollection?: () => void, +}; + +const NFTContextMenu = ({ + leftClick, + children, + account, + nft, + metadata, + onHideCollection, +}: Props) => { + const links = useNftLinks(account, nft, metadata, onHideCollection); + + return ( + + {children} + + ); +}; + +export default memo(NFTContextMenu); diff --git a/apps/ledger-live-desktop/src/renderer/components/CopyWithFeedback.js b/apps/ledger-live-desktop/src/renderer/components/CopyWithFeedback.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/CopyWithFeedback.js rename to apps/ledger-live-desktop/src/renderer/components/CopyWithFeedback.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/CountdownTimer.js b/apps/ledger-live-desktop/src/renderer/components/CountdownTimer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/CountdownTimer.js rename to apps/ledger-live-desktop/src/renderer/components/CountdownTimer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/CounterValue.js b/apps/ledger-live-desktop/src/renderer/components/CounterValue.js deleted file mode 100644 index d0aa97047c3c..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CounterValue.js +++ /dev/null @@ -1,119 +0,0 @@ -// @flow -import { BigNumber } from "bignumber.js"; -import React, { useEffect, useMemo } from "react"; -import { useSelector } from "react-redux"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { - useCalculate, - useCountervaluesPolling, -} from "@ledgerhq/live-common/lib/countervalues/react"; -import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import ToolTip from "./Tooltip"; -import { Trans } from "react-i18next"; -import useTheme from "~/renderer/hooks/useTheme"; -import { addExtraSessionTrackingPair, useTrackingPairs } from "../actions/general"; - -type Props = { - // wich market to query - currency: Currency, - - // when? if not given: take latest - date?: Date, - - value: BigNumber | number, - - alwaysShowSign?: boolean, - alwaysShowValue?: boolean, // overrides discreet mode - - subMagnitude?: number, - - placeholder?: React$Node, - - prefix?: React$Node, - suffix?: React$Node, - placeholderStyle?: { [key: string]: string | number }, -}; - -export const NoCountervaluePlaceholder = ({ - placeholder, - style = {}, -}: { - placeholder?: React$Node, - style?: *, -}) => { - const colors = useTheme("colors"); - - return ( -
- } - containerStyle={{ color: colors.palette.text.shade40 }} - > - {placeholder || "-"} - -
- ); -}; - -export default function CounterValue({ - value: valueProp, - date, - currency, - alwaysShowSign = false, - alwaysShowValue = false, - placeholder, - prefix, - suffix, - placeholderStyle, - ...props -}: Props) { - const value = valueProp instanceof BigNumber ? valueProp.toNumber() : valueProp; - const counterValueCurrency = useSelector(counterValueCurrencySelector); - const trackingPairs = useTrackingPairs(); - const cvPolling = useCountervaluesPolling(); - const hasTrackingPair = useMemo( - () => trackingPairs.some(tp => tp.from === currency && tp.to === counterValueCurrency), - [counterValueCurrency, currency, trackingPairs], - ); - - useEffect(() => { - let t; - if (!hasTrackingPair) { - addExtraSessionTrackingPair({ from: currency, to: counterValueCurrency }); - t = setTimeout(cvPolling.poll, 2000); // poll after 2s to ensure debounced CV userSettings are effective after this update - } - - return () => { - if (t) clearTimeout(t); - }; - }, [counterValueCurrency, currency, cvPolling, cvPolling.poll, hasTrackingPair, trackingPairs]); - - const countervalue = useCalculate({ - from: currency, - to: counterValueCurrency, - value, - disableRounding: true, - date, - }); - - if (typeof countervalue !== "number") { - return ; - } - - return ( - <> - {prefix || null} - - {suffix || null} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/CounterValue.jsx b/apps/ledger-live-desktop/src/renderer/components/CounterValue.jsx new file mode 100644 index 000000000000..31d727ef8fd4 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CounterValue.jsx @@ -0,0 +1,116 @@ +// @flow +import { BigNumber } from "bignumber.js"; +import React, { useEffect, useMemo } from "react"; +import { useSelector } from "react-redux"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import { useCalculate, useCountervaluesPolling } from "@ledgerhq/live-common/countervalues/react"; +import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import ToolTip from "./Tooltip"; +import { Trans } from "react-i18next"; +import useTheme from "~/renderer/hooks/useTheme"; +import { addExtraSessionTrackingPair, useTrackingPairs } from "../actions/general"; + +type Props = { + // wich market to query + currency: Currency, + + // when? if not given: take latest + date?: Date, + + value: BigNumber | number, + + alwaysShowSign?: boolean, + alwaysShowValue?: boolean, // overrides discreet mode + + subMagnitude?: number, + + placeholder?: React$Node, + + prefix?: React$Node, + suffix?: React$Node, + placeholderStyle?: { [key: string]: string | number }, +}; + +export const NoCountervaluePlaceholder = ({ + placeholder, + style = {}, +}: { + placeholder?: React$Node, + style?: *, +}) => { + const colors = useTheme("colors"); + + return ( +
+ } + containerStyle={{ color: colors.palette.text.shade40 }} + > + {placeholder || "-"} + +
+ ); +}; + +export default function CounterValue({ + value: valueProp, + date, + currency, + alwaysShowSign = false, + alwaysShowValue = false, + placeholder, + prefix, + suffix, + placeholderStyle, + ...props +}: Props) { + const value = valueProp instanceof BigNumber ? valueProp.toNumber() : valueProp; + const counterValueCurrency = useSelector(counterValueCurrencySelector); + const trackingPairs = useTrackingPairs(); + const cvPolling = useCountervaluesPolling(); + const hasTrackingPair = useMemo( + () => trackingPairs.some(tp => tp.from === currency && tp.to === counterValueCurrency), + [counterValueCurrency, currency, trackingPairs], + ); + + useEffect(() => { + let t; + if (!hasTrackingPair) { + addExtraSessionTrackingPair({ from: currency, to: counterValueCurrency }); + t = setTimeout(cvPolling.poll, 2000); // poll after 2s to ensure debounced CV userSettings are effective after this update + } + + return () => { + if (t) clearTimeout(t); + }; + }, [counterValueCurrency, currency, cvPolling, cvPolling.poll, hasTrackingPair, trackingPairs]); + + const countervalue = useCalculate({ + from: currency, + to: counterValueCurrency, + value, + disableRounding: true, + date, + }); + + if (typeof countervalue !== "number") { + return ; + } + + return ( + <> + {prefix || null} + + {suffix || null} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.js b/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.js deleted file mode 100644 index 34ab0af52546..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.js +++ /dev/null @@ -1,63 +0,0 @@ -// @flow -import React, { useEffect } from "react"; -import { - Countervalues, - useCountervaluesPolling, - useCountervaluesExport, -} from "@ledgerhq/live-common/lib/countervalues/react"; -import type { CountervaluesSettings } from "@ledgerhq/live-common/lib/countervalues/types"; -import { pairId } from "@ledgerhq/live-common/lib/countervalues/helpers"; -import { setKey } from "~/renderer/storage"; -import { useUserSettings } from "../actions/general"; - -export default function CountervaluesProvider({ - children, - initialState, -}: { - children: React$Node, - initialState: *, -}) { - const userSettings = useUserSettings(); - return ( - - {children} - - ); -} - -function CountervaluesManager({ - children, - userSettings, -}: { - children: React$Node, - userSettings: CountervaluesSettings, -}) { - useCacheManager(userSettings); - usePollingManager(); - return children; -} - -function useCacheManager(userSettings: CountervaluesSettings) { - const { status, ...state } = useCountervaluesExport(); - useEffect(() => { - if (!Object.keys(status).length) return; - const ids = userSettings.trackingPairs.map(pairId); - const newState = Object.entries(state).reduce( - (prev, [key, val]) => (ids.includes(key) ? { ...prev, [key]: val } : prev), - {}, - ); - setKey("app", "countervalues", { ...newState, status }); - }, [state, userSettings, status]); -} - -function usePollingManager() { - const { start, stop } = useCountervaluesPolling(); - useEffect(() => { - window.addEventListener("blur", stop); - window.addEventListener("focus", start); - return () => { - window.removeEventListener("blur", stop); - window.removeEventListener("focus", start); - }; - }, [start, stop]); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.jsx b/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.jsx new file mode 100644 index 000000000000..93b2088049a5 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CountervaluesProvider.jsx @@ -0,0 +1,63 @@ +// @flow +import React, { useEffect } from "react"; +import { + Countervalues, + useCountervaluesPolling, + useCountervaluesExport, +} from "@ledgerhq/live-common/countervalues/react"; +import type { CountervaluesSettings } from "@ledgerhq/live-common/countervalues/types"; +import { pairId } from "@ledgerhq/live-common/countervalues/helpers"; +import { setKey } from "~/renderer/storage"; +import { useUserSettings } from "../actions/general"; + +export default function CountervaluesProvider({ + children, + initialState, +}: { + children: React$Node, + initialState: *, +}) { + const userSettings = useUserSettings(); + return ( + + {children} + + ); +} + +function CountervaluesManager({ + children, + userSettings, +}: { + children: React$Node, + userSettings: CountervaluesSettings, +}) { + useCacheManager(userSettings); + usePollingManager(); + return children; +} + +function useCacheManager(userSettings: CountervaluesSettings) { + const { status, ...state } = useCountervaluesExport(); + useEffect(() => { + if (!Object.keys(status).length) return; + const ids = userSettings.trackingPairs.map(pairId); + const newState = Object.entries(state).reduce( + (prev, [key, val]) => (ids.includes(key) ? { ...prev, [key]: val } : prev), + {}, + ); + setKey("app", "countervalues", { ...newState, status }); + }, [state, userSettings, status]); +} + +function usePollingManager() { + const { start, stop } = useCountervaluesPolling(); + useEffect(() => { + window.addEventListener("blur", stop); + window.addEventListener("focus", start); + return () => { + window.removeEventListener("blur", stop); + window.removeEventListener("focus", start); + }; + }, [start, stop]); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.js b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.js deleted file mode 100644 index ec468917795f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.js +++ /dev/null @@ -1,113 +0,0 @@ -// @flow -import React from "react"; -import styled, { withTheme } from "styled-components"; -import { getCryptoCurrencyIcon, getTokenCurrencyIcon } from "@ledgerhq/live-common/lib/react"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { useCurrencyColor } from "~/renderer/getCurrencyColor"; -import { mix } from "~/renderer/styles/helpers"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -type Props = { - currency: Currency, - circle?: boolean, - size: number, - overrideColor?: string, - inactive?: boolean, - theme: any, - fallback?: React$Node, -}; - -// NB this is to avoid seeing the parent icon through -export const TokenIconWrapper: ThemedComponent<{}> = styled.div` - border-radius: 4px; -`; -export const CircleWrapper: ThemedComponent<{}> = styled.div` - border-radius: 50%; - border: 1px solid transparent; - background: ${p => p.color}; - height: ${p => p.size}px; - width: ${p => p.size}px; - align-items: center; - justify-content: center; - display: flex; -`; -export const TokenIcon: ThemedComponent<{ - fontSize?: number, - size: number, - color?: string, - circle?: boolean, -}> = styled.div` - font-size: ${p => (p.fontSize ? p.fontSize : p.size / 2)}px; - font-family: "Inter"; - font-weight: bold; - color: ${p => p.color}; - background-color: ${p => mix(p.color, p.theme.colors.palette.background.default, 0.9)}; - border-radius: 4px; - border-radius: ${p => (p.circle ? "50%" : "4px")}; - display: flex; - overflow: hidden; - flex-direction: column; - justify-content: center; - align-items: center; - width: ${p => p.size}px; - height: ${p => p.size}px; -`; - -// trick to format size for certain type of icons -const Container = styled.div` - width: ${p => p.size}px; - height: ${p => p.size}px; - position: relative; - overflow: visible; - > svg { - position: absolute; - height: 160%; - width: 160%; - top: -30%; - left: -30%; - } -`; - -const CryptoCurrencyIcon = ({ - currency, - circle, - size, - overrideColor, - inactive, - theme, - fallback, -}: Props) => { - const currencyColor = useCurrencyColor(currency, theme.colors.palette.background.paper); - const color = overrideColor || (inactive ? theme.colors.palette.text.shade60 : currencyColor); - - if (currency.type === "FiatCurrency") { - return null; - } - if (currency.type === "TokenCurrency") { - const TokenIconCurrency = getTokenCurrencyIcon && getTokenCurrencyIcon(currency); - - return ( - - - {TokenIconCurrency ? : currency.ticker[0]} - - - ); - } - const IconCurrency = getCryptoCurrencyIcon(currency); - return IconCurrency ? ( - circle ? ( - - - - ) : ( - - - - ) - ) : ( - fallback || null - ); -}; - -export default withTheme(CryptoCurrencyIcon); diff --git a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.jsx b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.jsx new file mode 100644 index 000000000000..49be5036b42e --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIcon.jsx @@ -0,0 +1,113 @@ +// @flow +import React from "react"; +import styled, { withTheme } from "styled-components"; +import { getCryptoCurrencyIcon, getTokenCurrencyIcon } from "@ledgerhq/live-common/react"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import { useCurrencyColor } from "~/renderer/getCurrencyColor"; +import { mix } from "~/renderer/styles/helpers"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +type Props = { + currency: Currency, + circle?: boolean, + size: number, + overrideColor?: string, + inactive?: boolean, + theme: any, + fallback?: React$Node, +}; + +// NB this is to avoid seeing the parent icon through +export const TokenIconWrapper: ThemedComponent<{}> = styled.div` + border-radius: 4px; +`; +export const CircleWrapper: ThemedComponent<{}> = styled.div` + border-radius: 50%; + border: 1px solid transparent; + background: ${p => p.color}; + height: ${p => p.size}px; + width: ${p => p.size}px; + align-items: center; + justify-content: center; + display: flex; +`; +export const TokenIcon: ThemedComponent<{ + fontSize?: number, + size: number, + color?: string, + circle?: boolean, +}> = styled.div` + font-size: ${p => (p.fontSize ? p.fontSize : p.size / 2)}px; + font-family: "Inter"; + font-weight: bold; + color: ${p => p.color}; + background-color: ${p => mix(p.color, p.theme.colors.palette.background.default, 0.9)}; + border-radius: 4px; + border-radius: ${p => (p.circle ? "50%" : "4px")}; + display: flex; + overflow: hidden; + flex-direction: column; + justify-content: center; + align-items: center; + width: ${p => p.size}px; + height: ${p => p.size}px; +`; + +// trick to format size for certain type of icons +const Container = styled.div` + width: ${p => p.size}px; + height: ${p => p.size}px; + position: relative; + overflow: visible; + > svg { + position: absolute; + height: 160%; + width: 160%; + top: -30%; + left: -30%; + } +`; + +const CryptoCurrencyIcon = ({ + currency, + circle, + size, + overrideColor, + inactive, + theme, + fallback, +}: Props) => { + const currencyColor = useCurrencyColor(currency, theme.colors.palette.background.paper); + const color = overrideColor || (inactive ? theme.colors.palette.text.shade60 : currencyColor); + + if (currency.type === "FiatCurrency") { + return null; + } + if (currency.type === "TokenCurrency") { + const TokenIconCurrency = getTokenCurrencyIcon && getTokenCurrencyIcon(currency); + + return ( + + + {TokenIconCurrency ? : currency.ticker[0]} + + + ); + } + const IconCurrency = getCryptoCurrencyIcon(currency); + return IconCurrency ? ( + circle ? ( + + + + ) : ( + + + + ) + ) : ( + fallback || null + ); +}; + +export default withTheme(CryptoCurrencyIcon); diff --git a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.js b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.js deleted file mode 100644 index ba7552adf0bc..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.js +++ /dev/null @@ -1,89 +0,0 @@ -// @flow -import React, { PureComponent } from "react"; -import styled, { withTheme } from "styled-components"; -import { Trans } from "react-i18next"; -import { listTokenTypesForCryptoCurrency } from "@ledgerhq/live-common/lib/currencies"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { getCurrencyColor } from "~/renderer/getCurrencyColor"; -import Tooltip from "~/renderer/components/Tooltip"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import CryptoCurrencyIcon, { TokenIconWrapper, TokenIcon } from "./CryptoCurrencyIcon"; - -type Props = { - currency: Currency, - count: number, - withTooltip?: boolean, - bigger?: boolean, - inactive?: boolean, - theme: any, -}; - -const Wrapper: ThemedComponent<{ - doubleIcon?: boolean, - bigger?: boolean, -}> = styled.div` - ${p => - p.doubleIcon - ? ` - margin-right: -12px;` - : ` - display: flex; - align-items: center;`} - - line-height: ${p => (p.bigger ? "18px" : "18px")}; - font-size: ${p => (p.bigger ? "12px" : "12px")}; - - > :nth-child(2) { - margin-top: ${p => (p.bigger ? "-14px" : "-12px")}; - margin-left: ${p => (p.bigger ? "10px" : "8px")}; - - border: 2px solid transparent; - } -`; - -class CryptoCurrencyIconWithCount extends PureComponent { - render() { - const { currency, bigger, withTooltip, inactive, count, theme } = this.props; - const color = inactive - ? theme.colors.palette.text.shade60 - : getCurrencyColor(currency, theme.colors.palette.background.paper); - - const size = bigger ? 20 : 16; - const fontSize = size / 2 + (count < 10 ? 2 : count >= 100 ? -2 : 0); - - const content = ( - 0} bigger={bigger}> - - {count > 0 && ( - - - {`+${count}`} - - - )} - - ); - - const isToken = - currency.type === "CryptoCurrency" && listTokenTypesForCryptoCurrency(currency).length > 0; - if (withTooltip && count > 0) { - return ( - - } - > - {content} - - ); - } - - return content; - } -} - -export default withTheme(CryptoCurrencyIconWithCount); diff --git a/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.jsx b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.jsx new file mode 100644 index 000000000000..844909fe9242 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CryptoCurrencyIconWithCount.jsx @@ -0,0 +1,89 @@ +// @flow +import React, { PureComponent } from "react"; +import styled, { withTheme } from "styled-components"; +import { Trans } from "react-i18next"; +import { listTokenTypesForCryptoCurrency } from "@ledgerhq/live-common/currencies/index"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import { getCurrencyColor } from "~/renderer/getCurrencyColor"; +import Tooltip from "~/renderer/components/Tooltip"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import CryptoCurrencyIcon, { TokenIconWrapper, TokenIcon } from "./CryptoCurrencyIcon"; + +type Props = { + currency: Currency, + count: number, + withTooltip?: boolean, + bigger?: boolean, + inactive?: boolean, + theme: any, +}; + +const Wrapper: ThemedComponent<{ + doubleIcon?: boolean, + bigger?: boolean, +}> = styled.div` + ${p => + p.doubleIcon + ? ` + margin-right: -12px;` + : ` + display: flex; + align-items: center;`} + + line-height: ${p => (p.bigger ? "18px" : "18px")}; + font-size: ${p => (p.bigger ? "12px" : "12px")}; + + > :nth-child(2) { + margin-top: ${p => (p.bigger ? "-14px" : "-12px")}; + margin-left: ${p => (p.bigger ? "10px" : "8px")}; + + border: 2px solid transparent; + } +`; + +class CryptoCurrencyIconWithCount extends PureComponent { + render() { + const { currency, bigger, withTooltip, inactive, count, theme } = this.props; + const color = inactive + ? theme.colors.palette.text.shade60 + : getCurrencyColor(currency, theme.colors.palette.background.paper); + + const size = bigger ? 20 : 16; + const fontSize = size / 2 + (count < 10 ? 2 : count >= 100 ? -2 : 0); + + const content = ( + 0} bigger={bigger}> + + {count > 0 && ( + + + {`+${count}`} + + + )} + + ); + + const isToken = + currency.type === "CryptoCurrency" && listTokenTypesForCryptoCurrency(currency).length > 0; + if (withTooltip && count > 0) { + return ( + + } + > + {content} + + ); + } + + return content; + } +} + +export default withTheme(CryptoCurrencyIconWithCount); diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.js b/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.js deleted file mode 100644 index b412a955b24f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.js +++ /dev/null @@ -1,111 +0,0 @@ -// @flow - -import React, { useMemo } from "react"; -import styled from "styled-components"; -import { getCryptoCurrencyIcon } from "@ledgerhq/live-common/lib/react"; -import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types"; -import { rgba } from "~/renderer/styles/helpers"; -import IconCheckFull from "~/renderer/icons/CheckFull"; -import Box from "~/renderer/components/Box"; -import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; -import useTheme from "~/renderer/hooks/useTheme"; -import ensureContrast from "~/renderer/ensureContrast"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import Spinner from "./Spinner"; - -const CryptoIconWrapper: ThemedComponent<{ - cryptoColor: string, -}> = styled(Box).attrs(p => ({ - alignItems: "center", - justifyContent: "center", - bg: rgba(p.cryptoColor, 0.15), - color: p.cryptoColor, -}))` - border-radius: ${p => p.borderRadius || "50%"}; - width: ${p => p.size || 40}px; - height: ${p => p.size || 40}px; - position: relative; - - & > :nth-child(2) { - position: absolute; - right: -6px; - top: -6px; - } -`; - -const SpinnerWrapper: ThemedComponent<{}> = styled.div` - background: ${p => p.theme.colors.palette.background.paper}; - border-radius: 100%; - padding: 2px; - width: 24px; - height: 24px; - display: flex; - align-items: center; - justify-content: center; - border: 2px solid ${p => p.theme.colors.palette.background.paper}; -`; - -/** - * Nb Not to be confused with CryptoCurrencyIcon which also has a circle - * mode. Not worth refactoring since this one brings the spinner/check-mark - * and CryptoCurrencyIcon is used in the selects. - */ -export function CurrencyCircleIcon({ - currency, - size, - showSpinner, - showCheckmark, -}: { - currency: CryptoCurrency | TokenCurrency, - size: number, - showSpinner?: boolean, - showCheckmark?: boolean, -}) { - const bgColor = useTheme("colors.palette.background.paper"); - const cryptoColor = useMemo( - () => (currency.type === "CryptoCurrency" ? ensureContrast(currency.color, bgColor) : ""), - [currency, bgColor], - ); - if (currency.type === "TokenCurrency") { - return ; - } - const Icon = getCryptoCurrencyIcon(currency); - return ( - - {Icon && } - {showCheckmark && ( -
- -
- )} - {showSpinner && ( - - - - )} -
- ); -} - -function CurrencyBadge({ currency }: { currency: CryptoCurrency | TokenCurrency }) { - return ( - - - - - {currency.ticker} - - - {currency.name} - - - - ); -} - -export default CurrencyBadge; diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.jsx b/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.jsx new file mode 100644 index 000000000000..c40475ffc8c5 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CurrencyBadge.jsx @@ -0,0 +1,111 @@ +// @flow + +import React, { useMemo } from "react"; +import styled from "styled-components"; +import { getCryptoCurrencyIcon } from "@ledgerhq/live-common/react"; +import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/types/index"; +import { rgba } from "~/renderer/styles/helpers"; +import IconCheckFull from "~/renderer/icons/CheckFull"; +import Box from "~/renderer/components/Box"; +import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; +import useTheme from "~/renderer/hooks/useTheme"; +import ensureContrast from "~/renderer/ensureContrast"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import Spinner from "./Spinner"; + +const CryptoIconWrapper: ThemedComponent<{ + cryptoColor: string, +}> = styled(Box).attrs(p => ({ + alignItems: "center", + justifyContent: "center", + bg: rgba(p.cryptoColor, 0.15), + color: p.cryptoColor, +}))` + border-radius: ${p => p.borderRadius || "50%"}; + width: ${p => p.size || 40}px; + height: ${p => p.size || 40}px; + position: relative; + + & > :nth-child(2) { + position: absolute; + right: -6px; + top: -6px; + } +`; + +const SpinnerWrapper: ThemedComponent<{}> = styled.div` + background: ${p => p.theme.colors.palette.background.paper}; + border-radius: 100%; + padding: 2px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border: 2px solid ${p => p.theme.colors.palette.background.paper}; +`; + +/** + * Nb Not to be confused with CryptoCurrencyIcon which also has a circle + * mode. Not worth refactoring since this one brings the spinner/check-mark + * and CryptoCurrencyIcon is used in the selects. + */ +export function CurrencyCircleIcon({ + currency, + size, + showSpinner, + showCheckmark, +}: { + currency: CryptoCurrency | TokenCurrency, + size: number, + showSpinner?: boolean, + showCheckmark?: boolean, +}) { + const bgColor = useTheme("colors.palette.background.paper"); + const cryptoColor = useMemo( + () => (currency.type === "CryptoCurrency" ? ensureContrast(currency.color, bgColor) : ""), + [currency, bgColor], + ); + if (currency.type === "TokenCurrency") { + return ; + } + const Icon = getCryptoCurrencyIcon(currency); + return ( + + {Icon && } + {showCheckmark && ( +
+ +
+ )} + {showSpinner && ( + + + + )} +
+ ); +} + +function CurrencyBadge({ currency }: { currency: CryptoCurrency | TokenCurrency }) { + return ( + + + + + {currency.ticker} + + + {currency.name} + + + + ); +} + +export default CurrencyBadge; diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.js b/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.js deleted file mode 100644 index 30ae5bdab1de..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.js +++ /dev/null @@ -1,34 +0,0 @@ -// @flow -import React from "react"; -import { createCustomErrorClass } from "@ledgerhq/errors"; -import type { TokenCurrency, CryptoCurrency } from "@ledgerhq/live-common/lib/types"; -import ErrorBanner from "./ErrorBanner"; -import { useFilteredServiceStatus } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider/index"; -type Props = { - currencies: Array, - hideStatusIncidents?: boolean, -}; - -const ServiceStatusWarning = createCustomErrorClass("ServiceStatusWarning"); - -const CurrencyDownStatusAlert = ({ currencies, hideStatusIncidents }: Props) => { - const errors = []; - const { incidents } = useFilteredServiceStatus({ tickers: currencies.map(c => c.ticker) }); - - if (!hideStatusIncidents) - incidents - .filter(c => c.components && c.components.length > 0) - .forEach(inc => { - errors.push(new ServiceStatusWarning(inc.name)); - }); - - return errors.length > 0 ? ( -
- {errors.map((e, i) => ( - - ))} -
- ) : null; -}; - -export default CurrencyDownStatusAlert; diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.jsx b/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.jsx new file mode 100644 index 000000000000..ff385696886f --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/CurrencyDownStatusAlert.jsx @@ -0,0 +1,34 @@ +// @flow +import React from "react"; +import { createCustomErrorClass } from "@ledgerhq/errors"; +import type { TokenCurrency, CryptoCurrency } from "@ledgerhq/live-common/types/index"; +import ErrorBanner from "./ErrorBanner"; +import { useFilteredServiceStatus } from "@ledgerhq/live-common/notifications/ServiceStatusProvider/index"; +type Props = { + currencies: Array, + hideStatusIncidents?: boolean, +}; + +const ServiceStatusWarning = createCustomErrorClass("ServiceStatusWarning"); + +const CurrencyDownStatusAlert = ({ currencies, hideStatusIncidents }: Props) => { + const errors = []; + const { incidents } = useFilteredServiceStatus({ tickers: currencies.map(c => c.ticker) }); + + if (!hideStatusIncidents) + incidents + .filter(c => c.components && c.components.length > 0) + .forEach(inc => { + errors.push(new ServiceStatusWarning(inc.name)); + }); + + return errors.length > 0 ? ( +
+ {errors.map((e, i) => ( + + ))} +
+ ) : null; +}; + +export default CurrencyDownStatusAlert; diff --git a/apps/ledger-live-desktop/src/renderer/components/CurrencyUnitValue.js b/apps/ledger-live-desktop/src/renderer/components/CurrencyUnitValue.js index ff78943305a5..0d9ccb981c80 100644 --- a/apps/ledger-live-desktop/src/renderer/components/CurrencyUnitValue.js +++ b/apps/ledger-live-desktop/src/renderer/components/CurrencyUnitValue.js @@ -1,8 +1,8 @@ // @flow import { useSelector } from "react-redux"; import { BigNumber } from "bignumber.js"; -import type { Unit } from "@ledgerhq/live-common/lib/types"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; +import type { Unit } from "@ledgerhq/live-common/types/index"; +import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; import { localeSelector } from "~/renderer/reducers/settings"; type RestProps = { diff --git a/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorListHeader.js b/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorListHeader.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorListHeader.js rename to apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorListHeader.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.js b/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.js deleted file mode 100644 index 2e9fffb0c23f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.js +++ /dev/null @@ -1,297 +0,0 @@ -// @flow -import React, { useRef, useCallback, memo } from "react"; -import { Trans } from "react-i18next"; -import styled, { css } from "styled-components"; -import { BigNumber } from "bignumber.js"; - -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import type { Unit } from "@ledgerhq/live-common/lib/types"; - -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import ExternalLink from "~/renderer/icons/ExternalLink"; - -import InputCurrency from "~/renderer/components/InputCurrency"; -import { colors } from "~/renderer/styles/theme"; - -export const IconContainer: ThemedComponent<*> = styled.div` - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - border-radius: 4px; - background-color: ${p => - p.isSR ? p.theme.colors.palette.action.hover : p.theme.colors.palette.divider}; - color: ${p => - p.isSR ? p.theme.colors.palette.primary.main : p.theme.colors.palette.text.shade60}; -`; - -const InfoContainer = styled(Box).attrs(() => ({ - vertical: true, - ml: 2, - flex: 1, -}))``; - -const Title = styled(Box).attrs(() => ({ - horizontal: true, - alignItems: "center", -}))` - width: min-content; - max-width: 100%; - font-size: 13px; - font-weight: 600; - cursor: pointer; - color: ${p => p.theme.colors.palette.text.shade100}; - ${IconContainer} { - background-color: rgba(0, 0, 0, 0); - color: ${p => p.theme.colors.palette.primary.main}; - opacity: 0; - } - &:hover { - color: ${p => p.theme.colors.palette.primary.main}; - } - &:hover > ${IconContainer} { - opacity: 1; - } - ${Text} { - flex: 0 1 auto; - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } -`; - -const SubTitle = styled(Box).attrs(() => ({ - horizontal: true, -}))` - font-size: 11px; - font-weight: 500; - color: ${p => p.theme.colors.palette.text.shade60}; -`; - -const SideInfo = styled(Box)``; - -const InputRight = styled(Box).attrs(() => ({ - ff: "Inter|Medium", - color: "palette.text.shade60", - fontSize: 4, - justifyContent: "center", - horizontal: true, -}))` - opacity: 0; - pointer-events: none; - padding: 5px ${p => p.theme.space[2]}px; - > * { - color: white !important; - } -`; - -const InputBox = styled(Box).attrs(() => ({ - horizontal: true, - alignItems: "center", -}))` - position: relative; - flex-basis: 160px; - height: 32px; - &:focus ${InputRight}, &:focus-within ${InputRight} { - opacity: 1; - pointer-events: auto; - } - #input-error { - font-size: 10px; - padding-bottom: 4px; - } -`; - -const MaxButton = styled.button` - background-color: ${p => p.theme.colors.palette.primary.main}; - color: ${p => p.theme.colors.palette.primary.contrastText}!important; - border: none; - border-radius: 4px; - padding: 0px ${p => p.theme.space[2]}px; - margin: 0 2.5px; - font-size: 10px; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 200ms ease-out; - &:hover { - filter: contrast(2); - } -`; - -const Row: ThemedComponent<{ active: boolean, disabled: boolean }> = styled(Box).attrs(() => ({ - horizontal: true, - flex: "0 0 56px", - mb: 2, - alignItems: "center", - justifyContent: "flex-start", - p: 2, -}))` - border-radius: 4px; - border: 1px solid transparent; - position: relative; - overflow: visible; - border-color: ${p => - p.active ? p.theme.colors.palette.primary.main : p.theme.colors.palette.divider}; - ${p => - p.active - ? `&:before { - content: ""; - width: 4px; - height: 100%; - top: 0; - left: 0; - position: absolute; - background-color: ${p.theme.colors.palette.primary.main}; - }` - : ""} - ${p => - p.disabled - ? css` - ${InputBox} { - pointer-events: none; - } - ` - : ""} - ${p => - p.onClick - ? css` - &:hover { - border-color: ${p.theme.colors.palette.primary.main}; - } - ${IconContainer} { - opacity: 1; - color: inherit; - } - ` - : ""} -`; - -export type ValidatorRowProps = { - validator: { address: string }, - icon: React$Node, - title: React$Node, - subtitle: React$Node, - sideInfo?: React$Node, - value?: number, - disabled?: boolean, - maxAvailable?: number, - notEnoughVotes?: boolean, - onClick?: (*) => void, - onUpdateVote?: (string, string) => void, - onExternalLink: (address: string) => void, - style?: *, - unit: Unit, - onMax?: () => void, - shouldRenderMax?: boolean, - className?: string, -}; - -const ValidatorRow = ({ - validator, - icon, - title, - subtitle, - sideInfo, - value, - disabled, - onUpdateVote, - onExternalLink, - maxAvailable = 0, - notEnoughVotes, - onClick = () => {}, - style, - unit, - onMax, - shouldRenderMax, - className, -}: ValidatorRowProps) => { - const inputRef = useRef(); - const onTitleClick = useCallback( - e => { - e.stopPropagation(); - onExternalLink(validator.address); - }, - [validator, onExternalLink], - ); - - const onChange = useCallback( - e => { - onUpdateVote && onUpdateVote(validator.address, e.toString()); - }, - [validator, onUpdateVote], - ); - const onMaxHandler = useCallback(() => { - onUpdateVote && - onUpdateVote( - validator.address, - BigNumber(value || 0) - .plus(maxAvailable) - .toString(), - ); - }, [validator, onUpdateVote, maxAvailable, value]); - - /** focus input on row click */ - const onRowClick = useCallback(() => { - if (inputRef && inputRef.current) { - inputRef.current.focus(); - } - onClick(validator); - }, [inputRef, onClick, validator]); - - const itemExists = typeof value === "number"; - - const input = onUpdateVote && ( - - - {shouldRenderMax && ( - - - - )} - - } - /> - - ); - - return ( - - {icon} - - - <Text>{title}</Text> - <IconContainer> - <ExternalLink size={16} /> - </IconContainer> - - {subtitle} - - {sideInfo} - {input} - - ); -}; - -export default memo(ValidatorRow); diff --git a/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.jsx b/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.jsx new file mode 100644 index 000000000000..5622c6446180 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorRow.jsx @@ -0,0 +1,297 @@ +// @flow +import React, { useRef, useCallback, memo } from "react"; +import { Trans } from "react-i18next"; +import styled, { css } from "styled-components"; +import { BigNumber } from "bignumber.js"; + +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import type { Unit } from "@ledgerhq/live-common/types/index"; + +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import ExternalLink from "~/renderer/icons/ExternalLink"; + +import InputCurrency from "~/renderer/components/InputCurrency"; +import { colors } from "~/renderer/styles/theme"; + +export const IconContainer: ThemedComponent<*> = styled.div` + display: flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 4px; + background-color: ${p => + p.isSR ? p.theme.colors.palette.action.hover : p.theme.colors.palette.divider}; + color: ${p => + p.isSR ? p.theme.colors.palette.primary.main : p.theme.colors.palette.text.shade60}; +`; + +const InfoContainer = styled(Box).attrs(() => ({ + vertical: true, + ml: 2, + flex: 1, +}))``; + +const Title = styled(Box).attrs(() => ({ + horizontal: true, + alignItems: "center", +}))` + width: min-content; + max-width: 100%; + font-size: 13px; + font-weight: 600; + cursor: pointer; + color: ${p => p.theme.colors.palette.text.shade100}; + ${IconContainer} { + background-color: rgba(0, 0, 0, 0); + color: ${p => p.theme.colors.palette.primary.main}; + opacity: 0; + } + &:hover { + color: ${p => p.theme.colors.palette.primary.main}; + } + &:hover > ${IconContainer} { + opacity: 1; + } + ${Text} { + flex: 0 1 auto; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +`; + +const SubTitle = styled(Box).attrs(() => ({ + horizontal: true, +}))` + font-size: 11px; + font-weight: 500; + color: ${p => p.theme.colors.palette.text.shade60}; +`; + +const SideInfo = styled(Box)``; + +const InputRight = styled(Box).attrs(() => ({ + ff: "Inter|Medium", + color: "palette.text.shade60", + fontSize: 4, + justifyContent: "center", + horizontal: true, +}))` + opacity: 0; + pointer-events: none; + padding: 5px ${p => p.theme.space[2]}px; + > * { + color: white !important; + } +`; + +const InputBox = styled(Box).attrs(() => ({ + horizontal: true, + alignItems: "center", +}))` + position: relative; + flex-basis: 160px; + height: 32px; + &:focus ${InputRight}, &:focus-within ${InputRight} { + opacity: 1; + pointer-events: auto; + } + #input-error { + font-size: 10px; + padding-bottom: 4px; + } +`; + +const MaxButton = styled.button` + background-color: ${p => p.theme.colors.palette.primary.main}; + color: ${p => p.theme.colors.palette.primary.contrastText}!important; + border: none; + border-radius: 4px; + padding: 0px ${p => p.theme.space[2]}px; + margin: 0 2.5px; + font-size: 10px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 200ms ease-out; + &:hover { + filter: contrast(2); + } +`; + +const Row: ThemedComponent<{ active: boolean, disabled: boolean }> = styled(Box).attrs(() => ({ + horizontal: true, + flex: "0 0 56px", + mb: 2, + alignItems: "center", + justifyContent: "flex-start", + p: 2, +}))` + border-radius: 4px; + border: 1px solid transparent; + position: relative; + overflow: visible; + border-color: ${p => + p.active ? p.theme.colors.palette.primary.main : p.theme.colors.palette.divider}; + ${p => + p.active + ? `&:before { + content: ""; + width: 4px; + height: 100%; + top: 0; + left: 0; + position: absolute; + background-color: ${p.theme.colors.palette.primary.main}; + }` + : ""} + ${p => + p.disabled + ? css` + ${InputBox} { + pointer-events: none; + } + ` + : ""} + ${p => + p.onClick + ? css` + &:hover { + border-color: ${p.theme.colors.palette.primary.main}; + } + ${IconContainer} { + opacity: 1; + color: inherit; + } + ` + : ""} +`; + +export type ValidatorRowProps = { + validator: { address: string }, + icon: React$Node, + title: React$Node, + subtitle: React$Node, + sideInfo?: React$Node, + value?: number, + disabled?: boolean, + maxAvailable?: number, + notEnoughVotes?: boolean, + onClick?: (*) => void, + onUpdateVote?: (string, string) => void, + onExternalLink: (address: string) => void, + style?: *, + unit: Unit, + onMax?: () => void, + shouldRenderMax?: boolean, + className?: string, +}; + +const ValidatorRow = ({ + validator, + icon, + title, + subtitle, + sideInfo, + value, + disabled, + onUpdateVote, + onExternalLink, + maxAvailable = 0, + notEnoughVotes, + onClick = () => {}, + style, + unit, + onMax, + shouldRenderMax, + className, +}: ValidatorRowProps) => { + const inputRef = useRef(); + const onTitleClick = useCallback( + e => { + e.stopPropagation(); + onExternalLink(validator.address); + }, + [validator, onExternalLink], + ); + + const onChange = useCallback( + e => { + onUpdateVote && onUpdateVote(validator.address, e.toString()); + }, + [validator, onUpdateVote], + ); + const onMaxHandler = useCallback(() => { + onUpdateVote && + onUpdateVote( + validator.address, + BigNumber(value || 0) + .plus(maxAvailable) + .toString(), + ); + }, [validator, onUpdateVote, maxAvailable, value]); + + /** focus input on row click */ + const onRowClick = useCallback(() => { + if (inputRef && inputRef.current) { + inputRef.current.focus(); + } + onClick(validator); + }, [inputRef, onClick, validator]); + + const itemExists = typeof value === "number"; + + const input = onUpdateVote && ( + + + {shouldRenderMax && ( + + + + )} + + } + /> + + ); + + return ( + + {icon} + + + <Text>{title}</Text> + <IconContainer> + <ExternalLink size={16} /> + </IconContainer> + + {subtitle} + + {sideInfo} + {input} + + ); +}; + +export default memo(ValidatorRow); diff --git a/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorSearchInput.js b/apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorSearchInput.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorSearchInput.js rename to apps/ledger-live-desktop/src/renderer/components/Delegation/ValidatorSearchInput.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/animations.js b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/animations.js index 19abe74c2eb5..dd56e2e5000a 100644 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/animations.js +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/animations.js @@ -1,172 +1,239 @@ // @flow import type { DeviceModelId } from "@ledgerhq/devices"; +/* eslint-disable camelcase */ + +// NANO S +import NANO_S_LIGHT_plugAndPinCode from "~/renderer/animations/nanoS/1PlugAndPinCode/light.json"; +import NANO_S_DARK_plugAndPinCode from "~/renderer/animations/nanoS/1PlugAndPinCode/dark.json"; +import NANO_S_LIGHT_enterPinCode from "~/renderer/animations/nanoS/3EnterPinCode/light.json"; +import NANO_S_DARK_enterPinCode from "~/renderer/animations/nanoS/3EnterPinCode/dark.json"; +import NANO_S_LIGHT_quitApp from "~/renderer/animations/nanoS/4QuitApp/light.json"; +import NANO_S_DARK_quitApp from "~/renderer/animations/nanoS/4QuitApp/dark.json"; +import NANO_S_LIGHT_allowManager from "~/renderer/animations/nanoS/5AllowManager/light.json"; +import NANO_S_DARK_allowManager from "~/renderer/animations/nanoS/5AllowManager/dark.json"; +import NANO_S_LIGHT_openApp from "~/renderer/animations/nanoS/6OpenApp/light.json"; +import NANO_S_DARK_openApp from "~/renderer/animations/nanoS/6OpenApp/dark.json"; +import NANO_S_LIGHT_validate from "~/renderer/animations/nanoS/7Validate/light.json"; +import NANO_S_DARK_validate from "~/renderer/animations/nanoS/7Validate/dark.json"; +import NANO_S_LIGHT_firmwareUpdating from "~/renderer/animations/nanoS/2FirmwareUpdating/light.json"; +import NANO_S_DARK_firmwareUpdating from "~/renderer/animations/nanoS/2FirmwareUpdating/dark.json"; +import NANO_S_LIGHT_installLoading from "~/renderer/animations/nanoS/8InstallLoading/light.json"; +import NANO_S_DARK_installLoading from "~/renderer/animations/nanoS/8InstallLoading/dark.json"; + +// NANO X +import NANO_X_LIGHT_plugAndPinCode from "~/renderer/animations/nanoX/1PlugAndPinCode/light.json"; +import NANO_X_DARK_plugAndPinCode from "~/renderer/animations/nanoX/1PlugAndPinCode/dark.json"; +import NANO_X_LIGHT_enterPinCode from "~/renderer/animations/nanoX/3EnterPinCode/light.json"; +import NANO_X_DARK_enterPinCode from "~/renderer/animations/nanoX/3EnterPinCode/dark.json"; +import NANO_X_LIGHT_quitApp from "~/renderer/animations/nanoX/4QuitApp/light.json"; +import NANO_X_DARK_quitApp from "~/renderer/animations/nanoX/4QuitApp/dark.json"; +import NANO_X_LIGHT_allowManager from "~/renderer/animations/nanoX/5AllowManager/light.json"; +import NANO_X_DARK_allowManager from "~/renderer/animations/nanoX/5AllowManager/dark.json"; +import NANO_X_LIGHT_openApp from "~/renderer/animations/nanoX/6OpenApp/light.json"; +import NANO_X_DARK_openApp from "~/renderer/animations/nanoX/6OpenApp/dark.json"; +import NANO_X_LIGHT_validate from "~/renderer/animations/nanoX/7Validate/light.json"; +import NANO_X_DARK_validate from "~/renderer/animations/nanoX/7Validate/dark.json"; +import NANO_X_LIGHT_firmwareUpdating from "~/renderer/animations/nanoX/2FirmwareUpdating/light.json"; +import NANO_X_DARK_firmwareUpdating from "~/renderer/animations/nanoX/2FirmwareUpdating/dark.json"; +import NANO_X_LIGHT_installLoading from "~/renderer/animations/nanoX/8InstallLoading/light.json"; +import NANO_X_DARK_installLoading from "~/renderer/animations/nanoX/8InstallLoading/dark.json"; + +// NANO SP +import NANO_SP_LIGHT_plugAndPinCode from "~/renderer/animations/nanoSP/1PlugAndPinCode/light.json"; +import NANO_SP_DARK_plugAndPinCode from "~/renderer/animations/nanoSP/1PlugAndPinCode/dark.json"; +import NANO_SP_LIGHT_enterPinCode from "~/renderer/animations/nanoSP/3EnterPinCode/light.json"; +import NANO_SP_DARK_enterPinCode from "~/renderer/animations/nanoSP/3EnterPinCode/dark.json"; +import NANO_SP_LIGHT_quitApp from "~/renderer/animations/nanoSP/4QuitApp/light.json"; +import NANO_SP_DARK_quitApp from "~/renderer/animations/nanoSP/4QuitApp/dark.json"; +import NANO_SP_LIGHT_allowManager from "~/renderer/animations/nanoSP/5AllowManager/light.json"; +import NANO_SP_DARK_allowManager from "~/renderer/animations/nanoSP/5AllowManager/dark.json"; +import NANO_SP_LIGHT_openApp from "~/renderer/animations/nanoSP/6OpenApp/light.json"; +import NANO_SP_DARK_openApp from "~/renderer/animations/nanoSP/6OpenApp/dark.json"; +import NANO_SP_LIGHT_validate from "~/renderer/animations/nanoSP/7Validate/light.json"; +import NANO_SP_DARK_validate from "~/renderer/animations/nanoSP/7Validate/dark.json"; +import NANO_SP_LIGHT_firmwareUpdating from "~/renderer/animations/nanoSP/2FirmwareUpdating/light.json"; +import NANO_SP_DARK_firmwareUpdating from "~/renderer/animations/nanoSP/2FirmwareUpdating/dark.json"; +import NANO_SP_LIGHT_installLoading from "~/renderer/animations/nanoSP/8InstallLoading/light.json"; +import NANO_SP_DARK_installLoading from "~/renderer/animations/nanoSP/8InstallLoading/dark.json"; + +// NANO BLUE + +import BLUE_LIGHT_plugAndPinCode from "~/renderer/animations/blue/1PlugAndPinCode/data.json"; +import BLUE_LIGHT_enterPinCode from "~/renderer/animations/blue/3EnterPinCode/data.json"; +import BLUE_LIGHT_quitApp from "~/renderer/animations/blue/4QuitApp/data.json"; +import BLUE_LIGHT_allowManager from "~/renderer/animations/blue/5AllowManager/data.json"; +import BLUE_LIGHT_openApp from "~/renderer/animations/blue/6OpenApp/data.json"; +import BLUE_LIGHT_validate from "~/renderer/animations/blue/7Validate/data.json"; + +/* eslint-enable camelcase */ + const nanoS = { plugAndPinCode: { - light: require("~/renderer/animations/nanoS/1PlugAndPinCode/light.json"), - dark: require("~/renderer/animations/nanoS/1PlugAndPinCode/dark.json"), + light: NANO_S_LIGHT_plugAndPinCode, + dark: NANO_S_DARK_plugAndPinCode, }, enterPinCode: { - light: require("~/renderer/animations/nanoS/3EnterPinCode/light.json"), - dark: require("~/renderer/animations/nanoS/3EnterPinCode/dark.json"), + light: NANO_S_LIGHT_enterPinCode, + dark: NANO_S_DARK_enterPinCode, }, quitApp: { - light: require("~/renderer/animations/nanoS/4QuitApp/light.json"), - dark: require("~/renderer/animations/nanoS/4QuitApp/dark.json"), + light: NANO_S_LIGHT_quitApp, + dark: NANO_S_DARK_quitApp, }, allowManager: { - light: require("~/renderer/animations/nanoS/5AllowManager/light.json"), - dark: require("~/renderer/animations/nanoS/5AllowManager/dark.json"), + light: NANO_S_LIGHT_allowManager, + dark: NANO_S_DARK_allowManager, }, openApp: { - light: require("~/renderer/animations/nanoS/6OpenApp/light.json"), - dark: require("~/renderer/animations/nanoS/6OpenApp/dark.json"), + light: NANO_S_LIGHT_openApp, + dark: NANO_S_DARK_openApp, }, validate: { - light: require("~/renderer/animations/nanoS/7Validate/light.json"), - dark: require("~/renderer/animations/nanoS/7Validate/dark.json"), + light: NANO_S_LIGHT_validate, + dark: NANO_S_DARK_validate, }, firmwareUpdating: { - light: require("~/renderer/animations/nanoS/2FirmwareUpdating/light.json"), - dark: require("~/renderer/animations/nanoS/2FirmwareUpdating/dark.json"), + light: NANO_S_LIGHT_firmwareUpdating, + dark: NANO_S_DARK_firmwareUpdating, }, installLoading: { - light: require("~/renderer/animations/nanoS/8InstallLoading/light.json"), - dark: require("~/renderer/animations/nanoS/8InstallLoading/dark.json"), + light: NANO_S_LIGHT_installLoading, + dark: NANO_S_DARK_installLoading, }, }; const nanoX = { plugAndPinCode: { - light: require("~/renderer/animations/nanoX/1PlugAndPinCode/light.json"), - dark: require("~/renderer/animations/nanoX/1PlugAndPinCode/dark.json"), + light: NANO_X_LIGHT_plugAndPinCode, + dark: NANO_X_DARK_plugAndPinCode, }, enterPinCode: { - light: require("~/renderer/animations/nanoX/3EnterPinCode/light.json"), - dark: require("~/renderer/animations/nanoX/3EnterPinCode/dark.json"), + light: NANO_X_LIGHT_enterPinCode, + dark: NANO_X_DARK_enterPinCode, }, quitApp: { - light: require("~/renderer/animations/nanoX/4QuitApp/light.json"), - dark: require("~/renderer/animations/nanoX/4QuitApp/dark.json"), + light: NANO_X_LIGHT_quitApp, + dark: NANO_X_DARK_quitApp, }, allowManager: { - light: require("~/renderer/animations/nanoX/5AllowManager/light.json"), - dark: require("~/renderer/animations/nanoX/5AllowManager/dark.json"), + light: NANO_X_LIGHT_allowManager, + dark: NANO_X_DARK_allowManager, }, openApp: { - light: require("~/renderer/animations/nanoX/6OpenApp/light.json"), - dark: require("~/renderer/animations/nanoX/6OpenApp/dark.json"), + light: NANO_X_LIGHT_openApp, + dark: NANO_X_DARK_openApp, }, validate: { - light: require("~/renderer/animations/nanoX/7Validate/light.json"), - dark: require("~/renderer/animations/nanoX/7Validate/dark.json"), + light: NANO_X_LIGHT_validate, + dark: NANO_X_DARK_validate, }, firmwareUpdating: { - light: require("~/renderer/animations/nanoX/2FirmwareUpdating/light.json"), - dark: require("~/renderer/animations/nanoX/2FirmwareUpdating/dark.json"), + light: NANO_X_LIGHT_firmwareUpdating, + dark: NANO_X_DARK_firmwareUpdating, }, installLoading: { - light: require("~/renderer/animations/nanoX/8InstallLoading/light.json"), - dark: require("~/renderer/animations/nanoX/8InstallLoading/dark.json"), + light: NANO_X_LIGHT_installLoading, + dark: NANO_X_DARK_installLoading, }, }; const nanoSP = { plugAndPinCode: { - light: require("~/renderer/animations/nanoSP/1PlugAndPinCode/light.json"), - dark: require("~/renderer/animations/nanoSP/1PlugAndPinCode/dark.json"), + light: NANO_SP_LIGHT_plugAndPinCode, + dark: NANO_SP_DARK_plugAndPinCode, }, enterPinCode: { - light: require("~/renderer/animations/nanoSP/3EnterPinCode/light.json"), - dark: require("~/renderer/animations/nanoSP/3EnterPinCode/dark.json"), + light: NANO_SP_LIGHT_enterPinCode, + dark: NANO_SP_DARK_enterPinCode, }, quitApp: { - light: require("~/renderer/animations/nanoSP/4QuitApp/light.json"), - dark: require("~/renderer/animations/nanoSP/4QuitApp/dark.json"), + light: NANO_SP_LIGHT_quitApp, + dark: NANO_SP_DARK_quitApp, }, allowManager: { - light: require("~/renderer/animations/nanoSP/5AllowManager/light.json"), - dark: require("~/renderer/animations/nanoSP/5AllowManager/dark.json"), + light: NANO_SP_LIGHT_allowManager, + dark: NANO_SP_DARK_allowManager, }, openApp: { - light: require("~/renderer/animations/nanoSP/6OpenApp/light.json"), - dark: require("~/renderer/animations/nanoSP/6OpenApp/dark.json"), + light: NANO_SP_LIGHT_openApp, + dark: NANO_SP_DARK_openApp, }, validate: { - light: require("~/renderer/animations/nanoSP/7Validate/light.json"), - dark: require("~/renderer/animations/nanoSP/7Validate/dark.json"), + light: NANO_SP_LIGHT_validate, + dark: NANO_SP_DARK_validate, }, firmwareUpdating: { - light: require("~/renderer/animations/nanoSP/2FirmwareUpdating/light.json"), - dark: require("~/renderer/animations/nanoSP/2FirmwareUpdating/dark.json"), + light: NANO_SP_LIGHT_firmwareUpdating, + dark: NANO_SP_DARK_firmwareUpdating, }, installLoading: { - light: require("~/renderer/animations/nanoSP/8InstallLoading/light.json"), - dark: require("~/renderer/animations/nanoSP/8InstallLoading/dark.json"), + light: NANO_SP_LIGHT_installLoading, + dark: NANO_SP_DARK_installLoading, }, }; const nanoFTS = { plugAndPinCode: { - light: require("~/renderer/animations/nanoS/1PlugAndPinCode/light.json"), - dark: require("~/renderer/animations/nanoS/1PlugAndPinCode/dark.json"), + light: NANO_S_LIGHT_plugAndPinCode, + dark: NANO_S_DARK_plugAndPinCode, }, enterPinCode: { - light: require("~/renderer/animations/nanoS/3EnterPinCode/light.json"), - dark: require("~/renderer/animations/nanoS/3EnterPinCode/dark.json"), + light: NANO_S_LIGHT_enterPinCode, + dark: NANO_S_DARK_enterPinCode, }, quitApp: { - light: require("~/renderer/animations/nanoS/4QuitApp/light.json"), - dark: require("~/renderer/animations/nanoS/4QuitApp/dark.json"), + light: NANO_S_LIGHT_quitApp, + dark: NANO_S_DARK_quitApp, }, allowManager: { - light: require("~/renderer/animations/nanoS/5AllowManager/light.json"), - dark: require("~/renderer/animations/nanoS/5AllowManager/dark.json"), + light: NANO_S_LIGHT_allowManager, + dark: NANO_S_DARK_allowManager, }, openApp: { - light: require("~/renderer/animations/nanoS/6OpenApp/light.json"), - dark: require("~/renderer/animations/nanoS/6OpenApp/dark.json"), + light: NANO_S_LIGHT_openApp, + dark: NANO_S_DARK_openApp, }, validate: { - light: require("~/renderer/animations/nanoS/7Validate/light.json"), - dark: require("~/renderer/animations/nanoS/7Validate/dark.json"), + light: NANO_S_LIGHT_validate, + dark: NANO_S_DARK_validate, }, firmwareUpdating: { - light: require("~/renderer/animations/nanoS/2FirmwareUpdating/light.json"), - dark: require("~/renderer/animations/nanoS/2FirmwareUpdating/dark.json"), + light: NANO_S_LIGHT_firmwareUpdating, + dark: NANO_S_DARK_firmwareUpdating, }, installLoading: { - light: require("~/renderer/animations/nanoS/8InstallLoading/light.json"), - dark: require("~/renderer/animations/nanoS/8InstallLoading/dark.json"), + light: NANO_S_LIGHT_installLoading, + dark: NANO_S_DARK_installLoading, }, }; const blue = { plugAndPinCode: { - light: require("~/renderer/animations/blue/1PlugAndPinCode/data.json"), + light: BLUE_LIGHT_plugAndPinCode, }, enterPinCode: { - light: require("~/renderer/animations/blue/3EnterPinCode/data.json"), + light: BLUE_LIGHT_enterPinCode, }, quitApp: { - light: require("~/renderer/animations/blue/4QuitApp/data.json"), + light: BLUE_LIGHT_quitApp, }, allowManager: { - light: require("~/renderer/animations/blue/5AllowManager/data.json"), + light: BLUE_LIGHT_allowManager, }, openApp: { - light: require("~/renderer/animations/blue/6OpenApp/data.json"), + light: BLUE_LIGHT_openApp, }, validate: { - light: require("~/renderer/animations/blue/7Validate/data.json"), + light: BLUE_LIGHT_validate, }, // Nb We are dropping the assets for blue soon, this is temp firmwareUpdating: { - light: require("~/renderer/animations/nanoS/2FirmwareUpdating/light.json"), - dark: require("~/renderer/animations/nanoS/2FirmwareUpdating/dark.json"), + light: NANO_S_LIGHT_firmwareUpdating, + dark: NANO_S_DARK_firmwareUpdating, }, installLoading: { - light: require("~/renderer/animations/nanoS/8InstallLoading/light.json"), - dark: require("~/renderer/animations/nanoS/8InstallLoading/dark.json"), + light: NANO_S_LIGHT_installLoading, + dark: NANO_S_DARK_installLoading, }, }; diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.js b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.js deleted file mode 100644 index e45daf7fbb69..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.js +++ /dev/null @@ -1,374 +0,0 @@ -// @flow -import React, { useEffect, Component } from "react"; -import { createStructuredSelector } from "reselect"; -import { Trans } from "react-i18next"; -import { connect } from "react-redux"; -import type { Device, Action } from "@ledgerhq/live-common/lib/hw/actions/types"; -import { - OutdatedApp, - LatestFirmwareVersionRequired, - DeviceNotOnboarded, - NoSuchAppOnProvider, -} from "@ledgerhq/live-common/lib/errors"; -import { getCurrentDevice } from "~/renderer/reducers/devices"; -import { setPreferredDeviceModel, setLastSeenDeviceInfo } from "~/renderer/actions/settings"; -import { preferredDeviceModelSelector } from "~/renderer/reducers/settings"; -import type { DeviceModelId } from "@ledgerhq/devices"; -import AutoRepair from "~/renderer/components/AutoRepair"; -import TransactionConfirm from "~/renderer/components/TransactionConfirm"; -import SignMessageConfirm from "~/renderer/components/SignMessageConfirm"; -import useTheme from "~/renderer/hooks/useTheme"; -import { ManagerNotEnoughSpaceError, UpdateYourApp, TransportStatusError } from "@ledgerhq/errors"; -import { - InstallingApp, - renderAllowManager, - renderAllowOpeningApp, - renderBootloaderStep, - renderConnectYourDevice, - renderError, - renderInWrongAppForAccount, - renderLoading, - renderRequestQuitApp, - renderRequiresAppInstallation, - renderListingApps, - renderWarningOutdated, - renderSwapDeviceConfirmation, - renderSecureTransferDeviceConfirmation, -} from "./rendering"; - -type OwnProps = { - overridesPreferredDeviceModel?: DeviceModelId, - Result?: React$ComponentType

, - onResult?: P => void, - action: Action, - request: R, -}; - -type Props = OwnProps & { - reduxDevice?: Device, - preferredDeviceModel: DeviceModelId, - dispatch: (*) => void, - analyticsPropertyFlow?: string, // if there are some events to be sent, there will be a property "flow" with this value (e.g: "send"/"receive"/"add account" etc.) -}; - -class OnResult extends Component<*> { - componentDidMount() { - const { onResult, ...rest } = this.props; - onResult(rest); - } - - render() { - return null; - } -} - -/** - * Perform an action involving a device. - * @prop action: one of the actions/* - * @prop request: an object that is the input of that action - * @prop Result optional: an action produces a result, this gives a component to render it - * @prop onResult optional: an action produces a result, this gives a callback to be called with it - */ -const DeviceAction = ({ - // $FlowFixMe god of flow help me - action, - // $FlowFixMe god of flow help me - request, - Result, - onResult, - // $FlowFixMe god of flow help me - reduxDevice, - overridesPreferredDeviceModel, - preferredDeviceModel, - dispatch, - analyticsPropertyFlow, -}: Props) => { - const hookState = action.useHook(reduxDevice, request); - const { - appAndVersion, - device, - unresponsive, - error, - isLoading, - allowManagerRequestedWording, - requestQuitApp, - deviceInfo, - latestFirmware, - repairModalOpened, - requestOpenApp, - allowOpeningRequestedWording, - installingApp, - progress, - listingApps, - requiresAppInstallation, - inWrongDeviceForAccount, - onRetry, - onAutoRepair, - closeRepairModal, - onRepairModal, - deviceSignatureRequested, - deviceStreamingProgress, - displayUpgradeWarning, - passWarning, - initSwapRequested, - initSwapError, - initSwapResult, - completeExchangeStarted, - completeExchangeResult, - completeExchangeError, - allowOpeningGranted, - initSellRequested, - initSellResult, - initSellError, - signMessageRequested, - } = hookState; - - const type = useTheme("colors.palette.type"); - - const modelId = device ? device.modelId : overridesPreferredDeviceModel || preferredDeviceModel; - useEffect(() => { - if (modelId !== preferredDeviceModel) { - dispatch(setPreferredDeviceModel(modelId)); - } - }, [dispatch, modelId, preferredDeviceModel]); - - useEffect(() => { - if (deviceInfo) { - const lastSeenDevice = { - modelId: device.modelId, - deviceInfo, - }; - - dispatch(setLastSeenDeviceInfo({ lastSeenDevice, latestFirmware })); - } - }, [dispatch, device, deviceInfo, latestFirmware]); - - if (displayUpgradeWarning && appAndVersion) { - return renderWarningOutdated({ appName: appAndVersion.name, passWarning }); - } - - if (repairModalOpened && repairModalOpened.auto) { - return ; - } - - if (requestQuitApp) { - return renderRequestQuitApp({ modelId, type }); - } - - if (installingApp) { - const appName = requestOpenApp; - const props = { type, modelId, appName, progress, request, analyticsPropertyFlow }; - return ; - } - - if (requiresAppInstallation) { - const { appName, appNames: maybeAppNames } = requiresAppInstallation; - const appNames = maybeAppNames?.length ? maybeAppNames : [appName]; - - return renderRequiresAppInstallation({ appNames }); - } - - if (allowManagerRequestedWording) { - const wording = allowManagerRequestedWording; - return renderAllowManager({ modelId, type, wording }); - } - - if (listingApps) { - return renderListingApps(); - } - - if (completeExchangeStarted && !completeExchangeResult && !completeExchangeError) { - const { exchangeType } = request; - - // FIXME: could use a TS enum (when LLD will be in TS) or a JS object instead of raw numbers for switch values for clarity - switch (exchangeType) { - // swap - case 0x00: { - // FIXME: should use `renderSwapDeviceConfirmationV2` but all params not available in hookState for this SDK exchange flow - return

{"Confirm swap on your device"}
; - } - - case 0x01: // sell - case 0x02: // fund - return renderSecureTransferDeviceConfirmation({ - exchangeType: exchangeType === 0x01 ? "sell" : "fund", - modelId, - type, - }); - - default: - return
{"Confirm exchange on your device"}
; - } - } - - if (initSwapRequested && !initSwapResult && !initSwapError) { - const { transaction, exchange, exchangeRate, status } = request; - const { amountExpectedTo, estimatedFees } = hookState; - return renderSwapDeviceConfirmation({ - modelId, - type, - transaction, - exchangeRate, - exchange, - status, - amountExpectedTo, - estimatedFees, - }); - } - - if (initSellRequested && !initSellResult && !initSellError) { - return renderSecureTransferDeviceConfirmation({ exchangeType: "sell", modelId, type }); - } - - if (allowOpeningRequestedWording || requestOpenApp) { - // requestOpenApp for Nano S 1.3.1 (need to ask user to open the app.) - const wording = allowOpeningRequestedWording || requestOpenApp; - const tokenContext = request && request.tokenCurrency; - return renderAllowOpeningApp({ - modelId, - type, - wording, - tokenContext, - isDeviceBlocker: !requestOpenApp, - }); - } - - if (inWrongDeviceForAccount) { - return renderInWrongAppForAccount({ - onRetry, - accountName: inWrongDeviceForAccount.accountName, - }); - } - - if (!isLoading && error) { - if ( - error instanceof ManagerNotEnoughSpaceError || - error instanceof OutdatedApp || - error instanceof UpdateYourApp - ) { - return renderError({ - error, - managerAppName: error.managerAppName, - }); - } - - if (error instanceof LatestFirmwareVersionRequired) { - return renderError({ - error, - requireFirmwareUpdate: true, - }); - } - - // NB Until we find a better way, remap the error if it's 6d06 and we haven't fallen - // into another handled case. - if ( - error instanceof DeviceNotOnboarded || - (error instanceof TransportStatusError && error.message.includes("0x6d06")) - ) { - return renderError({ - error: new DeviceNotOnboarded(), - withOnboardingCTA: true, - info: true, - }); - } - - if (error instanceof NoSuchAppOnProvider) { - return renderError({ - error, - withOpenManager: true, - withExportLogs: true, - }); - } - - return renderError({ - error, - onRetry, - withExportLogs: true, - }); - } - - if ((!isLoading && !device) || unresponsive) { - return renderConnectYourDevice({ - modelId, - type, - unresponsive, - device, - onRepairModal, - onRetry, - }); - } - - if (isLoading || (allowOpeningGranted && !appAndVersion)) { - return renderLoading({ modelId }); - } - - if (deviceInfo && deviceInfo.isBootloader) { - return renderBootloaderStep({ onAutoRepair }); - } - - if (request && device && deviceSignatureRequested) { - const { account, parentAccount, status, transaction } = request; - if (account && status && transaction) { - return ( - - ); - } - } - - if (request && signMessageRequested) { - const { account } = request; - return ( - - ); - } - - if (typeof deviceStreamingProgress === "number") { - return renderLoading({ - modelId, - children: - deviceStreamingProgress > 0 ? ( - // with streaming event, we have accurate version of the wording - - ) : ( - // otherwise, we're not accurate (usually because we don't need to, it's fast case) - - - ), - }); - } - - const payload = action.mapResult(hookState); - - if (!payload) { - return null; - } - - return ( - <> - {Result ? : null} - {onResult ? : null} - - ); -}; - -const mapStateToProps = createStructuredSelector({ - reduxDevice: getCurrentDevice, - preferredDeviceModel: preferredDeviceModelSelector, -}); - -const component: React$ComponentType> = connect(mapStateToProps)(DeviceAction); - -export default component; diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.jsx b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.jsx new file mode 100644 index 000000000000..eeaff56acc95 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/index.jsx @@ -0,0 +1,374 @@ +// @flow +import React, { useEffect, Component } from "react"; +import { createStructuredSelector } from "reselect"; +import { Trans } from "react-i18next"; +import { connect } from "react-redux"; +import type { Device, Action } from "@ledgerhq/live-common/hw/actions/types"; +import { + OutdatedApp, + LatestFirmwareVersionRequired, + DeviceNotOnboarded, + NoSuchAppOnProvider, +} from "@ledgerhq/live-common/errors"; +import { getCurrentDevice } from "~/renderer/reducers/devices"; +import { setPreferredDeviceModel, setLastSeenDeviceInfo } from "~/renderer/actions/settings"; +import { preferredDeviceModelSelector } from "~/renderer/reducers/settings"; +import type { DeviceModelId } from "@ledgerhq/devices"; +import AutoRepair from "~/renderer/components/AutoRepair"; +import TransactionConfirm from "~/renderer/components/TransactionConfirm"; +import SignMessageConfirm from "~/renderer/components/SignMessageConfirm"; +import useTheme from "~/renderer/hooks/useTheme"; +import { ManagerNotEnoughSpaceError, UpdateYourApp, TransportStatusError } from "@ledgerhq/errors"; +import { + InstallingApp, + renderAllowManager, + renderAllowOpeningApp, + renderBootloaderStep, + renderConnectYourDevice, + renderError, + renderInWrongAppForAccount, + renderLoading, + renderRequestQuitApp, + renderRequiresAppInstallation, + renderListingApps, + renderWarningOutdated, + renderSwapDeviceConfirmation, + renderSecureTransferDeviceConfirmation, +} from "./rendering"; + +type OwnProps = { + overridesPreferredDeviceModel?: DeviceModelId, + Result?: React$ComponentType

, + onResult?: P => void, + action: Action, + request: R, +}; + +type Props = OwnProps & { + reduxDevice?: Device, + preferredDeviceModel: DeviceModelId, + dispatch: (*) => void, + analyticsPropertyFlow?: string, // if there are some events to be sent, there will be a property "flow" with this value (e.g: "send"/"receive"/"add account" etc.) +}; + +class OnResult extends Component<*> { + componentDidMount() { + const { onResult, ...rest } = this.props; + onResult(rest); + } + + render() { + return null; + } +} + +/** + * Perform an action involving a device. + * @prop action: one of the actions/* + * @prop request: an object that is the input of that action + * @prop Result optional: an action produces a result, this gives a component to render it + * @prop onResult optional: an action produces a result, this gives a callback to be called with it + */ +const DeviceAction = ({ + // $FlowFixMe god of flow help me + action, + // $FlowFixMe god of flow help me + request, + Result, + onResult, + // $FlowFixMe god of flow help me + reduxDevice, + overridesPreferredDeviceModel, + preferredDeviceModel, + dispatch, + analyticsPropertyFlow, +}: Props) => { + const hookState = action.useHook(reduxDevice, request); + const { + appAndVersion, + device, + unresponsive, + error, + isLoading, + allowManagerRequestedWording, + requestQuitApp, + deviceInfo, + latestFirmware, + repairModalOpened, + requestOpenApp, + allowOpeningRequestedWording, + installingApp, + progress, + listingApps, + requiresAppInstallation, + inWrongDeviceForAccount, + onRetry, + onAutoRepair, + closeRepairModal, + onRepairModal, + deviceSignatureRequested, + deviceStreamingProgress, + displayUpgradeWarning, + passWarning, + initSwapRequested, + initSwapError, + initSwapResult, + completeExchangeStarted, + completeExchangeResult, + completeExchangeError, + allowOpeningGranted, + initSellRequested, + initSellResult, + initSellError, + signMessageRequested, + } = hookState; + + const type = useTheme("colors.palette.type"); + + const modelId = device ? device.modelId : overridesPreferredDeviceModel || preferredDeviceModel; + useEffect(() => { + if (modelId !== preferredDeviceModel) { + dispatch(setPreferredDeviceModel(modelId)); + } + }, [dispatch, modelId, preferredDeviceModel]); + + useEffect(() => { + if (deviceInfo) { + const lastSeenDevice = { + modelId: device.modelId, + deviceInfo, + }; + + dispatch(setLastSeenDeviceInfo({ lastSeenDevice, latestFirmware })); + } + }, [dispatch, device, deviceInfo, latestFirmware]); + + if (displayUpgradeWarning && appAndVersion) { + return renderWarningOutdated({ appName: appAndVersion.name, passWarning }); + } + + if (repairModalOpened && repairModalOpened.auto) { + return ; + } + + if (requestQuitApp) { + return renderRequestQuitApp({ modelId, type }); + } + + if (installingApp) { + const appName = requestOpenApp; + const props = { type, modelId, appName, progress, request, analyticsPropertyFlow }; + return ; + } + + if (requiresAppInstallation) { + const { appName, appNames: maybeAppNames } = requiresAppInstallation; + const appNames = maybeAppNames?.length ? maybeAppNames : [appName]; + + return renderRequiresAppInstallation({ appNames }); + } + + if (allowManagerRequestedWording) { + const wording = allowManagerRequestedWording; + return renderAllowManager({ modelId, type, wording }); + } + + if (listingApps) { + return renderListingApps(); + } + + if (completeExchangeStarted && !completeExchangeResult && !completeExchangeError) { + const { exchangeType } = request; + + // FIXME: could use a TS enum (when LLD will be in TS) or a JS object instead of raw numbers for switch values for clarity + switch (exchangeType) { + // swap + case 0x00: { + // FIXME: should use `renderSwapDeviceConfirmationV2` but all params not available in hookState for this SDK exchange flow + return

{"Confirm swap on your device"}
; + } + + case 0x01: // sell + case 0x02: // fund + return renderSecureTransferDeviceConfirmation({ + exchangeType: exchangeType === 0x01 ? "sell" : "fund", + modelId, + type, + }); + + default: + return
{"Confirm exchange on your device"}
; + } + } + + if (initSwapRequested && !initSwapResult && !initSwapError) { + const { transaction, exchange, exchangeRate, status } = request; + const { amountExpectedTo, estimatedFees } = hookState; + return renderSwapDeviceConfirmation({ + modelId, + type, + transaction, + exchangeRate, + exchange, + status, + amountExpectedTo, + estimatedFees, + }); + } + + if (initSellRequested && !initSellResult && !initSellError) { + return renderSecureTransferDeviceConfirmation({ exchangeType: "sell", modelId, type }); + } + + if (allowOpeningRequestedWording || requestOpenApp) { + // requestOpenApp for Nano S 1.3.1 (need to ask user to open the app.) + const wording = allowOpeningRequestedWording || requestOpenApp; + const tokenContext = request && request.tokenCurrency; + return renderAllowOpeningApp({ + modelId, + type, + wording, + tokenContext, + isDeviceBlocker: !requestOpenApp, + }); + } + + if (inWrongDeviceForAccount) { + return renderInWrongAppForAccount({ + onRetry, + accountName: inWrongDeviceForAccount.accountName, + }); + } + + if (!isLoading && error) { + if ( + error instanceof ManagerNotEnoughSpaceError || + error instanceof OutdatedApp || + error instanceof UpdateYourApp + ) { + return renderError({ + error, + managerAppName: error.managerAppName, + }); + } + + if (error instanceof LatestFirmwareVersionRequired) { + return renderError({ + error, + requireFirmwareUpdate: true, + }); + } + + // NB Until we find a better way, remap the error if it's 6d06 and we haven't fallen + // into another handled case. + if ( + error instanceof DeviceNotOnboarded || + (error instanceof TransportStatusError && error.message.includes("0x6d06")) + ) { + return renderError({ + error: new DeviceNotOnboarded(), + withOnboardingCTA: true, + info: true, + }); + } + + if (error instanceof NoSuchAppOnProvider) { + return renderError({ + error, + withOpenManager: true, + withExportLogs: true, + }); + } + + return renderError({ + error, + onRetry, + withExportLogs: true, + }); + } + + if ((!isLoading && !device) || unresponsive) { + return renderConnectYourDevice({ + modelId, + type, + unresponsive, + device, + onRepairModal, + onRetry, + }); + } + + if (isLoading || (allowOpeningGranted && !appAndVersion)) { + return renderLoading({ modelId }); + } + + if (deviceInfo && deviceInfo.isBootloader) { + return renderBootloaderStep({ onAutoRepair }); + } + + if (request && device && deviceSignatureRequested) { + const { account, parentAccount, status, transaction } = request; + if (account && status && transaction) { + return ( + + ); + } + } + + if (request && signMessageRequested) { + const { account } = request; + return ( + + ); + } + + if (typeof deviceStreamingProgress === "number") { + return renderLoading({ + modelId, + children: + deviceStreamingProgress > 0 ? ( + // with streaming event, we have accurate version of the wording + + ) : ( + // otherwise, we're not accurate (usually because we don't need to, it's fast case) + + + ), + }); + } + + const payload = action.mapResult(hookState); + + if (!payload) { + return null; + } + + return ( + <> + {Result ? : null} + {onResult ? : null} + + ); +}; + +const mapStateToProps = createStructuredSelector({ + reduxDevice: getCurrentDevice, + preferredDeviceModel: preferredDeviceModelSelector, +}); + +const component: React$ComponentType> = connect(mapStateToProps)(DeviceAction); + +export default component; diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.js b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.js deleted file mode 100644 index 6141e1f7d467..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.js +++ /dev/null @@ -1,766 +0,0 @@ -// @flow -import React, { useCallback, useContext, useEffect } from "react"; -import { BigNumber } from "bignumber.js"; -import map from "lodash/map"; -import { Trans } from "react-i18next"; -import { connect, useDispatch } from "react-redux"; -import { useHistory } from "react-router-dom"; -import styled from "styled-components"; -import type { - TokenCurrency, - Transaction, - TransactionStatus, -} from "@ledgerhq/live-common/lib/types"; -import type { ExchangeRate, Exchange } from "@ledgerhq/live-common/lib/exchange/swap/types"; -import { getProviderName } from "@ledgerhq/live-common/lib/exchange/swap/utils"; -import { WrongDeviceForAccount, UpdateYourApp } from "@ledgerhq/errors"; -import { LatestFirmwareVersionRequired } from "@ledgerhq/live-common/lib/errors"; -import type { DeviceModelId } from "@ledgerhq/devices"; -import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types"; -import { - getAccountUnit, - getMainAccount, - getAccountName, - getAccountCurrency, -} from "@ledgerhq/live-common/lib/account"; -import { closeAllModal } from "~/renderer/actions/modals"; -import { setNotSeededDeviceRelaunch } from "~/renderer/actions/application"; -import Animation from "~/renderer/animations"; -import Button from "~/renderer/components/Button"; -import TranslatedError from "~/renderer/components/TranslatedError"; -import Text from "~/renderer/components/Text"; -import Box from "~/renderer/components/Box"; -import BigSpinner from "~/renderer/components/BigSpinner"; -import Alert from "~/renderer/components/Alert"; -import ConnectTroubleshooting from "~/renderer/components/ConnectTroubleshooting"; -import ExportLogsButton from "~/renderer/components/ExportLogsButton"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { getDeviceAnimation } from "./animations"; -import { DeviceBlocker } from "./DeviceBlocker"; -import ErrorIcon from "~/renderer/components/ErrorIcon"; -import IconTriangleWarning from "~/renderer/icons/TriangleWarning"; -import SupportLinkError from "~/renderer/components/SupportLinkError"; -import { urls } from "~/config/urls"; -import CurrencyUnitValue from "~/renderer/components/CurrencyUnitValue"; -import ExternalLinkButton from "../ExternalLinkButton"; -import TrackPage, { setTrackingSource } from "~/renderer/analytics/TrackPage"; -import { Rotating } from "~/renderer/components/Spinner"; -import ProgressCircle from "~/renderer/components/ProgressCircle"; -import CrossCircle from "~/renderer/icons/CrossCircle"; -import { getProviderIcon } from "~/renderer/screens/exchange/Swap2/utils"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import { SWAP_VERSION } from "~/renderer/screens/exchange/Swap2/utils/index"; -import { context } from "~/renderer/drawers/Provider"; -import { track } from "~/renderer/analytics/segment"; -import { relaunchOnboarding } from "~/renderer/actions/onboarding"; -import { DrawerFooter } from "~/renderer/screens/exchange/Swap2/Form/DrawerFooter"; - -export const AnimationWrapper: ThemedComponent<{ modelId?: DeviceModelId }> = styled.div` - width: 600px; - max-width: 100%; - padding-bottom: 20px; - align-self: center; - display: flex; - align-items: center; - justify-content: center; -`; - -const ProgressWrapper: ThemedComponent<{}> = styled.div` - padding: 24px; - align-self: center; - display: flex; - align-items: center; - justify-content: center; -`; - -export const Wrapper: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; - flex: 1; - align-items: center; - justify-content: center; - min-height: 260px; - max-width: 100%; -`; - -export const ConfirmWrapper: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; - flex: 1; - justify-content: center; - min-height: 260px; - max-width: 100%; -`; - -const Logo: ThemedComponent<{ warning?: boolean }> = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - color: ${p => - p.info - ? p.theme.colors.palette.primary.main - : p.warning - ? p.theme.colors.warning - : p.theme.colors.alertRed}; - margin-bottom: 20px; -`; - -export const Header: ThemedComponent<{}> = styled.div` - display: flex; - flex: 1 0 0%; - flex-direction: column; - justify-content: flex-end; - align-content: center; - align-items: center; -`; - -export const Footer: ThemedComponent<{}> = styled.div` - display: flex; - flex: 1 0 0%; - flex-direction: column; - justify-content: flex-start; - align-content: center; - align-items: center; -`; - -export const Title: ThemedComponent<{}> = styled(Text).attrs({ - ff: "Inter|SemiBold", - color: "palette.text.shade100", - textAlign: "center", - fontSize: 5, -})` - white-space: pre-line; -`; - -export const SubTitle: ThemedComponent<{}> = styled(Text).attrs({ - ff: "Inter|Regular", - color: "palette.text.shade100", - textAlign: "center", - fontSize: 3, -})` - margin-top: 8px; -`; - -const ErrorTitle = styled(Text).attrs({ - ff: "Inter|SemiBold", - color: "palette.text.shade100", - textAlign: "center", - fontSize: 6, -})` - user-select: text; - margin-bottom: 10px; -`; - -const ErrorDescription = styled(Text).attrs({ - ff: "Inter|Regular", - color: "palette.text.shade60", - textAlign: "center", - fontSize: 4, -})` - user-select: text; -`; - -const ButtonContainer = styled(Box).attrs(p => ({ - mt: 25, - horizontal: true, -}))``; - -const TroubleshootingWrapper = styled.div` - margin-top: auto; - margin-bottom: 16px; -`; - -// these are not components because we want reconciliation to not remount the sub elements - -export const renderRequestQuitApp = ({ - modelId, - type, -}: { - modelId: DeviceModelId, - type: "light" | "dark", -}) => ( - -
- - - -
- - <Trans i18nKey="DeviceAction.quitApp" /> - -
- -); - -export const renderVerifyUnwrapped = ({ - modelId, - type, -}: { - modelId: DeviceModelId, - type: "light" | "dark", -}) => ( - - - - -); - -const OpenManagerBtn = ({ - closeAllModal, - appName, - updateApp, - firmwareUpdate, - mt = 2, - ml = 0, -}: { - closeAllModal: () => void, - appName?: string, - updateApp?: boolean, - firmwareUpdate?: boolean, - mt?: number, - ml?: number, -}) => { - const history = useHistory(); - const { setDrawer } = useContext(context); - - const onClick = useCallback(() => { - const urlParams = new URLSearchParams({ - updateApp: updateApp ? "true" : "false", - firmwareUpdate: firmwareUpdate ? "true" : "false", - ...(appName ? { q: appName } : {}), - }); - const search = urlParams.toString(); - setTrackingSource("device action open manager button"); - history.push({ - pathname: "/manager", - search: search ? `?${search}` : "", - }); - closeAllModal(); - setDrawer(undefined); - }, [updateApp, firmwareUpdate, appName, history, closeAllModal, setDrawer]); - - return ( - - ); -}; - -const OpenOnboardingBtn = () => { - const { setDrawer } = useContext(context); - const dispatch = useDispatch(); - - const onClick = useCallback(() => { - setTrackingSource("device action open onboarding button"); - dispatch(setNotSeededDeviceRelaunch(true)); - dispatch(relaunchOnboarding(true)); - dispatch(closeAllModal()); - setDrawer(undefined); - }, [dispatch, setDrawer]); - - return ( - - ); -}; - -const OpenManagerButton = connect(null, { closeAllModal })(OpenManagerBtn); - -export const renderRequiresAppInstallation = ({ appNames }: { appNames: string[] }) => { - const appNamesCSV = appNames.join(", "); - return ( - - - - - - - - - - - - - - - ); -}; - -export const InstallingApp = ({ - modelId, - type, - appName, - progress, - request, - analyticsPropertyFlow = "unknown", -}: { - modelId: DeviceModelId, - type: "light" | "dark", - appName: string, - progress: number, - request: any, - analyticsPropertyFlow?: string, -}) => { - const currency = request?.currency || request?.account?.currency; - const appNameToTrack = appName || request?.appName || currency?.managerAppName; - const cleanProgress = progress ? Math.round(progress * 100) : null; - useEffect(() => { - const trackingArgs = [ - "In-line app install", - { appName: appNameToTrack, flow: analyticsPropertyFlow }, - ]; - track(...trackingArgs); - }, [appNameToTrack, analyticsPropertyFlow]); - return ( - -
- - - -
- - <Trans i18nKey="DeviceAction.installApp" values={{ appName }} /> - - - - - {cleanProgress ? {`${cleanProgress}%`} : null} -
- - ); -}; - -export const renderListingApps = () => ( - -
- - - - - -
- - <Trans i18nKey="DeviceAction.listApps" /> - - - - -
- -); - -export const renderAllowManager = ({ - modelId, - type, - wording, -}: { - modelId: DeviceModelId, - type: "light" | "dark", - wording: string, -}) => ( - - -
- - - -
- - <Trans i18nKey="DeviceAction.allowManagerPermission" values={{ wording }} /> - -
- -); - -export const renderAllowOpeningApp = ({ - modelId, - type, - wording, - tokenContext, - isDeviceBlocker, -}: { - modelId: DeviceModelId, - type: "light" | "dark", - wording: string, - tokenContext?: ?TokenCurrency, - isDeviceBlocker?: boolean, -}) => ( - - {isDeviceBlocker ? : null} -
- - - -
- - <Trans i18nKey="DeviceAction.allowAppPermission" values={{ wording }} /> - {!tokenContext ? null : ( - <> - {"\n"} - <Trans - i18nKey="DeviceAction.allowAppPermissionSubtitleToken" - values={{ token: tokenContext.name }} - /> - </> - )} - -
- -); - -export const renderWarningOutdated = ({ - passWarning, - appName, -}: { - passWarning: () => void, - appName: string, -}) => ( - - - - - - - - - - - - - - - -); - -export const renderError = ({ - error, - withOpenManager, - onRetry, - withExportLogs, - list, - supportLink, - warning, - info, - managerAppName, - requireFirmwareUpdate, - withOnboardingCTA, -}: { - error: Error, - withOpenManager?: boolean, - onRetry?: () => void, - withExportLogs?: boolean, - list?: boolean, - supportLink?: string, - warning?: boolean, - info?: boolean, - managerAppName?: string, - requireFirmwareUpdate?: boolean, - withOnboardingCTA?: boolean, -}) => ( - - - - - - - - - - - {list ? ( - -
    - -
-
- ) : null} - - {managerAppName || requireFirmwareUpdate ? ( - - ) : ( - <> - {supportLink ? ( - } url={supportLink} /> - ) : null} - {withExportLogs ? ( - } - small={false} - primary={false} - outlineGrey - mx={1} - /> - ) : null} - {withOpenManager ? ( - - ) : onRetry ? ( - - ) : null} - {withOnboardingCTA ? : null} - - )} - -
-); - -export const renderInWrongAppForAccount = ({ - onRetry, - accountName, -}: { - onRetry: () => void, - accountName: string, -}) => - renderError({ - error: new WrongDeviceForAccount(null, { accountName }), - withExportLogs: true, - onRetry, - }); - -export const renderConnectYourDevice = ({ - modelId, - type, - onRetry, - onRepairModal, - device, - unresponsive, -}: { - modelId: DeviceModelId, - type: "light" | "dark", - onRetry: () => void, - onRepairModal: () => void, - device: ?Device, - unresponsive?: boolean, -}) => ( - -
- - - -
- - <Trans - i18nKey={ - unresponsive ? "DeviceAction.unlockDevice" : "DeviceAction.connectAndUnlockDevice" - } - /> - - {!device ? ( - - - - ) : null} -
- -); - -export const renderFirmwareUpdating = ({ - modelId, - type, -}: { - modelId: DeviceModelId, - type: "light" | "dark", -}) => ( - -
- - - -
- - <Trans i18nKey={"DeviceAction.unlockDeviceAfterFirmwareUpdate"} /> - -
- -); - -export const renderSwapDeviceConfirmation = ({ - modelId, - type, - transaction, - status, - exchangeRate, - exchange, - amountExpectedTo, - estimatedFees, -}: { - modelId: DeviceModelId, - type: "light" | "dark", - transaction: Transaction, - status: TransactionStatus, - exchangeRate: ExchangeRate, - exchange: Exchange, - amountExpectedTo?: string, - estimatedFees?: string, -}) => { - const ProviderIcon = getProviderIcon(exchangeRate); - const [sourceAccountName, sourceAccountCurrency] = [ - getAccountName(exchange.fromAccount), - getAccountCurrency(exchange.fromAccount), - ]; - const [targetAccountName, targetAccountCurrency] = [ - getAccountName(exchange.toAccount), - getAccountCurrency(exchange.toAccount), - ]; - return ( - <> - - - - - - - - - {map( - { - amountSent: ( - - ), - amountReceived: ( - - ), - provider: ( - - - {getProviderName(exchangeRate.provider)} - - ), - fees: ( - - ), - sourceAccount: ( - - {sourceAccountCurrency && ( - - )} - {sourceAccountName} - - ), - targetAccount: ( - - {targetAccountCurrency && ( - - )} - {targetAccountName} - - ), - }, - (value, key) => { - return ( - - - - - - {value} - - - ); - }, - )} - - {renderVerifyUnwrapped({ modelId, type })} - - - - ); -}; - -export const renderSecureTransferDeviceConfirmation = ({ - exchangeType, - modelId, - type, -}: { - exchangeType: "sell" | "fund", - modelId: DeviceModelId, - type: "light" | "dark", -}) => ( - <> - - - - {renderVerifyUnwrapped({ modelId, type })} - - - - - - -); - -export const renderLoading = ({ - modelId, - children, -}: { - modelId: DeviceModelId, - children?: React$Node, -}) => ( - -
- - - -
- {children || <Trans i18nKey="DeviceAction.loading" />} -
- -); - -export const renderBootloaderStep = ({ onAutoRepair }: { onAutoRepair: () => void }) => ( - - - <Trans i18nKey="genuinecheck.deviceInBootloader"> - {"placeholder"} - <b>{"placeholder"}</b> - {"placeholder"} - </Trans> - - - -); diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.jsx b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.jsx new file mode 100644 index 000000000000..076fde585773 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/DeviceAction/rendering.jsx @@ -0,0 +1,766 @@ +// @flow +import React, { useCallback, useContext, useEffect } from "react"; +import { BigNumber } from "bignumber.js"; +import map from "lodash/map"; +import { Trans } from "react-i18next"; +import { connect, useDispatch } from "react-redux"; +import { useHistory } from "react-router-dom"; +import styled from "styled-components"; +import type { + TokenCurrency, + Transaction, + TransactionStatus, +} from "@ledgerhq/live-common/types/index"; +import type { ExchangeRate, Exchange } from "@ledgerhq/live-common/exchange/swap/types"; +import { getProviderName } from "@ledgerhq/live-common/exchange/swap/utils/index"; +import { WrongDeviceForAccount, UpdateYourApp } from "@ledgerhq/errors"; +import { LatestFirmwareVersionRequired } from "@ledgerhq/live-common/errors"; +import type { DeviceModelId } from "@ledgerhq/devices"; +import type { Device } from "@ledgerhq/live-common/hw/actions/types"; +import { + getAccountUnit, + getMainAccount, + getAccountName, + getAccountCurrency, +} from "@ledgerhq/live-common/account/index"; +import { closeAllModal } from "~/renderer/actions/modals"; +import { setNotSeededDeviceRelaunch } from "~/renderer/actions/application"; +import Animation from "~/renderer/animations"; +import Button from "~/renderer/components/Button"; +import TranslatedError from "~/renderer/components/TranslatedError"; +import Text from "~/renderer/components/Text"; +import Box from "~/renderer/components/Box"; +import BigSpinner from "~/renderer/components/BigSpinner"; +import Alert from "~/renderer/components/Alert"; +import ConnectTroubleshooting from "~/renderer/components/ConnectTroubleshooting"; +import ExportLogsButton from "~/renderer/components/ExportLogsButton"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { getDeviceAnimation } from "./animations"; +import { DeviceBlocker } from "./DeviceBlocker"; +import ErrorIcon from "~/renderer/components/ErrorIcon"; +import IconTriangleWarning from "~/renderer/icons/TriangleWarning"; +import SupportLinkError from "~/renderer/components/SupportLinkError"; +import { urls } from "~/config/urls"; +import CurrencyUnitValue from "~/renderer/components/CurrencyUnitValue"; +import ExternalLinkButton from "../ExternalLinkButton"; +import TrackPage, { setTrackingSource } from "~/renderer/analytics/TrackPage"; +import { Rotating } from "~/renderer/components/Spinner"; +import ProgressCircle from "~/renderer/components/ProgressCircle"; +import CrossCircle from "~/renderer/icons/CrossCircle"; +import { getProviderIcon } from "~/renderer/screens/exchange/Swap2/utils"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; +import { SWAP_VERSION } from "~/renderer/screens/exchange/Swap2/utils/index"; +import { context } from "~/renderer/drawers/Provider"; +import { track } from "~/renderer/analytics/segment"; +import { relaunchOnboarding } from "~/renderer/actions/onboarding"; +import { DrawerFooter } from "~/renderer/screens/exchange/Swap2/Form/DrawerFooter"; + +export const AnimationWrapper: ThemedComponent<{ modelId?: DeviceModelId }> = styled.div` + width: 600px; + max-width: 100%; + padding-bottom: 20px; + align-self: center; + display: flex; + align-items: center; + justify-content: center; +`; + +const ProgressWrapper: ThemedComponent<{}> = styled.div` + padding: 24px; + align-self: center; + display: flex; + align-items: center; + justify-content: center; +`; + +export const Wrapper: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; + flex: 1; + align-items: center; + justify-content: center; + min-height: 260px; + max-width: 100%; +`; + +export const ConfirmWrapper: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; + flex: 1; + justify-content: center; + min-height: 260px; + max-width: 100%; +`; + +const Logo: ThemedComponent<{ warning?: boolean }> = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + color: ${p => + p.info + ? p.theme.colors.palette.primary.main + : p.warning + ? p.theme.colors.warning + : p.theme.colors.alertRed}; + margin-bottom: 20px; +`; + +export const Header: ThemedComponent<{}> = styled.div` + display: flex; + flex: 1 0 0%; + flex-direction: column; + justify-content: flex-end; + align-content: center; + align-items: center; +`; + +export const Footer: ThemedComponent<{}> = styled.div` + display: flex; + flex: 1 0 0%; + flex-direction: column; + justify-content: flex-start; + align-content: center; + align-items: center; +`; + +export const Title: ThemedComponent<{}> = styled(Text).attrs({ + ff: "Inter|SemiBold", + color: "palette.text.shade100", + textAlign: "center", + fontSize: 5, +})` + white-space: pre-line; +`; + +export const SubTitle: ThemedComponent<{}> = styled(Text).attrs({ + ff: "Inter|Regular", + color: "palette.text.shade100", + textAlign: "center", + fontSize: 3, +})` + margin-top: 8px; +`; + +const ErrorTitle = styled(Text).attrs({ + ff: "Inter|SemiBold", + color: "palette.text.shade100", + textAlign: "center", + fontSize: 6, +})` + user-select: text; + margin-bottom: 10px; +`; + +const ErrorDescription = styled(Text).attrs({ + ff: "Inter|Regular", + color: "palette.text.shade60", + textAlign: "center", + fontSize: 4, +})` + user-select: text; +`; + +const ButtonContainer = styled(Box).attrs(p => ({ + mt: 25, + horizontal: true, +}))``; + +const TroubleshootingWrapper = styled.div` + margin-top: auto; + margin-bottom: 16px; +`; + +// these are not components because we want reconciliation to not remount the sub elements + +export const renderRequestQuitApp = ({ + modelId, + type, +}: { + modelId: DeviceModelId, + type: "light" | "dark", +}) => ( + +
+ + + +
+ + <Trans i18nKey="DeviceAction.quitApp" /> + +
+ +); + +export const renderVerifyUnwrapped = ({ + modelId, + type, +}: { + modelId: DeviceModelId, + type: "light" | "dark", +}) => ( + + + + +); + +const OpenManagerBtn = ({ + closeAllModal, + appName, + updateApp, + firmwareUpdate, + mt = 2, + ml = 0, +}: { + closeAllModal: () => void, + appName?: string, + updateApp?: boolean, + firmwareUpdate?: boolean, + mt?: number, + ml?: number, +}) => { + const history = useHistory(); + const { setDrawer } = useContext(context); + + const onClick = useCallback(() => { + const urlParams = new URLSearchParams({ + updateApp: updateApp ? "true" : "false", + firmwareUpdate: firmwareUpdate ? "true" : "false", + ...(appName ? { q: appName } : {}), + }); + const search = urlParams.toString(); + setTrackingSource("device action open manager button"); + history.push({ + pathname: "/manager", + search: search ? `?${search}` : "", + }); + closeAllModal(); + setDrawer(undefined); + }, [updateApp, firmwareUpdate, appName, history, closeAllModal, setDrawer]); + + return ( + + ); +}; + +const OpenOnboardingBtn = () => { + const { setDrawer } = useContext(context); + const dispatch = useDispatch(); + + const onClick = useCallback(() => { + setTrackingSource("device action open onboarding button"); + dispatch(setNotSeededDeviceRelaunch(true)); + dispatch(relaunchOnboarding(true)); + dispatch(closeAllModal()); + setDrawer(undefined); + }, [dispatch, setDrawer]); + + return ( + + ); +}; + +const OpenManagerButton = connect(null, { closeAllModal })(OpenManagerBtn); + +export const renderRequiresAppInstallation = ({ appNames }: { appNames: string[] }) => { + const appNamesCSV = appNames.join(", "); + return ( + + + + + + + + + + + + + + + ); +}; + +export const InstallingApp = ({ + modelId, + type, + appName, + progress, + request, + analyticsPropertyFlow = "unknown", +}: { + modelId: DeviceModelId, + type: "light" | "dark", + appName: string, + progress: number, + request: any, + analyticsPropertyFlow?: string, +}) => { + const currency = request?.currency || request?.account?.currency; + const appNameToTrack = appName || request?.appName || currency?.managerAppName; + const cleanProgress = progress ? Math.round(progress * 100) : null; + useEffect(() => { + const trackingArgs = [ + "In-line app install", + { appName: appNameToTrack, flow: analyticsPropertyFlow }, + ]; + track(...trackingArgs); + }, [appNameToTrack, analyticsPropertyFlow]); + return ( + +
+ + + +
+ + <Trans i18nKey="DeviceAction.installApp" values={{ appName }} /> + + + + + {cleanProgress ? {`${cleanProgress}%`} : null} +
+ + ); +}; + +export const renderListingApps = () => ( + +
+ + + + + +
+ + <Trans i18nKey="DeviceAction.listApps" /> + + + + +
+ +); + +export const renderAllowManager = ({ + modelId, + type, + wording, +}: { + modelId: DeviceModelId, + type: "light" | "dark", + wording: string, +}) => ( + + +
+ + + +
+ + <Trans i18nKey="DeviceAction.allowManagerPermission" values={{ wording }} /> + +
+ +); + +export const renderAllowOpeningApp = ({ + modelId, + type, + wording, + tokenContext, + isDeviceBlocker, +}: { + modelId: DeviceModelId, + type: "light" | "dark", + wording: string, + tokenContext?: ?TokenCurrency, + isDeviceBlocker?: boolean, +}) => ( + + {isDeviceBlocker ? : null} +
+ + + +
+ + <Trans i18nKey="DeviceAction.allowAppPermission" values={{ wording }} /> + {!tokenContext ? null : ( + <> + {"\n"} + <Trans + i18nKey="DeviceAction.allowAppPermissionSubtitleToken" + values={{ token: tokenContext.name }} + /> + </> + )} + +
+ +); + +export const renderWarningOutdated = ({ + passWarning, + appName, +}: { + passWarning: () => void, + appName: string, +}) => ( + + + + + + + + + + + + + + + +); + +export const renderError = ({ + error, + withOpenManager, + onRetry, + withExportLogs, + list, + supportLink, + warning, + info, + managerAppName, + requireFirmwareUpdate, + withOnboardingCTA, +}: { + error: Error, + withOpenManager?: boolean, + onRetry?: () => void, + withExportLogs?: boolean, + list?: boolean, + supportLink?: string, + warning?: boolean, + info?: boolean, + managerAppName?: string, + requireFirmwareUpdate?: boolean, + withOnboardingCTA?: boolean, +}) => ( + + + + + + + + + + + {list ? ( + +
    + +
+
+ ) : null} + + {managerAppName || requireFirmwareUpdate ? ( + + ) : ( + <> + {supportLink ? ( + } url={supportLink} /> + ) : null} + {withExportLogs ? ( + } + small={false} + primary={false} + outlineGrey + mx={1} + /> + ) : null} + {withOpenManager ? ( + + ) : onRetry ? ( + + ) : null} + {withOnboardingCTA ? : null} + + )} + +
+); + +export const renderInWrongAppForAccount = ({ + onRetry, + accountName, +}: { + onRetry: () => void, + accountName: string, +}) => + renderError({ + error: new WrongDeviceForAccount(null, { accountName }), + withExportLogs: true, + onRetry, + }); + +export const renderConnectYourDevice = ({ + modelId, + type, + onRetry, + onRepairModal, + device, + unresponsive, +}: { + modelId: DeviceModelId, + type: "light" | "dark", + onRetry: () => void, + onRepairModal: () => void, + device: ?Device, + unresponsive?: boolean, +}) => ( + +
+ + + +
+ + <Trans + i18nKey={ + unresponsive ? "DeviceAction.unlockDevice" : "DeviceAction.connectAndUnlockDevice" + } + /> + + {!device ? ( + + + + ) : null} +
+ +); + +export const renderFirmwareUpdating = ({ + modelId, + type, +}: { + modelId: DeviceModelId, + type: "light" | "dark", +}) => ( + +
+ + + +
+ + <Trans i18nKey={"DeviceAction.unlockDeviceAfterFirmwareUpdate"} /> + +
+ +); + +export const renderSwapDeviceConfirmation = ({ + modelId, + type, + transaction, + status, + exchangeRate, + exchange, + amountExpectedTo, + estimatedFees, +}: { + modelId: DeviceModelId, + type: "light" | "dark", + transaction: Transaction, + status: TransactionStatus, + exchangeRate: ExchangeRate, + exchange: Exchange, + amountExpectedTo?: string, + estimatedFees?: string, +}) => { + const ProviderIcon = getProviderIcon(exchangeRate); + const [sourceAccountName, sourceAccountCurrency] = [ + getAccountName(exchange.fromAccount), + getAccountCurrency(exchange.fromAccount), + ]; + const [targetAccountName, targetAccountCurrency] = [ + getAccountName(exchange.toAccount), + getAccountCurrency(exchange.toAccount), + ]; + return ( + <> + + + + + + + + + {map( + { + amountSent: ( + + ), + amountReceived: ( + + ), + provider: ( + + + {getProviderName(exchangeRate.provider)} + + ), + fees: ( + + ), + sourceAccount: ( + + {sourceAccountCurrency && ( + + )} + {sourceAccountName} + + ), + targetAccount: ( + + {targetAccountCurrency && ( + + )} + {targetAccountName} + + ), + }, + (value, key) => { + return ( + + + + + + {value} + + + ); + }, + )} + + {renderVerifyUnwrapped({ modelId, type })} + + + + ); +}; + +export const renderSecureTransferDeviceConfirmation = ({ + exchangeType, + modelId, + type, +}: { + exchangeType: "sell" | "fund", + modelId: DeviceModelId, + type: "light" | "dark", +}) => ( + <> + + + + {renderVerifyUnwrapped({ modelId, type })} + + + + + + +); + +export const renderLoading = ({ + modelId, + children, +}: { + modelId: DeviceModelId, + children?: React$Node, +}) => ( + +
+ + + +
+ {children || <Trans i18nKey="DeviceAction.loading" />} +
+ +); + +export const renderBootloaderStep = ({ onAutoRepair }: { onAutoRepair: () => void }) => ( + + + <Trans i18nKey="genuinecheck.deviceInBootloader"> + {"placeholder"} + <b>{"placeholder"}</b> + {"placeholder"} + </Trans> + + + +); diff --git a/apps/ledger-live-desktop/src/renderer/components/DeviceBusyIndicator.js b/apps/ledger-live-desktop/src/renderer/components/DeviceBusyIndicator.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/DeviceBusyIndicator.js rename to apps/ledger-live-desktop/src/renderer/components/DeviceBusyIndicator.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.js b/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.js deleted file mode 100644 index af252efe77ec..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.js +++ /dev/null @@ -1,179 +0,0 @@ -// @flow -import { BigNumber } from "bignumber.js"; -import React, { memo } from "react"; -import styled from "styled-components"; -import { useSelector } from "react-redux"; -import { Trans } from "react-i18next"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { useCalculate } from "@ledgerhq/live-common/lib/countervalues/react"; -import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import InfoCircle from "~/renderer/icons/InfoCircle"; -import ToolTip from "~/renderer/components/Tooltip"; -import Box from "~/renderer/components/Box/Box"; -import Text from "~/renderer/components/Text"; -import FormattedDate from "./FormattedDate"; -import { NoCountervaluePlaceholder } from "./CounterValue"; - -const Row = styled(Box).attrs(() => ({ - minWidth: 250, - horizontal: true, - justifyContent: "space-between", - alignItems: "center", -}))``; - -const Column = styled(Box).attrs(() => ({ - justifyContent: "flex-start", - alignItems: "flex-start", -}))``; - -const Title = styled(Text).attrs(() => ({ - fontSize: 4, - fontWeight: 600, - color: "palette.text.shade100", -}))``; - -const Subtitle = styled(Text).attrs(() => ({ - fontSize: 2, - color: "palette.text.shade60", -}))` - font-style: italic; -`; - -const Separator = styled.div` - width: 100%; - height: 1px; - margin: ${p => p.theme.space[2]}px auto; - background-color: ${p => p.theme.colors.palette.divider}; -`; - -type Props = { - currency: Currency, - date: Date, - compareDate?: Date, - value: BigNumber, - alwaysShowSign?: boolean, - subMagnitude?: number, - placeholder?: React$Node, - prefix?: React$Node, - suffix?: React$Node, - tooltipDateLabel?: React$Node, - tooltipCompareDateLabel?: React$Node, -}; - -function DoubleCounterValue({ - value, - date, - compareDate, - currency, - alwaysShowSign = false, - placeholder, - prefix, - suffix, - tooltipDateLabel, - tooltipCompareDateLabel, - ...props -}: Props) { - const counterValueCurrency = useSelector(counterValueCurrencySelector); - const unit = counterValueCurrency.units[0]; - const valueNumber = value.toNumber(); - const countervalue = useCalculate({ - from: currency, - to: counterValueCurrency, - value: valueNumber, - disableRounding: true, - date, - }); - - const compareCountervalue = useCalculate({ - from: currency, - to: counterValueCurrency, - value: valueNumber, - disableRounding: true, - date: compareDate, - }); - - if (typeof countervalue === "undefined") { - return ; - } - - const val = BigNumber(countervalue ?? 0); - const compareVal = BigNumber(compareCountervalue ?? 0); - - return ( - <> - {prefix || null} - - - - - - - - - {tooltipDateLabel || <Trans i18nKey={"calendar.transactionDate"} />} - - - - - -
- -
-
- - - - {tooltipCompareDateLabel || <Trans i18nKey={"calendar.today"} />} - - - - -
- -
-
- - } - > - - - -
-
- - {suffix || null} - - ); -} - -export default memo(DoubleCounterValue); diff --git a/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.jsx b/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.jsx new file mode 100644 index 000000000000..002d6da26cc8 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/DoubleCounterValue.jsx @@ -0,0 +1,179 @@ +// @flow +import { BigNumber } from "bignumber.js"; +import React, { memo } from "react"; +import styled from "styled-components"; +import { useSelector } from "react-redux"; +import { Trans } from "react-i18next"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import { useCalculate } from "@ledgerhq/live-common/countervalues/react"; +import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import InfoCircle from "~/renderer/icons/InfoCircle"; +import ToolTip from "~/renderer/components/Tooltip"; +import Box from "~/renderer/components/Box/Box"; +import Text from "~/renderer/components/Text"; +import FormattedDate from "./FormattedDate"; +import { NoCountervaluePlaceholder } from "./CounterValue"; + +const Row = styled(Box).attrs(() => ({ + minWidth: 250, + horizontal: true, + justifyContent: "space-between", + alignItems: "center", +}))``; + +const Column = styled(Box).attrs(() => ({ + justifyContent: "flex-start", + alignItems: "flex-start", +}))``; + +const Title = styled(Text).attrs(() => ({ + fontSize: 4, + fontWeight: 600, + color: "palette.text.shade100", +}))``; + +const Subtitle = styled(Text).attrs(() => ({ + fontSize: 2, + color: "palette.text.shade60", +}))` + font-style: italic; +`; + +const Separator = styled.div` + width: 100%; + height: 1px; + margin: ${p => p.theme.space[2]}px auto; + background-color: ${p => p.theme.colors.palette.divider}; +`; + +type Props = { + currency: Currency, + date: Date, + compareDate?: Date, + value: BigNumber, + alwaysShowSign?: boolean, + subMagnitude?: number, + placeholder?: React$Node, + prefix?: React$Node, + suffix?: React$Node, + tooltipDateLabel?: React$Node, + tooltipCompareDateLabel?: React$Node, +}; + +function DoubleCounterValue({ + value, + date, + compareDate, + currency, + alwaysShowSign = false, + placeholder, + prefix, + suffix, + tooltipDateLabel, + tooltipCompareDateLabel, + ...props +}: Props) { + const counterValueCurrency = useSelector(counterValueCurrencySelector); + const unit = counterValueCurrency.units[0]; + const valueNumber = value.toNumber(); + const countervalue = useCalculate({ + from: currency, + to: counterValueCurrency, + value: valueNumber, + disableRounding: true, + date, + }); + + const compareCountervalue = useCalculate({ + from: currency, + to: counterValueCurrency, + value: valueNumber, + disableRounding: true, + date: compareDate, + }); + + if (typeof countervalue === "undefined") { + return ; + } + + const val = BigNumber(countervalue ?? 0); + const compareVal = BigNumber(compareCountervalue ?? 0); + + return ( + <> + {prefix || null} + + + + + + + + + {tooltipDateLabel || <Trans i18nKey={"calendar.transactionDate"} />} + + + + + +
+ +
+
+ + + + {tooltipCompareDateLabel || <Trans i18nKey={"calendar.today"} />} + + + + +
+ +
+
+ + } + > + + + +
+
+ + {suffix || null} + + ); +} + +export default memo(DoubleCounterValue); diff --git a/apps/ledger-live-desktop/src/renderer/components/DropDownSelector.js b/apps/ledger-live-desktop/src/renderer/components/DropDownSelector.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/DropDownSelector.js rename to apps/ledger-live-desktop/src/renderer/components/DropDownSelector.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/EarnRewardsInfoModal.js b/apps/ledger-live-desktop/src/renderer/components/EarnRewardsInfoModal.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/EarnRewardsInfoModal.js rename to apps/ledger-live-desktop/src/renderer/components/EarnRewardsInfoModal.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Ellipsis.js b/apps/ledger-live-desktop/src/renderer/components/Ellipsis.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Ellipsis.js rename to apps/ledger-live-desktop/src/renderer/components/Ellipsis.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ErrorBanner.js b/apps/ledger-live-desktop/src/renderer/components/ErrorBanner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ErrorBanner.js rename to apps/ledger-live-desktop/src/renderer/components/ErrorBanner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.js b/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.js deleted file mode 100644 index 69f9a58b9578..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.js +++ /dev/null @@ -1,50 +0,0 @@ -// @flow -import React from "react"; -import ExclamationCircleThin from "~/renderer/icons/ExclamationCircleThin"; -import Warning from "~/renderer/icons/TriangleWarning"; -import CrossCircle from "~/renderer/icons/CrossCircle"; -import InfoCircle from "~/renderer/icons/InfoCircle"; -import Lock from "~/renderer/icons/LockCircle"; - -import { - UserRefusedAllowManager, - UserRefusedFirmwareUpdate, - UserRefusedOnDevice, - UserRefusedAddress, - ManagerDeviceLockedError, -} from "@ledgerhq/errors"; - -import { - SwapGenericAPIError, - DeviceNotOnboarded, - NoSuchAppOnProvider, -} from "@ledgerhq/live-common/lib/errors"; - -export type ErrorIconProps = { - error: Error, - size?: number, -}; - -const ErrorIcon = ({ error, size = 44 }: ErrorIconProps) => { - switch (true) { - case !error: - return null; - case error instanceof DeviceNotOnboarded: - return ; - case error instanceof UserRefusedFirmwareUpdate: - return ; - case error instanceof UserRefusedAllowManager: - case error instanceof UserRefusedOnDevice: - case error instanceof UserRefusedAddress: - case error instanceof SwapGenericAPIError: - case error instanceof NoSuchAppOnProvider: - return ; - case error instanceof ManagerDeviceLockedError: - return ; - - default: - return ; - } -}; - -export default ErrorIcon; diff --git a/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.jsx b/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.jsx new file mode 100644 index 000000000000..be03d8f91463 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ErrorIcon.jsx @@ -0,0 +1,50 @@ +// @flow +import React from "react"; +import ExclamationCircleThin from "~/renderer/icons/ExclamationCircleThin"; +import Warning from "~/renderer/icons/TriangleWarning"; +import CrossCircle from "~/renderer/icons/CrossCircle"; +import InfoCircle from "~/renderer/icons/InfoCircle"; +import Lock from "~/renderer/icons/LockCircle"; + +import { + UserRefusedAllowManager, + UserRefusedFirmwareUpdate, + UserRefusedOnDevice, + UserRefusedAddress, + ManagerDeviceLockedError, +} from "@ledgerhq/errors"; + +import { + SwapGenericAPIError, + DeviceNotOnboarded, + NoSuchAppOnProvider, +} from "@ledgerhq/live-common/errors"; + +export type ErrorIconProps = { + error: Error, + size?: number, +}; + +const ErrorIcon = ({ error, size = 44 }: ErrorIconProps) => { + switch (true) { + case !error: + return null; + case error instanceof DeviceNotOnboarded: + return ; + case error instanceof UserRefusedFirmwareUpdate: + return ; + case error instanceof UserRefusedAllowManager: + case error instanceof UserRefusedOnDevice: + case error instanceof UserRefusedAddress: + case error instanceof SwapGenericAPIError: + case error instanceof NoSuchAppOnProvider: + return ; + case error instanceof ManagerDeviceLockedError: + return ; + + default: + return ; + } +}; + +export default ErrorIcon; diff --git a/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.js b/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.js deleted file mode 100644 index 9cff3c988d57..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.js +++ /dev/null @@ -1,136 +0,0 @@ -// @flow -import moment from "moment"; -import { ipcRenderer, webFrame } from "electron"; -import * as remote from "@electron/remote"; -import React, { useState, useCallback } from "react"; -import { useSelector } from "react-redux"; -import { useTranslation } from "react-i18next"; -import { getAllEnvs } from "@ledgerhq/live-common/lib/env"; -import type { Account } from "@ledgerhq/live-common/lib/types"; -import KeyHandler from "react-key-handler"; -import logger from "~/logger"; -import getUser from "~/helpers/user"; -import Button from "~/renderer/components/Button"; -import type { Props as ButtonProps } from "~/renderer/components/Button"; -import { accountsSelector } from "~/renderer/reducers/accounts"; - -const saveLogs = async (path: { canceled: boolean, filePath: string }) => { - await ipcRenderer.invoke("save-logs", path); -}; - -type RestProps = ButtonProps & {| - icon?: boolean, - inverted?: boolean, // only used with primary for now - lighterPrimary?: boolean, - danger?: boolean, - lighterDanger?: boolean, - disabled?: boolean, - isLoading?: boolean, - event?: string, - eventProperties?: Object, - outline?: boolean, - outlineGrey?: boolean, -|}; - -type Props = {| - ...RestProps, - primary?: boolean, - small?: boolean, - hookToShortcut?: boolean, - title?: React$Node, - withoutAppData?: boolean, - accounts?: Account[], -|}; - -const ExportLogsBtnWrapper = (args: Props) => { - if (args.withoutAppData) { - return ; - } else { - return ; - } -}; - -const ExportLogsBtnWithAccounts = (args: Props) => { - const accounts = useSelector(accountsSelector); - return ; -}; - -const ExportLogsBtn = ({ - hookToShortcut, - primary = true, - small = true, - title, - withoutAppData, - accounts = [], - ...rest -}: Props) => { - const { t } = useTranslation(); - const [exporting, setExporting] = useState(false); - - const exportLogs = useCallback(async () => { - const resourceUsage = webFrame.getResourceUsage(); - const user = await getUser(); - logger.log("exportLogsMeta", { - resourceUsage, - release: __APP_VERSION__, - git_commit: __GIT_REVISION__, - environment: __DEV__ ? "development" : "production", - userAgent: window.navigator.userAgent, - userAnonymousId: user.id, - env: { - ...getAllEnvs(), - }, - accountsIds: accounts.map(a => a.id), - }); - const path = await remote.dialog.showSaveDialog({ - title: "Export logs", - defaultPath: `ledgerlive-logs-${moment().format("YYYY.MM.DD-HH.mm.ss")}-${__GIT_REVISION__ || - "unversioned"}.json`, - filters: [ - { - name: "All Files", - extensions: ["json"], - }, - ], - }); - - if (path) { - await saveLogs(path); - } - }, [accounts]); - - const handleExportLogs = useCallback(async () => { - if (exporting) return; - - setExporting(true); - - try { - await exportLogs(); - } catch (error) { - logger.critical(error); - } finally { - setExporting(false); - } - }, [exporting, setExporting, exportLogs]); - - const onKeyHandle = useCallback( - e => { - if (e.ctrlKey) { - handleExportLogs(); - } - }, - [handleExportLogs], - ); - - const text = title || t("settings.exportLogs.btn"); - - return hookToShortcut ? ( - - ) : ( - - ); -}; - -export default ExportLogsBtnWrapper; diff --git a/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.jsx b/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.jsx new file mode 100644 index 000000000000..73d818f197a7 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ExportLogsButton.jsx @@ -0,0 +1,136 @@ +// @flow +import moment from "moment"; +import { ipcRenderer, webFrame } from "electron"; +import * as remote from "@electron/remote"; +import React, { useState, useCallback } from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { getAllEnvs } from "@ledgerhq/live-common/env"; +import type { Account } from "@ledgerhq/live-common/types/index"; +import KeyHandler from "react-key-handler"; +import logger from "~/logger"; +import getUser from "~/helpers/user"; +import Button from "~/renderer/components/Button"; +import type { Props as ButtonProps } from "~/renderer/components/Button"; +import { accountsSelector } from "~/renderer/reducers/accounts"; + +const saveLogs = async (path: { canceled: boolean, filePath: string }) => { + await ipcRenderer.invoke("save-logs", path); +}; + +type RestProps = ButtonProps & {| + icon?: boolean, + inverted?: boolean, // only used with primary for now + lighterPrimary?: boolean, + danger?: boolean, + lighterDanger?: boolean, + disabled?: boolean, + isLoading?: boolean, + event?: string, + eventProperties?: Object, + outline?: boolean, + outlineGrey?: boolean, +|}; + +type Props = {| + ...RestProps, + primary?: boolean, + small?: boolean, + hookToShortcut?: boolean, + title?: React$Node, + withoutAppData?: boolean, + accounts?: Account[], +|}; + +const ExportLogsBtnWrapper = (args: Props) => { + if (args.withoutAppData) { + return ; + } else { + return ; + } +}; + +const ExportLogsBtnWithAccounts = (args: Props) => { + const accounts = useSelector(accountsSelector); + return ; +}; + +const ExportLogsBtn = ({ + hookToShortcut, + primary = true, + small = true, + title, + withoutAppData, + accounts = [], + ...rest +}: Props) => { + const { t } = useTranslation(); + const [exporting, setExporting] = useState(false); + + const exportLogs = useCallback(async () => { + const resourceUsage = webFrame.getResourceUsage(); + const user = await getUser(); + logger.log("exportLogsMeta", { + resourceUsage, + release: __APP_VERSION__, + git_commit: __GIT_REVISION__, + environment: __DEV__ ? "development" : "production", + userAgent: window.navigator.userAgent, + userAnonymousId: user.id, + env: { + ...getAllEnvs(), + }, + accountsIds: accounts.map(a => a.id), + }); + const path = await remote.dialog.showSaveDialog({ + title: "Export logs", + defaultPath: `ledgerlive-logs-${moment().format("YYYY.MM.DD-HH.mm.ss")}-${__GIT_REVISION__ || + "unversioned"}.json`, + filters: [ + { + name: "All Files", + extensions: ["json"], + }, + ], + }); + + if (path) { + await saveLogs(path); + } + }, [accounts]); + + const handleExportLogs = useCallback(async () => { + if (exporting) return; + + setExporting(true); + + try { + await exportLogs(); + } catch (error) { + logger.critical(error); + } finally { + setExporting(false); + } + }, [exporting, setExporting, exportLogs]); + + const onKeyHandle = useCallback( + e => { + if (e.ctrlKey) { + handleExportLogs(); + } + }, + [handleExportLogs], + ); + + const text = title || t("settings.exportLogs.btn"); + + return hookToShortcut ? ( + + ) : ( + + ); +}; + +export default ExportLogsBtnWrapper; diff --git a/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.js b/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.js deleted file mode 100644 index 3c80f675ffa2..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.js +++ /dev/null @@ -1,72 +0,0 @@ -// @flow -import React, { Component } from "react"; -import { compose } from "redux"; -import { createStructuredSelector } from "reselect"; -import { withTranslation } from "react-i18next"; -import type { TFunction } from "react-i18next"; -import { connect } from "react-redux"; -import styled from "styled-components"; -import type { Account } from "@ledgerhq/live-common/lib/types/account"; -import { openModal } from "~/renderer/actions/modals"; -import Box from "~/renderer/components/Box"; -import DownloadCloud from "~/renderer/icons/DownloadCloud"; -import Label from "~/renderer/components/Label"; -import Button from "~/renderer/components/Button"; -import { activeAccountsSelector } from "~/renderer/reducers/accounts"; - -const mapDispatchToProps = { - openModal, -}; - -const mapStateToProps = createStructuredSelector({ - accounts: activeAccountsSelector, -}); - -class ExportOperationsBtn extends Component<{ - t: TFunction, - openModal: (string, any) => void, - primary?: boolean, - accounts: Account[], -}> { - openModal = () => this.props.openModal("MODAL_EXPORT_OPERATIONS"); - render() { - const { t, primary, accounts } = this.props; - if (!accounts.length && !primary) return null; - - return primary ? ( - - ) : ( - - - - - {t("exportOperationsModal.title")} - - ); - } -} - -export default compose( - // $FlowFixMe use OwnProps - connect(mapStateToProps, mapDispatchToProps), - withTranslation(), -)(ExportOperationsBtn); - -const LabelWrapper = styled(Label)` - &:hover { - color: ${p => p.theme.colors.wallet}; - cursor: pointer; - } - color: ${p => p.theme.colors.wallet}; - font-size: 13px; - font-family: "Inter", Arial; - font-weight: 600; -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.jsx b/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.jsx new file mode 100644 index 000000000000..eac866b8681b --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ExportOperationsBtn.jsx @@ -0,0 +1,72 @@ +// @flow +import React, { Component } from "react"; +import { compose } from "redux"; +import { createStructuredSelector } from "reselect"; +import { withTranslation } from "react-i18next"; +import type { TFunction } from "react-i18next"; +import { connect } from "react-redux"; +import styled from "styled-components"; +import type { Account } from "@ledgerhq/live-common/types/account"; +import { openModal } from "~/renderer/actions/modals"; +import Box from "~/renderer/components/Box"; +import DownloadCloud from "~/renderer/icons/DownloadCloud"; +import Label from "~/renderer/components/Label"; +import Button from "~/renderer/components/Button"; +import { activeAccountsSelector } from "~/renderer/reducers/accounts"; + +const mapDispatchToProps = { + openModal, +}; + +const mapStateToProps = createStructuredSelector({ + accounts: activeAccountsSelector, +}); + +class ExportOperationsBtn extends Component<{ + t: TFunction, + openModal: (string, any) => void, + primary?: boolean, + accounts: Account[], +}> { + openModal = () => this.props.openModal("MODAL_EXPORT_OPERATIONS"); + render() { + const { t, primary, accounts } = this.props; + if (!accounts.length && !primary) return null; + + return primary ? ( + + ) : ( + + + + + {t("exportOperationsModal.title")} + + ); + } +} + +export default compose( + // $FlowFixMe use OwnProps + connect(mapStateToProps, mapDispatchToProps), + withTranslation(), +)(ExportOperationsBtn); + +const LabelWrapper = styled(Label)` + &:hover { + color: ${p => p.theme.colors.wallet}; + cursor: pointer; + } + color: ${p => p.theme.colors.wallet}; + font-size: 13px; + font-family: "Inter", Arial; + font-weight: 600; +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/Exporter/ExportInstructions.js b/apps/ledger-live-desktop/src/renderer/components/Exporter/ExportInstructions.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Exporter/ExportInstructions.js rename to apps/ledger-live-desktop/src/renderer/components/Exporter/ExportInstructions.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.js b/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.js deleted file mode 100644 index 48dbd06b2e3f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.js +++ /dev/null @@ -1,120 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import { Buffer } from "buffer"; -import { createStructuredSelector } from "reselect"; -import { connect } from "react-redux"; -import styled from "styled-components"; -import { dataToFrames } from "qrloop"; - -import { encode } from "@ledgerhq/live-common/lib/cross"; - -import { activeAccountsSelector } from "~/renderer/reducers/accounts"; -import { exportSettingsSelector } from "~/renderer/reducers/settings"; -import QRCode from "~/renderer/components/QRCode"; - -const mapStateToProps = createStructuredSelector({ - accounts: (state, props) => props.accounts || activeAccountsSelector(state), - settings: exportSettingsSelector, -}); - -const QRCodeContainer = styled.div` - position: relative; - padding: 12px; - border-radius: 8px; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.03); - border: solid 1px ${props => props.theme.colors.palette.divider}; - background-color: ${p => p.theme.colors.white}; -`; - -type OwnProps = { - size: number, -}; - -type Props = OwnProps & { - accounts: *, - settings: *, -}; - -class QRCodeExporter extends PureComponent< - Props, - { - frame: number, - framesRendered: number, - fps: number, - }, -> { - static defaultProps = { - size: 460, - }; - - constructor(props) { - super(); - const { accounts, settings } = props; - const data = encode({ - accounts, - settings, - exporterName: "desktop", - exporterVersion: __APP_VERSION__, - }); - - this.chunks = dataToFrames(data, 160, 4); - - setTimeout(() => { - const BRIDGESTREAM_DATA = Buffer.from(JSON.stringify(this.chunks)).toString("base64"); - console.log(`BRIDGESTREAM_DATA=${BRIDGESTREAM_DATA}`); // eslint-disable-line - }, 500); - } - - state = { - frame: 0, - framesRendered: 1, - fps: 3, - }; - - componentDidMount() { - const nextFrame = ({ frame, framesRendered }) => { - frame = (frame + 1) % this.chunks.length; - framesRendered = Math.min(Math.max(framesRendered, frame + 1), this.chunks.length); - return { frame, framesRendered }; - }; - - let lastT; - const loop = t => { - this._raf = requestAnimationFrame(loop); - if (!lastT) lastT = t; - if ((t - lastT) * this.state.fps < 1000) return; - lastT = t; - this.setState(nextFrame); - }; - this._raf = requestAnimationFrame(loop); - } - - componentWillUnmount() { - cancelAnimationFrame(this._raf); - } - - chunks: string[]; - _raf: *; - - render() { - const { frame, framesRendered } = this.state; - const { size } = this.props; - const { chunks } = this; - const chunkValues = [0, framesRendered]; - return ( - - {chunks.slice(...chunkValues).map((chunk, i) => ( -
- -
- ))} -
- ); - } -} - -const ConnectedQRCodeExporter: React$ComponentType = connect(mapStateToProps)( - QRCodeExporter, -); -export default ConnectedQRCodeExporter; diff --git a/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.jsx b/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.jsx new file mode 100644 index 000000000000..1274da893a6a --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Exporter/QRCodeExporter.jsx @@ -0,0 +1,120 @@ +// @flow + +import React, { PureComponent } from "react"; +import { Buffer } from "buffer"; +import { createStructuredSelector } from "reselect"; +import { connect } from "react-redux"; +import styled from "styled-components"; +import { dataToFrames } from "qrloop"; + +import { encode } from "@ledgerhq/live-common/cross"; + +import { activeAccountsSelector } from "~/renderer/reducers/accounts"; +import { exportSettingsSelector } from "~/renderer/reducers/settings"; +import QRCode from "~/renderer/components/QRCode"; + +const mapStateToProps = createStructuredSelector({ + accounts: (state, props) => props.accounts || activeAccountsSelector(state), + settings: exportSettingsSelector, +}); + +const QRCodeContainer = styled.div` + position: relative; + padding: 12px; + border-radius: 8px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.03); + border: solid 1px ${props => props.theme.colors.palette.divider}; + background-color: ${p => p.theme.colors.white}; +`; + +type OwnProps = { + size: number, +}; + +type Props = OwnProps & { + accounts: *, + settings: *, +}; + +class QRCodeExporter extends PureComponent< + Props, + { + frame: number, + framesRendered: number, + fps: number, + }, +> { + static defaultProps = { + size: 460, + }; + + constructor(props) { + super(); + const { accounts, settings } = props; + const data = encode({ + accounts, + settings, + exporterName: "desktop", + exporterVersion: __APP_VERSION__, + }); + + this.chunks = dataToFrames(data, 160, 4); + + setTimeout(() => { + const BRIDGESTREAM_DATA = Buffer.from(JSON.stringify(this.chunks)).toString("base64"); + console.log(`BRIDGESTREAM_DATA=${BRIDGESTREAM_DATA}`); // eslint-disable-line + }, 500); + } + + state = { + frame: 0, + framesRendered: 1, + fps: 3, + }; + + componentDidMount() { + const nextFrame = ({ frame, framesRendered }) => { + frame = (frame + 1) % this.chunks.length; + framesRendered = Math.min(Math.max(framesRendered, frame + 1), this.chunks.length); + return { frame, framesRendered }; + }; + + let lastT; + const loop = t => { + this._raf = requestAnimationFrame(loop); + if (!lastT) lastT = t; + if ((t - lastT) * this.state.fps < 1000) return; + lastT = t; + this.setState(nextFrame); + }; + this._raf = requestAnimationFrame(loop); + } + + componentWillUnmount() { + cancelAnimationFrame(this._raf); + } + + chunks: string[]; + _raf: *; + + render() { + const { frame, framesRendered } = this.state; + const { size } = this.props; + const { chunks } = this; + const chunkValues = [0, framesRendered]; + return ( + + {chunks.slice(...chunkValues).map((chunk, i) => ( +
+ +
+ ))} +
+ ); + } +} + +const ConnectedQRCodeExporter: React$ComponentType = connect(mapStateToProps)( + QRCodeExporter, +); +export default ConnectedQRCodeExporter; diff --git a/apps/ledger-live-desktop/src/renderer/components/Exporter/index.js b/apps/ledger-live-desktop/src/renderer/components/Exporter/index.js deleted file mode 100644 index e1f255b4c40d..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Exporter/index.js +++ /dev/null @@ -1,31 +0,0 @@ -// @flow -import React from "react"; -import { createStructuredSelector } from "reselect"; -import { connect } from "react-redux"; - -import { activeAccountsSelector } from "~/renderer/reducers/accounts"; -import Box from "~/renderer/components/Box"; -import QRCodeExporter from "~/renderer/components/Exporter/QRCodeExporter"; -import ExportInstructions from "~/renderer/components/Exporter/ExportInstructions"; -import type { Account } from "@ledgerhq/live-common/lib/types"; - -type OwnProps = {}; -type Props = OwnProps & { - accounts?: Account[], -}; - -const Exporter = ({ accounts }: Props) => ( - - - - - - -); - -const mapStateToProps = createStructuredSelector({ - accounts: (state, props) => props.accounts || activeAccountsSelector(state), -}); - -const ConnectedExporter: React$ComponentType = connect(mapStateToProps)(Exporter); -export default ConnectedExporter; diff --git a/apps/ledger-live-desktop/src/renderer/components/Exporter/index.jsx b/apps/ledger-live-desktop/src/renderer/components/Exporter/index.jsx new file mode 100644 index 000000000000..d05d769b241d --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Exporter/index.jsx @@ -0,0 +1,31 @@ +// @flow +import React from "react"; +import { createStructuredSelector } from "reselect"; +import { connect } from "react-redux"; + +import { activeAccountsSelector } from "~/renderer/reducers/accounts"; +import Box from "~/renderer/components/Box"; +import QRCodeExporter from "~/renderer/components/Exporter/QRCodeExporter"; +import ExportInstructions from "~/renderer/components/Exporter/ExportInstructions"; +import type { Account } from "@ledgerhq/live-common/types/index"; + +type OwnProps = {}; +type Props = OwnProps & { + accounts?: Account[], +}; + +const Exporter = ({ accounts }: Props) => ( + + + + + + +); + +const mapStateToProps = createStructuredSelector({ + accounts: (state, props) => props.accounts || activeAccountsSelector(state), +}); + +const ConnectedExporter: React$ComponentType = connect(mapStateToProps)(Exporter); +export default ConnectedExporter; diff --git a/apps/ledger-live-desktop/src/renderer/components/ExternalLink/index.js b/apps/ledger-live-desktop/src/renderer/components/ExternalLink/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ExternalLink/index.js rename to apps/ledger-live-desktop/src/renderer/components/ExternalLink/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ExternalLinkButton.js b/apps/ledger-live-desktop/src/renderer/components/ExternalLinkButton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ExternalLinkButton.js rename to apps/ledger-live-desktop/src/renderer/components/ExternalLinkButton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/FadeInOutBox.js b/apps/ledger-live-desktop/src/renderer/components/FadeInOutBox.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/FadeInOutBox.js rename to apps/ledger-live-desktop/src/renderer/components/FadeInOutBox.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.js b/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.js deleted file mode 100644 index bd811151d171..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.js +++ /dev/null @@ -1,129 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import styled from "styled-components"; -import { BigNumber } from "bignumber.js"; -import { Trans } from "react-i18next"; -import type { Unit } from "@ledgerhq/live-common/lib/types"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import type { Range } from "@ledgerhq/live-common/lib/range"; -import { reverseRangeIndex, projectRangeIndex } from "@ledgerhq/live-common/lib/range"; -import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; -import Box from "./Box"; -import Text from "./Text"; -import CurrencyUnitValue from "./CurrencyUnitValue"; -import Slider from "./Slider"; -import GenericContainer from "./FeesContainer"; -import TranslatedError from "./TranslatedError"; - -type Props = { - range: Range, - value: BigNumber, - onChange: BigNumber => void, - unit: Unit, - error: ?Error, - defaultValue: BigNumber, -}; - -const ErrorWrapper: ThemedComponent<{}> = styled.div` - align-items: center; - color: ${p => p.theme.colors.alertRed}; - display: flex; - > :first-child { - margin-right: 5px; - } -`; - -const ErrorContainer = styled(Box)` - margin-top: 0px; - font-size: 12px; - width: 100%; - transition: all 0.4s ease-in-out; - will-change: max-height; - max-height: ${p => (p.hasError ? 60 : 0)}px; - min-height: ${p => (p.hasError ? 20 : 0)}px; - overflow: hidden; -`; - -const Holder = styled.div` - font-family: Inter; - font-weight: 500; - text-align: right; - color: ${p => p.theme.colors.wallet}; - background-color: ${p => p.theme.colors.pillActiveBackground}; - padding: 0 8px; - border-radius: 4px; -`; - -export function useDynamicRange({ - range, - value, - defaultValue, - onChange, -}: { - range: Range, - value: BigNumber, - defaultValue: BigNumber, - onChange: BigNumber => void, -}) { - const index = reverseRangeIndex(range, value); - const setValueIndex = useCallback((i: number) => onChange(projectRangeIndex(range, i)), [ - range, - onChange, - ]); - const constraintValue = projectRangeIndex(range, index); - return { range, index, constraintValue, setValueIndex }; -} - -const FeeSliderField = ({ range, value, onChange, unit, error, defaultValue }: Props) => { - const { index, constraintValue, setValueIndex } = useDynamicRange({ - range, - value, - defaultValue, - onChange, - }); - - return ( - - - - {" "} - {unit.code} - - } - > - - - - - - - - - - - - - {error ? ( - - - - - - - ) : null} - - - ); -}; - -export default FeeSliderField; diff --git a/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.jsx b/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.jsx new file mode 100644 index 000000000000..f88cde6cfb01 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/FeeSliderField.jsx @@ -0,0 +1,129 @@ +// @flow + +import React, { useCallback } from "react"; +import styled from "styled-components"; +import { BigNumber } from "bignumber.js"; +import { Trans } from "react-i18next"; +import type { Unit } from "@ledgerhq/live-common/types/index"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import type { Range } from "@ledgerhq/live-common/range"; +import { reverseRangeIndex, projectRangeIndex } from "@ledgerhq/live-common/range"; +import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; +import Box from "./Box"; +import Text from "./Text"; +import CurrencyUnitValue from "./CurrencyUnitValue"; +import Slider from "./Slider"; +import GenericContainer from "./FeesContainer"; +import TranslatedError from "./TranslatedError"; + +type Props = { + range: Range, + value: BigNumber, + onChange: BigNumber => void, + unit: Unit, + error: ?Error, + defaultValue: BigNumber, +}; + +const ErrorWrapper: ThemedComponent<{}> = styled.div` + align-items: center; + color: ${p => p.theme.colors.alertRed}; + display: flex; + > :first-child { + margin-right: 5px; + } +`; + +const ErrorContainer = styled(Box)` + margin-top: 0px; + font-size: 12px; + width: 100%; + transition: all 0.4s ease-in-out; + will-change: max-height; + max-height: ${p => (p.hasError ? 60 : 0)}px; + min-height: ${p => (p.hasError ? 20 : 0)}px; + overflow: hidden; +`; + +const Holder = styled.div` + font-family: Inter; + font-weight: 500; + text-align: right; + color: ${p => p.theme.colors.wallet}; + background-color: ${p => p.theme.colors.pillActiveBackground}; + padding: 0 8px; + border-radius: 4px; +`; + +export function useDynamicRange({ + range, + value, + defaultValue, + onChange, +}: { + range: Range, + value: BigNumber, + defaultValue: BigNumber, + onChange: BigNumber => void, +}) { + const index = reverseRangeIndex(range, value); + const setValueIndex = useCallback((i: number) => onChange(projectRangeIndex(range, i)), [ + range, + onChange, + ]); + const constraintValue = projectRangeIndex(range, index); + return { range, index, constraintValue, setValueIndex }; +} + +const FeeSliderField = ({ range, value, onChange, unit, error, defaultValue }: Props) => { + const { index, constraintValue, setValueIndex } = useDynamicRange({ + range, + value, + defaultValue, + onChange, + }); + + return ( + + + + {" "} + {unit.code} + + } + > + + + + + + + + + + + + + {error ? ( + + + + + + + ) : null} + + + ); +}; + +export default FeeSliderField; diff --git a/apps/ledger-live-desktop/src/renderer/components/FeesContainer.js b/apps/ledger-live-desktop/src/renderer/components/FeesContainer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/FeesContainer.js rename to apps/ledger-live-desktop/src/renderer/components/FeesContainer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/FirebaseFeatureFlags.tsx b/apps/ledger-live-desktop/src/renderer/components/FirebaseFeatureFlags.tsx index 5798195a63bc..316d9a0d551b 100644 --- a/apps/ledger-live-desktop/src/renderer/components/FirebaseFeatureFlags.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/FirebaseFeatureFlags.tsx @@ -1,6 +1,6 @@ import React, { ReactNode } from "react"; -import { FeatureFlagsProvider } from "@ledgerhq/live-common/lib/featureFlags"; -import { Feature, FeatureId } from "@ledgerhq/live-common/lib/types"; +import { FeatureFlagsProvider } from "@ledgerhq/live-common/featureFlags/index"; +import { Feature, FeatureId } from "@ledgerhq/live-common/types/index"; import { getValue } from "firebase/remote-config"; import { formatFeatureId, useFirebaseRemoteConfig } from "./FirebaseRemoteConfig"; diff --git a/apps/ledger-live-desktop/src/renderer/components/FirebaseRemoteConfig.tsx b/apps/ledger-live-desktop/src/renderer/components/FirebaseRemoteConfig.tsx index 8edb6c99cb54..4630675fbd5b 100644 --- a/apps/ledger-live-desktop/src/renderer/components/FirebaseRemoteConfig.tsx +++ b/apps/ledger-live-desktop/src/renderer/components/FirebaseRemoteConfig.tsx @@ -1,8 +1,8 @@ import React, { ReactNode, useContext, useEffect, useState } from "react"; import { initializeApp } from "firebase/app"; import { getRemoteConfig, fetchAndActivate, RemoteConfig } from "firebase/remote-config"; -import { defaultFeatures } from "@ledgerhq/live-common/lib/featureFlags"; -import { DefaultFeatures } from "@ledgerhq/live-common/lib/types"; +import { defaultFeatures } from "@ledgerhq/live-common/featureFlags/index"; +import { DefaultFeatures } from "@ledgerhq/live-common/types/index"; import { reduce, snakeCase } from "lodash"; import { getFirebaseConfig } from "~/firebase-setup"; diff --git a/apps/ledger-live-desktop/src/renderer/components/FirmwareUpdateBanner.js b/apps/ledger-live-desktop/src/renderer/components/FirmwareUpdateBanner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/FirmwareUpdateBanner.js rename to apps/ledger-live-desktop/src/renderer/components/FirmwareUpdateBanner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/FlashMCU.js b/apps/ledger-live-desktop/src/renderer/components/FlashMCU.js deleted file mode 100644 index 11b32a1ac2eb..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/FlashMCU.js +++ /dev/null @@ -1,125 +0,0 @@ -// @flow - -import React from "react"; -import styled from "styled-components"; -import { withTranslation, Trans } from "react-i18next"; -import type { DeviceModelId } from "@ledgerhq/devices"; -import { bootloader, bootloaderMode } from "@ledgerhq/live-common/lib/deviceWordings"; -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import Interactions from "~/renderer/icons/device/interactions"; - -const Bullet = styled.span` - font-weight: 600; - color: ${p => p.theme.colors.palette.text.shade100}; -`; - -const Separator = styled(Box).attrs(() => ({ - color: "palette.divider", -}))` - height: 1px; - width: 100%; - background-color: currentColor; -`; - -type Props = { - deviceModelId?: DeviceModelId, -}; - -const FlashMCUNanosLocal = ({ deviceModelId }: Props) => ( - <> - - - {"1. "} - - - - - - - - - - {"2. "} - {deviceModelId === "nanoX" ? ( - - {"text"} - - {bootloaderMode} - - {"text"} - - ) : ( - - {"text"} - - {bootloader} - - {"text"} - - )} - - - - - - -); - -const FlashMCUNanos = React.memo(FlashMCUNanosLocal); - -const Container = styled(Box)` - max-width: 50%; - display: flex; - flex: 1; - justify-content: space-between; - align-items: center; -`; - -const FlashMCUBlueLocal = ({ deviceModelId }: Props) => ( - <> - - - - {"1. "} - - - - - - - - - {"2. "} - - - - - - - - -); - -const FlashMCUBlue = React.memo(FlashMCUBlueLocal); - -const FlashMCU = (props: Props) => - props.deviceModelId === "blue" ? : ; - -export default withTranslation()(FlashMCU); diff --git a/apps/ledger-live-desktop/src/renderer/components/FlashMCU.jsx b/apps/ledger-live-desktop/src/renderer/components/FlashMCU.jsx new file mode 100644 index 000000000000..81e192ddce93 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/FlashMCU.jsx @@ -0,0 +1,125 @@ +// @flow + +import React from "react"; +import styled from "styled-components"; +import { withTranslation, Trans } from "react-i18next"; +import type { DeviceModelId } from "@ledgerhq/devices"; +import { bootloader, bootloaderMode } from "@ledgerhq/live-common/deviceWordings"; +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import Interactions from "~/renderer/icons/device/interactions"; + +const Bullet = styled.span` + font-weight: 600; + color: ${p => p.theme.colors.palette.text.shade100}; +`; + +const Separator = styled(Box).attrs(() => ({ + color: "palette.divider", +}))` + height: 1px; + width: 100%; + background-color: currentColor; +`; + +type Props = { + deviceModelId?: DeviceModelId, +}; + +const FlashMCUNanosLocal = ({ deviceModelId }: Props) => ( + <> + + + {"1. "} + + + + + + + + + + {"2. "} + {deviceModelId === "nanoX" ? ( + + {"text"} + + {bootloaderMode} + + {"text"} + + ) : ( + + {"text"} + + {bootloader} + + {"text"} + + )} + + + + + + +); + +const FlashMCUNanos = React.memo(FlashMCUNanosLocal); + +const Container = styled(Box)` + max-width: 50%; + display: flex; + flex: 1; + justify-content: space-between; + align-items: center; +`; + +const FlashMCUBlueLocal = ({ deviceModelId }: Props) => ( + <> + + + + {"1. "} + + + + + + + + + {"2. "} + + + + + + + + +); + +const FlashMCUBlue = React.memo(FlashMCUBlueLocal); + +const FlashMCU = (props: Props) => + props.deviceModelId === "blue" ? : ; + +export default withTranslation()(FlashMCU); diff --git a/apps/ledger-live-desktop/src/renderer/components/FlipTicker.js b/apps/ledger-live-desktop/src/renderer/components/FlipTicker.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/FlipTicker.js rename to apps/ledger-live-desktop/src/renderer/components/FlipTicker.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/FormattedVal.js b/apps/ledger-live-desktop/src/renderer/components/FormattedVal.js deleted file mode 100644 index 0b80390ae806..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/FormattedVal.js +++ /dev/null @@ -1,173 +0,0 @@ -// @flow - -import { BigNumber } from "bignumber.js"; -import invariant from "invariant"; -import React from "react"; -import styled from "styled-components"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import type { Unit } from "@ledgerhq/live-common/lib/types"; -import { formatCurrencyUnit } from "@ledgerhq/live-common/lib/currencies"; -import { - marketIndicatorSelector, - localeSelector, - discreetModeSelector, -} from "~/renderer/reducers/settings"; -import { getMarketColor } from "~/renderer/styles/helpers"; -import Box from "~/renderer/components/Box"; -import FlipTicker from "~/renderer/components/FlipTicker"; -import IconBottom from "~/renderer/icons/ArrowDownRight"; -import IconTop from "~/renderer/icons/ArrowUpRight"; -import Ellipsis from "~/renderer/components/Ellipsis"; - -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const T: ThemedComponent<{ color?: string, inline?: boolean, ff?: string }> = styled(Box).attrs( - p => ({ - ff: p.ff || "Inter|Medium", - horizontal: true, - color: p.color, - }), -)` - white-space: pre; - text-overflow: ellipsis; - display: ${p => (p.inline ? "inline-block" : "block")}; - flex-shrink: ${p => (p.noShrink ? "0" : "1")}; - width: ${p => (p.inline ? "" : "100%")}; - overflow: hidden; -`; - -const I = ({ color, children }: { color?: string, children: any }) => ( - {children} -); - -I.defaultProps = { - color: undefined, -}; - -type OwnProps = { - unit?: Unit, - val: BigNumber | number, - alwaysShowSign?: boolean, - showCode?: boolean, - withIcon?: boolean, - color?: string, - animateTicker?: boolean, - disableRounding?: boolean, - isPercent?: boolean, - subMagnitude?: number, - prefix?: string, - ellipsis?: boolean, - suffix?: string, - showAllDigits?: boolean, - alwaysShowValue?: boolean, // overrides discreet mode -}; - -const mapStateToProps = createStructuredSelector({ - marketIndicator: marketIndicatorSelector, - discreet: discreetModeSelector, - locale: localeSelector, -}); - -type Props = OwnProps & { - marketIndicator: string, - discreet: boolean, - locale: string, -}; - -function FormattedVal(props: Props) { - const { - animateTicker, - disableRounding, - unit, - isPercent, - alwaysShowSign, - showCode, - withIcon, - locale, - marketIndicator, - color, - ellipsis, - subMagnitude, - prefix, - suffix, - showAllDigits, - alwaysShowValue, - discreet, - ...p - } = props; - const valProp = props.val; - let val: BigNumber = valProp instanceof BigNumber ? valProp : BigNumber(valProp); - - invariant(val, "FormattedVal require a `val` prop. Received `undefined`"); - - const isZero = val.isZero(); - const isNegative = val.isNegative() && !isZero; - - let text = ""; - - if (isPercent) { - // FIXME move out the % feature of this component... totally unrelated to currency & annoying for flow type. - text = `${alwaysShowSign ? (isNegative ? "- " : "+ ") : ""}${(isNegative - ? val.negated() - : val - ).toString()} %`; - } else { - invariant(unit, "FormattedVal require a `unit` prop. Received `undefined`"); - - if (withIcon && isNegative) { - val = val.negated(); - } - - text = formatCurrencyUnit(unit, val, { - alwaysShowSign, - disableRounding, - showCode, - locale, - subMagnitude, - discreet: alwaysShowValue ? false : discreet, - showAllDigits, - }); - } - - if (prefix) text = prefix + text; - if (suffix) text += suffix; - - if (animateTicker) { - text = ; - } else if (ellipsis) { - text = {text}; - } - - const marketColor = getMarketColor({ - marketIndicator, - isNegative, - }); - - return ( - - {withIcon ? ( - - - - {isNegative ? : isZero ? null : } - - - - {text} - - - ) : ( - text - )} - - ); -} - -FormattedVal.defaultProps = { - subMagnitude: 0, -}; - -const m: React$ComponentType = connect(mapStateToProps)(FormattedVal); - -export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/FormattedVal.jsx b/apps/ledger-live-desktop/src/renderer/components/FormattedVal.jsx new file mode 100644 index 000000000000..f312dc578d01 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/FormattedVal.jsx @@ -0,0 +1,173 @@ +// @flow + +import { BigNumber } from "bignumber.js"; +import invariant from "invariant"; +import React from "react"; +import styled from "styled-components"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import type { Unit } from "@ledgerhq/live-common/types/index"; +import { formatCurrencyUnit } from "@ledgerhq/live-common/currencies/index"; +import { + marketIndicatorSelector, + localeSelector, + discreetModeSelector, +} from "~/renderer/reducers/settings"; +import { getMarketColor } from "~/renderer/styles/helpers"; +import Box from "~/renderer/components/Box"; +import FlipTicker from "~/renderer/components/FlipTicker"; +import IconBottom from "~/renderer/icons/ArrowDownRight"; +import IconTop from "~/renderer/icons/ArrowUpRight"; +import Ellipsis from "~/renderer/components/Ellipsis"; + +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const T: ThemedComponent<{ color?: string, inline?: boolean, ff?: string }> = styled(Box).attrs( + p => ({ + ff: p.ff || "Inter|Medium", + horizontal: true, + color: p.color, + }), +)` + white-space: pre; + text-overflow: ellipsis; + display: ${p => (p.inline ? "inline-block" : "block")}; + flex-shrink: ${p => (p.noShrink ? "0" : "1")}; + width: ${p => (p.inline ? "" : "100%")}; + overflow: hidden; +`; + +const I = ({ color, children }: { color?: string, children: any }) => ( + {children} +); + +I.defaultProps = { + color: undefined, +}; + +type OwnProps = { + unit?: Unit, + val: BigNumber | number, + alwaysShowSign?: boolean, + showCode?: boolean, + withIcon?: boolean, + color?: string, + animateTicker?: boolean, + disableRounding?: boolean, + isPercent?: boolean, + subMagnitude?: number, + prefix?: string, + ellipsis?: boolean, + suffix?: string, + showAllDigits?: boolean, + alwaysShowValue?: boolean, // overrides discreet mode +}; + +const mapStateToProps = createStructuredSelector({ + marketIndicator: marketIndicatorSelector, + discreet: discreetModeSelector, + locale: localeSelector, +}); + +type Props = OwnProps & { + marketIndicator: string, + discreet: boolean, + locale: string, +}; + +function FormattedVal(props: Props) { + const { + animateTicker, + disableRounding, + unit, + isPercent, + alwaysShowSign, + showCode, + withIcon, + locale, + marketIndicator, + color, + ellipsis, + subMagnitude, + prefix, + suffix, + showAllDigits, + alwaysShowValue, + discreet, + ...p + } = props; + const valProp = props.val; + let val: BigNumber = valProp instanceof BigNumber ? valProp : BigNumber(valProp); + + invariant(val, "FormattedVal require a `val` prop. Received `undefined`"); + + const isZero = val.isZero(); + const isNegative = val.isNegative() && !isZero; + + let text = ""; + + if (isPercent) { + // FIXME move out the % feature of this component... totally unrelated to currency & annoying for flow type. + text = `${alwaysShowSign ? (isNegative ? "- " : "+ ") : ""}${(isNegative + ? val.negated() + : val + ).toString()} %`; + } else { + invariant(unit, "FormattedVal require a `unit` prop. Received `undefined`"); + + if (withIcon && isNegative) { + val = val.negated(); + } + + text = formatCurrencyUnit(unit, val, { + alwaysShowSign, + disableRounding, + showCode, + locale, + subMagnitude, + discreet: alwaysShowValue ? false : discreet, + showAllDigits, + }); + } + + if (prefix) text = prefix + text; + if (suffix) text += suffix; + + if (animateTicker) { + text = ; + } else if (ellipsis) { + text = {text}; + } + + const marketColor = getMarketColor({ + marketIndicator, + isNegative, + }); + + return ( + + {withIcon ? ( + + + + {isNegative ? : isZero ? null : } + + + + {text} + + + ) : ( + text + )} + + ); +} + +FormattedVal.defaultProps = { + subMagnitude: 0, +}; + +const m: React$ComponentType = connect(mapStateToProps)(FormattedVal); + +export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.js b/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.js deleted file mode 100644 index 3ff98d2cfa8f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.js +++ /dev/null @@ -1,169 +0,0 @@ -// @flow - -import React, { useState, useEffect, useCallback } from "react"; -import { warnings } from "@ledgerhq/live-common/lib/api/socket"; -import styled from "styled-components"; -import { useTranslation } from "react-i18next"; -import uniqueId from "lodash/uniqueId"; - -import { SHOW_MOCK_HSMWARNINGS } from "~/config/constants"; -import { urls } from "~/config/urls"; - -import { openURL } from "~/renderer/linking"; - -import IconCross from "~/renderer/icons/Cross"; -import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; -import IconChevronRight from "~/renderer/icons/ChevronRightSmall"; - -import Box from "~/renderer/components/Box"; - -type HSMStatus = { - id: string, - message: string, -}; - -const CloseIconContainer = styled.div` - position: absolute; - top: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - padding: 10px; - border-bottom-left-radius: 4px; -`; - -const CloseIcon = (props: *) => ( - - - -); - -const UnderlinedLink = styled.span` - border-bottom: 1px solid transparent; - &:hover { - border-bottom-color: ${p => p.theme.colors.palette.background.paper}; - } -`; - -const Banner = styled(Box)` - background: ${p => p.theme.colors.orange}; - overflow: hidden; - border-radius: 4px; - font-size: 13px; - color: ${p => p.theme.colors.palette.background.paper}; - font-weight: bold; - padding: 17px 30px 15px 15px; - width: 350px; -`; - -type BannerItemLinkProps = { - onClick: void => *, -}; - -const BannerItemLink = ({ onClick }: BannerItemLinkProps) => { - const { t } = useTranslation(); - - return ( - - - {t("common.learnMore")} - - ); -}; - -type BannerItemProps = { - item: HSMStatus, - onItemDismiss: HSMStatus => void, -}; - -const BannerItem = ({ item, onItemDismiss }: BannerItemProps) => { - const { t } = useTranslation(); - - const onLinkClick = useCallback(() => openURL(urls.contactSupport), []); - const dismiss = useCallback(() => { - onItemDismiss(item); - }, [item, onItemDismiss]); - - return ( - - - - - - {item.message} - - - - - ); -}; - -const initialState: HSMStatus[] = SHOW_MOCK_HSMWARNINGS - ? [ - { - id: "mock1", - message: "Lorem Ipsum dolor sit amet #1", - }, - ] - : []; - -const styles = { - container: { - position: "fixed", - left: 32, - bottom: 32, - zIndex: 100, - }, - message: { - marginTop: -3, - }, -}; - -const HSMStatusBanner = () => { - const [pendingMessages, setPendingMessages] = useState(initialState); - const { t } = useTranslation(); - - const setNewMessage = useCallback( - (message: string) => { - setPendingMessages([...pendingMessages, { id: uniqueId(), message }]); - }, - [pendingMessages, setPendingMessages], - ); - - const dismissItem = useCallback( - (dismissedItem: HSMStatus) => { - setPendingMessages(pendingMessages.filter(item => item.id !== dismissItem.id)); - }, - [pendingMessages, setPendingMessages], - ); - - useEffect(() => { - const sub = warnings.subscribe({ - next: message => { - setNewMessage(message); - }, - }); - - return () => sub.unsubscribe(); - }, [setNewMessage]); - - const item = pendingMessages.length ? pendingMessages[0] : null; - - return item ? ( - - - - ) : null; -}; - -export default HSMStatusBanner; diff --git a/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.jsx b/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.jsx new file mode 100644 index 000000000000..49b8a4d09413 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/HSMStatusBanner.jsx @@ -0,0 +1,169 @@ +// @flow + +import React, { useState, useEffect, useCallback } from "react"; +import { warnings } from "@ledgerhq/live-common/api/socket"; +import styled from "styled-components"; +import { useTranslation } from "react-i18next"; +import uniqueId from "lodash/uniqueId"; + +import { SHOW_MOCK_HSMWARNINGS } from "~/config/constants"; +import { urls } from "~/config/urls"; + +import { openURL } from "~/renderer/linking"; + +import IconCross from "~/renderer/icons/Cross"; +import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; +import IconChevronRight from "~/renderer/icons/ChevronRightSmall"; + +import Box from "~/renderer/components/Box"; + +type HSMStatus = { + id: string, + message: string, +}; + +const CloseIconContainer = styled.div` + position: absolute; + top: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 10px; + border-bottom-left-radius: 4px; +`; + +const CloseIcon = (props: *) => ( + + + +); + +const UnderlinedLink = styled.span` + border-bottom: 1px solid transparent; + &:hover { + border-bottom-color: ${p => p.theme.colors.palette.background.paper}; + } +`; + +const Banner = styled(Box)` + background: ${p => p.theme.colors.orange}; + overflow: hidden; + border-radius: 4px; + font-size: 13px; + color: ${p => p.theme.colors.palette.background.paper}; + font-weight: bold; + padding: 17px 30px 15px 15px; + width: 350px; +`; + +type BannerItemLinkProps = { + onClick: void => *, +}; + +const BannerItemLink = ({ onClick }: BannerItemLinkProps) => { + const { t } = useTranslation(); + + return ( + + + {t("common.learnMore")} + + ); +}; + +type BannerItemProps = { + item: HSMStatus, + onItemDismiss: HSMStatus => void, +}; + +const BannerItem = ({ item, onItemDismiss }: BannerItemProps) => { + const { t } = useTranslation(); + + const onLinkClick = useCallback(() => openURL(urls.contactSupport), []); + const dismiss = useCallback(() => { + onItemDismiss(item); + }, [item, onItemDismiss]); + + return ( + + + + + + {item.message} + + + + + ); +}; + +const initialState: HSMStatus[] = SHOW_MOCK_HSMWARNINGS + ? [ + { + id: "mock1", + message: "Lorem Ipsum dolor sit amet #1", + }, + ] + : []; + +const styles = { + container: { + position: "fixed", + left: 32, + bottom: 32, + zIndex: 100, + }, + message: { + marginTop: -3, + }, +}; + +const HSMStatusBanner = () => { + const [pendingMessages, setPendingMessages] = useState(initialState); + const { t } = useTranslation(); + + const setNewMessage = useCallback( + (message: string) => { + setPendingMessages([...pendingMessages, { id: uniqueId(), message }]); + }, + [pendingMessages, setPendingMessages], + ); + + const dismissItem = useCallback( + (dismissedItem: HSMStatus) => { + setPendingMessages(pendingMessages.filter(item => item.id !== dismissItem.id)); + }, + [pendingMessages, setPendingMessages], + ); + + useEffect(() => { + const sub = warnings.subscribe({ + next: message => { + setNewMessage(message); + }, + }); + + return () => sub.unsubscribe(); + }, [setNewMessage]); + + const item = pendingMessages.length ? pendingMessages[0] : null; + + return item ? ( + + + + ) : null; +}; + +export default HSMStatusBanner; diff --git a/apps/ledger-live-desktop/src/renderer/components/Image.js b/apps/ledger-live-desktop/src/renderer/components/Image.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Image.js rename to apps/ledger-live-desktop/src/renderer/components/Image.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/InfoDisplay.js b/apps/ledger-live-desktop/src/renderer/components/InfoDisplay.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/InfoDisplay.js rename to apps/ledger-live-desktop/src/renderer/components/InfoDisplay.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Input.js b/apps/ledger-live-desktop/src/renderer/components/Input.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Input.js rename to apps/ledger-live-desktop/src/renderer/components/Input.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/InputCurrency.js b/apps/ledger-live-desktop/src/renderer/components/InputCurrency.js deleted file mode 100644 index 044ca3169761..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/InputCurrency.js +++ /dev/null @@ -1,249 +0,0 @@ -// @flow - -import React, { PureComponent, type ElementRef } from "react"; -import { BigNumber } from "bignumber.js"; -import { uncontrollable } from "uncontrollable"; -import styled from "styled-components"; -import { connect } from "react-redux"; -import { createStructuredSelector } from "reselect"; -import { localeSelector } from "~/renderer/reducers/settings"; -import { formatCurrencyUnit, sanitizeValueString } from "@ledgerhq/live-common/lib/currencies"; -import noop from "lodash/noop"; -import Box from "~/renderer/components/Box"; -import Input from "~/renderer/components/Input"; -import Select from "~/renderer/components/Select"; -import type { Unit } from "@ledgerhq/live-common/lib/types"; - -const unitGetOptionValue = unit => unit.magnitude; - -function format(unit: Unit, value: BigNumber, { locale, isFocused, showAllDigits, subMagnitude }) { - return formatCurrencyUnit(unit, value, { - locale, - useGrouping: !isFocused, - disableRounding: true, - showAllDigits: !!showAllDigits && !isFocused, - subMagnitude: value.isLessThan(1) ? subMagnitude : 0, - }); -} - -const Currencies = styled(Box)` - top: -1px; - right: -1px; - width: 100px; -`; - -function stopPropagation(e) { - e.stopPropagation(); -} - -type OwnProps = { - onChangeFocus?: boolean => void, - onChange: (BigNumber, Unit) => void, // FIXME Unit shouldn't be provided (this is not "standard" onChange) - onChangeUnit?: Unit => void, - renderRight: any, - defaultUnit?: Unit, - unit?: Unit, - units?: Unit[], - value: ?BigNumber, - showAllDigits?: boolean, - subMagnitude?: number, - allowZero?: boolean, - disabled?: boolean, - autoFocus?: boolean, - decimals?: number, -}; - -type Props = { - ...OwnProps, - unit: Unit, - units: Unit[], - onChangeFocus: boolean => void, - onChangeUnit: Unit => void, - locale: string, - forwardedRef: ?ElementRef, - placeholder?: string, - loading: boolean, -}; - -type State = { - isFocused: boolean, - displayValue: string, - rawValue: string, -}; - -class InputCurrency extends PureComponent { - static defaultProps = { - onChangeFocus: noop, - onChange: noop, - renderRight: null, - units: [], - value: null, - showAllDigits: false, - subMagnitude: 0, - allowZero: false, - autoFocus: false, - loading: false, - }; - - state = { - isFocused: false, - displayValue: "", - rawValue: "", - }; - - componentDidMount() { - this.syncInput({ isFocused: !!this.props.autoFocus }); - } - - // eslint-disable-next-line camelcase - UNSAFE_componentWillReceiveProps(nextProps: Props) { - const { locale, value, showAllDigits, unit } = this.props; - const needsToBeReformatted = - !this.state.isFocused && - (value !== nextProps.value || - showAllDigits !== nextProps.showAllDigits || - unit !== nextProps.unit); - - if (needsToBeReformatted) { - const { isFocused } = this.state; - this.setState({ - rawValue: "", - displayValue: - !nextProps.value || nextProps.value.isNaN() || nextProps.value.isZero() - ? "" - : format(nextProps.unit, nextProps.value, { - locale, - isFocused, - showAllDigits: nextProps.showAllDigits, - subMagnitude: nextProps.subMagnitude, - }), - }); - } - } - - handleChange = (val: string) => { - const { onChange, unit, value, locale, decimals } = this.props; - const v = decimals === 0 ? val.replace(/[.,]/g, "") : val; - const r = sanitizeValueString(unit, v, locale); - const satoshiValue = BigNumber(r.value); - if (!value || !value.isEqualTo(satoshiValue)) { - onChange(satoshiValue, unit); - } - this.setState({ rawValue: v, displayValue: r.display }); - }; - - handleBlur = () => { - this.syncInput({ isFocused: false }); - this.props.onChangeFocus(false); - }; - - handleFocus = () => { - this.syncInput({ isFocused: true }); - this.props.onChangeFocus(true); - }; - - syncInput = ({ isFocused }: { isFocused: boolean }) => { - const { - showAllDigits, - subMagnitude, - unit, - allowZero, - locale, - value: fallbackValue, - } = this.props; - const { rawValue } = this.state; - const value = rawValue - ? BigNumber(sanitizeValueString(unit, rawValue, locale).value) - : fallbackValue || ""; - - this.setState({ - isFocused, - displayValue: - !value || (value.isZero() && !allowZero) - ? "" - : format(unit, value, { locale, isFocused, showAllDigits, subMagnitude }), - }); - }; - - renderOption = item => item.data.code; - - renderValue = item => item.data.code; - - renderListUnits = () => { - const { units, onChangeUnit, unit } = this.props; - const { isFocused } = this.state; - const avoidEmptyValue = value => value && onChangeUnit(value); - if (units.length <= 1) { - return null; - } - - return ( - - - ); - } -} - -const Connected = uncontrollable( - connect( - createStructuredSelector({ - locale: localeSelector, - }), - )(InputCurrency), - { - unit: "onChangeUnit", - }, -); - -const m: React$ComponentType = React.forwardRef(function InputCurrency(props, ref) { - return ; -}); - -export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/InputCurrency.jsx b/apps/ledger-live-desktop/src/renderer/components/InputCurrency.jsx new file mode 100644 index 000000000000..72ccc9dba679 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/InputCurrency.jsx @@ -0,0 +1,249 @@ +// @flow + +import React, { PureComponent, type ElementRef } from "react"; +import { BigNumber } from "bignumber.js"; +import { uncontrollable } from "uncontrollable"; +import styled from "styled-components"; +import { connect } from "react-redux"; +import { createStructuredSelector } from "reselect"; +import { localeSelector } from "~/renderer/reducers/settings"; +import { formatCurrencyUnit, sanitizeValueString } from "@ledgerhq/live-common/currencies/index"; +import noop from "lodash/noop"; +import Box from "~/renderer/components/Box"; +import Input from "~/renderer/components/Input"; +import Select from "~/renderer/components/Select"; +import type { Unit } from "@ledgerhq/live-common/types/index"; + +const unitGetOptionValue = unit => unit.magnitude; + +function format(unit: Unit, value: BigNumber, { locale, isFocused, showAllDigits, subMagnitude }) { + return formatCurrencyUnit(unit, value, { + locale, + useGrouping: !isFocused, + disableRounding: true, + showAllDigits: !!showAllDigits && !isFocused, + subMagnitude: value.isLessThan(1) ? subMagnitude : 0, + }); +} + +const Currencies = styled(Box)` + top: -1px; + right: -1px; + width: 100px; +`; + +function stopPropagation(e) { + e.stopPropagation(); +} + +type OwnProps = { + onChangeFocus?: boolean => void, + onChange: (BigNumber, Unit) => void, // FIXME Unit shouldn't be provided (this is not "standard" onChange) + onChangeUnit?: Unit => void, + renderRight: any, + defaultUnit?: Unit, + unit?: Unit, + units?: Unit[], + value: ?BigNumber, + showAllDigits?: boolean, + subMagnitude?: number, + allowZero?: boolean, + disabled?: boolean, + autoFocus?: boolean, + decimals?: number, +}; + +type Props = { + ...OwnProps, + unit: Unit, + units: Unit[], + onChangeFocus: boolean => void, + onChangeUnit: Unit => void, + locale: string, + forwardedRef: ?ElementRef, + placeholder?: string, + loading: boolean, +}; + +type State = { + isFocused: boolean, + displayValue: string, + rawValue: string, +}; + +class InputCurrency extends PureComponent { + static defaultProps = { + onChangeFocus: noop, + onChange: noop, + renderRight: null, + units: [], + value: null, + showAllDigits: false, + subMagnitude: 0, + allowZero: false, + autoFocus: false, + loading: false, + }; + + state = { + isFocused: false, + displayValue: "", + rawValue: "", + }; + + componentDidMount() { + this.syncInput({ isFocused: !!this.props.autoFocus }); + } + + // eslint-disable-next-line camelcase + UNSAFE_componentWillReceiveProps(nextProps: Props) { + const { locale, value, showAllDigits, unit } = this.props; + const needsToBeReformatted = + !this.state.isFocused && + (value !== nextProps.value || + showAllDigits !== nextProps.showAllDigits || + unit !== nextProps.unit); + + if (needsToBeReformatted) { + const { isFocused } = this.state; + this.setState({ + rawValue: "", + displayValue: + !nextProps.value || nextProps.value.isNaN() || nextProps.value.isZero() + ? "" + : format(nextProps.unit, nextProps.value, { + locale, + isFocused, + showAllDigits: nextProps.showAllDigits, + subMagnitude: nextProps.subMagnitude, + }), + }); + } + } + + handleChange = (val: string) => { + const { onChange, unit, value, locale, decimals } = this.props; + const v = decimals === 0 ? val.replace(/[.,]/g, "") : val; + const r = sanitizeValueString(unit, v, locale); + const satoshiValue = BigNumber(r.value); + if (!value || !value.isEqualTo(satoshiValue)) { + onChange(satoshiValue, unit); + } + this.setState({ rawValue: v, displayValue: r.display }); + }; + + handleBlur = () => { + this.syncInput({ isFocused: false }); + this.props.onChangeFocus(false); + }; + + handleFocus = () => { + this.syncInput({ isFocused: true }); + this.props.onChangeFocus(true); + }; + + syncInput = ({ isFocused }: { isFocused: boolean }) => { + const { + showAllDigits, + subMagnitude, + unit, + allowZero, + locale, + value: fallbackValue, + } = this.props; + const { rawValue } = this.state; + const value = rawValue + ? BigNumber(sanitizeValueString(unit, rawValue, locale).value) + : fallbackValue || ""; + + this.setState({ + isFocused, + displayValue: + !value || (value.isZero() && !allowZero) + ? "" + : format(unit, value, { locale, isFocused, showAllDigits, subMagnitude }), + }); + }; + + renderOption = item => item.data.code; + + renderValue = item => item.data.code; + + renderListUnits = () => { + const { units, onChangeUnit, unit } = this.props; + const { isFocused } = this.state; + const avoidEmptyValue = value => value && onChangeUnit(value); + if (units.length <= 1) { + return null; + } + + return ( + + + ); + } +} + +const Connected = uncontrollable( + connect( + createStructuredSelector({ + locale: localeSelector, + }), + )(InputCurrency), + { + unit: "onChangeUnit", + }, +); + +const m: React$ComponentType = React.forwardRef(function InputCurrency(props, ref) { + return ; +}); + +export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/InputPassword.js b/apps/ledger-live-desktop/src/renderer/components/InputPassword.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/InputPassword.js rename to apps/ledger-live-desktop/src/renderer/components/InputPassword.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/IsUnlocked.js b/apps/ledger-live-desktop/src/renderer/components/IsUnlocked.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/IsUnlocked.js rename to apps/ledger-live-desktop/src/renderer/components/IsUnlocked.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LabelInfoTooltip.js b/apps/ledger-live-desktop/src/renderer/components/LabelInfoTooltip.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LabelInfoTooltip.js rename to apps/ledger-live-desktop/src/renderer/components/LabelInfoTooltip.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LabelWithExternalIcon.js b/apps/ledger-live-desktop/src/renderer/components/LabelWithExternalIcon.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LabelWithExternalIcon.js rename to apps/ledger-live-desktop/src/renderer/components/LabelWithExternalIcon.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LedgerLiveLogo.js b/apps/ledger-live-desktop/src/renderer/components/LedgerLiveLogo.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LedgerLiveLogo.js rename to apps/ledger-live-desktop/src/renderer/components/LedgerLiveLogo.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LinkHelp.js b/apps/ledger-live-desktop/src/renderer/components/LinkHelp.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LinkHelp.js rename to apps/ledger-live-desktop/src/renderer/components/LinkHelp.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LinkShowQRCode.js b/apps/ledger-live-desktop/src/renderer/components/LinkShowQRCode.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LinkShowQRCode.js rename to apps/ledger-live-desktop/src/renderer/components/LinkShowQRCode.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/LinkWithExternalIcon.js b/apps/ledger-live-desktop/src/renderer/components/LinkWithExternalIcon.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/LinkWithExternalIcon.js rename to apps/ledger-live-desktop/src/renderer/components/LinkWithExternalIcon.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/Hide.js b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/Hide.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/MainSideBar/Hide.js rename to apps/ledger-live-desktop/src/renderer/components/MainSideBar/Hide.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/TopGradient.js b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/TopGradient.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/MainSideBar/TopGradient.js rename to apps/ledger-live-desktop/src/renderer/components/MainSideBar/TopGradient.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.js b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.js deleted file mode 100644 index 18b77b34be7a..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.js +++ /dev/null @@ -1,466 +0,0 @@ -// @flow -import React, { useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import { Link, useHistory, useLocation } from "react-router-dom"; -import { Transition } from "react-transition-group"; -import styled from "styled-components"; -import { useManagerBlueDot } from "@ledgerhq/live-common/lib/manager/hooks"; -import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/lib/platform/providers/RemoteLiveAppProvider"; -import { FeatureToggle } from "@ledgerhq/live-common/lib/featureFlags"; -import { Icons } from "@ledgerhq/react-ui"; - -import { - accountsSelector, - starredAccountsSelector, - hasLendEnabledAccountsSelector, -} from "~/renderer/reducers/accounts"; -import { sidebarCollapsedSelector, lastSeenDeviceSelector } from "~/renderer/reducers/settings"; -import { isNavigationLocked } from "~/renderer/reducers/application"; - -import { openModal } from "~/renderer/actions/modals"; -import { setFirstTimeLend, setSidebarCollapsed } from "~/renderer/actions/settings"; - -import useExperimental from "~/renderer/hooks/useExperimental"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; - -import { darken, rgba } from "~/renderer/styles/helpers"; - -import IconCard from "~/renderer/icons/Card"; -import IconManager from "~/renderer/icons/Manager"; -import IconWallet from "~/renderer/icons/Wallet"; -import IconPortfolio from "~/renderer/icons/Portfolio"; -import IconApps from "~/renderer/icons/Apps"; -import IconReceive from "~/renderer/icons/Receive"; -import IconSend from "~/renderer/icons/Send"; -import IconExchange from "~/renderer/icons/Exchange"; -import IconChevron from "~/renderer/icons/ChevronRightSmall"; -import IconLending from "~/renderer/icons/Graph"; -import IconExperimental from "~/renderer/icons/Experimental"; -import IconSwap from "~/renderer/icons/Swap"; -import IconMarket from "~/renderer/icons/ChartLine"; - -import { SideBarList, SideBarListItem } from "~/renderer/components/SideBar"; -import Box from "~/renderer/components/Box"; -import Space from "~/renderer/components/Space"; -import UpdateDot from "~/renderer/components/Updater/UpdateDot"; -import { Dot } from "~/renderer/components/Dot"; -import Stars from "~/renderer/components/Stars"; -import useEnv from "~/renderer/hooks/useEnv"; - -import { CARD_APP_ID } from "~/renderer/screens/card"; - -import TopGradient from "./TopGradient"; -import Hide from "./Hide"; - -const MAIN_SIDEBAR_WIDTH = 230; - -const TagText = styled.div.attrs(p => ({ - style: { - opacity: p.collapsed ? 1 : 0, - }, -}))` - margin-left: ${p => p.theme.space[3]}px; - transition: opacity 0.2s; -`; - -const Tag = styled(Link)` - display: flex; - justify-self: flex-end; - justify-content: flex-start; - align-items: center; - font-family: "Inter"; - font-weight: bold; - font-size: 10px; - padding: 2px ${p => p.theme.space[3] - 1}px; - min-height: 32px; - border-radius: 4px; - margin: ${p => p.theme.space[2]}px ${p => p.theme.space[3]}px; - color: ${p => p.theme.colors.palette.text.shade100}; - background-color: ${p => p.theme.colors.palette.background.default}; - text-decoration: none; - cursor: pointer; - border: solid 1px rgba(0, 0, 0, 0); - - &:hover { - background-color: ${p => darken(p.theme.colors.palette.action.hover, 0.05)}; - border-color: ${p => p.theme.colors.wallet}; - } -`; - -const collapserSize = 24; -const collapsedWidth = 15 * 4 + 16; // 15 * 4 margins + 16 icon size - -const Collapser = styled(Box).attrs(() => ({ - alignItems: "center", - justifyContent: "center", -}))` - position: absolute; - top: ${58 - collapserSize / 2}px; - left: ${p => (p.collapsed ? collapsedWidth : MAIN_SIDEBAR_WIDTH) - collapserSize / 2}px; - - width: ${collapserSize}px; - height: ${collapserSize}px; - - cursor: pointer; - border-radius: 50%; - background: ${p => p.theme.colors.palette.background.paper}; - color: ${p => p.theme.colors.palette.text.shade80}; - border-color: ${p => p.theme.colors.palette.divider}; - box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); - border: 1px solid; - transition: all 0.5s; - z-index: 100; - - &:hover { - border-color: ${p => p.theme.colors.wallet}; - color: ${p => p.theme.colors.wallet}; - background: ${p => rgba(p.theme.colors.wallet, 0.1)}; - } - - & > * { - transform: ${p => (p.collapsed ? "" : "rotate(180deg)")}; - margin-left: ${p => (p.collapsed ? "" : "-2px")}; - - transition: transform 0.5s; - } -`; - -const Separator = styled(Box).attrs(() => ({ - mx: 4, -}))` - height: 1px; - background: ${p => p.theme.colors.palette.divider}; -`; - -const sideBarTransitionStyles = { - entering: { flexBasis: MAIN_SIDEBAR_WIDTH }, - entered: { flexBasis: MAIN_SIDEBAR_WIDTH }, - exiting: { flexBasis: collapsedWidth }, - exited: { flexBasis: collapsedWidth }, -}; - -const enableTransitions = () => - document.body && - setTimeout( - () => document.body && document.body.classList.remove("stop-container-transition"), - 500, - ); -const disableTransitions = () => - document.body && document.body.classList.add("stop-container-transition"); - -const sideBarTransitionSpeed = 500; - -const SideBar = styled(Box).attrs(() => ({ - relative: true, -}))` - flex: 0 0 auto; - width: auto; - background-color: ${p => p.theme.colors.palette.background.paper}; - transition: flex ${sideBarTransitionSpeed}ms; - will-change: flex; - transform: translate3d(0, 0, 10); - - & > ${Collapser} { - opacity: 0; - } - - &:hover { - > ${Collapser} { - opacity: 1; - } - } -`; - -const SideBarScrollContainer = styled(Box)` - overflow-y: scroll; - overflow-x: hidden; - - flex: 1; - - ::-webkit-scrollbar { - width: 0; - height: 0; - } -`; - -const TagContainer = ({ collapsed }: { collapsed: boolean }) => { - const isExperimental = useExperimental(); - const hasFullNodeConfigured = useEnv("SATSTACK"); // NB remove once full node is not experimental - - const { t } = useTranslation(); - - return isExperimental || hasFullNodeConfigured ? ( - setTrackingSource("sidebar")} - > - - {t("common.experimentalFeature")} - - ) : null; -}; - -const MainSideBar = () => { - const history = useHistory(); - const location = useLocation(); - const dispatch = useDispatch(); - const { t } = useTranslation(); - - const manifest = useRemoteLiveAppManifest(CARD_APP_ID); - const isCardDisabled = !manifest; - - /** redux navigation locked state */ - const navigationLocked = useSelector(isNavigationLocked); - const collapsed = useSelector(sidebarCollapsedSelector); - const lastSeenDevice = useSelector(lastSeenDeviceSelector); - const noAccounts = useSelector(accountsSelector).length === 0; - const hasStarredAccounts = useSelector(starredAccountsSelector).length > 0; - const displayBlueDot = useManagerBlueDot(lastSeenDevice); - const firstTimeLend = useSelector(state => state.settings.firstTimeLend); - - const lendingEnabled = useSelector(hasLendEnabledAccountsSelector); - - const handleCollapse = useCallback(() => { - dispatch(setSidebarCollapsed(!collapsed)); - }, [dispatch, collapsed]); - - const push = useCallback( - (pathname: string) => { - if (location.pathname === pathname) return; - setTrackingSource("sidebar"); - history.push({ pathname }); - }, - [history, location.pathname], - ); - - const handleClickCard = useCallback(() => { - push("/card"); - }, [push]); - - const handleClickLearn = useCallback(() => { - push("/learn"); - }, [push]); - - const handleClickDashboard = useCallback(() => { - push("/"); - }, [push]); - - const handleClickMarket = useCallback(() => { - push("/market"); - }, [push]); - - const handleClickManager = useCallback(() => { - push("/manager"); - }, [push]); - - const handleClickAccounts = useCallback(() => { - push("/accounts"); - }, [push]); - - const handleClickCatalog = useCallback(() => { - push("/platform"); - }, [push]); - - const handleClickExchange = useCallback(() => { - push("/exchange"); - }, [push]); - - const handleClickLend = useCallback(() => { - if (firstTimeLend) { - dispatch(setFirstTimeLend()); - } - push("/lend"); - }, [push, firstTimeLend, dispatch]); - - const handleClickSwap = useCallback(() => { - push("/swap"); - }, [push]); - - const maybeRedirectToAccounts = useCallback(() => { - return location.pathname === "/manager" && push("/accounts"); - }, [location.pathname, push]); - - const handleOpenSendModal = useCallback(() => { - maybeRedirectToAccounts(); - dispatch(openModal("MODAL_SEND")); - }, [dispatch, maybeRedirectToAccounts]); - - const handleOpenReceiveModal = useCallback(() => { - maybeRedirectToAccounts(); - dispatch(openModal("MODAL_RECEIVE")); - }, [dispatch, maybeRedirectToAccounts]); - - return ( - - {state => { - const secondAnim = !(state === "entered" && !collapsed); - return ( - - - - - - - - - } - collapsed={secondAnim} - /> - - - - - - - - - - - {lendingEnabled && ( - : null} - /> - )} - - : null} - collapsed={secondAnim} - /> - - - - - - - - - - - - - - - - ); - }} - - ); -}; - -export default MainSideBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.jsx b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.jsx new file mode 100644 index 000000000000..2df4ed288348 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/MainSideBar/index.jsx @@ -0,0 +1,466 @@ +// @flow +import React, { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { Link, useHistory, useLocation } from "react-router-dom"; +import { Transition } from "react-transition-group"; +import styled from "styled-components"; +import { useManagerBlueDot } from "@ledgerhq/live-common/manager/hooks"; +import { useRemoteLiveAppManifest } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index"; +import { FeatureToggle } from "@ledgerhq/live-common/featureFlags/index"; +import { Icons } from "@ledgerhq/react-ui"; + +import { + accountsSelector, + starredAccountsSelector, + hasLendEnabledAccountsSelector, +} from "~/renderer/reducers/accounts"; +import { sidebarCollapsedSelector, lastSeenDeviceSelector } from "~/renderer/reducers/settings"; +import { isNavigationLocked } from "~/renderer/reducers/application"; + +import { openModal } from "~/renderer/actions/modals"; +import { setFirstTimeLend, setSidebarCollapsed } from "~/renderer/actions/settings"; + +import useExperimental from "~/renderer/hooks/useExperimental"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; + +import { darken, rgba } from "~/renderer/styles/helpers"; + +import IconCard from "~/renderer/icons/Card"; +import IconManager from "~/renderer/icons/Manager"; +import IconWallet from "~/renderer/icons/Wallet"; +import IconPortfolio from "~/renderer/icons/Portfolio"; +import IconApps from "~/renderer/icons/Apps"; +import IconReceive from "~/renderer/icons/Receive"; +import IconSend from "~/renderer/icons/Send"; +import IconExchange from "~/renderer/icons/Exchange"; +import IconChevron from "~/renderer/icons/ChevronRightSmall"; +import IconLending from "~/renderer/icons/Graph"; +import IconExperimental from "~/renderer/icons/Experimental"; +import IconSwap from "~/renderer/icons/Swap"; +import IconMarket from "~/renderer/icons/ChartLine"; + +import { SideBarList, SideBarListItem } from "~/renderer/components/SideBar"; +import Box from "~/renderer/components/Box"; +import Space from "~/renderer/components/Space"; +import UpdateDot from "~/renderer/components/Updater/UpdateDot"; +import { Dot } from "~/renderer/components/Dot"; +import Stars from "~/renderer/components/Stars"; +import useEnv from "~/renderer/hooks/useEnv"; + +import { CARD_APP_ID } from "~/renderer/screens/card"; + +import TopGradient from "./TopGradient"; +import Hide from "./Hide"; + +const MAIN_SIDEBAR_WIDTH = 230; + +const TagText = styled.div.attrs(p => ({ + style: { + opacity: p.collapsed ? 1 : 0, + }, +}))` + margin-left: ${p => p.theme.space[3]}px; + transition: opacity 0.2s; +`; + +const Tag = styled(Link)` + display: flex; + justify-self: flex-end; + justify-content: flex-start; + align-items: center; + font-family: "Inter"; + font-weight: bold; + font-size: 10px; + padding: 2px ${p => p.theme.space[3] - 1}px; + min-height: 32px; + border-radius: 4px; + margin: ${p => p.theme.space[2]}px ${p => p.theme.space[3]}px; + color: ${p => p.theme.colors.palette.text.shade100}; + background-color: ${p => p.theme.colors.palette.background.default}; + text-decoration: none; + cursor: pointer; + border: solid 1px rgba(0, 0, 0, 0); + + &:hover { + background-color: ${p => darken(p.theme.colors.palette.action.hover, 0.05)}; + border-color: ${p => p.theme.colors.wallet}; + } +`; + +const collapserSize = 24; +const collapsedWidth = 15 * 4 + 16; // 15 * 4 margins + 16 icon size + +const Collapser = styled(Box).attrs(() => ({ + alignItems: "center", + justifyContent: "center", +}))` + position: absolute; + top: ${58 - collapserSize / 2}px; + left: ${p => (p.collapsed ? collapsedWidth : MAIN_SIDEBAR_WIDTH) - collapserSize / 2}px; + + width: ${collapserSize}px; + height: ${collapserSize}px; + + cursor: pointer; + border-radius: 50%; + background: ${p => p.theme.colors.palette.background.paper}; + color: ${p => p.theme.colors.palette.text.shade80}; + border-color: ${p => p.theme.colors.palette.divider}; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.05); + border: 1px solid; + transition: all 0.5s; + z-index: 100; + + &:hover { + border-color: ${p => p.theme.colors.wallet}; + color: ${p => p.theme.colors.wallet}; + background: ${p => rgba(p.theme.colors.wallet, 0.1)}; + } + + & > * { + transform: ${p => (p.collapsed ? "" : "rotate(180deg)")}; + margin-left: ${p => (p.collapsed ? "" : "-2px")}; + + transition: transform 0.5s; + } +`; + +const Separator = styled(Box).attrs(() => ({ + mx: 4, +}))` + height: 1px; + background: ${p => p.theme.colors.palette.divider}; +`; + +const sideBarTransitionStyles = { + entering: { flexBasis: MAIN_SIDEBAR_WIDTH }, + entered: { flexBasis: MAIN_SIDEBAR_WIDTH }, + exiting: { flexBasis: collapsedWidth }, + exited: { flexBasis: collapsedWidth }, +}; + +const enableTransitions = () => + document.body && + setTimeout( + () => document.body && document.body.classList.remove("stop-container-transition"), + 500, + ); +const disableTransitions = () => + document.body && document.body.classList.add("stop-container-transition"); + +const sideBarTransitionSpeed = 500; + +const SideBar = styled(Box).attrs(() => ({ + relative: true, +}))` + flex: 0 0 auto; + width: auto; + background-color: ${p => p.theme.colors.palette.background.paper}; + transition: flex ${sideBarTransitionSpeed}ms; + will-change: flex; + transform: translate3d(0, 0, 10); + + & > ${Collapser} { + opacity: 0; + } + + &:hover { + > ${Collapser} { + opacity: 1; + } + } +`; + +const SideBarScrollContainer = styled(Box)` + overflow-y: scroll; + overflow-x: hidden; + + flex: 1; + + ::-webkit-scrollbar { + width: 0; + height: 0; + } +`; + +const TagContainer = ({ collapsed }: { collapsed: boolean }) => { + const isExperimental = useExperimental(); + const hasFullNodeConfigured = useEnv("SATSTACK"); // NB remove once full node is not experimental + + const { t } = useTranslation(); + + return isExperimental || hasFullNodeConfigured ? ( + setTrackingSource("sidebar")} + > + + {t("common.experimentalFeature")} + + ) : null; +}; + +const MainSideBar = () => { + const history = useHistory(); + const location = useLocation(); + const dispatch = useDispatch(); + const { t } = useTranslation(); + + const manifest = useRemoteLiveAppManifest(CARD_APP_ID); + const isCardDisabled = !manifest; + + /** redux navigation locked state */ + const navigationLocked = useSelector(isNavigationLocked); + const collapsed = useSelector(sidebarCollapsedSelector); + const lastSeenDevice = useSelector(lastSeenDeviceSelector); + const noAccounts = useSelector(accountsSelector).length === 0; + const hasStarredAccounts = useSelector(starredAccountsSelector).length > 0; + const displayBlueDot = useManagerBlueDot(lastSeenDevice); + const firstTimeLend = useSelector(state => state.settings.firstTimeLend); + + const lendingEnabled = useSelector(hasLendEnabledAccountsSelector); + + const handleCollapse = useCallback(() => { + dispatch(setSidebarCollapsed(!collapsed)); + }, [dispatch, collapsed]); + + const push = useCallback( + (pathname: string) => { + if (location.pathname === pathname) return; + setTrackingSource("sidebar"); + history.push({ pathname }); + }, + [history, location.pathname], + ); + + const handleClickCard = useCallback(() => { + push("/card"); + }, [push]); + + const handleClickLearn = useCallback(() => { + push("/learn"); + }, [push]); + + const handleClickDashboard = useCallback(() => { + push("/"); + }, [push]); + + const handleClickMarket = useCallback(() => { + push("/market"); + }, [push]); + + const handleClickManager = useCallback(() => { + push("/manager"); + }, [push]); + + const handleClickAccounts = useCallback(() => { + push("/accounts"); + }, [push]); + + const handleClickCatalog = useCallback(() => { + push("/platform"); + }, [push]); + + const handleClickExchange = useCallback(() => { + push("/exchange"); + }, [push]); + + const handleClickLend = useCallback(() => { + if (firstTimeLend) { + dispatch(setFirstTimeLend()); + } + push("/lend"); + }, [push, firstTimeLend, dispatch]); + + const handleClickSwap = useCallback(() => { + push("/swap"); + }, [push]); + + const maybeRedirectToAccounts = useCallback(() => { + return location.pathname === "/manager" && push("/accounts"); + }, [location.pathname, push]); + + const handleOpenSendModal = useCallback(() => { + maybeRedirectToAccounts(); + dispatch(openModal("MODAL_SEND")); + }, [dispatch, maybeRedirectToAccounts]); + + const handleOpenReceiveModal = useCallback(() => { + maybeRedirectToAccounts(); + dispatch(openModal("MODAL_RECEIVE")); + }, [dispatch, maybeRedirectToAccounts]); + + return ( + + {state => { + const secondAnim = !(state === "entered" && !collapsed); + return ( + + + + + + + + + } + collapsed={secondAnim} + /> + + + + + + + + + + + {lendingEnabled && ( + : null} + /> + )} + + : null} + collapsed={secondAnim} + /> + + + + + + + + + + + + + + + + ); + }} + + ); +}; + +export default MainSideBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/Markdown.js b/apps/ledger-live-desktop/src/renderer/components/Markdown.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Markdown.js rename to apps/ledger-live-desktop/src/renderer/components/Markdown.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Modal/ModalBody.js b/apps/ledger-live-desktop/src/renderer/components/Modal/ModalBody.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Modal/ModalBody.js rename to apps/ledger-live-desktop/src/renderer/components/Modal/ModalBody.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Modal/ModalContent.js b/apps/ledger-live-desktop/src/renderer/components/Modal/ModalContent.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Modal/ModalContent.js rename to apps/ledger-live-desktop/src/renderer/components/Modal/ModalContent.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Modal/ModalHeader.js b/apps/ledger-live-desktop/src/renderer/components/Modal/ModalHeader.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Modal/ModalHeader.js rename to apps/ledger-live-desktop/src/renderer/components/Modal/ModalHeader.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Modal/index.js b/apps/ledger-live-desktop/src/renderer/components/Modal/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Modal/index.js rename to apps/ledger-live-desktop/src/renderer/components/Modal/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/NavigationGuard.js b/apps/ledger-live-desktop/src/renderer/components/NavigationGuard.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/NavigationGuard.js rename to apps/ledger-live-desktop/src/renderer/components/NavigationGuard.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.js b/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.js deleted file mode 100644 index ed7d253a9701..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.js +++ /dev/null @@ -1,62 +0,0 @@ -// @flow -import React, { memo, useMemo } from "react"; -import { useNftCollectionMetadata } from "@ledgerhq/live-common/lib/nft"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import type { Account, ProtoNFT } from "@ledgerhq/live-common/lib/types"; -import NFTCollectionContextMenu from "~/renderer/components/ContextMenu/NFTCollectionContextMenu"; -import Skeleton from "~/renderer/components/Nft/Skeleton"; -import IconDots from "~/renderer/icons/Dots"; -import styled from "styled-components"; - -const Dots: ThemedComponent<{}> = styled.div` - justify-content: flex-end; - display: flex; - align-items: center; - cursor: pointer; - padding: 5px; - color: ${p => p.theme.colors.palette.text.shade20}; - &:hover { - color: ${p => p.theme.colors.palette.text.shade40}; - } -`; - -const Container: ThemedComponent<{}> = styled.div` - display: flex; - column-gap: 10px; -`; - -type Props = { - nft?: ProtoNFT, - fallback?: string, - account?: Account, - showHideMenu?: boolean, -}; - -// TODO Make me pretty -const CollectionName = ({ nft, fallback, account, showHideMenu }: Props) => { - const { status, metadata } = useNftCollectionMetadata(nft?.contract, nft?.currencyId); - const { tokenName } = metadata || {}; - const loading = useMemo(() => status === "loading", [status]); - - return ( - - - {tokenName || fallback || "-"} - {account && showHideMenu && nft && ( - - - - - - )} - - - ); -}; - -export default memo(CollectionName); diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.jsx b/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.jsx new file mode 100644 index 000000000000..1031f009cba9 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Nft/CollectionName.jsx @@ -0,0 +1,62 @@ +// @flow +import React, { memo, useMemo } from "react"; +import { useNftCollectionMetadata } from "@ledgerhq/live-common/nft/index"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import type { Account, ProtoNFT } from "@ledgerhq/live-common/types/index"; +import NFTCollectionContextMenu from "~/renderer/components/ContextMenu/NFTCollectionContextMenu"; +import Skeleton from "~/renderer/components/Nft/Skeleton"; +import IconDots from "~/renderer/icons/Dots"; +import styled from "styled-components"; + +const Dots: ThemedComponent<{}> = styled.div` + justify-content: flex-end; + display: flex; + align-items: center; + cursor: pointer; + padding: 5px; + color: ${p => p.theme.colors.palette.text.shade20}; + &:hover { + color: ${p => p.theme.colors.palette.text.shade40}; + } +`; + +const Container: ThemedComponent<{}> = styled.div` + display: flex; + column-gap: 10px; +`; + +type Props = { + nft?: ProtoNFT, + fallback?: string, + account?: Account, + showHideMenu?: boolean, +}; + +// TODO Make me pretty +const CollectionName = ({ nft, fallback, account, showHideMenu }: Props) => { + const { status, metadata } = useNftCollectionMetadata(nft?.contract, nft?.currencyId); + const { tokenName } = metadata || {}; + const loading = useMemo(() => status === "loading", [status]); + + return ( + + + {tokenName || fallback || "-"} + {account && showHideMenu && nft && ( + + + + + + )} + + + ); +}; + +export default memo(CollectionName); diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Image.js b/apps/ledger-live-desktop/src/renderer/components/Nft/Image.js deleted file mode 100644 index cadec1e3fcc4..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Nft/Image.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow -import React from "react"; -import styled from "styled-components"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import type { NFTMetadata } from "@ledgerhq/live-common/lib/types"; -import Skeleton from "./Skeleton"; -import Placeholder from "./Placeholder"; - -/** - * Nb: This image component can be used for small listings, large gallery rendering, - * and even tokens without an image where it will fallback to a generative image - * based on the token metadata and some hue changes. - * - * The text in the fallback image is only visible if we are in `full` mode, since list - * mode is not large enough for the text to be readable. - */ -const Wrapper: ThemedComponent<{ - full?: boolean, - size?: number, - loaded: boolean, - square: boolean, - maxHeight?: number, - maxWidth?: number, - objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", -}> = styled.div` - width: ${({ full, size }) => (full ? "100%" : `${size}px`)}; - height: ${({ full }) => full && "100%"}; - aspect-ratio: ${({ square }) => (square ? "1 / 1" : "initial")}; - max-width: ${({ maxWidth }) => maxWidth && `${maxWidth}px`}; - max-height: ${({ maxHeight }) => maxHeight && `${maxHeight}px`}; - border-radius: 4px; - overflow: hidden; - background-size: contain; - - display: flex; - align-items: center; - justify-content: center; - - & > *:nth-child(1) { - display: ${({ loaded, error }) => (loaded || error ? "none" : "block")}; - } - - & > img { - display: ${({ loaded, error }) => (loaded || error ? "block" : "none")}; - ${({ objectFit }) => - objectFit === "cover" - ? `width: 100%; - height: 100%;` - : `max-width: 100%; - max-height: 100%;`} - object-fit: ${p => p.objectFit ?? "cover"}; - border-radius: 4px; - user-select: none; - } -`; - -type Props = { - uri: string, - mediaType: string, - metadata: NFTMetadata, - tokenId: string, - full?: boolean, - size?: number, - maxHeight?: number, - maxWidth?: number, - objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", - square?: boolean, - onClick?: (e: Event) => void, - setUseFallback: boolean => void, - isFallback: boolean, -}; - -type State = { - loaded: boolean, - error: boolean, -}; - -class Image extends React.PureComponent { - static defaultProps = { - full: false, - size: 32, - mediaFormat: "preview", - }; - - state = { - loaded: false, - error: false, - }; - - render() { - const { - uri, - metadata, - full, - size, - tokenId, - maxHeight, - onClick, - square = true, - objectFit = "cover", - setUseFallback, - isFallback, - } = this.props; - const { loaded, error } = this.state; - - return ( - - - {uri && !error ? ( - this.setState({ loaded: true })} - onError={() => { - if (isFallback) { - this.setState({ error: true }); - } else { - setUseFallback(true); - } - }} - src={uri} - /> - ) : ( - - )} - - ); - } -} - -export default Image; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Image.jsx b/apps/ledger-live-desktop/src/renderer/components/Nft/Image.jsx new file mode 100644 index 000000000000..f620fdf2f1fb --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Nft/Image.jsx @@ -0,0 +1,141 @@ +// @flow +import React from "react"; +import styled from "styled-components"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import type { NFTMetadata } from "@ledgerhq/live-common/types/index"; +import Skeleton from "./Skeleton"; +import Placeholder from "./Placeholder"; + +/** + * Nb: This image component can be used for small listings, large gallery rendering, + * and even tokens without an image where it will fallback to a generative image + * based on the token metadata and some hue changes. + * + * The text in the fallback image is only visible if we are in `full` mode, since list + * mode is not large enough for the text to be readable. + */ +const Wrapper: ThemedComponent<{ + full?: boolean, + size?: number, + loaded: boolean, + square: boolean, + maxHeight?: number, + maxWidth?: number, + objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", +}> = styled.div` + width: ${({ full, size }) => (full ? "100%" : `${size}px`)}; + height: ${({ full }) => full && "100%"}; + aspect-ratio: ${({ square }) => (square ? "1 / 1" : "initial")}; + max-width: ${({ maxWidth }) => maxWidth && `${maxWidth}px`}; + max-height: ${({ maxHeight }) => maxHeight && `${maxHeight}px`}; + border-radius: 4px; + overflow: hidden; + background-size: contain; + + display: flex; + align-items: center; + justify-content: center; + + & > *:nth-child(1) { + display: ${({ loaded, error }) => (loaded || error ? "none" : "block")}; + } + + & > img { + display: ${({ loaded, error }) => (loaded || error ? "block" : "none")}; + ${({ objectFit }) => + objectFit === "cover" + ? `width: 100%; + height: 100%;` + : `max-width: 100%; + max-height: 100%;`} + object-fit: ${p => p.objectFit ?? "cover"}; + border-radius: 4px; + user-select: none; + } +`; + +type Props = { + uri: string, + mediaType: string, + metadata: NFTMetadata, + tokenId: string, + full?: boolean, + size?: number, + maxHeight?: number, + maxWidth?: number, + objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", + square?: boolean, + onClick?: (e: Event) => void, + setUseFallback: boolean => void, + isFallback: boolean, +}; + +type State = { + loaded: boolean, + error: boolean, +}; + +class Image extends React.PureComponent { + static defaultProps = { + full: false, + size: 32, + mediaFormat: "preview", + }; + + state = { + loaded: false, + error: false, + }; + + render() { + const { + uri, + metadata, + full, + size, + tokenId, + maxHeight, + onClick, + square = true, + objectFit = "cover", + setUseFallback, + isFallback, + } = this.props; + const { loaded, error } = this.state; + + return ( + + + {uri && !error ? ( + this.setState({ loaded: true })} + onError={() => { + if (isFallback) { + this.setState({ error: true }); + } else { + setUseFallback(true); + } + }} + src={uri} + /> + ) : ( + + )} + + ); + } +} + +export default Image; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Media.js b/apps/ledger-live-desktop/src/renderer/components/Nft/Media.js deleted file mode 100644 index 2945a10dbaaf..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Nft/Media.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow -import React from "react"; -import type { NFTMetadata, NFTMediaSizes } from "@ledgerhq/live-common/lib/types"; -import { getMetadataMediaType } from "~/helpers/nft"; -import Placeholder from "./Placeholder"; -import Image from "./Image"; -import Video from "./Video"; - -type Props = { - metadata: NFTMetadata, - tokenId: string, - mediaFormat?: NFTMediaSizes, - full?: boolean, - size?: number, - maxHeight?: number, - maxWidth?: number, - objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", - square?: boolean, - onClick?: (e: Event) => void, -}; - -type State = { - useFallback: boolean, -}; - -class Media extends React.PureComponent { - state = { - useFallback: false, - }; - - setUseFallback = (_useFallback: boolean): void => { - this.setState({ useFallback: _useFallback }); - }; - - render() { - const { mediaFormat, metadata, square, tokenId } = this.props; - const { useFallback } = this.state; - - const contentType = getMetadataMediaType(metadata, mediaFormat); - const Component = contentType === "video" && !useFallback ? Video : Image; - - const { uri, mediaType } = metadata?.medias[useFallback ? "preview" : mediaFormat] || {}; - - const squareWithDefault = (() => { - if (typeof square !== "undefined") { - return square; - } - - return contentType !== "video"; - })(); - - return uri ? ( - - ) : ( - - ); - } -} - -export default Media; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Media.jsx b/apps/ledger-live-desktop/src/renderer/components/Nft/Media.jsx new file mode 100644 index 000000000000..79a2b0932b30 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Nft/Media.jsx @@ -0,0 +1,67 @@ +// @flow +import React from "react"; +import type { NFTMetadata, NFTMediaSizes } from "@ledgerhq/live-common/types/index"; +import { getMetadataMediaType } from "~/helpers/nft"; +import Placeholder from "./Placeholder"; +import Image from "./Image"; +import Video from "./Video"; + +type Props = { + metadata: NFTMetadata, + tokenId: string, + mediaFormat?: NFTMediaSizes, + full?: boolean, + size?: number, + maxHeight?: number, + maxWidth?: number, + objectFit?: "cover" | "contain" | "fill" | "none" | "scale-down", + square?: boolean, + onClick?: (e: Event) => void, +}; + +type State = { + useFallback: boolean, +}; + +class Media extends React.PureComponent { + state = { + useFallback: false, + }; + + setUseFallback = (_useFallback: boolean): void => { + this.setState({ useFallback: _useFallback }); + }; + + render() { + const { mediaFormat, metadata, square, tokenId } = this.props; + const { useFallback } = this.state; + + const contentType = getMetadataMediaType(metadata, mediaFormat); + const Component = contentType === "video" && !useFallback ? Video : Image; + + const { uri, mediaType } = metadata?.medias[useFallback ? "preview" : mediaFormat] || {}; + + const squareWithDefault = (() => { + if (typeof square !== "undefined") { + return square; + } + + return contentType !== "video"; + })(); + + return uri ? ( + + ) : ( + + ); + } +} + +export default Media; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.js b/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.js deleted file mode 100644 index f0356353df6a..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.js +++ /dev/null @@ -1,51 +0,0 @@ -// @flow -import React from "react"; -import styled from "styled-components"; -import type { NFTMetadata } from "@ledgerhq/live-common/lib/types"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { centerEllipsis } from "~/renderer/styles/helpers"; -import Fallback from "~/renderer/images/nftFallback.jpg"; - -type Props = { - metadata: NFTMetadata, - tokenId: string, - full?: boolean, -}; -// TODO Figure out if we really need this once we know who creates/processes the media. -const StyledPlaceholder: ThemedComponent = styled.div` - --hue: ${p => (p?.tokenId || "abcdefg").substr(-8) % 360}; - background-image: url(${Fallback}); - background-size: contain; - border-radius: 4px; - width: 100%; - height: 100%; - position: relative; - background-color: hsla(var(--hue), 55%, 66%, 1); - background-blend-mode: hard-light; - aspect-ratio: 1; - - &:after { - display: ${p => (p.full ? "flex" : "none")} - content: "${p => p?.metadata?.nftName || centerEllipsis(p?.tokenId || "-")}"; - font-size: 16px; - font-size: 1vw; - color: #fff; - padding: 0.1vh; - align-items: center; - justify-content: center; - text-align: center; - font-family: "Inter", Arial; - font-weight: 600; - width: 100%; - height: 100%; - } -`; - -class Placeholder extends React.PureComponent { - render() { - const { metadata, tokenId } = this.props; - return ; - } -} - -export default Placeholder; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.jsx b/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.jsx new file mode 100644 index 000000000000..2f7ec1ea2ba4 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Nft/Placeholder.jsx @@ -0,0 +1,51 @@ +// @flow +import React from "react"; +import styled from "styled-components"; +import type { NFTMetadata } from "@ledgerhq/live-common/types/index"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { centerEllipsis } from "~/renderer/styles/helpers"; +import Fallback from "~/renderer/images/nftFallback.jpg"; + +type Props = { + metadata: NFTMetadata, + tokenId: string, + full?: boolean, +}; +// TODO Figure out if we really need this once we know who creates/processes the media. +const StyledPlaceholder: ThemedComponent = styled.div` + --hue: ${p => (p?.tokenId || "abcdefg").substr(-8) % 360}; + background-image: url(${Fallback}); + background-size: contain; + border-radius: 4px; + width: 100%; + height: 100%; + position: relative; + background-color: hsla(var(--hue), 55%, 66%, 1); + background-blend-mode: hard-light; + aspect-ratio: 1; + + &:after { + display: ${p => (p.full ? "flex" : "none")} + content: "${p => p?.metadata?.nftName || centerEllipsis(p?.tokenId || "-")}"; + font-size: 16px; + font-size: 1vw; + color: #fff; + padding: 0.1vh; + align-items: center; + justify-content: center; + text-align: center; + font-family: "Inter", Arial; + font-weight: 600; + width: 100%; + height: 100%; + } +`; + +class Placeholder extends React.PureComponent { + render() { + const { metadata, tokenId } = this.props; + return ; + } +} + +export default Placeholder; diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Skeleton.js b/apps/ledger-live-desktop/src/renderer/components/Nft/Skeleton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Nft/Skeleton.js rename to apps/ledger-live-desktop/src/renderer/components/Nft/Skeleton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Nft/Video.js b/apps/ledger-live-desktop/src/renderer/components/Nft/Video.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Nft/Video.js rename to apps/ledger-live-desktop/src/renderer/components/Nft/Video.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/CarefullyFollowInstructions.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/CarefullyFollowInstructions.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/CarefullyFollowInstructions.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/CarefullyFollowInstructions.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/PreferLedgerRecoverySeed.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/PreferLedgerRecoverySeed.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/PreferLedgerRecoverySeed.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Alerts/PreferLedgerRecoverySeed.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/HideRecoverySeed.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/HideRecoverySeed.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/HideRecoverySeed.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/HideRecoverySeed.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/PinHelp.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/PinHelp.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/PinHelp.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/PinHelp.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoverySeed.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoverySeed.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoverySeed.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoverySeed.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoveryWarning.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoveryWarning.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoveryWarning.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Help/RecoveryWarning.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/LangSwitcher.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/LangSwitcher.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/LangSwitcher.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/LangSwitcher.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Modal.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Modal.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Modal.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Modal.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/assets/Wave.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/assets/Wave.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/assets/Wave.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/assets/Wave.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/screens.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/screens.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/screens.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Pedagogy/screens.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/assets/Wave.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/assets/Wave.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/assets/Wave.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/assets/Wave.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Intro.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Intro.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Intro.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Intro.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Question.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Question.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Question.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Question.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Result.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Result.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Result.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Quizz/screens/Result.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelector.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelector.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelector.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelector.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelectorOption.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelectorOption.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelectorOption.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/DeviceSelectorOption.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectDevice/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/Separator.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/Separator.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/Separator.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/Separator.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/UseCaseOption.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/UseCaseOption.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/UseCaseOption.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/UseCaseOption.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/SelectUseCase/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/TermsExternalLink.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/TermsExternalLink.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/TermsExternalLink.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/TermsExternalLink.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Terms/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/Stepper.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/Stepper.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/Stepper.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/Stepper.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/assets/AnimatedWave.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/assets/AnimatedWave.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/assets/AnimatedWave.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/assets/AnimatedWave.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo2.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo2.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo2.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/DeviceHowTo2.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ExistingRecoveryPhrase.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ExistingRecoveryPhrase.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ExistingRecoveryPhrase.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ExistingRecoveryPhrase.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.js deleted file mode 100644 index e8108bc4b4d4..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.js +++ /dev/null @@ -1,153 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import styled from "styled-components"; -import { useTranslation, Trans } from "react-i18next"; -import { createAction } from "@ledgerhq/live-common/lib/hw/actions/manager"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import type { DeviceModelId } from "@ledgerhq/devices"; -import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types"; -import { rgba } from "~/renderer/styles/helpers"; -import Text from "~/renderer/components/Text"; -import Button from "~/renderer/components/Button"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import ArrowLeft from "~/renderer/icons/ArrowLeft"; -import IconCheck from "~/renderer/icons/Check"; -import { ContentContainer } from "../shared"; -import DeviceAction from "~/renderer/components/DeviceAction"; -import { setLastSeenDeviceInfo } from "~/renderer/actions/settings"; -import { useDispatch } from "react-redux"; -import { mockedEventEmitter } from "~/renderer/components/debug/DebugMock"; -import { command } from "~/renderer/commands"; - -const connectManagerExec = command("connectManager"); -const action = createAction(getEnv("MOCK") ? mockedEventEmitter : connectManagerExec); - -const ScreenContainer: ThemedComponent<*> = styled.div` - display: flex; - flex-direction: column; - flex: 1; - justify-content: space-between; - align-items: center; - padding: 40px 80px; - box-sizing: border-box; -`; - -const ContentFooter = styled.div` - width: 100%; - display: flex; - flex-direction: row; - justify-content: space-between; -`; - -const Content = styled.div` - display: flex; - flex: 1; - align-items: center; - justify-content: center; -`; - -const SuccessContainer = styled(Content)` - flex-direction: column; - max-width: 250px; -`; - -const IconContainer = styled.div` - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - background-color: ${p => rgba(p.theme.colors.positiveGreen, 0.1)}; - color: ${p => p.theme.colors.positiveGreen}; - width: 48px; - height: 48px; -`; - -const Success = ({ device }: { device: Device }) => { - const { t } = useTranslation(); - return ( - - - - - - - - - - - - ); -}; - -type Props = { - sendEvent: any => void, - context: { - deviceId: DeviceModelId, - device?: Device, - }, -}; - -export function GenuineCheck({ sendEvent, context }: Props) { - const { t } = useTranslation(); - const { deviceId, device } = context; - - const reduxDispatch = useDispatch(); - const onClickNext = useCallback(() => sendEvent("NEXT"), [sendEvent]); - const onClickPrev = useCallback(() => sendEvent("PREV"), [sendEvent]); - - const onResult = useCallback( - res => { - const { device, deviceInfo, result } = res; - sendEvent({ type: "GENUINE_CHECK_SUCCESS", device: res.device }); - const lastSeenDevice = { - modelId: device.modelId, - deviceInfo: deviceInfo, - apps: result.installed.map(({ name, version }) => ({ name, version })), - }; - - command("getLatestFirmwareForDevice")(deviceInfo) - .toPromise() - .then(latestFirmware => { - reduxDispatch(setLastSeenDeviceInfo({ lastSeenDevice, latestFirmware })); - }) - .catch(console.error); - }, - [sendEvent], - ); - - return ( - - - - {device ? ( - - ) : ( - - )} - - - - - - - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.jsx b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.jsx new file mode 100644 index 000000000000..39dcdbe86751 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/GenuineCheck.jsx @@ -0,0 +1,153 @@ +// @flow + +import React, { useCallback } from "react"; +import styled from "styled-components"; +import { useTranslation, Trans } from "react-i18next"; +import { createAction } from "@ledgerhq/live-common/hw/actions/manager"; +import { getEnv } from "@ledgerhq/live-common/env"; +import type { DeviceModelId } from "@ledgerhq/devices"; +import type { Device } from "@ledgerhq/live-common/hw/actions/types"; +import { rgba } from "~/renderer/styles/helpers"; +import Text from "~/renderer/components/Text"; +import Button from "~/renderer/components/Button"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import ArrowLeft from "~/renderer/icons/ArrowLeft"; +import IconCheck from "~/renderer/icons/Check"; +import { ContentContainer } from "../shared"; +import DeviceAction from "~/renderer/components/DeviceAction"; +import { setLastSeenDeviceInfo } from "~/renderer/actions/settings"; +import { useDispatch } from "react-redux"; +import { mockedEventEmitter } from "~/renderer/components/debug/DebugMock"; +import { command } from "~/renderer/commands"; + +const connectManagerExec = command("connectManager"); +const action = createAction(getEnv("MOCK") ? mockedEventEmitter : connectManagerExec); + +const ScreenContainer: ThemedComponent<*> = styled.div` + display: flex; + flex-direction: column; + flex: 1; + justify-content: space-between; + align-items: center; + padding: 40px 80px; + box-sizing: border-box; +`; + +const ContentFooter = styled.div` + width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; +`; + +const Content = styled.div` + display: flex; + flex: 1; + align-items: center; + justify-content: center; +`; + +const SuccessContainer = styled(Content)` + flex-direction: column; + max-width: 250px; +`; + +const IconContainer = styled.div` + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + background-color: ${p => rgba(p.theme.colors.positiveGreen, 0.1)}; + color: ${p => p.theme.colors.positiveGreen}; + width: 48px; + height: 48px; +`; + +const Success = ({ device }: { device: Device }) => { + const { t } = useTranslation(); + return ( + + + + + + + + + + + + ); +}; + +type Props = { + sendEvent: any => void, + context: { + deviceId: DeviceModelId, + device?: Device, + }, +}; + +export function GenuineCheck({ sendEvent, context }: Props) { + const { t } = useTranslation(); + const { deviceId, device } = context; + + const reduxDispatch = useDispatch(); + const onClickNext = useCallback(() => sendEvent("NEXT"), [sendEvent]); + const onClickPrev = useCallback(() => sendEvent("PREV"), [sendEvent]); + + const onResult = useCallback( + res => { + const { device, deviceInfo, result } = res; + sendEvent({ type: "GENUINE_CHECK_SUCCESS", device: res.device }); + const lastSeenDevice = { + modelId: device.modelId, + deviceInfo: deviceInfo, + apps: result.installed.map(({ name, version }) => ({ name, version })), + }; + + command("getLatestFirmwareForDevice")(deviceInfo) + .toPromise() + .then(latestFirmware => { + reduxDispatch(setLastSeenDeviceInfo({ lastSeenDevice, latestFirmware })); + }) + .catch(console.error); + }, + [sendEvent], + ); + + return ( + + + + {device ? ( + + ) : ( + + )} + + + + + + + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HideRecoveryPhrase.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HideRecoveryPhrase.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HideRecoveryPhrase.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HideRecoveryPhrase.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HowToGetStarted.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HowToGetStarted.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HowToGetStarted.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/HowToGetStarted.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ImportYourRecoveryPhrase.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ImportYourRecoveryPhrase.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ImportYourRecoveryPhrase.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/ImportYourRecoveryPhrase.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/NewRecoveryPhrase.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/NewRecoveryPhrase.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/NewRecoveryPhrase.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/NewRecoveryPhrase.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PairMyNano.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PairMyNano.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PairMyNano.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PairMyNano.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCode.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCode.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCode.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCode.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCodeHowTo.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCodeHowTo.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCodeHowTo.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/PinCodeHowTo.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizFailure.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizFailure.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizFailure.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizFailure.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizSuccess.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizSuccess.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizSuccess.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/QuizSuccess.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo1.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo1.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo1.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo1.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo2.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo2.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo2.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo2.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo3.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo3.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo3.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/RecoveryHowTo3.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/UseRecoverySheet.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/UseRecoverySheet.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/UseRecoverySheet.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/screens/UseRecoverySheet.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/shared.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/shared.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/shared.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Tutorial/shared.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/Screens/Welcome/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/ScrollArea.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/ScrollArea.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/ScrollArea.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/ScrollArea.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Onboarding/index.js b/apps/ledger-live-desktop/src/renderer/components/Onboarding/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Onboarding/index.js rename to apps/ledger-live-desktop/src/renderer/components/Onboarding/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/OnboardingOrElse.js b/apps/ledger-live-desktop/src/renderer/components/OnboardingOrElse.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/OnboardingOrElse.js rename to apps/ledger-live-desktop/src/renderer/components/OnboardingOrElse.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/OpenUserDataDirectoryBtn.js b/apps/ledger-live-desktop/src/renderer/components/OpenUserDataDirectoryBtn.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/OpenUserDataDirectoryBtn.js rename to apps/ledger-live-desktop/src/renderer/components/OpenUserDataDirectoryBtn.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.js deleted file mode 100644 index 5c1a023c4ef1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.js +++ /dev/null @@ -1,46 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import Box from "~/renderer/components/Box"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import Tooltip from "~/renderer/components/Tooltip"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ - px: 4, - horizontal: true, - alignItems: "center", - height: "100%", -}))` - flex: 1 1 auto; - overflow: hidden; - max-width: 400px; -`; - -type Props = { - currency: Currency, - accountName: string, -}; - -class AccountCell extends PureComponent { - render() { - const { currency, accountName } = this.props; - return ( - - - - - - - {accountName} - - - - ); - } -} - -export default AccountCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.jsx new file mode 100644 index 000000000000..e130be80a065 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AccountCell.jsx @@ -0,0 +1,46 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import type { Currency } from "@ledgerhq/live-common/types/index"; +import Box from "~/renderer/components/Box"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import Tooltip from "~/renderer/components/Tooltip"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ + px: 4, + horizontal: true, + alignItems: "center", + height: "100%", +}))` + flex: 1 1 auto; + overflow: hidden; + max-width: 400px; +`; + +type Props = { + currency: Currency, + accountName: string, +}; + +class AccountCell extends PureComponent { + render() { + const { currency, accountName } = this.props; + return ( + + + + + + + {accountName} + + + + ); + } +} + +export default AccountCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.js deleted file mode 100644 index 5fe3d5e3babb..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.js +++ /dev/null @@ -1,110 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import type { Operation } from "@ledgerhq/live-common/lib/types"; -import Box from "~/renderer/components/Box"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -export const SplitAddress = ({ - value, - color, - ff, - fontSize, -}: { - value: string, - color?: string, - ff?: string, - fontSize?: number, -}) => { - if (!value) { - return ; - } - - const boxProps = { - color, - ff, - fontSize, - }; - - const third = Math.round(value.length / 3); - - // FIXME why not using CSS for this? meaning we might be able to have a left & right which both take 50% & play with overflow & text-align - const left = value.slice(0, third); - const right = value.slice(third, value.length); - - return ( - - {left} - {right} - - ); -}; - -export const Address = ({ value }: { value: string }) => ( - -); - -const Left: ThemedComponent<{}> = styled.div` - overflow: hidden; - white-space: nowrap; - font-kerning: none; - letter-spacing: 0px; -`; - -const Right: ThemedComponent<{}> = styled.div` - display: inline-block; - flex-shrink: 1; - direction: rtl; - text-indent: 0.6ex; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-kerning: none; - min-width: 3ex; - letter-spacing: 0px; -`; - -export const Cell: ThemedComponent<{ px?: number }> = styled(Box).attrs(p => ({ - px: p.px === 0 ? p.px : p.px || 4, - horizontal: true, - alignItems: "center", -}))` - width: 150px; - flex-grow: 1; - flex-shrink: 1; - display: block; -`; - -type Props = { - operation: Operation, -}; - -const showSender = o => o.senders[0]; -const showRecipient = o => o.recipients[0]; -const showNothing = o => null; -const perOperationType = { - IN: showSender, - REVEAL: showSender, - REWARD_PAYOUT: showSender, - NFT_IN: showNothing, - NFT_OUT: showNothing, - _: showRecipient, -}; - -class AddressCell extends PureComponent { - render() { - const { operation } = this.props; - const lense = perOperationType[operation.type] || perOperationType._; - const value = lense(operation); - return value ? ( - -
- - ) : ( - - ); - } -} - -export default AddressCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.jsx new file mode 100644 index 000000000000..3bb56fc54b2d --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AddressCell.jsx @@ -0,0 +1,110 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import type { Operation } from "@ledgerhq/live-common/types/index"; +import Box from "~/renderer/components/Box"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +export const SplitAddress = ({ + value, + color, + ff, + fontSize, +}: { + value: string, + color?: string, + ff?: string, + fontSize?: number, +}) => { + if (!value) { + return ; + } + + const boxProps = { + color, + ff, + fontSize, + }; + + const third = Math.round(value.length / 3); + + // FIXME why not using CSS for this? meaning we might be able to have a left & right which both take 50% & play with overflow & text-align + const left = value.slice(0, third); + const right = value.slice(third, value.length); + + return ( + + {left} + {right} + + ); +}; + +export const Address = ({ value }: { value: string }) => ( + +); + +const Left: ThemedComponent<{}> = styled.div` + overflow: hidden; + white-space: nowrap; + font-kerning: none; + letter-spacing: 0px; +`; + +const Right: ThemedComponent<{}> = styled.div` + display: inline-block; + flex-shrink: 1; + direction: rtl; + text-indent: 0.6ex; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-kerning: none; + min-width: 3ex; + letter-spacing: 0px; +`; + +export const Cell: ThemedComponent<{ px?: number }> = styled(Box).attrs(p => ({ + px: p.px === 0 ? p.px : p.px || 4, + horizontal: true, + alignItems: "center", +}))` + width: 150px; + flex-grow: 1; + flex-shrink: 1; + display: block; +`; + +type Props = { + operation: Operation, +}; + +const showSender = o => o.senders[0]; +const showRecipient = o => o.recipients[0]; +const showNothing = o => null; +const perOperationType = { + IN: showSender, + REVEAL: showSender, + REWARD_PAYOUT: showSender, + NFT_IN: showNothing, + NFT_OUT: showNothing, + _: showRecipient, +}; + +class AddressCell extends PureComponent { + render() { + const { operation } = this.props; + const lense = perOperationType[operation.type] || perOperationType._; + const value = lense(operation); + return value ? ( + +
+ + ) : ( + + ); + } +} + +export default AddressCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.js deleted file mode 100644 index 8247ecf334d1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.js +++ /dev/null @@ -1,90 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import { getOperationAmountNumber } from "@ledgerhq/live-common/lib/operation"; -import type { Currency, Unit, Operation } from "@ledgerhq/live-common/lib/types"; -import Box from "~/renderer/components/Box"; -import CounterValue from "~/renderer/components/CounterValue"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import perFamilyOperationDetails from "~/renderer/generated/operationDetails"; - -const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ - px: 4, - horizontal: false, - alignItems: "flex-end", -}))` - flex: 0 0 auto; - text-align: right; - justify-content: center; - height: 32px; - min-width: 150px; -`; - -type Props = { - operation: Operation, - currency: Currency, - unit: Unit, -}; - -class AmountCell extends PureComponent { - render() { - // eslint-disable-next-line no-unused-vars - const { currency, unit, operation } = this.props; - const amount = getOperationAmountNumber(operation); - - // $FlowFixMe - const specific = currency.family ? perFamilyOperationDetails[currency.family] : null; - - const Element = - specific && specific.amountCellExtra ? specific.amountCellExtra[operation.type] : null; - const AmountElement = - specific && specific.amountCell ? specific.amountCell[operation.type] : null; - - return ( - <> - {Element && ( - - - - )} - {(!amount.isZero() || AmountElement) && ( - - {AmountElement ? ( - - ) : ( - <> - - - - - )} - - )} - - ); - } -} - -export default AmountCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.jsx new file mode 100644 index 000000000000..d97b5f1030e1 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/AmountCell.jsx @@ -0,0 +1,90 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import { getOperationAmountNumber } from "@ledgerhq/live-common/operation"; +import type { Currency, Unit, Operation } from "@ledgerhq/live-common/types/index"; +import Box from "~/renderer/components/Box"; +import CounterValue from "~/renderer/components/CounterValue"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import perFamilyOperationDetails from "~/renderer/generated/operationDetails"; + +const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ + px: 4, + horizontal: false, + alignItems: "flex-end", +}))` + flex: 0 0 auto; + text-align: right; + justify-content: center; + height: 32px; + min-width: 150px; +`; + +type Props = { + operation: Operation, + currency: Currency, + unit: Unit, +}; + +class AmountCell extends PureComponent { + render() { + // eslint-disable-next-line no-unused-vars + const { currency, unit, operation } = this.props; + const amount = getOperationAmountNumber(operation); + + // $FlowFixMe + const specific = currency.family ? perFamilyOperationDetails[currency.family] : null; + + const Element = + specific && specific.amountCellExtra ? specific.amountCellExtra[operation.type] : null; + const AmountElement = + specific && specific.amountCell ? specific.amountCell[operation.type] : null; + + return ( + <> + {Element && ( + + + + )} + {(!amount.isZero() || AmountElement) && ( + + {AmountElement ? ( + + ) : ( + <> + + + + + )} + + )} + + ); + } +} + +export default AmountCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.js deleted file mode 100644 index 45b8e06ba986..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import { connect } from "react-redux"; -import type { TFunction } from "react-i18next"; -import styled from "styled-components"; -import { createStructuredSelector } from "reselect"; -import type { Account, Operation, AccountLike } from "@ledgerhq/live-common/lib/types"; -import { getMainAccount, getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import { - getOperationAmountNumber, - isConfirmedOperation, -} from "@ledgerhq/live-common/lib/operation"; -import { - confirmationsNbForCurrencySelector, - marketIndicatorSelector, -} from "~/renderer/reducers/settings"; -import { getMarketColor } from "~/renderer/styles/helpers"; - -import Box from "~/renderer/components/Box"; - -import ConfirmationCheck from "./ConfirmationCheck"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import perFamilyOperationDetails from "~/renderer/generated/operationDetails"; - -const mapStateToProps = createStructuredSelector({ - confirmationsNb: (state, { account, parentAccount }) => - confirmationsNbForCurrencySelector(state, { - currency: getMainAccount(account, parentAccount).currency, - }), - marketIndicator: marketIndicatorSelector, -}); - -const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ - pl: 4, - horizontal: true, - alignItems: "center", -}))``; - -type OwnProps = { - account: AccountLike, - parentAccount?: Account, - t: TFunction, - operation: Operation, -}; - -type Props = { - ...OwnProps, - confirmationsNb: number, - marketIndicator: string, -}; - -class ConfirmationCell extends PureComponent { - render() { - const { account, parentAccount, confirmationsNb, t, operation, marketIndicator } = this.props; - const mainAccount = getMainAccount(account, parentAccount); - const currency = getAccountCurrency(mainAccount); - - const amount = getOperationAmountNumber(operation); - - const isNegative = amount.isNegative(); - - const isConfirmed = isConfirmedOperation(operation, mainAccount, confirmationsNb); - - const marketColor = getMarketColor({ - marketIndicator, - isNegative, - }); - - // $FlowFixMe - const specific = currency.family ? perFamilyOperationDetails[currency.family] : null; - - const SpecificConfirmationCell = - specific && specific.confirmationCell ? specific.confirmationCell[operation.type] : null; - - return SpecificConfirmationCell ? ( - - ) : ( - - - - ); - } -} - -const ConnectedConfirmationCell: React$ComponentType = connect(mapStateToProps)( - ConfirmationCell, -); - -export default ConnectedConfirmationCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.jsx new file mode 100644 index 000000000000..e940878b9fc1 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCell.jsx @@ -0,0 +1,101 @@ +// @flow + +import React, { PureComponent } from "react"; +import { connect } from "react-redux"; +import type { TFunction } from "react-i18next"; +import styled from "styled-components"; +import { createStructuredSelector } from "reselect"; +import type { Account, Operation, AccountLike } from "@ledgerhq/live-common/types/index"; +import { getMainAccount, getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import { getOperationAmountNumber, isConfirmedOperation } from "@ledgerhq/live-common/operation"; +import { + confirmationsNbForCurrencySelector, + marketIndicatorSelector, +} from "~/renderer/reducers/settings"; +import { getMarketColor } from "~/renderer/styles/helpers"; + +import Box from "~/renderer/components/Box"; + +import ConfirmationCheck from "./ConfirmationCheck"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import perFamilyOperationDetails from "~/renderer/generated/operationDetails"; + +const mapStateToProps = createStructuredSelector({ + confirmationsNb: (state, { account, parentAccount }) => + confirmationsNbForCurrencySelector(state, { + currency: getMainAccount(account, parentAccount).currency, + }), + marketIndicator: marketIndicatorSelector, +}); + +const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ + pl: 4, + horizontal: true, + alignItems: "center", +}))``; + +type OwnProps = { + account: AccountLike, + parentAccount?: Account, + t: TFunction, + operation: Operation, +}; + +type Props = { + ...OwnProps, + confirmationsNb: number, + marketIndicator: string, +}; + +class ConfirmationCell extends PureComponent { + render() { + const { account, parentAccount, confirmationsNb, t, operation, marketIndicator } = this.props; + const mainAccount = getMainAccount(account, parentAccount); + const currency = getAccountCurrency(mainAccount); + + const amount = getOperationAmountNumber(operation); + + const isNegative = amount.isNegative(); + + const isConfirmed = isConfirmedOperation(operation, mainAccount, confirmationsNb); + + const marketColor = getMarketColor({ + marketIndicator, + isNegative, + }); + + // $FlowFixMe + const specific = currency.family ? perFamilyOperationDetails[currency.family] : null; + + const SpecificConfirmationCell = + specific && specific.confirmationCell ? specific.confirmationCell[operation.type] : null; + + return SpecificConfirmationCell ? ( + + ) : ( + + + + ); + } +} + +const ConnectedConfirmationCell: React$ComponentType = connect(mapStateToProps)( + ConfirmationCell, +); + +export default ConnectedConfirmationCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.js deleted file mode 100644 index d2e87f8e88e1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.js +++ /dev/null @@ -1,184 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; - -import type { OperationType } from "@ledgerhq/live-common/lib/types"; - -import { rgba, mix } from "~/renderer/styles/helpers"; - -import type { TFunction } from "react-i18next"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import IconClock from "~/renderer/icons/Clock"; -import IconReceive from "~/renderer/icons/Receive"; -import IconDelegate from "~/renderer/icons/Delegate"; -import IconUndelegate from "~/renderer/icons/Undelegate"; -import IconRedelegate from "~/renderer/icons/Redelegate"; -import IconSend from "~/renderer/icons/Send"; -import IconPlus from "~/renderer/icons/Plus"; -import IconEye from "~/renderer/icons/Eye"; -import IconFees from "~/renderer/icons/Fees"; -import IconTrash from "~/renderer/icons/Trash"; -import IconSupply from "~/renderer/icons/Supply"; -import IconWithdraw from "~/renderer/icons/Withdraw"; -import IconLink from "~/renderer/icons/LinkIcon"; -import IconCoins from "~/renderer/icons/Coins"; - -import Freeze from "~/renderer/icons/Freeze"; -import Unfreeze from "~/renderer/icons/Unfreeze"; - -import Box from "~/renderer/components/Box"; -import Tooltip from "~/renderer/components/Tooltip"; -import ClaimRewards from "~/renderer/icons/ClaimReward"; -import Vote from "~/renderer/icons/Vote"; -import VoteNay from "~/renderer/icons/VoteNay"; - -const border = p => - p.hasFailed - ? `1px solid ${p.theme.colors.alertRed}` - : p.isConfirmed - ? 0 - : `1px solid ${ - p.type === "IN" || p.type === "NFT_IN" - ? p.marketColor - : rgba(p.theme.colors.palette.text.shade60, 0.2) - }`; - -function inferColor(p) { - switch (p.type) { - case "IN": - case "NFT_IN": - return p.marketColor; - case "FREEZE": - return p.theme.colors.wallet; - case "REWARD": - return p.theme.colors.gold; - default: - return p.theme.colors.palette.text.shade60; - } -} - -export const Container: ThemedComponent<{ - isConfirmed: boolean, - type: string, - marketColor: string, - hasFailed?: boolean, -}> = styled(Box).attrs(p => ({ - bg: p.hasFailed - ? mix(p.theme.colors.alertRed, p.theme.colors.palette.background.paper, 0.95) - : p.isConfirmed - ? mix(inferColor(p), p.theme.colors.palette.background.paper, 0.8) - : p.theme.colors.palette.background.paper, - color: p.hasFailed ? p.theme.colors.alertRed : inferColor(p), - alignItems: "center", - justifyContent: "center", -}))` - border: ${border}; - border-radius: 50%; - position: relative; - height: 24px; - width: 24px; -`; - -const WrapperClock: ThemedComponent<{}> = styled(Box).attrs(() => ({ - bg: "palette.background.paper", - color: "palette.text.shade60", -}))` - border-radius: 50%; - position: absolute; - bottom: -4px; - right: -4px; - padding: 1px; -`; - -const iconsComponent = { - OUT: IconSend, - IN: IconReceive, - NFT_OUT: IconSend, - NFT_IN: IconReceive, - DELEGATE: IconDelegate, - REDELEGATE: IconRedelegate, - UNDELEGATE: IconUndelegate, - REVEAL: IconEye, - CREATE: IconPlus, - NONE: IconSend, - FREEZE: Freeze, - UNFREEZE: Unfreeze, - VOTE: Vote, - REWARD: ClaimRewards, - FEES: IconFees, - OPT_IN: IconPlus, - OPT_OUT: IconTrash, - CLOSE_ACCOUNT: IconTrash, - REDEEM: IconWithdraw, - SUPPLY: IconSupply, - APPROVE: IconPlus, - BOND: IconLink, - UNBOND: IconUndelegate, - WITHDRAW_UNBONDED: IconCoins, - SLASH: IconTrash, - NOMINATE: Vote, - CHILL: VoteNay, - REWARD_PAYOUT: ClaimRewards, - SET_CONTROLLER: IconSend, -}; - -class ConfirmationCheck extends PureComponent<{ - marketColor: string, - isConfirmed: boolean, - t: TFunction, - type: OperationType, - withTooltip?: boolean, - hasFailed?: boolean, -}> { - static defaultProps = { - withTooltip: true, - }; - - renderTooltip = () => { - const { t, isConfirmed } = this.props; - return t(isConfirmed ? "operationDetails.confirmed" : "operationDetails.notConfirmed"); - }; - - render() { - const { marketColor, isConfirmed, t, type, withTooltip, hasFailed, ...props } = this.props; - - const Icon = iconsComponent[type]; - - const content = ( - - {Icon ? : null} - {!isConfirmed && !hasFailed && ( - - - - )} - - ); - - return withTooltip ? ( - - {content} - - ) : ( - content - ); - } -} - -export default ConfirmationCheck; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.jsx new file mode 100644 index 000000000000..3fb001dded14 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/ConfirmationCheck.jsx @@ -0,0 +1,184 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; + +import type { OperationType } from "@ledgerhq/live-common/types/index"; + +import { rgba, mix } from "~/renderer/styles/helpers"; + +import type { TFunction } from "react-i18next"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import IconClock from "~/renderer/icons/Clock"; +import IconReceive from "~/renderer/icons/Receive"; +import IconDelegate from "~/renderer/icons/Delegate"; +import IconUndelegate from "~/renderer/icons/Undelegate"; +import IconRedelegate from "~/renderer/icons/Redelegate"; +import IconSend from "~/renderer/icons/Send"; +import IconPlus from "~/renderer/icons/Plus"; +import IconEye from "~/renderer/icons/Eye"; +import IconFees from "~/renderer/icons/Fees"; +import IconTrash from "~/renderer/icons/Trash"; +import IconSupply from "~/renderer/icons/Supply"; +import IconWithdraw from "~/renderer/icons/Withdraw"; +import IconLink from "~/renderer/icons/LinkIcon"; +import IconCoins from "~/renderer/icons/Coins"; + +import Freeze from "~/renderer/icons/Freeze"; +import Unfreeze from "~/renderer/icons/Unfreeze"; + +import Box from "~/renderer/components/Box"; +import Tooltip from "~/renderer/components/Tooltip"; +import ClaimRewards from "~/renderer/icons/ClaimReward"; +import Vote from "~/renderer/icons/Vote"; +import VoteNay from "~/renderer/icons/VoteNay"; + +const border = p => + p.hasFailed + ? `1px solid ${p.theme.colors.alertRed}` + : p.isConfirmed + ? 0 + : `1px solid ${ + p.type === "IN" || p.type === "NFT_IN" + ? p.marketColor + : rgba(p.theme.colors.palette.text.shade60, 0.2) + }`; + +function inferColor(p) { + switch (p.type) { + case "IN": + case "NFT_IN": + return p.marketColor; + case "FREEZE": + return p.theme.colors.wallet; + case "REWARD": + return p.theme.colors.gold; + default: + return p.theme.colors.palette.text.shade60; + } +} + +export const Container: ThemedComponent<{ + isConfirmed: boolean, + type: string, + marketColor: string, + hasFailed?: boolean, +}> = styled(Box).attrs(p => ({ + bg: p.hasFailed + ? mix(p.theme.colors.alertRed, p.theme.colors.palette.background.paper, 0.95) + : p.isConfirmed + ? mix(inferColor(p), p.theme.colors.palette.background.paper, 0.8) + : p.theme.colors.palette.background.paper, + color: p.hasFailed ? p.theme.colors.alertRed : inferColor(p), + alignItems: "center", + justifyContent: "center", +}))` + border: ${border}; + border-radius: 50%; + position: relative; + height: 24px; + width: 24px; +`; + +const WrapperClock: ThemedComponent<{}> = styled(Box).attrs(() => ({ + bg: "palette.background.paper", + color: "palette.text.shade60", +}))` + border-radius: 50%; + position: absolute; + bottom: -4px; + right: -4px; + padding: 1px; +`; + +const iconsComponent = { + OUT: IconSend, + IN: IconReceive, + NFT_OUT: IconSend, + NFT_IN: IconReceive, + DELEGATE: IconDelegate, + REDELEGATE: IconRedelegate, + UNDELEGATE: IconUndelegate, + REVEAL: IconEye, + CREATE: IconPlus, + NONE: IconSend, + FREEZE: Freeze, + UNFREEZE: Unfreeze, + VOTE: Vote, + REWARD: ClaimRewards, + FEES: IconFees, + OPT_IN: IconPlus, + OPT_OUT: IconTrash, + CLOSE_ACCOUNT: IconTrash, + REDEEM: IconWithdraw, + SUPPLY: IconSupply, + APPROVE: IconPlus, + BOND: IconLink, + UNBOND: IconUndelegate, + WITHDRAW_UNBONDED: IconCoins, + SLASH: IconTrash, + NOMINATE: Vote, + CHILL: VoteNay, + REWARD_PAYOUT: ClaimRewards, + SET_CONTROLLER: IconSend, +}; + +class ConfirmationCheck extends PureComponent<{ + marketColor: string, + isConfirmed: boolean, + t: TFunction, + type: OperationType, + withTooltip?: boolean, + hasFailed?: boolean, +}> { + static defaultProps = { + withTooltip: true, + }; + + renderTooltip = () => { + const { t, isConfirmed } = this.props; + return t(isConfirmed ? "operationDetails.confirmed" : "operationDetails.notConfirmed"); + }; + + render() { + const { marketColor, isConfirmed, t, type, withTooltip, hasFailed, ...props } = this.props; + + const Icon = iconsComponent[type]; + + const content = ( + + {Icon ? : null} + {!isConfirmed && !hasFailed && ( + + + + )} + + ); + + return withTooltip ? ( + + {content} + + ) : ( + content + ); + } +} + +export default ConfirmationCheck; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.js deleted file mode 100644 index 8334d442dcd7..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import type { Operation } from "@ledgerhq/live-common/lib/types"; -import type { TFunction } from "react-i18next"; -import Box from "~/renderer/components/Box"; -import OperationDate from "./OperationDate"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ - px: 3, - horizontal: false, -}))` - width: auto; - min-width: ${p => (p.compact ? 90 : 120)}px; -`; - -type Props = { - t: TFunction, - operation: Operation, - text?: string, - compact?: boolean, -}; - -class DateCell extends PureComponent { - static defaultProps = { - withAccount: false, - }; - - render() { - const { t, operation, compact, text } = this.props; - const ellipsis = { - display: "block", - textOverflow: "ellipsis", - overflow: "hidden", - whiteSpace: "nowrap", - }; - - return ( - - - {text || - t(operation.hasFailed ? "operationDetails.failed" : `operation.type.${operation.type}`)} - - - - ); - } -} - -export default DateCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.jsx new file mode 100644 index 000000000000..0cf70f493f09 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/DateCell.jsx @@ -0,0 +1,52 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import type { Operation } from "@ledgerhq/live-common/types/index"; +import type { TFunction } from "react-i18next"; +import Box from "~/renderer/components/Box"; +import OperationDate from "./OperationDate"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const Cell: ThemedComponent<{}> = styled(Box).attrs(() => ({ + px: 3, + horizontal: false, +}))` + width: auto; + min-width: ${p => (p.compact ? 90 : 120)}px; +`; + +type Props = { + t: TFunction, + operation: Operation, + text?: string, + compact?: boolean, +}; + +class DateCell extends PureComponent { + static defaultProps = { + withAccount: false, + }; + + render() { + const { t, operation, compact, text } = this.props; + const ellipsis = { + display: "block", + textOverflow: "ellipsis", + overflow: "hidden", + whiteSpace: "nowrap", + }; + + return ( + + + {text || + t(operation.hasFailed ? "operationDetails.failed" : `operation.type.${operation.type}`)} + + + + ); + } +} + +export default DateCell; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.js deleted file mode 100644 index 28bf8398762d..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.js +++ /dev/null @@ -1,85 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import { rgba } from "~/renderer/styles/helpers"; -import Box from "~/renderer/components/Box"; -import type { TFunction } from "react-i18next"; -import type { AccountLike, Account, Operation } from "@ledgerhq/live-common/lib/types"; -import { - getAccountCurrency, - getAccountName, - getAccountUnit, -} from "@ledgerhq/live-common/lib/account"; - -import ConfirmationCell from "./ConfirmationCell"; -import DateCell from "./DateCell"; -import AccountCell from "./AccountCell"; -import AddressCell from "./AddressCell"; -import AmountCell from "./AmountCell"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const OperationRow: ThemedComponent<{}> = styled(Box).attrs(() => ({ - horizontal: true, - alignItems: "center", -}))` - border-bottom: 1px solid ${p => p.theme.colors.palette.divider}; - height: 68px; - opacity: ${p => (p.isOptimistic ? 0.5 : 1)}; - cursor: pointer; - - &:hover { - background: ${p => rgba(p.theme.colors.wallet, 0.04)}; - } -`; - -type Props = { - operation: Operation, - account: AccountLike, - parentAccount?: Account, - onOperationClick: (operation: Operation, account: AccountLike, parentAccount?: Account) => void, - t: TFunction, - withAccount: boolean, - withAddress: boolean, - text?: string, -}; - -class OperationComponent extends PureComponent { - static defaultProps = { - withAccount: false, - withAddress: true, - }; - - onOperationClick = () => { - const { account, parentAccount, onOperationClick, operation } = this.props; - onOperationClick(operation, account, parentAccount); - }; - - render() { - const { account, parentAccount, t, operation, withAccount, text, withAddress } = this.props; - const isOptimistic = operation.blockHeight === null; - const currency = getAccountCurrency(account); - const unit = getAccountUnit(account); - - return ( - - - - {withAccount && } - {withAddress ? : } - - - ); - } -} - -export default OperationComponent; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.jsx new file mode 100644 index 000000000000..0efce73cc530 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/Operation.jsx @@ -0,0 +1,85 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import { rgba } from "~/renderer/styles/helpers"; +import Box from "~/renderer/components/Box"; +import type { TFunction } from "react-i18next"; +import type { AccountLike, Account, Operation } from "@ledgerhq/live-common/types/index"; +import { + getAccountCurrency, + getAccountName, + getAccountUnit, +} from "@ledgerhq/live-common/account/index"; + +import ConfirmationCell from "./ConfirmationCell"; +import DateCell from "./DateCell"; +import AccountCell from "./AccountCell"; +import AddressCell from "./AddressCell"; +import AmountCell from "./AmountCell"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const OperationRow: ThemedComponent<{}> = styled(Box).attrs(() => ({ + horizontal: true, + alignItems: "center", +}))` + border-bottom: 1px solid ${p => p.theme.colors.palette.divider}; + height: 68px; + opacity: ${p => (p.isOptimistic ? 0.5 : 1)}; + cursor: pointer; + + &:hover { + background: ${p => rgba(p.theme.colors.wallet, 0.04)}; + } +`; + +type Props = { + operation: Operation, + account: AccountLike, + parentAccount?: Account, + onOperationClick: (operation: Operation, account: AccountLike, parentAccount?: Account) => void, + t: TFunction, + withAccount: boolean, + withAddress: boolean, + text?: string, +}; + +class OperationComponent extends PureComponent { + static defaultProps = { + withAccount: false, + withAddress: true, + }; + + onOperationClick = () => { + const { account, parentAccount, onOperationClick, operation } = this.props; + onOperationClick(operation, account, parentAccount); + }; + + render() { + const { account, parentAccount, t, operation, withAccount, text, withAddress } = this.props; + const isOptimistic = operation.blockHeight === null; + const currency = getAccountCurrency(account); + const unit = getAccountUnit(account); + + return ( + + + + {withAccount && } + {withAddress ? : } + + + ); + } +} + +export default OperationComponent; diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/OperationDate.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/OperationDate.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/OperationsList/OperationDate.js rename to apps/ledger-live-desktop/src/renderer/components/OperationsList/OperationDate.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/SectionTitle.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/SectionTitle.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/OperationsList/SectionTitle.js rename to apps/ledger-live-desktop/src/renderer/components/OperationsList/SectionTitle.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.js b/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.js deleted file mode 100644 index 0de34953f5db..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.js +++ /dev/null @@ -1,191 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import { connect } from "react-redux"; -import { compose } from "redux"; -import { withTranslation } from "react-i18next"; -import type { TFunction } from "react-i18next"; -import type { Operation, Account, AccountLike } from "@ledgerhq/live-common/lib/types"; -import keyBy from "lodash/keyBy"; -import { - groupAccountOperationsByDay, - groupAccountsOperationsByDay, - flattenAccounts, -} from "@ledgerhq/live-common/lib/account"; -import logger from "~/logger"; -import { openModal } from "~/renderer/actions/modals"; -import IconAngleDown from "~/renderer/icons/AngleDown"; -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import { track } from "~/renderer/analytics/segment"; -import { createStructuredSelector } from "reselect"; -import { accountsSelector } from "~/renderer/reducers/accounts"; -import SectionTitle from "./SectionTitle"; -import OperationC from "./Operation"; -import TableContainer, { TableHeader } from "../TableContainer"; -import { OperationDetails } from "~/renderer/drawers/OperationDetails"; -import { setDrawer } from "~/renderer/drawers/Provider"; - -const ShowMore = styled(Box).attrs(() => ({ - horizontal: true, - flow: 1, - ff: "Inter|SemiBold", - fontSize: 3, - justifyContent: "center", - alignItems: "center", - p: 3, - color: "wallet", -}))` - &:hover { - text-decoration: underline; - } -`; - -const mapDispatchToProps = { - openModal, -}; - -type Props = { - account: AccountLike, - parentAccount?: ?Account, - accounts: AccountLike[], - allAccounts: AccountLike[], - openModal: (string, Object) => *, - t: TFunction, - withAccount?: boolean, - withSubAccounts?: boolean, - title?: string, - filterOperation?: (Operation, AccountLike) => boolean, -}; - -type State = { - nbToShow: number, -}; - -const initialState = { - nbToShow: 20, -}; - -export class OperationsList extends PureComponent { - static defaultProps = { - withAccount: false, - }; - - state = initialState; - - handleClickOperation = (operation: Operation, account: AccountLike, parentAccount?: Account) => - setDrawer(OperationDetails, { - operationId: operation.id, - accountId: account.id, - parentId: parentAccount && parentAccount.id, - }); - - // TODO: convert of async/await if fetching with the api - fetchMoreOperations = () => { - track("FetchMoreOperations"); - this.setState({ nbToShow: this.state.nbToShow + 20 }); - }; - - render() { - const { - account, - parentAccount, - accounts, - allAccounts, - t, - title, - withAccount, - withSubAccounts, - filterOperation, - } = this.props; - const { nbToShow } = this.state; - - if (!account && !accounts) { - console.warn("Preventing render OperationsList because not received account or accounts"); // eslint-disable-line no-console - return null; - } - - const groupedOperations = account - ? groupAccountOperationsByDay(account, { count: nbToShow, withSubAccounts, filterOperation }) - : groupAccountsOperationsByDay(accounts, { - count: nbToShow, - withSubAccounts, - filterOperation, - }); - - const all = flattenAccounts(accounts || []).concat([account, parentAccount].filter(Boolean)); - const accountsMap = keyBy(all, "id"); - - return ( - <> - - {title && ( - - )} - {groupedOperations.sections.map(group => ( - - - - {group.data.map(operation => { - const account = accountsMap[operation.accountId]; - if (!account) { - logger.warn(`no account found for operation ${operation.id}`); - return null; - } - let parentAccount; - if (account.type !== "Account") { - const pa = - accountsMap[account.parentId] || - allAccounts.find(a => a.id === account.parentId); - if (pa && pa.type === "Account") { - parentAccount = pa; - } - if (!parentAccount) { - logger.warn(`no token account found for token operation ${operation.id}`); - return null; - } - } - return ( - - ); - })} - - - ))} - - {!groupedOperations.completed ? ( - - {t("common.showMore")} - - - ) : ( - - - {t("operationList.noMoreOperations")} - - - )} - - ); - } -} - -export default compose( - withTranslation(), - connect( - createStructuredSelector({ - allAccounts: accountsSelector, - }), - mapDispatchToProps, - ), -)(OperationsList); diff --git a/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.jsx b/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.jsx new file mode 100644 index 000000000000..d9f9d6fe789c --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/OperationsList/index.jsx @@ -0,0 +1,191 @@ +// @flow + +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import { connect } from "react-redux"; +import { compose } from "redux"; +import { withTranslation } from "react-i18next"; +import type { TFunction } from "react-i18next"; +import type { Operation, Account, AccountLike } from "@ledgerhq/live-common/types/index"; +import keyBy from "lodash/keyBy"; +import { + groupAccountOperationsByDay, + groupAccountsOperationsByDay, + flattenAccounts, +} from "@ledgerhq/live-common/account/index"; +import logger from "~/logger"; +import { openModal } from "~/renderer/actions/modals"; +import IconAngleDown from "~/renderer/icons/AngleDown"; +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import { track } from "~/renderer/analytics/segment"; +import { createStructuredSelector } from "reselect"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import SectionTitle from "./SectionTitle"; +import OperationC from "./Operation"; +import TableContainer, { TableHeader } from "../TableContainer"; +import { OperationDetails } from "~/renderer/drawers/OperationDetails"; +import { setDrawer } from "~/renderer/drawers/Provider"; + +const ShowMore = styled(Box).attrs(() => ({ + horizontal: true, + flow: 1, + ff: "Inter|SemiBold", + fontSize: 3, + justifyContent: "center", + alignItems: "center", + p: 3, + color: "wallet", +}))` + &:hover { + text-decoration: underline; + } +`; + +const mapDispatchToProps = { + openModal, +}; + +type Props = { + account: AccountLike, + parentAccount?: ?Account, + accounts: AccountLike[], + allAccounts: AccountLike[], + openModal: (string, Object) => *, + t: TFunction, + withAccount?: boolean, + withSubAccounts?: boolean, + title?: string, + filterOperation?: (Operation, AccountLike) => boolean, +}; + +type State = { + nbToShow: number, +}; + +const initialState = { + nbToShow: 20, +}; + +export class OperationsList extends PureComponent { + static defaultProps = { + withAccount: false, + }; + + state = initialState; + + handleClickOperation = (operation: Operation, account: AccountLike, parentAccount?: Account) => + setDrawer(OperationDetails, { + operationId: operation.id, + accountId: account.id, + parentId: parentAccount && parentAccount.id, + }); + + // TODO: convert of async/await if fetching with the api + fetchMoreOperations = () => { + track("FetchMoreOperations"); + this.setState({ nbToShow: this.state.nbToShow + 20 }); + }; + + render() { + const { + account, + parentAccount, + accounts, + allAccounts, + t, + title, + withAccount, + withSubAccounts, + filterOperation, + } = this.props; + const { nbToShow } = this.state; + + if (!account && !accounts) { + console.warn("Preventing render OperationsList because not received account or accounts"); // eslint-disable-line no-console + return null; + } + + const groupedOperations = account + ? groupAccountOperationsByDay(account, { count: nbToShow, withSubAccounts, filterOperation }) + : groupAccountsOperationsByDay(accounts, { + count: nbToShow, + withSubAccounts, + filterOperation, + }); + + const all = flattenAccounts(accounts || []).concat([account, parentAccount].filter(Boolean)); + const accountsMap = keyBy(all, "id"); + + return ( + <> + + {title && ( + + )} + {groupedOperations.sections.map(group => ( + + + + {group.data.map(operation => { + const account = accountsMap[operation.accountId]; + if (!account) { + logger.warn(`no account found for operation ${operation.id}`); + return null; + } + let parentAccount; + if (account.type !== "Account") { + const pa = + accountsMap[account.parentId] || + allAccounts.find(a => a.id === account.parentId); + if (pa && pa.type === "Account") { + parentAccount = pa; + } + if (!parentAccount) { + logger.warn(`no token account found for token operation ${operation.id}`); + return null; + } + } + return ( + + ); + })} + + + ))} + + {!groupedOperations.completed ? ( + + {t("common.showMore")} + + + ) : ( + + + {t("operationList.noMoreOperations")} + + + )} + + ); + } +} + +export default compose( + withTranslation(), + connect( + createStructuredSelector({ + allAccounts: accountsSelector, + }), + mapDispatchToProps, + ), +)(OperationsList); diff --git a/apps/ledger-live-desktop/src/renderer/components/OptionRow.js b/apps/ledger-live-desktop/src/renderer/components/OptionRow.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/OptionRow.js rename to apps/ledger-live-desktop/src/renderer/components/OptionRow.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Page.js b/apps/ledger-live-desktop/src/renderer/components/Page.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Page.js rename to apps/ledger-live-desktop/src/renderer/components/Page.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.js b/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.js deleted file mode 100644 index 088e8a8b9cc2..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.js +++ /dev/null @@ -1,91 +0,0 @@ -// @flow -import React from "react"; -import { useTranslation } from "react-i18next"; -import styled, { withTheme } from "styled-components"; - -import type { Currency } from "@ledgerhq/live-common/lib/types"; - -import { rgba } from "~/renderer/styles/helpers"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import Tooltip from "~/renderer/components/Tooltip"; -import Text from "~/renderer/components/Text"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; - -const ParentCryptoCurrencyIconWrapper: ThemedComponent<{ - doubleIcon?: boolean, - bigger?: boolean, - flat?: boolean, -}> = styled.div` - ${p => - p.doubleIcon && !p.flat - ? ` - padding-right: 10px; - > :nth-child(2) { - position: absolute; - bottom: -8px; - left: 8px; - border: 2px solid transparent; - } - ` - : ` - display: flex; - align-items: center; - `} - position: relative; - line-height: ${p => (p.bigger ? "18px" : "18px")}; - font-size: 12px; - max-height: 25px; -`; -const TooltipWrapper = styled.div` - display: flex; - max-width: 150px; - flex-direction: column; -`; - -const CryptoCurrencyIconTooltip = withTheme(({ name, theme }: { theme: any, name: string }) => { - const { t } = useTranslation(); - return ( - - - {t("tokensList.tooltip")} - - {name} - - ); -}); - -type Props = { - currency: Currency, - withTooltip?: boolean, - bigger?: boolean, - inactive?: boolean, - flat?: boolean, -}; - -const ParentCryptoCurrencyIcon = ({ - currency, - withTooltip, - bigger, - inactive, - flat = false, -}: Props) => { - const parent = currency.type === "TokenCurrency" ? currency.parentCurrency : null; - - const content = ( - - {parent && ( - - )} - - - ); - - if (withTooltip && parent) { - return }>{content}; - } - - return content; -}; - -export default ParentCryptoCurrencyIcon; diff --git a/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.jsx b/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.jsx new file mode 100644 index 000000000000..b7d4eeb5c59e --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ParentCryptoCurrencyIcon.jsx @@ -0,0 +1,91 @@ +// @flow +import React from "react"; +import { useTranslation } from "react-i18next"; +import styled, { withTheme } from "styled-components"; + +import type { Currency } from "@ledgerhq/live-common/types/index"; + +import { rgba } from "~/renderer/styles/helpers"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import Tooltip from "~/renderer/components/Tooltip"; +import Text from "~/renderer/components/Text"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; + +const ParentCryptoCurrencyIconWrapper: ThemedComponent<{ + doubleIcon?: boolean, + bigger?: boolean, + flat?: boolean, +}> = styled.div` + ${p => + p.doubleIcon && !p.flat + ? ` + padding-right: 10px; + > :nth-child(2) { + position: absolute; + bottom: -8px; + left: 8px; + border: 2px solid transparent; + } + ` + : ` + display: flex; + align-items: center; + `} + position: relative; + line-height: ${p => (p.bigger ? "18px" : "18px")}; + font-size: 12px; + max-height: 25px; +`; +const TooltipWrapper = styled.div` + display: flex; + max-width: 150px; + flex-direction: column; +`; + +const CryptoCurrencyIconTooltip = withTheme(({ name, theme }: { theme: any, name: string }) => { + const { t } = useTranslation(); + return ( + + + {t("tokensList.tooltip")} + + {name} + + ); +}); + +type Props = { + currency: Currency, + withTooltip?: boolean, + bigger?: boolean, + inactive?: boolean, + flat?: boolean, +}; + +const ParentCryptoCurrencyIcon = ({ + currency, + withTooltip, + bigger, + inactive, + flat = false, +}: Props) => { + const parent = currency.type === "TokenCurrency" ? currency.parentCurrency : null; + + const content = ( + + {parent && ( + + )} + + + ); + + if (withTooltip && parent) { + return }>{content}; + } + + return content; +}; + +export default ParentCryptoCurrencyIcon; diff --git a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.js b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.js deleted file mode 100644 index 9e97ac824042..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.js +++ /dev/null @@ -1,141 +0,0 @@ -// @flow - -import React from "react"; -import styled from "styled-components"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import { - getAccountCurrency, - getAccountName, - getAccountUnit, -} from "@ledgerhq/live-common/lib/account"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import Box from "~/renderer/components/Box"; -import useTheme from "~/renderer/hooks/useTheme"; -import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; -import type { Account, AccountLike, SubAccount } from "@ledgerhq/live-common/lib/types/account"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const OptionContainer: ThemedComponent<{}> = styled.div` - display: flex; - flex: 1; - flex-direction: column; -`; - -const AccountRowContainer: ThemedComponent<{ - nested?: boolean, - disabled?: boolean, -}> = styled.div.attrs(p => ({ - style: { - paddingTop: p.nested ? 3 : 0, - color: p.disabled ? p.theme.colors.palette.text.shade40 : "inherit", - }, -}))` - display: flex; - flex-direction: row; - flex-grow: 1; - justify-content: space-between; -`; - -const NestingIndicator: ThemedComponent<{}> = styled.div` - padding-right: 16px; - border-left: ${p => p.theme.colors.palette.text.shade40} 1px solid; - margin-left: 6px; -`; - -const Left: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: row; - flex: 1; -`; - -const Right: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: row; -`; - -const IconContainer: ThemedComponent<{}> = styled.div` - padding-right: 6px; -`; - -function AccountRow({ - account, - parentAccount, - nested, - disabled, -}: { - account: AccountLike, - parentAccount?: Account, - nested?: boolean, - disabled?: boolean, -}) { - const palette = useTheme("colors.palette"); - - const balance = - account.type !== "ChildAccount" && account.spendableBalance - ? account.spendableBalance - : account.balance; - - return ( - - - {nested ? : null} - - {parentAccount ? ( - - ) : ( - - )} - - - - {getAccountName(account)} - - - - - - - - ); -} - -type Props = { - account: Account, - subAccount?: SubAccount, - isValue?: boolean, -}; - -export function MenuOption({ account, subAccount, isValue }: Props) { - if (isValue) { - return ( - - {subAccount ? ( - - ) : ( - - )} - - ); - } - - return ( - - - {subAccount ? : null} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.jsx b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.jsx new file mode 100644 index 000000000000..724e91866719 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/Option.jsx @@ -0,0 +1,141 @@ +// @flow + +import React from "react"; +import styled from "styled-components"; +import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; +import { + getAccountCurrency, + getAccountName, + getAccountUnit, +} from "@ledgerhq/live-common/account/index"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import Box from "~/renderer/components/Box"; +import useTheme from "~/renderer/hooks/useTheme"; +import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; +import type { Account, AccountLike, SubAccount } from "@ledgerhq/live-common/types/account"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const OptionContainer: ThemedComponent<{}> = styled.div` + display: flex; + flex: 1; + flex-direction: column; +`; + +const AccountRowContainer: ThemedComponent<{ + nested?: boolean, + disabled?: boolean, +}> = styled.div.attrs(p => ({ + style: { + paddingTop: p.nested ? 3 : 0, + color: p.disabled ? p.theme.colors.palette.text.shade40 : "inherit", + }, +}))` + display: flex; + flex-direction: row; + flex-grow: 1; + justify-content: space-between; +`; + +const NestingIndicator: ThemedComponent<{}> = styled.div` + padding-right: 16px; + border-left: ${p => p.theme.colors.palette.text.shade40} 1px solid; + margin-left: 6px; +`; + +const Left: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: row; + flex: 1; +`; + +const Right: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: row; +`; + +const IconContainer: ThemedComponent<{}> = styled.div` + padding-right: 6px; +`; + +function AccountRow({ + account, + parentAccount, + nested, + disabled, +}: { + account: AccountLike, + parentAccount?: Account, + nested?: boolean, + disabled?: boolean, +}) { + const palette = useTheme("colors.palette"); + + const balance = + account.type !== "ChildAccount" && account.spendableBalance + ? account.spendableBalance + : account.balance; + + return ( + + + {nested ? : null} + + {parentAccount ? ( + + ) : ( + + )} + + + + {getAccountName(account)} + + + + + + + + ); +} + +type Props = { + account: Account, + subAccount?: SubAccount, + isValue?: boolean, +}; + +export function MenuOption({ account, subAccount, isValue }: Props) { + if (isValue) { + return ( + + {subAccount ? ( + + ) : ( + + )} + + ); + } + + return ( + + + {subAccount ? : null} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/index.js b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/index.js deleted file mode 100644 index 501aaa1c81f7..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/index.js +++ /dev/null @@ -1,135 +0,0 @@ -// @flow - -import { getAccountCurrency, getAccountName } from "@ledgerhq/live-common/lib/account"; -import type { TFunction } from "react-i18next"; -import type { Account } from "@ledgerhq/live-common/lib/types"; -import React, { useCallback, useState } from "react"; -import { withTranslation } from "react-i18next"; -import { connect } from "react-redux"; -import { createFilter } from "react-select"; -import { createStructuredSelector } from "reselect"; -import { shallowAccountsSelector } from "~/renderer/reducers/accounts"; -import Select from "~/renderer/components/Select"; -import type { SubAccount } from "@ledgerhq/live-common/lib/types/account"; -import { MenuOption } from "~/renderer/components/PerCurrencySelectAccount/Option"; -import type { AccountTuple } from "~/renderer/components/PerCurrencySelectAccount/state"; - -const mapStateToProps = createStructuredSelector({ - accounts: shallowAccountsSelector, -}); - -type Option = { - matched: "boolean", - account: Account, - subAccount: SubAccount, -}; -const getOptionValue = option => { - return option.account ? option.account.id : null; -}; - -const defaultFilter = createFilter({ - stringify: ({ data: account }) => { - const currency = getAccountCurrency(account); - const name = getAccountName(account); - return `${currency.ticker}|${currency.name}|${name}`; - }, -}); -const filterOption = o => (candidate, input) => { - const selfMatches = defaultFilter(candidate, input); - if (selfMatches) return [selfMatches, true]; - return [false, false]; -}; - -const AccountOption = React.memo(function AccountOption({ - account, - subAccount, - isValue, -}: { - account: Account, - subAccount: SubAccount, - isValue?: boolean, -}) { - return ; -}); - -type OwnProps = { - value: AccountTuple, - onChange: (account: ?Account, subAccount: ?SubAccount) => void, - accounts: AccountTuple[], -}; - -type Props = { - ...OwnProps, - t: TFunction, -}; - -const RawSelectAccount = ({ accounts, value, onChange, t, ...props }: Props) => { - const [searchInputValue, setSearchInputValue] = useState(""); - - const renderValue = ({ data }: { data: Option }) => { - return data.account ? ( - - ) : null; - }; - - const renderOption = ({ data }: { data: Option }) => { - return data.account ? ( - - ) : null; - }; - - const onChangeCallback = useCallback( - (option?: Option) => { - if (option) { - onChange(option.account, option.subAccount); - } else { - onChange(null, null); - } - }, - [onChange], - ); - - const manualFilter = useCallback( - () => - accounts.reduce((result, option) => { - const [display, match] = filterOption({})({ data: option.account }, searchInputValue); - - if (display) { - result.push({ - matched: match, - account: option.account, - subAccount: option.subAccount, - }); - } - return result; - }, []), - [searchInputValue, accounts], - ); - const structuredResults = manualFilter(); - return ( - setSearchInputValue(v)} + inputValue={searchInputValue} + filterOption={false} + isOptionDisabled={option => !option.matched} + placeholder={t("common.selectAccount")} + noOptionsMessage={({ inputValue }) => + t("common.selectAccountNoOption", { accountName: inputValue }) + } + onChange={onChangeCallback} + /> + ); +}; + +export const SelectAccount: React$ComponentType = withTranslation()(RawSelectAccount); + +const m: React$ComponentType = connect(mapStateToProps)(SelectAccount); + +export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/state.js b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/state.js index 0f0c36c45502..a343429022f5 100644 --- a/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/state.js +++ b/apps/ledger-live-desktop/src/renderer/components/PerCurrencySelectAccount/state.js @@ -1,9 +1,9 @@ // @flow import { useState, useCallback, useMemo, useEffect } from "react"; -import type { Account, SubAccount } from "@ledgerhq/live-common/lib/types/account"; -import { makeEmptyTokenAccount } from "@ledgerhq/live-common/lib/account"; -import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/lib/types/currencies"; +import type { Account, SubAccount } from "@ledgerhq/live-common/types/account"; +import { makeEmptyTokenAccount } from "@ledgerhq/live-common/account/index"; +import type { CryptoCurrency, TokenCurrency } from "@ledgerhq/live-common/types/currencies"; export type AccountTuple = { account: ?Account, diff --git a/apps/ledger-live-desktop/src/renderer/components/PerfIndicator.js b/apps/ledger-live-desktop/src/renderer/components/PerfIndicator.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/PerfIndicator.js rename to apps/ledger-live-desktop/src/renderer/components/PerfIndicator.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Pills.js b/apps/ledger-live-desktop/src/renderer/components/Pills.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Pills.js rename to apps/ledger-live-desktop/src/renderer/components/Pills.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/PillsDaysCount.js b/apps/ledger-live-desktop/src/renderer/components/PillsDaysCount.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/PillsDaysCount.js rename to apps/ledger-live-desktop/src/renderer/components/PillsDaysCount.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.js b/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.js deleted file mode 100644 index 859a39f726bf..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.js +++ /dev/null @@ -1,47 +0,0 @@ -// @flow - -import React, { Component } from "react"; -import { BigNumber } from "bignumber.js"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/portfolio/v2/types"; -import Chart from "~/renderer/components/Chart"; -import { withTheme } from "styled-components"; - -type Props = { - chartId: string, - data: Array<*>, - tickXScale: PortfolioRange, - theme: any, - magnitude: number, -}; - -class PlaceholderChart extends Component { - shouldComponentUpdate(next: Props) { - return next.tickXScale !== this.props.tickXScale; - } - - render() { - const { chartId, data, tickXScale, theme } = this.props; - const themeType = theme.colors.palette.type; - return ( - ({ - ...i, - value: BigNumber( - 10000 * - (1 + - 0.1 * Math.sin(i.date * Math.cos(Number(i.date))) + // random-ish - 0.5 * Math.cos(i.date / 2000000000 + Math.sin(i.date / 1000000000))), - ), // general curve trend - }))} - height={200} - tickXScale={tickXScale} - renderTickY={() => ""} - magnitude={this.props.magnitude} - /> - ); - } -} - -export default withTheme(PlaceholderChart); diff --git a/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.jsx b/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.jsx new file mode 100644 index 000000000000..0e192c7e90ef --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/PlaceholderChart.jsx @@ -0,0 +1,47 @@ +// @flow + +import React, { Component } from "react"; +import { BigNumber } from "bignumber.js"; +import type { PortfolioRange } from "@ledgerhq/live-common/portfolio/v2/types"; +import Chart from "~/renderer/components/Chart"; +import { withTheme } from "styled-components"; + +type Props = { + chartId: string, + data: Array<*>, + tickXScale: PortfolioRange, + theme: any, + magnitude: number, +}; + +class PlaceholderChart extends Component { + shouldComponentUpdate(next: Props) { + return next.tickXScale !== this.props.tickXScale; + } + + render() { + const { chartId, data, tickXScale, theme } = this.props; + const themeType = theme.colors.palette.type; + return ( + ({ + ...i, + value: BigNumber( + 10000 * + (1 + + 0.1 * Math.sin(i.date * Math.cos(Number(i.date))) + // random-ish + 0.5 * Math.cos(i.date / 2000000000 + Math.sin(i.date / 1000000000))), + ), // general curve trend + }))} + height={200} + tickXScale={tickXScale} + renderTickY={() => ""} + magnitude={this.props.magnitude} + /> + ); + } +} + +export default withTheme(PlaceholderChart); diff --git a/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.js b/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.js deleted file mode 100644 index cc963fc07e84..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.js +++ /dev/null @@ -1,75 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import styled, { css } from "styled-components"; - -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; - -import { rgba } from "~/renderer/styles/helpers"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import { Tabbable } from "~/renderer/components/Box"; -import AppDetails, { IconContainer } from "./AppDetails"; - -const Container: ThemedComponent<{ isActive?: boolean, disabled?: boolean }> = styled( - Tabbable, -).attrs(p => ({ - flex: 1, - flexDirection: "column", - alignItems: "center", - fontSize: 4, -}))` - min-height: 180px; - padding: 24px; - border-radius: 4px; - cursor: ${p => (p.disabled ? "default" : "pointer")}; - background: ${p => p.theme.colors.palette.background.paper}; - color: ${p => p.theme.colors.palette.text.shade100}; - border: 1px solid ${p => p.theme.colors.palette.divider}; - - box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.04); - - ${p => - p.disabled - ? css` - background: ${p.theme.colors.palette.text.shade10}; - opacity: 0.5; - - ${IconContainer} { - filter: grayscale(100%); - } - ` - : css` - &:hover, - &:focus { - ${p => - css` - box-shadow: 0px 0px 0px 4px ${rgba(p.theme.colors.palette.primary.main, 0.25)}; - border: ${p => `1px solid ${p.theme.colors.palette.primary.main}`}; - `} - } - `} -`; - -type Props = { - manifest: AppManifest, - onClick: Function, -}; - -const AppCard = ({ manifest, onClick, ...rest }: Props) => { - const isDisabled = manifest.branch === "soon"; - - const handleClick = useCallback(() => { - if (!isDisabled) { - onClick(); - } - }, [onClick, isDisabled]); - - return ( - - - - ); -}; - -export default AppCard; diff --git a/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.jsx b/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.jsx new file mode 100644 index 000000000000..e30615279058 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Platform/AppCard.jsx @@ -0,0 +1,75 @@ +// @flow + +import React, { useCallback } from "react"; +import styled, { css } from "styled-components"; + +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; + +import { rgba } from "~/renderer/styles/helpers"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import { Tabbable } from "~/renderer/components/Box"; +import AppDetails, { IconContainer } from "./AppDetails"; + +const Container: ThemedComponent<{ isActive?: boolean, disabled?: boolean }> = styled( + Tabbable, +).attrs(p => ({ + flex: 1, + flexDirection: "column", + alignItems: "center", + fontSize: 4, +}))` + min-height: 180px; + padding: 24px; + border-radius: 4px; + cursor: ${p => (p.disabled ? "default" : "pointer")}; + background: ${p => p.theme.colors.palette.background.paper}; + color: ${p => p.theme.colors.palette.text.shade100}; + border: 1px solid ${p => p.theme.colors.palette.divider}; + + box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.04); + + ${p => + p.disabled + ? css` + background: ${p.theme.colors.palette.text.shade10}; + opacity: 0.5; + + ${IconContainer} { + filter: grayscale(100%); + } + ` + : css` + &:hover, + &:focus { + ${p => + css` + box-shadow: 0px 0px 0px 4px ${rgba(p.theme.colors.palette.primary.main, 0.25)}; + border: ${p => `1px solid ${p.theme.colors.palette.primary.main}`}; + `} + } + `} +`; + +type Props = { + manifest: AppManifest, + onClick: Function, +}; + +const AppCard = ({ manifest, onClick, ...rest }: Props) => { + const isDisabled = manifest.branch === "soon"; + + const handleClick = useCallback(() => { + if (!isDisabled) { + onClick(); + } + }, [onClick, isDisabled]); + + return ( + + + + ); +}; + +export default AppCard; diff --git a/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.js b/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.js deleted file mode 100644 index 08adbdb78072..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.js +++ /dev/null @@ -1,109 +0,0 @@ -// @flow - -import React from "react"; -import { useTranslation } from "react-i18next"; -import styled from "styled-components"; - -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; - -import Box from "~/renderer/components/Box"; -import LiveAppIcon from "~/renderer/components/WebPlatformPlayer/LiveAppIcon"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const getBranchColor = (branch, colors) => { - switch (branch) { - case "soon": - return colors.palette.text.shade100; - case "experimental": - return colors.warning; - case "debug": - return colors.palette.text.shade40; - default: - return "currentColor"; - } -}; - -const HeaderContainer: ThemedComponent<{}> = styled(Box)` - width: 100%; - flex-direction: row; - align-items: center; -`; - -export const IconContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ mr: 2 }))` - user-select: none; - pointer-events: none; -`; - -const TitleContainer: ThemedComponent<{}> = styled.div` - flex-shrink: 1; -`; - -const AppName: ThemedComponent<{}> = styled(Box).attrs(p => ({ - ff: "Inter|SemiBold", - fontSize: 5, - textAlign: "left", - color: p.theme.colors.palette.secondary.main, -}))` - line-height: 18px; -`; - -const Content: ThemedComponent<{}> = styled(Box)` - margin-top: 16px; - width: 100%; - - :empty { - display: none; - } -`; - -const BranchBadge: ThemedComponent<{}> = styled(Box).attrs(p => ({ - ff: "Inter|SemiBold", - fontSize: 1, - color: getBranchColor(p.branch, p.theme.colors), -}))` - display: inline-block; - padding: 1px 4px; - border: 1px solid currentColor; - border-radius: 3px; - text-transform: uppercase; - margin-bottom: 4px; - flex-grow: 0; - flex-shrink: 1; - - ${p => - p.branch === "soon" && - ` - background: ${p.theme.colors.palette.text.shade20}; - border-width: 0; - `} -`; - -type Props = { - manifest: AppManifest, -}; - -const AppDetails = ({ manifest }: Props) => { - const { t } = useTranslation(); - const description = manifest.content.description.en; - - return ( - <> - - - - - - {manifest.branch !== "stable" && ( - - {t(`platform.catalog.branch.${manifest.branch}`)} - - )} - {manifest.name} - - - {description} - - ); -}; - -export default AppDetails; diff --git a/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.jsx b/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.jsx new file mode 100644 index 000000000000..0053a7d74d01 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Platform/AppDetails.jsx @@ -0,0 +1,109 @@ +// @flow + +import React from "react"; +import { useTranslation } from "react-i18next"; +import styled from "styled-components"; + +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; + +import Box from "~/renderer/components/Box"; +import LiveAppIcon from "~/renderer/components/WebPlatformPlayer/LiveAppIcon"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const getBranchColor = (branch, colors) => { + switch (branch) { + case "soon": + return colors.palette.text.shade100; + case "experimental": + return colors.warning; + case "debug": + return colors.palette.text.shade40; + default: + return "currentColor"; + } +}; + +const HeaderContainer: ThemedComponent<{}> = styled(Box)` + width: 100%; + flex-direction: row; + align-items: center; +`; + +export const IconContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ mr: 2 }))` + user-select: none; + pointer-events: none; +`; + +const TitleContainer: ThemedComponent<{}> = styled.div` + flex-shrink: 1; +`; + +const AppName: ThemedComponent<{}> = styled(Box).attrs(p => ({ + ff: "Inter|SemiBold", + fontSize: 5, + textAlign: "left", + color: p.theme.colors.palette.secondary.main, +}))` + line-height: 18px; +`; + +const Content: ThemedComponent<{}> = styled(Box)` + margin-top: 16px; + width: 100%; + + :empty { + display: none; + } +`; + +const BranchBadge: ThemedComponent<{}> = styled(Box).attrs(p => ({ + ff: "Inter|SemiBold", + fontSize: 1, + color: getBranchColor(p.branch, p.theme.colors), +}))` + display: inline-block; + padding: 1px 4px; + border: 1px solid currentColor; + border-radius: 3px; + text-transform: uppercase; + margin-bottom: 4px; + flex-grow: 0; + flex-shrink: 1; + + ${p => + p.branch === "soon" && + ` + background: ${p.theme.colors.palette.text.shade20}; + border-width: 0; + `} +`; + +type Props = { + manifest: AppManifest, +}; + +const AppDetails = ({ manifest }: Props) => { + const { t } = useTranslation(); + const description = manifest.content.description.en; + + return ( + <> + + + + + + {manifest.branch !== "stable" && ( + + {t(`platform.catalog.branch.${manifest.branch}`)} + + )} + {manifest.name} + + + {description} + + ); +}; + +export default AppDetails; diff --git a/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.js b/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.js deleted file mode 100644 index 4da990feb078..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.js +++ /dev/null @@ -1,30 +0,0 @@ -// @flow -import React from "react"; -import { catalogProviderSelector } from "~/renderer/reducers/settings"; -import { useSelector } from "react-redux"; -import { RemoteLiveAppProvider } from "@ledgerhq/live-common/lib/platform/providers/RemoteLiveAppProvider"; -import { LocalLiveAppProvider } from "@ledgerhq/live-common/lib/platform/providers/LocalLiveAppProvider"; -import { GlobalCatalogProvider } from "@ledgerhq/live-common/lib/platform/providers/GlobalCatalogProvider"; -import { RampCatalogProvider } from "@ledgerhq/live-common/lib/platform/providers/RampCatalogProvider"; - -type Props = { - children: React$Node, -}; - -const AUTO_UPDATE_DEFAULT_DELAY = 1800 * 1000; // 1800 seconds - -export function PlatformAppProviderWrapper({ children }: Props) { - const provider = useSelector(catalogProviderSelector); - - return ( - - - - - {children} - - - - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.jsx b/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.jsx new file mode 100644 index 000000000000..22507d565a74 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/PlatformAppProviderWrapper.jsx @@ -0,0 +1,30 @@ +// @flow +import React from "react"; +import { catalogProviderSelector } from "~/renderer/reducers/settings"; +import { useSelector } from "react-redux"; +import { RemoteLiveAppProvider } from "@ledgerhq/live-common/platform/providers/RemoteLiveAppProvider/index"; +import { LocalLiveAppProvider } from "@ledgerhq/live-common/platform/providers/LocalLiveAppProvider/index"; +import { GlobalCatalogProvider } from "@ledgerhq/live-common/platform/providers/GlobalCatalogProvider/index"; +import { RampCatalogProvider } from "@ledgerhq/live-common/platform/providers/RampCatalogProvider/index"; + +type Props = { + children: React$Node, +}; + +const AUTO_UPDATE_DEFAULT_DELAY = 1800 * 1000; // 1800 seconds + +export function PlatformAppProviderWrapper({ children }: Props) { + const provider = useSelector(catalogProviderSelector); + + return ( + + + + + {children} + + + + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/Popover.js b/apps/ledger-live-desktop/src/renderer/components/Popover.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Popover.js rename to apps/ledger-live-desktop/src/renderer/components/Popover.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Price.js b/apps/ledger-live-desktop/src/renderer/components/Price.js deleted file mode 100644 index 61c323266c3b..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Price.js +++ /dev/null @@ -1,118 +0,0 @@ -// @flow - -import React, { useMemo } from "react"; -import styled from "styled-components"; -import { useSelector } from "react-redux"; -import { BigNumber } from "bignumber.js"; -import type { Currency, Unit } from "@ledgerhq/live-common/lib/types/currencies"; -import { useCalculate } from "@ledgerhq/live-common/lib/countervalues/react"; -import { getCurrencyColor } from "~/renderer/getCurrencyColor"; -import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; -import { colors } from "~/renderer/styles/theme"; -import useTheme from "~/renderer/hooks/useTheme"; -import Box from "~/renderer/components/Box"; -import CurrencyUnitValue from "~/renderer/components/CurrencyUnitValue"; -import IconActivity from "~/renderer/icons/Activity"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { NoCountervaluePlaceholder } from "./CounterValue"; - -type Props = { - unit?: Unit, - rate?: BigNumber, - showAllDigits?: boolean, - from: Currency, - to?: Currency, - withActivityCurrencyColor?: boolean, - withActivityColor?: string, - withIcon?: boolean, - withEquality?: boolean, - date?: Date, - color?: string, - fontSize?: number, - fontWeight?: number, - iconSize?: number, - placeholder?: React$Node, -}; - -export default function Price({ - from, - to, - unit, - date, - withActivityCurrencyColor, - withActivityColor, - withEquality, - placeholder, - color, - fontSize, - fontWeight, - iconSize, - showAllDigits, - withIcon = true, - rate, -}: Props) { - const effectiveUnit = unit || from.units[0]; - const valueNum = 10 ** effectiveUnit.magnitude; - const rawCounterValueCurrency = useSelector(counterValueCurrencySelector); - const counterValueCurrency = to || rawCounterValueCurrency; - const rawCounterValue = useCalculate({ - from, - to: counterValueCurrency, - value: valueNum, - disableRounding: true, - }); - - const counterValue = rate - ? rate.times(valueNum) // NB Allow to override the rate for swap - : typeof rawCounterValue === "number" - ? BigNumber(rawCounterValue) - : rawCounterValue; - - const bgColor = useTheme("colors.palette.background.paper"); - const activityColor = useMemo( - () => - withActivityColor - ? colors[withActivityColor] - : !withActivityCurrencyColor - ? color - ? colors[color] - : undefined - : getCurrencyColor(from, bgColor), - [bgColor, color, from, withActivityColor, withActivityCurrencyColor], - ); - - if (!counterValue || counterValue.isZero()) - return ; - - const subMagnitude = counterValue.lt(1) || showAllDigits ? 1 : 0; - - return ( - - {withIcon ? ( - - ) : null} - {!withEquality ? null : ( - <> - - {" = "} - - )} - - - ); -} - -const PriceWrapper: ThemedComponent<{}> = styled(Box).attrs(() => ({ - ff: "Inter", - horizontal: true, -}))` - line-height: 1.2; - white-space: pre; - align-items: baseline; -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/Price.jsx b/apps/ledger-live-desktop/src/renderer/components/Price.jsx new file mode 100644 index 000000000000..1b93beb6243f --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Price.jsx @@ -0,0 +1,118 @@ +// @flow + +import React, { useMemo } from "react"; +import styled from "styled-components"; +import { useSelector } from "react-redux"; +import { BigNumber } from "bignumber.js"; +import type { Currency, Unit } from "@ledgerhq/live-common/types/currencies"; +import { useCalculate } from "@ledgerhq/live-common/countervalues/react"; +import { getCurrencyColor } from "~/renderer/getCurrencyColor"; +import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; +import { colors } from "~/renderer/styles/theme"; +import useTheme from "~/renderer/hooks/useTheme"; +import Box from "~/renderer/components/Box"; +import CurrencyUnitValue from "~/renderer/components/CurrencyUnitValue"; +import IconActivity from "~/renderer/icons/Activity"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { NoCountervaluePlaceholder } from "./CounterValue"; + +type Props = { + unit?: Unit, + rate?: BigNumber, + showAllDigits?: boolean, + from: Currency, + to?: Currency, + withActivityCurrencyColor?: boolean, + withActivityColor?: string, + withIcon?: boolean, + withEquality?: boolean, + date?: Date, + color?: string, + fontSize?: number, + fontWeight?: number, + iconSize?: number, + placeholder?: React$Node, +}; + +export default function Price({ + from, + to, + unit, + date, + withActivityCurrencyColor, + withActivityColor, + withEquality, + placeholder, + color, + fontSize, + fontWeight, + iconSize, + showAllDigits, + withIcon = true, + rate, +}: Props) { + const effectiveUnit = unit || from.units[0]; + const valueNum = 10 ** effectiveUnit.magnitude; + const rawCounterValueCurrency = useSelector(counterValueCurrencySelector); + const counterValueCurrency = to || rawCounterValueCurrency; + const rawCounterValue = useCalculate({ + from, + to: counterValueCurrency, + value: valueNum, + disableRounding: true, + }); + + const counterValue = rate + ? rate.times(valueNum) // NB Allow to override the rate for swap + : typeof rawCounterValue === "number" + ? BigNumber(rawCounterValue) + : rawCounterValue; + + const bgColor = useTheme("colors.palette.background.paper"); + const activityColor = useMemo( + () => + withActivityColor + ? colors[withActivityColor] + : !withActivityCurrencyColor + ? color + ? colors[color] + : undefined + : getCurrencyColor(from, bgColor), + [bgColor, color, from, withActivityColor, withActivityCurrencyColor], + ); + + if (!counterValue || counterValue.isZero()) + return ; + + const subMagnitude = counterValue.lt(1) || showAllDigits ? 1 : 0; + + return ( + + {withIcon ? ( + + ) : null} + {!withEquality ? null : ( + <> + + {" = "} + + )} + + + ); +} + +const PriceWrapper: ThemedComponent<{}> = styled(Box).attrs(() => ({ + ff: "Inter", + horizontal: true, +}))` + line-height: 1.2; + white-space: pre; + align-items: baseline; +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/Progress.js b/apps/ledger-live-desktop/src/renderer/components/Progress.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Progress.js rename to apps/ledger-live-desktop/src/renderer/components/Progress.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ProgressCircle.js b/apps/ledger-live-desktop/src/renderer/components/ProgressCircle.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ProgressCircle.js rename to apps/ledger-live-desktop/src/renderer/components/ProgressCircle.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/QRCode.js b/apps/ledger-live-desktop/src/renderer/components/QRCode.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/QRCode.js rename to apps/ledger-live-desktop/src/renderer/components/QRCode.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/QRCodeCameraPickerCanvas.js b/apps/ledger-live-desktop/src/renderer/components/QRCodeCameraPickerCanvas.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/QRCodeCameraPickerCanvas.js rename to apps/ledger-live-desktop/src/renderer/components/QRCodeCameraPickerCanvas.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/RadioGroup.js b/apps/ledger-live-desktop/src/renderer/components/RadioGroup.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/RadioGroup.js rename to apps/ledger-live-desktop/src/renderer/components/RadioGroup.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ReadOnlyAddressField.js b/apps/ledger-live-desktop/src/renderer/components/ReadOnlyAddressField.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ReadOnlyAddressField.js rename to apps/ledger-live-desktop/src/renderer/components/ReadOnlyAddressField.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Receive2NoDevice.js b/apps/ledger-live-desktop/src/renderer/components/Receive2NoDevice.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Receive2NoDevice.js rename to apps/ledger-live-desktop/src/renderer/components/Receive2NoDevice.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.js b/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.js deleted file mode 100644 index 233fb9ece01e..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.js +++ /dev/null @@ -1,119 +0,0 @@ -// @flow - -import type { BigNumber } from "bignumber.js"; -import React, { PureComponent } from "react"; -import styled from "styled-components"; -import noop from "lodash/noop"; -import { decodeURIScheme } from "@ledgerhq/live-common/lib/currencies"; -import type { CryptoCurrency } from "@ledgerhq/live-common/lib/types"; - -import { radii } from "~/renderer/styles/theme"; - -import QRCodeCameraPickerCanvas from "~/renderer/components/QRCodeCameraPickerCanvas"; -import Box from "~/renderer/components/Box"; -import Input from "~/renderer/components/Input"; -import { track } from "~/renderer/analytics/segment"; - -import IconQrCode from "~/renderer/icons/QrCode"; - -const Right = styled(Box).attrs(() => ({ - bg: "palette.background.default", - px: 3, - alignItems: "center", - justifyContent: "center", -}))` - border-top-right-radius: ${radii[1]}px; - border-bottom-right-radius: ${radii[1]}px; - border-left: 1px solid ${p => p.theme.colors.palette.divider}; -`; - -const WrapperQrCode = styled(Box)` - margin-bottom: 10px; - position: absolute; - right: 0; - bottom: 100%; - z-index: 3; -`; - -const BackgroundLayer = styled(Box)` - position: fixed; - right: 0; - top: 0; - width: 100%; - height: 100%; - z-index: 2; -`; - -type Props = { - value: string, - // return false if it can't be changed (invalid info) - onChange: (string, ?{ amount?: BigNumber, currency?: CryptoCurrency }) => Promise, - withQrCode: boolean, -}; - -type State = { - qrReaderOpened: boolean, -}; - -class RecipientAddress extends PureComponent { - static defaultProps = { - value: "", - onChange: noop, - withQrCode: true, - }; - - state = { - qrReaderOpened: false, - }; - - handleClickQrCode = () => { - const { qrReaderOpened } = this.state; - this.setState(prev => ({ - qrReaderOpened: !prev.qrReaderOpened, - })); - !qrReaderOpened ? track("Send Flow QR Code Opened") : track("Send Flow QR Code Closed"); - }; - - handleOnPick = (code: string) => { - const { address, ...rest } = decodeURIScheme(code); - // $FlowFixMe - Object.assign(rest, { fromQRCode: true }); - if (this.props.onChange(address, rest) !== false) { - this.setState({ qrReaderOpened: false }); - } - }; - - render() { - const { onChange, withQrCode, value, ...rest } = this.props; - const { qrReaderOpened } = this.state; - - const renderRight = withQrCode ? ( - - - {qrReaderOpened && ( - <> - - - - - - )} - - ) : null; - - const preOnChange = text => onChange((text && text.replace(/\s/g, "")) || ""); - return ( - - - - ); - } -} - -export default RecipientAddress; diff --git a/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.jsx b/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.jsx new file mode 100644 index 000000000000..701bbf76cc99 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/RecipientAddress.jsx @@ -0,0 +1,119 @@ +// @flow + +import type { BigNumber } from "bignumber.js"; +import React, { PureComponent } from "react"; +import styled from "styled-components"; +import noop from "lodash/noop"; +import { decodeURIScheme } from "@ledgerhq/live-common/currencies/index"; +import type { CryptoCurrency } from "@ledgerhq/live-common/types/index"; + +import { radii } from "~/renderer/styles/theme"; + +import QRCodeCameraPickerCanvas from "~/renderer/components/QRCodeCameraPickerCanvas"; +import Box from "~/renderer/components/Box"; +import Input from "~/renderer/components/Input"; +import { track } from "~/renderer/analytics/segment"; + +import IconQrCode from "~/renderer/icons/QrCode"; + +const Right = styled(Box).attrs(() => ({ + bg: "palette.background.default", + px: 3, + alignItems: "center", + justifyContent: "center", +}))` + border-top-right-radius: ${radii[1]}px; + border-bottom-right-radius: ${radii[1]}px; + border-left: 1px solid ${p => p.theme.colors.palette.divider}; +`; + +const WrapperQrCode = styled(Box)` + margin-bottom: 10px; + position: absolute; + right: 0; + bottom: 100%; + z-index: 3; +`; + +const BackgroundLayer = styled(Box)` + position: fixed; + right: 0; + top: 0; + width: 100%; + height: 100%; + z-index: 2; +`; + +type Props = { + value: string, + // return false if it can't be changed (invalid info) + onChange: (string, ?{ amount?: BigNumber, currency?: CryptoCurrency }) => Promise, + withQrCode: boolean, +}; + +type State = { + qrReaderOpened: boolean, +}; + +class RecipientAddress extends PureComponent { + static defaultProps = { + value: "", + onChange: noop, + withQrCode: true, + }; + + state = { + qrReaderOpened: false, + }; + + handleClickQrCode = () => { + const { qrReaderOpened } = this.state; + this.setState(prev => ({ + qrReaderOpened: !prev.qrReaderOpened, + })); + !qrReaderOpened ? track("Send Flow QR Code Opened") : track("Send Flow QR Code Closed"); + }; + + handleOnPick = (code: string) => { + const { address, ...rest } = decodeURIScheme(code); + // $FlowFixMe + Object.assign(rest, { fromQRCode: true }); + if (this.props.onChange(address, rest) !== false) { + this.setState({ qrReaderOpened: false }); + } + }; + + render() { + const { onChange, withQrCode, value, ...rest } = this.props; + const { qrReaderOpened } = this.state; + + const renderRight = withQrCode ? ( + + + {qrReaderOpened && ( + <> + + + + + + )} + + ) : null; + + const preOnChange = text => onChange((text && text.replace(/\s/g, "")) || ""); + return ( + + + + ); + } +} + +export default RecipientAddress; diff --git a/apps/ledger-live-desktop/src/renderer/components/RemoteConfig.js b/apps/ledger-live-desktop/src/renderer/components/RemoteConfig.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/RemoteConfig.js rename to apps/ledger-live-desktop/src/renderer/components/RemoteConfig.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/RenderError.js b/apps/ledger-live-desktop/src/renderer/components/RenderError.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/RenderError.js rename to apps/ledger-live-desktop/src/renderer/components/RenderError.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/RepairDeviceButton.js b/apps/ledger-live-desktop/src/renderer/components/RepairDeviceButton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/RepairDeviceButton.js rename to apps/ledger-live-desktop/src/renderer/components/RepairDeviceButton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/RequestAmount.js b/apps/ledger-live-desktop/src/renderer/components/RequestAmount.js deleted file mode 100644 index 7d04cde14a6c..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/RequestAmount.js +++ /dev/null @@ -1,129 +0,0 @@ -// @flow -import { BigNumber } from "bignumber.js"; -import React, { useCallback } from "react"; -import { useSelector } from "react-redux"; -import styled from "styled-components"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; -import { useSendAmount } from "@ledgerhq/live-common/lib/countervalues/react"; -import Box from "~/renderer/components/Box"; -import InputCurrency from "~/renderer/components/InputCurrency"; -import IconTransfer from "~/renderer/icons/Transfer"; -import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; -import TranslatedError from "./TranslatedError"; - -const ErrorContainer = styled(Box)` - margin-top: 0px; - font-size: 12px; - width: 100%; - transition: all 0.4s ease-in-out; - will-change: max-height; - max-height: ${p => (p.hasError ? 60 : 0)}px; - min-height: ${p => (p.hasError ? 20 : 0)}px; -`; - -const ErrorDisplay = styled(Box)` - color: ${p => p.theme.colors.pearl}; -`; - -const WarningDisplay = styled(Box)` - color: ${p => p.theme.colors.warning}; -`; - -type Props = { - autoFocus?: boolean, - // crypto value (always the one which is returned) - value: BigNumber, - disabled?: boolean, - validTransactionError?: ?Error, - validTransactionWarning?: ?Error, - // change handler - onChange: BigNumber => void, - // used to determine the crypto input unit - account: AccountLike, -}; - -export default function RequestAmount({ - onChange, - autoFocus, - disabled, - value: cryptoAmount, - account, - validTransactionError, - validTransactionWarning, -}: Props) { - const fiatCurrency = useSelector(counterValueCurrencySelector); - const { cryptoUnit, fiatAmount, fiatUnit, calculateCryptoAmount } = useSendAmount({ - account, - fiatCurrency, - cryptoAmount, - }); - - const onChangeFiatAmount = useCallback( - (fiatAmount: BigNumber) => { - const amount = calculateCryptoAmount(fiatAmount); - onChange(amount); - }, - [onChange, calculateCryptoAmount], - ); - - return ( - - - {cryptoUnit.code}} - /> - - - - {fiatUnit.code}} - showAllDigits - subMagnitude={3} - /> - - - {validTransactionError ? ( - - - - ) : validTransactionWarning ? ( - - - - ) : null} - - - ); -} - -const InputRight = styled(Box).attrs(() => ({ - ff: "Inter|Medium", - color: "palette.text.shade60", - fontSize: 4, - justifyContent: "center", -}))` - padding-right: 10px; -`; - -const InputCenter = styled(Box).attrs(() => ({ - alignItems: "center", - justifyContent: "center", - color: "palette.text.shade40", -}))` - margin-left: 19px; - margin-right: 19px; -`; diff --git a/apps/ledger-live-desktop/src/renderer/components/RequestAmount.jsx b/apps/ledger-live-desktop/src/renderer/components/RequestAmount.jsx new file mode 100644 index 000000000000..1e872883fa1f --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/RequestAmount.jsx @@ -0,0 +1,129 @@ +// @flow +import { BigNumber } from "bignumber.js"; +import React, { useCallback } from "react"; +import { useSelector } from "react-redux"; +import styled from "styled-components"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; +import { useSendAmount } from "@ledgerhq/live-common/countervalues/react"; +import Box from "~/renderer/components/Box"; +import InputCurrency from "~/renderer/components/InputCurrency"; +import IconTransfer from "~/renderer/icons/Transfer"; +import { counterValueCurrencySelector } from "~/renderer/reducers/settings"; +import TranslatedError from "./TranslatedError"; + +const ErrorContainer = styled(Box)` + margin-top: 0px; + font-size: 12px; + width: 100%; + transition: all 0.4s ease-in-out; + will-change: max-height; + max-height: ${p => (p.hasError ? 60 : 0)}px; + min-height: ${p => (p.hasError ? 20 : 0)}px; +`; + +const ErrorDisplay = styled(Box)` + color: ${p => p.theme.colors.pearl}; +`; + +const WarningDisplay = styled(Box)` + color: ${p => p.theme.colors.warning}; +`; + +type Props = { + autoFocus?: boolean, + // crypto value (always the one which is returned) + value: BigNumber, + disabled?: boolean, + validTransactionError?: ?Error, + validTransactionWarning?: ?Error, + // change handler + onChange: BigNumber => void, + // used to determine the crypto input unit + account: AccountLike, +}; + +export default function RequestAmount({ + onChange, + autoFocus, + disabled, + value: cryptoAmount, + account, + validTransactionError, + validTransactionWarning, +}: Props) { + const fiatCurrency = useSelector(counterValueCurrencySelector); + const { cryptoUnit, fiatAmount, fiatUnit, calculateCryptoAmount } = useSendAmount({ + account, + fiatCurrency, + cryptoAmount, + }); + + const onChangeFiatAmount = useCallback( + (fiatAmount: BigNumber) => { + const amount = calculateCryptoAmount(fiatAmount); + onChange(amount); + }, + [onChange, calculateCryptoAmount], + ); + + return ( + + + {cryptoUnit.code}} + /> + + + + {fiatUnit.code}} + showAllDigits + subMagnitude={3} + /> + + + {validTransactionError ? ( + + + + ) : validTransactionWarning ? ( + + + + ) : null} + + + ); +} + +const InputRight = styled(Box).attrs(() => ({ + ff: "Inter|Medium", + color: "palette.text.shade60", + fontSize: 4, + justifyContent: "center", +}))` + padding-right: 10px; +`; + +const InputCenter = styled(Box).attrs(() => ({ + alignItems: "center", + justifyContent: "center", + color: "palette.text.shade40", +}))` + margin-left: 19px; + margin-right: 19px; +`; diff --git a/apps/ledger-live-desktop/src/renderer/components/RetryButton.js b/apps/ledger-live-desktop/src/renderer/components/RetryButton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/RetryButton.js rename to apps/ledger-live-desktop/src/renderer/components/RetryButton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ScrollLoadingList.js b/apps/ledger-live-desktop/src/renderer/components/ScrollLoadingList.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ScrollLoadingList.js rename to apps/ledger-live-desktop/src/renderer/components/ScrollLoadingList.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Select/createRenderers.js b/apps/ledger-live-desktop/src/renderer/components/Select/createRenderers.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Select/createRenderers.js rename to apps/ledger-live-desktop/src/renderer/components/Select/createRenderers.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Select/index.js b/apps/ledger-live-desktop/src/renderer/components/Select/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Select/index.js rename to apps/ledger-live-desktop/src/renderer/components/Select/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectAccount.js b/apps/ledger-live-desktop/src/renderer/components/SelectAccount.js deleted file mode 100644 index 9bbe0580c46e..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SelectAccount.js +++ /dev/null @@ -1,324 +0,0 @@ -// @flow - -import { - flattenAccounts, - getAccountCurrency, - getAccountUnit, - getAccountName, - listSubAccounts, -} from "@ledgerhq/live-common/lib/account"; -import type { TFunction } from "react-i18next"; -import type { AccountLike, Account, TokenAccount } from "@ledgerhq/live-common/lib/types"; -import styled from "styled-components"; -import React, { useCallback, useState, useMemo } from "react"; -import { withTranslation, Trans } from "react-i18next"; -import { connect, useDispatch } from "react-redux"; -import { createFilter, components } from "react-select"; -import { createStructuredSelector } from "reselect"; -import { shallowAccountsSelector } from "~/renderer/reducers/accounts"; -import Box from "~/renderer/components/Box"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import Select from "~/renderer/components/Select"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import AccountTagDerivationMode from "./AccountTagDerivationMode"; -import Button from "~/renderer/components//Button"; -import Plus from "~/renderer/icons/Plus"; -import Text from "./Text"; -import { openModal } from "../actions/modals"; - -const mapStateToProps = createStructuredSelector({ - accounts: shallowAccountsSelector, -}); - -const Tick = styled.div` - position: absolute; - top: -10px; - height: 40px; - width: 1px; - background: ${p => p.theme.colors.palette.divider}; -`; - -const tokenTick = ( -
- -
-); - -export type Option = { - matched: "boolean", - account: Account | TokenAccount, -}; - -const getOptionValue = option => option.account && option.account.id; - -const defaultFilter = createFilter({ - stringify: ({ data: account }) => { - const currency = getAccountCurrency(account); - const name = getAccountName(account); - return `${currency.ticker}|${currency.name}|${name}`; - }, -}); -const filterOption = o => (candidate, input) => { - const selfMatches = defaultFilter(candidate, input); - if (selfMatches) return [selfMatches, true]; - - if (candidate.data.type === "Account" && o.withSubAccounts) { - const subAccounts = o.enforceHideEmptySubAccounts - ? listSubAccounts(candidate.data) - : candidate.data.subAccounts; - if (subAccounts) { - for (let i = 0; i < subAccounts.length; i++) { - const ta = subAccounts[i]; - if (defaultFilter({ value: ta.id, data: ta }, input)) { - return [true, false]; - } - } - } - } - return [false, false]; -}; - -const OptionMultilineContainer = styled(Box)` - line-height: 1.3em; -`; - -type AccountOptionProps = { - account: AccountLike, - isValue?: boolean, - disabled?: boolean, - singleLineLayout?: boolean, - hideDerivationTag?: boolean, -}; -export const AccountOption = React.memo(function AccountOption({ - account, - isValue, - disabled, - hideDerivationTag = false, - singleLineLayout = true, -}: AccountOptionProps) { - const currency = getAccountCurrency(account); - const unit = getAccountUnit(account); - const name = getAccountName(account); - const nested = ["TokenAccount", "ChildAccount"].includes(account.type); - const balance = - account.type !== "ChildAccount" && account.spendableBalance - ? account.spendableBalance - : account.balance; - - const textContents = singleLineLayout ? ( - <> - - - - {name} - - - {!hideDerivationTag && } - - - - - - ) : ( - - - - - {name} - - - {!hideDerivationTag && } - - - - - - ); - - return ( - - {!isValue && nested ? tokenTick : null} - - {textContents} - - ); -}); - -const AddAccountContainer = styled(Box)` - // to prevent ScrollBlock.js (used by react-select under the hood) css stacking context issues - position: relative; - cursor: pointer; - flex-direction: row; - align-items: center; - border-top: 1px solid ${p => p.theme.colors.palette.divider}; - padding: ${p => (p.small ? "8px 15px 8px 15px" : "10px 15px 11px 15px")}; -`; -const RoundButton = styled(Button)` - padding: 6px; - border-radius: 9999px; - height: initial; -`; -function AddAccountButton() { - return ( - - - - ); -} -const AddAccountFooter = (small?: boolean) => - function AddAccountFooter({ children, ...props }: { children?: React$Node }) { - const dispatch = useDispatch(); - const openAddAccounts = useCallback(() => { - dispatch(openModal("MODAL_ADD_ACCOUNTS")); - }, [dispatch]); - - return ( - <> - {children} - - - - - - - - - - ); - }; -const extraAddAccountRenderer = (small?: boolean) => ({ - MenuList: AddAccountFooter(small), -}); - -const defaultRenderValue = ({ data }: { data: Option }) => - data.account ? : null; - -const defaultRenderOption = ({ data }: { data: Option }) => - data.account ? : null; - -type OwnProps = { - withSubAccounts?: boolean, - enforceHideEmptySubAccounts?: boolean, - filter?: Account => boolean, - onChange: (account: ?AccountLike, tokenAccount: ?Account) => void, - value: ?AccountLike, - renderValue?: typeof defaultRenderValue, - renderOption?: typeof defaultRenderOption, - placeholder?: string, - showAddAccount?: boolean, - disabledTooltipText?: string, -}; - -type Props = OwnProps & { - accounts: Account[], - small?: boolean, -}; - -export const RawSelectAccount = ({ - accounts, - onChange, - value, - withSubAccounts, - enforceHideEmptySubAccounts, - filter, - renderValue, - renderOption, - placeholder, - showAddAccount = false, - disabledTooltipText, - t, - ...props -}: Props & { t: TFunction }) => { - const [searchInputValue, setSearchInputValue] = useState(""); - - const filtered: Account[] = filter ? accounts.filter(filter) : accounts; - const all = withSubAccounts - ? flattenAccounts(filtered, { enforceHideEmptySubAccounts }) - : filtered; - - const selectedOption = value - ? { - account: all.find(o => o.id === value.id), - } - : null; - const onChangeCallback = useCallback( - (option?: Option) => { - if (!option) { - onChange(null); - } else { - const { account } = option; - const parentAccount = - account.type !== "Account" ? accounts.find(a => a.id === account.parentId) : null; - onChange(account, parentAccount); - } - }, - [accounts, onChange], - ); - - const manualFilter = useCallback( - () => - all.reduce((result, option) => { - const [display, match] = filterOption({ withSubAccounts, enforceHideEmptySubAccounts })( - { data: option }, - searchInputValue, - ); - - if (display) { - result.push({ - matched: match && !option.disabled, - account: option, - }); - } - return result; - }, []), - [searchInputValue, all, withSubAccounts, enforceHideEmptySubAccounts], - ); - const extraRenderers = useMemo(() => { - let extraProps = {}; - - if (showAddAccount) extraProps = { ...extraProps, ...extraAddAccountRenderer(props.small) }; - - return extraProps; - }, [showAddAccount, props.small]); - - const structuredResults = manualFilter(); - return ( - setSearchInputValue(v)} + inputValue={searchInputValue} + filterOption={false} + isOptionDisabled={option => !option.matched} + placeholder={placeholder || t("common.selectAccount")} + noOptionsMessage={({ inputValue }) => + t("common.selectAccountNoOption", { accountName: inputValue }) + } + onChange={onChangeCallback} + extraRenderers={extraRenderers} + disabledTooltipText={disabledTooltipText} + /> + ); +}; + +export const SelectAccount: React$ComponentType = withTranslation()(RawSelectAccount); + +const m: React$ComponentType = connect(mapStateToProps)(SelectAccount); + +export default m; diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.js b/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.js deleted file mode 100644 index 805ce9a3d973..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.js +++ /dev/null @@ -1,178 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import styled from "styled-components"; -import Text from "~/renderer/components/Text"; -import { useTranslation } from "react-i18next"; -import { SelectAccount } from "~/renderer/components/PerCurrencySelectAccount"; -import Label from "~/renderer/components/Label"; -import SelectCurrency from "~/renderer/components/SelectCurrency"; -import Button from "~/renderer/components/Button"; -import { useSelector, useDispatch } from "react-redux"; -import { accountsSelector } from "~/renderer/reducers/accounts"; - -import type { - Account, - AccountLike, - CryptoCurrency, - TokenCurrency, -} from "@ledgerhq/live-common/lib/types"; -import FakeLink from "~/renderer/components/FakeLink"; -import PlusIcon from "~/renderer/icons/Plus"; -import { openModal, closeModal } from "~/renderer/actions/modals"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { useCurrencyAccountSelect } from "~/renderer/components/PerCurrencySelectAccount/state"; -import CurrencyDownStatusAlert from "~/renderer/components/CurrencyDownStatusAlert"; - -const Container: ThemedComponent<{}> = styled.div` - min-width: 365px; - display: flex; - flex-direction: column; - align-items: center; -`; - -const ConfirmButton: ThemedComponent<{}> = styled(Button)` - width: 100%; - display: flex; - justify-content: center; -`; -const FormContainer: ThemedComponent<{}> = styled.div` - width: 100%; - margin-top: 8px; -`; -const FormContent: ThemedComponent<{}> = styled.div` - margin-top: 24px; - width: 100%; -`; - -type Props = { - selectAccount: (account: AccountLike, parentAccount: ?Account) => void, - allCurrencies: Array, - defaultCurrencyId?: ?string, - defaultAccountId?: ?string, - allowAddAccount?: boolean, - allowedCurrencies?: string[], - confirmCb?: Account => void, - flow?: string, -}; - -const AccountSelectorLabel = styled(Label)` - display: flex; - flex: 1; - justify-content: space-between; -`; -const SelectAccountAndCurrency = ({ - selectAccount, - allCurrencies, - defaultCurrencyId, - defaultAccountId, - allowAddAccount, - allowedCurrencies, - confirmCb, - flow, -}: Props) => { - const { t } = useTranslation(); - - const allAccounts = useSelector(accountsSelector); - const { - availableAccounts, - currency, - account, - subAccount, - setAccount, - setCurrency, - } = useCurrencyAccountSelect({ - allCurrencies, - allAccounts, - defaultCurrencyId, - defaultAccountId, - }); - const dispatch = useDispatch(); - - const openAddAccounts = useCallback(() => { - dispatch(closeModal("MODAL_REQUEST_ACCOUNT")); - dispatch( - openModal("MODAL_ADD_ACCOUNTS", { - currency, - flow, - onClose: () => selectAccount(), - }), - ); - }, [dispatch, currency, flow, selectAccount]); - - const addOrSelectAccount = () => { - if (!currency) { - return; - } - - if (availableAccounts.length) { - return ( - <> - - {/* FIXME: should display add account button only if allowAddAccount is true */} - - {t("exchange.buy.coinify.selectAccount")} - - - {t("exchange.buy.coinify.addAccount")} - - - - - - { - if (account) { - if (subAccount) { - selectAccount(subAccount, account); - } else { - selectAccount(account); - } - - confirmCb?.(account); - } - }} - disabled={!account} - data-test-id="modal-continue-button" - > - {t("exchange.buy.coinify.continue")} - - - - ); - } - - // FIXME: should display add account button only if allowAddAccount is true - return ( - - - {t("exchange.buy.coinify.addAccount")} - - - ); - }; - - return ( - - - {currency ? : null} - - - - - {addOrSelectAccount()} - - - ); -}; - -export default SelectAccountAndCurrency; diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.jsx b/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.jsx new file mode 100644 index 000000000000..ec2d82e0178c --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/SelectAccountAndCurrency.jsx @@ -0,0 +1,178 @@ +// @flow + +import React, { useCallback } from "react"; +import styled from "styled-components"; +import Text from "~/renderer/components/Text"; +import { useTranslation } from "react-i18next"; +import { SelectAccount } from "~/renderer/components/PerCurrencySelectAccount"; +import Label from "~/renderer/components/Label"; +import SelectCurrency from "~/renderer/components/SelectCurrency"; +import Button from "~/renderer/components/Button"; +import { useSelector, useDispatch } from "react-redux"; +import { accountsSelector } from "~/renderer/reducers/accounts"; + +import type { + Account, + AccountLike, + CryptoCurrency, + TokenCurrency, +} from "@ledgerhq/live-common/types/index"; +import FakeLink from "~/renderer/components/FakeLink"; +import PlusIcon from "~/renderer/icons/Plus"; +import { openModal, closeModal } from "~/renderer/actions/modals"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { useCurrencyAccountSelect } from "~/renderer/components/PerCurrencySelectAccount/state"; +import CurrencyDownStatusAlert from "~/renderer/components/CurrencyDownStatusAlert"; + +const Container: ThemedComponent<{}> = styled.div` + min-width: 365px; + display: flex; + flex-direction: column; + align-items: center; +`; + +const ConfirmButton: ThemedComponent<{}> = styled(Button)` + width: 100%; + display: flex; + justify-content: center; +`; +const FormContainer: ThemedComponent<{}> = styled.div` + width: 100%; + margin-top: 8px; +`; +const FormContent: ThemedComponent<{}> = styled.div` + margin-top: 24px; + width: 100%; +`; + +type Props = { + selectAccount: (account: AccountLike, parentAccount: ?Account) => void, + allCurrencies: Array, + defaultCurrencyId?: ?string, + defaultAccountId?: ?string, + allowAddAccount?: boolean, + allowedCurrencies?: string[], + confirmCb?: Account => void, + flow?: string, +}; + +const AccountSelectorLabel = styled(Label)` + display: flex; + flex: 1; + justify-content: space-between; +`; +const SelectAccountAndCurrency = ({ + selectAccount, + allCurrencies, + defaultCurrencyId, + defaultAccountId, + allowAddAccount, + allowedCurrencies, + confirmCb, + flow, +}: Props) => { + const { t } = useTranslation(); + + const allAccounts = useSelector(accountsSelector); + const { + availableAccounts, + currency, + account, + subAccount, + setAccount, + setCurrency, + } = useCurrencyAccountSelect({ + allCurrencies, + allAccounts, + defaultCurrencyId, + defaultAccountId, + }); + const dispatch = useDispatch(); + + const openAddAccounts = useCallback(() => { + dispatch(closeModal("MODAL_REQUEST_ACCOUNT")); + dispatch( + openModal("MODAL_ADD_ACCOUNTS", { + currency, + flow, + onClose: () => selectAccount(), + }), + ); + }, [dispatch, currency, flow, selectAccount]); + + const addOrSelectAccount = () => { + if (!currency) { + return; + } + + if (availableAccounts.length) { + return ( + <> + + {/* FIXME: should display add account button only if allowAddAccount is true */} + + {t("exchange.buy.coinify.selectAccount")} + + + {t("exchange.buy.coinify.addAccount")} + + + + + + { + if (account) { + if (subAccount) { + selectAccount(subAccount, account); + } else { + selectAccount(account); + } + + confirmCb?.(account); + } + }} + disabled={!account} + data-test-id="modal-continue-button" + > + {t("exchange.buy.coinify.continue")} + + + + ); + } + + // FIXME: should display add account button only if allowAddAccount is true + return ( + + + {t("exchange.buy.coinify.addAccount")} + + + ); + }; + + return ( + + + {currency ? : null} + + + + + {addOrSelectAccount()} + + + ); +}; + +export default SelectAccountAndCurrency; diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectCurrency.js b/apps/ledger-live-desktop/src/renderer/components/SelectCurrency.js deleted file mode 100644 index 7d68bb2c38d9..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SelectCurrency.js +++ /dev/null @@ -1,192 +0,0 @@ -// @flow - -import React, { useCallback, useMemo, useState, memo } from "react"; -import { useTranslation } from "react-i18next"; -import styled from "styled-components"; -import Fuse from "fuse.js"; -import type { Currency } from "@ledgerhq/live-common/lib/types"; -import { useCurrenciesByMarketcap } from "@ledgerhq/live-common/lib/currencies"; -import useEnv from "~/renderer/hooks/useEnv"; -import type { Option } from "~/renderer/components/Select"; -import type { CreateStylesReturnType } from "~/renderer/components/Select/createStyles"; -import Select from "~/renderer/components/Select"; -import Box from "~/renderer/components/Box"; -import CryptoCurrencyIcon from "~/renderer/components/CryptoCurrencyIcon"; -import Text from "./Text"; - -type Props = { - onChange: (?C) => void, - currencies: C[], - value?: C, - placeholder?: string, - autoFocus?: boolean, - minWidth?: number, - width?: number, - rowHeight?: number, - isCurrencyDisabled?: Currency => boolean, - isDisabled?: boolean, - id?: string, - renderOptionOverride?: (option: Option) => any, - renderValueOverride?: (option: Option) => any, - stylesMap?: CreateStylesReturnType => CreateStylesReturnType, -}; - -const getOptionValue = c => c.id; - -// TODO: I removed the {...props} that was passed to Select. We might need to check out it doesnt break other stuff -const SelectCurrency = ({ - onChange, - value, - placeholder, - currencies, - autoFocus, - minWidth, - width, - rowHeight = 47, - renderOptionOverride, - renderValueOverride, - isCurrencyDisabled, - isDisabled, - id, - stylesMap, -}: Props) => { - const { t } = useTranslation(); - const devMode = useEnv("MANAGER_DEV_MODE"); - let c = currencies; - if (!devMode) { - c = c.filter(c => c.type !== "CryptoCurrency" || !c.isTestnetFor); - } - const [searchInputValue, setSearchInputValue] = useState(""); - - const cryptos = useCurrenciesByMarketcap(c); - const onChangeCallback = useCallback(item => onChange(item ? item.currency : null), [onChange]); - const noOptionsMessage = useCallback( - ({ inputValue }: { inputValue: string }) => - inputValue - ? t("common.selectCurrencyNoOption", { currencyName: inputValue }) - : t("common.selectCurrencyEmptyOption"), - [t], - ); - - const options = useMemo( - () => - cryptos.map(c => ({ - ...c, - value: c, - label: c.name, - currency: c, - isDisabled: isCurrencyDisabled ? isCurrencyDisabled(c) : false, - })), - [isCurrencyDisabled, cryptos], - ); - - const fuseOptions = useMemo( - () => ({ - threshold: 0.1, - keys: ["name", "ticker"], - shouldSort: false, - }), - [], - ); - - const manualFilter = useCallback(() => { - const fuse = new Fuse(options, fuseOptions); - return searchInputValue.length > 0 ? fuse.search(searchInputValue) : options; - }, [searchInputValue, options, fuseOptions]); - - const filteredOptions = manualFilter(); - return ( - setSearchInputValue(v)} + inputValue={searchInputValue} + placeholder={placeholder || t("common.selectCurrency")} + noOptionsMessage={noOptionsMessage} + onChange={onChangeCallback} + minWidth={minWidth} + width={width} + isDisabled={isDisabled} + rowHeight={rowHeight} + stylesMap={stylesMap} + /> + ); +}; + +const OptionMultilineContainer = styled(Box)` + line-height: 1.3em; +`; +const CurrencyLabel = styled(Text).attrs(() => ({ + color: "palette.text.shade60", + ff: "Inter|SemiBold", + fontSize: 2, +}))` + padding: 0 6px; + height: 24px; + line-height: 24px; + border-color: currentColor; + border-width: 1px; + border-style: solid; + border-radius: 4px; + text-align: center; + flex: 0 0 auto; + box-sizing: content-box; +`; + +export function CurrencyOption({ + currency, + singleLineLayout = true, + hideParentTag = false, + tagVariant = "default", +}: { + currency: Currency, + singleLineLayout?: boolean, + hideParentTag?: boolean, + tagVariant?: "default" | "thin", +}) { + const isParentTagDisplayed = !hideParentTag && currency.parentCurrency; + + const textContents = singleLineLayout ? ( + <> + + {`${currency.name} (${currency.ticker})`} + + {isParentTagDisplayed ? {currency.parentCurrency.name} : null} + + ) : ( + <> + + + {currency.name} + + + + {currency.ticker}{" "} + {isParentTagDisplayed && tagVariant === "thin" + ? `(${currency.parentCurrency.name})` + : null} + + + + {isParentTagDisplayed && tagVariant === "default" ? ( + {currency.parentCurrency.name} + ) : null} + + ); + + return ( + + + {textContents} + + ); +} +const renderOption = ({ data: currency }: Option) => ; + +export default memo>(SelectCurrency); diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.js b/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.js deleted file mode 100644 index 536fb1f85c35..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.js +++ /dev/null @@ -1,159 +0,0 @@ -// @flow -import React from "react"; -import styled from "styled-components"; -import { useTranslation, Trans } from "react-i18next"; -import type { BigNumber } from "bignumber.js"; - -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import TachometerHigh from "~/renderer/icons/TachometerHigh"; -import TachometerLow from "~/renderer/icons/TachometerLow"; -import TachometerMedium from "~/renderer/icons/TachometerMedium"; - -import FormattedVal from "~/renderer/components/FormattedVal"; -import CounterValue from "~/renderer/components/CounterValue"; - -import { - getAccountCurrency, - getAccountUnit, - getMainAccount, -} from "@ledgerhq/live-common/lib/account"; -import type { Account, FeeStrategy } from "@ledgerhq/live-common/lib/types"; - -type OnClickType = { - amount: BigNumber, - feesStrategy: string, -}; - -type Props = { - onClick: OnClickType => void, - transaction: *, - account: Account, - parentAccount: ?Account, - strategies: FeeStrategy[], - mapStrategies?: FeeStrategy => FeeStrategy & { [string]: * }, - suffixPerByte?: boolean, -}; - -const FeesWrapper = styled(Box)` - flex-direction: row; - align-items: center; - justify-content: space-between; - - border: ${p => - `1px solid ${ - p.selected ? p.theme.colors.palette.primary.main : p.theme.colors.palette.divider - }`}; - ${p => (p.selected ? "box-shadow: 0px 0px 0px 4px rgba(138, 128, 219, 0.3);" : "")} - padding: 20px 16px; - width: 100%; - font-family: "Inter"; - border-radius: 4px; - ${p => (p.disabled ? `background: ${p.theme.colors.palette.background.default};` : "")}; - - &:hover { - cursor: ${p => (p.disabled ? "unset" : "pointer")}; - } -`; - -const FeesHeader = styled(Box)` - color: ${p => - p.selected - ? p.theme.colors.palette.primary.main - : p.disabled - ? p.theme.colors.palette.text.shade20 - : p.theme.colors.palette.text.shade50}; -`; - -const FeesValue = styled(Box)` - flex-direction: row; - align-items: center; -`; - -const SelectFeeStrategy = ({ - transaction, - account, - parentAccount, - onClick, - strategies, - mapStrategies, - suffixPerByte, -}: Props) => { - const mainAccount = getMainAccount(account, parentAccount); - const accountUnit = getAccountUnit(mainAccount); - const feesCurrency = getAccountCurrency(mainAccount); - const { t } = useTranslation(); - strategies = mapStrategies ? strategies.map(mapStrategies) : strategies; - - return ( - - {strategies.map(s => { - const selected = transaction.feesStrategy === s.label; - const amount = s.displayedAmount || s.amount; - const { label, disabled } = s; - return ( - { - !disabled && onClick({ amount: s.amount, feesStrategy: label }); - }} - > - - {label === "medium" ? ( - - ) : label === "slow" ? ( - - ) : ( - - )} - - - - - - {s.displayedAmount ? ( - - ) : null} - - - - ); - })} - - ); -}; - -export default SelectFeeStrategy; diff --git a/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.jsx b/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.jsx new file mode 100644 index 000000000000..57f119a66153 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/SelectFeeStrategy.jsx @@ -0,0 +1,159 @@ +// @flow +import React from "react"; +import styled from "styled-components"; +import { useTranslation, Trans } from "react-i18next"; +import type { BigNumber } from "bignumber.js"; + +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import TachometerHigh from "~/renderer/icons/TachometerHigh"; +import TachometerLow from "~/renderer/icons/TachometerLow"; +import TachometerMedium from "~/renderer/icons/TachometerMedium"; + +import FormattedVal from "~/renderer/components/FormattedVal"; +import CounterValue from "~/renderer/components/CounterValue"; + +import { + getAccountCurrency, + getAccountUnit, + getMainAccount, +} from "@ledgerhq/live-common/account/index"; +import type { Account, FeeStrategy } from "@ledgerhq/live-common/types/index"; + +type OnClickType = { + amount: BigNumber, + feesStrategy: string, +}; + +type Props = { + onClick: OnClickType => void, + transaction: *, + account: Account, + parentAccount: ?Account, + strategies: FeeStrategy[], + mapStrategies?: FeeStrategy => FeeStrategy & { [string]: * }, + suffixPerByte?: boolean, +}; + +const FeesWrapper = styled(Box)` + flex-direction: row; + align-items: center; + justify-content: space-between; + + border: ${p => + `1px solid ${ + p.selected ? p.theme.colors.palette.primary.main : p.theme.colors.palette.divider + }`}; + ${p => (p.selected ? "box-shadow: 0px 0px 0px 4px rgba(138, 128, 219, 0.3);" : "")} + padding: 20px 16px; + width: 100%; + font-family: "Inter"; + border-radius: 4px; + ${p => (p.disabled ? `background: ${p.theme.colors.palette.background.default};` : "")}; + + &:hover { + cursor: ${p => (p.disabled ? "unset" : "pointer")}; + } +`; + +const FeesHeader = styled(Box)` + color: ${p => + p.selected + ? p.theme.colors.palette.primary.main + : p.disabled + ? p.theme.colors.palette.text.shade20 + : p.theme.colors.palette.text.shade50}; +`; + +const FeesValue = styled(Box)` + flex-direction: row; + align-items: center; +`; + +const SelectFeeStrategy = ({ + transaction, + account, + parentAccount, + onClick, + strategies, + mapStrategies, + suffixPerByte, +}: Props) => { + const mainAccount = getMainAccount(account, parentAccount); + const accountUnit = getAccountUnit(mainAccount); + const feesCurrency = getAccountCurrency(mainAccount); + const { t } = useTranslation(); + strategies = mapStrategies ? strategies.map(mapStrategies) : strategies; + + return ( + + {strategies.map(s => { + const selected = transaction.feesStrategy === s.label; + const amount = s.displayedAmount || s.amount; + const { label, disabled } = s; + return ( + { + !disabled && onClick({ amount: s.amount, feesStrategy: label }); + }} + > + + {label === "medium" ? ( + + ) : label === "slow" ? ( + + ) : ( + + )} + + + + + + {s.displayedAmount ? ( + + ) : null} + + + + ); + })} + + ); +}; + +export default SelectFeeStrategy; diff --git a/apps/ledger-live-desktop/src/renderer/components/SendFeeMode.js b/apps/ledger-live-desktop/src/renderer/components/SendFeeMode.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SendFeeMode.js rename to apps/ledger-live-desktop/src/renderer/components/SendFeeMode.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarList.js b/apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarList.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarList.js rename to apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarList.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarListItem.js b/apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarListItem.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarListItem.js rename to apps/ledger-live-desktop/src/renderer/components/SideBar/SideBarListItem.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SideDrawer.js b/apps/ledger-live-desktop/src/renderer/components/SideDrawer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SideDrawer.js rename to apps/ledger-live-desktop/src/renderer/components/SideDrawer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/SignMessageConfirmField.js b/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/SignMessageConfirmField.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/SignMessageConfirmField.js rename to apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/SignMessageConfirmField.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.js b/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.js deleted file mode 100644 index 28bfca01205e..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.js +++ /dev/null @@ -1,109 +0,0 @@ -// @flow - -import invariant from "invariant"; -import React from "react"; -import { useTranslation } from "react-i18next"; -import styled from "styled-components"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; -import type { TypedMessageData } from "@ledgerhq/live-common/lib/families/ethereum/types"; -import type { MessageData } from "@ledgerhq/live-common/lib/hw/signMessage/types"; -import type { DeviceTransactionField } from "@ledgerhq/live-common/lib/transaction"; -import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types"; -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import useTheme from "~/renderer/hooks/useTheme"; -import { renderVerifyUnwrapped } from "~/renderer/components/DeviceAction/rendering"; -import SignMessageConfirmField from "./SignMessageConfirmField"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -const FieldText = styled(Text).attrs(() => ({ - ml: 1, - ff: "Inter|Medium", - color: "palette.text.shade80", - fontSize: 3, -}))` - word-break: break-all; - text-align: right; - max-width: 50%; -`; - -export type FieldComponentProps = { - account: AccountLike, - field: DeviceTransactionField, -}; - -export type FieldComponent = React$ComponentType; - -const TextField = ({ field }: FieldComponentProps) => { - invariant(field.type === "text", "TextField invalid"); - return ( - - {field.value} - - ); -}; - -const Container: ThemedComponent<*> = styled(Box).attrs(() => ({ - alignItems: "center", - fontSize: 4, - pb: 4, -}))``; - -type Props = { - device: Device, - account: AccountLike, - signMessageRequested: TypedMessageData | MessageData, -}; - -const SignMessageConfirm = ({ device, account, signMessageRequested: message }: Props) => { - const type = useTheme("colors.palette.type"); - const { t } = useTranslation(); - - if (!device) return null; - - const fields = []; - - if (message.hashes && message.hashes.domainHash) { - fields.push({ - type: "text", - label: t("SignMessageConfirm.domainHash"), - // $FlowFixMe - value: message.hashes.domainHash, - }); - } - if (message.hashes && message.hashes.messageHash) { - fields.push({ - type: "text", - label: t("SignMessageConfirm.messageHash"), - // $FlowFixMe - value: message.hashes.messageHash, - }); - } - if (message.hashes && message.hashes.stringHash) { - fields.push({ - type: "text", - label: t("SignMessageConfirm.stringHash"), - // $FlowFixMe - value: message.hashes.stringHash, - }); - } - fields.push({ - type: "text", - label: t("SignMessageConfirm.message"), - value: message.message.domain ? JSON.stringify(message.message) : message.message, - }); - - return ( - - - {fields.map((field, i) => { - return ; - })} - - - {renderVerifyUnwrapped({ modelId: device.modelId, type })} - - ); -}; - -export default SignMessageConfirm; diff --git a/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.jsx b/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.jsx new file mode 100644 index 000000000000..7eaae5927871 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/SignMessageConfirm/index.jsx @@ -0,0 +1,109 @@ +// @flow + +import invariant from "invariant"; +import React from "react"; +import { useTranslation } from "react-i18next"; +import styled from "styled-components"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; +import type { TypedMessageData } from "@ledgerhq/live-common/families/ethereum/types"; +import type { MessageData } from "@ledgerhq/live-common/hw/signMessage/types"; +import type { DeviceTransactionField } from "@ledgerhq/live-common/transaction/index"; +import type { Device } from "@ledgerhq/live-common/hw/actions/types"; +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import useTheme from "~/renderer/hooks/useTheme"; +import { renderVerifyUnwrapped } from "~/renderer/components/DeviceAction/rendering"; +import SignMessageConfirmField from "./SignMessageConfirmField"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +const FieldText = styled(Text).attrs(() => ({ + ml: 1, + ff: "Inter|Medium", + color: "palette.text.shade80", + fontSize: 3, +}))` + word-break: break-all; + text-align: right; + max-width: 50%; +`; + +export type FieldComponentProps = { + account: AccountLike, + field: DeviceTransactionField, +}; + +export type FieldComponent = React$ComponentType; + +const TextField = ({ field }: FieldComponentProps) => { + invariant(field.type === "text", "TextField invalid"); + return ( + + {field.value} + + ); +}; + +const Container: ThemedComponent<*> = styled(Box).attrs(() => ({ + alignItems: "center", + fontSize: 4, + pb: 4, +}))``; + +type Props = { + device: Device, + account: AccountLike, + signMessageRequested: TypedMessageData | MessageData, +}; + +const SignMessageConfirm = ({ device, account, signMessageRequested: message }: Props) => { + const type = useTheme("colors.palette.type"); + const { t } = useTranslation(); + + if (!device) return null; + + const fields = []; + + if (message.hashes && message.hashes.domainHash) { + fields.push({ + type: "text", + label: t("SignMessageConfirm.domainHash"), + // $FlowFixMe + value: message.hashes.domainHash, + }); + } + if (message.hashes && message.hashes.messageHash) { + fields.push({ + type: "text", + label: t("SignMessageConfirm.messageHash"), + // $FlowFixMe + value: message.hashes.messageHash, + }); + } + if (message.hashes && message.hashes.stringHash) { + fields.push({ + type: "text", + label: t("SignMessageConfirm.stringHash"), + // $FlowFixMe + value: message.hashes.stringHash, + }); + } + fields.push({ + type: "text", + label: t("SignMessageConfirm.message"), + value: message.message.domain ? JSON.stringify(message.message) : message.message, + }); + + return ( + + + {fields.map((field, i) => { + return ; + })} + + + {renderVerifyUnwrapped({ modelId: device.modelId, type })} + + ); +}; + +export default SignMessageConfirm; diff --git a/apps/ledger-live-desktop/src/renderer/components/Slider.js b/apps/ledger-live-desktop/src/renderer/components/Slider.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Slider.js rename to apps/ledger-live-desktop/src/renderer/components/Slider.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.js b/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.js deleted file mode 100644 index f2293ae61139..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.js +++ /dev/null @@ -1,67 +0,0 @@ -// @flow -import React, { useEffect, useState } from "react"; -import type { Account, AccountLike, Transaction } from "@ledgerhq/live-common/lib/types"; -import { useDebounce } from "@ledgerhq/live-common/lib//hooks/useDebounce"; -import { getAccountUnit } from "@ledgerhq/live-common/lib/account"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; - -import FormattedVal from "~/renderer/components/FormattedVal"; - -type Props = { - account: AccountLike, - transaction: Transaction, - parentAccount: ?Account, - prefix?: string, - showAllDigits?: boolean, - disableRounding?: boolean, -}; - -const SpendableAmount = ({ - account, - parentAccount, - transaction, - prefix, - showAllDigits, - disableRounding, -}: Props) => { - const [maxSpendable, setMaxSpendable] = useState(null); - - const debouncedTransaction = useDebounce(transaction, 500); - - useEffect(() => { - if (!account) return; - let cancelled = false; - getAccountBridge(account, parentAccount) - .estimateMaxSpendable({ - account, - parentAccount, - transaction: debouncedTransaction, - }) - .then(estimate => { - if (cancelled) return; - setMaxSpendable(estimate); - }); - - return () => { - cancelled = true; - }; - }, [account, parentAccount, debouncedTransaction]); - - const accountUnit = getAccountUnit(account); - - return maxSpendable ? ( - - ) : null; -}; - -export default SpendableAmount; diff --git a/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.jsx b/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.jsx new file mode 100644 index 000000000000..a2a773e375ab --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/SpendableAmount.jsx @@ -0,0 +1,67 @@ +// @flow +import React, { useEffect, useState } from "react"; +import type { Account, AccountLike, Transaction } from "@ledgerhq/live-common/types/index"; +import { useDebounce } from "@ledgerhq/live-common//hooks/useDebounce"; +import { getAccountUnit } from "@ledgerhq/live-common/account/index"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; + +import FormattedVal from "~/renderer/components/FormattedVal"; + +type Props = { + account: AccountLike, + transaction: Transaction, + parentAccount: ?Account, + prefix?: string, + showAllDigits?: boolean, + disableRounding?: boolean, +}; + +const SpendableAmount = ({ + account, + parentAccount, + transaction, + prefix, + showAllDigits, + disableRounding, +}: Props) => { + const [maxSpendable, setMaxSpendable] = useState(null); + + const debouncedTransaction = useDebounce(transaction, 500); + + useEffect(() => { + if (!account) return; + let cancelled = false; + getAccountBridge(account, parentAccount) + .estimateMaxSpendable({ + account, + parentAccount, + transaction: debouncedTransaction, + }) + .then(estimate => { + if (cancelled) return; + setMaxSpendable(estimate); + }); + + return () => { + cancelled = true; + }; + }, [account, parentAccount, debouncedTransaction]); + + const accountUnit = getAccountUnit(account); + + return maxSpendable ? ( + + ) : null; +}; + +export default SpendableAmount; diff --git a/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.js b/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.js deleted file mode 100644 index ae5c9d1a5bc1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.js +++ /dev/null @@ -1,36 +0,0 @@ -// @flow -import React from "react"; -import styled from "styled-components"; -import { Trans } from "react-i18next"; -import type { Account, AccountLike, Transaction } from "@ledgerhq/live-common/lib/types"; -import { urls } from "~/config/urls"; - -import Alert from "./Alert"; -import SpendableAmount from "./SpendableAmount"; - -// FormattedVal is a div, we want to avoid having it on a second line -const TextContent = styled.div` - display: inline-flex; -`; - -type Props = { - account: AccountLike, - transaction: Transaction, - parentAccount: ?Account, -}; - -const SpendableBanner = ({ account, parentAccount, transaction }: Props) => ( - - - - - - -); - -export default SpendableBanner; diff --git a/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.jsx b/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.jsx new file mode 100644 index 000000000000..01f874f1366c --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/SpendableBanner.jsx @@ -0,0 +1,36 @@ +// @flow +import React from "react"; +import styled from "styled-components"; +import { Trans } from "react-i18next"; +import type { Account, AccountLike, Transaction } from "@ledgerhq/live-common/types/index"; +import { urls } from "~/config/urls"; + +import Alert from "./Alert"; +import SpendableAmount from "./SpendableAmount"; + +// FormattedVal is a div, we want to avoid having it on a second line +const TextContent = styled.div` + display: inline-flex; +`; + +type Props = { + account: AccountLike, + transaction: Transaction, + parentAccount: ?Account, +}; + +const SpendableBanner = ({ account, parentAccount, transaction }: Props) => ( + + + + + + +); + +export default SpendableBanner; diff --git a/apps/ledger-live-desktop/src/renderer/components/Spinner.js b/apps/ledger-live-desktop/src/renderer/components/Spinner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Spinner.js rename to apps/ledger-live-desktop/src/renderer/components/Spinner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Spoiler.js b/apps/ledger-live-desktop/src/renderer/components/Spoiler.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Spoiler.js rename to apps/ledger-live-desktop/src/renderer/components/Spoiler.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Stars/Item.js b/apps/ledger-live-desktop/src/renderer/components/Stars/Item.js deleted file mode 100644 index c5180465fcea..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Stars/Item.js +++ /dev/null @@ -1,104 +0,0 @@ -// @flow -import React, { useCallback } from "react"; -import { useHistory } from "react-router-dom"; -import styled from "styled-components"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { - getAccountCurrency, - getAccountUnit, - getAccountName, -} from "@ledgerhq/live-common/lib/account/helpers"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; - -import Hide from "~/renderer/components/MainSideBar/Hide"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import Box from "~/renderer/components/Box"; -import Ellipsis from "~/renderer/components/Ellipsis"; -import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; -import { setTrackingSource } from "~/renderer/analytics/TrackPage"; - -const ParentCryptoCurrencyIconWrapper: ThemedComponent<{}> = styled.div` - width: 20px; -`; - -const ItemWrapper: ThemedComponent<{ active: boolean }> = styled.div.attrs(p => ({ - style: { - backgroundColor: p.active - ? p.theme.colors.palette.action.hover - : p.theme.colors.palette.background.paper, - }, -}))` - flex: 1; - align-items: center; - display: flex; - padding: 6px 15px; - width: 200px; - border-radius: 4px; - border: 1px solid transparent; - cursor: pointer; - margin: 2px 0px; - color: ${p => - p.active ? p.theme.colors.palette.text.shade100 : p.theme.colors.palette.text.shade80}; - - &:hover { - color: ${p => p.theme.colors.palette.text.shade100}; - } -`; - -type Props = { - account: AccountLike, - index: number, - pathname: string, - collapsed?: boolean, -}; - -const Item = ({ account, index, pathname, collapsed }: Props) => { - const history = useHistory(); - const active = pathname.endsWith(account.id); - - const onAccountClick = useCallback(() => { - const parentAccountId = account.type !== "Account" ? account.parentId : undefined; - setTrackingSource("starred account item"); - history.push({ - pathname: parentAccountId - ? `/account/${parentAccountId}/${account.id}` - : `/account/${account.id}`, - }); - }, [account, history]); - - const unit = getAccountUnit(account); - - return ( - - - - - - - - {getAccountName(account)} - - - - - - ); -}; - -export default Item; diff --git a/apps/ledger-live-desktop/src/renderer/components/Stars/Item.jsx b/apps/ledger-live-desktop/src/renderer/components/Stars/Item.jsx new file mode 100644 index 000000000000..b9d86855ee3f --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Stars/Item.jsx @@ -0,0 +1,104 @@ +// @flow +import React, { useCallback } from "react"; +import { useHistory } from "react-router-dom"; +import styled from "styled-components"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { + getAccountCurrency, + getAccountUnit, + getAccountName, +} from "@ledgerhq/live-common/account/helpers"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; + +import Hide from "~/renderer/components/MainSideBar/Hide"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import Box from "~/renderer/components/Box"; +import Ellipsis from "~/renderer/components/Ellipsis"; +import ParentCryptoCurrencyIcon from "~/renderer/components/ParentCryptoCurrencyIcon"; +import { setTrackingSource } from "~/renderer/analytics/TrackPage"; + +const ParentCryptoCurrencyIconWrapper: ThemedComponent<{}> = styled.div` + width: 20px; +`; + +const ItemWrapper: ThemedComponent<{ active: boolean }> = styled.div.attrs(p => ({ + style: { + backgroundColor: p.active + ? p.theme.colors.palette.action.hover + : p.theme.colors.palette.background.paper, + }, +}))` + flex: 1; + align-items: center; + display: flex; + padding: 6px 15px; + width: 200px; + border-radius: 4px; + border: 1px solid transparent; + cursor: pointer; + margin: 2px 0px; + color: ${p => + p.active ? p.theme.colors.palette.text.shade100 : p.theme.colors.palette.text.shade80}; + + &:hover { + color: ${p => p.theme.colors.palette.text.shade100}; + } +`; + +type Props = { + account: AccountLike, + index: number, + pathname: string, + collapsed?: boolean, +}; + +const Item = ({ account, index, pathname, collapsed }: Props) => { + const history = useHistory(); + const active = pathname.endsWith(account.id); + + const onAccountClick = useCallback(() => { + const parentAccountId = account.type !== "Account" ? account.parentId : undefined; + setTrackingSource("starred account item"); + history.push({ + pathname: parentAccountId + ? `/account/${parentAccountId}/${account.id}` + : `/account/${account.id}`, + }); + }, [account, history]); + + const unit = getAccountUnit(account); + + return ( + + + + + + + + {getAccountName(account)} + + + + + + ); +}; + +export default Item; diff --git a/apps/ledger-live-desktop/src/renderer/components/Stars/Star.js b/apps/ledger-live-desktop/src/renderer/components/Stars/Star.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Stars/Star.js rename to apps/ledger-live-desktop/src/renderer/components/Stars/Star.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Stars/index.js b/apps/ledger-live-desktop/src/renderer/components/Stars/index.js deleted file mode 100644 index 2a08a5307d6e..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/Stars/index.js +++ /dev/null @@ -1,96 +0,0 @@ -// @flow -import React from "react"; -import { useSelector } from "react-redux"; -import { Trans } from "react-i18next"; -import styled from "styled-components"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import Hide from "~/renderer/components/MainSideBar/Hide"; -import Text from "~/renderer/components/Text"; -import Tooltip from "~/renderer/components/Tooltip"; -import Image from "~/renderer/components/Image"; -import emptyBookmarksDark from "~/renderer/images/dark-empty-bookmarks.png"; -import emptyBookmarksLight from "~/renderer/images/light-empty-bookmarks.png"; - -import Item from "./Item"; -import { starredAccountsSelector } from "~/renderer/reducers/accounts"; - -const Container: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; -`; -const Placeholder: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; - align-items: center; - align-self: center; - text-align: center; - padding: 0px 8px; - & > :first-child { - margin-bottom: 14px; - } -`; - -type Props = { - pathname: string, - collapsed: boolean, -}; - -const Stars = ({ pathname, collapsed }: Props) => { - const starredAccounts = useSelector(starredAccountsSelector); - - return starredAccounts && starredAccounts.length ? ( - - {starredAccounts.map((account, i) => ( - - - - ))} - - ) : ( - - - stars placeholder - - - {"Accounts that you star on the"} - - {"Accounts"} - - {" page will now appear here!."} - - - - - ); -}; - -export default Stars; diff --git a/apps/ledger-live-desktop/src/renderer/components/Stars/index.jsx b/apps/ledger-live-desktop/src/renderer/components/Stars/index.jsx new file mode 100644 index 000000000000..3519b27ad2ce --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/Stars/index.jsx @@ -0,0 +1,96 @@ +// @flow +import React from "react"; +import { useSelector } from "react-redux"; +import { Trans } from "react-i18next"; +import styled from "styled-components"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import Hide from "~/renderer/components/MainSideBar/Hide"; +import Text from "~/renderer/components/Text"; +import Tooltip from "~/renderer/components/Tooltip"; +import Image from "~/renderer/components/Image"; +import emptyBookmarksDark from "~/renderer/images/dark-empty-bookmarks.png"; +import emptyBookmarksLight from "~/renderer/images/light-empty-bookmarks.png"; + +import Item from "./Item"; +import { starredAccountsSelector } from "~/renderer/reducers/accounts"; + +const Container: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; +`; +const Placeholder: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; + align-items: center; + align-self: center; + text-align: center; + padding: 0px 8px; + & > :first-child { + margin-bottom: 14px; + } +`; + +type Props = { + pathname: string, + collapsed: boolean, +}; + +const Stars = ({ pathname, collapsed }: Props) => { + const starredAccounts = useSelector(starredAccountsSelector); + + return starredAccounts && starredAccounts.length ? ( + + {starredAccounts.map((account, i) => ( + + + + ))} + + ) : ( + + + stars placeholder + + + {"Accounts that you star on the"} + + {"Accounts"} + + {" page will now appear here!."} + + + + + ); +}; + +export default Stars; diff --git a/apps/ledger-live-desktop/src/renderer/components/StepRecipientSeparator.js b/apps/ledger-live-desktop/src/renderer/components/StepRecipientSeparator.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/StepRecipientSeparator.js rename to apps/ledger-live-desktop/src/renderer/components/StepRecipientSeparator.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Stepper/Breadcrumb.js b/apps/ledger-live-desktop/src/renderer/components/Stepper/Breadcrumb.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Stepper/Breadcrumb.js rename to apps/ledger-live-desktop/src/renderer/components/Stepper/Breadcrumb.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Stepper/Step.js b/apps/ledger-live-desktop/src/renderer/components/Stepper/Step.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Stepper/Step.js rename to apps/ledger-live-desktop/src/renderer/components/Stepper/Step.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Stepper/index.js b/apps/ledger-live-desktop/src/renderer/components/Stepper/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Stepper/index.js rename to apps/ledger-live-desktop/src/renderer/components/Stepper/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/StepperNumber.js b/apps/ledger-live-desktop/src/renderer/components/StepperNumber.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/StepperNumber.js rename to apps/ledger-live-desktop/src/renderer/components/StepperNumber.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SuccessAnimatedIcon.js b/apps/ledger-live-desktop/src/renderer/components/SuccessAnimatedIcon.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SuccessAnimatedIcon.js rename to apps/ledger-live-desktop/src/renderer/components/SuccessAnimatedIcon.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SuccessDisplay.js b/apps/ledger-live-desktop/src/renderer/components/SuccessDisplay.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SuccessDisplay.js rename to apps/ledger-live-desktop/src/renderer/components/SuccessDisplay.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/SupportLinkError.js b/apps/ledger-live-desktop/src/renderer/components/SupportLinkError.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/SupportLinkError.js rename to apps/ledger-live-desktop/src/renderer/components/SupportLinkError.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Switch.js b/apps/ledger-live-desktop/src/renderer/components/Switch.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Switch.js rename to apps/ledger-live-desktop/src/renderer/components/Switch.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TabBar.js b/apps/ledger-live-desktop/src/renderer/components/TabBar.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TabBar.js rename to apps/ledger-live-desktop/src/renderer/components/TabBar.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TableContainer.js b/apps/ledger-live-desktop/src/renderer/components/TableContainer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TableContainer.js rename to apps/ledger-live-desktop/src/renderer/components/TableContainer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ThrowBlock.js b/apps/ledger-live-desktop/src/renderer/components/ThrowBlock.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ThrowBlock.js rename to apps/ledger-live-desktop/src/renderer/components/ThrowBlock.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/TimeBasedProgressBar.js b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/TimeBasedProgressBar.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ToastOverlay/TimeBasedProgressBar.js rename to apps/ledger-live-desktop/src/renderer/components/ToastOverlay/TimeBasedProgressBar.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.js b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.js deleted file mode 100644 index 23d22eb83ef1..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.js +++ /dev/null @@ -1,189 +0,0 @@ -// @flow - -import React, { useEffect } from "react"; -import styled from "styled-components"; -import Text from "~/renderer/components/Text"; -import FakeLink from "~/renderer/components/FakeLink"; -import IconCross from "~/renderer/icons/Cross"; -import { TimeBasedProgressBar } from "./TimeBasedProgressBar"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { animated, useTransition } from "react-spring"; -import { delay } from "@ledgerhq/live-common/lib/promise"; -import TriangleWarning from "~/renderer/icons/TriangleWarning"; -import { useTranslation } from "react-i18next"; -import InfoCircle from "~/renderer/icons/InfoCircle"; -import Box from "~/renderer/components/Box"; - -const Content: ThemedComponent<{}> = styled.div` - color: ${p => p.theme.colors.palette.background.paper}; - padding: 16px; - display: flex; - flex-direction: row; -`; - -const Wrapper: ThemedComponent<{ onClick?: () => void }> = styled(animated.div)` - cursor: ${p => (p.onClick ? "pointer" : "auto")}; - background-color: ${p => p.theme.colors.palette.text.shade100}; - position: relative; - overflow: hidden; - height: auto; - border-radius: 3px; - margin: 12px; - width: 400px; - box-shadow: 0px 20px 40px rgba(0, 0, 0, 0.1); -`; - -const DismissWrapper: ThemedComponent<{}> = styled.div` - position: absolute; - cursor: pointer; - color: ${p => p.theme.colors.palette.background.paper}; - display: flex; - top: 17px; - right: 17px; -`; - -const IconContainer = styled(Box)` - display: flex; - margin-right: 15px; - justify-content: center; -`; - -const TextContainer = styled.div` - display: flex; - flex: 1; - flex-direction: column; -`; - -const icons = { - warning: { - defaultIconColor: "orange", - Icon: TriangleWarning, - }, - info: { - defaultIconColor: "wallet", - Icon: InfoCircle, - }, -}; - -export function Toast({ - duration, - onDismiss, - dismissable = true, - callback, - type, - title, - cta, - text, - icon, - id, -}: { - duration?: number, - onDismiss: (id: string) => void, - dismissable?: boolean, - callback: any, - type?: string, - title: string, - cta?: string, - text?: string, - icon: string, - id: string, -}) { - const { t } = useTranslation(); - const { Icon, defaultIconColor } = icons[icon]; - - const transitions = useTransition(1, null, { - from: { - height: 0, - opacity: 0, - }, - enter: { - height: "auto", - opacity: 1, - }, - leave: { - height: 0, - opacity: 0, - }, - config: { duration: 1000, tension: 125, friction: 20, precision: 0.1 }, - }); - - useEffect(() => { - async function scheduledDismiss(duration) { - await delay(duration); - onDismiss(id); - } - if (duration) { - scheduledDismiss(duration); - } - }, [duration, id, onDismiss]); - - return transitions.map(({ key, item, props }) => ( - { - callback(); - onDismiss(id); - event.stopPropagation(); - }} - > - - - - - - {type ? ( - - {t(`toastOverlay.toastType.${type}`)} - - ) : null} - - - {title} - - {cta ? ( - - {cta} - - ) : null} - - {text ? ( - - {text} - - ) : null} - - - {duration ? ( - - ) : dismissable ? ( - { - onDismiss(id); - event.stopPropagation(); - }} - > - - - ) : null} - - )); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.jsx b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.jsx new file mode 100644 index 000000000000..b7a1d9fbf7ef --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/Toast.jsx @@ -0,0 +1,189 @@ +// @flow + +import React, { useEffect } from "react"; +import styled from "styled-components"; +import Text from "~/renderer/components/Text"; +import FakeLink from "~/renderer/components/FakeLink"; +import IconCross from "~/renderer/icons/Cross"; +import { TimeBasedProgressBar } from "./TimeBasedProgressBar"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { animated, useTransition } from "react-spring"; +import { delay } from "@ledgerhq/live-common/promise"; +import TriangleWarning from "~/renderer/icons/TriangleWarning"; +import { useTranslation } from "react-i18next"; +import InfoCircle from "~/renderer/icons/InfoCircle"; +import Box from "~/renderer/components/Box"; + +const Content: ThemedComponent<{}> = styled.div` + color: ${p => p.theme.colors.palette.background.paper}; + padding: 16px; + display: flex; + flex-direction: row; +`; + +const Wrapper: ThemedComponent<{ onClick?: () => void }> = styled(animated.div)` + cursor: ${p => (p.onClick ? "pointer" : "auto")}; + background-color: ${p => p.theme.colors.palette.text.shade100}; + position: relative; + overflow: hidden; + height: auto; + border-radius: 3px; + margin: 12px; + width: 400px; + box-shadow: 0px 20px 40px rgba(0, 0, 0, 0.1); +`; + +const DismissWrapper: ThemedComponent<{}> = styled.div` + position: absolute; + cursor: pointer; + color: ${p => p.theme.colors.palette.background.paper}; + display: flex; + top: 17px; + right: 17px; +`; + +const IconContainer = styled(Box)` + display: flex; + margin-right: 15px; + justify-content: center; +`; + +const TextContainer = styled.div` + display: flex; + flex: 1; + flex-direction: column; +`; + +const icons = { + warning: { + defaultIconColor: "orange", + Icon: TriangleWarning, + }, + info: { + defaultIconColor: "wallet", + Icon: InfoCircle, + }, +}; + +export function Toast({ + duration, + onDismiss, + dismissable = true, + callback, + type, + title, + cta, + text, + icon, + id, +}: { + duration?: number, + onDismiss: (id: string) => void, + dismissable?: boolean, + callback: any, + type?: string, + title: string, + cta?: string, + text?: string, + icon: string, + id: string, +}) { + const { t } = useTranslation(); + const { Icon, defaultIconColor } = icons[icon]; + + const transitions = useTransition(1, null, { + from: { + height: 0, + opacity: 0, + }, + enter: { + height: "auto", + opacity: 1, + }, + leave: { + height: 0, + opacity: 0, + }, + config: { duration: 1000, tension: 125, friction: 20, precision: 0.1 }, + }); + + useEffect(() => { + async function scheduledDismiss(duration) { + await delay(duration); + onDismiss(id); + } + if (duration) { + scheduledDismiss(duration); + } + }, [duration, id, onDismiss]); + + return transitions.map(({ key, item, props }) => ( + { + callback(); + onDismiss(id); + event.stopPropagation(); + }} + > + + + + + + {type ? ( + + {t(`toastOverlay.toastType.${type}`)} + + ) : null} + + + {title} + + {cta ? ( + + {cta} + + ) : null} + + {text ? ( + + {text} + + ) : null} + + + {duration ? ( + + ) : dismissable ? ( + { + onDismiss(id); + event.stopPropagation(); + }} + > + + + ) : null} + + )); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.js b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.js deleted file mode 100644 index 3ff978d0570f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.js +++ /dev/null @@ -1,60 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import { useTranslation } from "react-i18next"; -import { useDispatch } from "react-redux"; -import styled from "styled-components"; -import { Toast } from "./Toast"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { useToasts } from "@ledgerhq/live-common/lib/notifications/ToastProvider"; -import { v4 as uuidv4 } from "uuid"; -import { openInformationCenter } from "~/renderer/actions/UI"; - -const Wrapper: ThemedComponent<{}> = styled.div` - position: absolute; - bottom: 0; - right: 0; - padding: 18px; - & *:nth-child(n + 6) { - display: none; - } -`; - -export function ToastOverlay() { - const { toasts, dismissToast } = useToasts(); - const dispatch = useDispatch(); - const { t } = useTranslation(); - const onOpenInformationCenter = useCallback( - () => dispatch(openInformationCenter("announcement")), - [], - ); - - return ( - - {toasts.length < 2 ? ( - toasts.map(({ id, type, title, text, icon, callback }) => ( - - )) - ) : ( - - )} - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.jsx b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.jsx new file mode 100644 index 000000000000..103daada8d38 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/ToastOverlay/index.jsx @@ -0,0 +1,60 @@ +// @flow + +import React, { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import styled from "styled-components"; +import { Toast } from "./Toast"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; +import { v4 as uuidv4 } from "uuid"; +import { openInformationCenter } from "~/renderer/actions/UI"; + +const Wrapper: ThemedComponent<{}> = styled.div` + position: absolute; + bottom: 0; + right: 0; + padding: 18px; + & *:nth-child(n + 6) { + display: none; + } +`; + +export function ToastOverlay() { + const { toasts, dismissToast } = useToasts(); + const dispatch = useDispatch(); + const { t } = useTranslation(); + const onOpenInformationCenter = useCallback( + () => dispatch(openInformationCenter("announcement")), + [], + ); + + return ( + + {toasts.length < 2 ? ( + toasts.map(({ id, type, title, text, icon, callback }) => ( + + )) + ) : ( + + )} + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/ToggleButton.js b/apps/ledger-live-desktop/src/renderer/components/ToggleButton.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/ToggleButton.js rename to apps/ledger-live-desktop/src/renderer/components/ToggleButton.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TokenRow.js b/apps/ledger-live-desktop/src/renderer/components/TokenRow.js deleted file mode 100644 index e0cb98737542..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TokenRow.js +++ /dev/null @@ -1,72 +0,0 @@ -// @flow - -import React, { PureComponent } from "react"; -import Box from "~/renderer/components/Box"; -import type { Account, AccountLike } from "@ledgerhq/live-common/lib/types/account"; -import type { PortfolioRange } from "@ledgerhq/live-common/lib/portfolio/v2/types"; -import { getAccountCurrency } from "@ledgerhq/live-common/lib/account"; -import styled from "styled-components"; -import Header from "~/renderer/screens/accounts/AccountRowItem/Header"; -import Balance from "~/renderer/screens/accounts/AccountRowItem/Balance"; -import Delta from "~/renderer/screens/accounts/AccountRowItem/Delta"; -import Countervalue from "~/renderer/screens/accounts/AccountRowItem/Countervalue"; -import Star from "~/renderer/components/Stars/Star"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { TableRow } from "./TableContainer"; - -type Props = { - account: AccountLike, - nested?: boolean, - disableRounding?: boolean, - index: number, - parentAccount: Account, - onClick: (AccountLike, Account) => void, - range: PortfolioRange, -}; - -const NestedRow: ThemedComponent<{}> = styled(Box)` - flex: 1; - font-weight: 600; - align-items: center; - justify-content: flex-start; - display: flex; - flex-direction: row; - cursor: pointer; - position: relative; - margin: 0 -20px; - padding: 0 20px; - &:last-of-type { - margin-bottom: 0px; - } - :active { - background: ${p => p.theme.colors.palette.action.hover}; - } -`; - -class TokenRow extends PureComponent { - onClick = () => { - const { account, parentAccount, onClick } = this.props; - onClick(account, parentAccount); - }; - - render() { - const { account, range, index, nested, disableRounding } = this.props; - const currency = getAccountCurrency(account); - const unit = currency.units[0]; - const Row = nested ? NestedRow : TableRow; - return ( - -
- - - - - - ); - } -} - -export default TokenRow; diff --git a/apps/ledger-live-desktop/src/renderer/components/TokenRow.jsx b/apps/ledger-live-desktop/src/renderer/components/TokenRow.jsx new file mode 100644 index 000000000000..6fa7ab4303df --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TokenRow.jsx @@ -0,0 +1,72 @@ +// @flow + +import React, { PureComponent } from "react"; +import Box from "~/renderer/components/Box"; +import type { Account, AccountLike } from "@ledgerhq/live-common/types/account"; +import type { PortfolioRange } from "@ledgerhq/live-common/portfolio/v2/types"; +import { getAccountCurrency } from "@ledgerhq/live-common/account/index"; +import styled from "styled-components"; +import Header from "~/renderer/screens/accounts/AccountRowItem/Header"; +import Balance from "~/renderer/screens/accounts/AccountRowItem/Balance"; +import Delta from "~/renderer/screens/accounts/AccountRowItem/Delta"; +import Countervalue from "~/renderer/screens/accounts/AccountRowItem/Countervalue"; +import Star from "~/renderer/components/Stars/Star"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { TableRow } from "./TableContainer"; + +type Props = { + account: AccountLike, + nested?: boolean, + disableRounding?: boolean, + index: number, + parentAccount: Account, + onClick: (AccountLike, Account) => void, + range: PortfolioRange, +}; + +const NestedRow: ThemedComponent<{}> = styled(Box)` + flex: 1; + font-weight: 600; + align-items: center; + justify-content: flex-start; + display: flex; + flex-direction: row; + cursor: pointer; + position: relative; + margin: 0 -20px; + padding: 0 20px; + &:last-of-type { + margin-bottom: 0px; + } + :active { + background: ${p => p.theme.colors.palette.action.hover}; + } +`; + +class TokenRow extends PureComponent { + onClick = () => { + const { account, parentAccount, onClick } = this.props; + onClick(account, parentAccount); + }; + + render() { + const { account, range, index, nested, disableRounding } = this.props; + const currency = getAccountCurrency(account); + const unit = currency.units[0]; + const Row = nested ? NestedRow : TableRow; + return ( + +
+ + + + + + ); + } +} + +export default TokenRow; diff --git a/apps/ledger-live-desktop/src/renderer/components/Tooltip.js b/apps/ledger-live-desktop/src/renderer/components/Tooltip.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Tooltip.js rename to apps/ledger-live-desktop/src/renderer/components/Tooltip.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBanner.js b/apps/ledger-live-desktop/src/renderer/components/TopBanner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TopBanner.js rename to apps/ledger-live-desktop/src/renderer/components/TopBanner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.js deleted file mode 100644 index 0ccbfab4ad6a..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.js +++ /dev/null @@ -1,123 +0,0 @@ -// @flow -import React, { useState, useCallback } from "react"; -import { useSelector } from "react-redux"; -import { Trans } from "react-i18next"; -import { useBridgeSync, useGlobalSyncState } from "@ledgerhq/live-common/lib/bridge/react"; -import { useCountervaluesPolling } from "@ledgerhq/live-common/lib/countervalues/react"; -import { track } from "~/renderer/analytics/segment"; -import { isUpToDateSelector } from "~/renderer/reducers/accounts"; -import IconLoader from "~/renderer/icons/Loader"; -import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; -import IconCheckCircle from "~/renderer/icons/CheckCircle"; -import { Rotating } from "../Spinner"; -import Tooltip from "../Tooltip"; -import TranslatedError from "../TranslatedError"; -import Box from "../Box"; -import { ItemContainer } from "./shared"; - -export default function ActivityIndicatorInner() { - const bridgeSync = useBridgeSync(); - const globalSyncState = useGlobalSyncState(); - const isUpToDate = useSelector(isUpToDateSelector); - const cvPolling = useCountervaluesPolling(); - const isPending = cvPolling.pending || globalSyncState.pending; - const syncError = !isPending && (cvPolling.error || globalSyncState.error); - // we only show error if it's not up to date. this hide a bit error that happen from time to time - const isError = !!syncError && !isUpToDate; - const error = syncError ? globalSyncState.error : null; - - const [lastClickTime, setLastclickTime] = useState(0); - - const onClick = useCallback(() => { - cvPolling.poll(); - bridgeSync({ type: "SYNC_ALL_ACCOUNTS", priority: 5 }); - setLastclickTime(Date.now()); - track("SyncRefreshClick"); - }, [cvPolling, bridgeSync]); - - const isSpectronRun = !!process.env.PLAYWRIGHT_RUN; // we will keep 'spinning' in spectron case - const userClickTime = isSpectronRun ? 10000 : 1000; - const isUserClick = Date.now() - lastClickTime < userClickTime; // time to keep display the spinning on a UI click. - const isRotating = isPending && (!isUpToDate || isUserClick); - const isDisabled = isError || isRotating; - - const content = ( - - - {isError ? ( - - ) : isRotating ? ( - - ) : isUpToDate ? ( - - ) : ( - - )} - - - {isRotating ? ( - - ) : isError ? ( - <> - - - - - - - - ) : isUpToDate ? ( - - - - ) : ( - - )} - - - ); - - if (isError && error) { - return ( - - - - } - > - {content} - - ); - } - - return content; -} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.jsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.jsx new file mode 100644 index 000000000000..b05db6691a0e --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.jsx @@ -0,0 +1,123 @@ +// @flow +import React, { useState, useCallback } from "react"; +import { useSelector } from "react-redux"; +import { Trans } from "react-i18next"; +import { useBridgeSync, useGlobalSyncState } from "@ledgerhq/live-common/bridge/react/index"; +import { useCountervaluesPolling } from "@ledgerhq/live-common/countervalues/react"; +import { track } from "~/renderer/analytics/segment"; +import { isUpToDateSelector } from "~/renderer/reducers/accounts"; +import IconLoader from "~/renderer/icons/Loader"; +import IconExclamationCircle from "~/renderer/icons/ExclamationCircle"; +import IconCheckCircle from "~/renderer/icons/CheckCircle"; +import { Rotating } from "../Spinner"; +import Tooltip from "../Tooltip"; +import TranslatedError from "../TranslatedError"; +import Box from "../Box"; +import { ItemContainer } from "./shared"; + +export default function ActivityIndicatorInner() { + const bridgeSync = useBridgeSync(); + const globalSyncState = useGlobalSyncState(); + const isUpToDate = useSelector(isUpToDateSelector); + const cvPolling = useCountervaluesPolling(); + const isPending = cvPolling.pending || globalSyncState.pending; + const syncError = !isPending && (cvPolling.error || globalSyncState.error); + // we only show error if it's not up to date. this hide a bit error that happen from time to time + const isError = !!syncError && !isUpToDate; + const error = syncError ? globalSyncState.error : null; + + const [lastClickTime, setLastclickTime] = useState(0); + + const onClick = useCallback(() => { + cvPolling.poll(); + bridgeSync({ type: "SYNC_ALL_ACCOUNTS", priority: 5 }); + setLastclickTime(Date.now()); + track("SyncRefreshClick"); + }, [cvPolling, bridgeSync]); + + const isSpectronRun = !!process.env.PLAYWRIGHT_RUN; // we will keep 'spinning' in spectron case + const userClickTime = isSpectronRun ? 10000 : 1000; + const isUserClick = Date.now() - lastClickTime < userClickTime; // time to keep display the spinning on a UI click. + const isRotating = isPending && (!isUpToDate || isUserClick); + const isDisabled = isError || isRotating; + + const content = ( + + + {isError ? ( + + ) : isRotating ? ( + + ) : isUpToDate ? ( + + ) : ( + + )} + + + {isRotating ? ( + + ) : isError ? ( + <> + + + + + + + + ) : isUpToDate ? ( + + + + ) : ( + + )} + + + ); + + if (isError && error) { + return ( + + + + } + > + {content} + + ); + } + + return content; +} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.js deleted file mode 100644 index 7cfd5b03d9d5..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.js +++ /dev/null @@ -1,355 +0,0 @@ -// @flow -import React, { useCallback, useRef, useMemo, useState } from "react"; -import styled from "styled-components"; - -import { Trans } from "react-i18next"; -import { InView } from "react-intersection-observer"; - -import { useAnnouncements } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider"; -import { groupAnnouncements } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider/helpers"; - -import { useDispatch } from "react-redux"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import InfoCircle from "~/renderer/icons/InfoCircle"; -import TriangleWarning from "~/renderer/icons/TriangleWarning"; -import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; -import { openURL } from "~/renderer/linking"; -import { ScrollArea } from "~/renderer/components/Onboarding/ScrollArea"; -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import { useDeepLinkHandler } from "~/renderer/hooks/useDeeplinking"; - -import { closeInformationCenter } from "~/renderer/actions/UI"; -import useDateTimeFormat from "~/renderer/hooks/useDateTimeFormat"; - -const DateRowContainer = styled.div` - padding: 4px 16px; - background-color: ${({ theme }) => theme.colors.palette.background.default}; - border-radius: 4px; - margin: 25px 0px; -`; - -const levelThemes = { - info: { - title: "palette.text.shade100", - text: "palette.text.shade50", - background: undefined, - icon: undefined, - link: undefined, - padding: undefined, - }, - warning: { - title: "white", - text: "white", - background: "orange", - icon: "white", - link: "white", - padding: "16px", - }, - alert: { - title: "palette.text.shade100", - text: "palette.text.shade50", - background: "red", - icon: "white", - link: "white", - padding: undefined, - }, -}; - -const getLevelTheme = (levelName: string) => { - const levelData = levelThemes[levelName]; - - if (levelData) { - return levelData; - } - return levelThemes.info; -}; - -const UnReadNotifBadge = styled.div` - width: 8px; - height: 8px; - background-color: ${p => p.theme.colors.wallet}; - border-radius: 8px; - position: absolute; - top: calc(50% - 4px); - left: 0px; - z-index: 1; -`; - -type DateRowProps = { - date: Date, -}; - -const DateLabel = styled(Text).attrs({ - color: "palette.text.shade60", - ff: "Inter|SemiBold", - fontSize: "11px", - lineHeight: "18px", -})` - display: inline-block; - - &::first-letter { - text-transform: uppercase; - } -`; - -function DateRow({ date }: DateRowProps) { - const dateFormatter = useDateTimeFormat({ dateStyle: "full" }); - - return ( - - {dateFormatter(date)} - - ); -} - -const ArticleRootContainer = styled.div` - padding-left: ${p => (p.isRead ? 0 : 16)}px; - position: relative; -`; - -const ArticleContainer = styled(Box)` - display: flex; - flex-direction: row; - border-radius: 4px; -`; - -const ArticleRightColumnContainer = styled.div` - display: flex; - flex-direction: column; - flex: 1; -`; -type ArticleProps = { - level: string, - icon: string, - title: string, - text: string, - link?: { - label?: string, - href: string, - }, - utmCampaign?: string, - isRead?: boolean, -}; - -const icons = { - warning: { - defaultIconColor: "orange", - Icon: TriangleWarning, - }, - info: { - defaultIconColor: "wallet", - Icon: InfoCircle, - }, -}; - -const getIcon = (iconName: string) => { - const iconData = icons[iconName]; - - if (iconData) { - return iconData; - } - return icons.info; -}; - -type ArticleLinkProps = { - label?: string, - href: string, - utmCampaign?: string, - color?: string, -}; - -function ArticleLink({ label, href, utmCampaign, color }: ArticleLinkProps) { - const { handler } = useDeepLinkHandler(); - const dispatch = useDispatch(); - const url = useMemo(() => { - const url = new URL(href); - url.searchParams.set("utm_medium", "announcement"); - - if (utmCampaign) { - url.searchParams.set("utm_campaign", utmCampaign); - } - return url; - }, [href, utmCampaign]); - - const onLinkClick = useCallback(() => { - const isDeepLink = url.protocol === "ledgerlive:"; - - if (isDeepLink) { - handler(null, url.href); - dispatch(closeInformationCenter()); - } else openURL(url.href); - }, [url, handler, dispatch]); - - return ( - - {label || href} - - ); -} - -function Article({ - level = "info", - icon = "info", - title, - text, - link, - utmCampaign, - isRead, -}: ArticleProps) { - const [isSeen] = useState(isRead); - - const levelTheme = getLevelTheme(level); - const { Icon, defaultIconColor } = getIcon(icon); - - return ( - - - - - - - - {title} - - - - - - {text} - - {link ? ( - - ) : null} - - - {isSeen ? null : } - - ); -} - -const PanelContainer: ThemedComponent<*> = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex: 1; -`; - -const Separator = styled.div` - margin: 25px 0px; - width: 100%; - height: 1px; - background-color: ${({ theme }) => theme.colors.palette.text.shade10}; -`; - -export function AnnouncementPanel() { - const { cache, setAsSeen, seenIds, allIds } = useAnnouncements(); - const groupedAnnouncements = useMemo(() => groupAnnouncements(allIds.map(uuid => cache[uuid])), [ - cache, - allIds, - ]); - - const timeoutByUUID = useRef({}); - const handleInView = useCallback( - (visible, uuid) => { - const timeouts = timeoutByUUID.current; - if (!seenIds.includes(uuid) && visible && !timeouts[uuid]) { - timeouts[uuid] = setTimeout(() => { - setAsSeen(uuid); - delete timeouts[uuid]; - }, 1000); - } - - if (!visible && timeouts[uuid]) { - clearTimeout(timeouts[uuid]); - delete timeouts[uuid]; - } - }, - [seenIds, setAsSeen], - ); - - if (!groupedAnnouncements.length) { - return ( - - - - - - - - - ); - } - - return ( - - - {groupedAnnouncements.map((group, index) => ( - - {group.day ? : null} - {// $FlowFixMe - group.data.map(({ level, icon, content, uuid, utm_campaign: utmCampaign }, index) => ( - - handleInView(visible, uuid)}> -
- - {index < group.data.length - 1 ? : null} - - ))} - - ))} - - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.jsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.jsx new file mode 100644 index 000000000000..2bec20683380 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel.jsx @@ -0,0 +1,355 @@ +// @flow +import React, { useCallback, useRef, useMemo, useState } from "react"; +import styled from "styled-components"; + +import { Trans } from "react-i18next"; +import { InView } from "react-intersection-observer"; + +import { useAnnouncements } from "@ledgerhq/live-common/notifications/AnnouncementProvider/index"; +import { groupAnnouncements } from "@ledgerhq/live-common/notifications/AnnouncementProvider/helpers"; + +import { useDispatch } from "react-redux"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import InfoCircle from "~/renderer/icons/InfoCircle"; +import TriangleWarning from "~/renderer/icons/TriangleWarning"; +import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; +import { openURL } from "~/renderer/linking"; +import { ScrollArea } from "~/renderer/components/Onboarding/ScrollArea"; +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import { useDeepLinkHandler } from "~/renderer/hooks/useDeeplinking"; + +import { closeInformationCenter } from "~/renderer/actions/UI"; +import useDateTimeFormat from "~/renderer/hooks/useDateTimeFormat"; + +const DateRowContainer = styled.div` + padding: 4px 16px; + background-color: ${({ theme }) => theme.colors.palette.background.default}; + border-radius: 4px; + margin: 25px 0px; +`; + +const levelThemes = { + info: { + title: "palette.text.shade100", + text: "palette.text.shade50", + background: undefined, + icon: undefined, + link: undefined, + padding: undefined, + }, + warning: { + title: "white", + text: "white", + background: "orange", + icon: "white", + link: "white", + padding: "16px", + }, + alert: { + title: "palette.text.shade100", + text: "palette.text.shade50", + background: "red", + icon: "white", + link: "white", + padding: undefined, + }, +}; + +const getLevelTheme = (levelName: string) => { + const levelData = levelThemes[levelName]; + + if (levelData) { + return levelData; + } + return levelThemes.info; +}; + +const UnReadNotifBadge = styled.div` + width: 8px; + height: 8px; + background-color: ${p => p.theme.colors.wallet}; + border-radius: 8px; + position: absolute; + top: calc(50% - 4px); + left: 0px; + z-index: 1; +`; + +type DateRowProps = { + date: Date, +}; + +const DateLabel = styled(Text).attrs({ + color: "palette.text.shade60", + ff: "Inter|SemiBold", + fontSize: "11px", + lineHeight: "18px", +})` + display: inline-block; + + &::first-letter { + text-transform: uppercase; + } +`; + +function DateRow({ date }: DateRowProps) { + const dateFormatter = useDateTimeFormat({ dateStyle: "full" }); + + return ( + + {dateFormatter(date)} + + ); +} + +const ArticleRootContainer = styled.div` + padding-left: ${p => (p.isRead ? 0 : 16)}px; + position: relative; +`; + +const ArticleContainer = styled(Box)` + display: flex; + flex-direction: row; + border-radius: 4px; +`; + +const ArticleRightColumnContainer = styled.div` + display: flex; + flex-direction: column; + flex: 1; +`; +type ArticleProps = { + level: string, + icon: string, + title: string, + text: string, + link?: { + label?: string, + href: string, + }, + utmCampaign?: string, + isRead?: boolean, +}; + +const icons = { + warning: { + defaultIconColor: "orange", + Icon: TriangleWarning, + }, + info: { + defaultIconColor: "wallet", + Icon: InfoCircle, + }, +}; + +const getIcon = (iconName: string) => { + const iconData = icons[iconName]; + + if (iconData) { + return iconData; + } + return icons.info; +}; + +type ArticleLinkProps = { + label?: string, + href: string, + utmCampaign?: string, + color?: string, +}; + +function ArticleLink({ label, href, utmCampaign, color }: ArticleLinkProps) { + const { handler } = useDeepLinkHandler(); + const dispatch = useDispatch(); + const url = useMemo(() => { + const url = new URL(href); + url.searchParams.set("utm_medium", "announcement"); + + if (utmCampaign) { + url.searchParams.set("utm_campaign", utmCampaign); + } + return url; + }, [href, utmCampaign]); + + const onLinkClick = useCallback(() => { + const isDeepLink = url.protocol === "ledgerlive:"; + + if (isDeepLink) { + handler(null, url.href); + dispatch(closeInformationCenter()); + } else openURL(url.href); + }, [url, handler, dispatch]); + + return ( + + {label || href} + + ); +} + +function Article({ + level = "info", + icon = "info", + title, + text, + link, + utmCampaign, + isRead, +}: ArticleProps) { + const [isSeen] = useState(isRead); + + const levelTheme = getLevelTheme(level); + const { Icon, defaultIconColor } = getIcon(icon); + + return ( + + + + + + + + {title} + + + + + + {text} + + {link ? ( + + ) : null} + + + {isSeen ? null : } + + ); +} + +const PanelContainer: ThemedComponent<*> = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; +`; + +const Separator = styled.div` + margin: 25px 0px; + width: 100%; + height: 1px; + background-color: ${({ theme }) => theme.colors.palette.text.shade10}; +`; + +export function AnnouncementPanel() { + const { cache, setAsSeen, seenIds, allIds } = useAnnouncements(); + const groupedAnnouncements = useMemo(() => groupAnnouncements(allIds.map(uuid => cache[uuid])), [ + cache, + allIds, + ]); + + const timeoutByUUID = useRef({}); + const handleInView = useCallback( + (visible, uuid) => { + const timeouts = timeoutByUUID.current; + if (!seenIds.includes(uuid) && visible && !timeouts[uuid]) { + timeouts[uuid] = setTimeout(() => { + setAsSeen(uuid); + delete timeouts[uuid]; + }, 1000); + } + + if (!visible && timeouts[uuid]) { + clearTimeout(timeouts[uuid]); + delete timeouts[uuid]; + } + }, + [seenIds, setAsSeen], + ); + + if (!groupedAnnouncements.length) { + return ( + + + + + + + + + ); + } + + return ( + + + {groupedAnnouncements.map((group, index) => ( + + {group.day ? : null} + {// $FlowFixMe + group.data.map(({ level, icon, content, uuid, utm_campaign: utmCampaign }, index) => ( + + handleInView(visible, uuid)}> +
+ + {index < group.data.length - 1 ? : null} + + ))} + + ))} + + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.js deleted file mode 100644 index 0c37ef15706b..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.js +++ /dev/null @@ -1,107 +0,0 @@ -// @flow - -import React, { useMemo } from "react"; -import { SideDrawer } from "~/renderer/components/SideDrawer"; -import Box from "~/renderer/components/Box"; -import styled from "styled-components"; -import TabBar from "~/renderer/components/TabBar"; -import { AnnouncementPanel } from "~/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel"; -import { ServiceStatusPanel } from "~/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel"; - -import { useTranslation } from "react-i18next"; -import { useAnnouncements } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider"; -import { CSSTransition } from "react-transition-group"; -import { useSelector, useDispatch } from "react-redux"; -import { informationCenterStateSelector } from "~/renderer/reducers/UI"; -import { setTabInformationCenter } from "~/renderer/actions/UI"; -import { useFilteredServiceStatus } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider/index"; - -const FADE_DURATION = 200; - -const PanelContainer = styled.div` - display: flex; - flex-direction: column; - flex: 1; - overflow-y: hidden; - padding-bottom: 60px; - - &.information-panel-switch-appear { - opacity: 0; - } - - &.information-panel-switch-appear-active { - opacity: 1; - transition: opacity ${FADE_DURATION}ms ease-in; - } -`; - -export const InformationDrawer = ({ - isOpen, - onRequestClose, -}: { - isOpen: boolean, - onRequestClose: () => void, -}) => { - const { t } = useTranslation(); - const { allIds, seenIds } = useAnnouncements(); - const { incidents } = useFilteredServiceStatus(); - const unseenCount = allIds.length - seenIds.length; - const incidentCount = incidents.length; - const { tabId } = useSelector(informationCenterStateSelector); - const dispatch = useDispatch(); - - const tabs = useMemo( - () => [ - { - id: "announcement", - label: t( - unseenCount > 0 - ? "informationCenter.tabs.announcementsUnseen" - : "informationCenter.tabs.announcements", - { unseenCount }, - ), - Component: AnnouncementPanel, - }, - { - id: "status", - label: t( - incidentCount > 0 - ? "informationCenter.tabs.serviceStatusIncidentsOngoing" - : "informationCenter.tabs.serviceStatus", - { incidentCount }, - ), - Component: ServiceStatusPanel, - }, - ], - [unseenCount, t, incidentCount], - ); - - const tabIndex = useMemo(() => tabs.findIndex(tab => tab.id === tabId), [tabId, tabs]); - const CurrentPanel = tabs[tabIndex].Component; - - return ( - - - label)} - onIndexChange={newTabIndex => { - dispatch(setTabInformationCenter(tabs[newTabIndex].id)); - }} - index={tabs.findIndex(tab => tab.id === tabId)} - /> - - - - - - - - ); -}; diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.jsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.jsx new file mode 100644 index 000000000000..f0a90726c037 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/InformationDrawer.jsx @@ -0,0 +1,107 @@ +// @flow + +import React, { useMemo } from "react"; +import { SideDrawer } from "~/renderer/components/SideDrawer"; +import Box from "~/renderer/components/Box"; +import styled from "styled-components"; +import TabBar from "~/renderer/components/TabBar"; +import { AnnouncementPanel } from "~/renderer/components/TopBar/NotificationIndicator/AnnouncementPanel"; +import { ServiceStatusPanel } from "~/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel"; + +import { useTranslation } from "react-i18next"; +import { useAnnouncements } from "@ledgerhq/live-common/notifications/AnnouncementProvider/index"; +import { CSSTransition } from "react-transition-group"; +import { useSelector, useDispatch } from "react-redux"; +import { informationCenterStateSelector } from "~/renderer/reducers/UI"; +import { setTabInformationCenter } from "~/renderer/actions/UI"; +import { useFilteredServiceStatus } from "@ledgerhq/live-common/notifications/ServiceStatusProvider/index"; + +const FADE_DURATION = 200; + +const PanelContainer = styled.div` + display: flex; + flex-direction: column; + flex: 1; + overflow-y: hidden; + padding-bottom: 60px; + + &.information-panel-switch-appear { + opacity: 0; + } + + &.information-panel-switch-appear-active { + opacity: 1; + transition: opacity ${FADE_DURATION}ms ease-in; + } +`; + +export const InformationDrawer = ({ + isOpen, + onRequestClose, +}: { + isOpen: boolean, + onRequestClose: () => void, +}) => { + const { t } = useTranslation(); + const { allIds, seenIds } = useAnnouncements(); + const { incidents } = useFilteredServiceStatus(); + const unseenCount = allIds.length - seenIds.length; + const incidentCount = incidents.length; + const { tabId } = useSelector(informationCenterStateSelector); + const dispatch = useDispatch(); + + const tabs = useMemo( + () => [ + { + id: "announcement", + label: t( + unseenCount > 0 + ? "informationCenter.tabs.announcementsUnseen" + : "informationCenter.tabs.announcements", + { unseenCount }, + ), + Component: AnnouncementPanel, + }, + { + id: "status", + label: t( + incidentCount > 0 + ? "informationCenter.tabs.serviceStatusIncidentsOngoing" + : "informationCenter.tabs.serviceStatus", + { incidentCount }, + ), + Component: ServiceStatusPanel, + }, + ], + [unseenCount, t, incidentCount], + ); + + const tabIndex = useMemo(() => tabs.findIndex(tab => tab.id === tabId), [tabId, tabs]); + const CurrentPanel = tabs[tabIndex].Component; + + return ( + + + label)} + onIndexChange={newTabIndex => { + dispatch(setTabInformationCenter(tabs[newTabIndex].id)); + }} + index={tabs.findIndex(tab => tab.id === tabId)} + /> + + + + + + + + ); +}; diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.js deleted file mode 100644 index 65d1b266eebd..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.js +++ /dev/null @@ -1,160 +0,0 @@ -// @flow - -import styled from "styled-components"; -import TriangleWarning from "~/renderer/icons/TriangleWarning"; -import React from "react"; -import { openURL } from "~/renderer/linking"; -import { useFilteredServiceStatus } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider"; -import type { Incident } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider/types"; -import Text from "~/renderer/components/Text"; -import SuccessAnimatedIcon from "~/renderer/components/SuccessAnimatedIcon"; -import { Trans } from "react-i18next"; -import { FakeLink } from "~/renderer/components/Link"; -import Box from "~/renderer/components/Box"; -import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; -import { ScrollArea } from "~/renderer/components/Onboarding/ScrollArea"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { urls } from "~/config/urls"; - -const IncidentContainer = styled(Box)` - width: 100%; - display: flex; - flex-direction: row; - border-radius: 4px; - border: 1px solid ${p => p.theme.colors.palette.text.shade10}; - padding: 16px; - margin: 6px; -`; - -const IncidentRightColumnContainer = styled.div` - display: flex; - flex-direction: column; - flex: 1; -`; - -const IncidentLeftColumnContainer = styled.div` - display: flex; - flex-direction: column; -`; - -const IncidentIconContainer = styled(Box)` - padding-right: 14.5px; -`; - -type IncidentProps = { - incidentData: Incident, -}; - -function IncidentArticle({ incidentData }: IncidentProps) { - const { name, incident_updates: incidentUpdates, shortlink } = incidentData; - return ( - - - - - - - - - {name} - - {incidentUpdates - ? incidentUpdates.map(({ body }, index) => ( - - {body} - - )) - : null} - openURL(shortlink) : null} - style={{ - marginTop: 15, - }} - > - - - - - ); -} - -function StatusOkHeader() { - return ( - <> - - - - - - - openURL(urls.ledgerStatus)} /> - - - - ); -} - -function StatusNotOkHeader({ incidents }: { incidents: Incident[] }) { - return ( - - - - - - - - - {incidents.map(incidentData => ( - - ))} - - - ); -} - -const PanelContainer: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex: 1; - height: 100%; -`; - -export function ServiceStatusPanel() { - const { incidents } = useFilteredServiceStatus(); - - console.log({ incidents }); - - return ( - - {incidents.length > 0 ? : } - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.jsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.jsx new file mode 100644 index 000000000000..d68cca3230c9 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/ServiceStatusPanel.jsx @@ -0,0 +1,160 @@ +// @flow + +import styled from "styled-components"; +import TriangleWarning from "~/renderer/icons/TriangleWarning"; +import React from "react"; +import { openURL } from "~/renderer/linking"; +import { useFilteredServiceStatus } from "@ledgerhq/live-common/notifications/ServiceStatusProvider/index"; +import type { Incident } from "@ledgerhq/live-common/notifications/ServiceStatusProvider/types"; +import Text from "~/renderer/components/Text"; +import SuccessAnimatedIcon from "~/renderer/components/SuccessAnimatedIcon"; +import { Trans } from "react-i18next"; +import { FakeLink } from "~/renderer/components/Link"; +import Box from "~/renderer/components/Box"; +import LinkWithExternalIcon from "~/renderer/components/LinkWithExternalIcon"; +import { ScrollArea } from "~/renderer/components/Onboarding/ScrollArea"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { urls } from "~/config/urls"; + +const IncidentContainer = styled(Box)` + width: 100%; + display: flex; + flex-direction: row; + border-radius: 4px; + border: 1px solid ${p => p.theme.colors.palette.text.shade10}; + padding: 16px; + margin: 6px; +`; + +const IncidentRightColumnContainer = styled.div` + display: flex; + flex-direction: column; + flex: 1; +`; + +const IncidentLeftColumnContainer = styled.div` + display: flex; + flex-direction: column; +`; + +const IncidentIconContainer = styled(Box)` + padding-right: 14.5px; +`; + +type IncidentProps = { + incidentData: Incident, +}; + +function IncidentArticle({ incidentData }: IncidentProps) { + const { name, incident_updates: incidentUpdates, shortlink } = incidentData; + return ( + + + + + + + + + {name} + + {incidentUpdates + ? incidentUpdates.map(({ body }, index) => ( + + {body} + + )) + : null} + openURL(shortlink) : null} + style={{ + marginTop: 15, + }} + > + + + + + ); +} + +function StatusOkHeader() { + return ( + <> + + + + + + + openURL(urls.ledgerStatus)} /> + + + + ); +} + +function StatusNotOkHeader({ incidents }: { incidents: Incident[] }) { + return ( + + + + + + + + + {incidents.map(incidentData => ( + + ))} + + + ); +} + +const PanelContainer: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + flex: 1; + height: 100%; +`; + +export function ServiceStatusPanel() { + const { incidents } = useFilteredServiceStatus(); + + console.log({ incidents }); + + return ( + + {incidents.length > 0 ? : } + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.js deleted file mode 100644 index daa671b195e7..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.js +++ /dev/null @@ -1,41 +0,0 @@ -// @flow - -import Tooltip from "~/renderer/components/Tooltip"; -import React from "react"; -import { ItemContainer } from "../shared"; -import IconBell from "~/renderer/icons/Bell"; -import { useAnnouncements } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider"; -import { useTranslation } from "react-i18next"; -import { InformationDrawer } from "./InformationDrawer"; -import { useDispatch, useSelector } from "react-redux"; -import { informationCenterStateSelector } from "~/renderer/reducers/UI"; -import { openInformationCenter, closeInformationCenter } from "~/renderer/actions/UI"; - -export function NotificationIndicator() { - const { t } = useTranslation(); - const { allIds, seenIds } = useAnnouncements(); - - const totalNotifCount = allIds.length - seenIds.length; - const { isOpen } = useSelector(informationCenterStateSelector); - const dispatch = useDispatch(); - - return ( - <> - dispatch(closeInformationCenter())} - /> - - { - dispatch(openInformationCenter()); - }} - > - - - - - ); -} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.jsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.jsx new file mode 100644 index 000000000000..090708f75182 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/NotificationIndicator/index.jsx @@ -0,0 +1,41 @@ +// @flow + +import Tooltip from "~/renderer/components/Tooltip"; +import React from "react"; +import { ItemContainer } from "../shared"; +import IconBell from "~/renderer/icons/Bell"; +import { useAnnouncements } from "@ledgerhq/live-common/notifications/AnnouncementProvider/index"; +import { useTranslation } from "react-i18next"; +import { InformationDrawer } from "./InformationDrawer"; +import { useDispatch, useSelector } from "react-redux"; +import { informationCenterStateSelector } from "~/renderer/reducers/UI"; +import { openInformationCenter, closeInformationCenter } from "~/renderer/actions/UI"; + +export function NotificationIndicator() { + const { t } = useTranslation(); + const { allIds, seenIds } = useAnnouncements(); + + const totalNotifCount = allIds.length - seenIds.length; + const { isOpen } = useSelector(informationCenterStateSelector); + const dispatch = useDispatch(); + + return ( + <> + dispatch(closeInformationCenter())} + /> + + { + dispatch(openInformationCenter()); + }} + > + + + + + ); +} diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/index.js b/apps/ledger-live-desktop/src/renderer/components/TopBar/index.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TopBar/index.js rename to apps/ledger-live-desktop/src/renderer/components/TopBar/index.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TrackAppStart.js b/apps/ledger-live-desktop/src/renderer/components/TrackAppStart.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TrackAppStart.js rename to apps/ledger-live-desktop/src/renderer/components/TrackAppStart.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/TransactionConfirmField.js b/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/TransactionConfirmField.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/TransactionConfirmField.js rename to apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/TransactionConfirmField.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.js b/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.js deleted file mode 100644 index 096ef0d9f91c..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.js +++ /dev/null @@ -1,221 +0,0 @@ -// @flow - -import invariant from "invariant"; -import React from "react"; -import { Trans, withTranslation } from "react-i18next"; -import type { TFunction } from "react-i18next"; -import styled from "styled-components"; -import { getAccountUnit, getMainAccount } from "@ledgerhq/live-common/lib/account"; -import type { - Account, - AccountLike, - Transaction, - TransactionStatus, -} from "@ledgerhq/live-common/lib/types"; -import { getDeviceTransactionConfig } from "@ledgerhq/live-common/lib/transaction"; -import type { DeviceTransactionField } from "@ledgerhq/live-common/lib/transaction"; -import type { Device } from "@ledgerhq/live-common/lib/hw/actions/types"; -import transactionConfirmFieldsPerFamily from "~/renderer/generated/TransactionConfirmFields"; -import Box from "~/renderer/components/Box"; -import Text from "~/renderer/components/Text"; -import WarnBox from "~/renderer/components/WarnBox"; -import useTheme from "~/renderer/hooks/useTheme"; -import FormattedVal from "~/renderer/components/FormattedVal"; -import { renderVerifyUnwrapped } from "~/renderer/components/DeviceAction/rendering"; -import TransactionConfirmField from "./TransactionConfirmField"; - -const FieldText = styled(Text).attrs(() => ({ - ml: 1, - ff: "Inter|Medium", - color: "palette.text.shade80", - fontSize: 3, -}))` - word-break: break-all; - text-align: right; - max-width: 50%; -`; - -export type FieldComponentProps = { - account: AccountLike, - parentAccount: ?Account, - transaction: Transaction, - status: TransactionStatus, - field: DeviceTransactionField, -}; - -export type FieldComponent = React$ComponentType; - -const AmountField = ({ account, status: { amount }, field }: FieldComponentProps) => ( - - - -); - -const FeesField = ({ account, parentAccount, status, field }: FieldComponentProps) => { - const mainAccount = getMainAccount(account, parentAccount); - const { estimatedFees } = status; - const feesUnit = getAccountUnit(mainAccount); - return ( - - - - ); -}; - -const AddressField = ({ field }: FieldComponentProps) => { - invariant(field.type === "address", "AddressField invalid"); - return ( - - {field.address} - - ); -}; - -// NB Leaving AddressField although I think it's redundant at this point -// in case we want specific styles for addresses. -const TextField = ({ field }: FieldComponentProps) => { - invariant(field.type === "text", "TextField invalid"); - return ( - - {field.value} - - ); -}; - -const commonFieldComponents: { [_: *]: FieldComponent } = { - amount: AmountField, - fees: FeesField, - address: AddressField, - text: TextField, -}; - -const Container = styled(Box).attrs(() => ({ - alignItems: "center", - fontSize: 4, - pb: 4, -}))``; - -const Info = styled(Box).attrs(() => ({ - ff: "Inter|SemiBold", - color: "palette.text.shade100", - mt: 6, - mb: 4, - px: 5, -}))` - text-align: center; -`; - -type Props = { - t: TFunction, - device: Device, - account: AccountLike, - parentAccount: ?Account, - transaction: Transaction, - status: TransactionStatus, -}; - -const TransactionConfirm = ({ t, device, account, parentAccount, transaction, status }: Props) => { - const mainAccount = getMainAccount(account, parentAccount); - const type = useTheme("colors.palette.type"); - - if (!device) return null; - - const r = transactionConfirmFieldsPerFamily[mainAccount.currency.family]; - - const fieldComponents = { - ...commonFieldComponents, - ...(r && r.fieldComponents), - }; - const Warning = r && r.warning; - const Title = r && r.title; - const Footer = r && r.footer; - - const fields = getDeviceTransactionConfig({ - account, - parentAccount, - transaction, - status, - }); - - const key = transaction.mode || "send"; - const recipientWording = t(`TransactionConfirm.recipientWording.${key}`); - - return ( - - {Warning ? ( - - ) : ( - - - - )} - {Title ? ( - - ) : ( - <Info> - <Trans i18nKey="TransactionConfirm.title" /> - </Info> - )} - - <Box style={{ width: "100%" }} px={30} mb={20}> - {fields.map((field, i) => { - const MaybeComponent = fieldComponents[field.type]; - if (!MaybeComponent) { - console.log( - `TransactionConfirm field ${field.type} is not implemented! add a generic implementation in components/TransactionConfirm.js or inside families/*/TransactionConfirmFields.js`, - ); - return null; - } - return ( - <MaybeComponent - key={i} - field={field} - account={account} - parentAccount={parentAccount} - transaction={transaction} - status={status} - /> - ); - })} - </Box> - - {Footer ? <Footer transaction={transaction} /> : null} - - {renderVerifyUnwrapped({ modelId: device.modelId, type })} - </Container> - ); -}; - -export default withTranslation()(TransactionConfirm); diff --git a/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.jsx b/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.jsx new file mode 100644 index 000000000000..2884333ab424 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TransactionConfirm/index.jsx @@ -0,0 +1,221 @@ +// @flow + +import invariant from "invariant"; +import React from "react"; +import { Trans, withTranslation } from "react-i18next"; +import type { TFunction } from "react-i18next"; +import styled from "styled-components"; +import { getAccountUnit, getMainAccount } from "@ledgerhq/live-common/account/index"; +import type { + Account, + AccountLike, + Transaction, + TransactionStatus, +} from "@ledgerhq/live-common/types/index"; +import { getDeviceTransactionConfig } from "@ledgerhq/live-common/transaction/index"; +import type { DeviceTransactionField } from "@ledgerhq/live-common/transaction/index"; +import type { Device } from "@ledgerhq/live-common/hw/actions/types"; +import transactionConfirmFieldsPerFamily from "~/renderer/generated/TransactionConfirmFields"; +import Box from "~/renderer/components/Box"; +import Text from "~/renderer/components/Text"; +import WarnBox from "~/renderer/components/WarnBox"; +import useTheme from "~/renderer/hooks/useTheme"; +import FormattedVal from "~/renderer/components/FormattedVal"; +import { renderVerifyUnwrapped } from "~/renderer/components/DeviceAction/rendering"; +import TransactionConfirmField from "./TransactionConfirmField"; + +const FieldText = styled(Text).attrs(() => ({ + ml: 1, + ff: "Inter|Medium", + color: "palette.text.shade80", + fontSize: 3, +}))` + word-break: break-all; + text-align: right; + max-width: 50%; +`; + +export type FieldComponentProps = { + account: AccountLike, + parentAccount: ?Account, + transaction: Transaction, + status: TransactionStatus, + field: DeviceTransactionField, +}; + +export type FieldComponent = React$ComponentType<FieldComponentProps>; + +const AmountField = ({ account, status: { amount }, field }: FieldComponentProps) => ( + <TransactionConfirmField label={field.label}> + <FormattedVal + color={"palette.text.shade80"} + unit={getAccountUnit(account)} + val={amount} + fontSize={3} + inline + showCode + alwaysShowValue + disableRounding + /> + </TransactionConfirmField> +); + +const FeesField = ({ account, parentAccount, status, field }: FieldComponentProps) => { + const mainAccount = getMainAccount(account, parentAccount); + const { estimatedFees } = status; + const feesUnit = getAccountUnit(mainAccount); + return ( + <TransactionConfirmField label={field.label}> + <FormattedVal + color={"palette.text.shade80"} + unit={feesUnit} + val={estimatedFees} + fontSize={3} + inline + showCode + alwaysShowValue + /> + </TransactionConfirmField> + ); +}; + +const AddressField = ({ field }: FieldComponentProps) => { + invariant(field.type === "address", "AddressField invalid"); + return ( + <TransactionConfirmField label={field.label}> + <FieldText>{field.address}</FieldText> + </TransactionConfirmField> + ); +}; + +// NB Leaving AddressField although I think it's redundant at this point +// in case we want specific styles for addresses. +const TextField = ({ field }: FieldComponentProps) => { + invariant(field.type === "text", "TextField invalid"); + return ( + <TransactionConfirmField + label={field.label} + tooltipKey={field.tooltipI18nKey} + tooltipArgs={field.tooltipI18nArgs} + > + <FieldText>{field.value}</FieldText> + </TransactionConfirmField> + ); +}; + +const commonFieldComponents: { [_: *]: FieldComponent } = { + amount: AmountField, + fees: FeesField, + address: AddressField, + text: TextField, +}; + +const Container = styled(Box).attrs(() => ({ + alignItems: "center", + fontSize: 4, + pb: 4, +}))``; + +const Info = styled(Box).attrs(() => ({ + ff: "Inter|SemiBold", + color: "palette.text.shade100", + mt: 6, + mb: 4, + px: 5, +}))` + text-align: center; +`; + +type Props = { + t: TFunction, + device: Device, + account: AccountLike, + parentAccount: ?Account, + transaction: Transaction, + status: TransactionStatus, +}; + +const TransactionConfirm = ({ t, device, account, parentAccount, transaction, status }: Props) => { + const mainAccount = getMainAccount(account, parentAccount); + const type = useTheme("colors.palette.type"); + + if (!device) return null; + + const r = transactionConfirmFieldsPerFamily[mainAccount.currency.family]; + + const fieldComponents = { + ...commonFieldComponents, + ...(r && r.fieldComponents), + }; + const Warning = r && r.warning; + const Title = r && r.title; + const Footer = r && r.footer; + + const fields = getDeviceTransactionConfig({ + account, + parentAccount, + transaction, + status, + }); + + const key = transaction.mode || "send"; + const recipientWording = t(`TransactionConfirm.recipientWording.${key}`); + + return ( + <Container> + {Warning ? ( + <Warning + account={account} + parentAccount={parentAccount} + transaction={transaction} + recipientWording={recipientWording} + status={status} + /> + ) : ( + <WarnBox> + <Trans i18nKey="TransactionConfirm.warning" values={{ recipientWording }} /> + </WarnBox> + )} + {Title ? ( + <Title + account={account} + parentAccount={parentAccount} + transaction={transaction} + status={status} + /> + ) : ( + <Info> + <Trans i18nKey="TransactionConfirm.title" /> + </Info> + )} + + <Box style={{ width: "100%" }} px={30} mb={20}> + {fields.map((field, i) => { + const MaybeComponent = fieldComponents[field.type]; + if (!MaybeComponent) { + console.log( + `TransactionConfirm field ${field.type} is not implemented! add a generic implementation in components/TransactionConfirm.js or inside families/*/TransactionConfirmFields.js`, + ); + return null; + } + return ( + <MaybeComponent + key={i} + field={field} + account={account} + parentAccount={parentAccount} + transaction={transaction} + status={status} + /> + ); + })} + </Box> + + {Footer ? <Footer transaction={transaction} /> : null} + + {renderVerifyUnwrapped({ modelId: device.modelId, type })} + </Container> + ); +}; + +export default withTranslation()(TransactionConfirm); diff --git a/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.js b/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.js deleted file mode 100644 index 19596d20fe28..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.js +++ /dev/null @@ -1,35 +0,0 @@ -// @flow - -import React from "react"; -import { useSelector } from "react-redux"; -import { useTranslation } from "react-i18next"; -import type { AccountLike } from "@ledgerhq/live-common/lib/types"; -import { isAccountBalanceUnconfirmed } from "@ledgerhq/live-common/lib/account"; -import { accountsSelector } from "./../reducers/accounts"; -import IconClock from "~/renderer/icons/Clock"; -import ToolTip from "~/renderer/components/Tooltip"; -import Box from "~/renderer/components/Box"; - -const TransactionsPendingConfirmationWarning = ({ - maybeAccount, -}: { - maybeAccount?: AccountLike, -}) => { - let accounts = useSelector(accountsSelector); - accounts = maybeAccount ? [maybeAccount] : accounts; - const { t } = useTranslation(); - const content = ( - <Box p={1} style={{ maxWidth: 230 }}> - {t("dashboard.transactionsPendingConfirmation")} - </Box> - ); - return accounts.some(isAccountBalanceUnconfirmed) ? ( - <ToolTip content={content}> - <Box px={1} justifyContent={"center"}> - <IconClock size={16} /> - </Box> - </ToolTip> - ) : null; -}; - -export default TransactionsPendingConfirmationWarning; diff --git a/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.jsx b/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.jsx new file mode 100644 index 000000000000..051b2601f571 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/TransactionsPendingConfirmationWarning.jsx @@ -0,0 +1,35 @@ +// @flow + +import React from "react"; +import { useSelector } from "react-redux"; +import { useTranslation } from "react-i18next"; +import type { AccountLike } from "@ledgerhq/live-common/types/index"; +import { isAccountBalanceUnconfirmed } from "@ledgerhq/live-common/account/index"; +import { accountsSelector } from "./../reducers/accounts"; +import IconClock from "~/renderer/icons/Clock"; +import ToolTip from "~/renderer/components/Tooltip"; +import Box from "~/renderer/components/Box"; + +const TransactionsPendingConfirmationWarning = ({ + maybeAccount, +}: { + maybeAccount?: AccountLike, +}) => { + let accounts = useSelector(accountsSelector); + accounts = maybeAccount ? [maybeAccount] : accounts; + const { t } = useTranslation(); + const content = ( + <Box p={1} style={{ maxWidth: 230 }}> + {t("dashboard.transactionsPendingConfirmation")} + </Box> + ); + return accounts.some(isAccountBalanceUnconfirmed) ? ( + <ToolTip content={content}> + <Box px={1} justifyContent={"center"}> + <IconClock size={16} /> + </Box> + </ToolTip> + ) : null; +}; + +export default TransactionsPendingConfirmationWarning; diff --git a/apps/ledger-live-desktop/src/renderer/components/TranslatedError.js b/apps/ledger-live-desktop/src/renderer/components/TranslatedError.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TranslatedError.js rename to apps/ledger-live-desktop/src/renderer/components/TranslatedError.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/TriggerAppReady.js b/apps/ledger-live-desktop/src/renderer/components/TriggerAppReady.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/TriggerAppReady.js rename to apps/ledger-live-desktop/src/renderer/components/TriggerAppReady.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Updater/Banner.js b/apps/ledger-live-desktop/src/renderer/components/Updater/Banner.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Updater/Banner.js rename to apps/ledger-live-desktop/src/renderer/components/Updater/Banner.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Updater/UpdateDot.js b/apps/ledger-live-desktop/src/renderer/components/Updater/UpdateDot.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Updater/UpdateDot.js rename to apps/ledger-live-desktop/src/renderer/components/Updater/UpdateDot.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/Updater/UpdaterContext.js b/apps/ledger-live-desktop/src/renderer/components/Updater/UpdaterContext.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/Updater/UpdaterContext.js rename to apps/ledger-live-desktop/src/renderer/components/Updater/UpdaterContext.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/WarnBox.js b/apps/ledger-live-desktop/src/renderer/components/WarnBox.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/WarnBox.js rename to apps/ledger-live-desktop/src/renderer/components/WarnBox.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.js deleted file mode 100644 index 35797bc7c47f..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.js +++ /dev/null @@ -1,110 +0,0 @@ -// @flow - -import React from "react"; -import styled, { useTheme } from "styled-components"; -import { useTranslation } from "react-i18next"; - -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; - -import LiveAppIcon from "~/renderer/components/WebPlatformPlayer/LiveAppIcon"; -import LedgerLiveLogo from "~/renderer/components/LedgerLiveLogo"; -import InfoIcon from "~/renderer/icons/InfoCircle"; -import { rgba } from "~/renderer/styles/helpers"; -import Logo from "~/renderer/icons/Logo"; -import Text from "~/renderer/components/Text"; -import Box from "~/renderer/components/Box"; - -type Props = { - manifest: AppManifest, -}; - -const Head: ThemedComponent<{}> = styled(Box).attrs(p => ({ - horizontal: true, - alignItems: "center", - justifyContent: "center", - mb: 24, -}))` - > * { - margin: 0 4px; - } -`; - -const Dashes: ThemedComponent<{}> = styled.div` - width: 40px; - opacity: 0.5; - height: 3px; - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1' y1='1.5' x2='39' y2='1.5' stroke='%23${p => - p.theme.colors.palette.primary.main.slice( - 1, - )}' stroke-opacity='0.5' stroke-width='2' stroke-linecap='round' stroke-dasharray='2 6'/%3E%3C/svg%3E%0A"); -`; - -const Title: ThemedComponent<{}> = styled(Text).attrs(p => ({ - mb: 12, - fontSize: 22, - textAlign: "center", - ff: "Inter|SemiBold", -}))``; - -const Description: ThemedComponent<{}> = styled(Text).attrs(p => ({ - color: p.theme.colors.palette.text.shade60, - ff: "Inter|Regular", - mb: 12, - textAlign: "center", - fontSize: 14, -}))``; - -const BlueInfoContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ - flex: 1, - p: 12, - horizontal: true, - alignItems: "center", - mt: 28, -}))` - background-color: ${p => rgba(p.theme.colors.palette.primary.main, 0.1)}; -`; - -const InfoIconContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ - mr: "6px", -}))``; - -const InfoText: ThemedComponent<{}> = styled(Text).attrs(p => ({ - ml: "6px", - ff: "Inter|Medium", - fontSize: 12, - lineHeight: 18, - color: p.theme.colors.palette.primary.main, -}))` - flex: 1; - line-height: 18px; - white-space: pre-wrap; -`; - -export const LiveAppDisclaimer = ({ manifest }: Props) => { - const { colors } = useTheme(); - const { t } = useTranslation(); - - return ( - <> - <Head> - <LedgerLiveLogo width={48} height={48} icon={<Logo size={31} />} /> - <Dashes /> - <LiveAppIcon size={48} name={manifest.name} icon={manifest.icon || ""} /> - </Head> - - <Title>{t("platform.disclaimer.title")} - - {t("platform.disclaimer.description")} - - - - - - {t("platform.disclaimer.legalAdvice")} - - - ); -}; - -export default LiveAppDisclaimer; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.jsx b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.jsx new file mode 100644 index 000000000000..a22ffcbb474b --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDisclaimer.jsx @@ -0,0 +1,110 @@ +// @flow + +import React from "react"; +import styled, { useTheme } from "styled-components"; +import { useTranslation } from "react-i18next"; + +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; + +import LiveAppIcon from "~/renderer/components/WebPlatformPlayer/LiveAppIcon"; +import LedgerLiveLogo from "~/renderer/components/LedgerLiveLogo"; +import InfoIcon from "~/renderer/icons/InfoCircle"; +import { rgba } from "~/renderer/styles/helpers"; +import Logo from "~/renderer/icons/Logo"; +import Text from "~/renderer/components/Text"; +import Box from "~/renderer/components/Box"; + +type Props = { + manifest: AppManifest, +}; + +const Head: ThemedComponent<{}> = styled(Box).attrs(p => ({ + horizontal: true, + alignItems: "center", + justifyContent: "center", + mb: 24, +}))` + > * { + margin: 0 4px; + } +`; + +const Dashes: ThemedComponent<{}> = styled.div` + width: 40px; + opacity: 0.5; + height: 3px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='1' y1='1.5' x2='39' y2='1.5' stroke='%23${p => + p.theme.colors.palette.primary.main.slice( + 1, + )}' stroke-opacity='0.5' stroke-width='2' stroke-linecap='round' stroke-dasharray='2 6'/%3E%3C/svg%3E%0A"); +`; + +const Title: ThemedComponent<{}> = styled(Text).attrs(p => ({ + mb: 12, + fontSize: 22, + textAlign: "center", + ff: "Inter|SemiBold", +}))``; + +const Description: ThemedComponent<{}> = styled(Text).attrs(p => ({ + color: p.theme.colors.palette.text.shade60, + ff: "Inter|Regular", + mb: 12, + textAlign: "center", + fontSize: 14, +}))``; + +const BlueInfoContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ + flex: 1, + p: 12, + horizontal: true, + alignItems: "center", + mt: 28, +}))` + background-color: ${p => rgba(p.theme.colors.palette.primary.main, 0.1)}; +`; + +const InfoIconContainer: ThemedComponent<{}> = styled(Box).attrs(p => ({ + mr: "6px", +}))``; + +const InfoText: ThemedComponent<{}> = styled(Text).attrs(p => ({ + ml: "6px", + ff: "Inter|Medium", + fontSize: 12, + lineHeight: 18, + color: p.theme.colors.palette.primary.main, +}))` + flex: 1; + line-height: 18px; + white-space: pre-wrap; +`; + +export const LiveAppDisclaimer = ({ manifest }: Props) => { + const { colors } = useTheme(); + const { t } = useTranslation(); + + return ( + <> + + } /> + + + + + {t("platform.disclaimer.title")} + + {t("platform.disclaimer.description")} + + + + + + {t("platform.disclaimer.legalAdvice")} + + + ); +}; + +export default LiveAppDisclaimer; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDrawer.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDrawer.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDrawer.js rename to apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppDrawer.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppIcon.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppIcon.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppIcon.js rename to apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/LiveAppIcon.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.js deleted file mode 100644 index c83cf61e8836..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.js +++ /dev/null @@ -1,190 +0,0 @@ -// @flow - -import React, { useCallback } from "react"; -import { Trans } from "react-i18next"; -import styled from "styled-components"; - -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; - -import type { TopBarConfig } from "./type"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import { rgba } from "~/renderer/styles/helpers"; - -import Box, { Tabbable } from "~/renderer/components/Box"; - -import IconInfoCircle from "~/renderer/icons/InfoCircle"; -import IconReload from "~/renderer/icons/UpdateCircle"; -import LightBulb from "~/renderer/icons/LightBulb"; -import IconClose from "~/renderer/icons/Cross"; - -import LiveAppIcon from "./LiveAppIcon"; -import { useSelector, useDispatch } from "react-redux"; -import { enablePlatformDevToolsSelector } from "~/renderer/reducers/settings"; - -import { openPlatformAppInfoDrawer } from "~/renderer/actions/UI"; -const Container: ThemedComponent<{}> = styled(Box).attrs(() => ({ - horizontal: true, - grow: 0, - alignItems: "center", -}))` - padding: 10px 16px; - background-color: ${p => p.theme.colors.palette.background.paper}; - border-bottom: 1px solid ${p => p.theme.colors.palette.text.shade10}; -`; - -const TitleContainer: ThemedComponent<{}> = styled(Box).attrs(() => ({ - horizontal: true, - grow: 0, - alignItems: "center", - ff: "Inter|SemiBold", -}))` - margin-right: 16px; - color: ${p => - p.theme.colors.palette.type === "dark" ? p.theme.colors.white : p.theme.colors.black}; - - > * + * { - margin-left: 8px; - } -`; - -const RightContainer: ThemedComponent<{}> = styled(Box).attrs(() => ({ - horizontal: true, - grow: 0, - alignItems: "center", - ml: "auto", -}))``; - -const ItemContainer: ThemedComponent<{ - "data-e2e"?: string, - isInteractive?: boolean, - onClick?: () => void, - disabled?: boolean, - children: React$Node, - justifyContent?: string, -}> = styled(Tabbable).attrs(p => ({ - padding: 1, - alignItems: "center", - cursor: p.disabled ? "not-allowed" : "default", - horizontal: true, - borderRadius: 1, -}))` - -webkit-app-region: no-drag; - height: 24px; - position: relative; - cursor: pointer; - pointer-events: ${p => (p.disabled ? "none" : "unset")}; - - margin-right: 16px; - &:last-child { - margin-right: 0; - } - - > * + * { - margin-left: 8px; - } - - &:hover { - color: ${p => (p.disabled ? "" : p.theme.colors.palette.text.shade100)}; - background: ${p => (p.disabled ? "" : rgba(p.theme.colors.palette.action.active, 0.05))}; - } - - &:active { - background: ${p => (p.disabled ? "" : rgba(p.theme.colors.palette.action.active, 0.1))}; - } -`; - -const ItemContent: ThemedComponent<{}> = styled(Box).attrs(() => ({ - ff: "Inter|SemiBold", -}))` - font-size: 14px; - line-height: 20px; -`; - -export const Separator: ThemedComponent<*> = styled.div` - margin-right: 16px; - height: 15px; - width: 1px; - background: ${p => p.theme.colors.palette.divider}; -`; - -export type Props = { - icon?: boolean, - manifest: AppManifest, - onReload: Function, - onClose?: Function, - onHelp?: Function, - onOpenDevTools: Function, - config?: TopBarConfig, -}; - -const WebPlatformTopBar = ({ - manifest, - onReload, - onHelp, - onClose, - onOpenDevTools, - config = {}, -}: Props) => { - const { name, icon } = manifest; - - const { - shouldDisplayName = true, - shouldDisplayInfo = true, - shouldDisplayClose = !!onClose, - } = config; - - const enablePlatformDevTools = useSelector(enablePlatformDevToolsSelector); - const dispatch = useDispatch(); - - const onClick = useCallback(() => { - dispatch(openPlatformAppInfoDrawer({ manifest })); - }, [manifest, dispatch]); - - return ( - - {shouldDisplayName && ( - <> - - - {name} - - - - )} - - - - - - - {enablePlatformDevTools && ( - <> - - - - - - - - - )} - {(shouldDisplayInfo || shouldDisplayClose) && ( - - {shouldDisplayInfo && ( - - - - )} - - {shouldDisplayClose && ( - - - - )} - - )} - - ); -}; - -export default WebPlatformTopBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.jsx b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.jsx new file mode 100644 index 000000000000..690b736ee384 --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/TopBar.jsx @@ -0,0 +1,190 @@ +// @flow + +import React, { useCallback } from "react"; +import { Trans } from "react-i18next"; +import styled from "styled-components"; + +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; + +import type { TopBarConfig } from "./type"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import { rgba } from "~/renderer/styles/helpers"; + +import Box, { Tabbable } from "~/renderer/components/Box"; + +import IconInfoCircle from "~/renderer/icons/InfoCircle"; +import IconReload from "~/renderer/icons/UpdateCircle"; +import LightBulb from "~/renderer/icons/LightBulb"; +import IconClose from "~/renderer/icons/Cross"; + +import LiveAppIcon from "./LiveAppIcon"; +import { useSelector, useDispatch } from "react-redux"; +import { enablePlatformDevToolsSelector } from "~/renderer/reducers/settings"; + +import { openPlatformAppInfoDrawer } from "~/renderer/actions/UI"; +const Container: ThemedComponent<{}> = styled(Box).attrs(() => ({ + horizontal: true, + grow: 0, + alignItems: "center", +}))` + padding: 10px 16px; + background-color: ${p => p.theme.colors.palette.background.paper}; + border-bottom: 1px solid ${p => p.theme.colors.palette.text.shade10}; +`; + +const TitleContainer: ThemedComponent<{}> = styled(Box).attrs(() => ({ + horizontal: true, + grow: 0, + alignItems: "center", + ff: "Inter|SemiBold", +}))` + margin-right: 16px; + color: ${p => + p.theme.colors.palette.type === "dark" ? p.theme.colors.white : p.theme.colors.black}; + + > * + * { + margin-left: 8px; + } +`; + +const RightContainer: ThemedComponent<{}> = styled(Box).attrs(() => ({ + horizontal: true, + grow: 0, + alignItems: "center", + ml: "auto", +}))``; + +const ItemContainer: ThemedComponent<{ + "data-e2e"?: string, + isInteractive?: boolean, + onClick?: () => void, + disabled?: boolean, + children: React$Node, + justifyContent?: string, +}> = styled(Tabbable).attrs(p => ({ + padding: 1, + alignItems: "center", + cursor: p.disabled ? "not-allowed" : "default", + horizontal: true, + borderRadius: 1, +}))` + -webkit-app-region: no-drag; + height: 24px; + position: relative; + cursor: pointer; + pointer-events: ${p => (p.disabled ? "none" : "unset")}; + + margin-right: 16px; + &:last-child { + margin-right: 0; + } + + > * + * { + margin-left: 8px; + } + + &:hover { + color: ${p => (p.disabled ? "" : p.theme.colors.palette.text.shade100)}; + background: ${p => (p.disabled ? "" : rgba(p.theme.colors.palette.action.active, 0.05))}; + } + + &:active { + background: ${p => (p.disabled ? "" : rgba(p.theme.colors.palette.action.active, 0.1))}; + } +`; + +const ItemContent: ThemedComponent<{}> = styled(Box).attrs(() => ({ + ff: "Inter|SemiBold", +}))` + font-size: 14px; + line-height: 20px; +`; + +export const Separator: ThemedComponent<*> = styled.div` + margin-right: 16px; + height: 15px; + width: 1px; + background: ${p => p.theme.colors.palette.divider}; +`; + +export type Props = { + icon?: boolean, + manifest: AppManifest, + onReload: Function, + onClose?: Function, + onHelp?: Function, + onOpenDevTools: Function, + config?: TopBarConfig, +}; + +const WebPlatformTopBar = ({ + manifest, + onReload, + onHelp, + onClose, + onOpenDevTools, + config = {}, +}: Props) => { + const { name, icon } = manifest; + + const { + shouldDisplayName = true, + shouldDisplayInfo = true, + shouldDisplayClose = !!onClose, + } = config; + + const enablePlatformDevTools = useSelector(enablePlatformDevToolsSelector); + const dispatch = useDispatch(); + + const onClick = useCallback(() => { + dispatch(openPlatformAppInfoDrawer({ manifest })); + }, [manifest, dispatch]); + + return ( + + {shouldDisplayName && ( + <> + + + {name} + + + + )} + + + + + + + {enablePlatformDevTools && ( + <> + + + + + + + + + )} + {(shouldDisplayInfo || shouldDisplayClose) && ( + + {shouldDisplayInfo && ( + + + + )} + + {shouldDisplayClose && ( + + + + )} + + )} + + ); +}; + +export default WebPlatformTopBar; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.js deleted file mode 100644 index 3a6f40af6ecc..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.js +++ /dev/null @@ -1,531 +0,0 @@ -// @flow -import * as remote from "@electron/remote"; -import { addPendingOperation, getMainAccount } from "@ledgerhq/live-common/lib/account"; -import { getAccountBridge } from "@ledgerhq/live-common/lib/bridge"; -import { listSupportedCurrencies } from "@ledgerhq/live-common/lib/currencies"; -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import { useToasts } from "@ledgerhq/live-common/lib/notifications/ToastProvider"; -import { - accountToPlatformAccount, - currencyToPlatformCurrency, - getPlatformTransactionSignFlowInfos, -} from "@ledgerhq/live-common/lib/platform/converters"; -import { useJSONRPCServer } from "@ledgerhq/live-common/lib/platform/JSONRPCServer"; -import type { - RawPlatformSignedTransaction, - RawPlatformTransaction, -} from "@ledgerhq/live-common/lib/platform/rawTypes"; -import { - deserializePlatformSignedTransaction, - deserializePlatformTransaction, - serializePlatformAccount, - serializePlatformSignedTransaction, -} from "@ledgerhq/live-common/lib/platform/serializers"; -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; -import { WebviewTag } from "electron"; -import { JSONRPCRequest } from "json-rpc-2.0"; -import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useDispatch, useSelector } from "react-redux"; -import styled from "styled-components"; -import { updateAccountWithUpdater } from "~/renderer/actions/accounts"; -import { openModal } from "~/renderer/actions/modals"; -import TrackPage from "~/renderer/analytics/TrackPage"; -import BigSpinner from "~/renderer/components/BigSpinner"; -import Box from "~/renderer/components/Box"; -import useTheme from "~/renderer/hooks/useTheme"; -import { accountsSelector } from "~/renderer/reducers/accounts"; -import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; -import TopBar from "./TopBar"; -import * as tracking from "./tracking"; -import type { TopBarConfig } from "./type"; -import { handleMessageEvent, handleNewWindowEvent } from "./utils"; - -const Container: ThemedComponent<{}> = styled.div` - display: flex; - flex-direction: column; - flex: 1; - height: 100%; -`; - -// $FlowFixMe -const CustomWebview: ThemedComponent<{}> = styled("webview")` - border: none; - width: 100%; - flex: 1; - transition: opacity 200ms ease-out; -`; - -const Wrapper: ThemedComponent<{}> = styled(Box).attrs(() => ({ - flex: 1, -}))` - position: relative; -`; - -const Loader: ThemedComponent<{}> = styled.div` - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -`; - -type WebPlatformPlayerConfig = { - topBarConfig?: TopBarConfig, -}; - -type Props = { - manifest: AppManifest, - onClose?: Function, - inputs?: Object, - config?: WebPlatformPlayerConfig, -}; - -const WebPlatformPlayer = ({ manifest, onClose, inputs, config }: Props) => { - const theme = useTheme("colors.palette"); - - const targetRef: { current: null | WebviewTag } = useRef(null); - const dispatch = useDispatch(); - const accounts = useSelector(accountsSelector); - const currencies = useMemo(() => listSupportedCurrencies(), []); - const { pushToast } = useToasts(); - const { t } = useTranslation(); - - const [widgetLoaded, setWidgetLoaded] = useState(false); - - const url = useMemo(() => { - const urlObj = new URL(manifest.url.toString()); - - if (inputs) { - for (const key in inputs) { - if (Object.prototype.hasOwnProperty.call(inputs, key)) { - urlObj.searchParams.set(key, inputs[key]); - } - } - } - - urlObj.searchParams.set("backgroundColor", theme.background.paper); - urlObj.searchParams.set("textColor", theme.text.shade100); - if (manifest.params) { - urlObj.searchParams.set("params", JSON.stringify(manifest.params)); - } - - return urlObj; - }, [manifest.url, theme, inputs, manifest.params]); - - const listAccounts = useCallback(() => { - return accounts.map(account => serializePlatformAccount(accountToPlatformAccount(account))); - }, [accounts]); - - const listCurrencies = useCallback(() => { - return currencies.map(currencyToPlatformCurrency); - }, [currencies]); - - const receiveOnAccount = useCallback( - ({ accountId }: { accountId: string }) => { - const account = accounts.find(account => account.id === accountId); - tracking.platformReceiveRequested(manifest); - - // FIXME: handle address rejection (if user reject address, we don't end up in onResult nor in onCancel 🤔) - return new Promise((resolve, reject) => - dispatch( - openModal("MODAL_EXCHANGE_CRYPTO_DEVICE", { - account, - parentAccount: null, - onResult: account => { - tracking.platformReceiveSuccess(manifest); - resolve(account.freshAddress); - }, - onCancel: error => { - tracking.platformReceiveFail(manifest); - reject(error); - }, - verifyAddress: true, - }), - ), - ); - }, - [manifest, accounts, dispatch], - ); - - const broadcastTransaction = useCallback( - async ({ - accountId, - signedTransaction, - }: { - accountId: string, - signedTransaction: RawPlatformSignedTransaction, - }) => { - const account = accounts.find(account => account.id === accountId); - if (!account) return null; - - const signedOperation = deserializePlatformSignedTransaction(signedTransaction, accountId); - const bridge = getAccountBridge(account); - - let optimisticOperation = signedOperation.operation; - - // FIXME: couldn't we use `useBroadcast` here? - if (!getEnv("DISABLE_TRANSACTION_BROADCAST")) { - try { - optimisticOperation = await bridge.broadcast({ - account, - signedOperation, - }); - tracking.platformBroadcastSuccess(manifest); - } catch (error) { - tracking.platformBroadcastFail(manifest); - throw error; - } - } - - dispatch( - updateAccountWithUpdater(account.id, account => - addPendingOperation(account, optimisticOperation), - ), - ); - - pushToast({ - id: optimisticOperation.id, - type: "operation", - title: t("platform.flows.broadcast.toast.title"), - text: t("platform.flows.broadcast.toast.text"), - icon: "info", - callback: () => { - tracking.platformBroadcastOperationDetailsClick(manifest); - dispatch( - openModal("MODAL_OPERATION_DETAILS", { - operationId: optimisticOperation.id, - accountId: account.id, - parentId: null, - }), - ); - }, - }); - - return optimisticOperation.hash; - }, - [manifest, accounts, pushToast, dispatch, t], - ); - - const requestAccount = useCallback( - ({ currencies, allowAddAccount }: { currencies?: string[], allowAddAccount?: boolean }) => { - tracking.platformRequestAccountRequested(manifest); - return new Promise((resolve, reject) => - dispatch( - openModal("MODAL_REQUEST_ACCOUNT", { - currencies, - allowAddAccount, - onResult: account => { - tracking.platformRequestAccountSuccess(manifest); - /** - * If account does not exist, it means one (or multiple) account(s) have been created - * In this case, to notify the user of the API that an account has been created, - * and that he should refetch the accounts list, we return an empty object - * (that will be deserialized as an empty Account object in the SDK) - * - * FIXME: this overall handling of created accounts could be improved and might not handle "onCancel" - */ - // - resolve(account ? serializePlatformAccount(accountToPlatformAccount(account)) : {}); - }, - onCancel: error => { - tracking.platformRequestAccountFail(manifest); - reject(error); - }, - }), - ), - ); - }, - [manifest, dispatch], - ); - - const signTransaction = useCallback( - ({ - accountId, - transaction, - params = {}, - }: { - accountId: string, - transaction: RawPlatformTransaction, - params: any, - }) => { - const platformTransaction = deserializePlatformTransaction(transaction); - - const account = accounts.find(account => account.id === accountId); - - if (!account) return null; - - if (account.currency.family !== platformTransaction.family) { - throw new Error("Transaction family not matching account currency family"); - } - - tracking.platformSignTransactionRequested(manifest); - - const { canEditFees, liveTx, hasFeesProvided } = getPlatformTransactionSignFlowInfos( - platformTransaction, - ); - - return new Promise((resolve, reject) => - dispatch( - openModal("MODAL_SIGN_TRANSACTION", { - canEditFees, - stepId: canEditFees && !hasFeesProvided ? "amount" : "summary", - transactionData: liveTx, - useApp: params.useApp, - account, - parentAccount: null, - onResult: signedOperation => { - tracking.platformSignTransactionRequested(manifest); - resolve(serializePlatformSignedTransaction(signedOperation)); - }, - onCancel: error => { - tracking.platformSignTransactionFail(manifest); - reject(error); - }, - }), - ), - ); - }, - [manifest, dispatch, accounts], - ); - - const startExchange = useCallback( - ({ exchangeType }: { exchangeType: number }) => { - tracking.platformStartExchangeRequested(manifest); - return new Promise((resolve, reject) => - dispatch( - openModal("MODAL_PLATFORM_EXCHANGE_START", { - exchangeType, - onResult: nonce => { - tracking.platformStartExchangeSuccess(manifest); - resolve(nonce); - }, - onCancel: error => { - tracking.platformStartExchangeFail(manifest); - reject(error); - }, - }), - ), - ); - }, - [manifest, dispatch], - ); - - const completeExchange = useCallback( - ({ - provider, - fromAccountId, - toAccountId, - transaction, - binaryPayload, - signature, - feesStrategy, - exchangeType, - }: { - provider: string, - fromAccountId: string, - toAccountId: string, - transaction: RawPlatformTransaction, - binaryPayload: string, - signature: string, - feesStrategy: string, - exchangeType: number, - }) => { - // Nb get a hold of the actual accounts, and parent accounts - const fromAccount = accounts.find(a => a.id === fromAccountId); - let fromParentAccount; - - const toAccount = accounts.find(a => a.id === toAccountId); - let toParentAccount; - - if (!fromAccount) { - return null; - } - - if (exchangeType === 0x00 && !toAccount) { - // if we do a swap, a destination account must be provided - return null; - } - - if (fromAccount.type === "TokenAccount") { - fromParentAccount = accounts.find(a => a.id === fromAccount.parentId); - } - if (toAccount && toAccount.type === "TokenAccount") { - toParentAccount = accounts.find(a => a.id === toAccount.parentId); - } - - const accountBridge = getAccountBridge(fromAccount, fromParentAccount); - const mainFromAccount = getMainAccount(fromAccount, fromParentAccount); - - transaction.family = mainFromAccount.currency.family; - - const platformTransaction = deserializePlatformTransaction(transaction); - - platformTransaction.feesStrategy = feesStrategy; - - let processedTransaction = accountBridge.createTransaction(mainFromAccount); - processedTransaction = accountBridge.updateTransaction( - processedTransaction, - platformTransaction, - ); - - tracking.platformCompleteExchangeRequested(manifest); - return new Promise((resolve, reject) => - dispatch( - openModal("MODAL_PLATFORM_EXCHANGE_COMPLETE", { - provider, - exchange: { - fromAccount, - fromParentAccount, - toAccount, - toParentAccount, - }, - transaction: processedTransaction, - binaryPayload, - signature, - feesStrategy, - exchangeType, - - onResult: operation => { - tracking.platformCompleteExchangeSuccess(manifest); - resolve(operation); - }, - onCancel: error => { - tracking.platformCompleteExchangeFail(manifest); - reject(error); - }, - }), - ), - ); - }, - [accounts, dispatch, manifest], - ); - - const handlers = useMemo( - () => ({ - "account.list": listAccounts, - "currency.list": listCurrencies, - "account.request": requestAccount, - "account.receive": receiveOnAccount, - "transaction.sign": signTransaction, - "transaction.broadcast": broadcastTransaction, - "exchange.start": startExchange, - "exchange.complete": completeExchange, - }), - [ - listAccounts, - listCurrencies, - requestAccount, - receiveOnAccount, - signTransaction, - broadcastTransaction, - startExchange, - completeExchange, - ], - ); - - const handleSend = useCallback( - (request: JSONRPCRequest) => { - const webview = targetRef.current; - if (webview) { - webview.contentWindow.postMessage(JSON.stringify(request), url.origin); - } - }, - [url], - ); - - const [receive] = useJSONRPCServer(handlers, handleSend); - - const handleMessage = useCallback(event => handleMessageEvent({ event, handler: receive }), [ - receive, - ]); - - useEffect(() => { - tracking.platformLoad(manifest); - const webview = targetRef.current; - if (webview) { - webview.addEventListener("ipc-message", handleMessage); - } - - return () => { - if (webview) { - webview.removeEventListener("ipc-message", handleMessage); - } - }; - }, [manifest, handleMessage]); - - const handleLoad = useCallback(() => { - tracking.platformLoadSuccess(manifest); - setWidgetLoaded(true); - }, [manifest]); - - const handleReload = useCallback(() => { - const webview = targetRef.current; - if (webview) { - tracking.platformReload(manifest); - setWidgetLoaded(false); - webview.reloadIgnoringCache(); - } - }, [manifest]); - - const handleNewWindow = useCallback(handleNewWindowEvent, []); - - useEffect(() => { - const webview = targetRef.current; - - if (webview) { - // For mysterious reasons, the webpreferences attribute does not - // pass through the styled component when added in the JSX. - webview.webpreferences = "nativeWindowOpen=no"; - webview.addEventListener("new-window", handleNewWindow); - webview.addEventListener("did-finish-load", handleLoad); - } - - return () => { - if (webview) { - webview.removeEventListener("new-window", handleNewWindow); - webview.removeEventListener("did-finish-load", handleLoad); - } - }; - }, [handleLoad, handleNewWindow]); - - const handleOpenDevTools = useCallback(() => { - const webview = targetRef.current; - - if (webview) { - webview.openDevTools(); - } - }, []); - - return ( - - - - - - - {!widgetLoaded ? ( - - - - ) : null} - - - ); -}; - -export default WebPlatformPlayer; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.jsx b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.jsx new file mode 100644 index 000000000000..3ded534a769f --- /dev/null +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/index.jsx @@ -0,0 +1,532 @@ +// @flow +import * as remote from "@electron/remote"; +import { addPendingOperation, getMainAccount } from "@ledgerhq/live-common/account/index"; +import { getAccountBridge } from "@ledgerhq/live-common/bridge/index"; +import { listSupportedCurrencies } from "@ledgerhq/live-common/currencies/index"; +import { getEnv } from "@ledgerhq/live-common/env"; +import { useToasts } from "@ledgerhq/live-common/notifications/ToastProvider/index"; + +import { + accountToPlatformAccount, + currencyToPlatformCurrency, + getPlatformTransactionSignFlowInfos, +} from "@ledgerhq/live-common/platform/converters"; +import { useJSONRPCServer } from "@ledgerhq/live-common/platform/JSONRPCServer"; +import type { + RawPlatformSignedTransaction, + RawPlatformTransaction, +} from "@ledgerhq/live-common/platform/rawTypes"; +import { + deserializePlatformSignedTransaction, + deserializePlatformTransaction, + serializePlatformAccount, + serializePlatformSignedTransaction, +} from "@ledgerhq/live-common/platform/serializers" +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; +import { WebviewTag } from "electron"; +import { JSONRPCRequest } from "json-rpc-2.0"; +import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import styled from "styled-components"; +import { updateAccountWithUpdater } from "~/renderer/actions/accounts"; +import { openModal } from "~/renderer/actions/modals"; +import TrackPage from "~/renderer/analytics/TrackPage"; +import BigSpinner from "~/renderer/components/BigSpinner"; +import Box from "~/renderer/components/Box"; +import useTheme from "~/renderer/hooks/useTheme"; +import { accountsSelector } from "~/renderer/reducers/accounts"; +import type { ThemedComponent } from "~/renderer/styles/StyleProvider"; +import TopBar from "./TopBar"; +import * as tracking from "./tracking"; +import type { TopBarConfig } from "./type"; +import { handleMessageEvent, handleNewWindowEvent } from "./utils"; + +const Container: ThemedComponent<{}> = styled.div` + display: flex; + flex-direction: column; + flex: 1; + height: 100%; +`; + +// $FlowFixMe +const CustomWebview: ThemedComponent<{}> = styled("webview")` + border: none; + width: 100%; + flex: 1; + transition: opacity 200ms ease-out; +`; + +const Wrapper: ThemedComponent<{}> = styled(Box).attrs(() => ({ + flex: 1, +}))` + position: relative; +`; + +const Loader: ThemedComponent<{}> = styled.div` + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +`; + +type WebPlatformPlayerConfig = { + topBarConfig?: TopBarConfig, +}; + +type Props = { + manifest: AppManifest, + onClose?: Function, + inputs?: Object, + config?: WebPlatformPlayerConfig, +}; + +const WebPlatformPlayer = ({ manifest, onClose, inputs, config }: Props) => { + const theme = useTheme("colors.palette"); + + const targetRef: { current: null | WebviewTag } = useRef(null); + const dispatch = useDispatch(); + const accounts = useSelector(accountsSelector); + const currencies = useMemo(() => listSupportedCurrencies(), []); + const { pushToast } = useToasts(); + const { t } = useTranslation(); + + const [widgetLoaded, setWidgetLoaded] = useState(false); + + const url = useMemo(() => { + const urlObj = new URL(manifest.url.toString()); + + if (inputs) { + for (const key in inputs) { + if (Object.prototype.hasOwnProperty.call(inputs, key)) { + urlObj.searchParams.set(key, inputs[key]); + } + } + } + + urlObj.searchParams.set("backgroundColor", theme.background.paper); + urlObj.searchParams.set("textColor", theme.text.shade100); + if (manifest.params) { + urlObj.searchParams.set("params", JSON.stringify(manifest.params)); + } + + return urlObj; + }, [manifest.url, theme, inputs, manifest.params]); + + const listAccounts = useCallback(() => { + return accounts.map(account => serializePlatformAccount(accountToPlatformAccount(account))); + }, [accounts]); + + const listCurrencies = useCallback(() => { + return currencies.map(currencyToPlatformCurrency); + }, [currencies]); + + const receiveOnAccount = useCallback( + ({ accountId }: { accountId: string }) => { + const account = accounts.find(account => account.id === accountId); + tracking.platformReceiveRequested(manifest); + + // FIXME: handle address rejection (if user reject address, we don't end up in onResult nor in onCancel 🤔) + return new Promise((resolve, reject) => + dispatch( + openModal("MODAL_EXCHANGE_CRYPTO_DEVICE", { + account, + parentAccount: null, + onResult: account => { + tracking.platformReceiveSuccess(manifest); + resolve(account.freshAddress); + }, + onCancel: error => { + tracking.platformReceiveFail(manifest); + reject(error); + }, + verifyAddress: true, + }), + ), + ); + }, + [manifest, accounts, dispatch], + ); + + const broadcastTransaction = useCallback( + async ({ + accountId, + signedTransaction, + }: { + accountId: string, + signedTransaction: RawPlatformSignedTransaction, + }) => { + const account = accounts.find(account => account.id === accountId); + if (!account) return null; + + const signedOperation = deserializePlatformSignedTransaction(signedTransaction, accountId); + const bridge = getAccountBridge(account); + + let optimisticOperation = signedOperation.operation; + + // FIXME: couldn't we use `useBroadcast` here? + if (!getEnv("DISABLE_TRANSACTION_BROADCAST")) { + try { + optimisticOperation = await bridge.broadcast({ + account, + signedOperation, + }); + tracking.platformBroadcastSuccess(manifest); + } catch (error) { + tracking.platformBroadcastFail(manifest); + throw error; + } + } + + dispatch( + updateAccountWithUpdater(account.id, account => + addPendingOperation(account, optimisticOperation), + ), + ); + + pushToast({ + id: optimisticOperation.id, + type: "operation", + title: t("platform.flows.broadcast.toast.title"), + text: t("platform.flows.broadcast.toast.text"), + icon: "info", + callback: () => { + tracking.platformBroadcastOperationDetailsClick(manifest); + dispatch( + openModal("MODAL_OPERATION_DETAILS", { + operationId: optimisticOperation.id, + accountId: account.id, + parentId: null, + }), + ); + }, + }); + + return optimisticOperation.hash; + }, + [manifest, accounts, pushToast, dispatch, t], + ); + + const requestAccount = useCallback( + ({ currencies, allowAddAccount }: { currencies?: string[], allowAddAccount?: boolean }) => { + tracking.platformRequestAccountRequested(manifest); + return new Promise((resolve, reject) => + dispatch( + openModal("MODAL_REQUEST_ACCOUNT", { + currencies, + allowAddAccount, + onResult: account => { + tracking.platformRequestAccountSuccess(manifest); + /** + * If account does not exist, it means one (or multiple) account(s) have been created + * In this case, to notify the user of the API that an account has been created, + * and that he should refetch the accounts list, we return an empty object + * (that will be deserialized as an empty Account object in the SDK) + * + * FIXME: this overall handling of created accounts could be improved and might not handle "onCancel" + */ + // + resolve(account ? serializePlatformAccount(accountToPlatformAccount(account)) : {}); + }, + onCancel: error => { + tracking.platformRequestAccountFail(manifest); + reject(error); + }, + }), + ), + ); + }, + [manifest, dispatch], + ); + + const signTransaction = useCallback( + ({ + accountId, + transaction, + params = {}, + }: { + accountId: string, + transaction: RawPlatformTransaction, + params: any, + }) => { + const platformTransaction = deserializePlatformTransaction(transaction); + + const account = accounts.find(account => account.id === accountId); + + if (!account) return null; + + if (account.currency.family !== platformTransaction.family) { + throw new Error("Transaction family not matching account currency family"); + } + + tracking.platformSignTransactionRequested(manifest); + + const { canEditFees, liveTx, hasFeesProvided } = getPlatformTransactionSignFlowInfos( + platformTransaction, + ); + + return new Promise((resolve, reject) => + dispatch( + openModal("MODAL_SIGN_TRANSACTION", { + canEditFees, + stepId: canEditFees && !hasFeesProvided ? "amount" : "summary", + transactionData: liveTx, + useApp: params.useApp, + account, + parentAccount: null, + onResult: signedOperation => { + tracking.platformSignTransactionRequested(manifest); + resolve(serializePlatformSignedTransaction(signedOperation)); + }, + onCancel: error => { + tracking.platformSignTransactionFail(manifest); + reject(error); + }, + }), + ), + ); + }, + [manifest, dispatch, accounts], + ); + + const startExchange = useCallback( + ({ exchangeType }: { exchangeType: number }) => { + tracking.platformStartExchangeRequested(manifest); + return new Promise((resolve, reject) => + dispatch( + openModal("MODAL_PLATFORM_EXCHANGE_START", { + exchangeType, + onResult: nonce => { + tracking.platformStartExchangeSuccess(manifest); + resolve(nonce); + }, + onCancel: error => { + tracking.platformStartExchangeFail(manifest); + reject(error); + }, + }), + ), + ); + }, + [manifest, dispatch], + ); + + const completeExchange = useCallback( + ({ + provider, + fromAccountId, + toAccountId, + transaction, + binaryPayload, + signature, + feesStrategy, + exchangeType, + }: { + provider: string, + fromAccountId: string, + toAccountId: string, + transaction: RawPlatformTransaction, + binaryPayload: string, + signature: string, + feesStrategy: string, + exchangeType: number, + }) => { + // Nb get a hold of the actual accounts, and parent accounts + const fromAccount = accounts.find(a => a.id === fromAccountId); + let fromParentAccount; + + const toAccount = accounts.find(a => a.id === toAccountId); + let toParentAccount; + + if (!fromAccount) { + return null; + } + + if (exchangeType === 0x00 && !toAccount) { + // if we do a swap, a destination account must be provided + return null; + } + + if (fromAccount.type === "TokenAccount") { + fromParentAccount = accounts.find(a => a.id === fromAccount.parentId); + } + if (toAccount && toAccount.type === "TokenAccount") { + toParentAccount = accounts.find(a => a.id === toAccount.parentId); + } + + const accountBridge = getAccountBridge(fromAccount, fromParentAccount); + const mainFromAccount = getMainAccount(fromAccount, fromParentAccount); + + transaction.family = mainFromAccount.currency.family; + + const platformTransaction = deserializePlatformTransaction(transaction); + + platformTransaction.feesStrategy = feesStrategy; + + let processedTransaction = accountBridge.createTransaction(mainFromAccount); + processedTransaction = accountBridge.updateTransaction( + processedTransaction, + platformTransaction, + ); + + tracking.platformCompleteExchangeRequested(manifest); + return new Promise((resolve, reject) => + dispatch( + openModal("MODAL_PLATFORM_EXCHANGE_COMPLETE", { + provider, + exchange: { + fromAccount, + fromParentAccount, + toAccount, + toParentAccount, + }, + transaction: processedTransaction, + binaryPayload, + signature, + feesStrategy, + exchangeType, + + onResult: operation => { + tracking.platformCompleteExchangeSuccess(manifest); + resolve(operation); + }, + onCancel: error => { + tracking.platformCompleteExchangeFail(manifest); + reject(error); + }, + }), + ), + ); + }, + [accounts, dispatch, manifest], + ); + + const handlers = useMemo( + () => ({ + "account.list": listAccounts, + "currency.list": listCurrencies, + "account.request": requestAccount, + "account.receive": receiveOnAccount, + "transaction.sign": signTransaction, + "transaction.broadcast": broadcastTransaction, + "exchange.start": startExchange, + "exchange.complete": completeExchange, + }), + [ + listAccounts, + listCurrencies, + requestAccount, + receiveOnAccount, + signTransaction, + broadcastTransaction, + startExchange, + completeExchange, + ], + ); + + const handleSend = useCallback( + (request: JSONRPCRequest) => { + const webview = targetRef.current; + if (webview) { + webview.contentWindow.postMessage(JSON.stringify(request), url.origin); + } + }, + [url], + ); + + const [receive] = useJSONRPCServer(handlers, handleSend); + + const handleMessage = useCallback(event => handleMessageEvent({ event, handler: receive }), [ + receive, + ]); + + useEffect(() => { + tracking.platformLoad(manifest); + const webview = targetRef.current; + if (webview) { + webview.addEventListener("ipc-message", handleMessage); + } + + return () => { + if (webview) { + webview.removeEventListener("ipc-message", handleMessage); + } + }; + }, [manifest, handleMessage]); + + const handleLoad = useCallback(() => { + tracking.platformLoadSuccess(manifest); + setWidgetLoaded(true); + }, [manifest]); + + const handleReload = useCallback(() => { + const webview = targetRef.current; + if (webview) { + tracking.platformReload(manifest); + setWidgetLoaded(false); + webview.reloadIgnoringCache(); + } + }, [manifest]); + + const handleNewWindow = useCallback(handleNewWindowEvent, []); + + useEffect(() => { + const webview = targetRef.current; + + if (webview) { + // For mysterious reasons, the webpreferences attribute does not + // pass through the styled component when added in the JSX. + webview.webpreferences = "nativeWindowOpen=no"; + webview.addEventListener("new-window", handleNewWindow); + webview.addEventListener("did-finish-load", handleLoad); + } + + return () => { + if (webview) { + webview.removeEventListener("new-window", handleNewWindow); + webview.removeEventListener("did-finish-load", handleLoad); + } + }; + }, [handleLoad, handleNewWindow]); + + const handleOpenDevTools = useCallback(() => { + const webview = targetRef.current; + + if (webview) { + webview.openDevTools(); + } + }, []); + + return ( + + + + + + + {!widgetLoaded ? ( + + + + ) : null} + + + ); +}; + +export default WebPlatformPlayer; diff --git a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/tracking.js b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/tracking.js index b360a3eb77bf..667183134c77 100644 --- a/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/tracking.js +++ b/apps/ledger-live-desktop/src/renderer/components/WebPlatformPlayer/tracking.js @@ -1,6 +1,6 @@ // @flow import { track } from "~/renderer/analytics/segment"; -import type { AppManifest } from "@ledgerhq/live-common/lib/platform/types"; +import type { AppManifest } from "@ledgerhq/live-common/platform/types"; /** * Obtain Event data from Platform App manifest diff --git a/apps/ledger-live-desktop/src/renderer/components/debug/DebugFirmwareUpdater.js b/apps/ledger-live-desktop/src/renderer/components/debug/DebugFirmwareUpdater.jsx similarity index 100% rename from apps/ledger-live-desktop/src/renderer/components/debug/DebugFirmwareUpdater.js rename to apps/ledger-live-desktop/src/renderer/components/debug/DebugFirmwareUpdater.jsx diff --git a/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.js b/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.js deleted file mode 100644 index ce75ed724da5..000000000000 --- a/apps/ledger-live-desktop/src/renderer/components/debug/DebugMock.js +++ /dev/null @@ -1,587 +0,0 @@ -// @flow -import React, { useCallback, useState } from "react"; - -import { getEnv } from "@ledgerhq/live-common/lib/env"; -import Text from "~/renderer/components/Text"; -import { ReplaySubject } from "rxjs"; -import { deserializeError } from "@ledgerhq/errors"; -import { fromTransactionRaw } from "@ledgerhq/live-common/lib/transaction"; -import { - deviceInfo155, - mockListAppsResult as innerMockListAppResult, -} from "@ledgerhq/live-common/lib/apps/mock"; - -import { useAnnouncements } from "@ledgerhq/live-common/lib/notifications/AnnouncementProvider"; -import { useFilteredServiceStatus } from "@ledgerhq/live-common/lib/notifications/ServiceStatusProvider"; - -import { addMockAnnouncement } from "../../../../tests/mocks/notificationsHelpers"; -import { toggleMockIncident } from "../../../../tests/mocks/serviceStatusHelpers"; - -import useInterval from "~/renderer/hooks/useInterval"; -import Box from "~/renderer/components/Box"; -import { Item, MockContainer, EllipsesText, MockedGlobalStyle } from "./shared"; - -const mockListAppsResult = (...params) => { - // Nb Should move this polyfill to live-common eventually. - const result = innerMockListAppResult(...params); - Object.keys(result?.appByName).forEach(key => { - result.appByName[key] = { ...result.appByName[key], type: "app" }; - }); - return result; -}; -/** - * List of events that will be displayed in the quick-link section of the mock menu - * to ease the usability when mock is done manually instead of through playwright. - */ -const helpfulEvents = [ - { name: "opened", event: { type: "opened" } }, - { name: "deviceChange", event: { type: "deviceChange", device: null } }, - { - name: "listApps", - event: { - type: "listingApps", - deviceInfo: deviceInfo155, - }, - }, - { - name: "result", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar", - "Bitcoin,Tron,Litecoin,Ethereum", - deviceInfo155, - ), - }, - }, - { - name: "resultOutdated", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar", - "Bitcoin,Tron,Litecoin,Ethereum (outdated)", - deviceInfo155, - ), - }, - }, - { - name: "permission requested", - event: { type: "device-permission-requested", wording: "Allow Ledger Manager" }, - }, - { - name: "permission granted", - event: { type: "device-permission-granted" }, - }, - { name: "quitApp", event: { type: "appDetected" } }, - { name: "unresponsiveDevice", event: { type: "unresponsiveDevice" } }, - { name: "complete", event: { type: "complete" } }, -]; - -const swapEvents = [ - { - name: "result without Exchange", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar", - "Bitcoin,Tron,Litecoin,Ethereum", - deviceInfo155, - ), - }, - }, - { - name: "result with outdated Exchange", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar,Exchange", - "Exchange(outdated),Tron,Bitcoin,Ethereum", - deviceInfo155, - ), - }, - }, - { - name: "result with Exchange", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar,Exchange", - "Exchange,Tron,Bitcoin,Ethereum", - deviceInfo155, - ), - }, - }, - { - name: "result with only Exchange", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar,Exchange", - "Exchange", - deviceInfo155, - ), - }, - }, - { - name: "result with only Exchange+BTC", - event: { - type: "result", - result: mockListAppsResult( - "Bitcoin,Tron,Litecoin,Ethereum,Ripple,Stellar,Exchange", - "Exchange,Bitcoin", - deviceInfo155, - ), - }, - }, - { - name: "init-swap-requested", - event: { - type: "init-swap-requested", - }, - }, - { - name: "init-swap-error", - event: { - type: "init-swap-error", - error: { name: "SwapGenericAPIError" }, - }, - }, - { - name: "init-swap-result", - event: { - type: "init-swap-result", - initSwapResult: { - transaction: fromTransactionRaw({ - family: "bitcoin", - recipient: "1Cz2ZXb6Y6AacXJTpo4RBjQMLEmscuxD8e", - amount: "1", - feePerByte: "1", - networkInfo: { - family: "bitcoin", - feeItems: { - items: [ - { key: "0", speed: "high", feePerByte: "3" }, - { key: "1", speed: "standard", feePerByte: "2" }, - { key: "2", speed: "low", feePerByte: "1" }, - ], - defaultFeePerByte: "1", - }, - }, - rbf: false, - utxoStrategy: { - strategy: 0, - excludeUTXOs: [], - }, - }), - swapId: "12345", - }, - }, - }, -]; - -if (getEnv("MOCK")) { - window.mock = { - events: { - test: 0, - queue: [], - history: [], - subject: new ReplaySubject(), - get parseRawEvents() { - return (rawEvents, maybeKey): Object => { - if (rawEvents && typeof rawEvents === "object") { - if (maybeKey === "error") { - return deserializeError(rawEvents); - } - if (Array.isArray(rawEvents)) return rawEvents.map(this.parseRawEvents); - const event = {}; - for (const k in rawEvents) { - if (rawEvents.hasOwnProperty(k)) { - event[k] = this.parseRawEvents(rawEvents[k], k); - } - } - return event; - } - return rawEvents; - }; - }, - get emitter() { - return () => { - // Cleanup the queue of complete events - while (this.queue.length && this.queue[0].type === "complete") { - this.queue.shift(); - } - - if (this.subject.isStopped) { - this.subject = new ReplaySubject(); - } - - return this.subject; - }; - }, - get mockDeviceEvent() { - return (...o: any[]) => { - for (const e of this.parseRawEvents(o)) this.queue.push(e); - }; - }, - exposed: { - mockListAppsResult, - deviceInfo155, - }, - }, - }; - - const observerAwareEventLoop = () => { - const { subject, queue, history } = window.mock.events; - while (subject.observers.length && !subject.isStopped && queue.length) { - const event = queue.shift(); - if (event.type === "complete") { - subject.complete(); - window.mock.events.subject = new ReplaySubject(); - } else { - subject.next(event); - } - history.push(event); - } - - // If no observers consume "complete" events that are on top of the list - while (!subject.observers.length && queue.length && queue[0].type === "complete") { - const event = queue.shift(); - subject.complete(); - history.push(event); - window.mock.events.subject = new ReplaySubject(); - } - setTimeout(observerAwareEventLoop, 400); - }; - - observerAwareEventLoop(); -} - -// $FlowFixMe -export const mockedEventEmitter = getEnv("MOCK") ? window.mock.events.emitter : null; - -const DebugMock = () => { - const [queue, setQueue] = useState(window.mock.events.queue); - const [history, setHistory] = useState(window.mock.events.history); - const [nonce, setNonce] = useState(0); - const [expanded, setExpanded] = useState(true); - const [expandedQueue, setExpandedQueue] = useState(true); - const [expandedSwap, setExpandedSwap] = useState(false); - const [expandedQuick, setExpandedQuick] = useState(false); - const [expandedHistory, setExpandedHistory] = useState(true); - const [expandedNotif, setExpandedNotif] = useState(false); - - const [notifPlatform, setNotifPlatform] = useState(""); - const [notifAppVersions, setNotifAppVersions] = useState(""); - const [notifLiveCommonVersions, setNotifLiveCommonVersions] = useState(""); - const [notifCurrencies, setNotifCurrencies] = useState(""); - const [notifDeviceVersion, setNotifDeviceVersion] = useState(""); - const [notifDeviceModelId, setNotifDeviceModelId] = useState(""); - const [notifDeviceApps, setNotifDeviceApps] = useState(""); - const [notifLanguages, setNotifLanguages] = useState(""); - const [notifExtra, setNotifExtra] = useState(""); - - const { updateCache } = useAnnouncements(); - const { updateData } = useFilteredServiceStatus(); - - useInterval(() => { - setQueue(window.mock.events.queue); - setHistory(window.mock.events.history); - setNonce(nonce + 1); - }, 2000); - - const toggleExpanded = useCallback(() => setExpanded(!expanded), [expanded, setExpanded]); - const toggleExpandedQueue = useCallback(() => setExpandedQueue(!expandedQueue), [expandedQueue]); - const toggleExpandedQuick = useCallback(() => setExpandedQuick(!expandedQuick), [expandedQuick]); - const toggleExpandedHistory = useCallback(() => setExpandedHistory(!expandedHistory), [ - expandedHistory, - ]); - const toggleExpandedSwap = useCallback(() => setExpandedSwap(!expandedSwap), [expandedSwap]); - const toggleExpandedNotif = useCallback(() => setExpandedNotif(!expandedNotif), [expandedNotif]); - - const queueEvent = useCallback( - event => { - setQueue([...queue, event]); - window.mock.events.mockDeviceEvent(event); - }, - [queue, setQueue], - ); - - const unQueueEventByIndex = useCallback( - i => { - window.mock.events.queue.splice(i, 1); - setQueue(window.mock.events.queue); - }, - [setQueue], - ); - - const formatInputValue = useCallback((inputValue: string): ?(string[]) => { - const val: string[] = inputValue - .replace(/\s/g, "") - .split(",") - .filter(Boolean); - return val.length > 0 ? val : undefined; - }, []); - - const onNotifClick = useCallback(() => { - const params = { - currencies: formatInputValue(notifCurrencies), - platforms: formatInputValue(notifPlatform), - appVersions: formatInputValue(notifAppVersions), - liveCommonVersions: formatInputValue(notifLiveCommonVersions), - languages: formatInputValue(notifLanguages), - }; - - const formattedParams: any = Object.keys(params) - .filter(k => !!params[k] && params[k].length > 0) - .reduce((sum, k: string) => ({ ...sum, [k]: params[k] }), {}); - - let extra = {}; - - try { - extra = JSON.parse(notifExtra) || {}; - } catch (e) { - console.error(e); - } - - addMockAnnouncement({ - ...formattedParams, - device: { - modelIds: formatInputValue(notifDeviceModelId), - versions: formatInputValue(notifDeviceVersion), - apps: formatInputValue(notifDeviceApps), - }, - ...extra, - }); - updateCache(); - }, [ - formatInputValue, - notifCurrencies, - notifDeviceApps, - notifDeviceModelId, - notifDeviceVersion, - notifExtra, - notifLanguages, - notifPlatform, - notifAppVersions, - notifLiveCommonVersions, - updateCache, - ]); - - const setValue = useCallback(setter => evt => setter(evt.target.value), []); - - return ( - - - - {expanded ? "mock [ - ]" : "m"} - - - {expanded ? ( - <> - {queue.length ? ( - - - {"queue "} - {expandedQueue ? "[ - ]" : "[ + ]"} - - {expandedQueue - ? queue.map((e, i) => ( - - unQueueEventByIndex(i)} - > - {"[ x ] "} - - {JSON.stringify(e)} - - )) - : null} - - ) : null} - {history.length ? ( - - - {"history "} - {expandedHistory ? "[ - ]" : "[ + ]"} - - {expandedHistory - ? history.map((e, i) => ( - queueEvent(e)}> - {JSON.stringify(e)} - - )) - : null} - - ) : null} - {/* Events here are supposed to be generic and not for a specific flow */} - - - {"quick-list "} - {expandedQuick ? "[ - ]" : "[ + ]"} - - {expandedQuick - ? helpfulEvents.map(({ name, event }, i) => ( - queueEvent(event)} - > - {name} - - )) - : null} - - - - {"swap "} - {expandedSwap ? "[ - ]" : "[ + ]"} - - {expandedSwap - ? swapEvents.map(({ name, event }, i) => ( - queueEvent(event)} - > - {name} - - )) - : null} - - - - {"notif "} - {expandedNotif ? "[ - ]" : "[ + ]"} - - {expandedNotif ? ( - <> - - - - - - - - -