Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add whale alliance hub #14

Merged
merged 7 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -80,26 +113,19 @@ export default function Home() {
{" "}Alliance assets on Terra
</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
Loading