├── .eslintrc.json ├── .gitignore ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── imagens │ └── logo.svg └── vercel.svg ├── src ├── data │ ├── @types │ │ ├── Pet.ts │ │ └── Relatorio.ts │ ├── hooks │ │ └── pages │ │ │ ├── pets │ │ │ ├── useCadastro.ts │ │ │ └── useRelatorio.ts │ │ │ └── useIndex.ts │ └── services │ │ ├── ApiService.ts │ │ └── TextService.ts ├── pages │ ├── _app.tsx │ ├── index.tsx │ └── pets │ │ ├── cadastro.tsx │ │ └── relatorio.tsx └── ui │ ├── components │ ├── Cabecalho │ │ ├── Cabecalho.style.tsx │ │ └── Cabecalho.tsx │ ├── CabecalhoAdmin │ │ ├── CabecalhoAdmin.style.tsx │ │ └── CabecalhoAdmin.tsx │ ├── Lista │ │ ├── Lista.style.tsx │ │ └── Lista.tsx │ └── Titulo │ │ ├── Titulo.style.tsx │ │ └── Titulo.tsx │ ├── styles │ └── globals.css │ └── themes │ └── tema.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TreinaWeb: ["Workshop Multi-stack 03"](https://www.treinaweb.com.br/painel/multi-stack) 2 | 3 | ## Projeto "Adote um Pet" (React + Next.js) 4 | 5 | #### Lista de branches 6 | 7 | | | Branch | Descrição | 8 | | -------------------------------------------------------------------------------------------- | ------ | --------- | 9 | | [Download](https://github.com/treinaweb/treinaweb-workshop-multistack-react-03-pets/archive/v01.zip) | v01 | Versão 01 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /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": "pets", 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 | "@emotion/react": "^11.8.1", 13 | "@emotion/styled": "^11.8.1", 14 | "@mui/material": "^5.4.3", 15 | "axios": "^0.26.0", 16 | "next": "12.1.0", 17 | "react": "17.0.2", 18 | "react-dom": "17.0.2" 19 | }, 20 | "devDependencies": { 21 | "@types/node": "17.0.19", 22 | "@types/react": "17.0.39", 23 | "eslint": "8.9.0", 24 | "eslint-config-next": "12.1.0", 25 | "typescript": "4.5.5" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/treinaweb/workshop-multistack-adote-um-pet-react/80210619a28a66f1dec032dddf3868a038cbb59b/public/favicon.ico -------------------------------------------------------------------------------- /public/imagens/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /src/data/@types/Pet.ts: -------------------------------------------------------------------------------- 1 | export interface Pet { 2 | id: number; 3 | nome: string; 4 | historia: string; 5 | foto: string; 6 | } -------------------------------------------------------------------------------- /src/data/@types/Relatorio.ts: -------------------------------------------------------------------------------- 1 | import { Pet } from './Pet'; 2 | 3 | export interface Relatorio{ 4 | id: number; 5 | email: string; 6 | valor: string; 7 | pet: Pet; 8 | } -------------------------------------------------------------------------------- /src/data/hooks/pages/pets/useCadastro.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { ApiService } from '../../../services/ApiService'; 3 | import { AxiosError } from 'axios'; 4 | 5 | export function useCadastro(){ 6 | const [nome, setNome] = useState(''), 7 | [historia, setHistoria] = useState(''), 8 | [foto, setFoto] = useState(''), 9 | [mensagem, setMensagem] = useState(''); 10 | 11 | function cadastrar(){ 12 | if(validarFormulario()){ 13 | ApiService.post('/pets', { 14 | nome, 15 | historia, 16 | foto 17 | }) 18 | .then(() => { 19 | limpar(); 20 | setMensagem('Pet cadastrado com sucesso!') 21 | }) 22 | .catch((error: AxiosError) => { 23 | setMensagem(error.response?.data.message); 24 | }) 25 | } else { 26 | setMensagem('Preencha todos os campos!'); 27 | } 28 | } 29 | 30 | function validarFormulario(){ 31 | return nome.length > 2 && historia.length > 20 && foto.length > 5; 32 | } 33 | 34 | function limpar(){ 35 | setNome(''); 36 | setHistoria(''); 37 | setFoto(''); 38 | } 39 | 40 | return { 41 | nome, 42 | historia, 43 | foto, 44 | setNome, 45 | setHistoria, 46 | setFoto, 47 | cadastrar, 48 | mensagem, 49 | setMensagem 50 | } 51 | } -------------------------------------------------------------------------------- /src/data/hooks/pages/pets/useRelatorio.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { Relatorio } from "../../../@types/Relatorio"; 3 | import { ApiService } from "../../../services/ApiService"; 4 | 5 | export function useRelatorio(){ 6 | const [listaRelatorio, setListaRelatorio] = useState([]); 7 | 8 | useEffect(() => { 9 | ApiService.get('/adocoes').then((resposta) => { 10 | setListaRelatorio(resposta.data); 11 | }) 12 | }, []) 13 | 14 | return { 15 | listaRelatorio 16 | } 17 | } -------------------------------------------------------------------------------- /src/data/hooks/pages/useIndex.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { Pet } from '../../@types/Pet'; 3 | import { ApiService } from '../../services/ApiService'; 4 | import { AxiosError } from 'axios'; 5 | 6 | 7 | export function useIndex(){ 8 | const [listaPets, setListaPets] = useState([]), 9 | [petSelecionado, setPetSelecionado] = useState(null), 10 | [email, setEmail] = useState(''), 11 | [valor, setValor] = useState(''), 12 | [mensagem, setMensagem] = useState(''); 13 | 14 | useEffect(() => { 15 | ApiService.get('/pets') 16 | .then((resposta) => { 17 | setListaPets(resposta.data); 18 | }) 19 | }, []) 20 | 21 | useEffect(() => { 22 | if(petSelecionado === null){ 23 | limparFormulario(); 24 | } 25 | }, [petSelecionado]) 26 | 27 | function adotar(){ 28 | if(petSelecionado !== null){ 29 | if(validarDadosAdocao()){ 30 | ApiService.post('/adocoes', { 31 | pet_id: petSelecionado.id, 32 | email, 33 | valor 34 | }) 35 | .then(() => { 36 | setPetSelecionado(null); 37 | setMensagem('Pet adotado com sucesso!'); 38 | // limparFormulario(); 39 | }) 40 | .catch((error: AxiosError) => { 41 | setMensagem(error.response?.data.message); 42 | }) 43 | } else { 44 | setMensagem('Preencha todos os campos corretamente!') 45 | } 46 | } 47 | } 48 | 49 | function validarDadosAdocao(){ 50 | return email.length > 0 && valor.length > 0; 51 | } 52 | 53 | function limparFormulario(){ 54 | setEmail(''); 55 | setValor(''); 56 | } 57 | 58 | return { 59 | listaPets, 60 | petSelecionado, 61 | setPetSelecionado, 62 | email, 63 | setEmail, 64 | valor, 65 | setValor, 66 | mensagem, 67 | setMensagem, 68 | adotar 69 | }; 70 | } -------------------------------------------------------------------------------- /src/data/services/ApiService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export const ApiService = axios.create({ 4 | baseURL: 'https://adote-um-pet-multistack.herokuapp.com/api', 5 | headers: { 6 | 'Content-Type': 'application/json' 7 | } 8 | }) 9 | 10 | -------------------------------------------------------------------------------- /src/data/services/TextService.ts: -------------------------------------------------------------------------------- 1 | export const TextService = { 2 | limitarTexto(texto: string, tamanhoMaximo: number): string{ 3 | if(texto.length < tamanhoMaximo){ 4 | return texto; 5 | } 6 | return texto.slice(0, tamanhoMaximo) + '...'; 7 | } 8 | } -------------------------------------------------------------------------------- /src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../ui/styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | import { ThemeProvider } from '@mui/material'; 4 | import tema from '../ui/themes/tema'; 5 | import Cabecalho from '../ui/components/Cabecalho/Cabecalho' 6 | import CabecalhoAdmin from '../ui/components/CabecalhoAdmin/CabecalhoAdmin' 7 | import { useRouter } from 'next/router'; 8 | 9 | function MyApp({ Component, pageProps }: AppProps) { 10 | const router = useRouter(); 11 | 12 | return ( 13 | 14 | {router.pathname === '/' ? : } 15 | 16 | 17 | ); 18 | } 19 | 20 | export default MyApp 21 | -------------------------------------------------------------------------------- /src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Titulo from '../ui/components/Titulo/Titulo'; 3 | import Lista from '../ui/components/Lista/Lista' 4 | import { Dialog, TextField, Grid, DialogActions, Button, Snackbar } from '@mui/material' 5 | import { useIndex } from '../data/hooks/pages/useIndex'; 6 | 7 | const Home: NextPage = () => { 8 | const { 9 | listaPets, 10 | petSelecionado, 11 | setPetSelecionado, 12 | email, 13 | setEmail, 14 | valor, 15 | setValor, 16 | mensagem, 17 | setMensagem, 18 | adotar 19 | } = useIndex(); 20 | 21 | return ( 22 |
23 | 27 | Com um pequeno valor mensal, você
28 | pode adotar um pet virtualmente 29 | 30 | } /> 31 | 32 | setPetSelecionado(pet)} 35 | /> 36 | 37 | 38 | setPetSelecionado(null)} 43 | > 44 | 45 | 46 | setEmail(e.target.value)} 52 | /> 53 | 54 | 55 | setValor(e.target.value)} 61 | /> 62 | 63 | 64 | 65 | 71 | 77 | 78 | 79 | 80 | 0} 82 | message={mensagem} 83 | autoHideDuration={2500} 84 | onClose={() => setMensagem('')} 85 | /> 86 |
87 | ) 88 | } 89 | 90 | export default Home 91 | -------------------------------------------------------------------------------- /src/pages/pets/cadastro.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | import { useCadastro } from '../../data/hooks/pages/pets/useCadastro'; 3 | import Titulo from '../../ui/components/Titulo/Titulo'; 4 | import { Paper, Grid, TextField, Button, Snackbar } from '@mui/material'; 5 | 6 | 7 | const Cadastro: NextPage = () => { 8 | const { 9 | nome, 10 | historia, 11 | foto, 12 | setNome, 13 | setHistoria, 14 | setFoto, 15 | cadastrar, 16 | mensagem, 17 | setMensagem 18 | } = useCadastro(); 19 | 20 | return ( 21 | <> 22 | 26 | 27 | 28 | 29 | 30 | setNome(e.target.value)} 33 | label={'Nome'} 34 | placeholder={'Digite o nome do pet'} 35 | fullWidth 36 | /> 37 | 38 | 39 | setHistoria(e.target.value)} 42 | label={'História do Pet'} 43 | multiline 44 | fullWidth 45 | rows={4} 46 | /> 47 | 48 | 49 | setFoto(e.target.value)} 52 | label={'Foto'} 53 | placeholder={'Digite o endereço da imagem'} 54 | fullWidth 55 | /> 56 | 66 | 67 | 68 | 76 | 77 | 78 | 79 | 80 | 0} 82 | autoHideDuration={2500} 83 | onClose={() => setMensagem('')} 84 | message={mensagem} 85 | /> 86 | 87 | ) 88 | } 89 | 90 | export default Cadastro; -------------------------------------------------------------------------------- /src/pages/pets/relatorio.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from "next"; 2 | import Titulo from '../../ui/components/Titulo/Titulo'; 3 | import { 4 | Paper, 5 | TableContainer, 6 | Table, 7 | TableHead, 8 | TableRow, 9 | TableCell, 10 | TableBody 11 | } from '@mui/material'; 12 | import { useRelatorio } from "../../data/hooks/pages/pets/useRelatorio"; 13 | 14 | const Relatorio: NextPage = () => { 15 | const { listaRelatorio } = useRelatorio(); 16 | return ( 17 | <> 18 | 22 | 26 | 27 | 28 | 29 | Pet 30 | E-mail 31 | Valor Mensal 32 | 33 | 34 | 35 | {listaRelatorio.map((relatorio) => ( 36 | 37 | {relatorio.pet.nome} 38 | {relatorio.email} 39 | {relatorio.valor} 40 | 41 | ))} 42 | 43 |
44 |
45 | 46 | ) 47 | } 48 | 49 | export default Relatorio; -------------------------------------------------------------------------------- /src/ui/components/Cabecalho/Cabecalho.style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material'; 2 | 3 | export const CabecalhoContainer = styled('header')` 4 | display: flex; 5 | justify-content: center; 6 | border-bottom: 1px solid #f0f0f0; 7 | padding: ${({theme}) => theme.spacing(6)}; 8 | `; 9 | 10 | 11 | 12 | export const Logo = styled('img')` 13 | width: 230px; 14 | `; -------------------------------------------------------------------------------- /src/ui/components/Cabecalho/Cabecalho.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | CabecalhoContainer, 3 | Logo 4 | } from './Cabecalho.style'; 5 | 6 | export default function Cabecalho(){ 7 | return( 8 | 9 | 10 | 11 | ) 12 | } -------------------------------------------------------------------------------- /src/ui/components/CabecalhoAdmin/CabecalhoAdmin.style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material'; 2 | 3 | export const CabecalhoContainer = styled('header')` 4 | height: 115px; 5 | background-color: #f6f6f6; 6 | padding: ${({ theme }) => theme.spacing(2) }; 7 | 8 | div { 9 | height: 100%; 10 | max-width: 970px; 11 | margin: 0 auto; 12 | display: flex; 13 | justify-content: space-between; 14 | align-items: center; 15 | gap: ${({ theme }) => theme.spacing(2) }; 16 | } 17 | 18 | a{ 19 | font-size: 14px; 20 | } 21 | `; 22 | 23 | export const Logo = styled('img')` 24 | width: 125px; 25 | `; 26 | 27 | export const LinksContainer = styled('nav')` 28 | display: flex; 29 | gap: ${({ theme }) => theme.spacing(2) }; 30 | flex-wrap: wrap; 31 | justify-content: flex-end; 32 | `; -------------------------------------------------------------------------------- /src/ui/components/CabecalhoAdmin/CabecalhoAdmin.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Box } from '@mui/material'; 2 | import NextLink from 'next/link'; 3 | import { 4 | CabecalhoContainer, 5 | Logo, 6 | LinksContainer 7 | } from './CabecalhoAdmin.style'; 8 | 9 | export default function CabecalhoAdmin(){ 10 | return ( 11 | 12 |
13 | 14 | 15 | 16 | Cadastrar Pet 17 | 18 | 19 | 20 | Relatório{' '} 21 | 25 | de Adoção 26 | 27 | 28 | 29 | 30 |
31 |
32 | ) 33 | } -------------------------------------------------------------------------------- /src/ui/components/Lista/Lista.style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material'; 2 | 3 | export const ListaStyled = styled('ul')` 4 | width: 100%; 5 | max-width: 800px; 6 | margin: 0 auto; 7 | padding: ${({ theme }) => theme.spacing(2) }; 8 | ` 9 | 10 | export const ItemLista = styled('li')` 11 | display: grid; 12 | grid-template-columns: repeat(2, 1fr); 13 | gap: ${({ theme }) => theme.spacing(5) }; 14 | margin-bottom: ${({ theme }) => theme.spacing(5) }; 15 | 16 | ${({ theme }) => theme.breakpoints.down('md') } { 17 | grid-template-columns: 1fr; 18 | gap: ${({ theme }) => theme.spacing(2) }; 19 | margin-bottom: ${({ theme }) => theme.spacing(10) }; 20 | } 21 | ` 22 | 23 | export const Foto = styled('img')` 24 | width: 100%; 25 | ` 26 | 27 | export const Informacoes = styled('div')` 28 | display: flex; 29 | flex-direction: column; 30 | gap: ${({ theme }) => theme.spacing(2) }; 31 | ` 32 | 33 | export const Nome = styled('h2')` 34 | margin: 0; 35 | `; 36 | 37 | export const Descricao = styled('p')` 38 | margin: 0; 39 | word-break: break-word; 40 | `; -------------------------------------------------------------------------------- /src/ui/components/Lista/Lista.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from '@mui/material' 2 | import { 3 | ListaStyled, 4 | ItemLista, 5 | Foto, 6 | Informacoes, 7 | Nome, 8 | Descricao 9 | } from './Lista.style' 10 | import { Pet } from '../../../data/@types/Pet' 11 | import { TextService } from '../../../data/services/TextService'; 12 | 13 | interface ListaProps{ 14 | pets: Pet[]; 15 | onSelect: (pet: Pet) => void; 16 | } 17 | 18 | export default function Lista(props: ListaProps){ 19 | const tamanhoMaximoTexto = 200; 20 | 21 | return ( 22 | 23 | {props.pets.map(pet => ( 24 | 25 | 26 | 27 | {pet.nome} 28 | 29 | {TextService.limitarTexto(pet.historia, tamanhoMaximoTexto)} 30 | 31 | 38 | 39 | 40 | ))} 41 | 42 | ) 43 | } -------------------------------------------------------------------------------- /src/ui/components/Titulo/Titulo.style.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from '@mui/material'; 2 | 3 | export const TituloStyled = styled('h1')` 4 | font-size: 20px; 5 | text-align: center; 6 | margin-top: ${({ theme }) => theme.spacing(5) }; 7 | `; 8 | 9 | export const Subtitulo = styled('h2')` 10 | font-size: 18px; 11 | text-align: center; 12 | margin-bottom: ${({ theme }) => theme.spacing(5) }; 13 | font-weight: normal; 14 | color: ${({ theme }) => theme.palette.text.secondary }; 15 | `; -------------------------------------------------------------------------------- /src/ui/components/Titulo/Titulo.tsx: -------------------------------------------------------------------------------- 1 | import { TituloStyled, Subtitulo } from "./Titulo.style"; 2 | 3 | interface TituloProps { 4 | titulo: string; 5 | subtitulo?: string | JSX.Element; 6 | } 7 | 8 | export default function Titulo(props: TituloProps){ 9 | return ( 10 | <> 11 | {props.titulo} 12 | {props.subtitulo} 13 | 14 | ) 15 | } -------------------------------------------------------------------------------- /src/ui/styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /src/ui/themes/tema.ts: -------------------------------------------------------------------------------- 1 | import { createTheme } from '@mui/material'; 2 | 3 | const tema = createTheme({ 4 | palette: { 5 | primary: { 6 | main: '#AE0FEA', 7 | }, 8 | secondary: { 9 | main: '#C5C5C5', 10 | }, 11 | text: { 12 | primary: '#293845', 13 | secondary: '#9EADBA', 14 | }, 15 | }, 16 | typography: { 17 | fontFamily: 'Roboto, sans-serif', 18 | }, 19 | shape: { 20 | borderRadius: '3px', 21 | }, 22 | components: { 23 | MuiButton: { 24 | styleOverrides: { 25 | root: { 26 | textTransform: 'none', 27 | borderRadius: '5px', 28 | fontWeight: 'normal', 29 | }, 30 | }, 31 | }, 32 | MuiPaper: { 33 | styleOverrides: { 34 | root: { 35 | boxShadow: '0px 0px 39px rgba(0, 0, 0, 0.05)', 36 | }, 37 | }, 38 | }, 39 | MuiTextField: { 40 | defaultProps: { 41 | InputLabelProps: { 42 | required: false, 43 | }, 44 | required: true, 45 | }, 46 | }, 47 | MuiTableHead: { 48 | styleOverrides: { 49 | root: { 50 | '& .MuiTableCell-root': { 51 | fontWeight: 'bold', 52 | }, 53 | }, 54 | }, 55 | }, 56 | MuiTableCell: { 57 | styleOverrides: { 58 | root: { 59 | border: '1px solid #D8D8D8', 60 | }, 61 | }, 62 | }, 63 | }, 64 | }) 65 | 66 | export default tema; 67 | -------------------------------------------------------------------------------- /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 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | --------------------------------------------------------------------------------