Skip to content

Commit 31e4a61

Browse files
authored
[GSW-2363] Explore token-amount decimals (#777)
* feat: [GSW-2363] Explore token-amount decimals * refactor: [GSW-2363] Prefer `Number.isNaN` over `isNaN`.
1 parent 30f31bd commit 31e4a61

File tree

3 files changed

+81
-28
lines changed

3 files changed

+81
-28
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { GovernanceOverviewResponse } from "@repositories/dashboard";
2+
3+
export const DEFAULT_GOVERNANCE_OVERVIEW_INFO: GovernanceOverviewResponse = {
4+
totalDelegated: 0,
5+
holders: 0,
6+
passedCount: 0,
7+
activeCount: 0,
8+
communityPool: 0,
9+
};

packages/web/src/layouts/dashboard/containers/dashboard-info-container/DashboardInfoContainer.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,18 @@ const DashboardInfoContainer: React.FC = () => {
2222
const { breakpoint } = useWindowSize();
2323
const { isLoading: isLoadingCommon } = useLoading();
2424

25-
const { data: tokenData, isFetched: isFetchedDashboardToken } = useGetDashboardToken();
26-
const convertedTokenData = React.useMemo(() => {
27-
return ExploreDashboardConverter.convertDashboardToken(tokenData);
28-
}, [tokenData]);
25+
const { data: dashboardTokenData, isFetched: isFetchedDashboardToken } = useGetDashboardToken();
26+
const convertedDashboardTokenData = React.useMemo(() => {
27+
return ExploreDashboardConverter.convertDashboardToken(dashboardTokenData);
28+
}, [dashboardTokenData]);
2929

3030
const { data: governanceOverview = null, isFetched: isFetchedGovernanceOverview } =
3131
useGetDashboardGovernanceOverview();
3232

33+
const convertedGovernanceOverview = React.useMemo(() => {
34+
return ExploreDashboardConverter.convertGovernanceOverview(governanceOverview);
35+
}, [governanceOverview]);
36+
3337
const isLoading = useMemo(() => {
3438
if (isLoadingCommon) {
3539
return true;
@@ -39,23 +43,23 @@ const DashboardInfoContainer: React.FC = () => {
3943
}, [isFetchedDashboardToken, isFetchedGovernanceOverview, isLoadingCommon]);
4044

4145
const progressBar = useMemo(() => {
42-
if (!convertedTokenData) return "0%";
43-
const circSupply = Number(convertedTokenData?.gnsCirculatingSupply);
44-
const totalSupply = Number(convertedTokenData?.gnsTotalSupply);
46+
if (!convertedDashboardTokenData) return "0%";
47+
const circSupply = Number(convertedDashboardTokenData?.gnsCirculatingSupply);
48+
const totalSupply = Number(convertedDashboardTokenData?.gnsTotalSupply);
4549
if (totalSupply === 0) return "0%";
4650
const percent = Math.min((circSupply / totalSupply) * 100, 100);
4751
return `${percent}%`;
48-
}, [convertedTokenData]);
52+
}, [convertedDashboardTokenData]);
4953
const stakingRatio = useMemo(() => {
50-
if (!convertedTokenData) return "-";
51-
const circSupply = Number(convertedTokenData?.gnsCirculatingSupply);
52-
const totalStaked = Number(convertedTokenData?.gnsTotalStaked);
54+
if (!convertedDashboardTokenData) return "-";
55+
const circSupply = Number(convertedDashboardTokenData?.gnsCirculatingSupply);
56+
const totalStaked = Number(convertedDashboardTokenData?.gnsTotalStaked);
5357

5458
if (totalStaked === 0 || circSupply === 0) return "0%";
5559
if ((totalStaked * 100) / circSupply < 0.01) return "<0.01%";
5660
const ratio = ((totalStaked / circSupply) * 100).toFixed(3);
5761
return `${ratio}%`;
58-
}, [convertedTokenData]);
62+
}, [convertedDashboardTokenData]);
5963

6064
const supplyOverviewInfo: SupplyOverviewInfo = useMemo(() => {
6165
const DISTRIBUTION_RATIOS = {
@@ -64,10 +68,10 @@ const DashboardInfoContainer: React.FC = () => {
6468
COMMUNITY: 0.05, // 5%
6569
};
6670

67-
const circulatingSupply = convertedTokenData.gnsCirculatingSupply;
68-
const totalSupply = convertedTokenData.gnsTotalSupply;
69-
const totalStaked = Number(convertedTokenData.gnsTotalStaked);
70-
const dailyBlockEmissions = Number(convertedTokenData.gnsDailyBlockEmissions);
71+
const circulatingSupply = convertedDashboardTokenData.gnsCirculatingSupply;
72+
const totalSupply = convertedDashboardTokenData.gnsTotalSupply;
73+
const totalStaked = Number(convertedDashboardTokenData.gnsTotalStaked);
74+
const dailyBlockEmissions = Number(convertedDashboardTokenData.gnsDailyBlockEmissions);
7175

7276
const emissionDistribution = {
7377
liquidityStaking: dailyBlockEmissions * DISTRIBUTION_RATIOS.LIQUIDITY_STAKING,
@@ -95,31 +99,31 @@ const DashboardInfoContainer: React.FC = () => {
9599
community: formatOtherPrice(Math.floor(emissionDistribution.community), { isKMB: false, usd: false }),
96100
},
97101
};
98-
}, [tokenData, progressBar, stakingRatio]);
102+
}, [dashboardTokenData, progressBar, stakingRatio]);
99103

100104
const governanceOverviewInfo = useMemo(() => {
101-
if (!governanceOverview) {
105+
if (!convertedGovernanceOverview) {
102106
return null;
103107
}
104108

105109
return {
106-
totalDelegated: `${numberToFormat(governanceOverview.totalDelegated)} ${XGNS_TOKEN.symbol}`,
107-
holders: `${numberToFormat(governanceOverview.holders)}`,
108-
passedCount: `${numberToFormat(governanceOverview.passedCount)}`,
109-
activeCount: `${numberToFormat(governanceOverview.activeCount)} `,
110-
communityPool: `${formatOtherPrice(governanceOverview.communityPool, {
110+
totalDelegated: `${numberToFormat(convertedGovernanceOverview.totalDelegated)} ${XGNS_TOKEN.symbol}`,
111+
holders: `${numberToFormat(convertedGovernanceOverview.holders)}`,
112+
passedCount: `${numberToFormat(convertedGovernanceOverview.passedCount)}`,
113+
activeCount: `${numberToFormat(convertedGovernanceOverview.activeCount)} `,
114+
communityPool: `${formatOtherPrice(convertedGovernanceOverview.communityPool, {
111115
isKMB: false,
112116
})}`,
113117
};
114-
}, [governanceOverview]);
118+
}, [convertedGovernanceOverview]);
115119

116120
return (
117121
<DashboardInfo
118122
dashboardTokenInfo={{
119-
gnosAmount: formatPrice(tokenData?.gnsPrice, {
123+
gnosAmount: formatPrice(dashboardTokenData?.gnsPrice, {
120124
isKMB: false,
121125
}),
122-
gnotAmount: formatPrice(tokenData?.gnotPrice ?? "0", {
126+
gnotAmount: formatPrice(dashboardTokenData?.gnotPrice ?? "0", {
123127
isKMB: false,
124128
}),
125129
}}

packages/web/src/services/converters/explore-dashboard/explore-dashboard.converter.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { AmountConverter } from "@services/converters/common/amount";
22

3-
import { GNS_TOKEN } from "@common/values/token-constant";
4-
import { DashboardTokenResponse } from "@repositories/dashboard";
3+
import { GNS_TOKEN, XGNS_TOKEN } from "@common/values/token-constant";
4+
import { DashboardTokenResponse, GovernanceOverviewResponse } from "@repositories/dashboard";
55
import { DEFAULT_DASHBOARD_TOKEN_INFO } from "@common/values/default-object/explore-dashboard/dashboard-token-info";
66
import { ActivityData } from "@repositories/activity/responses/activity-responses";
7+
import { DEFAULT_GOVERNANCE_OVERVIEW_INFO } from "@common/values/default-object/explore-dashboard/governance-overview-info";
78

89
/**
910
* Utility class responsible for converting dashboard token data
@@ -31,6 +32,24 @@ export class ExploreDashboardConverter {
3132
};
3233
}
3334

35+
/**
36+
* Convert raw governance overview data to display format
37+
* Converts XGNS token amounts to safe number format while preserving other fields
38+
*
39+
* @param data - raw governance overview response from API (can be null)
40+
* @returns governance overview data with converted XGNS amounts as numbers (default values if input is invalid)
41+
*/
42+
static convertGovernanceOverview(data: GovernanceOverviewResponse | null): GovernanceOverviewResponse {
43+
if (!data) {
44+
return DEFAULT_GOVERNANCE_OVERVIEW_INFO;
45+
}
46+
47+
return {
48+
...data,
49+
totalDelegated: this.safeConvertToNumber(AmountConverter.convertSingle(XGNS_TOKEN, data.totalDelegated)),
50+
};
51+
}
52+
3453
/**
3554
* Convert dashboard activity list with token amounts to display format
3655
* Converts raw token amounts for both tokenA and tokenB in each activity using their respective token models
@@ -49,4 +68,25 @@ export class ExploreDashboardConverter {
4968
};
5069
});
5170
}
71+
72+
/**
73+
* Safely convert a value to number type with fallback to 0
74+
* Handles null, undefined, and invalid number conversions (NaN)
75+
*
76+
* @param value - value to convert (string, number, null, or undefined)
77+
* @returns converted number value, or 0 if conversion fails or value is null/undefined
78+
*
79+
* @example
80+
* safeConvertToNumber("123") // returns 123
81+
* safeConvertToNumber(null) // returns 0
82+
* safeConvertToNumber("invalid") // returns 0
83+
*/
84+
private static safeConvertToNumber(value: string | number | null | undefined): number {
85+
if (value == null) {
86+
return 0;
87+
}
88+
89+
const converted = Number(value);
90+
return Number.isNaN(converted) ? 0 : converted;
91+
}
5292
}

0 commit comments

Comments
 (0)