├── .gitignore ├── README.md ├── components ├── Navbar.tsx └── SwapInput.tsx ├── const └── details.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ └── hello.ts └── index.tsx ├── public ├── favicon.ico ├── next.svg ├── thirteen.svg ├── token.png └── vercel.svg ├── styles ├── Home.module.css └── globals.css └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!Important] 2 | > This repository is referencing the `mumbai` chain. 3 | > 4 | > `Mumbai` [is deprecated since 08/04/2024](https://blog.thirdweb.com/deprecation-of-mumbai-testnet/), meaning the code in this repository will no longer work out of the box. 5 | > 6 | > You can still use this repository, however you will have to switch any references to `mumbai` to another chain. 7 | 8 | # DEX app 9 | 10 | Create your own DEX for your ERC-20 token. Find the related contracts in the [dex-contracts repository](https://github.com/thirdweb-example/dex-contracts). 11 | 12 | ## Installation 13 | 14 | After cloning this repository, install the dependencies using the following command: 15 | 16 | ```bash 17 | # npm 18 | npm install 19 | 20 | # yarn 21 | yarn install 22 | ``` 23 | 24 | ## Adding details 25 | 26 | Make sure you deploy the contracts specified in the dex-contracts repository and add the contract details in the `const/details.ts` file. 27 | 28 | ## Running the app 29 | 30 | Use the following command to start the development server: 31 | 32 | ```bash 33 | # npm 34 | npm run dev 35 | 36 | # yarn 37 | yarn dev 38 | ``` 39 | 40 | ## Conclusion 41 | 42 | If you need any support, feel free to join our [Discord server](https://discord.gg/thirdweb) and ask us. If you have any feedback related to thirdweb, please leave it on our [feedback board](https://feedback.thirdweb.com). 43 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text } from "@chakra-ui/react"; 2 | import { ConnectWallet } from "@thirdweb-dev/react"; 3 | import React from "react"; 4 | 5 | export default function Navbar() { 6 | return ( 7 | 8 | 17 | 18 | Token DEX 19 | 20 | 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /components/SwapInput.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, HStack, Image, Input } from "@chakra-ui/react"; 2 | import { resolveIpfsUri } from "@thirdweb-dev/react"; 3 | import React from "react"; 4 | 5 | type Props = { 6 | type: "native" | "token"; 7 | tokenImage?: string; 8 | current: string; 9 | setValue: (value: string) => void; 10 | max?: string; 11 | value: string; 12 | }; 13 | 14 | export default function SwapInput({ 15 | type, 16 | tokenImage, 17 | setValue, 18 | value, 19 | current, 20 | max, 21 | }: Props) { 22 | return ( 23 | 24 | 25 | setValue(e.target.value)} 31 | outline="none" 32 | py="10" 33 | isDisabled={current !== type} 34 | border="none" 35 | fontFamily="monospace" 36 | _focus={{ boxShadow: "none" }} 37 | /> 38 | {current === type && ( 39 | 40 | )} 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /const/details.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Mumbai } from "@thirdweb-dev/chains"; 2 | 3 | export const TOKEN_ADDRESS: string = 4 | "0x9b84D0E4fc807CB664aFdBF9E7096bed3a7aB18A"; 5 | export const DEX_ADDRESS: string = "0x92c85c31c74F482AB30D94D7e5c63fe9BD53883B"; 6 | export const ACTIVE_CHAIN: Chain = Mumbai; 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = nextConfig 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "^2.5.5", 13 | "@emotion/react": "^11.10.6", 14 | "@emotion/styled": "^11.10.6", 15 | "@thirdweb-dev/chains": "^0.1.11", 16 | "@thirdweb-dev/react": "^3.11.7", 17 | "@thirdweb-dev/sdk": "^3.10.10", 18 | "@types/node": "18.15.11", 19 | "@types/react": "18.0.35", 20 | "@types/react-dom": "18.0.11", 21 | "ethers": "5", 22 | "framer-motion": "^10.12.2", 23 | "next": "13.3.0", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "typescript": "5.0.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { ACTIVE_CHAIN } from "@/const/details"; 2 | import "@/styles/globals.css"; 3 | import { ChakraProvider } from "@chakra-ui/react"; 4 | import { ThirdwebProvider } from "@thirdweb-dev/react"; 5 | import type { AppProps } from "next/app"; 6 | 7 | export default function App({ Component, pageProps }: AppProps) { 8 | return ( 9 | 13 | 14 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from "next/head"; 2 | import { Inter } from "next/font/google"; 3 | import Navbar from "@/components/Navbar"; 4 | import { 5 | Box, 6 | Button, 7 | Flex, 8 | HStack, 9 | Image, 10 | Input, 11 | Spinner, 12 | useToast, 13 | } from "@chakra-ui/react"; 14 | import { ACTIVE_CHAIN, DEX_ADDRESS, TOKEN_ADDRESS } from "@/const/details"; 15 | import { 16 | ConnectWallet, 17 | resolveIpfsUri, 18 | toEther, 19 | toWei, 20 | useAddress, 21 | useBalance, 22 | useContract, 23 | useContractMetadata, 24 | useContractRead, 25 | useContractWrite, 26 | useNetworkMismatch, 27 | useSDK, 28 | useSwitchChain, 29 | useTokenBalance, 30 | } from "@thirdweb-dev/react"; 31 | import { useEffect, useState } from "react"; 32 | import SwapInput from "@/components/SwapInput"; 33 | 34 | const inter = Inter({ subsets: ["latin"] }); 35 | 36 | export default function Home() { 37 | const toast = useToast(); 38 | const address = useAddress(); 39 | const { contract: tokenContract } = useContract(TOKEN_ADDRESS, "token"); 40 | const { contract: dexContract } = useContract(DEX_ADDRESS, "custom"); 41 | const { data: symbol } = useContractRead(tokenContract, "symbol"); 42 | const { data: tokenMetadata } = useContractMetadata(tokenContract); 43 | const { data: tokenBalance } = useTokenBalance(tokenContract, address); 44 | const { data: nativeBalance } = useBalance(); 45 | const { data: contractTokenBalance } = useTokenBalance( 46 | tokenContract, 47 | DEX_ADDRESS 48 | ); 49 | 50 | const isMismatched = useNetworkMismatch(); 51 | const switchChain = useSwitchChain(); 52 | 53 | const sdk = useSDK(); 54 | const [contractBalance, setContractBalance] = useState("0"); 55 | 56 | const [nativeValue, setNativeValue] = useState("0"); 57 | const [tokenValue, setTokenValue] = useState("0"); 58 | const [currentFrom, setCurrentFrom] = useState("native"); 59 | const [loading, setLoading] = useState(false); 60 | 61 | const { mutateAsync: swapNativeToToken } = useContractWrite( 62 | dexContract, 63 | "swapEthTotoken" 64 | ); 65 | const { mutateAsync: swapTokenToNative } = useContractWrite( 66 | dexContract, 67 | "swapTokenToEth" 68 | ); 69 | const { mutateAsync: approveTokenSpending } = useContractWrite( 70 | tokenContract, 71 | "approve" 72 | ); 73 | 74 | const { data: amountToGet } = useContractRead( 75 | dexContract, 76 | "getAmountOfTokens", 77 | currentFrom === "native" 78 | ? [ 79 | toWei(nativeValue || "0"), 80 | toWei(contractBalance || "0"), 81 | contractTokenBalance?.value, 82 | ] 83 | : [ 84 | toWei(tokenValue || "0"), 85 | contractTokenBalance?.value, 86 | toWei(contractBalance || "0"), 87 | ] 88 | ); 89 | 90 | const fetchContractBalance = async () => { 91 | try { 92 | const balance = await sdk?.getBalance(DEX_ADDRESS); 93 | setContractBalance(balance?.displayValue || "0"); 94 | } catch (err) { 95 | console.error(err); 96 | } 97 | }; 98 | 99 | const executeSwap = async () => { 100 | setLoading(true); 101 | if (isMismatched) { 102 | switchChain(ACTIVE_CHAIN.chainId); 103 | setLoading(false); 104 | return; 105 | } 106 | try { 107 | if (currentFrom === "native") { 108 | await swapNativeToToken({ overrides: { value: toWei(nativeValue) } }); 109 | toast({ 110 | status: "success", 111 | title: "Swap Successful", 112 | description: `You have successfully swapped your ${ 113 | ACTIVE_CHAIN.nativeCurrency.symbol 114 | } to ${symbol || "tokens"}.`, 115 | }); 116 | } else { 117 | // Approve token spending 118 | await approveTokenSpending({ args: [DEX_ADDRESS, toWei(tokenValue)] }); 119 | // Swap! 120 | await swapTokenToNative({ args: [toWei(tokenValue)] }); 121 | toast({ 122 | status: "success", 123 | title: "Swap Successful", 124 | description: `You have successfully swapped your ${ 125 | symbol || "tokens" 126 | } to ${ACTIVE_CHAIN.nativeCurrency.symbol}.`, 127 | }); 128 | } 129 | setLoading(false); 130 | } catch (err) { 131 | console.error(err); 132 | toast({ 133 | status: "error", 134 | title: "Swap Failed", 135 | description: 136 | "There was an error performing the swap. Please try again.", 137 | }); 138 | setLoading(false); 139 | } 140 | }; 141 | 142 | useEffect(() => { 143 | fetchContractBalance(); 144 | setInterval(fetchContractBalance, 10000); 145 | }, []); 146 | 147 | useEffect(() => { 148 | if (!amountToGet) return; 149 | if (currentFrom === "native") { 150 | setTokenValue(toEther(amountToGet)); 151 | } else { 152 | setNativeValue(toEther(amountToGet)); 153 | } 154 | }, [amountToGet]); 155 | 156 | return ( 157 | <> 158 | 159 | Decentralised Exchange 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 179 | 183 | 191 | 192 | 203 | 204 | 212 | 213 | 214 | {address ? ( 215 | 225 | ) : ( 226 | 230 | )} 231 | 232 | 233 | ); 234 | } 235 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thirdweb-example/dex-app/a5ef43eba5536cd8b1d4f77edd5b7d5c5a15f4e6/public/favicon.ico -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/thirteen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thirdweb-example/dex-app/a5ef43eba5536cd8b1d4f77edd5b7d5c5a15f4e6/public/token.png -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/Home.module.css: -------------------------------------------------------------------------------- 1 | .main { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: space-between; 5 | align-items: center; 6 | padding: 6rem; 7 | min-height: 100vh; 8 | } 9 | 10 | .description { 11 | display: inherit; 12 | justify-content: inherit; 13 | align-items: inherit; 14 | font-size: 0.85rem; 15 | max-width: var(--max-width); 16 | width: 100%; 17 | z-index: 2; 18 | font-family: var(--font-mono); 19 | } 20 | 21 | .description a { 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | gap: 0.5rem; 26 | } 27 | 28 | .description p { 29 | position: relative; 30 | margin: 0; 31 | padding: 1rem; 32 | background-color: rgba(var(--callout-rgb), 0.5); 33 | border: 1px solid rgba(var(--callout-border-rgb), 0.3); 34 | border-radius: var(--border-radius); 35 | } 36 | 37 | .code { 38 | font-weight: 700; 39 | font-family: var(--font-mono); 40 | } 41 | 42 | .grid { 43 | display: grid; 44 | grid-template-columns: repeat(4, minmax(25%, auto)); 45 | width: var(--max-width); 46 | max-width: 100%; 47 | } 48 | 49 | .card { 50 | padding: 1rem 1.2rem; 51 | border-radius: var(--border-radius); 52 | background: rgba(var(--card-rgb), 0); 53 | border: 1px solid rgba(var(--card-border-rgb), 0); 54 | transition: background 200ms, border 200ms; 55 | } 56 | 57 | .card span { 58 | display: inline-block; 59 | transition: transform 200ms; 60 | } 61 | 62 | .card h2 { 63 | font-weight: 600; 64 | margin-bottom: 0.7rem; 65 | } 66 | 67 | .card p { 68 | margin: 0; 69 | opacity: 0.6; 70 | font-size: 0.9rem; 71 | line-height: 1.5; 72 | max-width: 30ch; 73 | } 74 | 75 | .center { 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | position: relative; 80 | padding: 4rem 0; 81 | } 82 | 83 | .center::before { 84 | background: var(--secondary-glow); 85 | border-radius: 50%; 86 | width: 480px; 87 | height: 360px; 88 | margin-left: -400px; 89 | } 90 | 91 | .center::after { 92 | background: var(--primary-glow); 93 | width: 240px; 94 | height: 180px; 95 | z-index: -1; 96 | } 97 | 98 | .center::before, 99 | .center::after { 100 | content: ''; 101 | left: 50%; 102 | position: absolute; 103 | filter: blur(45px); 104 | transform: translateZ(0); 105 | } 106 | 107 | .logo, 108 | .thirteen { 109 | position: relative; 110 | } 111 | 112 | .thirteen { 113 | display: flex; 114 | justify-content: center; 115 | align-items: center; 116 | width: 75px; 117 | height: 75px; 118 | padding: 25px 10px; 119 | margin-left: 16px; 120 | transform: translateZ(0); 121 | border-radius: var(--border-radius); 122 | overflow: hidden; 123 | box-shadow: 0px 2px 8px -1px #0000001a; 124 | } 125 | 126 | .thirteen::before, 127 | .thirteen::after { 128 | content: ''; 129 | position: absolute; 130 | z-index: -1; 131 | } 132 | 133 | /* Conic Gradient Animation */ 134 | .thirteen::before { 135 | animation: 6s rotate linear infinite; 136 | width: 200%; 137 | height: 200%; 138 | background: var(--tile-border); 139 | } 140 | 141 | /* Inner Square */ 142 | .thirteen::after { 143 | inset: 0; 144 | padding: 1px; 145 | border-radius: var(--border-radius); 146 | background: linear-gradient( 147 | to bottom right, 148 | rgba(var(--tile-start-rgb), 1), 149 | rgba(var(--tile-end-rgb), 1) 150 | ); 151 | background-clip: content-box; 152 | } 153 | 154 | /* Enable hover only on non-touch devices */ 155 | @media (hover: hover) and (pointer: fine) { 156 | .card:hover { 157 | background: rgba(var(--card-rgb), 0.1); 158 | border: 1px solid rgba(var(--card-border-rgb), 0.15); 159 | } 160 | 161 | .card:hover span { 162 | transform: translateX(4px); 163 | } 164 | } 165 | 166 | @media (prefers-reduced-motion) { 167 | .thirteen::before { 168 | animation: none; 169 | } 170 | 171 | .card:hover span { 172 | transform: none; 173 | } 174 | } 175 | 176 | /* Mobile */ 177 | @media (max-width: 700px) { 178 | .content { 179 | padding: 4rem; 180 | } 181 | 182 | .grid { 183 | grid-template-columns: 1fr; 184 | margin-bottom: 120px; 185 | max-width: 320px; 186 | text-align: center; 187 | } 188 | 189 | .card { 190 | padding: 1rem 2.5rem; 191 | } 192 | 193 | .card h2 { 194 | margin-bottom: 0.5rem; 195 | } 196 | 197 | .center { 198 | padding: 8rem 0 6rem; 199 | } 200 | 201 | .center::before { 202 | transform: none; 203 | height: 300px; 204 | } 205 | 206 | .description { 207 | font-size: 0.8rem; 208 | } 209 | 210 | .description a { 211 | padding: 1rem; 212 | } 213 | 214 | .description p, 215 | .description div { 216 | display: flex; 217 | justify-content: center; 218 | position: fixed; 219 | width: 100%; 220 | } 221 | 222 | .description p { 223 | align-items: center; 224 | inset: 0 0 auto; 225 | padding: 2rem 1rem 1.4rem; 226 | border-radius: 0; 227 | border: none; 228 | border-bottom: 1px solid rgba(var(--callout-border-rgb), 0.25); 229 | background: linear-gradient( 230 | to bottom, 231 | rgba(var(--background-start-rgb), 1), 232 | rgba(var(--callout-rgb), 0.5) 233 | ); 234 | background-clip: padding-box; 235 | backdrop-filter: blur(24px); 236 | } 237 | 238 | .description div { 239 | align-items: flex-end; 240 | pointer-events: none; 241 | inset: auto 0 0; 242 | padding: 2rem; 243 | height: 200px; 244 | background: linear-gradient( 245 | to bottom, 246 | transparent 0%, 247 | rgb(var(--background-end-rgb)) 40% 248 | ); 249 | z-index: 1; 250 | } 251 | } 252 | 253 | /* Tablet and Smaller Desktop */ 254 | @media (min-width: 701px) and (max-width: 1120px) { 255 | .grid { 256 | grid-template-columns: repeat(2, 50%); 257 | } 258 | } 259 | 260 | @media (prefers-color-scheme: dark) { 261 | .vercelLogo { 262 | filter: invert(1); 263 | } 264 | 265 | .logo, 266 | .thirteen img { 267 | filter: invert(1) drop-shadow(0 0 0.3rem #ffffff70); 268 | } 269 | } 270 | 271 | @keyframes rotate { 272 | from { 273 | transform: rotate(360deg); 274 | } 275 | to { 276 | transform: rotate(0deg); 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --max-width: 1100px; 3 | --border-radius: 12px; 4 | --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 5 | 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 6 | 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; 7 | 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | 12 | --primary-glow: conic-gradient( 13 | from 180deg at 50% 50%, 14 | #16abff33 0deg, 15 | #0885ff33 55deg, 16 | #54d6ff33 120deg, 17 | #0071ff33 160deg, 18 | transparent 360deg 19 | ); 20 | --secondary-glow: radial-gradient( 21 | rgba(255, 255, 255, 1), 22 | rgba(255, 255, 255, 0) 23 | ); 24 | 25 | --tile-start-rgb: 239, 245, 249; 26 | --tile-end-rgb: 228, 232, 233; 27 | --tile-border: conic-gradient( 28 | #00000080, 29 | #00000040, 30 | #00000030, 31 | #00000020, 32 | #00000010, 33 | #00000010, 34 | #00000080 35 | ); 36 | 37 | --callout-rgb: 238, 240, 241; 38 | --callout-border-rgb: 172, 175, 176; 39 | --card-rgb: 180, 185, 188; 40 | --card-border-rgb: 131, 134, 135; 41 | } 42 | 43 | @media (prefers-color-scheme: dark) { 44 | :root { 45 | --foreground-rgb: 255, 255, 255; 46 | --background-start-rgb: 0, 0, 0; 47 | --background-end-rgb: 0, 0, 0; 48 | 49 | --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); 50 | --secondary-glow: linear-gradient( 51 | to bottom right, 52 | rgba(1, 65, 255, 0), 53 | rgba(1, 65, 255, 0), 54 | rgba(1, 65, 255, 0.3) 55 | ); 56 | 57 | --tile-start-rgb: 2, 13, 46; 58 | --tile-end-rgb: 2, 5, 19; 59 | --tile-border: conic-gradient( 60 | #ffffff80, 61 | #ffffff40, 62 | #ffffff30, 63 | #ffffff20, 64 | #ffffff10, 65 | #ffffff10, 66 | #ffffff80 67 | ); 68 | 69 | --callout-rgb: 20, 20, 20; 70 | --callout-border-rgb: 108, 108, 108; 71 | --card-rgb: 100, 100, 100; 72 | --card-border-rgb: 200, 200, 200; 73 | } 74 | } 75 | 76 | * { 77 | box-sizing: border-box; 78 | padding: 0; 79 | margin: 0; 80 | } 81 | 82 | html, 83 | body { 84 | max-width: 100vw; 85 | overflow-x: hidden; 86 | } 87 | 88 | body { 89 | color: rgb(var(--foreground-rgb)); 90 | background: linear-gradient( 91 | to bottom, 92 | transparent, 93 | rgb(var(--background-end-rgb)) 94 | ) 95 | rgb(var(--background-start-rgb)); 96 | } 97 | 98 | a { 99 | color: inherit; 100 | text-decoration: none; 101 | } 102 | 103 | @media (prefers-color-scheme: dark) { 104 | html { 105 | color-scheme: dark; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "paths": { 18 | "@/*": ["./*"] 19 | } 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"] 23 | } 24 | --------------------------------------------------------------------------------