Skip to content

Commit

Permalink
feat(home): stats section (#9)
Browse files Browse the repository at this point in the history
* feat: partners carousel

* fixup

* feat(home): hero section

* refactor: cleaner file structure

* fix: formatting

* fixup

* fixup

* feat: add reusable text component

* rename carousel to ticker

* fix: format

* feat(home): technology section

* fixup

* fixup

* feat(home): stats section

* fixup

* fixup

* fix: hero image size

* refine revalidation of stats

* remove catch

* fixup
  • Loading branch information
dohaki authored Feb 14, 2024
1 parent 9a35942 commit 65ca23c
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 3 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
"dependencies": {
"@headlessui/react": "^1.7.18",
"next": "14.1.0",
"numeral": "^2.0.6",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.2.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/numeral": "^2.0.5",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/app/(routes)/_components/hero-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { INFORMATION_LINKS } from "../../_constants";
export function HeroSection() {
return (
<Hero>
<div className="container mx-auto flex flex-col items-center gap-16 px-4 pb-16 pt-8 md:flex-row-reverse">
<div className="container mx-auto flex flex-col items-center gap-16 px-4 pb-16 pt-8 md:flex-row-reverse md:gap-0 md:pr-2">
<div className="flex max-w-80 flex-1 sm:max-w-100 md:max-w-full">
<Image src={landingHeroSrc} alt="Across protocol diagram" priority={true} />
</div>
Expand Down
73 changes: 73 additions & 0 deletions src/app/(routes)/_components/stats-section.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { Text } from "../../_components/text";
import { StatBox } from "../../_components/stat-box";

import { getProtocolStats } from "../../_lib/scraper";
import { humanReadableNumber } from "../../_lib/format";

async function getFormattedStatsData() {
const protocolStats = await getProtocolStats({
revalidate: 24 * 60 * 60, // Update once a day
});

return {
totalVolumeUsd: `$${humanReadableNumber(protocolStats.totalVolumeUsd)}`,
totalDeposits: `${humanReadableNumber(protocolStats.totalDeposits)}`,
avgFillTimeInMinutes: `${protocolStats.avgFillTimeInMinutes < 1 ? "<" : ""} ${Math.max(
protocolStats.avgFillTimeInMinutes,
1,
)}m`,
bridgeFee: "<$1",
};
}

export async function StatsSection() {
const formattedStatsData = await getFormattedStatsData();
return (
<section className="container mx-auto flex flex-col gap-10 px-4 sm:gap-16">
<div className="flex flex-col items-center gap-4">
<Text variant="cap-case" className="text-aqua-100 md:text-center">
power in originality
</Text>
<Text variant="heading-2" className="text-center capitalize text-light-200">
Production ready <br />
Empirically Proven
</Text>
<Text className="max-w-xl text-center">
Across is the only cross-chain intents protocol in production today, enabling
the fastest and lowest-cost interoperability solution without security
tradeoffs.
</Text>
</div>
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatBox
title="volume"
value={formattedStatsData.totalVolumeUsd}
titleClassName="text-teal-100"
className="group-hover:border-teal-100"
dividerClassName="group-hover:bg-teal-100/[.5]"
/>
<StatBox
title="transaction"
value={formattedStatsData.totalDeposits}
titleClassName="text-orange-100"
className="group-hover:border-orange-100"
dividerClassName="group-hover:bg-orange-100/[.5]"
/>
<StatBox
title="avg. fill time"
value={formattedStatsData.avgFillTimeInMinutes}
titleClassName="text-purple-100"
className="group-hover:border-purple-100"
dividerClassName="group-hover:bg-purple-100/[.5]"
/>
<StatBox
title="bridge 1 eth"
value={formattedStatsData.bridgeFee}
titleClassName="text-aqua-100"
className="group-hover:border-aqua-100"
dividerClassName="group-hover:bg-aqua-100/[.5]"
/>
</div>
</section>
);
}
13 changes: 11 additions & 2 deletions src/app/(routes)/_components/technology-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ const sections = [
{
Icon: FeatherIcon,
title: "Elegant Abstraction",
body: "Intents replace explicit execution steps with implicit user outcomes, relying on a competitive network of market makers to fulfill outcomes. Cross-chain intents are a cross-chain limit order plus an action to execute.",
body: "Across connects users and applications via intents, not blockchains to other blockchains via complex or trusted message passing. Developers only need to attach a standard order to protocol actions to create seamless cross-chain experiences.",
},
{
Icon: BlocksDiagonalIcon,
title: "Modular Interoperability",
body: "Intents replace explicit execution steps with implicit user outcomes, relying on a competitive network of market makers to fulfill outcomes. Cross-chain intents are a cross-chain limit order plus an action to execute.",
body: (
<>
Cross-chain intents are powered by a modular system of 3 layers:
<ol className="list-decimal pl-6">
<li>Request for quote mechanism</li>
<li>Network of competitive market makers</li>
<li>Settlement layer to escrow user input funds, verify, and repay relayers</li>
</ol>
</>
),
},
];

Expand Down
2 changes: 2 additions & 0 deletions src/app/(routes)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Metadata } from "next";

import { HeroSection } from "./_components/hero-section";
import { TechnologySection } from "./_components/technology-section";
import { StatsSection } from "./_components/stats-section";

export const metadata: Metadata = {
title: "Home | Across Protocol",
Expand All @@ -13,6 +14,7 @@ export default function Home() {
<main className="z-0 flex min-h-screen flex-col gap-16 overflow-hidden px-4">
<HeroSection />
<TechnologySection />
<StatsSection />
</main>
);
}
45 changes: 45 additions & 0 deletions src/app/_components/stat-box.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ComponentProps } from "react";
import { twMerge } from "tailwind-merge";

import { Text } from "./text";

type Props = ComponentProps<"div"> & {
title: string;
titleClassName: string;
value: string;
dividerClassName?: string;
};

export function StatBox({
className,
title,
titleClassName,
value,
dividerClassName,
...props
}: Props) {
return (
<div className="group">
<div
className={twMerge(
"group flex flex-col rounded-2xl border border-grey-600 px-4 pb-6 transition sm:pb-10",
className,
)}
{...props}
>
<div
className={twMerge(
"mb-6 h-1 w-11 self-center rounded-b-[12px] rounded-t-none bg-grey-600 sm:mb-10",
dividerClassName,
)}
/>
<Text variant="cap-case" className={twMerge("mb-2 text-center", titleClassName)}>
{title}
</Text>
<Text variant="heading-3" className="text-center">
{value}
</Text>
</div>
</div>
);
}
15 changes: 15 additions & 0 deletions src/app/_lib/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import numeral from "numeral";

/**
* Formats a number into a human readable format
* @param num The number to format
* @returns A human readable format. I.e. 1000 -> 1K, 1001 -> 1K+
*/
export function humanReadableNumber(num: number, decimals = 0): string {
if (num <= 0) return "0";
return (
numeral(num)
.format(decimals <= 0 ? "0a" : `0.${"0".repeat(decimals)}a`)
.toUpperCase() + "+"
);
}
31 changes: 31 additions & 0 deletions src/app/_lib/scraper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
type ProtocolStatsResponse = {
totalDeposits: number;
avgFillTime: number;
totalVolumeUsd: number;
};

type ProtocolStatsFormatted = ProtocolStatsResponse & {
avgFillTimeInMinutes: number;
};

export async function getProtocolStats(
nextFetchRequestConfig?: NextFetchRequestConfig,
): Promise<ProtocolStatsFormatted> {
const response = await fetch(`https://public.api.across.to/deposits/stats`, {
next: nextFetchRequestConfig,
});
if (!response.ok) {
throw new Error(
`Failed to fetch protocol stats: ${response.status} ${response.statusText}`,
);
}
const data = await response.json();
return formatResult(data);
}

function formatResult(data: ProtocolStatsResponse) {
return {
...data,
avgFillTimeInMinutes: Math.floor(data.avgFillTime / 60),
};
}
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@
dependencies:
undici-types "~5.26.4"

"@types/numeral@^2.0.5":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.5.tgz#388e5c4ff4b0e1787f130753cbbe83d3ba770858"
integrity sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==

"@types/prop-types@*":
version "15.7.11"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
Expand Down Expand Up @@ -1806,6 +1811,11 @@ normalize-range@^0.1.2:
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==

numeral@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/numeral/-/numeral-2.0.6.tgz#4ad080936d443c2561aed9f2197efffe25f4e506"
integrity sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==

object-assign@^4.0.1, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
Expand Down

0 comments on commit 65ca23c

Please sign in to comment.