├── .vscode └── settings.json ├── src ├── react-app-env.d.ts ├── images │ ├── cat1.png │ ├── cat2.png │ ├── dog1.png │ ├── logow.png │ ├── background.png │ └── map-marker.png ├── services │ └── api.ts ├── index.tsx ├── App.tsx ├── styles │ ├── global.css │ ├── components │ │ └── sidebar.css │ └── pages │ │ ├── landing.css │ │ ├── shelters-map.css │ │ ├── create-shelter.css │ │ └── shelter.css ├── utils │ └── mapIcon.ts ├── components │ └── Sidebar.tsx ├── routes.tsx └── screens │ ├── Landing.tsx │ ├── SheltersMap.tsx │ ├── Shelter.tsx │ └── CreateShelter.tsx ├── .gitignore ├── public └── index.html ├── tsconfig.json ├── package.json └── README.md /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src/images/cat1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/cat1.png -------------------------------------------------------------------------------- /src/images/cat2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/cat2.png -------------------------------------------------------------------------------- /src/images/dog1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/dog1.png -------------------------------------------------------------------------------- /src/images/logow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/logow.png -------------------------------------------------------------------------------- /src/images/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/background.png -------------------------------------------------------------------------------- /src/images/map-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjulialobo/happy/HEAD/src/images/map-marker.png -------------------------------------------------------------------------------- /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/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | 13 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles/global.css'; 3 | import 'leaflet/dist/leaflet.css'; 4 | 5 | import Routes from './routes'; 6 | 7 | function App() { 8 | return ( 9 | 10 | ); 11 | 12 | } 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | color: #fff; 9 | background: #ebf2f5; 10 | overflow-x: hidden; 11 | } 12 | body, 13 | input, 14 | button, 15 | textarea { 16 | font: 600 18px Nunito, sans-serif; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/mapIcon.ts: -------------------------------------------------------------------------------- 1 | import L from 'leaflet'; 2 | import mapMarkerImg from '../images/map-marker.png'; 3 | 4 | const happyMapIcon = L.icon({ 5 | iconUrl: mapMarkerImg, 6 | 7 | iconSize: [58, 68], 8 | iconAnchor: [29, 68], 9 | popupAnchor: [0, -60] 10 | }) 11 | 12 | export default happyMapIcon; -------------------------------------------------------------------------------- /.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 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Happy 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /src/components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FiArrowLeft } from "react-icons/fi"; 3 | import{useHistory} from 'react-router-dom' 4 | import mapMarkerImg from '../images/map-marker.png'; 5 | 6 | import '../styles/components/sidebar.css' 7 | export default function Sidebar(){ 8 | const {goBack} = useHistory(); 9 | return( 10 | 21 | 22 | ) 23 | } -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {BrowserRouter, Switch, Route} from 'react-router-dom' 3 | import Landing from './screens/Landing'; 4 | import SheltersMap from './screens/SheltersMap'; 5 | import Shelter from './screens/Shelter'; 6 | import CreateShelters from './screens/CreateShelter'; 7 | 8 | function Routes(){ 9 | return( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default Routes; 23 | -------------------------------------------------------------------------------- /src/styles/components/sidebar.css: -------------------------------------------------------------------------------- 1 | aside.app-sidebar { 2 | position: fixed; 3 | height: 100%; 4 | padding: 32px 24px; 5 | background: linear-gradient(329.54deg, #37c77f 0%, #aadaa9 100%); 6 | 7 | display: flex; 8 | flex-direction: column; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | aside.app-sidebar img { 14 | width: 48px; 15 | } 16 | 17 | aside.app-sidebar footer a, 18 | aside.app-sidebar footer button { 19 | width: 48px; 20 | height: 48px; 21 | 22 | border: 0; 23 | 24 | background: #58e09c; 25 | border-radius: 16px; 26 | 27 | cursor: pointer; 28 | 29 | transition: background-color 0.2s; 30 | 31 | display: flex; 32 | justify-content: center; 33 | align-items: center; 34 | } 35 | 36 | aside.app-sidebar footer a:hover, 37 | aside.app-sidebar footer button:hover { 38 | background: #52faa6; 39 | } 40 | -------------------------------------------------------------------------------- /src/screens/Landing.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {FiArrowRight} from 'react-icons/fi' 3 | import {Link} from 'react-router-dom' 4 | 5 | import '../styles/pages/landing.css' 6 | import logoImg from '../images/logow.png' 7 | 8 | function Landing(){ 9 | return( 10 |
11 |
12 | Happy 13 |
14 |

Leve a felicidade para casa

15 |

Busque ONGs e encontre seu melhor amigo.

16 |
17 | 18 |
19 | São Paulo 20 | Cotia e região 21 |
22 | 23 | 24 | 25 | 26 |
27 |
28 | ) 29 | } 30 | 31 | export default Landing; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happy", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "@types/jest": "^24.0.0", 10 | "@types/node": "^12.0.0", 11 | "@types/react": "^16.9.0", 12 | "@types/react-dom": "^16.9.0", 13 | "@types/react-leaflet": "^2.5.2", 14 | "axios": "^0.20.0", 15 | "leaflet": "^1.7.1", 16 | "react": "^16.13.1", 17 | "react-dom": "^16.13.1", 18 | "react-icons": "^3.11.0", 19 | "react-leaflet": "^2.7.0", 20 | "react-router-dom": "^5.2.0", 21 | "react-scripts": "3.4.3", 22 | "typescript": "~3.7.2" 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": "react-app" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | }, 45 | "devDependencies": { 46 | "@types/react-router-dom": "^5.1.6" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/styles/pages/landing.css: -------------------------------------------------------------------------------- 1 | #page-landing { 2 | width: 100vw; 3 | height: 100vh; 4 | background: linear-gradient(329.54deg, #37c77f 0%, #aadaa9 100%); 5 | display: flex; 6 | justify-content: center; 7 | align-items: center; 8 | } 9 | 10 | #page-landing .content-wrapper { 11 | position: relative; 12 | width: 100%; 13 | max-width: 1100px; 14 | height: 100%; 15 | max-height: 680px; 16 | display: flex; 17 | align-items: flex-start; 18 | flex-direction: column; 19 | justify-content: space-between; 20 | 21 | background: url("../../images/background.png") no-repeat 90% center; 22 | background-size: 70%; 23 | } 24 | #page-landing .content-wrapper img { 25 | width: 250px; 26 | margin-top: 20px; 27 | } 28 | #page-landing .content-wrapper main { 29 | max-width: 350px; 30 | height: 60%; 31 | } 32 | #page-landing .content-wrapper main h1 { 33 | font-size: 76px; 34 | font-weight: 900; 35 | line-height: 70px; 36 | } 37 | #page-landing .content-wrapper main p { 38 | margin-top: 20px; 39 | font-size: 24px; 40 | line-height: 34px; 41 | } 42 | .content-wrapper .location { 43 | position: absolute; 44 | right: 0; 45 | top: 0; 46 | font-size: 24px; 47 | line-height: 34px; 48 | display: flex; 49 | flex-direction: column; 50 | text-align: right; 51 | margin-top: 30px; 52 | } 53 | .content-wrapper .location strong { 54 | font-weight: 800; 55 | } 56 | .content-wrapper .enter-app { 57 | position: absolute; 58 | right: 0; 59 | bottom: 0; 60 | width: 80px; 61 | height: 80px; 62 | margin-bottom: 30px; 63 | background-color: #ffd666; 64 | border-radius: 30px; 65 | display: flex; 66 | align-items: center; 67 | justify-content: center; 68 | transition: background-color 0.2s; 69 | } 70 | .content-wrapper .enter-app:hover { 71 | background: #fbba15; 72 | } 73 | -------------------------------------------------------------------------------- /src/styles/pages/shelters-map.css: -------------------------------------------------------------------------------- 1 | #page-map { 2 | width: 100vw; 3 | height: 100vh; 4 | position: relative; 5 | display: flex; 6 | } 7 | 8 | #page-map aside { 9 | width: 440px; 10 | background: linear-gradient(329.54deg, #37c77f 0%, #aadaa9 100%); 11 | padding: 80px; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: space-between; 15 | } 16 | #page-map aside header img { 17 | width: 70px; 18 | } 19 | #page-map aside h2 { 20 | font-size: 40px; 21 | font-weight: 800; 22 | line-height: 42px; 23 | margin-top: 64px; 24 | } 25 | #page-map aside p { 26 | line-height: 28px; 27 | margin-top: 24px; 28 | } 29 | #page-map aside footer { 30 | display: flex; 31 | flex-direction: column; 32 | line-height: 24px; 33 | } 34 | #page-map aside footer strong { 35 | font-weight: 800; 36 | } 37 | #page-map .leaflet-container { 38 | z-index: 5; 39 | } 40 | #page-map .create-shelter { 41 | position: absolute; 42 | right: 40px; 43 | bottom: 40px; 44 | z-index: 10; 45 | width: 64px; 46 | height: 64px; 47 | background-color: #37c77f; 48 | border-radius: 20px; 49 | display: flex; 50 | justify-content: center; 51 | align-items: center; 52 | transition: background-color 0.2s; 53 | } 54 | #page-map .create-shelter:hover { 55 | background-color: #22794d; 56 | } 57 | #page-map .map-popup .leaflet-popup-content-wrapper { 58 | background: rgba(225, 225, 225, 0.966); 59 | border-radius: 20px; 60 | } 61 | #page-map .map-popup .leaflet-popup-content { 62 | color: #26a867; 63 | font-size: 16px; 64 | font-weight: bold; 65 | margin: 8px; 66 | display: flex; 67 | justify-content: space-between; 68 | align-items: center; 69 | } 70 | #page-map .map-popup .leaflet-popup-content a { 71 | width: 40px; 72 | height: 40px; 73 | background-color: #26a867; 74 | box-shadow: 17.2868px 27.6589px 41.4554 rgba(23, 142, 166, 0.16); 75 | border-radius: 12px; 76 | display: flex; 77 | justify-content: center; 78 | align-items: center; 79 | } 80 | 81 | #page-map .map-popup .leaflet-popup-tip-container { 82 | display: none; 83 | } 84 | -------------------------------------------------------------------------------- /src/screens/SheltersMap.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import {Link} from 'react-router-dom'; 3 | import {FiPlus, FiArrowRight} from 'react-icons/fi'; 4 | import {Map, TileLayer, Marker, Popup} from 'react-leaflet'; 5 | 6 | import 'leaflet/dist/leaflet.css'; 7 | import mapMarkerImg from '../images/map-marker.png'; 8 | import '../styles/pages/shelters-map.css'; 9 | import api from '../services/api'; 10 | import happyMapIcon from '../utils/mapIcon' 11 | 12 | interface Shelter{ 13 | id:number; 14 | latitude:number; 15 | longitude:number; 16 | name:string; 17 | } 18 | 19 | function SheltersMap(){ 20 | const [shelters, setShelters] = useState([]); 21 | 22 | useEffect(() => { 23 | api.get('shelters').then(response =>{ 24 | setShelters(response.data); 25 | }); 26 | }, []); 27 | 28 | return( 29 |
30 | 44 | 45 | 50 | {/* */} 51 | 54 | 55 | 56 | {shelters.map(shelter => { 57 | return( 58 | 63 | 64 | {shelter.name} 65 | 66 | 67 | 68 | 69 | 70 | ) 71 | })} 72 | 73 | 74 | 75 | 76 | 77 |
78 | ) 79 | } 80 | 81 | export default SheltersMap; -------------------------------------------------------------------------------- /src/styles/pages/create-shelter.css: -------------------------------------------------------------------------------- 1 | #page-create-shelter { 2 | display: flex; 3 | } 4 | 5 | #page-create-shelter main { 6 | flex: 1; 7 | } 8 | 9 | form.create-shelter-form { 10 | width: 700px; 11 | margin: 64px auto; 12 | 13 | background: #ffffff; 14 | border: 1px solid #d3e2e5; 15 | border-radius: 20px; 16 | 17 | padding: 64px 80px; 18 | 19 | overflow: hidden; 20 | } 21 | form.create-shelter-form .leaflet-container { 22 | margin-bottom: 48px; 23 | border: solid 2px #e7e7e783; 24 | border-radius: 8px; 25 | } 26 | 27 | form.create-shelter-form fieldset { 28 | border: 0; 29 | } 30 | 31 | form.create-shelter-form fieldset + fieldset { 32 | margin-top: 80px; 33 | } 34 | 35 | form.create-shelter-form fieldset legend { 36 | width: 100%; 37 | 38 | font-size: 32px; 39 | line-height: 34px; 40 | color: #5c8599; 41 | font-weight: 700; 42 | 43 | border-bottom: 1px solid #d3e2e5; 44 | margin-bottom: 40px; 45 | padding-bottom: 24px; 46 | } 47 | 48 | form.create-shelter-form .input-block + .input-block { 49 | margin-top: 24px; 50 | } 51 | 52 | form.create-shelter-form .input-block label { 53 | display: flex; 54 | color: #8fa7b3; 55 | margin-bottom: 8px; 56 | line-height: 24px; 57 | } 58 | 59 | form.create-shelter-form .input-block label span { 60 | font-size: 14px; 61 | color: #8fa7b3; 62 | margin-left: 24px; 63 | line-height: 24px; 64 | } 65 | 66 | form.create-shelter-form .input-block input, 67 | form.create-shelter-form .input-block textarea { 68 | width: 100%; 69 | background: #f5f8fa; 70 | border: 1px solid #d3e2e5; 71 | border-radius: 20px; 72 | outline: none; 73 | color: #5c8599; 74 | } 75 | 76 | form.create-shelter-form .input-block input { 77 | height: 64px; 78 | padding: 0 16px; 79 | } 80 | 81 | form.create-shelter-form .input-block textarea { 82 | min-height: 120px; 83 | max-height: 240px; 84 | resize: vertical; 85 | padding: 16px; 86 | line-height: 28px; 87 | } 88 | 89 | form.create-shelter-form .input-block .images-container { 90 | display: grid; 91 | grid-template-columns: repeat(5, 1fr); 92 | grid-gap: 16px; 93 | } 94 | form.create-shelter-form .input-block .images-container img { 95 | width: 100%; 96 | height: 96px; 97 | object-fit: cover; 98 | border-radius: 20px; 99 | } 100 | form.create-shelter-form .input-block .images-container .new-image { 101 | height: 96px; 102 | background: #f5f8fa; 103 | border: 1px dashed #96d2f0; 104 | border-radius: 20px; 105 | cursor: pointer; 106 | 107 | display: flex; 108 | justify-content: center; 109 | align-items: center; 110 | } 111 | form.create-shelter-form .input-block input[type="file"] { 112 | visibility: hidden; 113 | display: none; 114 | } 115 | 116 | form.create-shelter-form .input-block .button-select { 117 | display: grid; 118 | grid-template-columns: 1fr 1fr; 119 | } 120 | 121 | form.create-shelter-form .input-block .button-select button { 122 | height: 64px; 123 | background: #f5f8fa; 124 | border: 1px solid #d3e2e5; 125 | color: #5c8599; 126 | cursor: pointer; 127 | } 128 | 129 | form.create-shelter-form .input-block .button-select button.active { 130 | background: #edfff6; 131 | border: 1px solid #a1e9c5; 132 | color: #37c77f; 133 | } 134 | 135 | form.create-shelter-form .input-block .button-select button:first-child { 136 | border-radius: 20px 0px 0px 20px; 137 | } 138 | 139 | form.create-shelter-form .input-block .button-select button:last-child { 140 | border-radius: 0 20px 20px 0; 141 | border-left: 0; 142 | } 143 | 144 | form.create-shelter-form button.confirm-button { 145 | margin-top: 64px; 146 | 147 | width: 100%; 148 | height: 64px; 149 | border: 0; 150 | cursor: pointer; 151 | background: #3cdc8c; 152 | border-radius: 20px; 153 | color: #ffffff; 154 | font-weight: 800; 155 | 156 | display: flex; 157 | justify-content: center; 158 | align-items: center; 159 | 160 | transition: background-color 0.2s; 161 | } 162 | 163 | form.create-shelter-form button.confirm-button svg { 164 | margin-right: 16px; 165 | } 166 | 167 | form.create-shelter-form button.confirm-button:hover { 168 | background: #36cf82; 169 | } 170 | -------------------------------------------------------------------------------- /src/styles/pages/shelter.css: -------------------------------------------------------------------------------- 1 | #page-shelter { 2 | display: flex; 3 | min-height: 100vh; 4 | overflow-y: visible; 5 | } 6 | 7 | #page-shelter main { 8 | flex: 1; 9 | } 10 | 11 | .shelter-details { 12 | width: 700px; 13 | margin: 64px auto; 14 | 15 | background: #ffffff; 16 | border: 1px solid #d3e2e5; 17 | border-radius: 20px; 18 | 19 | overflow: hidden; 20 | } 21 | 22 | .shelter-details > img { 23 | width: 100%; 24 | height: 300px; 25 | object-fit: cover; 26 | } 27 | 28 | .shelter-details .images { 29 | display: grid; 30 | grid-template-columns: repeat(6, 1fr); 31 | column-gap: 16px; 32 | 33 | margin: 16px 40px 0; 34 | } 35 | 36 | .shelter-details .images button { 37 | border: 0; 38 | height: 88px; 39 | background: none; 40 | cursor: pointer; 41 | border-radius: 20px; 42 | overflow: hidden; 43 | outline: none; 44 | 45 | opacity: 0.6; 46 | } 47 | 48 | .shelter-details .images button.active { 49 | opacity: 1; 50 | } 51 | 52 | .shelter-details .images button img { 53 | width: 100%; 54 | height: 88px; 55 | object-fit: cover; 56 | } 57 | 58 | .shelter-details .shelter-details-content { 59 | padding: 80px; 60 | } 61 | 62 | .shelter-details .shelter-details-content h1 { 63 | color: #4d6f80; 64 | font-size: 54px; 65 | line-height: 54px; 66 | margin-bottom: 8px; 67 | } 68 | 69 | .shelter-details .shelter-details-content p { 70 | line-height: 28px; 71 | color: #5c8599; 72 | margin-top: 24px; 73 | } 74 | 75 | .shelter-details .shelter-details-content .map-container { 76 | margin-top: 64px; 77 | background: #e6f7fb; 78 | border: 1px solid #b3dae2; 79 | border-radius: 20px; 80 | } 81 | 82 | .shelter-details .shelter-details-content .map-container footer { 83 | padding: 20px 0; 84 | text-align: center; 85 | } 86 | 87 | .shelter-details .shelter-details-content .map-container footer a { 88 | line-height: 24px; 89 | color: #37c77f; 90 | text-decoration: none; 91 | } 92 | 93 | .shelter-details .shelter-details-content .map-container .leaflet-container { 94 | border-bottom: 1px solid #dde3f0; 95 | border-radius: 20px; 96 | } 97 | 98 | .shelter-details .shelter-details-content hr { 99 | width: 100%; 100 | height: 1px; 101 | border: 0; 102 | background: #d3e2e6; 103 | margin: 64px 0; 104 | } 105 | 106 | .shelter-details .shelter-details-content h2 { 107 | font-size: 36px; 108 | line-height: 46px; 109 | color: #4d6f80; 110 | } 111 | 112 | .shelter-details .shelter-details-content .open-details { 113 | margin-top: 24px; 114 | 115 | display: grid; 116 | grid-template-columns: 1fr 1fr; 117 | column-gap: 20px; 118 | } 119 | 120 | .shelter-details .shelter-details-content .open-details div { 121 | padding: 32px 24px; 122 | border-radius: 20px; 123 | line-height: 28px; 124 | } 125 | 126 | .shelter-details .shelter-details-content .open-details div svg { 127 | display: block; 128 | margin-bottom: 20px; 129 | } 130 | 131 | .shelter-details .shelter-details-content .open-details div.hour { 132 | background: linear-gradient(149.97deg, #e6f7fb 8.13%, #ffffff 92.67%); 133 | border: 1px solid #b3dae2; 134 | color: #5c8599; 135 | } 136 | 137 | .shelter-details .shelter-details-content .open-details div.open-on-weekends { 138 | background: linear-gradient(154.16deg, #edfff6 7.85%, #ffffff 91.03%); 139 | border: 1px solid #a1e9c5; 140 | color: #37c77f; 141 | } 142 | .shelter-details 143 | .shelter-details-content 144 | .open-details 145 | div.open-on-weekends.dont-open { 146 | background: linear-gradient(154.16deg, #ffe7e7 7.85%, #ffffff 91.03%); 147 | border: 1px solid #ffd4d4; 148 | color: #fa4b34; 149 | } 150 | 151 | .shelter-details .shelter-details-content button.contact-button { 152 | margin-top: 64px; 153 | 154 | width: 100%; 155 | height: 64px; 156 | border: 0; 157 | cursor: pointer; 158 | background: #3cdc8c; 159 | border-radius: 20px; 160 | color: #ffffff; 161 | font-weight: 800; 162 | 163 | display: flex; 164 | justify-content: center; 165 | align-items: center; 166 | 167 | transition: background-color 0.2s; 168 | } 169 | 170 | .shelter-details .shelter-details-content button.contact-button svg { 171 | margin-right: 16px; 172 | } 173 | 174 | .shelter-details .shelter-details-content button.contact-button:hover { 175 | background: #36cf82; 176 | } 177 | -------------------------------------------------------------------------------- /src/screens/Shelter.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import { FaWhatsapp } from "react-icons/fa"; 3 | import { FiClock, FiInfo } from "react-icons/fi"; 4 | import { Map, Marker, TileLayer } from "react-leaflet"; 5 | import {useParams} from 'react-router-dom' 6 | 7 | import Sidebar from "../components/Sidebar"; 8 | import happyMapIcon from '../utils/mapIcon' 9 | 10 | import api from '../services/api'; 11 | import '../styles/pages/shelter.css'; 12 | 13 | interface Shelter{ 14 | id:number; 15 | latitude:number; 16 | longitude:number; 17 | name:string; 18 | about: string; 19 | instructions: string; 20 | opening_hours:string, 21 | open_on_weekends:boolean; 22 | images:Array<{ 23 | url:string; 24 | id:number; 25 | }>; 26 | } 27 | 28 | interface ShelterParams{ 29 | id:string; 30 | } 31 | 32 | export default function Shelter() { 33 | const params = useParams(); 34 | const [shelter, setShelter] = useState(); 35 | const [activeImageIndex, setActiveImageIndex]= useState(0); 36 | 37 | useEffect(() => { 38 | api.get(`shelters/${params.id}`).then(response =>{ 39 | setShelter(response.data); 40 | }); 41 | }, [params.id]); 42 | 43 | if(!shelter){ 44 | return

Carregando...

; 45 | } 46 | 47 | return ( 48 |
49 | 50 | 51 |
52 |
53 | {shelter.name} 54 | 55 |
56 | {shelter.images.map((image, index) =>{ 57 | return( 58 | 66 | ) 67 | })} 68 | 69 |
70 | 71 |
72 |

{shelter.name}

73 |

{shelter.about}

74 | 75 |
76 | 86 | 89 | 90 | 91 | 92 | 97 |
98 | 99 |
100 | 101 |

Instruções para visita e/ou adoção

102 |

{shelter.instructions}

103 | 104 |
105 |
106 | 107 | Segunda à Sexta
108 | {shelter.opening_hours} 109 |
110 | 111 | {shelter.open_on_weekends ? ( 112 |
113 | 114 | Atendemos
115 | fim de semana 116 |
117 | ) : ( 118 |
119 | 120 | Não atendemos
121 | fim de semana 122 |
123 | 124 | )} 125 |
126 | 127 | {/* */} 131 |
132 |
133 |
134 |
135 | ); 136 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 |

O que é o Happy?

6 | 7 |

A Next Level Week é um evento online gratuito promovido pela Rocketsat, em que durante 5 dias desenvolvemos uma aplicação completa. Na trilha Omni, a proposta foi criar uma aplicação web e mobile em que lares adotivos podem cadastrar seus endereços e informações para pessoas visitarem.

8 | 9 |

Eu decidi mudar a proposta da aplicação, e transformei o Happy em um buscador de abrigos de animais, ajudando a unir pessoas e seus futuros melhores amigos. 10 |

11 | 12 |

Como usar?

13 | 14 | ### Pré-requisitos 15 | 16 | É necessário ter instalado na sua máquina para execução desse projeto: 17 | - NodeJS 18 | - Gerenciador de pacotes (Npm ou Yarn) 19 | 20 | ### ♊ Clonando o Repositório (frontend, backend e mobile) 21 |

Repositório FRONTEND. 22 |

Repositório BACKEND. 23 |

Repositório MOBILE. 24 | 25 | ```bash 26 | 27 | $ git clone https://github.com/mjulialobo/happy.git 28 | $ git clone https://github.com/mjulialobo/happy-backend.git 29 | $ git clone https://github.com/mjulialobo/happy-mobile.git 30 | 31 | # entre na pasta do projeto 32 | 33 | $ cd happy 34 | 35 | ``` 36 | ### 💻 Rodando o Happy web 37 | 38 | Entre na pasta 39 | 40 | ```bash 41 | 42 | $ cd web 43 | 44 | ``` 45 | Instale as dependências 46 | 47 | ```bash 48 | 49 | $ yarn 50 | 51 | # ou, caso use npm 52 | 53 | $ npm install 54 | 55 | ``` 56 | 57 | Rode a aplicação 58 | 59 | ```bash 60 | 61 | $ yarn start 62 | 63 | # ou, caso use npm 64 | 65 | $ npm start 66 | 67 | ``` 68 | 69 | Caso você tenha uma conta no [mapbox](https://www.mapbox.com/), pode usar seu token para utilizar o mapa da aplicação. No entanto, se você não quiser 70 | ter este trabalho, sem problemas, por padrão já tem um mapa configurado para uso. 71 | 72 | Caso queira, vá para a seção do Mapbox. 73 | 74 | ### 🌐 Rodando o Servidor 75 | 76 | Entre na pasta 77 | 78 | ```bash 79 | 80 | $ cd backend 81 | 82 | ``` 83 | Instale as dependências 84 | 85 | ```bash 86 | 87 | $ yarn 88 | 89 | # ou, caso use npm 90 | 91 | $ npm install 92 | 93 | ``` 94 | 95 | Rode o servidor 96 | 97 | ```bash 98 | 99 | $ yarn dev 100 | 101 | # ou, caso use npm 102 | 103 | $ npm dev 104 | 105 | ``` 106 | 107 | ### 📱 Rodando o Happy mobile 108 | 109 | Entre na pasta 110 | 111 | ```bash 112 | 113 | $ cd mobile 114 | 115 | ``` 116 | Instale as dependências 117 | 118 | ```bash 119 | 120 | $ yarn 121 | 122 | # ou, caso use npm 123 | 124 | $ npm install 125 | 126 | ``` 127 | 128 | Rode o mobile 129 | 130 | ```bash 131 | 132 | $ yarn start 133 | 134 | # ou, caso use npm 135 | 136 | $ npm start 137 | 138 | ``` 139 | 140 | Depois de fazer isso, irá abrir o metro bundler no seu navegador. A partir de agora você tem algumas opções para acessar o app. 141 | 142 | #### 1 - Emulador Android 143 | Na página do metro bundler, clique em "Run on Android device/emulator" e espere carregar. Tenha em mente que é necessário ter passado pelo processo de instalação 144 | do android sdk, etc. 145 | 146 | #### 2 - Emulador IOS 147 | Na página do metro bundler, clique em "Run on iOS simulator" e espere carregar. 148 | 149 | #### 3 - Seu smartphone 150 | Baixe o aplicativo do Expo: 151 | - [iOS](https://itunes.apple.com/app/apple-store/id982107779) 152 | - [Android](https://play.google.com/store/apps/details?id=host.exp.exponent&referrer=www) 153 | 154 | Depois de baixar, volte a página do metro bundler e escaneie o QR Code com o app do Expo. 155 | 156 |
157 | 158 | Se tudo deu certo, o app deve estar disponível agora! 👩🏽‍🔧 159 | 160 | --- 161 | 162 | ## 🗺 Mapbox 163 | 164 | Siga as instruções para usar o mapbox no lugar do openstreetmap. 165 | 166 | - Em "https://account.mapbox.com/", copie seu token. 167 | - Na raiz do projeto web crie um arquivo chamado ".env" 168 | - Dentro desse arquivo, digite "REACT_APP_MAPBOX_TOKEN =" e cole seu token logo depois. 169 | - Entre no arquivo "OrphanagesMap.tsx", descomente o trecho de código correspondente as linhas 34, 35 e 36. 170 | - No mesmo arquivo, comente a linha 32. 171 | 172 | Se você fez tudo corretamente, estás usando a API do mapbox com seu Token na página do mapa. 😄 173 | 174 | --- 175 | 176 | 177 |

Resultados

178 | 179 |

Note: gif distorts images and colors

180 |

Happy - Web

181 | 182 |

Happy - Mobile

183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/screens/CreateShelter.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { ChangeEvent, FormEvent, useState } from "react"; 3 | import { useHistory } from "react-router-dom"; 4 | import { Map, Marker, TileLayer } from 'react-leaflet'; 5 | import { LeafletMouseEvent } from 'leaflet'; 6 | import { FiPlus } from "react-icons/fi"; 7 | 8 | import Sidebar from "../components/Sidebar"; 9 | import happyMapIcon from "../utils/mapIcon"; 10 | 11 | 12 | import '../styles/pages/create-shelter.css'; 13 | import api from "../services/api"; 14 | 15 | export default function CreateShelter() { 16 | const history = useHistory(); 17 | 18 | const [position, setPosition] = useState({latitude:0, longitude:0}) 19 | const[name, setName] = useState(''); 20 | const[about, setAbout] = useState(''); 21 | const[instructions, setInstructions] = useState(''); 22 | const[opening_hours, setOpeningHours] = useState(''); 23 | const [open_on_weekends, setOpenOnWeekends] = useState(true); 24 | const [images, setImages] = useState([]); 25 | const [previewImages, setPreviewImages] = useState([]) 26 | 27 | function handleMapClick(event:LeafletMouseEvent){ 28 | const {lat, lng} = event.latlng 29 | setPosition({ 30 | latitude: lat, 31 | longitude:lng, 32 | }) 33 | } 34 | async function handleSubmit(event: FormEvent){ 35 | event.preventDefault(); 36 | 37 | const {latitude, longitude} = position; 38 | 39 | const data= new FormData(); 40 | 41 | data.append('name', name); 42 | data.append('about', about); 43 | data.append('latitude', String(latitude)); 44 | data.append('longitude',String(longitude)); 45 | data.append('instructions', instructions); 46 | data.append('opening_hours', opening_hours); 47 | data.append('open_on_weekends', String(open_on_weekends)); 48 | 49 | images.forEach(image=>{ 50 | data.append('images', image); 51 | }) 52 | 53 | await api.post('shelters', data); 54 | 55 | alert('Cadastro realizado com sucesso!') 56 | 57 | history.push('/app'); 58 | 59 | } 60 | 61 | 62 | function handleSelectImages(event: ChangeEvent) { 63 | if (!event.target.files) { 64 | return 65 | } 66 | 67 | const selectedImages = Array.from(event.target.files) 68 | 69 | setImages(selectedImages); 70 | 71 | const selectedImagesPreview = selectedImages.map(image => { 72 | return URL.createObjectURL(image); 73 | }); 74 | 75 | setPreviewImages(selectedImagesPreview); 76 | } 77 | 78 | 79 | return ( 80 |
81 | 82 | 83 |
84 |
85 |
86 | Dados 87 | 88 | 94 | 97 | 98 | {position.latitude !== 0 && } 99 | 100 | 101 | 102 |
103 | 104 | setName(event.target.value)} /> 108 |
109 | 110 |
111 | 112 |