Skip to content

Commit fed3018

Browse files
Merge pull request #14 from terra-money/feat/whale/alliance/hub
feat: add whale alliance hub
2 parents 30021b5 + 6f04fe8 commit fed3018

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3384
-873
lines changed

app/layout.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Metadata } from 'next'
2-
import '@/styles/globals.css';
3-
import Nav from '@/components/Nav';
4-
import { Footer } from '@/components/Footer';
2+
import '../styles/globals.css';
3+
import Nav from '../components/Nav';
54
import { Suspense } from 'react';
6-
import CSSLoader from '@/components/CSSLoader';
5+
import CSSLoader from '../components/CSSLoader';
76

87
export const metadata: Metadata = {
98
title: 'Alliance Analytics Dashboard',
@@ -47,7 +46,6 @@ export default function RootLayout({
4746
<Suspense fallback={<CSSLoader />}>
4847
{children}
4948
</Suspense>
50-
<Footer />
5149
</main>
5250
</body>
5351
</html>

app/page.tsx

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,92 @@
11
"use client";
22

3-
import CSSLoader from "@/components/CSSLoader";
4-
import Card from "@/components/Card";
5-
import Graph from "@/components/Graph";
6-
import LoadingComponent from "@/components/LoadingComponent";
7-
import Pill from "@/components/Pill";
8-
import Table from "@/components/Table";
9-
import { MOCK_PRICES, defaultChain, pills, supportedChains } from "@/const/Variables";
10-
import { QueryForAlliances } from "@/lib/AllianceQuery";
11-
import { Alliance, AllianceResponse } from "@/types/ResponseTypes";
3+
import CSSLoader from "../components/CSSLoader";
4+
import Card from "../components/Card";
5+
import Kpi from "../components/Kpi";
6+
import Table from "../components/Table";
7+
import { DEFAULT_CHAIN, SUPPORTED_CHAINS } from "../const/chains";
8+
import { QueryAlliances } from "../lib/QueryAlliances";
129
import Link from "next/link";
13-
import { useSearchParams } from "next/navigation";
10+
import { useSearchParams, useRouter } from "next/navigation";
1411
import { Suspense, useEffect, useState } from "react";
12+
import { Prices, QueryAndMergePrices } from "../models/Prices";
13+
import { Kpis } from "../const/kpis";
14+
import { LCD } from "../models/LCDConfig";
15+
import TableState from "../models/TableState";
16+
import { GetInflationEndpoint, ParseInflation } from "../lib/QueryInflation";
17+
import { QueryLP } from "../lib/QueryLP";
18+
import { Chain } from "../models/Chain";
1519

1620
export default function Home() {
17-
const [usdValues, setUsdValues] = useState<any>();
18-
const [pillPrices, setPillPrices] = useState<any>(null);
19-
const [data, setData] = useState<Alliance[]>([]);
2021
const params = useSearchParams();
21-
const [loading, setLoading] = useState(true);
22+
const router = useRouter();
23+
24+
const [tableState, setTableState] = useState<TableState | null>(null);
25+
const [selectedChain, setSelectedChain] = useState<Chain | undefined>(undefined);
26+
const [prices, setPrices] = useState<Prices>({});
27+
const [isLoading, setIsLoading] = useState(true);
2228

2329
useEffect(() => {
24-
(async () => {
25-
if (!usdValues || !pillPrices) {
26-
setLoading(true);
27-
const result = await fetch("https://price.api.tfm.com/tokens/?limit=1500");
28-
const json = await result.json();
29-
setUsdValues({
30-
...json,
31-
...MOCK_PRICES,
32-
});
30+
const selectedParamIsSupported = SUPPORTED_CHAINS[params.get("selected") as any];
31+
if (selectedParamIsSupported) {
32+
setSelectedChain(SUPPORTED_CHAINS[params.get("selected") as string]);
33+
} else {
34+
setSelectedChain(SUPPORTED_CHAINS[DEFAULT_CHAIN])
35+
router.push(`?selected=${SUPPORTED_CHAINS[DEFAULT_CHAIN].id}`);
36+
}
37+
}, [params])
38+
39+
useEffect(() => {
40+
if (selectedChain) {
41+
setIsLoading(true);
42+
(async () => {
43+
// Load the prices only the first time a user lands on
44+
// the page, then use the prices from the state.
45+
//
46+
// NOTE: the variable is not being shadowed because otherwise
47+
// the first load will result on 0 prices.
48+
const _prices = Object.keys(prices).length === 0 ? await QueryAndMergePrices() : prices;
49+
setPrices(_prices);
50+
51+
// Query selectedChain alliances info
52+
const _allianceAssetRes = await QueryAlliances(selectedChain).catch(() => []);
3353

34-
const priceResult = await fetch("https://pisco-price-server.terra.dev/latest");
35-
const pillJson = await priceResult.json();
36-
setPillPrices(Object.fromEntries(pillJson.prices.map((p:any) => {
37-
return [p.denom, {
38-
usd: p.price
39-
}]
40-
})))
41-
setLoading(false);
42-
}
54+
// Query chain info on parallel to speed up the loading time
55+
let chainInfoRes = await Promise.all([
56+
LCD.alliance.params(selectedChain.id),
57+
LCD.bank.supplyByDenom(selectedChain.id, selectedChain.bondDenom),
58+
GetInflationEndpoint(selectedChain.id),
59+
]).catch((e) => {
60+
console.error(e)
61+
return []
62+
});
4363

44-
const chain = supportedChains[params.get("selected") ?? defaultChain];
45-
let response = [];
64+
// Query this info in parallel to speed up the loading time
65+
// because the requested data is independent from each other
66+
const [inflation, allianceCoins] = await Promise.all([
67+
ParseInflation(selectedChain.id, chainInfoRes[2]),
68+
QueryLP(selectedChain.allianceCoins)
69+
])
4670

47-
try {
48-
const resp = await QueryForAlliances(chain);
49-
response = [...resp.alliances];
50-
} catch {
51-
response = [...[]];
52-
}
71+
selectedChain.allianceCoins = allianceCoins;
5372

54-
setData(response);
55-
})();
56-
}, [params]);
73+
// If no error occured, set the data
74+
// otherwise, keep the default data
75+
if (chainInfoRes != undefined) {
76+
let tableState = new TableState(
77+
selectedChain,
78+
_allianceAssetRes,
79+
_prices,
80+
chainInfoRes[0].params,
81+
chainInfoRes[1],
82+
inflation,
83+
);
84+
setTableState(tableState)
85+
}
86+
setIsLoading(false);
87+
})();
88+
}
89+
}, [selectedChain]);
5790

5891
return (
5992
<section className="w-full flex-col">
@@ -69,26 +102,19 @@ export default function Home() {
69102
Learn how to <Link href="https://medium.com/terra-money/how-to-stake-alliance-assets-a-step-by-step-guide-8e1b263830c2"><u>stake Alliance assets</u></Link>.
70103
</h3>
71104
</div>
72-
<div className="flex flex-col pt-3 pb-3 mt-12 overflow-auto">
73-
<LoadingComponent isLoading={loading} values={pillPrices}>
74-
<div className="flex gap-3">{pillPrices && pills.map((pill) => <Pill key={pill.id} pill={pill} data={pillPrices[pill.token]} />)}</div>
75-
</LoadingComponent>
105+
<div className="flex flex-col pt-3 mt-12 overflow-auto">
106+
<div className="flex gap-3">
107+
{Kpis.map((kpi) => <Kpi key={kpi.id} kpi={kpi} data={prices[kpi.token]} />)}
108+
</div>
76109
</div>
77110
<div className="flex w-full flex-col lg:flex-row gap-3">
78111
<div className="w-full lg:w-6/6">
79112
<Card name="Assets">
80113
<Suspense fallback={<CSSLoader />}>
81-
<Table usdValues={usdValues} values={data} />
114+
<Table tableState={tableState} isLoading={isLoading} />
82115
</Suspense>
83116
</Card>
84117
</div>
85-
{/* <div className="w-full lg:w-2/6">
86-
<Card name="Overview" className="flex flex-col items-center overflow-auto">
87-
<Suspense fallback={<CSSLoader />}>
88-
<Graph values={data} />
89-
</Suspense>
90-
</Card>
91-
</div> */}
92118
</div>
93119
</section>
94120
);

components/CSSLoader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import styles from '@/styles/CSSLoader.module.css';
1+
import styles from '../styles/CSSLoader.module.css';
22

33
const CSSLoader = () => {
44
return (

components/Card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default function Card({
1313
<div className="flex justify-between mb-6 pr-4 pl-4">
1414
<h1 className="text-3xl font-medium">{name}</h1>
1515
</div>
16-
<div className={'pr-4 pl-4 pb-3 pt-3 max-h-80 overflow-auto'}>
16+
<div className={'pr-4 pl-4 pb-3 pt-3 overflow-auto'}>
1717
{children}
1818
</div>
1919
</div>

components/Dropdown.tsx

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,25 @@
11
"use client";
22

3-
import { useState } from "react";
4-
import styles from "@/styles/Dropdown.module.css";
5-
import { supportedChains, defaultChain } from "@/const/Variables";
3+
import { useEffect, useState } from "react";
4+
import styles from "../styles/Dropdown.module.css";
65
import { useRouter, useSearchParams } from "next/navigation";
6+
import { DEFAULT_CHAIN, SUPPORTED_CHAINS } from "../const/chains";
77

88
export default function Dropdown() {
99
const params = useSearchParams();
1010
const [show, setShow] = useState<boolean>(false);
11-
const [selected, setSelected] = useState<any>(supportedChains[params.get("selected") ?? defaultChain]);
11+
const [selected, setSelected] = useState<any>();
1212
const router = useRouter();
1313

14+
useEffect(() => {
15+
if (SUPPORTED_CHAINS[params.get("selected") as any]) {
16+
setSelected(SUPPORTED_CHAINS[params.get("selected") as string]);
17+
}
18+
else {
19+
setSelected(SUPPORTED_CHAINS[DEFAULT_CHAIN]);
20+
}
21+
}, [])
22+
1423
return (
1524
<div
1625
className={styles.select}
@@ -30,24 +39,22 @@ export default function Dropdown() {
3039
<div className={styles.selected}>
3140
<img src={selected.icon} alt={"selected"} width={30} height={30} />
3241
<span>{selected.name}</span>
33-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-6 h-6">
34-
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
35-
</svg>
42+
<img src="/images/arrow-down.svg" alt="arrow" width={24} height={24} />
3643
</div>
3744
)}
3845
<div id={"dropdown"} className={`${show ? "" : styles.reversed} hidden`}>
3946
<ul>
40-
{Object.keys(supportedChains).map((chain) => (
47+
{Object.keys(SUPPORTED_CHAINS).map((chain) => (
4148
<li
4249
key={chain}
4350
className="flex gap-3 p-3 items-center"
4451
onClick={() => {
45-
setSelected(supportedChains[chain]);
46-
router.push(`?selected=${supportedChains[chain].name.toLocaleLowerCase()}`);
52+
setSelected(SUPPORTED_CHAINS[chain]);
53+
router.push(`?selected=${SUPPORTED_CHAINS[chain].id}`);
4754
}}
4855
>
49-
<img src={supportedChains[chain].icon} alt={chain} width={30} height={30} />
50-
<span>{supportedChains[chain].name}</span>
56+
<img src={SUPPORTED_CHAINS[chain].icon} alt={chain} width={30} height={30} />
57+
<span>{SUPPORTED_CHAINS[chain].name}</span>
5158
</li>
5259
))}
5360
</ul>

components/Footer.tsx

Lines changed: 0 additions & 7 deletions
This file was deleted.

components/Graph.tsx

Lines changed: 0 additions & 80 deletions
This file was deleted.

components/Kpi.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { Tooltip } from '@nextui-org/react';
2+
import LoadingComponent from './LoadingComponent';
3+
4+
export interface KpiData {
5+
id: number;
6+
name: string;
7+
symbol: string;
8+
token: string;
9+
}
10+
11+
export default function Kpi({ kpi, data }: { kpi: KpiData, data?: any }) {
12+
return (
13+
<div className="flex custom_card gap-3">
14+
<LoadingComponent isLoading={data === undefined} values={data}>
15+
<div className='flex items-center'>
16+
<Tooltip content={kpi.name}>
17+
<img
18+
src={kpi.symbol}
19+
alt='Coin image'
20+
width={45}
21+
height={45}
22+
className="object-contain"
23+
/>
24+
</Tooltip>
25+
</div>
26+
<div className="flex items-center w-full">
27+
<h2>${parseFloat(data?.usd ?? '0').toLocaleString('en-US', {
28+
minimumFractionDigits: 4
29+
})}</h2>
30+
</div>
31+
</LoadingComponent>
32+
</div>
33+
);
34+
}

0 commit comments

Comments
 (0)