From 9318d21e2274d5409eff4ed8a593085767d244dc Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 10:37:35 +0100 Subject: [PATCH 1/9] no assets cards --- .../components/portfolio/NoNeuronsCard.svelte | 61 ++++++++++++++++++ .../components/portfolio/NoTokensCard.svelte | 62 +++++++++++++++++++ frontend/src/lib/i18n/en.json | 7 ++- frontend/src/lib/pages/Portfolio.svelte | 7 ++- frontend/src/lib/types/i18n.d.ts | 5 ++ 5 files changed, 139 insertions(+), 3 deletions(-) create mode 100644 frontend/src/lib/components/portfolio/NoNeuronsCard.svelte create mode 100644 frontend/src/lib/components/portfolio/NoTokensCard.svelte diff --git a/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte b/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte new file mode 100644 index 0000000000..9069989094 --- /dev/null +++ b/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte @@ -0,0 +1,61 @@ + + + +
+
+ +
+
+

{$i18n.portfolio.no_neurons_card_description}

+
+ {$i18n.portfolio.no_neurons_card_button} +
+
+ + diff --git a/frontend/src/lib/components/portfolio/NoTokensCard.svelte b/frontend/src/lib/components/portfolio/NoTokensCard.svelte new file mode 100644 index 0000000000..ccddd6a3ab --- /dev/null +++ b/frontend/src/lib/components/portfolio/NoTokensCard.svelte @@ -0,0 +1,62 @@ + + + +
+
+ +
+
+

{$i18n.portfolio.no_tokens_card_title}

+

{$i18n.portfolio.no_tokens_card_description}

+
+ {$i18n.portfolio.no_tokens_card_button} +
+
+ + diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index b21df7ba6c..426a385aa6 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1192,6 +1192,11 @@ }, "portfolio": { "login_title": "Session expired", - "login_description": "Earn rewards, participate in governance and join the communities. Login to gain optimal access of the platform." + "login_description": "Earn rewards, participate in governance and join the communities. Login to gain optimal access of the platform.", + "no_tokens_card_title": "Store tokens safely, invest and become part of shaping the Internet Computer", + "no_tokens_card_description": "Vote on project and protocol developments and earn rewards - Ready to get started?", + "no_neurons_card_description": "Take part of voting for development decisions on SNS projects and earn rewards - start staking your neurons", + "no_tokens_card_button": "Buy ICP", + "no_neurons_card_button": "Start Staking" } } diff --git a/frontend/src/lib/pages/Portfolio.svelte b/frontend/src/lib/pages/Portfolio.svelte index 20c3c2db3d..bfa8a6da21 100644 --- a/frontend/src/lib/pages/Portfolio.svelte +++ b/frontend/src/lib/pages/Portfolio.svelte @@ -1,6 +1,8 @@ @@ -12,8 +14,8 @@ Card1
- Card3 - Card4 + +
@@ -55,6 +57,7 @@ .content { display: grid; grid-template-columns: 1fr; + grid-template-rows: auto; gap: var(--padding-2x); @include media.min-width(large) { diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index 0335bd88e9..c3fb51e06b 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1258,6 +1258,11 @@ interface I18nImport_token { interface I18nPortfolio { login_title: string; login_description: string; + no_tokens_card_title: string; + no_tokens_card_description: string; + no_neurons_card_description: string; + no_tokens_card_button: string; + no_neurons_card_button: string; } interface I18nNeuron_state { From 339fe5f61d5ee9be5df4cc7150be3f0da38e1ae7 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 11:49:30 +0100 Subject: [PATCH 2/9] styles --- .../src/lib/components/portfolio/Card.svelte | 1 + .../lib/components/portfolio/LoginCard.svelte | 2 +- .../components/portfolio/NoNeuronsCard.svelte | 29 ++++++++++--------- .../components/portfolio/NoTokensCard.svelte | 25 +++++++++++----- frontend/src/lib/pages/Portfolio.svelte | 5 ++-- 5 files changed, 38 insertions(+), 24 deletions(-) diff --git a/frontend/src/lib/components/portfolio/Card.svelte b/frontend/src/lib/components/portfolio/Card.svelte index 55eb6dd70b..9e2bc9d09e 100644 --- a/frontend/src/lib/components/portfolio/Card.svelte +++ b/frontend/src/lib/components/portfolio/Card.svelte @@ -15,5 +15,6 @@ color var(--animation-time-normal), box-shadow var(--animation-time-normal); border-radius: var(--border-radius-2x); + overflow: hidden; } diff --git a/frontend/src/lib/components/portfolio/LoginCard.svelte b/frontend/src/lib/components/portfolio/LoginCard.svelte index 709dad7fbe..7aa6a0e6d4 100644 --- a/frontend/src/lib/components/portfolio/LoginCard.svelte +++ b/frontend/src/lib/components/portfolio/LoginCard.svelte @@ -12,7 +12,7 @@

{$i18n.portfolio.login_title}

-

{$i18n.portfolio.login_description}

+

{$i18n.portfolio.login_description}

diff --git a/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte b/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte index 9069989094..a304b07990 100644 --- a/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte +++ b/frontend/src/lib/components/portfolio/NoNeuronsCard.svelte @@ -1,6 +1,6 @@ From f0720d49cfba35698994332e1ba9e44b80e71f1c Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 12:36:02 +0100 Subject: [PATCH 5/9] fixes bg color --- frontend/src/lib/components/portfolio/LoginCard.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/lib/components/portfolio/LoginCard.svelte b/frontend/src/lib/components/portfolio/LoginCard.svelte index 7aa6a0e6d4..c0a088f77c 100644 --- a/frontend/src/lib/components/portfolio/LoginCard.svelte +++ b/frontend/src/lib/components/portfolio/LoginCard.svelte @@ -30,6 +30,7 @@ "action action"; gap: var(--padding-2x); padding: var(--padding-2x); + background-color: var(--card-background-tint); @include media.min-width(medium) { grid-template-areas: From b0da4238dd4cfdc619f475aacf512f272d83deb1 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 15:01:16 +0100 Subject: [PATCH 6/9] TotalAssetsCard --- .../portfolio/IcpExchangeRate.svelte | 74 ++++++++++++ .../portfolio/TotalAssetsCard.svelte | 111 ++++++++++++++++++ frontend/src/lib/pages/Portfolio.svelte | 4 +- 3 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 frontend/src/lib/components/portfolio/IcpExchangeRate.svelte create mode 100644 frontend/src/lib/components/portfolio/TotalAssetsCard.svelte diff --git a/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte b/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte new file mode 100644 index 0000000000..3e8b9759d3 --- /dev/null +++ b/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte @@ -0,0 +1,74 @@ + + +
+ {$i18n.auth.ic_logo} + + 1 {$i18n.core.icp} = ${icpPriceFormatted} + + + {#if hasError} + {$i18n.accounts.token_price_error} + {:else} +
+ 1 {$i18n.core.icp} = ${icpPriceFormatted} +
+ {$i18n.accounts.token_price_source} +
+ {/if} +
+
+ + diff --git a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte new file mode 100644 index 0000000000..870cf48d90 --- /dev/null +++ b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte @@ -0,0 +1,111 @@ + + + +
+

Total Holdings

+ +
+
+
+ + ${usdAmountFormatted} + + {#if hasPrices && hasUnpricedTokens} + + {$i18n.accounts.unpriced_tokens_warning} + + {/if} +
+
+ {icpAmountFormatted} + {$i18n.core.icp} +
+
+ +
+
+
+ + diff --git a/frontend/src/lib/pages/Portfolio.svelte b/frontend/src/lib/pages/Portfolio.svelte index 1163a37de0..13e963e0a7 100644 --- a/frontend/src/lib/pages/Portfolio.svelte +++ b/frontend/src/lib/pages/Portfolio.svelte @@ -1,8 +1,8 @@ @@ -11,7 +11,7 @@ {#if !$authSignedInStore} {/if} - Card1 +
From df80d9aaa10f831e4e2b3a64eae9c4af6cd6754b Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 15:09:09 +0100 Subject: [PATCH 7/9] extract constant to contants file --- .../lib/components/portfolio/IcpExchangeRate.svelte | 5 ++--- .../lib/components/portfolio/TotalAssetsCard.svelte | 13 +++++++------ frontend/src/lib/constants/constants.ts | 2 ++ frontend/src/lib/i18n/en.json | 1 + frontend/src/lib/types/i18n.d.ts | 1 + 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte b/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte index 3e8b9759d3..ae6b7aaa8c 100644 --- a/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte +++ b/frontend/src/lib/components/portfolio/IcpExchangeRate.svelte @@ -2,6 +2,7 @@ import IC_LOGO_ROUNDED from "$lib/assets/icp-rounded.svg"; import TooltipIcon from "$lib/components/ui/TooltipIcon.svelte"; import { LEDGER_CANISTER_ID } from "$lib/constants/canister-ids.constants"; + import { PRICE_NOT_AVAILABLE_PLACEHOLDER } from "$lib/constants/constants"; import { icpSwapUsdPricesStore } from "$lib/derived/icp-swap.derived"; import { i18n } from "$lib/stores/i18n"; import { formatNumber } from "$lib/utils/format.utils"; @@ -9,8 +10,6 @@ export let hasError: boolean; - const absentValue = "-/-"; - let icpPrice: number | undefined; $: icpPrice = isNullish($icpSwapUsdPricesStore) || $icpSwapUsdPricesStore === "error" @@ -20,7 +19,7 @@ let icpPriceFormatted: string; $: icpPriceFormatted = nonNullish(icpPrice) ? formatNumber(icpPrice) - : absentValue; + : PRICE_NOT_AVAILABLE_PLACEHOLDER;
diff --git a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte index 870cf48d90..63c7671c29 100644 --- a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte +++ b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte @@ -1,18 +1,17 @@
-

Total Holdings

+

{$i18n.portfolio.total_assets_title}

diff --git a/frontend/src/lib/constants/constants.ts b/frontend/src/lib/constants/constants.ts index eaec7f04fa..4fa66d242c 100644 --- a/frontend/src/lib/constants/constants.ts +++ b/frontend/src/lib/constants/constants.ts @@ -23,3 +23,5 @@ export const DAYS_IN_NON_LEAP_YEAR = 365; export const NANO_SECONDS_IN_MILLISECOND = 1_000_000; export const NANO_SECONDS_IN_MINUTE = NANO_SECONDS_IN_MILLISECOND * 1_000 * 60; + +export const PRICE_NOT_AVAILABLE_PLACEHOLDER = "-/-"; diff --git a/frontend/src/lib/i18n/en.json b/frontend/src/lib/i18n/en.json index 426a385aa6..96d8a32369 100644 --- a/frontend/src/lib/i18n/en.json +++ b/frontend/src/lib/i18n/en.json @@ -1193,6 +1193,7 @@ "portfolio": { "login_title": "Session expired", "login_description": "Earn rewards, participate in governance and join the communities. Login to gain optimal access of the platform.", + "total_assets_title": "Total Holdings", "no_tokens_card_title": "Store tokens safely, invest and become part of shaping the Internet Computer", "no_tokens_card_description": "Vote on project and protocol developments and earn rewards - Ready to get started?", "no_neurons_card_description": "Take part of voting for development decisions on SNS projects and earn rewards - start staking your neurons", diff --git a/frontend/src/lib/types/i18n.d.ts b/frontend/src/lib/types/i18n.d.ts index c3fb51e06b..9962a3d784 100644 --- a/frontend/src/lib/types/i18n.d.ts +++ b/frontend/src/lib/types/i18n.d.ts @@ -1258,6 +1258,7 @@ interface I18nImport_token { interface I18nPortfolio { login_title: string; login_description: string; + total_assets_title: string; no_tokens_card_title: string; no_tokens_card_description: string; no_neurons_card_description: string; From 336215a313895d041bfe9986f2dc6533a13f7082 Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 16:45:38 +0100 Subject: [PATCH 8/9] fetch tokens --- .../portfolio/TotalAssetsCard.svelte | 2 +- .../services/accounts-balances.services.ts | 118 ++++++++++++++++++ .../routes/(app)/(nns)/portfolio/+page.svelte | 22 ++++ 3 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 frontend/src/lib/services/accounts-balances.services.ts diff --git a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte index 63c7671c29..f62d23161a 100644 --- a/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte +++ b/frontend/src/lib/components/portfolio/TotalAssetsCard.svelte @@ -9,7 +9,7 @@ import { formatNumber } from "$lib/utils/format.utils"; import { isNullish, nonNullish } from "@dfinity/utils"; - export let usdAmount: number | undefined = 14500; + export let usdAmount: number | undefined; export let hasUnpricedTokens: boolean = false; let hasError: boolean; diff --git a/frontend/src/lib/services/accounts-balances.services.ts b/frontend/src/lib/services/accounts-balances.services.ts new file mode 100644 index 0000000000..535526a44c --- /dev/null +++ b/frontend/src/lib/services/accounts-balances.services.ts @@ -0,0 +1,118 @@ +import { CKBTC_ADDITIONAL_CANISTERS } from "$lib/constants/ckbtc-additional-canister-ids.constants"; +import type { IcrcCanistersStoreData } from "$lib/derived/icrc-canisters.derived"; +import type { SnsFullProject } from "$lib/derived/sns/sns-projects.derived"; +import type { Universe } from "$lib/types/universe"; +import { isArrayEmpty } from "$lib/utils/utils"; +import type { CanisterIdString } from "@dfinity/nns"; +import { Principal } from "@dfinity/principal"; +import { nonNullish } from "@dfinity/utils"; +import { updateBalance } from "./ckbtc-minter.services"; +import { uncertifiedLoadSnsesAccountsBalances } from "./sns-accounts-balance.services"; +import { uncertifiedLoadAccountsBalance } from "./wallet-uncertified-accounts.services"; + +class BalanceFetchTracker { + private static instance: BalanceFetchTracker; + private loadedBalances: Set = new Set(); + + public static getInstance(): BalanceFetchTracker { + if (!this.instance) { + this.instance = new BalanceFetchTracker(); + } + return this.instance; + } + + public getNotLoadedIds(ids: CanisterIdString[]): CanisterIdString[] { + const notLoadedIds = ids.filter((id) => !this.loadedBalances.has(id)); + notLoadedIds.forEach((id) => this.loadedBalances.add(id)); + return notLoadedIds; + } + + public reset(): void { + this.loadedBalances.clear(); + } +} + +export const balanceLoader = { + reset() { + BalanceFetchTracker.getInstance().reset(); + }, + async loadAllBalances({ + snsProjects, + ckBTCUniverses, + icrcCanisters, + }: { + snsProjects: SnsFullProject[]; + ckBTCUniverses: Universe[]; + icrcCanisters: IcrcCanistersStoreData; + }) { + await Promise.all([ + this.loadSnsAccountsBalances(snsProjects), + this.loadCkBTCAccountsBalances(ckBTCUniverses), + this.loadIcrcTokenAccounts(icrcCanisters), + ]); + }, + + async loadSnsAccountsBalances(projects: SnsFullProject[]) { + if (projects.length === 0) return; + + const rootCanisterIds = projects.map(({ rootCanisterId }) => + rootCanisterId.toText() + ); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(rootCanisterIds); + + if (notLoadedCanisterIds.length === 0) return; + + await uncertifiedLoadSnsesAccountsBalances({ + rootCanisterIds: notLoadedCanisterIds.map((id) => Principal.fromText(id)), + excludeRootCanisterIds: [], + }); + }, + + async loadCkBTCAccountsBalances(universes: Universe[]) { + const canisterIds = universes.map((universe) => universe.canisterId); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(canisterIds); + + if (notLoadedCanisterIds.length === 0) return; + + // Update balance for each universe + universes.forEach((universe) => { + if (!notLoadedCanisterIds.includes(universe.canisterId)) return; + + const ckBTCCanisters = CKBTC_ADDITIONAL_CANISTERS[universe.canisterId]; + if (nonNullish(ckBTCCanisters.minterCanisterId)) { + updateBalance({ + universeId: Principal.fromText(universe.canisterId), + minterCanisterId: ckBTCCanisters.minterCanisterId, + reload: () => this.loadAccountsBalances([universe.canisterId]), + deferReload: false, + uiIndicators: false, + }); + } + }); + + await this.loadAccountsBalances( + universes.map(({ canisterId }) => canisterId) + ); + }, + + async loadIcrcTokenAccounts(icrcCanisters: IcrcCanistersStoreData) { + const ids = Object.keys(icrcCanisters); + const notLoadedCanisterIds = + BalanceFetchTracker.getInstance().getNotLoadedIds(ids); + + if (notLoadedCanisterIds.length === 0) return; + + await this.loadAccountsBalances(notLoadedCanisterIds); + }, + + async loadAccountsBalances(universeIds: CanisterIdString[]) { + if (isArrayEmpty(universeIds)) return; + + await uncertifiedLoadAccountsBalance({ + universeIds, + excludeUniverseIds: [], + }); + }, +}; diff --git a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte index c0dcb5da75..2ef27c7769 100644 --- a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte +++ b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte @@ -1,6 +1,28 @@ From bea2cbee658a8b564b935d46ec27c1b533a4c3da Mon Sep 17 00:00:00 2001 From: Yusef Habib Fernandez Date: Fri, 27 Dec 2024 16:49:02 +0100 Subject: [PATCH 9/9] rename file --- .../accounts-balances.utils.ts} | 6 +++--- frontend/src/routes/(app)/(nns)/portfolio/+page.svelte | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) rename frontend/src/lib/{services/accounts-balances.services.ts => utils/accounts-balances.utils.ts} (93%) diff --git a/frontend/src/lib/services/accounts-balances.services.ts b/frontend/src/lib/utils/accounts-balances.utils.ts similarity index 93% rename from frontend/src/lib/services/accounts-balances.services.ts rename to frontend/src/lib/utils/accounts-balances.utils.ts index 535526a44c..2f66a80e2a 100644 --- a/frontend/src/lib/services/accounts-balances.services.ts +++ b/frontend/src/lib/utils/accounts-balances.utils.ts @@ -1,14 +1,14 @@ import { CKBTC_ADDITIONAL_CANISTERS } from "$lib/constants/ckbtc-additional-canister-ids.constants"; import type { IcrcCanistersStoreData } from "$lib/derived/icrc-canisters.derived"; import type { SnsFullProject } from "$lib/derived/sns/sns-projects.derived"; +import { updateBalance } from "$lib/services/ckbtc-minter.services"; +import { uncertifiedLoadSnsesAccountsBalances } from "$lib/services/sns-accounts-balance.services"; +import { uncertifiedLoadAccountsBalance } from "$lib/services/wallet-uncertified-accounts.services"; import type { Universe } from "$lib/types/universe"; import { isArrayEmpty } from "$lib/utils/utils"; import type { CanisterIdString } from "@dfinity/nns"; import { Principal } from "@dfinity/principal"; import { nonNullish } from "@dfinity/utils"; -import { updateBalance } from "./ckbtc-minter.services"; -import { uncertifiedLoadSnsesAccountsBalances } from "./sns-accounts-balance.services"; -import { uncertifiedLoadAccountsBalance } from "./wallet-uncertified-accounts.services"; class BalanceFetchTracker { private static instance: BalanceFetchTracker; diff --git a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte index 2ef27c7769..fec7c13136 100644 --- a/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte +++ b/frontend/src/routes/(app)/(nns)/portfolio/+page.svelte @@ -5,9 +5,9 @@ import { icrcCanistersStore } from "$lib/derived/icrc-canisters.derived"; import { snsProjectsCommittedStore } from "$lib/derived/sns/sns-projects.derived"; import Portfolio from "$lib/pages/Portfolio.svelte"; - import { balanceLoader } from "$lib/services/accounts-balances.services"; import { loadCkBTCTokens } from "$lib/services/ckbtc-tokens.services"; import { loadIcpSwapTickers } from "$lib/services/icp-swap.services"; + import { balanceLoader } from "$lib/utils/accounts-balances.utils"; import { onMount } from "svelte"; onMount(() => {