diff --git a/.env.example b/.env.example index 0a3e03d..4a4032e 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -ONEINCH_API_KEY="" \ No newline at end of file +ONEINCH_API_KEY="" +WEB3_API_URL="" \ No newline at end of file diff --git a/app/api/prices/route.ts b/app/api/prices/route.ts new file mode 100644 index 0000000..798f0e3 --- /dev/null +++ b/app/api/prices/route.ts @@ -0,0 +1,46 @@ +import { type NextRequest } from 'next/server' + +export const runtime = 'edge' + +const apiUrl = process.env.WEB3_API_URL + +const config: RequestInit = { + headers: { + 'content-type': 'application/json' + }, + method: 'GET', + next: { revalidate: 60 } +} + +export async function GET(request: NextRequest) { + const searchParams = request?.nextUrl?.searchParams + const tokens = searchParams?.get('tokens') + + if (!tokens) { + return Response.json(null, { status: 400 }) + } + + const url = `${apiUrl}/prices/?tokens=${tokens}` + let data + let status + + try { + const res = await fetch(url, config) + const json = await res.json() + + data = json + status = res.status + } catch (error: unknown) { + console.error((error as Error).message) + data = null + status = 500 + } + + return new Response(JSON.stringify(data), { + status, + headers: { + 'Content-Type': 'application/json', + 'Cache-Control': 'public, s-maxage=10' + } + }) +} diff --git a/app/globals.css b/app/globals.css index 698772a..4eba5d2 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,5 +1,5 @@ :root { - --max-width: 800px; + --max-width: 740px; --border-radius: 0.3rem; --foreground-rgb: 20, 20, 20; @@ -27,6 +27,8 @@ html, body { max-width: 100vw; overflow-x: hidden; + font-family: var(--font-firaCode); + text-rendering: optimizeLegibility; } body { @@ -39,6 +41,9 @@ body { rgb(var(--background-start-rgb)); line-height: 1.5; min-height: 100vh; + display: flex; + flex-direction: column; + padding: 2rem; } a { @@ -46,6 +51,11 @@ a { text-decoration: none; } +ul { + list-style: none; + padding: 0; +} + @media (prefers-color-scheme: dark) { html { color-scheme: dark; diff --git a/app/layout.tsx b/app/layout.tsx index 4a00b66..9993651 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,11 +2,11 @@ import type { Metadata } from 'next' import { Fira_Code } from 'next/font/google' import './globals.css' -const firaCode = Fira_Code({ subsets: ['latin'] }) +const firaCode = Fira_Code({ subsets: ['latin'], variable: '--font-firaCode' }) export const metadata: Metadata = { - title: 'OCEAN + AGIX + FET = ASI', - description: 'Calculate how much ASI you get for your OCEAN, AGIX, or FET.' + title: 'ASI Calculator', + description: 'See how much ASI you get for your OCEAN, AGIX, or FET.' } export default function RootLayout({ diff --git a/app/page.module.css b/app/page.module.css index 3ab6606..6734b36 100644 --- a/app/page.module.css +++ b/app/page.module.css @@ -1,6 +1,5 @@ -.main, -.footer { - padding: 2rem; +.main { + flex: 1 0 auto; } .title, @@ -11,16 +10,16 @@ } .title { - margin-bottom: 0.5rem; - font-size: 2rem; + font-size: clamp(1.3rem, 10vw, 2.5rem); } .description { margin-bottom: 2rem; - font-size: 1.3rem; + font-size: clamp(1.1rem, 10vw, 1.75rem); } .footer { margin-top: 2rem; + flex-shrink: 0; font-size: 0.8rem; } diff --git a/app/page.tsx b/app/page.tsx index c7f3d18..abbca06 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,4 +1,4 @@ -import { Prices } from '@/features/Prices' +import { Prices } from '@/components/Prices' import styles from './page.module.css' import { metadata } from './layout' diff --git a/components/Content/Content.module.css b/components/Content/Content.module.css new file mode 100644 index 0000000..93e5ffc --- /dev/null +++ b/components/Content/Content.module.css @@ -0,0 +1,44 @@ +.content { + max-width: var(--max-width); + margin: auto; +} + +.content p { + margin-bottom: 1rem; +} + +.content a { + text-decoration: underline; +} + +.label { + font-size: 0.7rem; + color: rgba(var(--foreground-rgb), 0.6); + border: 1px solid rgba(var(--foreground-rgb), 0.2); + border-radius: var(--border-radius); + padding: 0.1rem 0.3rem; + margin-left: 0.2rem; + margin-right: 0.2rem; + vertical-align: middle; +} + +.calculationBase { + border: 1px solid rgba(var(--foreground-rgb), 0.2); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + border-radius: var(--border-radius); +} + +.calculationBase li { + border-bottom: 1px solid rgba(var(--foreground-rgb), 0.2); + padding: 1rem; + font-size: 0.8rem; +} + +.calculationBase li:first-child { + border-right: 1px solid rgba(var(--foreground-rgb), 0.2); +} + +.calculationBase li:last-child { + border-bottom: none; +} diff --git a/components/Content/Content.tsx b/components/Content/Content.tsx new file mode 100644 index 0000000..50d17f3 --- /dev/null +++ b/components/Content/Content.tsx @@ -0,0 +1,41 @@ +import { ratioOceanToAsi, ratioAgixToAsi, ratioFetToAsi } from '@/constants' +import styles from './Content.module.css' + +type Props = { + prices: { + [key: string]: number + } +} + +export function Content({ prices }: Props) { + return ( +
+ Calculations are based on the{' '} + + fixed ASI exchange rate + + , the fluctuating fiat values fetched from Coingecko, and + token swap quotes from 1inch. +
+ +- 1 OCEAN = {ratioOceanToAsi} ASI (fixed) = ${priceOcean} -
-- 1 AGIX = {ratioAgixToAsi} ASI (fixed) = ${priceAgix} -
-1 Fet = 1 ASI (fixed) = ${priceAsi}
-