diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..1380c2e --- /dev/null +++ b/.eslintignore @@ -0,0 +1,2 @@ +node_modules +.next \ No newline at end of file diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..c4e3711 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,44 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + } + }, + "env": { + "browser": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "plugin:jsx-a11y/recommended" + ], + "rules": { + "@typescript-eslint/explicit-module-boundary-types": "off", + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/ban-ts-ignore": "off", + "jsx-a11y/label-has-associated-control": [ + "error", + { + "labelComponents": [], + "labelAttributes": [], + "controlComponents": [], + "assert": "either", + "depth": 25 + } + ], + "@typescript-eslint/no-explicit-any": "off" + }, + "settings": { + "react": { + "version": "detect" + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1944bd --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +// .gitnignore +# next.js build output +.next +# dotenv environment variables file +.env +.env.build +# Dependency directories +node_modules/ +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +.DS_Store \ No newline at end of file diff --git a/.pretterrc b/.pretterrc new file mode 100644 index 0000000..5012c79 --- /dev/null +++ b/.pretterrc @@ -0,0 +1,4 @@ +{ + "proseWrap": "always", + "jsxBracketSameLine": true +} \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..1380c2e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules +.next \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..14cf2c8 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# The Web3 Index + +The Web3 Index provides usage data across the web3 stack with an initial focus on middleware network revenue. + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. + +[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. + +The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. diff --git a/components/Box/index.tsx b/components/Box/index.tsx new file mode 100644 index 0000000..ba5074c --- /dev/null +++ b/components/Box/index.tsx @@ -0,0 +1,8 @@ +import { styled } from "../../stitches.config"; + +const Box = styled("div", { + // Reset + boxSizing: "border-box", +}); + +export default Box; diff --git a/components/Button/index.tsx b/components/Button/index.tsx new file mode 100644 index 0000000..fe5eaa9 --- /dev/null +++ b/components/Button/index.tsx @@ -0,0 +1,21 @@ +import { styled } from "../../stitches.config"; + +const StyledButton = styled("button", { + // Reset + boxSizing: "border-box", + border: 0, + borderRadius: "$round", + fontWeight: 700, + fontSize: "$1", + backgroundColor: "$hiContrast", + color: "$loContrast", + py: "$3", + px: "$4", + cursor: "pointer", +}); + +const Button = ({ children, ...props }) => ( + {children} +); + +export default Button; diff --git a/components/CallToAction/index.tsx b/components/CallToAction/index.tsx new file mode 100644 index 0000000..f041e99 --- /dev/null +++ b/components/CallToAction/index.tsx @@ -0,0 +1,24 @@ +import Box from "../Box"; +import Button from "../Button"; +import { PlusCircledIcon } from "@modulz/radix-icons"; +import { styled } from "../../stitches.config"; + +const StyledIcon = styled(PlusCircledIcon, { + ml: "$2", +}); + +const CallToAction = ({ ...props }) => { + return ( + + + Help Grow the Index + + + + ); +}; + +export default CallToAction; diff --git a/components/Container/index.tsx b/components/Container/index.tsx new file mode 100644 index 0000000..c39ef55 --- /dev/null +++ b/components/Container/index.tsx @@ -0,0 +1,36 @@ +import { styled, StitchesCss, StitchesVariants } from "../../stitches.config"; + +export type ContainerProps = StitchesCss; +export type ContainerVariants = StitchesVariants; + +const Container = styled("div", { + // Reset + boxSizing: "border-box", + flexShrink: 0, + + // Custom + mx: "auto", + px: "$4", + + variants: { + size: { + "1": { + maxWidth: "430px", + }, + "2": { + maxWidth: "715px", + }, + "3": { + maxWidth: "1145px", + }, + "4": { + maxWidth: "1400px", + }, + "5": { + maxWidth: "none", + }, + }, + }, +}); + +export default Container; diff --git a/components/Faq/index.tsx b/components/Faq/index.tsx new file mode 100644 index 0000000..8ccaca6 --- /dev/null +++ b/components/Faq/index.tsx @@ -0,0 +1,137 @@ +import Box from "../Box"; +import * as Accordion from "@radix-ui/react-accordion"; +import { ChevronDownIcon } from "@modulz/radix-icons"; +import { styled } from "../../stitches.config"; +import { filterCssFromProps } from "../../lib/utils"; + +const AccordionChevron = styled(ChevronDownIcon, { + transition: "transform 300ms", + "[data-state=open] &": { + transform: "rotate(180deg)", + }, +}); + +const Item = ({ emoji, question, answer }) => ( + + + + + + + {emoji} + + + {question} + + + + + + {answer} + + +); + +const items = [ + { + emoji: "🌐", + question: "What is Web3?", + answer: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur.`, + }, + { + emoji: "πŸ€™πŸ»", + question: "What’s the purpose of The Web3 Index?", + answer: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur.`, + }, + + { + emoji: "πŸ”’", + question: "How do we calculate total participant revenue (TPR)?", + answer: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur.`, + }, + { + emoji: "🀘🏻", + question: "How do I get involved in Web3?", + answer: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur.`, + }, + { + emoji: "βœ…", + question: "How do I get my project listed on The Web3 Index?", + answer: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim +veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea +commodo consequat. Duis aute irure dolor in reprehenderit in voluptate +velit esse cillum dolore eu fugiat nulla pariatur.`, + }, +]; +const Faq = ({ ...props }) => { + return ( + + + {items.map((item, i) => ( + + ))} + + + ); +}; + +export default Faq; diff --git a/components/Footer/index.tsx b/components/Footer/index.tsx new file mode 100644 index 0000000..0ef2153 --- /dev/null +++ b/components/Footer/index.tsx @@ -0,0 +1,48 @@ +import Box from "../Box"; +import { TwitterLogoIcon, GitHubLogoIcon } from "@modulz/radix-icons"; +import ThemeToggle from "../ThemeToggle"; + +const Footer = ({ ...props }) => { + return ( + + + + + Twitter + + + + Github + + + + Discord + + + + The Web3 Indexβ„’. All rights reserved + + + + + + ); +}; + +export default Footer; diff --git a/components/Header/Revenue.tsx b/components/Header/Revenue.tsx new file mode 100644 index 0000000..6fd1e62 --- /dev/null +++ b/components/Header/Revenue.tsx @@ -0,0 +1,36 @@ +import Box from "../Box"; +import RevenueChange from "../RevenueChange"; + +const Revenue = ({ revenue = 0, percentChange = 0 }) => { + return ( + + Revenue (7 day) + + $ + {Intl.NumberFormat("en-US", { + maximumFractionDigits: 2, + }).format(revenue)} + + + + ); +}; + +export default Revenue; diff --git a/components/Header/index.tsx b/components/Header/index.tsx new file mode 100644 index 0000000..3f94125 --- /dev/null +++ b/components/Header/index.tsx @@ -0,0 +1,59 @@ +import Box from "../Box"; +import Revenue from "./Revenue"; +import SubmitButton from "../SubmitButton"; +import { styled } from "../../stitches.config"; + +const StyledSubmitButton = styled(SubmitButton, { + flex: "1 0 auto", + backgroundColor: "$loContrast", + color: "$hiContrast", + border: "1px solid", + borderColor: "$border", +}); + +const Header = ({ revenue, ...props }) => { + return ( + + + The Web3 Index + + + + + + + + ); +}; + +export default Header; diff --git a/components/LineAndBarGraph/index.tsx b/components/LineAndBarGraph/index.tsx new file mode 100644 index 0000000..936e3cc --- /dev/null +++ b/components/LineAndBarGraph/index.tsx @@ -0,0 +1,91 @@ +import { useEffect, useRef, useState } from "react"; +import { defaultTheme } from "../../stitches.config"; +import Box from "../Box"; + +const LineAndBarGraph = ({ color, days }) => { + const chartRef = useRef(null); + const [chartCreated, setChartCreated] = useState(null); + useEffect(() => { + if (!chartCreated) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { createChart } = require("lightweight-charts"); + const chart = createChart(chartRef.current, { + width: 700, + height: 400, + layout: { + backgroundColor: "transparent", + textColor: "#fff", + }, + rightPriceScale: { + scaleMargins: { + top: 0.2, + bottom: 0, + }, + borderVisible: false, + }, + timeScale: { + borderVisible: false, + }, + grid: { + horzLines: { + color: "rgba(197, 203, 206, 0.5)", + visible: false, + }, + vertLines: { + color: "rgba(197, 203, 206, 0.5)", + visible: false, + }, + }, + crosshair: { + horzLine: { + visible: false, + labelVisible: false, + }, + vertLine: { + visible: true, + style: 0, + width: 2, + color: "rgba(32, 38, 46, 0.1)", + labelVisible: false, + }, + }, + }); + + const lineSeries = chart.addLineSeries({ + priceLineVisible: false, + lastValueVisible: false, + lineStyle: 0, + lineWidth: 2, + color: color.rgba, + crosshairMarkerVisible: false, + }); + + const volumeSeries = chart.addHistogramSeries({ + scaleMargins: { + top: 0.32, + bottom: 0, + }, + lineColor: color.rgba, + color: color.rgba, + priceFormat: { + type: "volume", + }, + }); + + // parse the data and format for tradingview consumption + const formattedData = days.map((day) => { + return { + time: new Date(day.date * 1000).toLocaleDateString("fr-CA"), + value: day.revenue, + }; + }); + //lineSeries.setData(formattedData); + volumeSeries.setData(formattedData); + setChartCreated(chartCreated); + } + }, [chartCreated]); + + return ; +}; + +export default LineAndBarGraph; diff --git a/components/LineGraph/index.tsx b/components/LineGraph/index.tsx new file mode 100644 index 0000000..ce5e609 --- /dev/null +++ b/components/LineGraph/index.tsx @@ -0,0 +1,77 @@ +import { useEffect, useRef, useState } from "react"; +import { defaultTheme } from "../../stitches.config"; +import Box from "../Box"; + +const LineGraph = ({ days }) => { + const chartRef = useRef(null); + const [chartCreated, setChartCreated] = useState(null); + useEffect(() => { + if (!chartCreated) { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { createChart } = require("lightweight-charts"); + const chart = createChart(chartRef.current, { width: 56, height: 24 }); + chart.applyOptions({ + scales: { + xAxis: { + visible: false, + }, + }, + priceScale: { + position: "none", + drawTicks: false, + borderVisible: false, + }, + grid: { + vertLines: { + visible: false, + }, + horzLines: { + visible: false, + }, + }, + layout: { + backgroundColor: "transparent", + textColor: "transparent", + }, + handleScroll: false, + handleScale: false, + timeScale: { + visible: false, + }, + crosshair: { + vertLine: { + visible: false, + labelVisible: false, + }, + horzLine: { + visible: false, + labelVisible: false, + }, + }, + }); + + const lineSeries = chart.addLineSeries({ + priceLineVisible: false, + lastValueVisible: false, + lineStyle: 0, + lineWidth: 2, + color: defaultTheme.colors.green, + crosshairMarkerVisible: false, + }); + + // parse the data and format for tradingview consumption + const formattedData = days.map((day) => { + return { + time: day.date * 1000, + value: day.revenue, + }; + }); + lineSeries.setData(formattedData); + setChartCreated(chartCreated); + } + }, [chartCreated]); + + return ; +}; + +export default LineGraph; diff --git a/components/ProjectHeader/index.tsx b/components/ProjectHeader/index.tsx new file mode 100644 index 0000000..cddb0c4 --- /dev/null +++ b/components/ProjectHeader/index.tsx @@ -0,0 +1,16 @@ +import Box from "../Box"; + +const ProjectHeader = ({ color, ...props }) => { + return ( + + ); +}; + +export default ProjectHeader; diff --git a/components/RevenueChange/index.tsx b/components/RevenueChange/index.tsx new file mode 100644 index 0000000..ee1bbd7 --- /dev/null +++ b/components/RevenueChange/index.tsx @@ -0,0 +1,26 @@ +import Box from "../Box"; +import { filterCssFromProps } from "../../lib/utils"; + +const RevenueChange = ({ percentChange, ...props }) => { + return ( + + + {percentChange}% + + ); +}; + +export default RevenueChange; diff --git a/components/Section/index.tsx b/components/Section/index.tsx new file mode 100644 index 0000000..af39c1a --- /dev/null +++ b/components/Section/index.tsx @@ -0,0 +1,32 @@ +import { styled, StitchesCss, StitchesVariants } from "../../stitches.config"; + +export type SectionProps = StitchesCss; +export type SectionVariants = StitchesVariants; + +const Section = styled("section", { + // Reset + boxSizing: "border-box", + flexShrink: 0, + "::before": { + boxSizing: "border-box", + }, + "::after": { + boxSizing: "border-box", + }, + + variants: { + size: { + "1": { + py: "$3", + }, + "2": { + py: "$7", + }, + "3": { + py: "$9", + }, + }, + }, +}); + +export default Section; diff --git a/components/SubmitButton/index.tsx b/components/SubmitButton/index.tsx new file mode 100644 index 0000000..6d17e94 --- /dev/null +++ b/components/SubmitButton/index.tsx @@ -0,0 +1,21 @@ +import Button from "../Button"; +import { PlusCircledIcon } from "@modulz/radix-icons"; +import { styled } from "../../stitches.config"; + +const StyledIcon = styled(PlusCircledIcon, { + ml: "$2", +}); + +const SubmitButton = ({ ...props }) => { + return ( + + ); +}; + +export default SubmitButton; diff --git a/components/Table/index.tsx b/components/Table/index.tsx new file mode 100644 index 0000000..2b859b0 --- /dev/null +++ b/components/Table/index.tsx @@ -0,0 +1,166 @@ +import { useTable, useSortBy } from "react-table"; +import Box from "../Box"; +import Section from "../Section"; +import RevenueChange from "../RevenueChange"; +import LineGraph from "../LineGraph"; +import { ChevronDownIcon, ChevronUpIcon } from "@modulz/radix-icons"; +import { styled } from "../../stitches.config"; +import Link from "next/link"; + +const Table = ({ columns, data, ...props }) => { + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + } = useTable( + { + columns, + data, + initialState: { + hiddenColumns: ["image", "usage", "slug"], + }, + }, + useSortBy + ); + + // We don't want to render all 2000 rows for this example, so cap + // it at 20 for this use case + const firstPageRows = rows.slice(0, 20); + + return ( +
+ + + {headerGroups.map((headerGroup, i) => ( + + {headerGroup.headers.map((column, i) => ( + + + + {column.render("Header")} + + {/* Add a sort direction indicator */} + + {column.isSorted ? ( + column.isSortedDesc ? ( + + ) : ( + + ) + ) : ( + "" + )} + + + + ))} + + ))} + + + {firstPageRows.map((row, rowIndex) => { + prepareRow(row); + return ( + + {row.cells.map((cell, i) => { + return ( + + {renderSwitch(cell)} + + ); + })} + + ); + })} + + +
+ ); +}; + +const StyledImage = styled("img", { + mr: "$3", +}); + +function renderSwitch(cell) { + switch (cell.column.id) { + case "revenue": { + return `$${Math.round( + cell.row.values.usage.revenue.oneWeekTotal + ).toLocaleString()}`; + } + case "percentChange": { + return ( + + + + + ); + } + case "name": + return ( + + + + {cell.render("Cell")} + + + ); + default: + return cell.render("Cell"); + } +} + +export default Table; diff --git a/components/ThemeToggle/index.tsx b/components/ThemeToggle/index.tsx new file mode 100644 index 0000000..e6994d2 --- /dev/null +++ b/components/ThemeToggle/index.tsx @@ -0,0 +1,62 @@ +import { useTheme } from "next-themes"; +import Box from "../../components/Box"; + +const ThemeToggle = ({ ...props }) => { + const { resolvedTheme, setTheme } = useTheme(); + return ( + + resolvedTheme === "dark" ? setTheme("light") : setTheme("dark") + } + {...props} + > + + + + + + + + + + + ); +}; + +export default ThemeToggle; diff --git a/components/Ticker/index.tsx b/components/Ticker/index.tsx new file mode 100644 index 0000000..d78c3d4 --- /dev/null +++ b/components/Ticker/index.tsx @@ -0,0 +1,75 @@ +import Box from "../Box"; +import RevenueChange from "../RevenueChange"; +import Marquee from "react-fast-marquee"; +import { useTheme } from "next-themes"; +import LineGraph from "../LineGraph"; + +const Project = ({ project }) => { + return ( + + + {project.symbol} + {project.name} + + + + + + + ); +}; + +const Ticker = ({ projects }) => { + const { resolvedTheme } = useTheme(); + + return ( + + + + + + + + + {/* {projects.map((project, i) => ( + + ))} */} + + + ); +}; + +export default Ticker; diff --git a/layouts/index.tsx b/layouts/index.tsx new file mode 100644 index 0000000..448ed8b --- /dev/null +++ b/layouts/index.tsx @@ -0,0 +1,29 @@ +import Box from "../components/Box"; +import Section from "../components/Section"; +import Container from "../components/Container"; +import Ticker from "../components/Ticker"; +import Footer from "../components/Footer"; + +const Index = ({ data, children }) => { + const { projects } = data; + + return ( + + + {children} +
+ +
+ +
+
+ ); +}; + +export default Index; diff --git a/lib/utils.ts b/lib/utils.ts new file mode 100644 index 0000000..70192f6 --- /dev/null +++ b/lib/utils.ts @@ -0,0 +1,139 @@ +import { request, gql } from "graphql-request"; +import Ajv from "ajv"; +import schema from "../schema.json"; +import registry from "../registry.json"; + +export const getBlocksFromTimestamps = async (timestamps) => { + if (!timestamps?.length) { + return []; + } + const blocks = []; + for (const timestamp of timestamps) { + const json = await request( + "https://api.thegraph.com/subgraphs/name/blocklytics/ethereum-blocks", + gql` + query blocks($timestampFrom: Int!, $timestampTo: Int!) { + blocks( + first: 1 + orderBy: timestamp + orderDirection: asc + where: { timestamp_gt: $timestampFrom, timestamp_lt: $timestampTo } + ) { + id + number + timestamp + } + } + `, + { timestampFrom: timestamp, timestampTo: timestamp + 600 } + ); + blocks.push(+json.blocks[0].number); + } + + return blocks; +}; + +/** + * gets the amount difference plus the % change in change itself (second order change) + * @param {*} valueNow + * @param {*} valueAsOfPeriodOne + * @param {*} valueAsOfPeriodTwo + */ +export const getTwoPeriodPercentChange = ( + valueNow: number, + valueAsOfPeriodOne: number, + valueAsOfPeriodTwo: number +) => { + // get volume info for both 24 hour periods + const currentChange = valueNow - valueAsOfPeriodOne; + const previousChange = valueAsOfPeriodOne - valueAsOfPeriodTwo; + + const adjustedPercentChange = + ((currentChange - previousChange) / previousChange) * 100; + if (isNaN(adjustedPercentChange) || !isFinite(adjustedPercentChange)) { + return [currentChange, 0]; + } + return [currentChange, adjustedPercentChange]; +}; + +export const filterCssFromProps = (props) => { + const p = Object.fromEntries( + Object.entries(props).filter(([key]) => key !== "css") + ); + return p; +}; + +export const getProjects = async () => { + const ajv = new Ajv(); + const validate = ajv.compile(schema); + + const projects = []; + let totalParticipantRevenueNow = 0; + let totalParticipantRevenueOneWeekAgo = 0; + let totalParticipantRevenueTwoWeeksAgo = 0; + + for (const project in registry) { + let data; + if (registry[project].includes("web3index.org")) { + const { getProject } = await import(`../pages/api/${project}`); + data = await getProject(); + } else { + const res = await fetch(registry[project]); + data = await res.json(); + } + const valid = validate(data); + + if (valid) { + const [oneWeekTotal, oneWeekPercentChange] = getTwoPeriodPercentChange( + data.usage.revenue.now, + data.usage.revenue.oneWeekAgo, + data.usage.revenue.twoWeeksAgo + ); + + projects.push({ + ...data, + slug: project, + usage: { + ...data.usage, + revenue: { + ...data.usage.revenue, + oneWeekTotal, + oneWeekPercentChange, + }, + }, + }); + totalParticipantRevenueNow += data.usage.revenue.now; + totalParticipantRevenueOneWeekAgo += data.usage.revenue.oneWeekAgo; + totalParticipantRevenueTwoWeeksAgo += data.usage.revenue.twoWeeksAgo; + } + } + + const [oneWeekTotal, oneWeekPercentChange] = getTwoPeriodPercentChange( + totalParticipantRevenueNow, + totalParticipantRevenueOneWeekAgo, + totalParticipantRevenueTwoWeeksAgo + ); + + return { + projects, + revenue: { + totalParticipantRevenueNow, + totalParticipantRevenueOneWeekAgo, + totalParticipantRevenueTwoWeeksAgo, + oneWeekTotal, + oneWeekPercentChange, + }, + }; +}; + +export const getProjectBySlug = async (slug) => { + let data; + if (registry[slug].includes("web3index.org")) { + const { getProject } = await import(`../pages/api/${slug}`); + data = await getProject(); + } else { + const res = await fetch(registry[slug]); + data = await res.json(); + } + return data; +}; diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..7b7aa2c --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/package.json b/package.json new file mode 100644 index 0000000..a177efa --- /dev/null +++ b/package.json @@ -0,0 +1,59 @@ +{ + "name": "web3index-org", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "next", + "build": "next build", + "start": "next start", + "type-check": "tsc", + "lint-fix": "yarn prettier-fix && yarn eslint-fix", + "eslint-fix": "eslint . --ext js,jsx,ts,tsx --fix --max-warnings=0", + "prettier-fix": "prettier --write '**/*.{ts,js,css,html,md,tsx,mdx,graphql,gql}'" + }, + "dependencies": { + "@modulz/radix-icons": "^4.0.0", + "@radix-ui/react-accordion": "^0.0.13", + "@stitches/core": "latest", + "@stitches/react": "latest", + "ajv": "^7.2.1", + "dayjs": "^1.10.4", + "fast-average-color-node": "^1.0.3", + "graphql": "^15.5.0", + "graphql-request": "^3.4.0", + "lightweight-charts": "^3.3.0", + "next": "^10.0.9", + "next-themes": "^0.0.14", + "react": "17.0.1", + "react-dom": "17.0.2", + "react-fast-marquee": "^1.1.2", + "react-table": "^7.6.3" + }, + "devDependencies": { + "@types/node": "^14.14.33", + "@types/react": "^17.0.3", + "@types/react-dom": "^17.0.2", + "@typescript-eslint/eslint-plugin": "^4.17.0", + "@typescript-eslint/parser": "^4.17.0", + "eslint": "^7.21.0", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-prettier": "^3.3.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0", + "husky": "^4.2.5", + "lint-staged": "^10.5.4", + "prettier": "^2.2.1", + "typescript": "^4.2.3" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "*.{ts,js,css,html,tsx,gql,graphql}": [ + "eslint --fix", + "prettier --write" + ] + } +} diff --git a/pages/_app.tsx b/pages/_app.tsx new file mode 100644 index 0000000..703a5ec --- /dev/null +++ b/pages/_app.tsx @@ -0,0 +1,56 @@ +import { darkThemeClass, global } from "../stitches.config"; +import { ThemeProvider } from "next-themes"; + +const globalStyles = global({ + body: { + margin: 0, + backgroundColor: "$loContrast", + color: "$hiContrast", + }, + + "body, button": { + fontFamily: "$sans", + }, + + svg: { display: "block" }, + + pre: { margin: 0 }, + + "::selection": { + backgroundColor: "$violet300", + }, + + "@font-face": [ + { + fontFamily: "Whyte Inktrap", + src: + 'url("/fonts/whyte/inktrap-bold.woff2") format("woff2"), url("/fonts/whyte/inktrap-bold.woff") format("woff")', + fontWeight: 700, + fontStyle: "normal", + }, + { + fontFamily: "Whyte Inktrap", + src: + 'url("/fonts/whyte/inktrap-heavy.woff2") format("woff2"), url("/fonts/whyte/inktrap-heavy.woff") format("woff")', + fontWeight: 900, + fontStyle: "normal", + }, + ], +}); + +const App = ({ Component, pageProps }) => { + globalStyles(); + + return ( + + + + ); +}; + +export default App; diff --git a/pages/_document.tsx b/pages/_document.tsx new file mode 100644 index 0000000..408c44e --- /dev/null +++ b/pages/_document.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import NextDocument, { Html, Head, Main, NextScript } from "next/document"; +import { getCssString } from "../stitches.config"; + +export default class Document extends NextDocument { + render() { + return ( + + +