",
15 | "homepage": "https://github.com/DanielObara/SemanaOmnistack11#readme",
16 | "dependencies": {
17 | "axios": "^0.19.2",
18 | "celebrate": "^12.0.1",
19 | "cors": "^2.8.5",
20 | "cross-env": "^7.0.2",
21 | "express": "^4.17.1",
22 | "knex": "^0.20.13",
23 | "sqlite3": "^4.1.1"
24 | },
25 | "devDependencies": {
26 | "eslint": "^6.8.0",
27 | "eslint-config-airbnb-base": "^14.1.0",
28 | "eslint-config-prettier": "^6.10.1",
29 | "eslint-plugin-import": "^2.20.2",
30 | "eslint-plugin-prettier": "^3.1.2",
31 | "jest": "^25.2.4",
32 | "prettier": "^2.0.2",
33 | "nodemon": "^2.0.2",
34 | "supertest": "^4.0.2"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/mobile/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "node_modules/expo/AppEntry.js",
3 | "scripts": {
4 | "start": "expo start",
5 | "android": "expo start --android",
6 | "ios": "expo start --ios",
7 | "web": "expo start --web",
8 | "eject": "expo eject"
9 | },
10 | "dependencies": {
11 | "@react-native-community/masked-view": "0.1.5",
12 | "@react-navigation/native": "^5.1.3",
13 | "@react-navigation/stack": "^5.2.7",
14 | "axios": "^0.19.2",
15 | "expo": "~36.0.0",
16 | "expo-constants": "^9.0.0",
17 | "expo-mail-composer": "^8.1.0",
18 | "intl": "^1.2.5",
19 | "react": "~16.9.0",
20 | "react-dom": "~16.9.0",
21 | "react-native": "https://github.com/expo/react-native/archive/sdk-36.0.0.tar.gz",
22 | "react-native-gesture-handler": "~1.5.0",
23 | "react-native-reanimated": "~1.4.0",
24 | "react-native-safe-area-context": "0.6.0",
25 | "react-native-screens": "2.0.0-alpha.12",
26 | "react-native-web": "~0.11.7"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "^7.0.0",
30 | "babel-preset-expo": "~8.0.0"
31 | },
32 | "private": true
33 | }
34 |
--------------------------------------------------------------------------------
/frontend/src/pages/Register/styles.css:
--------------------------------------------------------------------------------
1 | .register-container {
2 | width: 100%;
3 | max-width: 1120px;
4 | height: 100vh;
5 | margin: 0 auto;
6 | display: flex;
7 | align-items: center;
8 | justify-content: center;
9 | }
10 |
11 | .register-container .content {
12 | width: 100%;
13 | padding: 96px;
14 | background: #f0f0f5;
15 | box-shadow: 0 0 100px rgba(0, 0, 0, 0.1);
16 | display: flex;
17 | justify-content: space-between;
18 | align-items: center;
19 | }
20 |
21 | .register-container .content section {
22 | width: 100%;
23 | max-width: 380px;
24 | }
25 |
26 | .register-container .content section h1 {
27 | margin: 64px 0 32px;
28 | font-size: 32px;
29 | }
30 |
31 | .register-container .content section p {
32 | font-size: 18px;
33 | color: #737380;
34 | line-height: 32px;
35 | }
36 |
37 | .register-container .content form {
38 | width: 100%;
39 | max-width: 450px;
40 | }
41 |
42 | .register-container .content form input {
43 | margin-top: 8px;
44 | }
45 |
46 | .register-container .content .input-group {
47 | display: flex;
48 | }
49 |
50 | .register-container .content .input-group input+input {
51 | margin-left: 8px;
52 | }
--------------------------------------------------------------------------------
/backend/knexfile.js:
--------------------------------------------------------------------------------
1 | // Update with your config settings.
2 |
3 | module.exports = {
4 | development: {
5 | client: "sqlite3",
6 | connection: {
7 | filename: "./src/database/db.sqlite"
8 | },
9 | migrations: {
10 | directory: "./src/database/migrations"
11 | },
12 | useNullAsDefault: true
13 | },
14 |
15 | test: {
16 | client: "sqlite3",
17 | connection: {
18 | filename: "./src/database/test.sqlite"
19 | },
20 | migrations: {
21 | directory: "./src/database/migrations"
22 | },
23 | useNullAsDefault: true
24 | },
25 |
26 | staging: {
27 | client: "postgresql",
28 | connection: {
29 | database: "my_db",
30 | user: "username",
31 | password: "password"
32 | },
33 | pool: {
34 | min: 2,
35 | max: 10
36 | },
37 | migrations: {
38 | tableName: "knex_migrations"
39 | }
40 | },
41 |
42 | production: {
43 | client: "postgresql",
44 | connection: {
45 | database: "my_db",
46 | user: "username",
47 | password: "password"
48 | },
49 | pool: {
50 | min: 2,
51 | max: 10
52 | },
53 | migrations: {
54 | tableName: "knex_migrations"
55 | }
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/mobile/src/pages/Detail/styles.js:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet
3 | }
4 |
5 | from "react-native";
6 | import Constants from "expo-constants";
7 | export default StyleSheet.create( {
8 | container: {
9 | flex: 1, paddingHorizontal: 24, paddingTop: Constants.statusBarHeight + 20
10 | }
11 | , header: {
12 | flexDirection: "row", justifyContent: "space-between", alignItems: "center"
13 | }
14 | , incident: {
15 | padding: 24, borderRadius: 8, backgroundColor: "#FFF", marginBottom: 16, marginTop: 48
16 | }
17 | , incidentProperty: {
18 | fontSize: 14, color: "#41414d", fontWeight: "bold", marginTop: 24
19 | }
20 | , incidentValue: {
21 | marginTop: 8, fontSize: 15, color: "#737380"
22 | }
23 | , contactBox: {
24 | padding: 24, borderRadius: 8, backgroundColor: "#FFF", marginBottom: 16
25 | }
26 | , heroTitle: {
27 | fontWeight: "bold", fontSize: 20, color: "#13131a", lineHeight: 30
28 | }
29 | , heroDescription: {
30 | fontSize: 15, color: "#737380", marginTop: 16
31 | }
32 | , actions: {
33 | marginTop: 16, flexDirection: "row", justifyContent: "space-between"
34 | }
35 | , action: {
36 | backgroundColor: "#e02041", borderRadius: 8, height: 50, width: "48%", justifyContent: "center", alignItems: "center"
37 | }
38 | , actionText: {
39 | color: "#FFF", fontSize: 15, fontWeight: "bold"
40 | }
41 | }
42 |
43 | );
--------------------------------------------------------------------------------
/mobile/src/pages/Incidents/styles.js:
--------------------------------------------------------------------------------
1 | import { StyleSheet } from "react-native";
2 | import Constants from "expo-constants";
3 |
4 | export default StyleSheet.create({
5 | container: {
6 | flex: 1,
7 | paddingHorizontal: 24,
8 | paddingTop: Constants.statusBarHeight + 20
9 | },
10 |
11 | header: {
12 | flexDirection: "row",
13 | justifyContent: "space-between",
14 | alignItems: "center"
15 | },
16 |
17 | headerText: {
18 | fontSize: 15,
19 | color: "#737380"
20 | },
21 |
22 | headerTextBold: {
23 | fontWeight: "bold"
24 | },
25 |
26 | title: {
27 | fontSize: 30,
28 | marginBottom: 16,
29 | marginTop: 48,
30 | color: "#13131a",
31 | fontWeight: "bold"
32 | },
33 |
34 | description: {
35 | fontSize: 16,
36 | lineHeight: 24,
37 | color: "#737380"
38 | },
39 |
40 | incidentList: {
41 | marginTop: 32
42 | },
43 |
44 | incident: {
45 | padding: 24,
46 | borderRadius: 8,
47 | backgroundColor: "#FFF",
48 | marginBottom: 16
49 | },
50 |
51 | incidentProperty: {
52 | fontSize: 14,
53 | color: "#41414d",
54 | fontWeight: "bold"
55 | },
56 |
57 | incidentValue: {
58 | marginTop: 8,
59 | fontSize: 15,
60 | marginBottom: 24,
61 | color: "#737380"
62 | },
63 |
64 | detailsButton: {
65 | flexDirection: "row",
66 | justifyContent: "space-between",
67 | alignItems: "center"
68 | },
69 |
70 | detailsButtonText: {
71 | color: "#e02041",
72 | fontSize: 15,
73 | fontWeight: "bold"
74 | }
75 | });
76 |
--------------------------------------------------------------------------------
/frontend/src/global.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | outline: 0;
6 | box-sizing: border-box;
7 | }
8 |
9 | body {
10 | font: 400 14px Roboto, sans-serif;
11 | background: #f0f0f5;
12 | -webkit-font-smoothing: antialiased;
13 | }
14 |
15 | input, button, textarea {
16 | font: 400 18px Roboto, sans-serif;
17 | }
18 |
19 | button {
20 | cursor: pointer;
21 | }
22 |
23 | form input {
24 | width: 100%;
25 | height: 60px;
26 | color: #333;
27 | border: 1px solid #dcdce6;
28 | border-radius: 8px;
29 | padding: 0 24px;
30 | }
31 |
32 | form textarea {
33 | width: 100%;
34 | height: 140px;
35 | color: #333;
36 | border: 1px solid #dcdce6;
37 | border-radius: 8px;
38 | padding: 16px 24px;
39 | line-height: 24px;
40 | }
41 |
42 | .button {
43 | width: 100%;
44 | height: 60px;
45 | background: #e02041;
46 | border: 0;
47 | border-radius: 8px;
48 | color: #fff;
49 | font-weight: 700;
50 | margin-top: 16px;
51 | display: inline-block;
52 | text-align: center;
53 | text-decoration: none;
54 | font-size: 18px;
55 | line-height: 60px;
56 | transition: ease 0.2s;
57 | }
58 |
59 | .button:hover {
60 | filter: brightness(90%);
61 | }
62 |
63 | .back-link {
64 | display: flex;
65 | align-items: center;
66 | margin-top: 40px;
67 | color: #41414d;
68 | font-size: 18px;
69 | text-decoration: none;
70 | font-weight: 500;
71 | transition: opacity 0.2s;
72 | }
73 |
74 | .back-link svg {
75 | margin-right: 8px;
76 | }
77 |
78 | .back-link:hover {
79 | opacity: 0.8;
80 | }
--------------------------------------------------------------------------------
/frontend/src/pages/Profile/styles.css:
--------------------------------------------------------------------------------
1 | .profile-container {
2 | width: 100%;
3 | max-width: 1180px;
4 | padding: 0 30px;
5 | margin: 32px auto;
6 | }
7 |
8 | .profile-container header {
9 | display: flex;
10 | align-items: center;
11 | }
12 |
13 | .profile-container header span {
14 | font-size: 20px;
15 | margin-left: 24px;
16 | }
17 |
18 | .profile-container header a {
19 | width: 260px;
20 | margin-left: auto;
21 | margin-top: 0;
22 | }
23 |
24 | .profile-container header button {
25 | width: 60px;
26 | height: 60px;
27 | border-radius: 4px;
28 | border: 1px solid #dcdce6;
29 | background: transparent;
30 | margin-left: 16px;
31 | }
32 |
33 | .profile-container header button:hover {
34 | border-color: #999;
35 | }
36 |
37 | .profile-container h1 {
38 | margin-top: 80px;
39 | margin-bottom: 24px;
40 | }
41 |
42 | .profile-container ul {
43 | display: grid;
44 | grid-template-columns: repeat(2, 1fr);
45 | grid-gap: 24px;
46 | list-style: none;
47 | }
48 |
49 | .profile-container ul li {
50 | background: #fff;
51 | padding: 24px;
52 | border-radius: 8px;
53 | position: relative;
54 | }
55 |
56 | .profile-container ul li button {
57 | position: absolute;
58 | right: 24px;
59 | top: 24px;
60 | border: 0;
61 | }
62 |
63 | .profile-container ul li button:hover {
64 | opacity: 0.8;
65 | }
66 |
67 | .profile-container ul li strong {
68 | display: block;
69 | margin-bottom: 16px;
70 | color: #41414d;
71 | }
72 |
73 | .profile-container ul li p+strong {
74 | margin-top: 32px;
75 | }
76 |
77 | .profile-container ul li p {
78 | color: #737380;
79 | line-height: 21px;
80 | font-size: 16px;
81 | }
--------------------------------------------------------------------------------
/frontend/src/pages/Logon/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useHistory } from "react-router-dom";
3 | import { FiLogIn } from "react-icons/fi";
4 |
5 | import heroesImg from "../../assets/heroes.png";
6 | import logoImg from "../../assets/logo.svg";
7 | import api from "../../services/api";
8 |
9 | import "./styles.css";
10 |
11 | export default function Logon() {
12 | const [id, setId] = useState("");
13 |
14 | const history = useHistory();
15 |
16 | async function handleLogin(e) {
17 | e.preventDefault();
18 |
19 | try {
20 | const response = await api.post("/sessions", { id });
21 |
22 | localStorage.setItem("ongId", id);
23 | localStorage.setItem("ongName", response.data.name);
24 |
25 | history.push("/profile");
26 | } catch (err) {
27 | console.log(err);
28 | alert("Falha no login, tente novamente.");
29 | }
30 | }
31 | return (
32 |
33 |
34 |
35 |
36 |
52 |
53 |
54 |
55 | );
56 | }
57 |
--------------------------------------------------------------------------------
/frontend/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/backend/src/controllers/IncidentController.js:
--------------------------------------------------------------------------------
1 | const connection = require('../database/connection')
2 |
3 | module.exports = {
4 | async index(request, response) {
5 | const { page = 1 } = request.query
6 | const [count] = await connection('incidents').count()
7 | const incidents = await connection('incidents')
8 | .join('ongs', 'ongs.id', '=', 'incidents.ong_id')
9 | .limit(5)
10 | .offset((page - 1) * 5)
11 | .select([
12 | 'incidents.*',
13 | 'ongs.name',
14 | 'ongs.email',
15 | 'ongs.whatsapp',
16 | 'ongs.city',
17 | 'ongs.uf',
18 | ])
19 |
20 | response.header('X-Total-Count', count['count(*)'])
21 | return response.json(incidents)
22 | },
23 |
24 | async show(request, response) {
25 | const { id } = request.params
26 | const incident = await connection('incidents').where('id', id).first()
27 |
28 | if (!incident) {
29 | return response.status(404).json({
30 | error: {
31 | message: 'Incident not found',
32 | },
33 | })
34 | }
35 |
36 | return response.json(incident)
37 | },
38 |
39 | async create(request, response) {
40 | const { title, description, value } = request.body
41 |
42 | const ong_id = request.headers.authorization
43 |
44 | const [id] = await connection('incidents').insert({
45 | title,
46 | description,
47 | value,
48 | ong_id,
49 | })
50 |
51 | return response.json({ id })
52 | },
53 |
54 | async delete(request, response) {
55 | const { id } = request.params
56 |
57 | const ong_id = request.headers.authorization
58 |
59 | const incident = await connection('incidents')
60 | .where('id', id)
61 | .select('ong_id')
62 | .first()
63 |
64 | if (!incident) response.status(404).json({ error: 'Incident not found' })
65 |
66 | if (incident.ong_id !== ong_id)
67 | response.status(401).json({ error: 'Operation not permitted' })
68 |
69 | await connection('incidents').where('id', id).delete()
70 |
71 | return response
72 | .status(200)
73 | .send({ msg: 'Incident was successfully deleted' })
74 | },
75 | }
76 |
--------------------------------------------------------------------------------
/frontend/src/pages/NewIncident/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useHistory } from "react-router-dom";
3 | import { FiArrowLeft } from "react-icons/fi";
4 |
5 | import api from "../../services/api";
6 |
7 | import "./styles.css";
8 |
9 | import logoImg from "../../assets/logo.svg";
10 |
11 | export default function NewIncident() {
12 | const [title, setTitle] = useState("");
13 | const [description, setDescription] = useState("");
14 | const [value, setValue] = useState("");
15 |
16 | const ongId = localStorage.getItem("ongId");
17 |
18 | const history = useHistory();
19 |
20 | async function handleNewIncident(e) {
21 | e.preventDefault();
22 |
23 | const data = {
24 | title,
25 | description,
26 | value
27 | };
28 |
29 | try {
30 | await api.post("/incidents", data, {
31 | headers: { Authorization: ongId }
32 | });
33 |
34 | history.push("/profile");
35 | } catch (err) {
36 | console.log(err);
37 | alert("Erro no cadastro, tente novamente");
38 | }
39 | }
40 | return (
41 |
42 |
43 |
44 |
45 | Cadastrar novo caso
46 |
47 | Descreva o caso detalhadamente para encontrar um herói para resolver
48 | isso.
49 |
50 |
51 |
52 |
53 | Voltar para home
54 |
55 |
56 |
57 |
78 |
79 |
80 | );
81 | }
--------------------------------------------------------------------------------
/frontend/src/pages/Profile/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import logoImg from "../../assets/logo.svg";
3 | import { Link, useHistory } from "react-router-dom";
4 | import { FiPower, FiTrash2 } from "react-icons/fi";
5 | import api from "../../services/api";
6 | import "./styles.css";
7 |
8 | export default function Profile() {
9 | const [incidents, setIncidents] = useState([]);
10 |
11 | const ongName = localStorage.getItem("ongName");
12 | const ongId = localStorage.getItem("ongId");
13 |
14 | const history = useHistory();
15 |
16 | useEffect(() => {
17 | api
18 | .get("/profile", {
19 | headers: { Authorization: ongId }
20 | })
21 | .then(response => {
22 | setIncidents(response.data);
23 | });
24 | }, [ongId]);
25 |
26 | async function handleDeleteIncident(id) {
27 | try {
28 | await api.delete(`incidents/${id}`, {
29 | headers: {
30 | Authorization: ongId
31 | }
32 | });
33 |
34 | setIncidents(incidents.filter(incident => incident.id !== id));
35 | } catch (err) {
36 | alert("Erro ao deletar o caso");
37 | }
38 | }
39 |
40 | function handleLogout() {
41 | localStorage.clear();
42 | history.push("/");
43 | }
44 | return (
45 |
46 |
47 |
48 | Bem vinda, {ongName}
49 |
50 |
51 | Cadastrar novo caso
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
Casos cadastrados
60 |
61 |
87 |
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/mobile/README.md:
--------------------------------------------------------------------------------
1 |
2 | :iphone: Mobile :iphone:
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 | Technologies |
31 | Project |
32 | Layout |
33 | How to contribute |
34 | License
35 |
36 |
37 |
38 |
39 |
40 | ## :rocket: Technologies
41 |
42 | This project was developed with the following technologies:
43 |
44 | - [Node.js](https://nodejs.org/en/)
45 | - [React](https://reactjs.org)
46 | - [React Native](https://facebook.github.io/react-native/)
47 | - [Expo](https://expo.io/)
48 |
49 | ## 💻 Project
50 |
51 | Be The Hero is a project that aims to connect people who are willing to help ONGs.
52 |
53 | ## 🔖 Layout
54 |
55 | Para acessar o layout utilize a ferramenta [Figma](https://www.figma.com/file/2C2yvw7jsCOGmaNUDftX9n/Be-The-Hero---OmniStack-11?node-id=0%3A1).
56 |
57 | ## 🤔 How to contribute
58 |
59 | - Make a fork;
60 | - Create a branck with your feature: `git checkout -b my-feature`;
61 | - Commit changes: `git commit -m 'feat: My new feature'`;
62 | - Make a push to your branch: `git push origin my-feature`.
63 |
64 | After merging your receipt request to done, you can delete a branch from yours.
65 |
66 | ## :memo: License
67 |
68 | This project is under the MIT license. See the [LICENSE](LICENSE.md) for details.
69 |
70 | ---
71 |
72 | Made with ♥ by Daniel Obara :wave: [Get in touch!](https://www.linkedin.com/in/danielobara/)
73 |
--------------------------------------------------------------------------------
/frontend/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | :computer: Desktop Preview :computer:
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 | Technologies |
31 | Project |
32 | Layout |
33 | How to contribute |
34 | License
35 |
36 |
37 |
38 |
39 |
40 | ## :rocket: Technologies
41 |
42 | This project was developed with the following technologies:
43 |
44 | - [Node.js](https://nodejs.org/en/)
45 | - [React](https://reactjs.org)
46 | - [React Native](https://facebook.github.io/react-native/)
47 | - [Expo](https://expo.io/)
48 |
49 | ## 💻 Project
50 |
51 | Be The Hero is a project that aims to connect people who are willing to help ONGs.
52 |
53 | ## 🔖 Layout
54 |
55 | Para acessar o layout utilize a ferramenta [Figma](https://www.figma.com/file/2C2yvw7jsCOGmaNUDftX9n/Be-The-Hero---OmniStack-11?node-id=0%3A1).
56 |
57 | ## 🤔 How to contribute
58 |
59 | - Make a fork;
60 | - Create a branck with your feature: `git checkout -b my-feature`;
61 | - Commit changes: `git commit -m 'feat: My new feature'`;
62 | - Make a push to your branch: `git push origin my-feature`.
63 |
64 | After merging your receipt request to done, you can delete a branch from yours.
65 |
66 | ## :memo: License
67 |
68 | This project is under the MIT license. See the [LICENSE](LICENSE.md) for details.
69 |
70 | ---
71 |
72 | Made with ♥ by Daniel Obara :wave: [Get in touch!](https://www.linkedin.com/in/danielobara/)
73 |
--------------------------------------------------------------------------------
/frontend/src/pages/Register/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useHistory } from "react-router-dom";
3 | import { FiArrowLeft } from "react-icons/fi";
4 |
5 | import "./styles.css";
6 |
7 | import api from "../../services/api";
8 |
9 | import logoImg from "../../assets/logo.svg";
10 |
11 | export default function Register() {
12 | const [name, setName] = useState("");
13 | const [email, setEmail] = useState("");
14 | const [whatsapp, setWhatsapp] = useState("");
15 | const [city, setCity] = useState("");
16 | const [uf, setUf] = useState("");
17 |
18 | const history = useHistory();
19 |
20 | async function handleRegister(e) {
21 | e.preventDefault();
22 |
23 | const data = {
24 | name,
25 | email,
26 | whatsapp,
27 | city,
28 | uf
29 | };
30 |
31 | try {
32 | const response = await api.post("/ongs", data);
33 |
34 | alert(`Seu ID de acesso: ${response.data.id}`);
35 |
36 | history.push("/");
37 | } catch (err) {
38 | console.log(err);
39 | alert("Erro no cadastro, tente novamente");
40 | }
41 | }
42 |
43 | return (
44 |
97 | );
98 | }
99 |
--------------------------------------------------------------------------------
/mobile/src/pages/Detail/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Feather } from "@expo/vector-icons";
3 | import { useNavigation, useRoute } from "@react-navigation/native";
4 | import { View, Text, Image, TouchableOpacity, Linking } from "react-native";
5 | import * as MailComposer from "expo-mail-composer";
6 |
7 | import logoImg from "../../assets/logo.png";
8 |
9 | import styles from "./styles";
10 |
11 | export default function Detail() {
12 | const navigation = useNavigation();
13 | const route = useRoute();
14 |
15 | const incident = route.params.incident;
16 | const message = `Olá ${
17 | incident.name
18 | }, estou entrando em contato pois gostaria de ajudar no caso "${
19 | incident.title
20 | }" com o valor de ${Intl.NumberFormat("pt-BR", {
21 | style: "currency",
22 | currency: "BRL"
23 | }).format(incident.value)}`;
24 |
25 | function navigateBack() {
26 | navigation.goBack();
27 | }
28 |
29 | function sendMail() {
30 | MailComposer.composeAsync({
31 | subject: `Herói do caso: ${incident.title}`,
32 | recipients: [incident.email],
33 | body: message
34 | });
35 | }
36 |
37 | function sendWhatsapp() {
38 | Linking.openURL(
39 | `whatsapp://send?phone=${incident.whatsapp}&text=${message}`
40 | );
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | ONG:
55 |
56 | {incident.name} de {incident.city}/{incident.uf}
57 |
58 |
59 | CASO:
60 | {incident.title}
61 |
62 | VALOR:
63 |
64 | {Intl.NumberFormat("pt-BR", {
65 | style: "currency",
66 | currency: "BRL"
67 | }).format(incident.value)}
68 |
69 |
70 |
71 |
72 | Salve o dia!
73 | Seja o herói desse caso.
74 |
75 | Entre em contato:
76 |
77 |
78 |
79 | WhatsApp
80 |
81 |
82 |
83 | E-mail
84 |
85 |
86 |
87 |
88 | );
89 | }
90 |
--------------------------------------------------------------------------------
/mobile/src/pages/Incidents/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Feather } from "@expo/vector-icons";
3 | import { useNavigation } from "@react-navigation/native";
4 | import { View, FlatList, Image, Text, TouchableOpacity } from "react-native";
5 |
6 | import api from "../../services/api";
7 |
8 | import logoImg from "../../assets/logo.png";
9 |
10 | import styles from "./styles";
11 |
12 | export default function Incidents() {
13 | const [incidents, setIncidents] = useState([]);
14 | const [total, setTotal] = useState(0);
15 |
16 | const [page, setPage] = useState(1);
17 | const [loading, setLoading] = useState(false);
18 |
19 | const navigation = useNavigation();
20 |
21 | function navigateToDetail(incident) {
22 | navigation.navigate("Detail", { incident });
23 | }
24 |
25 | async function loadIncidents() {
26 | if (loading) {
27 | return;
28 | }
29 |
30 | if (total > 0 && incidents.length === total) {
31 | return;
32 | }
33 |
34 | setLoading(true);
35 |
36 | const response = await api.get("incidents", {
37 | params: { page }
38 | });
39 |
40 | setIncidents([...incidents, ...response.data]);
41 | setTotal(response.headers["x-total-count"]);
42 | setPage(page + 1);
43 | setLoading(false);
44 | }
45 |
46 | useEffect(() => {
47 | loadIncidents();
48 | }, []);
49 |
50 | return (
51 |
52 |
53 |
54 |
55 | Total de {total} casos .
56 |
57 |
58 |
59 | Bem-vindo!
60 |
61 | Escolha um dos casos abaixo e salve o dia.
62 |
63 |
64 | String(incident.id)}
68 | // showsVerticalScrollIndicator={false}
69 | onEndReached={loadIncidents}
70 | onEndReachedThreshold={0.2}
71 | renderItem={({ item: incident }) => (
72 |
73 | ONG:
74 | {incident.name}
75 |
76 | CASO:
77 | {incident.title}
78 |
79 | VALOR:
80 |
81 | {Intl.NumberFormat("pt-BR", {
82 | style: "currency",
83 | currency: "BRL"
84 | }).format(incident.value)}
85 |
86 |
87 | navigateToDetail(incident)}
90 | >
91 | Ver mais detalhes
92 |
93 |
94 |
95 | )}
96 | />
97 |
98 | );
99 | }
100 |
--------------------------------------------------------------------------------
/.github/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/frontend/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | :heavy_check_mark: 🚀 Semana OmniStack 11.0 :heavy_check_mark:
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 | Project |
34 | Technologies |
35 | Layout |
36 | How to use |
37 | How to contribute |
38 | License
39 |
40 |
41 | ## 💻 Project
42 |
43 | Be The Hero is a project that aims to connect people who are willing to help ONGs.
44 |
45 |
46 |
47 |
48 |
49 |
50 | ## :rocket: Technologies
51 |
52 | This project was developed with the following technologies:
53 |
54 | - [Node.js](https://nodejs.org/en/)
55 | - [React](https://reactjs.org)
56 | - [React Native](https://facebook.github.io/react-native/)
57 | - [Expo](https://expo.io/)
58 |
59 | ## 🔖 Layout
60 |
61 | Para acessar o layout utilize a ferramenta [Figma](https://www.figma.com/file/2C2yvw7jsCOGmaNUDftX9n/Be-The-Hero---OmniStack-11?node-id=0%3A1).
62 |
63 | ## :information_source: How To Use
64 |
65 | To clone and run this application, you'll need [Git](https://git-scm.com), [Node.js][nodejs] + [Yarn][yarn] installed on your computer.
66 |
67 | From your command line:
68 |
69 | ### Install API
70 | ```bash
71 | # Clone this repository
72 | $ git clone https://github.com/DanielObara/SemanaOmnistack11
73 |
74 | # Go into the repository
75 | $ cd SemanaOmnistack11/backend
76 |
77 | # Install dependencies
78 | $ yarn install
79 |
80 | # Run Migrates
81 | $ yarn knex migrate:latest
82 |
83 | # Run Seeds
84 | $ yarn seed
85 |
86 | # Run the API
87 | $ yarn dev
88 |
89 | # Run tests
90 | $ yarn test
91 | ```
92 |
93 | ## 🤔 How to contribute
94 |
95 | - Make a fork;
96 | - Create a branck with your feature: `git checkout -b my-feature`;
97 | - Commit changes: `git commit -m 'feat: My new feature'`;
98 | - Make a push to your branch: `git push origin my-feature`.
99 |
100 | After merging your receipt request to done, you can delete a branch from yours.
101 |
102 | ## :memo: License
103 |
104 | This project is under the MIT license. See the [LICENSE](LICENSE.md) for details.
105 |
106 | ---
107 |
108 | Made with ♥ by Daniel Obara :wave: [Get in touch!](https://www.linkedin.com/in/danielobara/)
109 |
110 | [nodejs]: https://nodejs.org/
111 | [yarn]: https://yarnpkg.com/
112 | [vc]: https://code.visualstudio.com/
113 | [vceditconfig]: https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig
114 | [vceslint]: https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint
115 | [prettier]: https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
116 |
--------------------------------------------------------------------------------
/Insomnia_2020-03-24.json:
--------------------------------------------------------------------------------
1 | {"_type":"export","__export_format":4,"__export_date":"2020-03-24T20:30:29.017Z","__export_source":"insomnia.desktop.app:v7.0.6","resources":[{"_id":"req_d0345eab87d248b3813eb14692e78b45","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"id\": \"{% response 'body', 'req_aa29252a9d734a159bf98fccbf8e9273', 'b64::JFswXS5pZA==::46b', 'always' %}\"\n}"},"created":1585081027899,"description":"","headers":[{"id":"pair_c87321b3563e4c19954c8eb6f97cdc9d","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1583143283732.25,"method":"POST","modified":1585081268697,"name":"LOGIN","parameters":[],"parentId":"fld_e1ca9190c8e2443faf1d8a2c71607aaf","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"fld_e1ca9190c8e2443faf1d8a2c71607aaf","created":1585081103166,"description":"","environment":{"resource":"sessions"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1585081103166,"modified":1585081148396,"name":"Session","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"request_group"},{"_id":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","created":1585062435907,"description":"","modified":1585062435907,"name":"Omnistack11","parentId":null,"_type":"workspace"},{"_id":"req_a79db039374845ababc0e18f75563e75","authentication":{},"body":{},"created":1585080808128,"description":"","headers":[{"description":"","id":"pair_b352871c9011427099a8b24e504fc564","name":"Authorization","value":"{% response 'body', 'req_aa29252a9d734a159bf98fccbf8e9273', 'b64::JFswXS5pZA==::46b', 'always' %}"}],"isPrivate":false,"metaSortKey":-1583143283744.75,"method":"GET","modified":1585080888862,"name":"ONG INCIDENT","parameters":[],"parentId":"fld_2e370ec4855740dca099dda9bb6d0c20","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"fld_2e370ec4855740dca099dda9bb6d0c20","created":1585080782862,"description":"","environment":{"resource":"profile"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1585080782862,"modified":1585080828734,"name":"Profile","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"request_group"},{"_id":"req_d5e2810637e14731b6fa04b84e6f9899","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"name\": \"{% uuid 'v4' %}\",\n\t\"email\": \"{% uuid 'v4' %}\",\n\t\"whatsapp\": \"00000000000\",\n\t\"city\": \"Okazaki\",\n\t\"uf\": \"JP\"\n}"},"created":1585062497139,"description":"","headers":[{"id":"pair_5c7aa08cb14f46d39cb2f81dcd858c6d","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1585062497139,"method":"POST","modified":1585068392862,"name":"CREATE ONGs","parameters":[],"parentId":"fld_e42ab4eac4054b67b9ce38d13ace98f1","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"fld_e42ab4eac4054b67b9ce38d13ace98f1","created":1585062482437,"description":"","environment":{"resource":"ongs"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1585062482437,"modified":1585068383888,"name":"ONGS","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"request_group"},{"_id":"req_cdf8a55cbfcd48a49e9e72f1914c7fc2","authentication":{},"body":{"mimeType":"application/json","text":""},"created":1585062506735,"description":"","headers":[{"id":"pair_5c7aa08cb14f46d39cb2f81dcd858c6d","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1583783021559.5,"method":"PUT","modified":1585062539096,"name":"UPDATE ONGs","parameters":[],"parentId":"fld_e42ab4eac4054b67b9ce38d13ace98f1","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"","_type":"request"},{"_id":"req_aa29252a9d734a159bf98fccbf8e9273","authentication":{},"body":{},"created":1585062521339,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1583143283769.75,"method":"GET","modified":1585074750961,"name":"LIST ONGs","parameters":[],"parentId":"fld_e42ab4eac4054b67b9ce38d13ace98f1","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"req_396fff25bbf14abd8ba628b392f7ddca","authentication":{},"body":{"mimeType":"application/json","text":"{\n\t\"description\": \"asdfasfdasfsafsafd\",\n\t\"title\": \"teste\",\n\t\"value\": 150\n}"},"created":1585074565727,"description":"","headers":[{"description":"","id":"pair_0a2091863ee74596b440d627e3e1210d","name":"Content-Type","value":"application/json"},{"description":"","id":"pair_73aeb301f8ad4fa793eec5270ddcd114","name":"authorization","value":"d8e081b52301a558c58ccb10a5b0c8c1"}],"isPrivate":false,"metaSortKey":-1585062497139,"method":"POST","modified":1585075984359,"name":"CREATE INCIDENT","parameters":[],"parentId":"fld_7f57682471874b83891fc6b795da66f9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}","_type":"request"},{"_id":"fld_7f57682471874b83891fc6b795da66f9","created":1585074565722,"description":"","environment":{"resource":"incidents"},"environmentPropertyOrder":{"&":["resource"]},"metaSortKey":-1583308336635.5,"modified":1585074617705,"name":"Incidents","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"request_group"},{"_id":"req_baacde76f7354989a8f33b9657a98aee","authentication":{},"body":{"mimeType":"application/json","text":""},"created":1585074565732,"description":"","headers":[{"id":"pair_5c7aa08cb14f46d39cb2f81dcd858c6d","name":"Content-Type","value":"application/json"}],"isPrivate":false,"metaSortKey":-1583783021559.5,"method":"PUT","modified":1585074586196,"name":"UPDATE INCIDENT","parameters":[],"parentId":"fld_7f57682471874b83891fc6b795da66f9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"","_type":"request"},{"_id":"req_a2f88e69acd548d48811d4f9cf0a218f","authentication":{},"body":{},"created":1585074565738,"description":"","headers":[],"isPrivate":false,"metaSortKey":-1583143283769.75,"method":"GET","modified":1585081418280,"name":"LIST INCIDENTS","parameters":[],"parentId":"fld_7f57682471874b83891fc6b795da66f9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}?page=1","_type":"request"},{"_id":"req_ee96276120f7497fa4aa9a82fa05b238","authentication":{},"body":{},"created":1585080404689,"description":"","headers":[{"description":"","id":"pair_73aeb301f8ad4fa793eec5270ddcd114","name":"authorization","value":"d8e081b52301a558c58ccb10a5b0c8c1"}],"isPrivate":false,"metaSortKey":-1583143283719.75,"method":"DELETE","modified":1585080539923,"name":"DELETE INCIDENT","parameters":[],"parentId":"fld_7f57682471874b83891fc6b795da66f9","settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingFollowRedirects":"global","settingRebuildPath":true,"settingSendCookies":true,"settingStoreCookies":true,"url":"{{ base_url }}/{{ resource }}/{% response 'body', 'req_396fff25bbf14abd8ba628b392f7ddca', 'b64::JC5pZA==::46b', 'always' %}","_type":"request"},{"_id":"env_74b77e3a1f681f1a92efee92e9d013f6bfdbddfe","color":null,"created":1585062435955,"data":{"base_url":"http://localhost:3333"},"dataPropertyOrder":{"&":["base_url"]},"isPrivate":false,"metaSortKey":1585062435955,"modified":1585062462125,"name":"Base Environment","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"environment"},{"_id":"jar_74b77e3a1f681f1a92efee92e9d013f6bfdbddfe","cookies":[],"created":1585062435957,"modified":1585062435957,"name":"Default Jar","parentId":"wrk_52f6eb05b6b4497fa2c56e786dc53e16","_type":"cookie_jar"}]}
--------------------------------------------------------------------------------