Skip to content

Commit

Permalink
Merge pull request #14 from terra-money/feat/whale/alliance/hub
Browse files Browse the repository at this point in the history
feat: add whale alliance hub
  • Loading branch information
javiersuweijie authored Feb 19, 2024
2 parents 30021b5 + 6f04fe8 commit fed3018
Show file tree
Hide file tree
Showing 42 changed files with 3,384 additions and 873 deletions.
8 changes: 3 additions & 5 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { Metadata } from 'next'
import '@/styles/globals.css';
import Nav from '@/components/Nav';
import { Footer } from '@/components/Footer';
import '../styles/globals.css';
import Nav from '../components/Nav';
import { Suspense } from 'react';
import CSSLoader from '@/components/CSSLoader';
import CSSLoader from '../components/CSSLoader';

export const metadata: Metadata = {
title: 'Alliance Analytics Dashboard',
Expand Down Expand Up @@ -47,7 +46,6 @@ export default function RootLayout({
<Suspense fallback={<CSSLoader />}>
{children}
</Suspense>
<Footer />
</main>
</body>
</html>
Expand Down
136 changes: 81 additions & 55 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,59 +1,92 @@
"use client";

import CSSLoader from "@/components/CSSLoader";
import Card from "@/components/Card";
import Graph from "@/components/Graph";
import LoadingComponent from "@/components/LoadingComponent";
import Pill from "@/components/Pill";
import Table from "@/components/Table";
import { MOCK_PRICES, defaultChain, pills, supportedChains } from "@/const/Variables";
import { QueryForAlliances } from "@/lib/AllianceQuery";
import { Alliance, AllianceResponse } from "@/types/ResponseTypes";
import CSSLoader from "../components/CSSLoader";
import Card from "../components/Card";
import Kpi from "../components/Kpi";
import Table from "../components/Table";
import { DEFAULT_CHAIN, SUPPORTED_CHAINS } from "../const/chains";
import { QueryAlliances } from "../lib/QueryAlliances";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useSearchParams, useRouter } from "next/navigation";
import { Suspense, useEffect, useState } from "react";
import { Prices, QueryAndMergePrices } from "../models/Prices";
import { Kpis } from "../const/kpis";
import { LCD } from "../models/LCDConfig";
import TableState from "../models/TableState";
import { GetInflationEndpoint, ParseInflation } from "../lib/QueryInflation";
import { QueryLP } from "../lib/QueryLP";
import { Chain } from "../models/Chain";

export default function Home() {
const [usdValues, setUsdValues] = useState<any>();
const [pillPrices, setPillPrices] = useState<any>(null);
const [data, setData] = useState<Alliance[]>([]);
const params = useSearchParams();
const [loading, setLoading] = useState(true);
const router = useRouter();

const [tableState, setTableState] = useState<TableState | null>(null);
const [selectedChain, setSelectedChain] = useState<Chain | undefined>(undefined);
const [prices, setPrices] = useState<Prices>({});
const [isLoading, setIsLoading] = useState(true);

useEffect(() => {
(async () => {
if (!usdValues || !pillPrices) {
setLoading(true);
const result = await fetch("https://price.api.tfm.com/tokens/?limit=1500");
const json = await result.json();
setUsdValues({
...json,
...MOCK_PRICES,
});
const selectedParamIsSupported = SUPPORTED_CHAINS[params.get("selected") as any];
if (selectedParamIsSupported) {
setSelectedChain(SUPPORTED_CHAINS[params.get("selected") as string]);
} else {
setSelectedChain(SUPPORTED_CHAINS[DEFAULT_CHAIN])
router.push(`?selected=${SUPPORTED_CHAINS[DEFAULT_CHAIN].id}`);
}
}, [params])

useEffect(() => {
if (selectedChain) {
setIsLoading(true);
(async () => {
// Load the prices only the first time a user lands on
// the page, then use the prices from the state.
//
// NOTE: the variable is not being shadowed because otherwise
// the first load will result on 0 prices.
const _prices = Object.keys(prices).length === 0 ? await QueryAndMergePrices() : prices;
setPrices(_prices);

// Query selectedChain alliances info
const _allianceAssetRes = await QueryAlliances(selectedChain).catch(() => []);

const priceResult = await fetch("https://pisco-price-server.terra.dev/latest");
const pillJson = await priceResult.json();
setPillPrices(Object.fromEntries(pillJson.prices.map((p:any) => {
return [p.denom, {
usd: p.price
}]
})))
setLoading(false);
}
// Query chain info on parallel to speed up the loading time
let chainInfoRes = await Promise.all([
LCD.alliance.params(selectedChain.id),
LCD.bank.supplyByDenom(selectedChain.id, selectedChain.bondDenom),
GetInflationEndpoint(selectedChain.id),
]).catch((e) => {
console.error(e)
return []
});

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

try {
const resp = await QueryForAlliances(chain);
response = [...resp.alliances];
} catch {
response = [...[]];
}
selectedChain.allianceCoins = allianceCoins;

setData(response);
})();
}, [params]);
// If no error occured, set the data
// otherwise, keep the default data
if (chainInfoRes != undefined) {
let tableState = new TableState(
selectedChain,
_allianceAssetRes,
_prices,
chainInfoRes[0].params,
chainInfoRes[1],
inflation,
);
setTableState(tableState)
}
setIsLoading(false);
})();
}
}, [selectedChain]);

return (
<section className="w-full flex-col">
Expand All @@ -69,26 +102,19 @@ export default function Home() {
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>.
</h3>
</div>
<div className="flex flex-col pt-3 pb-3 mt-12 overflow-auto">
<LoadingComponent isLoading={loading} values={pillPrices}>
<div className="flex gap-3">{pillPrices && pills.map((pill) => <Pill key={pill.id} pill={pill} data={pillPrices[pill.token]} />)}</div>
</LoadingComponent>
<div className="flex flex-col pt-3 mt-12 overflow-auto">
<div className="flex gap-3">
{Kpis.map((kpi) => <Kpi key={kpi.id} kpi={kpi} data={prices[kpi.token]} />)}
</div>
</div>
<div className="flex w-full flex-col lg:flex-row gap-3">
<div className="w-full lg:w-6/6">
<Card name="Assets">
<Suspense fallback={<CSSLoader />}>
<Table usdValues={usdValues} values={data} />
<Table tableState={tableState} isLoading={isLoading} />
</Suspense>
</Card>
</div>
{/* <div className="w-full lg:w-2/6">
<Card name="Overview" className="flex flex-col items-center overflow-auto">
<Suspense fallback={<CSSLoader />}>
<Graph values={data} />
</Suspense>
</Card>
</div> */}
</div>
</section>
);
Expand Down
2 changes: 1 addition & 1 deletion components/CSSLoader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import styles from '@/styles/CSSLoader.module.css';
import styles from '../styles/CSSLoader.module.css';

const CSSLoader = () => {
return (
Expand Down
2 changes: 1 addition & 1 deletion components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function Card({
<div className="flex justify-between mb-6 pr-4 pl-4">
<h1 className="text-3xl font-medium">{name}</h1>
</div>
<div className={'pr-4 pl-4 pb-3 pt-3 max-h-80 overflow-auto'}>
<div className={'pr-4 pl-4 pb-3 pt-3 overflow-auto'}>
{children}
</div>
</div>
Expand Down
31 changes: 19 additions & 12 deletions components/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
"use client";

import { useState } from "react";
import styles from "@/styles/Dropdown.module.css";
import { supportedChains, defaultChain } from "@/const/Variables";
import { useEffect, useState } from "react";
import styles from "../styles/Dropdown.module.css";
import { useRouter, useSearchParams } from "next/navigation";
import { DEFAULT_CHAIN, SUPPORTED_CHAINS } from "../const/chains";

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

useEffect(() => {
if (SUPPORTED_CHAINS[params.get("selected") as any]) {
setSelected(SUPPORTED_CHAINS[params.get("selected") as string]);
}
else {
setSelected(SUPPORTED_CHAINS[DEFAULT_CHAIN]);
}
}, [])

return (
<div
className={styles.select}
Expand All @@ -30,24 +39,22 @@ export default function Dropdown() {
<div className={styles.selected}>
<img src={selected.icon} alt={"selected"} width={30} height={30} />
<span>{selected.name}</span>
<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">
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
</svg>
<img src="/images/arrow-down.svg" alt="arrow" width={24} height={24} />
</div>
)}
<div id={"dropdown"} className={`${show ? "" : styles.reversed} hidden`}>
<ul>
{Object.keys(supportedChains).map((chain) => (
{Object.keys(SUPPORTED_CHAINS).map((chain) => (
<li
key={chain}
className="flex gap-3 p-3 items-center"
onClick={() => {
setSelected(supportedChains[chain]);
router.push(`?selected=${supportedChains[chain].name.toLocaleLowerCase()}`);
setSelected(SUPPORTED_CHAINS[chain]);
router.push(`?selected=${SUPPORTED_CHAINS[chain].id}`);
}}
>
<img src={supportedChains[chain].icon} alt={chain} width={30} height={30} />
<span>{supportedChains[chain].name}</span>
<img src={SUPPORTED_CHAINS[chain].icon} alt={chain} width={30} height={30} />
<span>{SUPPORTED_CHAINS[chain].name}</span>
</li>
))}
</ul>
Expand Down
7 changes: 0 additions & 7 deletions components/Footer.tsx

This file was deleted.

80 changes: 0 additions & 80 deletions components/Graph.tsx

This file was deleted.

34 changes: 34 additions & 0 deletions components/Kpi.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Tooltip } from '@nextui-org/react';
import LoadingComponent from './LoadingComponent';

export interface KpiData {
id: number;
name: string;
symbol: string;
token: string;
}

export default function Kpi({ kpi, data }: { kpi: KpiData, data?: any }) {
return (
<div className="flex custom_card gap-3">
<LoadingComponent isLoading={data === undefined} values={data}>
<div className='flex items-center'>
<Tooltip content={kpi.name}>
<img
src={kpi.symbol}
alt='Coin image'
width={45}
height={45}
className="object-contain"
/>
</Tooltip>
</div>
<div className="flex items-center w-full">
<h2>${parseFloat(data?.usd ?? '0').toLocaleString('en-US', {
minimumFractionDigits: 4
})}</h2>
</div>
</LoadingComponent>
</div>
);
}
Loading

0 comments on commit fed3018

Please sign in to comment.