├── .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 |
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 |
33 |
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 |
68 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------