├── .vscode └── settings.json ├── src ├── react-app-env.d.ts ├── services │ └── api.ts ├── App.tsx ├── index.tsx ├── setupTests.ts ├── routes.tsx ├── App.css ├── components │ └── Dropzone │ │ ├── styles.css │ │ └── index.tsx ├── pages │ ├── Home │ │ ├── index.tsx │ │ └── styles.css │ └── CreateLocation │ │ ├── styles.css │ │ └── index.tsx └── assets │ └── logo.svg ├── .gitignore ├── tsconfig.json ├── public └── index.html ├── package.json └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/services/api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const api = axios.create({ 4 | baseURL: 'http://localhost:3333' 5 | }); 6 | 7 | export default api; -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './App.css'; 3 | 4 | import Routes from './routes'; 5 | 6 | function App() { 7 | return ( 8 | <> 9 | 10 | 11 | ); 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /.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 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, BrowserRouter } from 'react-router-dom'; 3 | 4 | import Home from './pages/Home'; 5 | import CreateLocation from './pages/CreateLocation'; 6 | 7 | const Routes = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | export default Routes; -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --primary-color: #79ab7f; 3 | --title-color: #79ab7f; 4 | --text-color: #034AAD; 5 | } 6 | 7 | * { 8 | margin: 0; 9 | padding: 0; 10 | box-sizing: border-box; 11 | } 12 | 13 | body { 14 | background: #fff5e7; 15 | -webkit-font-smoothing: antialiased; 16 | color: var(--text-color); 17 | } 18 | 19 | body, input, button { 20 | font-family: Roboto, Arial, Helvetica, sans-serif; 21 | } 22 | 23 | h1, h2, h3, h4, h5, h6 { 24 | color: var(--title-color); 25 | font-family: Ubuntu; 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Dropzone/styles.css: -------------------------------------------------------------------------------- 1 | .dropzone { 2 | height: 300px; 3 | background: #e1faec; 4 | border-radius: 10px; 5 | 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | margin-top: 48px; 10 | outline: 0; 11 | } 12 | 13 | .dropzone img { 14 | width: 100%; 15 | height: 100%; 16 | object-fit: cover; 17 | } 18 | 19 | .dropzone p { 20 | width: calc(100% - 60px); 21 | height: calc(100% - 60px); 22 | border-radius: 10px; 23 | border: 1px dashed #79ab7f; 24 | 25 | display: flex; 26 | flex-direction: column; 27 | justify-content: center; 28 | align-items: center; 29 | color: #333; 30 | } 31 | 32 | .dropzone p svg { 33 | color: #79ab7f; 34 | width: 24px; 35 | height: 24px; 36 | margin-bottom: 8px; 37 | } 38 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | Coleta Seletiva 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FiLogIn } from 'react-icons/fi'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | import './styles.css'; 6 | import logo from '../../assets/logo.svg'; 7 | 8 | const Home: React.FC = () => { 9 | return ( 10 |
11 |
12 |
13 | Reciclagem 14 |
15 | 16 |
17 |

Coleta Seletiva e reciclagem em geral.

18 |

Reciclagem de materiais diversos, tais como, papel, plástico, metal, pilhas e baterias, etc.

19 | 20 | 21 | 22 | 23 | 24 | Cadastrar novo local de coleta 25 | 26 |
27 |
28 |
29 | ); 30 | } 31 | 32 | export default Home; -------------------------------------------------------------------------------- /src/components/Dropzone/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useCallback, useState} from 'react'; 2 | import { FiUpload } from 'react-icons/fi'; 3 | import {useDropzone} from 'react-dropzone'; 4 | import './styles.css'; 5 | 6 | interface Props { 7 | onFileUploaded: (file: File) => void; 8 | } 9 | 10 | const Dropzone: React.FC = ({ onFileUploaded }) => { 11 | const [selectedFileUrl, setSelectedFileUrl] = useState(''); 12 | 13 | const onDrop = useCallback(acceptedFiles => { 14 | const file = acceptedFiles[0]; 15 | 16 | const fileUrl = URL.createObjectURL(file); 17 | 18 | setSelectedFileUrl(fileUrl); 19 | 20 | onFileUploaded(file); 21 | }, [onFileUploaded]); 22 | 23 | const {getRootProps, getInputProps} = useDropzone({ 24 | onDrop, 25 | accept: 'image/*' 26 | }) 27 | 28 | return ( 29 |
{}}> 30 | 31 | { selectedFileUrl 32 | ? Imagem do Estabelecimento 33 | : ( 34 |

35 | 36 | Imagem do Estabelecimento 37 |

38 | ) 39 | } 40 |
41 | ) 42 | } 43 | 44 | export default Dropzone; 45 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-curso-reactjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.11.4", 7 | "@testing-library/react": "^11.1.0", 8 | "@testing-library/user-event": "^12.1.10", 9 | "@types/jest": "^26.0.15", 10 | "@types/node": "^12.0.0", 11 | "@types/react-dom": "^16.9.8", 12 | "axios": "^0.21.0", 13 | "leaflet": "^1.7.1", 14 | "react": "^17.0.1", 15 | "react-dom": "^17.0.1", 16 | "react-dropzone": "^11.0.1", 17 | "react-icons": "^3.11.0", 18 | "react-leaflet": "^2.8.0", 19 | "react-router-dom": "^5.2.0", 20 | "react-scripts": "4.0.0", 21 | "typescript": "^4.0.3", 22 | "web-vitals": "^0.2.4" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject" 29 | }, 30 | "eslintConfig": { 31 | "extends": [ 32 | "react-app", 33 | "react-app/jest" 34 | ] 35 | }, 36 | "browserslist": { 37 | "production": [ 38 | ">0.2%", 39 | "not dead", 40 | "not op_mini all" 41 | ], 42 | "development": [ 43 | "last 1 chrome version", 44 | "last 1 firefox version", 45 | "last 1 safari version" 46 | ] 47 | }, 48 | "devDependencies": { 49 | "@types/react": "^16.9.56", 50 | "@types/react-leaflet": "^2.5.2", 51 | "@types/react-router-dom": "^5.1.6" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/pages/Home/styles.css: -------------------------------------------------------------------------------- 1 | #page-home { 2 | height: 100vh; 3 | 4 | background: url('../../assets/background.svg') no-repeat; 5 | } 6 | 7 | #page-home .content { 8 | width: 100%; 9 | height: 100%; 10 | max-width: 1100px; 11 | margin: 0 auto; 12 | padding: 0 30px; 13 | 14 | display: flex; 15 | flex-direction: column; 16 | align-items: flex-start; 17 | } 18 | 19 | #page-home .content header { 20 | margin: 48px 0 0; 21 | } 22 | 23 | #page-home .content main { 24 | flex: 1; 25 | max-width: 660px; 26 | 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: center; 30 | } 31 | 32 | #page-home .content main h1 { 33 | font-size: 64px; 34 | color: #333; 35 | } 36 | 37 | #page-home .content main p { 38 | font-size: 24px; 39 | margin-top: 24px; 40 | line-height: 38px; 41 | } 42 | 43 | #page-home .content main a { 44 | width: 100%; 45 | max-width: 360px; 46 | height: 72px; 47 | background: #79ab7f; 48 | border-radius: 8px; 49 | text-decoration: none; 50 | 51 | display: flex; 52 | align-items: center; 53 | overflow: hidden; 54 | 55 | margin-top: 40px; 56 | } 57 | 58 | #page-home .content main a span { 59 | display: block; 60 | background: rgba(0, 0, 0, 0.08); 61 | width: 72px; 62 | height: 72px; 63 | 64 | display: flex; 65 | align-items: center; 66 | justify-content: center; 67 | transition: background-color 0.2s; 68 | } 69 | 70 | #page-home .content main a span svg { 71 | color: #FFF; 72 | width: 20px; 73 | height: 20px; 74 | } 75 | 76 | #page-home .content main a strong { 77 | flex: 1; 78 | text-align: center; 79 | color: #FFF; 80 | } 81 | 82 | #page-home .content main a:hover { 83 | background: #79ab7f; 84 | } 85 | 86 | @media(max-width: 900px) { 87 | #page-home .content { 88 | align-items: center; 89 | text-align: center; 90 | } 91 | 92 | #page-home .content header { 93 | margin: 48px auto 0; 94 | } 95 | 96 | #page-home .content main { 97 | align-items: center; 98 | } 99 | 100 | #page-home .content main h1 { 101 | font-size: 42px; 102 | } 103 | 104 | #page-home .content main p { 105 | font-size: 24px; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Aluizio Developer 4 | 5 |

6 |

7 | Informação sobre tecnologia, dicas, tutoriais, mini-cursos e muito mais. 8 |

9 | 10 | ## Mini Curso Gratuito - Criação de SPA com ReactJS e Typescript 11 | 12 | Seja bem-vindo a este mini curso onde iremos conhecer o ReactJS e entender o processo de criação de SPA's - Single Page Application. 13 | 14 | Mini curso disponível no YouTube. Segue abaixo o link da playlist. 15 | 16 | [Playlist no YouTube deste Mini Curso de ReactJS e Typescript](https://www.youtube.com/watch?v=KhuHgLdwIsg&list=PLE0DHiXlN_qpm0nMlvcVxG_O580IXmeRU) 17 | 18 | ## Objetivo 19 | 20 | O objetivo do mini curso é dar o ponta pé inicial nos conceitos e recursos do ReactJS, focado no aprendizado daqueles que ainda não conhecem essa biblioteca ou framework, iniciantes em desenvolvimento de aplicações web com foco no front-end. 21 | 22 | A idéia é implementar uma aplicação front-end com ReactJS e Typescript para consumir a API que foi desenvolvida durante o mini curso gratuito de API Node.js com Typescript, também disponível no meu canal do Youtube. 23 | 24 | Mesmo que o seu foco seja somente front-end, recomendo que acompanhe também o mini curso de back-end, pois vários conceitos que estão naquele curso não serão repetidos aqui, como por exemplo, Node.js e NPM, API Restful, Requisições HTTP, Typescript, além de outros. 25 | 26 | Playlist Mini Curso API Restful com Node.js e Typescript: https://www.youtube.com/watch?v=M-pNDHC25Vg&list=PLE0DHiXlN_qp251xWxdb_stPj98y1auhc 27 | 28 | Repositório no Github da API: https://github.com/aluiziodeveloper/mini-curso-gratuito-node-typescript 29 | 30 | ## Rodando a aplicação no seu PC 31 | 32 | Faça um clone deste repositório e instale no seu ambiente de desenvolvimento usando o seguinte comando no seu terminal (escolha um diretório apropriado): 33 | 34 | ``` 35 | git clone https://github.com/aluiziodeveloper/mini-curso-reactjs.git 36 | ``` 37 | 38 | Após clonar o conteúdo do repositório, acesse o diretório criado e efetue a instalação das dependências: 39 | 40 | ``` 41 | cd mini-curso-reactjs 42 | 43 | npm install 44 | ``` 45 | 46 | Após essa instalação execute a aplicação com o comando `npm start`. Acesse a url `http://localhost:3000` no browser. 47 | 48 | ## Redes Sociais 49 | 50 | [Site Aluizio Developer](https://aluiziodeveloper.com.br) 51 | 52 | [YouTube](https://www.youtube.com/jorgealuizio) 53 | 54 | [Servidor no Discord](https://discord.gg/3J87BMz5fD) 55 | 56 | [LinkedIn](https://www.linkedin.com/in/jorgealuizio/) 57 | -------------------------------------------------------------------------------- /src/pages/CreateLocation/styles.css: -------------------------------------------------------------------------------- 1 | #page-create-location { 2 | width: 100%; 3 | max-width: 1100px; 4 | 5 | margin: 0 auto; 6 | } 7 | 8 | #page-create-location header { 9 | margin-top: 48px; 10 | 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | } 15 | 16 | #page-create-location header a { 17 | color: var(--title-color); 18 | font-weight: bold; 19 | text-decoration: none; 20 | 21 | display: flex; 22 | align-items: center; 23 | } 24 | 25 | #page-create-location header a svg { 26 | margin-right: 16px; 27 | color: var(--primary-color); 28 | } 29 | 30 | #page-create-location form { 31 | margin: 80px auto; 32 | padding: 64px; 33 | max-width: 730px; 34 | background: #FFF; 35 | border-radius: 8px; 36 | 37 | display: flex; 38 | flex-direction: column; 39 | } 40 | 41 | #page-create-location form h1 { 42 | font-size: 36px; 43 | } 44 | 45 | #page-create-location form fieldset { 46 | margin-top: 64px; 47 | min-inline-size: auto; 48 | border: 0; 49 | } 50 | 51 | #page-create-location form legend { 52 | width: 100%; 53 | display: flex; 54 | justify-content: space-between; 55 | align-items: center; 56 | margin-bottom: 40px; 57 | } 58 | 59 | #page-create-location form legend h2 { 60 | font-size: 24px; 61 | } 62 | 63 | #page-create-location form legend span { 64 | font-size: 14px; 65 | font-weight: normal; 66 | color: #034AAD; 67 | } 68 | 69 | #page-create-location form .field-group { 70 | flex: 1; 71 | display: flex; 72 | } 73 | 74 | #page-create-location form .field { 75 | flex: 1; 76 | 77 | display: flex; 78 | flex-direction: column; 79 | margin-bottom: 24px; 80 | } 81 | 82 | #page-create-location form .field input[type=text], 83 | #page-create-location form .field input[type=email], 84 | #page-create-location form .field input[type=number] { 85 | flex: 1; 86 | background: #fff5e7; 87 | border-radius: 8px; 88 | border: 0; 89 | padding: 16px 24px; 90 | font-size: 16px; 91 | color: #333; 92 | } 93 | 94 | #page-create-location form .field select { 95 | -webkit-appearance: none; 96 | -moz-appearance: none; 97 | appearance: none; 98 | flex: 1; 99 | background: #fff5e7; 100 | border-radius: 8px; 101 | border: 0; 102 | padding: 16px 24px; 103 | font-size: 16px; 104 | color: #333; 105 | } 106 | 107 | #page-create-location form .field input::placeholder { 108 | color: #A0A0B2; 109 | } 110 | 111 | #page-create-location form .field label { 112 | font-size: 14px; 113 | margin-bottom: 8px; 114 | } 115 | 116 | #page-create-location form .field :disabled { 117 | cursor: not-allowed; 118 | } 119 | 120 | #page-create-location form .field-group .field + .field { 121 | margin-left: 24px; 122 | } 123 | 124 | #page-create-location form .field-group input + input { 125 | margin-left: 24px; 126 | } 127 | 128 | #page-create-location form .field-check { 129 | flex-direction: row; 130 | align-items: center; 131 | } 132 | 133 | #page-create-location form .field-check input[type=checkbox] { 134 | background: #fff5e7; 135 | } 136 | 137 | #page-create-location form .field-check label { 138 | margin: 0 0 0 8px; 139 | } 140 | 141 | #page-create-location form .leaflet-container { 142 | width: 100%; 143 | height: 350px; 144 | border-radius: 8px; 145 | margin-bottom: 24px; 146 | } 147 | 148 | #page-create-location form button { 149 | width: 260px; 150 | height: 56px; 151 | background: var(--primary-color); 152 | border-radius: 8px; 153 | color: #FFF; 154 | font-weight: bold; 155 | font-size: 16px; 156 | border: 0; 157 | align-self: flex-end; 158 | margin-top: 40px; 159 | transition: background-color 0.2s; 160 | cursor: locationer; 161 | } 162 | 163 | #page-create-location form button:hover { 164 | background: #79ab7f; 165 | } 166 | 167 | .items-grid { 168 | display: grid; 169 | grid-template-columns: repeat(3, 1fr); 170 | gap: 16px; 171 | list-style: none; 172 | } 173 | 174 | .items-grid li { 175 | background: #fff5e7; 176 | border: 2px solid #fff5e7; 177 | height: 180px; 178 | border-radius: 8px; 179 | padding: 32px 24px 16px; 180 | 181 | display: flex; 182 | flex-direction: column; 183 | justify-content: space-between; 184 | align-items: center; 185 | 186 | text-align: center; 187 | 188 | cursor: locationer; 189 | } 190 | 191 | .items-grid li span { 192 | flex: 1; 193 | margin-top: 12px; 194 | 195 | display: flex; 196 | align-items: center; 197 | color: var(--title-color) 198 | } 199 | 200 | .items-grid li.selected { 201 | background: #F6DF8C; 202 | border: 2px solid #79ab7f; 203 | } 204 | -------------------------------------------------------------------------------- /src/pages/CreateLocation/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { ChangeEvent, FormEvent, useCallback, useEffect, useState } from 'react'; 2 | import { Link, useHistory } from 'react-router-dom'; 3 | import {FiArrowLeft} from 'react-icons/fi'; 4 | import { Map, TileLayer, Marker} from 'react-leaflet'; 5 | import { LeafletMouseEvent } from 'leaflet'; 6 | import api from '../../services/api'; 7 | import Dropzone from '../../components/Dropzone'; 8 | import logo from '../../assets/logo.svg'; 9 | import './styles.css'; 10 | 11 | interface Item { 12 | id: number; 13 | title: string; 14 | image_url: string; 15 | } 16 | 17 | const CreateLocation: React.FC = () => { 18 | const [items, setItems] = useState([]); 19 | 20 | const [selectedMapPosition, setSelectedMapPosition] = useState<[number, number]>([0, 0]); 21 | 22 | const [formData, setFormData] = useState({ 23 | name: '', 24 | email: '', 25 | whatsapp: '', 26 | city: '', 27 | uf: '', 28 | }); 29 | 30 | const [selectedItems, setSelectedItems] = useState([]); 31 | 32 | const [selectedFile, setSelectedFile] = useState(); 33 | 34 | const history = useHistory(); 35 | 36 | useEffect(() => { 37 | api.get('items').then(response => { 38 | setItems(response.data); 39 | }); 40 | }, []); 41 | 42 | const handleMapClick = useCallback((event: LeafletMouseEvent): void => { 43 | //console.log(event); 44 | setSelectedMapPosition([ 45 | event.latlng.lat, 46 | event.latlng.lng, 47 | ]); 48 | }, []); 49 | 50 | const handleInputChange = useCallback((event: ChangeEvent) => { 51 | //console.log(event.target.name, event.target.value); 52 | const { name, value } = event.target; 53 | setFormData({...formData, [name]: value }); 54 | }, [formData]); 55 | 56 | const handleSelectItem = useCallback((id: number) => { 57 | //setSelectedItems([...selectedItems, id]); 58 | const alreadySelected = selectedItems.findIndex(item => item === id); 59 | 60 | if (alreadySelected >= 0) { 61 | const filteredItems = selectedItems.filter(item => item !== id); 62 | setSelectedItems(filteredItems); 63 | } else { 64 | setSelectedItems([...selectedItems, id]); 65 | } 66 | }, [selectedItems]); 67 | 68 | const handleSubmit = useCallback(async (event: FormEvent) => { 69 | event.preventDefault(); 70 | 71 | const {city, email, name, uf, whatsapp} = formData; 72 | const [latitude, longitude] = selectedMapPosition; 73 | const items = selectedItems; 74 | 75 | const data = new FormData(); 76 | 77 | data.append('name', name); 78 | data.append('email', email); 79 | data.append('whatsapp', whatsapp); 80 | data.append('uf', uf); 81 | data.append('city', city); 82 | data.append('latitude', String(latitude)); 83 | data.append('longitude', String(longitude)); 84 | data.append('items', items.join(',')); 85 | if (selectedFile) { 86 | data.append('image', selectedFile); 87 | } 88 | 89 | await api.post('locations', data); 90 | 91 | alert('Estabelecimento cadastrado com sucesso!'); 92 | 93 | history.push('/'); 94 | }, [formData, selectedItems, selectedMapPosition, history, selectedFile]); 95 | 96 | return ( 97 |
98 |
99 |
100 | Coleta Seletiva 101 | 102 | 103 | Voltar para home 104 | 105 |
106 | 107 |
108 |

Cadastro do
local de coleta

109 | 110 |
111 | 112 |

Dados

113 |
114 | 115 | 116 | 117 |
118 | 119 | 125 |
126 |
127 |
128 | 129 | 135 |
136 |
137 | 138 | 144 |
145 |
146 |
147 | 148 |
149 | 150 |

Endereço

151 | Marque o endereço no mapa 152 |
153 | 154 | 158 | 159 | 160 |
161 |
162 | 163 | 169 |
170 |
171 | 172 | 178 |
179 |
180 |
181 | 182 |
183 | 184 |

Ítens coletados

185 | Você pode marcar um ou mais ítens 186 |
187 |
    188 | {items.map(item => ( 189 |
  • handleSelectItem(item.id)} 192 | className={selectedItems.includes(item.id) ? 'selected' : ''} 193 | > 194 | {item.title} 195 |
  • 196 | ))} 197 |
198 |
199 | 200 | 203 |
204 |
205 |
206 | ); 207 | } 208 | 209 | export default CreateLocation; -------------------------------------------------------------------------------- /src/assets/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 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | --------------------------------------------------------------------------------