├── .gitignore ├── README.md ├── craco.config.js ├── note.txt ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.tsx ├── components │ ├── AssetLogo.tsx │ ├── ItemList.tsx │ ├── Spinner.tsx │ └── TradeButton.tsx ├── index.css ├── index.tsx ├── layout │ ├── index.tsx │ └── topnavigation │ │ └── index.js ├── pages │ ├── Home.tsx │ ├── Login.tsx │ └── Trade.tsx ├── react-app-env.d.ts ├── services │ └── assetApi.tsx └── store │ ├── assetSlice.tsx │ ├── authSlice.tsx │ ├── index.tsx │ └── pageLimitSlice.tsx ├── tailwind.config.js ├── tsconfig.json └── yarn.lock /.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 | # production 12 | /build 13 | 14 | # misc 15 | .note 16 | .DS_Store 17 | .env 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React crypto trading app 2 | 3 | 4 | ## Installation 5 | 6 | Clone the repository, change directories, and use yarn to install the dependencies. 7 | 8 | ```bash 9 | $ git clone https://github.com/talentedev/react-crypto-trade.git 10 | $ cd react-crypto-trade 11 | $ yarn 12 | ``` 13 | 14 | ## Usage 15 | 16 | - start development server with `yarn start` 17 | 18 | * `yarn start` 19 | 20 | now The project can be viewed in the browser at 21 | 22 | - [http://localhost:3000](http://localhost:3000) 23 | 24 | ## Credential 25 | ``` 26 | email: test@email.com 27 | password: password 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | style: { 3 | postcss: { 4 | plugins: [require("tailwindcss"), require("autoprefixer")], 5 | }, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /note.txt: -------------------------------------------------------------------------------- 1 | // const url = 2 | // "https://min-api.cryptocompare.com/data/v2/histoday?fsym=BTC&tsym=USD&limit=180"; 3 | // axios 4 | // .get(url) 5 | // .then((res) => { 6 | // //console.log(res.data); 7 | // }) 8 | // .catch((err) => console.error(err)); 9 | 10 | 11 | 12 | //StreamQuote("BTC"); 13 | //StreamPrice("bitcoin"); 14 | //StreamPrices(); 15 | //SearchAsset("BTC2", "bitcoin2"); 16 | 17 | // dispacth(getTopAssets("bitcoin")); 18 | GetAsset("bitcoin"); 19 | //dispacth(getAsset("bitcoin")); 20 | 21 | //const { data, error } = useGetDailyOHLCVQuery(180); 22 | //console.log(error, data?.Data); 23 | 24 | // const { data, error } = useGetStatsQuery("BTC"); 25 | // console.log(error, data?.RAW["BTC"].USD); 26 | 27 | // const { data, error } = useGetTradingSignalsQuery("BTC"); 28 | // console.log(error, data?.Data); 29 | 30 | // const { data, error } = useGetNewsQuery(""); 31 | // console.log(error, data?.Data); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-crypto-trade", 3 | "version": "0.1.0", 4 | "dependencies": { 5 | "@ant-design/icons": "^4.7.0", 6 | "@craco/craco": "^6.3.0", 7 | "@headlessui/react": "^1.4.3", 8 | "@heroicons/react": "^1.0.5", 9 | "@reduxjs/toolkit": "^1.5.1", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "@types/jest": "^24.0.0", 14 | "@types/node": "^12.0.0", 15 | "@types/react": "^16.9.0", 16 | "@types/react-dom": "^16.9.0", 17 | "@types/react-redux": "^7.1.7", 18 | "@types/react-router-dom": "^5.3.0", 19 | "@types/react-tabs": "^2.3.3", 20 | "axios": "^0.22.0", 21 | "chart.js": "^3.5.1", 22 | "millify": "^4.0.0", 23 | "moment": "^2.29.1", 24 | "numeral": "^2.0.6", 25 | "react": "^17.0.2", 26 | "react-chartjs-2": "^3.0.5", 27 | "react-dom": "^17.0.2", 28 | "react-hook-form": "^7.17.1", 29 | "react-icons": "^4.3.1", 30 | "react-redux": "^7.2.0", 31 | "react-router-dom": "^5.3.0", 32 | "react-scripts": "4.0.3", 33 | "react-tabs": "^3.2.2", 34 | "recharts": "^2.1.4", 35 | "typescript": "~4.1.5", 36 | "zustand": "^3.6.9" 37 | }, 38 | "scripts": { 39 | "start": "craco start", 40 | "build": "craco build", 41 | "test": "craco test", 42 | "eject": "react-scripts eject" 43 | }, 44 | "eslintConfig": { 45 | "extends": "react-app" 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "@types/numeral": "^2.0.2", 61 | "autoprefixer": "^9.8.7", 62 | "postcss": "^7.0.38", 63 | "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.16" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VadymCodes/CryptoTrade/d6b51222a2090c11d5fa1c64d948f7fb5af3044b/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React Redux App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VadymCodes/CryptoTrade/d6b51222a2090c11d5fa1c64d948f7fb5af3044b/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VadymCodes/CryptoTrade/d6b51222a2090c11d5fa1c64d948f7fb5af3044b/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Switch, Redirect, Route } from "react-router-dom"; 2 | import MasterLayout from "./layout"; 3 | import Home from "./pages/Home"; 4 | import Trade from "./pages/Trade"; 5 | import Login from "./pages/Login"; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /src/components/AssetLogo.tsx: -------------------------------------------------------------------------------- 1 | interface AssetLogoProps { 2 | id: string; 3 | } 4 | 5 | const AssetLogo = ({id}: AssetLogoProps) => { 6 | const url = `https://messari.io/asset-images/${id}/16.png`; 7 | 8 | return ( 9 | asset logo 10 | ) 11 | } 12 | 13 | export default AssetLogo; -------------------------------------------------------------------------------- /src/components/ItemList.tsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from 'react' 2 | import { Listbox, Transition } from '@headlessui/react' 3 | import { CheckIcon, SelectorIcon } from '@heroicons/react/solid' 4 | 5 | import useStore from "../store" 6 | 7 | const list = [ 8 | { name: 10 }, 9 | { name: 30 }, 10 | { name: 50 }, 11 | ] 12 | 13 | export default function ItemList() { 14 | const [selected, setSelected] = useState(list[0]) 15 | const setLimit = useStore(state => state.setLimit); 16 | 17 | function handleChange(e: any) { 18 | setSelected(e); 19 | setLimit(e.name); 20 | } 21 | 22 | return ( 23 |
24 | 25 |
26 | 27 | {selected.name} 28 | 29 | 34 | 35 | 41 | 42 | {list.map((item, itemIdx) => ( 43 | 46 | `${active ? 'text-amber-900 bg-amber-100' : 'text-gray-900'} 47 | cursor-default select-none relative py-2 pl-10 pr-4` 48 | } 49 | value={item} 50 | > 51 | {({ selected, active }) => ( 52 | <> 53 | 58 | {item.name} 59 | 60 | {selected ? ( 61 | 67 | 69 | ) : null} 70 | 71 | )} 72 | 73 | ))} 74 | 75 | 76 |
77 |
78 |
79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/components/Spinner.tsx: -------------------------------------------------------------------------------- 1 | const Spinner = () => { 2 | return
; 3 | }; 4 | 5 | export default Spinner; 6 | -------------------------------------------------------------------------------- /src/components/TradeButton.tsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from '@headlessui/react' 2 | import { Fragment } from 'react' 3 | import { ChevronDownIcon } from '@heroicons/react/solid' 4 | 5 | export default function TradeButton() { 6 | return ( 7 |
8 | 9 |
10 | 11 | Buy/Sell 12 | 17 |
18 | 27 | 28 |
29 | 30 | {({ active }) => ( 31 | 38 | )} 39 | 40 | 41 | {({ active }) => ( 42 | 49 | )} 50 | 51 |
52 |
53 |
54 |
55 |
56 | ) 57 | } 58 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | .spinner-5 { 6 | width: 50px; 7 | height: 50px; 8 | display: grid; 9 | border: 4px solid #0000; 10 | border-radius: 50%; 11 | border-right-color: #25b09b; 12 | animation: s5 1s infinite linear; 13 | } 14 | .spinner-5::before, 15 | .spinner-5::after { 16 | content: ""; 17 | grid-area: 1/1; 18 | margin: 2px; 19 | border: inherit; 20 | border-radius: 50%; 21 | animation: s5 2s infinite; 22 | } 23 | .spinner-5::after { 24 | margin: 8px; 25 | animation-duration: 3s; 26 | } 27 | 28 | @keyframes s5 { 29 | 100% { 30 | transform: rotate(1turn); 31 | } 32 | } 33 | 34 | .spinner-6 { 35 | width: 100px; 36 | height: 100px; 37 | display: grid; 38 | border: 4px solid #0000; 39 | border-radius: 50%; 40 | border-color: #1e3a8a #0000; 41 | animation: s6 1s infinite linear; 42 | } 43 | .spinner-6::before, 44 | .spinner-6::after { 45 | content: ""; 46 | grid-area: 1/1; 47 | margin: 2px; 48 | border: inherit; 49 | border-radius: 50%; 50 | } 51 | .spinner-6::before { 52 | border-color: #34d399 #0000; 53 | animation: inherit; 54 | animation-duration: 0.5s; 55 | animation-direction: reverse; 56 | } 57 | .spinner-6::after { 58 | margin: 8px; 59 | } 60 | 61 | @keyframes s6 { 62 | 100% { 63 | transform: rotate(1turn); 64 | } 65 | } 66 | 67 | * { 68 | box-sizing: border-box; 69 | } 70 | 71 | input::-webkit-outer-spin-button, 72 | input::-webkit-inner-spin-button { 73 | /* display: none; <- Crashes Chrome on hover */ 74 | -webkit-appearance: none; 75 | margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ 76 | } 77 | 78 | input[type=number] { 79 | -moz-appearance:textfield; /* Firefox */ 80 | } 81 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import { BrowserRouter } from "react-router-dom"; 4 | import App from "./App"; 5 | import "./index.css"; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById("root") 14 | ); 15 | -------------------------------------------------------------------------------- /src/layout/index.tsx: -------------------------------------------------------------------------------- 1 | import TopNavigation from "./topnavigation"; 2 | 3 | const style = { 4 | container: "bg-gray-900 h-screen overflow-hidden relative", 5 | mainContainer: "flex flex-col h-screen pl-0 w-full lg:space-y-4", 6 | main: "h-screen overflow-auto pb-36 pt-4 px-2 md:pb-8 md:pt-4 lg:pt-0 lg:px-4", 7 | }; 8 | 9 | const MasterLayout: React.FC = ({ children, ...props }) => { 10 | return ( 11 |
12 |
13 |
14 | 15 |
{children}
16 |
17 |
18 |
19 | ); 20 | }; 21 | 22 | export default MasterLayout; 23 | -------------------------------------------------------------------------------- /src/layout/topnavigation/index.js: -------------------------------------------------------------------------------- 1 | import { Fragment, useState } from 'react' 2 | import { Link, useHistory } from "react-router-dom" 3 | import { Dialog, Transition } from '@headlessui/react' 4 | 5 | import useStore from "../../store"; 6 | 7 | export default function TopNavigation() { 8 | const history = useHistory(); 9 | let [isOpen, setIsOpen] = useState(false) 10 | let [email, setEmail] = useState('') 11 | let [password, setPassword] = useState('') 12 | let [invalidLogin, setInvalidLogin] = useState(false) 13 | const isLogged = useStore(state => state.isLogged) 14 | const setIsLogged = useStore(state => state.setIsLogged) 15 | 16 | function closeModal() { 17 | setIsOpen(false) 18 | } 19 | 20 | function openModal() { 21 | setIsOpen(true) 22 | } 23 | 24 | function handleEmailChange(e) { 25 | setEmail(e.target.value) 26 | } 27 | 28 | function handlePasswordChange(e) { 29 | setPassword(e.target.value) 30 | } 31 | 32 | function logout() { 33 | setIsLogged(false) 34 | } 35 | 36 | function login() { 37 | if(email === 'test@email.com' && password === 'password') { 38 | setIsLogged(true) 39 | setInvalidLogin(false) 40 | history.push("/home") 41 | closeModal() 42 | } else { 43 | setIsLogged(false) 44 | setInvalidLogin(true) 45 | } 46 | } 47 | 48 | function renderLoginForm() { 49 | return ( 50 | 51 | 56 |
57 | 66 | 67 | 68 | 69 | {/* This element is to trick the browser into centering the modal contents. */} 70 | 76 | 85 |
86 | 90 | Login 91 | 92 |
93 | 102 |
103 |
104 | 112 |
113 | 114 | {invalidLogin && (

Invalid email or password

)} 115 |
116 |
117 |
118 |
119 |
120 | ); 121 | } 122 | 123 | return ( 124 | <> 125 |
126 |
127 |
128 |
129 | 130 | Home 131 | 132 | 133 | Trade 134 | 135 |
136 |
137 |
138 | {isLogged ? ( 139 | 140 | Log out 141 | 142 | ): ( 143 | 144 | Log in 145 | 146 | )} 147 |
148 |
149 |
150 |
151 |
152 | {renderLoginForm()} 153 | 154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | import moment from "moment"; 4 | import millify from "millify"; 5 | 6 | import useStore from "../store"; 7 | import AssetLogo from "../components/AssetLogo"; 8 | import Spinner from "../components/Spinner"; 9 | import ItemList from "../components/ItemList"; 10 | import TradeButton from "../components/TradeButton"; 11 | 12 | const Home = () => { 13 | const history = useHistory(); 14 | const today = moment(new Date()).format("DD/MM/YYYY"); 15 | 16 | const isLogged = useStore(state => state.isLogged); 17 | const assets = useStore(state => state.assets); 18 | const loading = useStore(state => state.loading); 19 | const pageLimit = useStore(state => state.pageLimit); 20 | const fetchAssets = useStore(state => state.fetchAssets); 21 | 22 | // Check if user is logged in 23 | useEffect(() => { 24 | if(!isLogged) 25 | history.push("/login"); 26 | }); 27 | 28 | // Get all crypto assets 29 | useEffect(() => { 30 | fetchAssets(pageLimit); 31 | }, [fetchAssets, pageLimit]); 32 | 33 | return ( 34 |
35 |
36 |
37 |

Crypto

38 |

{today}

39 |
40 | 41 |
42 | 43 |
44 | 45 |
46 | {!loading ? ( 47 | assets && assets?.map( 48 | (crypto: { 49 | id: string; 50 | name: string; 51 | symbol: string; 52 | metrics: any; 53 | }, index) => ( 54 |
58 |
59 |
60 | 61 | {index + 1}. 62 | 63 | 64 | 65 | {crypto.symbol} - {crypto.name} 66 | 67 |
68 |
69 |
70 | Price: 71 | {millify(crypto.metrics.market_data.price_usd)} 72 |
73 |
74 | Market Cap: 75 | {millify(crypto.metrics.marketcap.current_marketcap_usd)} 76 |
77 |
78 |
79 | 80 |
81 |
82 |
83 | ) 84 | )):
} 85 |
86 |
87 |
88 | ); 89 | }; 90 | 91 | export default Home; 92 | -------------------------------------------------------------------------------- /src/pages/Login.tsx: -------------------------------------------------------------------------------- 1 | const Trade = () => { 2 | 3 | return ( 4 | <> 5 |
6 |
7 |
8 | You logged out. Please login again. 9 |
10 |
11 |
12 | 13 | ); 14 | }; 15 | 16 | export default Trade; 17 | -------------------------------------------------------------------------------- /src/pages/Trade.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { useHistory } from "react-router-dom"; 3 | 4 | import useStore from "../store"; 5 | 6 | const Trade = () => { 7 | const history = useHistory(); 8 | const isLogged = useStore(state => state.isLogged); 9 | const [asset, setAsset] = useState(null); 10 | const [crypto, setCtypto] = useState(0); 11 | const [fiat, setFiat] = useState(0); 12 | const [price, setPrice] = useState(0); 13 | const assets = useStore(state => state.assets); 14 | const pageLimit = useStore(state => state.pageLimit); 15 | const fetchAssets = useStore(state => state.fetchAssets); 16 | 17 | // Check if user is logged in 18 | useEffect(() => { 19 | if(!isLogged) 20 | history.push("/login"); 21 | 22 | if(assets && price === 0){ 23 | // @ts-ignore 24 | setPrice(assets[0].metrics.market_data.price_usd); 25 | } 26 | }); 27 | 28 | // Fetch assets data 29 | useEffect(() => { 30 | fetchAssets(pageLimit); 31 | 32 | }, [fetchAssets, pageLimit, assets]); 33 | 34 | function changeAsset(e: any) { 35 | setFiat(e.target.value * crypto); 36 | setPrice(e.target.value); 37 | } 38 | 39 | function changeCrypto(e: any) { 40 | setCtypto(e.target.value); 41 | // @ts-ignore 42 | // const price = asset.metrics.market_data.price_usd; 43 | setFiat(e.target.value * price); 44 | } 45 | 46 | function changeFiat(e: any) { 47 | setFiat(e.target.value); 48 | // @ts-ignore 49 | const price = asset.metrics.market_data.price_usd; 50 | setCtypto(e.target.value / price); 51 | } 52 | 53 | return ( 54 | <> 55 |
56 |
57 |
58 |

Swap Crypto

59 |
60 | 69 |
70 | 73 | 90 |
91 |
92 |
93 |
94 | $ 95 |
96 | 105 |
106 | 107 |
108 |
109 |
110 | 111 | ); 112 | }; 113 | 114 | export default Trade; 115 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/services/assetApi.tsx: -------------------------------------------------------------------------------- 1 | const endpoint = "https://data.messari.io/api/v1"; 2 | 3 | export const getAssets = (limit: number = 10) => { 4 | return fetch(`${endpoint}/assets?limit=${limit}`) 5 | }; 6 | -------------------------------------------------------------------------------- /src/store/assetSlice.tsx: -------------------------------------------------------------------------------- 1 | import { getAssets } from "../services/assetApi"; 2 | 3 | const createAssetSlice = (set: any, get: any) => ({ 4 | loading: false, 5 | assets: [], 6 | fetchAssets: async (limit: number) => { 7 | set({ loading: true }); 8 | const response = await getAssets(limit); 9 | const assets = await response.json(); 10 | set({ assets: assets.data }); 11 | set({ loading: false }); 12 | } 13 | }); 14 | 15 | export default createAssetSlice; -------------------------------------------------------------------------------- /src/store/authSlice.tsx: -------------------------------------------------------------------------------- 1 | const createAuthSlice = (set: any, get: any) => ({ 2 | isLogged: false, 3 | setIsLogged: (isLogged: boolean) => { 4 | set({ isLogged: isLogged }); 5 | } 6 | }); 7 | 8 | export default createAuthSlice; -------------------------------------------------------------------------------- /src/store/index.tsx: -------------------------------------------------------------------------------- 1 | import create from 'zustand'; 2 | 3 | import createAssetSlice from './assetSlice'; 4 | import createPageLimitSlice from './pageLimitSlice'; 5 | import createAuthSlice from './authSlice'; 6 | 7 | const useStore = create((set, get) => ({ 8 | ...createAssetSlice(set, get), 9 | ...createPageLimitSlice(set, get), 10 | ...createAuthSlice(set, get), 11 | })); 12 | 13 | export default useStore; 14 | -------------------------------------------------------------------------------- /src/store/pageLimitSlice.tsx: -------------------------------------------------------------------------------- 1 | const createPageLimitSlice = (set: any, get: any) => ({ 2 | pageLimit: 10, 3 | setLimit: (pageLimit: number) => { 4 | set({ pageLimit: pageLimit }); 5 | } 6 | }); 7 | 8 | export default createPageLimitSlice; -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ["./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: {}, 6 | }, 7 | variants: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"] 20 | } 21 | --------------------------------------------------------------------------------