diff --git a/.changeset/long-meals-laugh.md b/.changeset/long-meals-laugh.md
new file mode 100644
index 000000000000..45be9c142b63
--- /dev/null
+++ b/.changeset/long-meals-laugh.md
@@ -0,0 +1,6 @@
+---
+"ledger-live-desktop": patch
+"@ledgerhq/live-common": patch
+---
+
+Update the ui of the tooltip when there is some sync errors. We now display the name of the accounts that failed to sync
diff --git a/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.tsx b/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.tsx
index ca9c3be61b6a..6deed57f567a 100644
--- a/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.tsx
+++ b/apps/ledger-live-desktop/src/renderer/components/TopBar/ActivityIndicator.tsx
@@ -1,7 +1,11 @@
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 {
+ useBatchAccountsSyncState,
+ useBridgeSync,
+ useGlobalSyncState,
+} from "@ledgerhq/live-common/bridge/react/index";
import { useCountervaluesPolling } from "@ledgerhq/live-countervalues-react";
import { getEnv } from "@ledgerhq/live-env";
import { track } from "~/renderer/analytics/segment";
@@ -15,18 +19,37 @@ import TranslatedError from "../TranslatedError";
import Box from "../Box";
import { ItemContainer } from "./shared";
import { useWalletSyncUserState } from "LLD/features/WalletSync/components/WalletSyncContext";
+import { useBatchMaybeAccountName } from "~/renderer/reducers/wallet";
+import { getDefaultAccountName } from "@ledgerhq/live-wallet/accountName";
+import { Text } from "@ledgerhq/react-ui";
-export default function ActivityIndicatorInner() {
+const ActivityIndicatorInner = () => {
const wsUserState = useWalletSyncUserState();
const bridgeSync = useBridgeSync();
const globalSyncState = useGlobalSyncState();
const isUpToDate = useSelector(isUpToDateSelector);
const cvPolling = useCountervaluesPolling();
+
+ const allAccounts = isUpToDate.map(ojb => ojb.account);
+ const allAccountsWithSyncProblem = useBatchAccountsSyncState({ accounts: allAccounts }).filter(
+ ({ syncState, account }) =>
+ !syncState.pending &&
+ (syncState.error || !isUpToDate.find(obj => obj.account.id === account.id)?.isUpToDate),
+ );
+ const allMaybeAccountNames = useBatchMaybeAccountName(
+ allAccountsWithSyncProblem.map(ojb => ojb.account),
+ );
+ const allAccountNamesWithSyncError = allMaybeAccountNames.map(
+ (name, index) => name ?? getDefaultAccountName(allAccountsWithSyncProblem[index].account),
+ );
+
+ const areAllAccountsUpToDate = allAccountNamesWithSyncError.length === 0;
+
const isPending = cvPolling.pending || globalSyncState.pending || wsUserState.visualPending;
const syncError =
!isPending && (cvPolling.error || globalSyncState.error || wsUserState.walletSyncError);
- // 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) || !!wsUserState.walletSyncError;
+
+ const isError = !!syncError || !areAllAccountsUpToDate || !!wsUserState.walletSyncError;
const error = (syncError ? globalSyncState.error : null) || wsUserState.walletSyncError;
const [lastClickTime, setLastclickTime] = useState(0);
const onClick = useCallback(() => {
@@ -40,11 +63,51 @@ export default function ActivityIndicatorInner() {
setLastclickTime(Date.now());
track("SyncRefreshClick");
}, [cvPolling, bridgeSync, wsUserState]);
- const isSpectronRun = getEnv("PLAYWRIGHT_RUN"); // we will keep 'spinning' in spectron case
+ const isSpectronRun = getEnv("PLAYWRIGHT_RUN");
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 getIcon = () => {
+ if (isError) return ;
+ if (isRotating) return ;
+ if (areAllAccountsUpToDate) return ;
+ return ;
+ };
+
+ const getText = () => {
+ if (isRotating) return ;
+ if (isError) {
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }
+ if (areAllAccountsUpToDate) {
+ return (
+
+
+
+ );
+ }
+ return ;
+ };
+
const content = (
- {isError ? (
-
- ) : isRotating ? (
-
- ) : isUpToDate ? (
-
- ) : (
-
- )}
+ {getIcon()}
- {isRotating ? (
-
- ) : isError ? (
- <>
-
-
-
-
-
-
- >
- ) : isUpToDate ? (
-
-
-
- ) : (
-
- )}
+ {getText()}
);
- if (isError && error) {
+
+ if (!areAllAccountsUpToDate || (isError && error)) {
return (
-
+ {isError && error && areAllAccountsUpToDate ? (
+
+ ) : (
+
+
+ {"Accounts failing to sync:"}
+
+ {allAccountNamesWithSyncError.map((accountName, index) => (
+
+ {accountName}
+
+ ))}
+
+ )}
}
>
@@ -132,5 +176,8 @@ export default function ActivityIndicatorInner() {
);
}
+
return content;
-}
+};
+
+export default ActivityIndicatorInner;
diff --git a/apps/ledger-live-desktop/src/renderer/reducers/accounts.ts b/apps/ledger-live-desktop/src/renderer/reducers/accounts.ts
index bb391069b0f5..d14f8d08965a 100644
--- a/apps/ledger-live-desktop/src/renderer/reducers/accounts.ts
+++ b/apps/ledger-live-desktop/src/renderer/reducers/accounts.ts
@@ -117,14 +117,17 @@ export const subAccountByCurrencyOrderedSelector = createSelector(
// FIXME we might reboot this idea later!
export const activeAccountsSelector = accountsSelector;
export const isUpToDateSelector = createSelector(activeAccountsSelector, accounts =>
- accounts.every(a => {
+ accounts.map(a => {
const { lastSyncDate } = a;
const { blockAvgTime } = a.currency;
- if (!blockAvgTime) return true;
- const outdated =
- Date.now() - (lastSyncDate.getTime() || 0) >
- blockAvgTime * 1000 + getEnv("SYNC_OUTDATED_CONSIDERED_DELAY");
- return !outdated;
+ let isUpToDate = true;
+ if (blockAvgTime) {
+ const outdated =
+ Date.now() - (lastSyncDate.getTime() || 0) >
+ blockAvgTime * 1000 + getEnv("SYNC_OUTDATED_CONSIDERED_DELAY");
+ isUpToDate = !outdated;
+ }
+ return { account: a, isUpToDate };
}),
);
diff --git a/apps/ledger-live-desktop/src/renderer/reducers/wallet.ts b/apps/ledger-live-desktop/src/renderer/reducers/wallet.ts
index 036de6a31fe2..1b41ab05a23e 100644
--- a/apps/ledger-live-desktop/src/renderer/reducers/wallet.ts
+++ b/apps/ledger-live-desktop/src/renderer/reducers/wallet.ts
@@ -38,6 +38,15 @@ export const useMaybeAccountName = (
!account ? undefined : accountNameWithDefaultSelector(state.wallet, account),
);
};
+export const useBatchMaybeAccountName = (
+ accounts: (AccountLike | null | undefined)[],
+): (string | undefined)[] => {
+ return useSelector((state: State) =>
+ accounts.map(account =>
+ !account ? undefined : accountNameWithDefaultSelector(state.wallet, account),
+ ),
+ );
+};
export const useAccountName = (account: AccountLike) => {
return useSelector((state: State) => accountNameWithDefaultSelector(state.wallet, account));
diff --git a/libs/ledger-live-common/src/bridge/react/useAccountSyncState.ts b/libs/ledger-live-common/src/bridge/react/useAccountSyncState.ts
index 987a9ccfb4c4..e006b55c8fcd 100644
--- a/libs/ledger-live-common/src/bridge/react/useAccountSyncState.ts
+++ b/libs/ledger-live-common/src/bridge/react/useAccountSyncState.ts
@@ -1,9 +1,12 @@
-import type { SyncState } from "./types";
+import { Account } from "@ledgerhq/types-live";
import { useBridgeSyncState } from "./context";
-const nothingState = {
+import type { SyncState } from "./types";
+
+const nothingState: SyncState = {
pending: false,
error: null,
};
+
export function useAccountSyncState({
accountId,
}: {
@@ -12,3 +15,27 @@ export function useAccountSyncState({
const syncState = useBridgeSyncState();
return (accountId && syncState[accountId]) || nothingState;
}
+
+interface AccountWithSyncState {
+ account: Account;
+ syncState: SyncState;
+}
+
+export function useBatchAccountsSyncState({
+ accounts,
+}: {
+ accounts?: (Account | null)[];
+} = {}): AccountWithSyncState[] {
+ const syncState = useBridgeSyncState();
+ if (!accounts) return [];
+
+ return accounts.reduce((acc, account) => {
+ if (account) {
+ acc.push({
+ account,
+ syncState: syncState[account.id] || nothingState,
+ });
+ }
+ return acc;
+ }, [] as AccountWithSyncState[]);
+}