diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index 6ef9628..0000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,2 +0,0 @@ - -npx --no -- commitlint --edit ${1} diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 6b636e2..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,2 +0,0 @@ - -npx lint-staged diff --git a/components/Primitives/Button.tsx b/app/components/Button.tsx similarity index 77% rename from components/Primitives/Button.tsx rename to app/components/Button.tsx index 4dea136..4e01c1e 100644 --- a/components/Primitives/Button.tsx +++ b/app/components/Button.tsx @@ -1,8 +1,11 @@ -import React, {forwardRef} from 'react'; +'use client'; + +import React from 'react'; import {cl} from '@builtbymom/web3/utils'; -import {IconSpinner} from '@icons/IconSpinner'; -import type {ComponentPropsWithoutRef, ForwardedRef, MouseEvent, ReactElement, ReactNode} from 'react'; +import type {ComponentPropsWithoutRef, MouseEvent, ReactElement, ReactNode} from 'react'; + +import {IconSpinner} from '@/app/components/icons/IconSpinner'; export type TButtonVariant = 'filled' | 'outlined' | 'light' | 'inherit' | string; @@ -16,7 +19,7 @@ export type TButton = { export type TMouseEvent = MouseEvent & MouseEvent; -export const Button = forwardRef((props: TButton, ref: ForwardedRef): ReactElement => { +function Button(props: TButton): ReactElement { const { children, variant = 'filled', @@ -29,7 +32,6 @@ export const Button = forwardRef((props: TButton, ref: ForwardedRef)} - ref={ref} data-variant={variant} className={cl('button', rest.className)} aria-busy={isBusy} @@ -50,6 +52,6 @@ export const Button = forwardRef((props: TButton, ref: ForwardedRef ); -}); +} -Button.displayName = 'Button'; +export default Button; diff --git a/components/apps/tokenlistooor/DownloadAssetButton.tsx b/app/components/DownloadAssetButton.tsx similarity index 93% rename from components/apps/tokenlistooor/DownloadAssetButton.tsx rename to app/components/DownloadAssetButton.tsx index 504a8c2..282f3bf 100644 --- a/components/apps/tokenlistooor/DownloadAssetButton.tsx +++ b/app/components/DownloadAssetButton.tsx @@ -1,12 +1,15 @@ +'use client'; + import {toast} from 'react-hot-toast'; import {useDownloadFile} from 'hooks/useDownloadFile'; import axios from 'axios'; -import {IconDownload} from '@icons/IconDownload'; import type {AxiosResponse} from 'axios'; import type {ReactElement} from 'react'; import type {TAddress} from '@builtbymom/web3/types'; +import {IconDownload} from '@/app/components/icons/IconDownload'; + export type TDownloadAssetButtonProps = { chainId: number; address: TAddress; diff --git a/components/common/EmptyListMessage.tsx b/app/components/EmptyListMessage.tsx similarity index 98% rename from components/common/EmptyListMessage.tsx rename to app/components/EmptyListMessage.tsx index 47f4ce8..ac1e600 100644 --- a/components/common/EmptyListMessage.tsx +++ b/app/components/EmptyListMessage.tsx @@ -1,3 +1,5 @@ +'use client'; + import type {ReactElement, ReactNode} from 'react'; export function EmptyListMessage({children}: {children: ReactNode}): ReactElement { diff --git a/components/common/ImageWithFallback.tsx b/app/components/ImageWithFallback.tsx similarity index 98% rename from components/common/ImageWithFallback.tsx rename to app/components/ImageWithFallback.tsx index 3e4a9a2..a1272bd 100755 --- a/components/common/ImageWithFallback.tsx +++ b/app/components/ImageWithFallback.tsx @@ -1,3 +1,5 @@ +'use client'; + import React, {useState} from 'react'; import Image from 'next/image'; import {useUpdateEffect} from '@react-hookz/web'; diff --git a/pages/list/[list].tsx b/app/components/List.tsx old mode 100755 new mode 100644 similarity index 78% rename from pages/list/[list].tsx rename to app/components/List.tsx index 0bbfc0d..043a561 --- a/pages/list/[list].tsx +++ b/app/components/List.tsx @@ -1,9 +1,9 @@ +'use client'; + import React, {useMemo, useState} from 'react'; import {toast} from 'react-hot-toast'; import Link from 'next/link'; -import {useRouter} from 'next/router'; -import {DefaultSeo} from 'next-seo'; -import {Button} from 'components/Primitives/Button'; +import {useParams, useRouter} from 'next/navigation'; import {extend} from 'dayjs'; import dayjsDuration from 'dayjs/plugin/duration.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; @@ -11,19 +11,21 @@ import weekday from 'dayjs/plugin/weekday.js'; import * as chains from 'wagmi/chains'; import {motion} from 'framer-motion'; import {getNetwork} from '@builtbymom/web3/utils/wagmi'; -import {IconSocialGithub} from '@icons/IconSocialGithub'; import {useMountEffect} from '@react-hookz/web'; -import {DownloadAssetButton} from '@tokenlistooor/DownloadAssetButton'; -import {EmptyListMessage} from '@common/EmptyListMessage'; -import {ImageWithFallback} from '@common/ImageWithFallback'; + +import Button from './Button'; +import {IconSocialGithub} from './icons/IconSocialGithub'; import type {Variants} from 'framer-motion'; -import type {GetServerSidePropsResult, NextPageContext} from 'next'; import type {ReactElement} from 'react'; import type {TNDict} from '@builtbymom/web3/types'; import type {TExtendedChain} from '@builtbymom/web3/utils/wagmi'; import type {TTokenListItem} from '@utils/types/types'; +import {DownloadAssetButton} from '@/app/components/DownloadAssetButton'; +import {EmptyListMessage} from '@/app/components/EmptyListMessage'; +import {ImageWithFallback} from '@/app/components/ImageWithFallback'; + extend(relativeTime); extend(dayjsDuration); extend(weekday); @@ -126,7 +128,7 @@ function TokenListHero({list}: {list: TTokenListItem}): ReactElement { } function TokenListItem({item}: {item: TTokenListItem['tokens'][0]}): ReactElement { - const router = useRouter(); + const params = useParams(); const currentNetwork = useMemo((): TExtendedChain => { try { return getNetwork(item.chainId); @@ -136,7 +138,7 @@ function TokenListItem({item}: {item: TTokenListItem['tokens'][0]}): ReactElemen }, [item.chainId]); const isLogoInAssetLists = (item.logoURI || '').includes('assets.smold.app'); - const isSmolAssetsPage = router.query.list === 'smolAssets'; + const isSmolAssetsPage = params.list === 'smolAssets'; const shouldDisplayDownloadButtons = isLogoInAssetLists && isSmolAssetsPage; const downloadAssetCommonParams = { @@ -208,18 +210,19 @@ function TokenListItem({item}: {item: TTokenListItem['tokens'][0]}): ReactElemen } function TokenListContent({list}: {list: TTokenListItem}): ReactElement { + const params = useParams(); const router = useRouter(); const [currentPage, set_currentPage] = useState(1); const [itemsPerPage] = useState(50); const [search, set_search] = useState(''); const [network, set_network] = useState(-1); useMountEffect((): void => { - const {query} = router; - if (query.page) { - set_currentPage(Number(query.page)); + const {page, search} = params; + if (page) { + set_currentPage(Number(page)); } - if (query.search) { - set_search(String(query.search)); + if (search) { + set_search(String(search)); } }); @@ -263,7 +266,7 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { }, [list.tokens, search, network]); const isSearchResultEmpty = searchResult.length === 0; - const isSmolAssetsPage = router.query.list === 'smolAssets'; + const isSmolAssetsPage = params.list === 'smolAssets'; const emptyListMessage = "Oh no! Looks like we don't have that token in stock."; const smolEmptyListMessage = ( @@ -294,16 +297,11 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { set_search(e.target.value || ''); set_currentPage(1); if (!e.target.value) { - const {search, ...queryNoSearch} = router.query; + const {search, ...queryNoSearch} = params; search; - router.push({query: queryNoSearch}); + router.push(`/${queryNoSearch}`); } else { - router.push({ - query: { - ...router.query, - search: e.target.value - } - }); + router.push(`/${params.toString()}&search=${e.target.value}`); } }} /> @@ -318,16 +316,11 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { set_network(Number(e.target.value)); set_currentPage(1); if (Number(e.target.value) === -1) { - const {network, ...queryNoNetwork} = router.query; + const {network, ...queryNoNetwork} = params; network; - router.push({query: queryNoNetwork}); + router.push(`/${queryNoNetwork}`); } else { - router.push({ - query: { - ...router.query, - network: e.target.value - } - }); + router.push(`/${params.toString()}&network=${e.target.value}`); } }}> @@ -381,12 +374,7 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { onClick={(): void => { set_currentPage(1); window.scrollTo({top: 0, behavior: 'smooth'}); - router.push({ - query: { - ...router.query, - page: 1 - } - }); + router.push(`/${params.toString()}&page=1`); }}> {'◁◁ '} @@ -402,12 +390,7 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { onClick={(): void => { set_currentPage(currentPage - 1); window.scrollTo({top: 0, behavior: 'smooth'}); - router.push({ - query: { - ...router.query, - page: currentPage - 1 - } - }); + router.push(`/${params.toString()}&page=${currentPage - 1}`); }}> {'◁ Previous'} @@ -433,12 +416,7 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { onClick={(): void => { set_currentPage(currentPage + 1); window.scrollTo({top: 0, behavior: 'smooth'}); - router.push({ - query: { - ...router.query, - page: currentPage + 1 - } - }); + router.push(`/${params.toString()}&page=${currentPage + 1}`); }}> {'Next ▷'} @@ -456,12 +434,9 @@ function TokenListContent({list}: {list: TTokenListItem}): ReactElement { onClick={(): void => { set_currentPage(Math.ceil(searchResult.length / itemsPerPage)); window.scrollTo({top: 0, behavior: 'smooth'}); - router.push({ - query: { - ...router.query, - page: Math.ceil(searchResult.length / itemsPerPage) - } - }); + router.push( + `/${params.toString()}&page=${Math.ceil(searchResult.length / itemsPerPage)}` + ); }}> {' ▷▷'} @@ -480,71 +455,4 @@ function List({list}: {list: TTokenListItem}): ReactElement { ); } - -export default function Wrapper({pageProps}: {pageProps: {list: TTokenListItem}}): ReactElement { - return ( - <> - - - - ); -} - -export const getServerSideProps = async ( - context: NextPageContext -): Promise> => { - const listID = context.query.list; - if (!listID) { - return { - redirect: { - permanent: false, - destination: '/tokenlistooor' - } - }; - } - try { - const listRes = await fetch(`https://raw.githubusercontent.com/SmolDapp/tokenLists/main/lists/${listID}.json`); - const tokenListResponse = (await listRes.json()) as TTokenListItem; - return { - props: { - list: { - ...tokenListResponse, - URI: `https://raw.githubusercontent.com/SmolDapp/tokenLists/main/lists/${listID}.json` - } - } - }; - } catch (e) { - console.error(e); - return { - redirect: { - permanent: false, - destination: '/tokenlistooor' - } - }; - } -}; +export default List; diff --git a/pages/index.tsx b/app/components/Lists.tsx old mode 100755 new mode 100644 similarity index 82% rename from pages/index.tsx rename to app/components/Lists.tsx index 03dc1b8..b908520 --- a/pages/index.tsx +++ b/app/components/Lists.tsx @@ -1,18 +1,21 @@ +'use client'; + import React, {Fragment, useMemo, useState} from 'react'; -import {useRouter} from 'next/router'; -import {DefaultSeo} from 'next-seo'; -import LEGACY_TOKEN_LISTS from 'utils/legacyTokenLists'; +import {useParams, useRouter} from 'next/navigation'; import * as chains from 'wagmi/chains'; import axios from 'axios'; import useSWR from 'swr'; import {motion} from 'framer-motion'; -import TokenListCard, {LegacyTokenListCard} from '@tokenlistooor/TokenListCard'; -import TokenListHero from '@tokenlistooor/TokenListHero'; + +import TokenListCard, {LegacyTokenListCard} from './TokenListCard'; import type {Variants} from 'framer-motion'; import type {ReactElement} from 'react'; import type {TNDict} from '@builtbymom/web3/types'; -import type {TTokenListItem, TTokenListSummary} from '@utils/types/types'; +import type {TTokenListItem, TTokenListSummary} from '@/utils/types/types'; + +import TokenListHero from '@/app/components/TokenListHero'; +import LEGACY_TOKEN_LISTS from '@/utils/legacyTokenLists'; const fetcher = async (url: string): Promise => axios.get(url).then(res => res.data); @@ -40,6 +43,7 @@ export function Filters({ set_search: (value: string) => void; set_network: (value: number) => void; }): ReactElement { + const params = useParams(); const router = useRouter(); return ( @@ -54,16 +58,11 @@ export function Filters({ onChange={(e): void => { set_search(e.target.value || ''); if (!e.target.value) { - const {search, ...queryNoSearch} = router.query; + const {search, ...queryNoSearch} = params; search; - router.push({query: queryNoSearch}); + router.push(`/${queryNoSearch}`); } else { - router.push({ - query: { - ...router.query, - search: e.target.value - } - }); + router.push(`/${params.toString()}&search=${e.target.value}`); } }} /> @@ -77,16 +76,11 @@ export function Filters({ onChange={(e): void => { set_network(Number(e.target.value)); if (Number(e.target.value) === -1) { - const {network, ...queryNoNetwork} = router.query; + const {network, ...queryNoNetwork} = params; network; - router.push({query: queryNoNetwork}); + router.push(`/${queryNoNetwork}`); } else { - router.push({ - query: { - ...router.query, - network: e.target.value - } - }); + router.push(`/${params.toString()}&network=${e.target.value}`); } }}> @@ -321,44 +315,4 @@ function Lists(): ReactElement { ); } - -function Home(): ReactElement { - return ; -} - -export default function Wrapper(): ReactElement { - return ( - <> - - - - ); -} +export default Lists; diff --git a/components/apps/tokenlistooor/Logo.tsx b/app/components/Logo.tsx similarity index 99% rename from components/apps/tokenlistooor/Logo.tsx rename to app/components/Logo.tsx index 0e02c0a..eb160d1 100644 --- a/components/apps/tokenlistooor/Logo.tsx +++ b/app/components/Logo.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import type {ReactElement} from 'react'; diff --git a/components/apps/tokenlistooor/TokenListCard.tsx b/app/components/TokenListCard.tsx similarity index 98% rename from components/apps/tokenlistooor/TokenListCard.tsx rename to app/components/TokenListCard.tsx index 132c6f1..9ed8598 100644 --- a/components/apps/tokenlistooor/TokenListCard.tsx +++ b/app/components/TokenListCard.tsx @@ -7,11 +7,11 @@ import relativeTime from 'dayjs/plugin/relativeTime.js'; import weekday from 'dayjs/plugin/weekday.js'; import {formatAmount} from '@builtbymom/web3/utils'; -import {ImageWithFallback} from '../../common/ImageWithFallback'; - import type {ReactElement} from 'react'; import type {TTokenListItem} from '@utils/types/types'; +import {ImageWithFallback} from '@/app/components/ImageWithFallback'; + extend(relativeTime); extend(dayjsDuration); extend(weekday); diff --git a/components/apps/tokenlistooor/TokenListHero.tsx b/app/components/TokenListHero.tsx similarity index 97% rename from components/apps/tokenlistooor/TokenListHero.tsx rename to app/components/TokenListHero.tsx index 7cfceb1..403cc0d 100644 --- a/components/apps/tokenlistooor/TokenListHero.tsx +++ b/app/components/TokenListHero.tsx @@ -1,18 +1,21 @@ +'use client'; + import React from 'react'; import {toast} from 'react-hot-toast'; import Link from 'next/link'; -import {Button} from 'components/Primitives/Button'; import dayjs, {extend} from 'dayjs'; import dayjsDuration from 'dayjs/plugin/duration.js'; import relativeTime from 'dayjs/plugin/relativeTime.js'; import weekday from 'dayjs/plugin/weekday.js'; import {useTimer} from 'hooks/useTimer'; import {copyToClipboard} from '@builtbymom/web3/utils'; -import {IconSocialGithub} from '@icons/IconSocialGithub'; import type {ReactElement} from 'react'; import type {TTokenListSummary} from '@utils/types/types'; +import Button from '@/app/components/Button'; +import {IconSocialGithub} from '@/app/components/icons/IconSocialGithub'; + extend(relativeTime); extend(dayjsDuration); extend(weekday); diff --git a/components/common/ViewSectionHeading.tsx b/app/components/ViewSectionHeading.tsx similarity index 98% rename from components/common/ViewSectionHeading.tsx rename to app/components/ViewSectionHeading.tsx index 8a94e3e..7114a4f 100644 --- a/components/common/ViewSectionHeading.tsx +++ b/app/components/ViewSectionHeading.tsx @@ -1,3 +1,5 @@ +'use client'; + import React from 'react'; import {cl} from '@builtbymom/web3/utils'; diff --git a/components/icons/IconCheck.tsx b/app/components/icons/IconCheck.tsx similarity index 100% rename from components/icons/IconCheck.tsx rename to app/components/icons/IconCheck.tsx diff --git a/components/icons/IconChevron.tsx b/app/components/icons/IconChevron.tsx similarity index 100% rename from components/icons/IconChevron.tsx rename to app/components/icons/IconChevron.tsx diff --git a/components/icons/IconChevronBoth.tsx b/app/components/icons/IconChevronBoth.tsx similarity index 100% rename from components/icons/IconChevronBoth.tsx rename to app/components/icons/IconChevronBoth.tsx diff --git a/components/icons/IconChevronPlain.tsx b/app/components/icons/IconChevronPlain.tsx similarity index 100% rename from components/icons/IconChevronPlain.tsx rename to app/components/icons/IconChevronPlain.tsx diff --git a/components/icons/IconCircleCross.tsx b/app/components/icons/IconCircleCross.tsx similarity index 100% rename from components/icons/IconCircleCross.tsx rename to app/components/icons/IconCircleCross.tsx diff --git a/components/icons/IconDownload.tsx b/app/components/icons/IconDownload.tsx similarity index 100% rename from components/icons/IconDownload.tsx rename to app/components/icons/IconDownload.tsx diff --git a/components/icons/IconSocialGithub.tsx b/app/components/icons/IconSocialGithub.tsx similarity index 100% rename from components/icons/IconSocialGithub.tsx rename to app/components/icons/IconSocialGithub.tsx diff --git a/components/icons/IconSpinner.tsx b/app/components/icons/IconSpinner.tsx similarity index 100% rename from components/icons/IconSpinner.tsx rename to app/components/icons/IconSpinner.tsx diff --git a/components/icons/logo.tsx b/app/components/icons/logo.tsx similarity index 100% rename from components/icons/logo.tsx rename to app/components/icons/logo.tsx diff --git a/app/error.tsx b/app/error.tsx new file mode 100644 index 0000000..d51897f --- /dev/null +++ b/app/error.tsx @@ -0,0 +1,22 @@ +'use client'; + +import React from 'react'; +import Button from '@/app/components/Button'; + +export default function Error({ + error, + reset +}: { + error: Error & {digest?: string}; + reset: () => void; +}): React.ReactElement { + return ( +
+
+

Something went wrong!

+

{error.message}

+
+ +
+ ); +} diff --git a/app/layout.tsx b/app/layout.tsx new file mode 100644 index 0000000..dd8a9d7 --- /dev/null +++ b/app/layout.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import {Toaster} from 'react-hot-toast'; +import {Rubik, Source_Code_Pro} from 'next/font/google'; + +import type {ReactElement, ReactNode} from 'react'; + +import './style.css'; + +import {Providers} from '@/app/providers'; + +const rubik = Rubik({ + weight: ['400', '500', '600', '700'], + subsets: ['latin'], + display: 'swap', + variable: '--rubik-font' +}); + +const sourceCodePro = Source_Code_Pro({ + weight: ['400', '500', '600', '700'], + subsets: ['latin'], + display: 'swap', + variable: '--scp-font' +}); + +export const metadata = { + title: 'Tokenlistooor - SmolDapp', + description: + 'Up to date token lists that fulfill your needs! Tokenlistooor is a fork of Uniswap Tokenlists, with focus on adding more automation and extra features.' +}; + +export default function RootLayout({children}: {children: ReactNode}): ReactElement { + return ( + + + +
{children}
+ +
+ + + ); +} diff --git a/app/list/[list]/page.tsx b/app/list/[list]/page.tsx new file mode 100644 index 0000000..bad3827 --- /dev/null +++ b/app/list/[list]/page.tsx @@ -0,0 +1,75 @@ +import type {Metadata} from 'next'; +import type {TTokenListItem} from '@/utils/types/types'; + +import List from '@/app/components/List'; + +type TProps = { + params: {list: string}; +}; + +export async function generateMetadata({params}: TProps): Promise { + try { + const list = (await fetch( + `https://raw.githubusercontent.com/SmolDapp/tokenLists/main/lists/${params.list}.json` + ).then(async res => res.json())) as TTokenListItem; + + return { + title: `${list.name} tokenList - SmolDapp`, + description: list.description, + openGraph: { + type: 'website', + locale: 'en-US', + url: 'https://smold.app/tokenlistooor', + siteName: `${list.name} tokenList - SmolDapp`, + title: `${list.name} tokenList - SmolDapp`, + description: list.description, + images: [ + { + url: 'https://smold.app/og_tokenlistooor.png', + width: 800, + height: 400, + alt: 'tokenListooor' + } + ] + }, + twitter: { + creator: '@smoldapp', + site: '@smoldapp', + card: 'summary_large_image' + } + }; + } catch (e) { + return { + title: 'Not Found', + description: 'The page you are looking for does not exist.' + }; + } +} + +async function getList(listId: string): Promise { + try { + const listRes = await fetch(`https://raw.githubusercontent.com/SmolDapp/tokenLists/main/lists/${listId}.json`); + const tokenListResponse = (await listRes.json()) as TTokenListItem; + return { + ...tokenListResponse, + URI: `https://raw.githubusercontent.com/SmolDapp/tokenLists/main/lists/${listId}.json` + }; + } catch (e) { + return null; + } +} + +export default async function ListPage({params}: TProps): Promise { + const list = await getList(params.list); + + if (!list) { + return { + redirect: { + destination: '/', + permanent: false + } + }; + } + + return ; +} diff --git a/app/loading.tsx b/app/loading.tsx new file mode 100644 index 0000000..2c2aebf --- /dev/null +++ b/app/loading.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import {IconSpinner} from '@/app/components/icons/IconSpinner'; + +export default function Loading(): React.ReactElement { + return ( +
+ +
+ ); +} diff --git a/app/not-found.tsx b/app/not-found.tsx new file mode 100644 index 0000000..45f9a6e --- /dev/null +++ b/app/not-found.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import Link from 'next/link'; +import Button from '@/app/components/Button'; + +export default function NotFound(): React.ReactElement { + return ( +
+
+

Not Found

+

Could not find requested resource

+
+ + + +
+ ); +} diff --git a/app/page.tsx b/app/page.tsx new file mode 100644 index 0000000..95a7397 --- /dev/null +++ b/app/page.tsx @@ -0,0 +1,36 @@ +import type {Metadata} from 'next'; +import type {ReactElement} from 'react'; + +import Lists from '@/app/components/Lists'; + +export const metadata: Metadata = { + title: 'Tokenlistooor - SmolDapp', + description: + 'Up to date token lists that fulfill your needs! Tokenlistooor is a fork of Uniswap Tokenlists, with focus on adding more automation and extra features.', + openGraph: { + type: 'website', + locale: 'en-US', + url: 'https://smold.app/tokenlistooor', + siteName: 'Tokenlistooor - SmolDapp', + title: 'Tokenlistooor - SmolDapp', + description: + 'Up to date token lists that fulfill your needs! Tokenlistooor is a fork of Uniswap Tokenlists, with focus on adding more automation and extra features.', + images: [ + { + url: 'https://smold.app/og_tokenlistooor.png', + width: 800, + height: 400, + alt: 'tokenListooor' + } + ] + }, + twitter: { + creator: '@smoldapp', + card: 'summary_large_image', + title: 'Tokenlists - MOM' + } +}; + +export default function Home(): ReactElement { + return ; +} diff --git a/app/providers.tsx b/app/providers.tsx new file mode 100644 index 0000000..ecb4816 --- /dev/null +++ b/app/providers.tsx @@ -0,0 +1,15 @@ +'use client'; + +import React from 'react'; +import {SWRConfig} from 'swr'; + +export function Providers({children}: {children: React.ReactNode}) { + return ( + fetch(resource, init).then(res => res.json()) + }}> + {children} + + ); +} diff --git a/app/style.css b/app/style.css new file mode 100644 index 0000000..418d364 --- /dev/null +++ b/app/style.css @@ -0,0 +1,614 @@ +@import '@rainbow-me/rainbowkit/styles.css'; + +@tailwind base; + +body { + @apply text-neutral-900 bg-primary/10; +} +h1 { + @apply text-xl font-bold text-neutral-900; +} +h4 { + @apply text-lg font-bold text-neutral-700; +} +h6 { + @apply text-base font-bold text-neutral-700; +} +small { + @apply text-xxs font-normal text-neutral-600 block; +} +#__next { + @apply w-full h-full; +} + +/* 🔵 - Yearn Finance ****************************************************** +** Overwritting the defaults to match our needs +**************************************************************************/ +* { + @apply scroll-smooth font-sans; +} +input::placeholder { + @apply text-neutral-400 not-italic; +} +textarea::placeholder { + @apply text-neutral-400 not-italic; +} +input[type=file], /* FF, IE7+, chrome (except button) */ +input[type=file]::-webkit-file-upload-button { + /* chromes and blink button */ + cursor: pointer; +} +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +textarea:-webkit-autofill, +textarea:-webkit-autofill:hover, +textarea:-webkit-autofill:focus, +select:-webkit-autofill, +select:-webkit-autofill:hover, +select:-webkit-autofill:focus { + -webkit-box-shadow: 0 0 0px 1000px rgba(0, 0, 0, 0) inset; + transition: background-color 5000s ease-in-out 0s; +} +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +input[type='number'] { + -moz-appearance: textfield; +} +label[aria-invalid='true'] { + @apply text-neutral-600 transition-colors; + & > form > div { + @apply border-[#FE0000] focus-within:border-[#FE0000] text-[#FE0000] focus-within:text-[#FE0000]; + } + & > p { + @apply text-[#FF0000]; + } +} + +:focus { + outline-width: 0px; + outline-offset: 0px; + outline: none; + outline-color: transparent; +} + +/* -- Button.tsx ------------------------------------------------------- */ +.smol--input-wrapper { + @apply flex h-10 w-full items-center rounded-md border border-neutral-200 bg-neutral-0 transition-colors; + + &:has(input:focus) { + @apply border-primary; + } +} +.smol--input { + @apply w-full overflow-x-scroll border-none bg-transparent text-sm outline-none scrollbar-none p-2; +} + +select.smol--input { + @apply pr-7 truncate; +} + +/* 🔵 - Yearn Finance ****************************************************** +** Then, we import the tailwind class. They will be able to overwrite all +** the previous classes, not the next ones directly. +**************************************************************************/ +@tailwind components; +@tailwind utilities; + +/* 🔵 - Yearn Finance ****************************************************** +** NsProgress is used to display the loading indicator. All of theses +** styles are required to make it visible +**************************************************************************/ +.nprogress-custom-parent { + @apply overflow-hidden relative; +} +.nprogress-custom-parent #nprogress .spinner { + @apply absolute; +} +.nprogress-custom-parent #nprogress .bar { + @apply absolute; +} +#nprogress { + @apply pointer-events-none; +} +#nprogress .bar { + @apply bg-neutral-900 fixed top-0 left-0 w-full h-1 z-[1031]; +} +#nprogress .spinner { + @apply block fixed z-[1031] top-4 right-4; +} +#nprogress .spinner-icon { + @apply hidden; +} + +/* 🔵 - Yearn Finance ****************************************************** +** Some accessibilities fixes +**************************************************************************/ +.scrollbar-none::-webkit-scrollbar { + display: none; +} +.scrollbar-none { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} +.yearn--select-reset { + @apply bg-transparent p-0 border-none; + &:focus { + outline: none; + box-shadow: none; + } +} +.yearn--select-no-arrow { + -webkit-appearance: none; + -moz-appearance: none; + background-image: none; +} +.yearn--select-no-arrow::-ms-expand { + display: none; +} + +/* 🔵 - Yearn Finance ****************************************************** +** Some custom css for the components +**************************************************************************/ +.img-gradient::after { + content: ''; + background-image: linear-gradient(90deg, rgba(0, 0, 0, 0.6) 0%, rgba(0, 0, 0, 0) 100%); + @apply absolute inset-0 w-full h-full; +} + +/* 🔵 - Yearn Finance ****************************************************** +** Header an navbar +**************************************************************************/ +.yearn--header { + @apply inset-x-0 top-0 z-50 mb-5 flex h-20 w-full max-w-[1200px] flex-row items-center justify-between p-4 text-xs sm:text-sm md:inset-x-auto md:mb-0 md:px-0 md:text-base; +} +.yearn--nav { + @apply hidden w-1/3 flex-row items-center space-x-3 md:flex md:space-x-6; +} +.yearn--header-nav-item { + @apply relative cursor-pointer transition-colors text-neutral-600 hover:text-neutral-900 font-normal text-sm; + + &.active { + @apply text-neutral-900 font-bold; + } +} + +.yearn--toast-options { + @apply !w-screen text-sm text-neutral-700 !px-4 !rounded-none !shadow-none !py-2 !max-w-full; +} + +.yearn--toast-button { + @apply text-xs px-3 py-1 text-primary bg-white; +} + +.mobile-nav-item { + @apply flex flex-col items-start justify-between rounded-md bg-neutral-200; + & > p { + @apply p-2 text-base font-bold text-neutral-900; + } +} +.mobile-nav-item { + @apply rounded-none border-b border-neutral-300 bg-neutral-200/60; +} + +.tab { + @apply font-normal text-neutral-600 transition-colors hover:text-neutral-900 cursor-pointer border-b-2 border-transparent pb-4 z-20; + &[aria-selected='true'] { + @apply font-bold text-neutral-900 border-neutral-900; + } +} +.hover-fix::before { + display: block; + content: attr(title); + font-weight: bold; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.font-number { + @apply font-mono tabular-nums; +} + +[type='text']:focus, +[type='email']:focus, +[type='url']:focus, +[type='password']:focus, +[type='number']:focus, +[type='date']:focus, +[type='datetime-local']:focus, +[type='month']:focus, +[type='search']:focus, +[type='tel']:focus, +[type='time']:focus, +[type='week']:focus, +[multiple]:focus, +textarea:focus, +select:focus { + outline: none; + box-shadow: none; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes fadeOut { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +.TooltipContent[data-state='delayed-open'], +.PopoverContent[data-state='open'] { + animation: fadeIn 100ms ease-out; +} +.TooltipContent[data-state='closed'], +.PopoverContent[data-state='closed'] { + animation: fadeOut 100ms ease-in; +} + +.DropdownMenuContent { + width: var(--radix-dropdown-menu-trigger-width); + max-height: var(--radix-dropdown-menu-content-available-height); +} + +.skeleton-lg { + @apply animate-pulse rounded-lg bg-neutral-400; +} +.skeleton-full { + @apply animate-pulse rounded-full bg-neutral-400; +} + +.withRing:focus-visible { + @apply ring-2 ring-primary ring-offset-2; +} +.button { + @apply cursor-pointer px-5 flex justify-center items-center h-10 text-base transition-all relative rounded-lg; + @apply withRing; + + &[data-variant='filled'] { + @apply text-white bg-black font-medium border border-transparent text-sm; + &:disabled { + @apply bg-neutral-600 opacity-40; + } + &:not(:disabled):not([aria-busy='true']):hover { + @apply bg-primaryHover; + } + } + &[data-variant='light'] { + @apply text-neutral-900 bg-neutral-300 font-normal border border-transparent; + &:disabled { + @apply opacity-40; + } + &:not(:disabled):not([aria-busy='true']):hover { + @apply bg-neutral-400; + } + } + &[aria-busy='true'] { + @apply cursor-wait; + color: transparent !important; + } + &:disabled { + @apply cursor-not-allowed; + } +} + +.input { + @apply w-full rounded-lg bg-transparent py-3 px-4 text-base; + @apply text-neutral-900 placeholder:text-neutral-600 caret-neutral-700; + @apply focus:placeholder:text-neutral-300 placeholder:transition-colors; + @apply focus:border-neutral-600 disabled:bg-neutral-300 transition-colors; + @apply border-neutral-400 disabled:border-transparent; +} +.font-mono { + font-family: 'Source Code Pro', monospace !important; +} +@layer base { + .max-w-4xl, + .max-w-5xl, + .max-w-6xl { + @apply px-4; + } + + .font-number { + @apply font-mono tabular-nums; + } + + .yearn--header { + @apply !p-0 !h-14; + } + + /* 🔵 - Yearn Finance ****************************************************************************** +** Table +** Fake table properties +***************************************************************************************************/ + .yearn--table-head-wrapper { + @apply mb-2 hidden w-full grid-cols-9 px-6 md:grid; + } + .yearn--table-head-token-section { + @apply col-span-3; + } + .yearn--table-head-data-section { + @apply col-span-6 grid grid-cols-12 gap-x-7; + } + .yearn--table-head-label { + @apply text-start text-sm text-neutral-400; + } + .yearn--table-head-label-wrapper { + @apply col-span-1 flex flex-row items-center justify-start space-x-1; + &[datatype='number'] { + @apply justify-end; + & > .yearn--table-head-label { + @apply text-end; + } + } + } + .yearn--table-wrapper { + @apply grid w-full grid-cols-1 border-t border-neutral-200 py-2 px-4 md:grid-cols-9 md:border-none md:px-6; + } + .yearn--table-token-section { + @apply col-span-3 mb-2 flex flex-row items-center justify-between py-4 md:mb-0 md:py-0; + } + .yearn--table-token-section-item { + @apply flex flex-row items-center space-x-4 md:space-x-6; + } + .yearn--table-token-section-item-image { + @apply h-8 min-h-[32px] w-8 min-w-[32px] md:flex md:h-10 md:w-10; + } + .yearn--table-data-section { + @apply col-span-6 grid grid-cols-1 gap-x-0 md:grid-cols-12 gap-y-2 md:gap-y-0 mb-4 md:mb-0; + } + .yearn--table-data-section-item-label { + @apply inline text-start text-neutral-600 md:hidden text-xs ml-1; + } + .yearn--table-data-section-item-value { + @apply text-neutral-900; + } + .yearn--table-data-section-item { + @apply col-span-1 flex h-auto flex-col justify-between pt-0 px-0 md:h-14 md:py-2; + &[datatype='number'] { + @apply md:justify-end font-number; + & > .yearn--table-data-section-item-value { + @apply font-number text-end; + } + } + } + .yearn--table-label { + @apply inline text-start text-neutral-600 md:hidden; + } + + /* 🔵 - Yearn Finance ****************************************************************************** + ** Details and summary nice styles. + ***************************************************************************************************/ + details > summary { + list-style: none; + } + details > summary::-webkit-details-marker { + display: none; + } + details { + & > summary { + @apply px-4 md:px-6 py-6 md:py-8 cursor-pointer flex flex-row items-center justify-between; + } + } + + /* 🔵 - Yearn Finance ****************************************************************************** +** Loader +** Custom style for the loader icon +***************************************************************************************************/ + .loader { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: #fff; + box-shadow: + 16px 0 #fff, + -16px 0 #fff; + position: relative; + animation: flash 1s ease-out infinite alternate; + } + + @keyframes flash { + 0% { + background-color: #fff2; + box-shadow: + 12px 0 #fff2, + -12px 0 #fff; + } + 50% { + background-color: #fff; + box-shadow: + 12px 0 #fff2, + -12px 0 #fff2; + } + 100% { + background-color: #fff2; + box-shadow: + 12px 0 #fff, + -12px 0 #fff2; + } + } +} + +@keyframes flash { + 0% { + background-color: #aaaaaa; + box-shadow: + 12px 0 #aaaaaa, + -12px 0 #000000; + } + 50% { + background-color: #000000; + box-shadow: + 12px 0 #aaaaaa, + -12px 0 #aaaaaa; + } + 100% { + background-color: #aaaaaa; + box-shadow: + 12px 0 #000000, + -12px 0 #aaaaaa; + } +} + +.box-0 { + @apply bg-neutral-0 border border-primary/20 rounded-md shadow-sm; + &.hover { + @apply transition-colors hover:bg-neutral-200; + } +} + +.tooltip { + @apply relative cursor-help; +} +.tooltip .tooltiptext, +.tooltip .tooltiptextsmall { + @apply text-xs text-center invisible bg-neutral-0 text-neutral-900 absolute z-50 right-1 opacity-0 transition-opacity p-2 rounded; + box-shadow: 0px 4px 16px rgba(0, 0, 0, 0.16); +} +.tooltip .tooltipLight { + @apply invisible absolute z-50 opacity-0 transition-opacity flex justify-center items-center; +} +.tooltiptext { + top: 110%; + width: 16rem; + margin-right: calc(-122px + 50%); +} +.tooltiptextsmall { + top: 150%; + width: 10rem; + margin-right: calc(-5rem + 4px); +} +.tooltip:hover .tooltiptext, +.tooltip:hover .tooltiptextsmall, +.tooltip:hover .tooltipLight { + @apply visible opacity-100; +} +.tooltip .tooltiptext::after, +.tooltip .tooltiptextsmall::after { + content: ''; + position: absolute; + bottom: 98%; + right: 50%; + margin-right: -5px; + border-width: 5px; + border-style: solid; + border-color: hsl(var(--color-neutral-0)) transparent transparent transparent; + transform: rotate(180deg); +} + +.tooltip.top .tooltiptext, +.tooltip.top .tooltiptextsmall { + top: -105%; + bottom: unset; +} +.tooltip.top .tooltiptext::after, +.tooltip.top .tooltiptextsmall::after { + top: 98%; + transform: rotate(0deg); +} + +.modal { + @apply inline-block overflow-hidden relative z-50 w-full text-left align-bottom transition-all sm:my-8 sm:w-full sm:max-w-lg sm:align-middle md:mb-96 mx-auto md:max-w-4xl bg-transparent; +} +.modal-overlay { + @apply fixed inset-0 z-10 bg-neutral-900/40 transition-opacity backdrop-blur-[1px]; +} +.detailsMigrate { + @apply bg-neutral-0 flex w-full flex-col justify-center border-t border-b-0 border-neutral-200 transition-colors; +} + +/************************************************************************************************** +** AppBox is the style used to make the nices animations on the home page feel nice and smooth +** A custom overwrite is required for dark/light mode +**************************************************************************************************/ +.appBox { + @apply relative flex aspect-video h-full w-full cursor-pointer flex-col items-start border border-neutral-200 p-4 transition-colors rounded-md bg-neutral-0 hover:bg-primary text-neutral-900; +} + +.groupHoverText { + &:hover .noHoverContent { + @apply opacity-0; + } + &:hover .withHoverContent { + @apply opacity-100; + } +} +.approvalWizardDivider { + & > *:last-child { + @apply border-b-0 !mb-0 !pb-0; + } +} + +.container { + @apply flex flex-row justify-between items-center space-x-10 relative max-w-xs; + & > dt { + @apply bg-primary z-10 pr-2; + } + & > dd { + @apply bg-primary z-10 pl-2 font-number whitespace-nowrap; + } +} +.filler { + @apply w-3/4 border-b-2 border-dashed border-neutral-0 absolute inset-0 h-full; + top: calc(-50% + 1px); +} + +.svg-fit { + & > svg { + @apply w-full h-full; + } +} + +.addr > span { + & > span { + @apply break-normal; + } + @apply break-all; +} + +.scrollbar-show::-webkit-scrollbar { + -webkit-appearance: none; + overflow: hidden; + width: 4px; +} +.scrollbar-show::-webkit-scrollbar-thumb { + @apply rounded-md; + background-color: rgba(0, 0, 0, 0.1); + -webkit-box-shadow: 0 0 1px rgba(255, 255, 255, 0.1); +} + +.scrollable { + @apply -mr-2 pr-1 overflow-y-auto; + scrollbar-gutter: stable; +} + +* { + scrollbar-color: #f7f7f7; + scroll-behavior: smooth; + + &::-webkit-scrollbar { + background: #f7f7f7; + border-radius: 14px; + width: 4px; + height: 4px; + } + + &::-webkit-scrollbar-thumb { + background: #dcdddd; + border-radius: 14px; + background-clip: padding-box; + } +} diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..3863001 Binary files /dev/null and b/bun.lockb differ diff --git a/components/common/Meta.tsx b/components/common/Meta.tsx deleted file mode 100644 index 9c183b5..0000000 --- a/components/common/Meta.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import React from 'react'; -import Head from 'next/head'; -import {DefaultSeo} from 'next-seo'; -import meta from 'public/manifest.json'; - -import type {ReactElement} from 'react'; - -function Meta(): ReactElement { - return ( - <> - - {meta.name} - - - - - - - - - - - - - - - - - - - - - - - - - - - - -